2024-09-01 16:17:48 -04:00
|
|
|
package orm
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"reflect"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/fatih/structtag"
|
|
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
|
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
|
|
"go.mongodb.org/mongo-driver/mongo"
|
|
|
|
"go.mongodb.org/mongo-driver/mongo/options"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Model - "base" struct for all queryable models
|
|
|
|
type Model struct {
|
|
|
|
// Created time. updated/added automatically.
|
|
|
|
Created time.Time `bson:"createdAt" json:"createdAt"`
|
|
|
|
// Modified time. updated/added automatically.
|
|
|
|
Modified time.Time `bson:"updatedAt" json:"updatedAt"`
|
2024-09-02 19:32:39 -04:00
|
|
|
typeName string `bson:"-"`
|
|
|
|
self any `bson:"-"`
|
2024-09-01 16:17:48 -04:00
|
|
|
exists bool `bson:"-"`
|
|
|
|
}
|
|
|
|
|
2024-09-02 19:32:39 -04:00
|
|
|
// HasID is a simple interface that you must implement
|
|
|
|
// in your models, using a pointer receiver.
|
|
|
|
// This allows for more flexibility in cases where
|
|
|
|
// your ID isn't an ObjectID (e.g., int, uint, string...).
|
2024-09-01 16:17:48 -04:00
|
|
|
//
|
|
|
|
// and yes, those darn ugly ObjectIDs are supported :)
|
|
|
|
type HasID interface {
|
|
|
|
Id() any
|
|
|
|
SetId(id any)
|
|
|
|
}
|
2024-09-02 19:32:39 -04:00
|
|
|
type HasIDSlice []HasID
|
2024-09-01 16:17:48 -04:00
|
|
|
|
|
|
|
type IModel interface {
|
|
|
|
Find(query interface{}, opts ...*options.FindOptions) (*mongo.Cursor, error)
|
|
|
|
FindAll(query interface{}, opts ...*options.FindOptions) (*Query, error)
|
|
|
|
FindByID(id interface{}) (*Query, error)
|
|
|
|
FindOne(query interface{}, options ...*options.FindOneOptions) (*Query, error)
|
|
|
|
FindPaged(query interface{}, page int64, perPage int64, options ...*options.FindOptions) (*Query, error)
|
|
|
|
getColl() *mongo.Collection
|
|
|
|
getIdxs() []*mongo.IndexModel
|
|
|
|
getParsedIdxs() map[string][]InternalIndex
|
|
|
|
Save() error
|
|
|
|
serializeToStore() primitive.M
|
|
|
|
setTypeName(str string)
|
2024-09-02 19:32:39 -04:00
|
|
|
getExists() bool
|
|
|
|
setExists(n bool)
|
2024-09-01 16:17:48 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Model) setTypeName(str string) {
|
|
|
|
m.typeName = str
|
|
|
|
}
|
|
|
|
|
2024-09-02 19:32:39 -04:00
|
|
|
func (m *Model) getExists() bool {
|
|
|
|
return m.exists
|
|
|
|
}
|
|
|
|
func (m *Model) setExists(n bool) {
|
|
|
|
m.exists = n
|
|
|
|
}
|
|
|
|
|
2024-09-01 16:17:48 -04:00
|
|
|
func (m *Model) getColl() *mongo.Collection {
|
|
|
|
_, ri, ok := ModelRegistry.HasByName(m.typeName)
|
|
|
|
if !ok {
|
|
|
|
panic(fmt.Sprintf("the model '%s' has not been registered", m.typeName))
|
|
|
|
}
|
|
|
|
return DB.Collection(ri.Collection)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Model) getIdxs() []*mongo.IndexModel {
|
2024-09-02 19:32:39 -04:00
|
|
|
mi := make([]*mongo.IndexModel, 0)
|
2024-09-01 16:17:48 -04:00
|
|
|
if mpi := m.getParsedIdxs(); mpi != nil {
|
|
|
|
for _, v := range mpi {
|
|
|
|
for _, i := range v {
|
2024-09-02 19:32:39 -04:00
|
|
|
mi = append(mi, buildIndex(i))
|
2024-09-01 16:17:48 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return mi
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Model) getParsedIdxs() map[string][]InternalIndex {
|
|
|
|
_, ri, ok := ModelRegistry.HasByName(m.typeName)
|
|
|
|
if !ok {
|
|
|
|
panic(fmt.Sprintf("model '%s' not registered", m.typeName))
|
|
|
|
}
|
|
|
|
return ri.Indexes
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Model) Find(query interface{}, opts ...*options.FindOptions) (*mongo.Cursor, error) {
|
|
|
|
coll := m.getColl()
|
|
|
|
cursor, err := coll.Find(context.TODO(), query, opts...)
|
|
|
|
return cursor, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Model) FindAll(query interface{}, opts ...*options.FindOptions) (*Query, error) {
|
|
|
|
qqn := ModelRegistry.new_(m.typeName)
|
|
|
|
qqv := reflect.New(reflect.SliceOf(reflect.TypeOf(qqn).Elem()))
|
|
|
|
qqv.Elem().Set(reflect.Zero(qqv.Elem().Type()))
|
|
|
|
qq := &Query{
|
|
|
|
Model: *m,
|
|
|
|
Collection: m.getColl(),
|
|
|
|
doc: qqv.Interface(),
|
|
|
|
Op: OP_FIND_ALL,
|
|
|
|
}
|
|
|
|
q, err := m.Find(query, opts...)
|
|
|
|
|
|
|
|
if err == nil {
|
|
|
|
rawRes := bson.A{}
|
|
|
|
err = q.All(context.TODO(), &rawRes)
|
|
|
|
if err == nil {
|
|
|
|
m.exists = true
|
|
|
|
}
|
|
|
|
qq.rawDoc = rawRes
|
|
|
|
err = q.All(context.TODO(), &qq.doc)
|
|
|
|
if err != nil {
|
|
|
|
qq.reOrganize()
|
|
|
|
err = nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return qq, err
|
|
|
|
}
|
|
|
|
|
2024-09-02 19:32:39 -04:00
|
|
|
func (m *Model) FindPaged(query interface{}, page int64, perPage int64, opts ...*options.FindOptions) (*Query, error) {
|
2024-09-01 16:17:48 -04:00
|
|
|
skipAmt := perPage * (page - 1)
|
|
|
|
if skipAmt < 0 {
|
|
|
|
skipAmt = 0
|
|
|
|
}
|
2024-09-02 19:32:39 -04:00
|
|
|
if len(opts) > 0 {
|
|
|
|
opts[0].SetSkip(skipAmt).SetLimit(perPage)
|
|
|
|
} else {
|
|
|
|
opts = append(opts, options.Find().SetSkip(skipAmt).SetLimit(perPage))
|
2024-09-01 16:17:48 -04:00
|
|
|
}
|
2024-09-02 19:32:39 -04:00
|
|
|
q, err := m.FindAll(query, opts...)
|
2024-09-01 16:17:48 -04:00
|
|
|
q.Op = OP_FIND_PAGED
|
|
|
|
return q, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Model) FindByID(id interface{}) (*Query, error) {
|
|
|
|
return m.FindOne(bson.D{{"_id", id}})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Model) FindOne(query interface{}, options ...*options.FindOneOptions) (*Query, error) {
|
|
|
|
coll := m.getColl()
|
|
|
|
rip := coll.FindOne(context.TODO(), query, options...)
|
|
|
|
raw := bson.M{}
|
|
|
|
err := rip.Decode(&raw)
|
|
|
|
panik(err)
|
|
|
|
m.exists = true
|
|
|
|
qq := &Query{
|
|
|
|
Collection: m.getColl(),
|
|
|
|
rawDoc: raw,
|
|
|
|
doc: ModelRegistry.new_(m.typeName),
|
|
|
|
Op: OP_FIND_ONE,
|
|
|
|
Model: *m,
|
|
|
|
}
|
|
|
|
qq.rawDoc = raw
|
|
|
|
err = rip.Decode(qq.doc)
|
|
|
|
if err != nil {
|
|
|
|
qq.reOrganize()
|
|
|
|
err = nil
|
|
|
|
}
|
|
|
|
m.self = qq.doc
|
|
|
|
return qq, err
|
|
|
|
}
|
|
|
|
|
2024-09-02 19:32:39 -04:00
|
|
|
func (m *Model) DeleteOne() error {
|
|
|
|
c := m.getColl()
|
|
|
|
if valueOf(m.self).Kind() == reflect.Slice {
|
|
|
|
}
|
|
|
|
id, ok := m.self.(HasID)
|
|
|
|
if !ok {
|
|
|
|
id2, ok2 := m.self.(HasIDSlice)
|
|
|
|
if !ok2 {
|
|
|
|
return fmt.Errorf("model '%s' is not registered", m.typeName)
|
|
|
|
}
|
|
|
|
_, err := c.DeleteOne(context.TODO(), bson.M{"_id": id2[0].Id()})
|
|
|
|
return err
|
|
|
|
} else {
|
|
|
|
_, err := c.DeleteOne(context.TODO(), bson.M{"_id": id.Id()})
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Append appends one or more items to `field`.
|
|
|
|
// will error if this Model contains a reference
|
|
|
|
// to multiple documents, or if `field` is not a
|
|
|
|
// slice.
|
|
|
|
func (m *Model) Append(field string, a ...interface{}) error {
|
|
|
|
rv := reflect.ValueOf(m.self)
|
|
|
|
ref := rv
|
|
|
|
rt := reflect.TypeOf(m.self)
|
|
|
|
if ref.Kind() == reflect.Pointer {
|
|
|
|
ref = ref.Elem()
|
|
|
|
rt = rt.Elem()
|
|
|
|
}
|
|
|
|
if ref.Kind() == reflect.Slice {
|
|
|
|
return fmt.Errorf("Cannot append to multiple documents!")
|
|
|
|
}
|
|
|
|
if ref.Kind() != reflect.Struct {
|
|
|
|
return fmt.Errorf("Current object is not a struct!")
|
|
|
|
}
|
|
|
|
_, ofv, err := getNested(field, ref)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
oofv := makeSettable(*ofv, (*ofv).Interface())
|
|
|
|
fv := oofv
|
|
|
|
if fv.Kind() == reflect.Pointer {
|
|
|
|
fv = fv.Elem()
|
|
|
|
}
|
|
|
|
if fv.Kind() != reflect.Slice {
|
|
|
|
return fmt.Errorf("Current object is not a slice!")
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, b := range a {
|
|
|
|
va := reflect.ValueOf(b)
|
|
|
|
fv.Set(reflect.Append(fv, va))
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-09-01 16:17:48 -04:00
|
|
|
func (m *Model) Save() error {
|
|
|
|
var err error
|
|
|
|
c := m.getColl()
|
|
|
|
now := time.Now()
|
|
|
|
selfo := reflect.ValueOf(m.self)
|
|
|
|
vp := selfo
|
|
|
|
if vp.Kind() != reflect.Ptr {
|
|
|
|
vp = reflect.New(selfo.Type())
|
|
|
|
vp.Elem().Set(selfo)
|
|
|
|
}
|
|
|
|
var asHasId = vp.Interface().(HasID)
|
|
|
|
(asHasId).Id()
|
2024-09-02 19:32:39 -04:00
|
|
|
isNew := reflect.ValueOf(asHasId.Id()).IsZero() && !m.exists
|
|
|
|
if isNew || !m.exists {
|
2024-09-01 16:17:48 -04:00
|
|
|
m.Created = now
|
|
|
|
}
|
|
|
|
m.Modified = now
|
|
|
|
idxs := m.getIdxs()
|
|
|
|
for _, i := range idxs {
|
|
|
|
_, err = c.Indexes().CreateOne(context.TODO(), *i)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2024-09-02 19:32:39 -04:00
|
|
|
if isNew || !m.exists {
|
2024-09-01 16:17:48 -04:00
|
|
|
nid := getLastInColl(c.Name(), asHasId.Id())
|
|
|
|
switch pnid := nid.(type) {
|
|
|
|
case uint:
|
|
|
|
nid = pnid + 1
|
|
|
|
case uint32:
|
|
|
|
nid = pnid + 1
|
|
|
|
case uint64:
|
|
|
|
nid = pnid + 1
|
|
|
|
case int:
|
|
|
|
nid = pnid + 1
|
|
|
|
case int32:
|
|
|
|
nid = pnid + 1
|
|
|
|
case int64:
|
|
|
|
nid = pnid + 1
|
|
|
|
case string:
|
|
|
|
nid = NextStringID()
|
|
|
|
case primitive.ObjectID:
|
|
|
|
nid = primitive.NewObjectID()
|
|
|
|
default:
|
|
|
|
panic("unknown or unsupported id type")
|
|
|
|
}
|
|
|
|
if reflect.ValueOf(asHasId.Id()).IsZero() {
|
|
|
|
(asHasId).SetId(nid)
|
|
|
|
}
|
|
|
|
|
|
|
|
m.self = asHasId
|
2024-09-02 19:32:39 -04:00
|
|
|
_, err = c.InsertOne(context.TODO(), m.serializeToStore())
|
|
|
|
if err == nil {
|
|
|
|
m.exists = true
|
|
|
|
}
|
2024-09-01 16:17:48 -04:00
|
|
|
} else {
|
|
|
|
_, err = c.ReplaceOne(context.TODO(), bson.D{{Key: "_id", Value: m.self.(HasID).Id()}}, m.serializeToStore())
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Model) serializeToStore() bson.M {
|
|
|
|
return serializeIDs((*m).self)
|
|
|
|
}
|
|
|
|
|
|
|
|
func serializeIDs(input interface{}) bson.M {
|
|
|
|
ret := bson.M{}
|
|
|
|
mv := reflect.ValueOf(input)
|
|
|
|
mt := reflect.TypeOf(input)
|
|
|
|
|
|
|
|
vp := mv
|
|
|
|
if vp.Kind() != reflect.Ptr {
|
|
|
|
vp = reflect.New(mv.Type())
|
|
|
|
vp.Elem().Set(mv)
|
|
|
|
}
|
|
|
|
if mv.Kind() == reflect.Pointer {
|
|
|
|
mv = mv.Elem()
|
|
|
|
}
|
|
|
|
if mt.Kind() == reflect.Pointer {
|
|
|
|
mt = mt.Elem()
|
|
|
|
}
|
|
|
|
for i := 0; i < mv.NumField(); i++ {
|
|
|
|
fv := mv.Field(i)
|
|
|
|
ft := mt.Field(i)
|
2024-09-02 19:32:39 -04:00
|
|
|
var dr = fv
|
2024-09-01 16:17:48 -04:00
|
|
|
tag, err := structtag.Parse(string(mt.Field(i).Tag))
|
|
|
|
panik(err)
|
|
|
|
bbson, err := tag.Get("bson")
|
|
|
|
if err != nil || bbson.Name == "-" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
_, terr := tag.Get("ref")
|
|
|
|
switch dr.Type().Kind() {
|
|
|
|
case reflect.Slice:
|
|
|
|
rarr := make([]interface{}, 0)
|
|
|
|
intArr := iFaceSlice(fv.Interface())
|
|
|
|
for _, idHaver := range intArr {
|
|
|
|
if terr == nil {
|
|
|
|
if reflect.ValueOf(idHaver).Type().Kind() != reflect.Pointer {
|
|
|
|
vp := reflect.New(reflect.ValueOf(idHaver).Type())
|
|
|
|
vp.Elem().Set(reflect.ValueOf(idHaver))
|
|
|
|
idHaver = vp.Interface()
|
|
|
|
}
|
|
|
|
ifc, ok := idHaver.(HasID)
|
|
|
|
if !ok {
|
|
|
|
panic(fmt.Sprintf("referenced model slice '%s' does not implement HasID", ft.Name))
|
|
|
|
}
|
|
|
|
rarr = append(rarr, ifc.Id())
|
|
|
|
} else if reflect.ValueOf(idHaver).Kind() == reflect.Struct {
|
|
|
|
rarr = append(rarr, serializeIDs(idHaver))
|
|
|
|
} else {
|
|
|
|
if reflect.ValueOf(idHaver).Kind() == reflect.Slice {
|
|
|
|
rarr = append(rarr, serializeIDSlice(iFaceSlice(idHaver)))
|
|
|
|
} else {
|
|
|
|
rarr = append(rarr, idHaver)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ret[bbson.Name] = rarr
|
|
|
|
}
|
|
|
|
case reflect.Pointer:
|
|
|
|
dr = fv.Elem()
|
|
|
|
fallthrough
|
|
|
|
case reflect.Struct:
|
|
|
|
if bbson.Name != "" {
|
|
|
|
if terr == nil {
|
|
|
|
idHaver := fv.Interface()
|
|
|
|
ifc, ok := idHaver.(HasID)
|
|
|
|
if !ok {
|
|
|
|
panic(fmt.Sprintf("referenced model '%s' does not implement HasID", ft.Name))
|
|
|
|
}
|
|
|
|
if !fv.IsNil() {
|
|
|
|
ret[bbson.Name] = ifc.Id()
|
|
|
|
} else {
|
|
|
|
ret[bbson.Name] = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if reflect.TypeOf(fv.Interface()) != reflect.TypeOf(time.Now()) {
|
|
|
|
ret[bbson.Name] = serializeIDs(fv.Interface())
|
|
|
|
} else {
|
|
|
|
ret[bbson.Name] = fv.Interface()
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for k, v := range serializeIDs(fv.Interface()) {
|
|
|
|
ret[k] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
ret[bbson.Name] = fv.Interface()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
|
|
|
func serializeIDSlice(input []interface{}) bson.A {
|
|
|
|
var a bson.A
|
|
|
|
for _, in := range input {
|
|
|
|
fv := reflect.ValueOf(in)
|
|
|
|
switch fv.Type().Kind() {
|
|
|
|
case reflect.Slice:
|
|
|
|
a = append(a, serializeIDSlice(iFaceSlice(fv.Interface())))
|
|
|
|
case reflect.Struct:
|
|
|
|
a = append(a, serializeIDs(fv.Interface()))
|
|
|
|
default:
|
|
|
|
a = append(a, fv.Interface())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return a
|
|
|
|
}
|
|
|
|
|
2024-09-02 19:32:39 -04:00
|
|
|
// Create creates a new instance of a given model
|
|
|
|
// and returns a pointer to it.
|
2024-09-01 16:17:48 -04:00
|
|
|
func Create(d any) any {
|
|
|
|
var n string
|
|
|
|
var ri *InternalModel
|
|
|
|
var ok bool
|
|
|
|
|
|
|
|
n, ri, ok = ModelRegistry.HasByName(nameOf(d))
|
|
|
|
|
|
|
|
if !ok {
|
|
|
|
ModelRegistry.Model(d)
|
|
|
|
n, ri, _ = ModelRegistry.Has(d)
|
|
|
|
}
|
|
|
|
t := ri.Type
|
|
|
|
v := valueOf(d)
|
|
|
|
i := ModelRegistry.Index(n)
|
|
|
|
|
|
|
|
r := reflect.New(t)
|
|
|
|
|
|
|
|
r.Elem().Set(v)
|
|
|
|
|
|
|
|
df := r.Elem().Field(i)
|
|
|
|
dm := df.Interface().(Model)
|
|
|
|
if reflect.ValueOf(d).Kind() == reflect.Pointer {
|
|
|
|
r.Elem().Set(reflect.ValueOf(d).Elem())
|
|
|
|
} else {
|
|
|
|
r.Elem().Set(reflect.ValueOf(d))
|
|
|
|
}
|
|
|
|
dm.typeName = n
|
|
|
|
what := r.Interface()
|
|
|
|
dm.self = what
|
|
|
|
df.Set(reflect.ValueOf(dm))
|
|
|
|
return what
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Model) PrintMe() {
|
|
|
|
fmt.Printf("My name is %s !\n", nameOf(m))
|
|
|
|
}
|