hello world! 🌸
This commit is contained in:
		
						commit
						f46aab2f5f
					
				
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| *.exe | ||||
| *.sum | ||||
| muck/ | ||||
							
								
								
									
										8
									
								
								.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| # Default ignored files | ||||
| /shelf/ | ||||
| /workspace.xml | ||||
| # Editor-based HTTP Client requests | ||||
| /httpRequests/ | ||||
| # Datasource local storage ignored files | ||||
| /dataSources/ | ||||
| /dataSources.local.xml | ||||
							
								
								
									
										26
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | ||||
| module rockfic.com/orm | ||||
| 
 | ||||
| go 1.23 | ||||
| 
 | ||||
| require ( | ||||
| 	github.com/fatih/structtag v1.2.0 | ||||
| 	go.mongodb.org/mongo-driver v1.16.1 | ||||
| 	golang.org/x/net v0.21.0 | ||||
| ) | ||||
| 
 | ||||
| require ( | ||||
| 	github.com/go-loremipsum/loremipsum v1.1.3 | ||||
| 	github.com/golang/snappy v0.0.4 // indirect | ||||
| 	github.com/klauspost/compress v1.13.6 // indirect | ||||
| 	github.com/montanaflynn/stats v0.7.1 // indirect | ||||
| 	github.com/stretchr/testify v1.9.0 // indirect | ||||
| 	github.com/xdg-go/pbkdf2 v1.0.0 // indirect | ||||
| 	github.com/xdg-go/scram v1.1.2 // indirect | ||||
| 	github.com/xdg-go/stringprep v1.0.4 // indirect | ||||
| 	github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect | ||||
| 	golang.org/x/crypto v0.22.0 // indirect | ||||
| 	golang.org/x/sync v0.7.0 // indirect | ||||
| 	golang.org/x/text v0.14.0 // indirect | ||||
| ) | ||||
| 
 | ||||
| replace rockfic.com/orm => C:/rockfic/orm | ||||
							
								
								
									
										74
									
								
								idcounter.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								idcounter.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,74 @@ | ||||
| package orm | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"reflect" | ||||
| 
 | ||||
| 	"go.mongodb.org/mongo-driver/bson" | ||||
| 	"go.mongodb.org/mongo-driver/bson/primitive" | ||||
| 	"go.mongodb.org/mongo-driver/mongo/options" | ||||
| ) | ||||
| 
 | ||||
| const COUNTER_COL = "@counters" | ||||
| 
 | ||||
| type Counter struct { | ||||
| 	Current    any    `bson:"current"` | ||||
| 	Collection string `bson:"collection"` | ||||
| } | ||||
| 
 | ||||
| func getLastInColl(cname string, id interface{}) interface{} { | ||||
| 	var opts *options.FindOneOptions = options.FindOne() | ||||
| 
 | ||||
| 	switch id.(type) { | ||||
| 	case int, int64, int32, uint, uint32, uint64, primitive.ObjectID: | ||||
| 		opts.SetSort(bson.M{"_id": -1}) | ||||
| 	case string: | ||||
| 		opts.SetSort(bson.M{"createdAt": -1}) | ||||
| 	default: | ||||
| 		panic("unsupported id type provided") | ||||
| 	} | ||||
| 
 | ||||
| 	var cnt Counter | ||||
| 	if !reflect.ValueOf(id).IsZero() { | ||||
| 		return id | ||||
| 	} | ||||
| 	if err := DB.Collection(COUNTER_COL).FindOne(context.TODO(), bson.M{ | ||||
| 		"collection": cname, | ||||
| 	}, opts).Decode(&cnt); err != nil { | ||||
| 		cnt = Counter{ | ||||
| 			Current: id, | ||||
| 		} | ||||
| 		cnt.Collection = cname | ||||
| 		switch nini := cnt.Current.(type) { | ||||
| 		case int: | ||||
| 			if nini == 0 { | ||||
| 				cnt.Current = nini + 1 | ||||
| 			} | ||||
| 		case int32: | ||||
| 			if nini == int32(0) { | ||||
| 				cnt.Current = nini + 1 | ||||
| 			} | ||||
| 		case int64: | ||||
| 			if nini == int64(0) { | ||||
| 				cnt.Current = nini + 1 | ||||
| 			} | ||||
| 		case uint: | ||||
| 			if nini == uint(0) { | ||||
| 				cnt.Current = nini + 1 | ||||
| 			} | ||||
| 		case uint32: | ||||
| 			if nini == uint32(0) { | ||||
| 				cnt.Current = nini + 1 | ||||
| 			} | ||||
| 		case uint64: | ||||
| 			if nini == uint64(0) { | ||||
| 				cnt.Current = nini + 1 | ||||
| 			} | ||||
| 		case string: | ||||
| 			cnt.Current = NextStringID() | ||||
| 		case primitive.ObjectID: | ||||
| 			cnt.Current = primitive.NewObjectID() | ||||
| 		} | ||||
| 	} | ||||
| 	return cnt.Current | ||||
| } | ||||
							
								
								
									
										148
									
								
								indexes.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								indexes.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,148 @@ | ||||
| package orm | ||||
| 
 | ||||
| import ( | ||||
| 	"go/scanner" | ||||
| 	"go/token" | ||||
| 	"strings" | ||||
| 	"unicode" | ||||
| 
 | ||||
| 	"go.mongodb.org/mongo-driver/mongo" | ||||
| ) | ||||
| 
 | ||||
| var optionKeywords = [...]string{"unique", "sparse", "background", "dropdups"} | ||||
| 
 | ||||
| // prolly won't need to use indexes, but just in case...
 | ||||
| type InternalIndex struct { | ||||
| 	Fields       []string | ||||
| 	Options      []string | ||||
| 	lastFieldIdx int | ||||
| 	sticky       bool | ||||
| } | ||||
| 
 | ||||
| func (in *InternalIndex) appendOption(o string) { | ||||
| 	o = strings.ToLower(o) | ||||
| 	o = strings.Trim(o, " ") | ||||
| 	for i := range optionKeywords { | ||||
| 		if optionKeywords[i] == o { | ||||
| 			in.Options = append(in.Options, o) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (in *InternalIndex) appendField(f string) { | ||||
| 	in.Fields = append(in.Fields, f) | ||||
| } | ||||
| 
 | ||||
| func (in *InternalIndex) appendDotField(f string) { | ||||
| 	in.Fields[in.lastFieldIdx] = in.Fields[in.lastFieldIdx] + "." + f | ||||
| } | ||||
| 
 | ||||
| func (in *InternalIndex) updateLastField() { | ||||
| 	if len(in.Fields) > 0 { | ||||
| 		in.lastFieldIdx = len(in.Fields) - 1 | ||||
| 		return | ||||
| 	} | ||||
| 	in.lastFieldIdx = 0 | ||||
| } | ||||
| 
 | ||||
| func (in *InternalIndex) setSticky(s bool) { | ||||
| 	in.sticky = s | ||||
| } | ||||
| 
 | ||||
| func (in *InternalIndex) getSticky() bool { | ||||
| 	return in.sticky | ||||
| } | ||||
| 
 | ||||
| func scanIndex(src string) []InternalIndex { | ||||
| 	var s scanner.Scanner | ||||
| 	var parsed []InternalIndex | ||||
| 
 | ||||
| 	src = func(ss string) string { | ||||
| 		return strings.Map(func(r rune) rune { | ||||
| 			if unicode.IsSpace(r) { | ||||
| 				return -1 | ||||
| 			} | ||||
| 			return r | ||||
| 		}, ss) | ||||
| 	}(src) | ||||
| 	fset := token.NewFileSet() | ||||
| 	file := fset.AddFile("", fset.Base(), len(src)) | ||||
| 	s.Init(file, []byte(src), nil, scanner.ScanComments) | ||||
| 
 | ||||
| 	lb := false | ||||
| 
 | ||||
| 	p := &InternalIndex{} | ||||
| 
 | ||||
| 	for { | ||||
| 		_, tok, lit := s.Scan() | ||||
| 
 | ||||
| 		switch tok { | ||||
| 		case token.LBRACE: | ||||
| 			if lb { | ||||
| 				goto panik | ||||
| 			} | ||||
| 			lb = true | ||||
| 		case token.RBRACE: | ||||
| 			if !lb || len(p.Fields) == 0 { | ||||
| 				goto panik | ||||
| 			} | ||||
| 			lb = false | ||||
| 		case token.IDENT: | ||||
| 			if lb { | ||||
| 				if p.getSticky() { | ||||
| 					p.appendDotField(lit) | ||||
| 					p.setSticky(false) | ||||
| 					break | ||||
| 				} | ||||
| 				p.appendField(lit) | ||||
| 				break | ||||
| 			} | ||||
| 			p.appendOption(lit) | ||||
| 		case token.PERIOD: | ||||
| 			if p.getSticky() { | ||||
| 				goto panik | ||||
| 			} | ||||
| 			p.setSticky(true) | ||||
| 			p.updateLastField() | ||||
| 		case token.COMMA: | ||||
| 		case token.COLON: | ||||
| 			if lb { | ||||
| 				goto panik | ||||
| 			} | ||||
| 		case token.SEMICOLON: | ||||
| 			if lb { | ||||
| 				goto panik | ||||
| 			} | ||||
| 			parsed = append(parsed, *p) | ||||
| 			p = &InternalIndex{} | ||||
| 		case token.EOF: | ||||
| 			if lb { | ||||
| 				goto panik | ||||
| 			} | ||||
| 			return parsed | ||||
| 		default: | ||||
| 			goto panik | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| panik: | ||||
| 	panic("parsing error in index expression!") | ||||
| } | ||||
| 
 | ||||
| func BuildIndex(i InternalIndex) *mongo.IndexModel { | ||||
| 	idx := &mongo.IndexModel{ | ||||
| 		Keys: i.Fields, | ||||
| 	} | ||||
| 
 | ||||
| 	for _, o := range i.Options { | ||||
| 		switch o { | ||||
| 		case "unique": | ||||
| 			idx.Options.SetUnique(true) | ||||
| 		case "background": | ||||
| 			idx.Options.SetBackground(true) | ||||
| 		case "sparse": | ||||
| 			idx.Options.SetSparse(true) | ||||
| 		} | ||||
| 	} | ||||
| 	return idx | ||||
| } | ||||
							
								
								
									
										371
									
								
								model.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										371
									
								
								model.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,371 @@ | ||||
| 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"` | ||||
| 	typeName string    `bson:"-" json:"-"` | ||||
| 	self     any       `bson:"-" json:"-"` | ||||
| 	exists   bool      `bson:"-"` | ||||
| } | ||||
| 
 | ||||
| // HasID is a simple interface that you must implement.
 | ||||
| // This allows for more flexibility if your ID isn't an
 | ||||
| // ObjectID (e.g., int, uint, string...).
 | ||||
| //
 | ||||
| // and yes, those darn ugly ObjectIDs are supported :)
 | ||||
| type HasID interface { | ||||
| 	Id() any | ||||
| 	SetId(id any) | ||||
| } | ||||
| 
 | ||||
| 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) | ||||
| } | ||||
| 
 | ||||
| func (m *Model) setTypeName(str string) { | ||||
| 	m.typeName = str | ||||
| } | ||||
| 
 | ||||
| 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 { | ||||
| 	mi := []*mongo.IndexModel{} | ||||
| 	if mpi := m.getParsedIdxs(); mpi != nil { | ||||
| 		for _, v := range mpi { | ||||
| 			for _, i := range v { | ||||
| 				mi = append(mi, BuildIndex(i)) | ||||
| 			} | ||||
| 		} | ||||
| 		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 | ||||
| } | ||||
| 
 | ||||
| func (m *Model) FindPaged(query interface{}, page int64, perPage int64, options ...*options.FindOptions) (*Query, error) { | ||||
| 	skipAmt := perPage * (page - 1) | ||||
| 	if skipAmt < 0 { | ||||
| 		skipAmt = 0 | ||||
| 	} | ||||
| 	if len(options) > 0 { | ||||
| 		options[0].SetSkip(skipAmt).SetLimit(perPage) | ||||
| 	} | ||||
| 	q, err := m.FindAll(query, options...) | ||||
| 	q.Op = OP_FIND_PAGED | ||||
| 	return q, err | ||||
| } | ||||
| 
 | ||||
| func (m *Model) FindByID(id interface{}) (*Query, error) { | ||||
| 	return m.FindOne(bson.D{{"_id", id}}) | ||||
| } | ||||
| 
 | ||||
| // 1  2  3  4  5  6  7  8  9  10 11  12  13  14  15  16  17  18  19  20  21  22  23  24  25
 | ||||
| // 0  1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24
 | ||||
| 
 | ||||
| // ^                          ^
 | ||||
| //                               ^                                   ^
 | ||||
| 
 | ||||
| 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 | ||||
| } | ||||
| 
 | ||||
| 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() | ||||
| 	isNew := reflect.ValueOf(asHasId.Id()).IsZero() || !m.exists | ||||
| 	if isNew { | ||||
| 		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 | ||||
| 		} | ||||
| 	} | ||||
| 	if isNew { | ||||
| 		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 | ||||
| 		c.InsertOne(context.TODO(), m.serializeToStore()) | ||||
| 		m.exists = true | ||||
| 	} 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) | ||||
| 		var dr reflect.Value = fv | ||||
| 		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 | ||||
| } | ||||
| 
 | ||||
| // Create creates a new instance of a given model.
 | ||||
| // returns a pointer to the newly created model.
 | ||||
| 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)) | ||||
| } | ||||
							
								
								
									
										109
									
								
								model_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								model_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,109 @@ | ||||
| package orm | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"go.mongodb.org/mongo-driver/bson" | ||||
| 	"go.mongodb.org/mongo-driver/mongo/options" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
| 
 | ||||
| func TestNew(t *testing.T) { | ||||
| 	initTest() | ||||
| 	doc := Create(&iti_single).(*story) | ||||
| 	assert.Equal(t, iti_single.Title, doc.Title) | ||||
| 	assert.Equal(t, iti_single.Chapters[0].Summary, doc.Chapters[0].Summary) | ||||
| } | ||||
| 
 | ||||
| func TestSave(t *testing.T) { | ||||
| 	initTest() | ||||
| 	storyDoc := Create(iti_multi).(*story) | ||||
| 	lauthor := Create(author).(*user) | ||||
| 	storyDoc.Author = lauthor | ||||
| 	assert.Equal(t, storyDoc.Id(), int64(0)) | ||||
| 	assert.Equal(t, lauthor.ID, storyDoc.Author.ID) | ||||
| 	aerr := lauthor.Save() | ||||
| 	assert.Equal(t, nil, aerr) | ||||
| 	serr := storyDoc.Save() | ||||
| 	assert.Equal(t, nil, serr) | ||||
| 	assert.Less(t, int64(0), storyDoc.ID) | ||||
| 	assert.Less(t, int64(0), lauthor.ID) | ||||
| } | ||||
| 
 | ||||
| func TestPopulate(t *testing.T) { | ||||
| 	initTest() | ||||
| 
 | ||||
| 	bandDoc := Create(iti_single.Chapters[0].Bands[0]).(*band) | ||||
| 	storyDoc := Create(iti_single).(*story) | ||||
| 	author := Create(author).(*user) | ||||
| 
 | ||||
| 	err := bandDoc.Save() | ||||
| 	assert.Equal(t, nil, err) | ||||
| 	storyDoc.Author = author | ||||
| 	err = author.Save() | ||||
| 	assert.Equal(t, nil, err) | ||||
| 	err = storyDoc.Save() | ||||
| 	assert.Equal(t, nil, err) | ||||
| 	assert.Greater(t, storyDoc.ID, int64(0)) | ||||
| 
 | ||||
| 	smodel := Create(story{}).(*story) | ||||
| 	q, err := smodel.FindByID(storyDoc.ID) | ||||
| 	assert.Equal(t, nil, err) | ||||
| 	//assert.Greater(t, smodel.ID, int64(0))
 | ||||
| 	assert.NotPanics(t, func() { | ||||
| 		foundDoc := &story{} | ||||
| 		q.Populate("Author", "Chapters.Bands").Exec(foundDoc) | ||||
| 		j, _ := q.JSON() | ||||
| 		fmt.Printf("%s\n", j) | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| func TestUpdate(t *testing.T) { | ||||
| 	initTest() | ||||
| 	nb := Create(band{ | ||||
| 		ID:   1, | ||||
| 		Name: "Metallica", | ||||
| 		Characters: []string{ | ||||
| 			"James Hetfield", | ||||
| 			"Lars Ulrich", | ||||
| 			"Kirk Hammett", | ||||
| 			"Cliff Burton", | ||||
| 		}, | ||||
| 		Locked: false, | ||||
| 	}).(*band) | ||||
| 	err := nb.Save() | ||||
| 	assert.Equal(t, nil, err) | ||||
| 	nb.Locked = true | ||||
| 	err = nb.Save() | ||||
| 	assert.Equal(t, nil, err) | ||||
| 
 | ||||
| 	foundM := Create(band{}).(*band) | ||||
| 	q, err := foundM.FindByID(int64(1)) | ||||
| 	found := &band{} | ||||
| 	q.Exec(found) | ||||
| 	assert.Equal(t, nil, err) | ||||
| 	assert.Equal(t, int64(1), found.ID) | ||||
| 	assert.Equal(t, nil, err) | ||||
| 	assert.Equal(t, true, found.Locked) | ||||
| } | ||||
| 
 | ||||
| func TestModel_FindAll(t *testing.T) { | ||||
| 	initTest() | ||||
| 	smodel := Create(story{}).(*story) | ||||
| 	query, err := smodel.FindAll(bson.M{}, options.Find()) | ||||
| 	assert.Equal(t, nil, err) | ||||
| 	final := make([]story, 0) | ||||
| 	query.Exec(&final) | ||||
| 	assert.Greater(t, len(final), 0) | ||||
| } | ||||
| 
 | ||||
| func TestModel_PopulateMulti(t *testing.T) { | ||||
| 	initTest() | ||||
| 	smodel := Create(story{}).(*story) | ||||
| 	query, err := smodel.FindAll(bson.M{}, options.Find()) | ||||
| 	assert.Equal(t, nil, err) | ||||
| 	final := make([]story, 0) | ||||
| 	query.Populate("Author", "Chapters.Bands").Exec(&final) | ||||
| 	assert.Greater(t, len(final), 0) | ||||
| } | ||||
							
								
								
									
										482
									
								
								query.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										482
									
								
								query.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,482 @@ | ||||
| package orm | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"github.com/fatih/structtag" | ||||
| 	"go.mongodb.org/mongo-driver/bson" | ||||
| 	"go.mongodb.org/mongo-driver/bson/primitive" | ||||
| 	"go.mongodb.org/mongo-driver/mongo" | ||||
| 	"log" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| type Query struct { | ||||
| 	// the handle of the collection associated with this query
 | ||||
| 	Collection *mongo.Collection | ||||
| 	// the operation from which this query stems
 | ||||
| 	Op string | ||||
| 	// the model instance associated with this query
 | ||||
| 	Model  Model | ||||
| 	done   bool | ||||
| 	rawDoc any | ||||
| 	doc    any | ||||
| } | ||||
| 
 | ||||
| const ( | ||||
| 	OP_FIND_ONE   = "findOne" | ||||
| 	OP_FIND_PAGED = "findPaged" | ||||
| 	OP_FIND_ALL   = "findAll" | ||||
| 	OP_FIND       = "find" | ||||
| ) | ||||
| 
 | ||||
| func populate(r Reference, rcoll string, rawDoc interface{}, d string, src interface{}) any { | ||||
| 	rt := reflect.TypeOf(src) | ||||
| 	rv := reflect.ValueOf(src) | ||||
| 	srt := rt | ||||
| 	if srt.Kind() == reflect.Pointer { | ||||
| 		srt = rt.Elem() | ||||
| 	} | ||||
| 	if rv.Kind() != reflect.Pointer { | ||||
| 		rv = reflect.New(rt) | ||||
| 		rv.Elem().Set(reflect.ValueOf(src)) | ||||
| 	} | ||||
| 
 | ||||
| 	var fieldsMap [3]string | ||||
| 	type bsonWhat struct { | ||||
| 		What string | ||||
| 	} | ||||
| 	/*switch cl.Kind() { | ||||
| 	case reflect.Struct: | ||||
| 
 | ||||
| 	}*/ | ||||
| 	var w bsonWhat | ||||
| 	switch rawDoc.(type) { | ||||
| 	case bson.A: | ||||
| 		w.What = "A" | ||||
| 	case bson.D: | ||||
| 		w.What = "D" | ||||
| 	case bson.M: | ||||
| 		w.What = "M" | ||||
| 	default: | ||||
| 		w.What = "-" | ||||
| 	} | ||||
| 	var next string | ||||
| 	if len(strings.Split(d, ".")) > 1 { | ||||
| 		next = strings.Join(strings.Split(d, ".")[1:], ".") | ||||
| 		d = strings.Split(d, ".")[0] | ||||
| 	} else { | ||||
| 		next = d | ||||
| 	} | ||||
| 	var toReturn interface{} | ||||
| 	switch w.What { | ||||
| 	case "A": | ||||
| 		rvs := reflect.MakeSlice(rt, 0, 0) | ||||
| 		var rahh reflect.Value | ||||
| 		if rv.Kind() == reflect.Ptr { | ||||
| 			rahh = rv.Elem() | ||||
| 		} else { | ||||
| 			rahh = rv | ||||
| 		} | ||||
| 		for i, el := range rawDoc.(bson.A) { | ||||
| 			it := rahh.Index(i) | ||||
| 			popped := populate(r, rcoll, el, next, it.Interface()) | ||||
| 			poppedVal := reflect.ValueOf(popped) | ||||
| 			if poppedVal.Kind() == reflect.Pointer { | ||||
| 				rvs = reflect.Append(rvs, poppedVal.Elem()) | ||||
| 			} else { | ||||
| 				rvs = reflect.Append(rvs, poppedVal) | ||||
| 			} | ||||
| 		} | ||||
| 		if rv.CanSet() { | ||||
| 			rv.Set(rvs) | ||||
| 		} else if rv.Kind() == reflect.Pointer { | ||||
| 			rv.Elem().Set(rvs) | ||||
| 		} else { | ||||
| 			src = rvs.Interface() | ||||
| 			toReturn = src | ||||
| 		} | ||||
| 	case "D": | ||||
| 		loc := rawDoc.(bson.D) | ||||
| 		nrd := bson.M{} | ||||
| 		for _, el := range loc { | ||||
| 			nrd[el.Key] = el.Value | ||||
| 		} | ||||
| 		rawDoc = nrd | ||||
| 		fallthrough | ||||
| 	case "M": | ||||
| 		dd := rawDoc.(bson.M) | ||||
| 		var sf reflect.Value | ||||
| 		var rsf reflect.Value | ||||
| 		if rv.Kind() == reflect.Pointer { | ||||
| 			sf = rv.Elem().FieldByName(d) | ||||
| 		} else { | ||||
| 			sf = rv.FieldByName(d) | ||||
| 		} | ||||
| 		if rv.Kind() == reflect.Pointer { | ||||
| 			rsf = rv.Elem().FieldByName(d) | ||||
| 		} else { | ||||
| 			rsf = rv.FieldByName(d) | ||||
| 		} | ||||
| 
 | ||||
| 		var ff reflect.StructField | ||||
| 		var ok bool | ||||
| 		if rt.Kind() == reflect.Pointer { | ||||
| 			ff, ok = rt.Elem().FieldByName(d) | ||||
| 		} else { | ||||
| 			ff, ok = rt.FieldByName(d) | ||||
| 		} | ||||
| 
 | ||||
| 		if ok { | ||||
| 			tag, err := structtag.Parse(string(ff.Tag)) | ||||
| 			if err == nil { | ||||
| 				val, err2 := tag.Get("bson") | ||||
| 				if err2 == nil { | ||||
| 					fttt := ff.Type | ||||
| 					if fttt.Kind() == reflect.Pointer || fttt.Kind() == reflect.Slice { | ||||
| 						fttt = fttt.Elem() | ||||
| 					} | ||||
| 					fieldsMap = [3]string{d, fttt.Name(), val.Name} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		intermediate := populate(r, rcoll, dd[fieldsMap[2]], next, sf.Interface()) | ||||
| 		if rsf.CanSet() { | ||||
| 			ival := reflect.ValueOf(intermediate) | ||||
| 			if ival.Kind() != reflect.Pointer && rsf.Kind() == reflect.Pointer { | ||||
| 				rsf.Set(ival.Elem()) | ||||
| 			} else if ival.Kind() == reflect.Pointer && rsf.Kind() != reflect.Pointer { | ||||
| 				rsf.Set(ival.Elem()) | ||||
| 			} else { | ||||
| 				rsf.Set(ival) | ||||
| 			} | ||||
| 		} else { | ||||
| 			src = intermediate | ||||
| 		} | ||||
| 	default: | ||||
| 		tto := r.HydratedType | ||||
| 		if tto.Kind() == reflect.Pointer || tto.Kind() == reflect.Slice { | ||||
| 			tto = tto.Elem() | ||||
| 		} | ||||
| 
 | ||||
| 		rawt := ModelRegistry.new_(tto.Name()) | ||||
| 		t := rawt.(IModel) | ||||
| 		q := bson.M{"_id": rawDoc} | ||||
| 		reso := DB.Collection(rcoll).FindOne(context.TODO(), q) | ||||
| 		reso.Decode(t) | ||||
| 		hatred := rv | ||||
| 		if hatred.Kind() == reflect.Pointer { | ||||
| 			hatred = hatred.Elem() | ||||
| 		} | ||||
| 		if hatred.CanSet() { | ||||
| 			if reflect.ValueOf(t).Kind() == reflect.Pointer { | ||||
| 				fmt.Println(reflect.ValueOf(t).Elem().Type().String()) | ||||
| 				fmt.Println(rv.Type().String()) | ||||
| 				hatred.Set(reflect.ValueOf(t).Elem()) | ||||
| 			} else { | ||||
| 				hatred.Set(reflect.ValueOf(t)) | ||||
| 			} | ||||
| 		} else { | ||||
| 			src = t | ||||
| 			toReturn = src | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if toReturn == nil { | ||||
| 		return rv.Interface() | ||||
| 	} | ||||
| 	return src | ||||
| } | ||||
| 
 | ||||
| // Populate populates document references via reflection
 | ||||
| func (q *Query) Populate(fields ...string) *Query { | ||||
| 	_, cm, _ := ModelRegistry.HasByName(q.Model.typeName) | ||||
| 
 | ||||
| 	if cm != nil { | ||||
| 		rawDoc := q.rawDoc | ||||
| 		for _, field := range fields { | ||||
| 			// 0 = fieldname, 1 = typename, 2 = bson name
 | ||||
| 
 | ||||
| 			var r Reference | ||||
| 
 | ||||
| 			for k2, v := range cm.References { | ||||
| 				if strings.HasPrefix(k2, field) { | ||||
| 					r = v | ||||
| 					break | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			if r.Exists { | ||||
| 				// get self
 | ||||
| 				// get ptr
 | ||||
| 				// find
 | ||||
| 				// unmarshal...
 | ||||
| 				htt := r.HydratedType | ||||
| 				if htt.Kind() == reflect.Pointer || htt.Kind() == reflect.Slice { | ||||
| 					htt = htt.Elem() | ||||
| 				} | ||||
| 				if strings.HasSuffix(field, ".") || strings.HasPrefix(field, ".") { | ||||
| 					log.Printf("WARN: invalid field name passed to Populate(). skipping...\n") | ||||
| 					continue | ||||
| 				} | ||||
| 
 | ||||
| 				tto := r.HydratedType | ||||
| 				if tto.Kind() == reflect.Pointer || tto.Kind() == reflect.Slice { | ||||
| 					tto = tto.Elem() | ||||
| 				} | ||||
| 				_, refColl, _ := ModelRegistry.HasByName(tto.Name()) | ||||
| 				var tmp1 interface{} | ||||
| 				if arr, ok := rawDoc.(bson.A); ok { | ||||
| 					typ := cm.Type | ||||
| 					slic := reflect.New(reflect.SliceOf(typ)) | ||||
| 					for i, val2 := range arr { | ||||
| 						ref := reflect.ValueOf(q.doc) | ||||
| 						if ref.Kind() == reflect.Pointer { | ||||
| 							ref = ref.Elem() | ||||
| 						} | ||||
| 						src := ref.Index(i).Interface() | ||||
| 						inter := populate(r, refColl.Collection, val2, field, src) | ||||
| 						if reflect.ValueOf(inter).Kind() == reflect.Pointer { | ||||
| 							slic.Elem().Set(reflect.Append(slic.Elem(), reflect.ValueOf(inter).Elem())) | ||||
| 						} else { | ||||
| 							slic.Elem().Set(reflect.Append(slic, reflect.ValueOf(inter))) | ||||
| 						} | ||||
| 					} | ||||
| 					tmp1 = slic.Interface() | ||||
| 				} else { | ||||
| 					tmp1 = populate(r, refColl.Collection, rawDoc, field, reflect.ValueOf(q.doc).Interface()) | ||||
| 				} | ||||
| 				q.doc = tmp1 | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return q | ||||
| } | ||||
| func (q *Query) reOrganize() { | ||||
| 	var trvo reflect.Value | ||||
| 	if arr, ok := q.rawDoc.(bson.A); ok { | ||||
| 		typ := ModelRegistry[q.Model.typeName].Type | ||||
| 		slic := reflect.New(reflect.SliceOf(typ)) | ||||
| 		for _, v2 := range arr { | ||||
| 			inter := reflect.ValueOf(rerere(v2, typ)) | ||||
| 			if inter.Kind() == reflect.Pointer { | ||||
| 				inter = inter.Elem() | ||||
| 			} | ||||
| 			slic.Elem().Set(reflect.Append(slic.Elem(), inter)) | ||||
| 		} | ||||
| 		trvo = slic.Elem() | ||||
| 	} else { | ||||
| 		trvo = reflect.ValueOf(rerere(q.rawDoc, reflect.TypeOf(q.doc))) | ||||
| 		for { | ||||
| 			if trvo.Kind() == reflect.Pointer { | ||||
| 				trvo = trvo.Elem() | ||||
| 			} else { | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	resV := reflect.ValueOf(q.doc) | ||||
| 	for { | ||||
| 		if resV.Kind() == reflect.Pointer { | ||||
| 			resV = resV.Elem() | ||||
| 		} else { | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	resV.Set(trvo) | ||||
| } | ||||
| 
 | ||||
| func rerere(input interface{}, resType reflect.Type) interface{} { | ||||
| 	t := reflect.TypeOf(input) | ||||
| 	v := reflect.ValueOf(input) | ||||
| 	if input == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	if v.CanAddr() && v.IsNil() { | ||||
| 		return nil | ||||
| 	} | ||||
| 	if v.Type().Kind() == reflect.Pointer { | ||||
| 		v = v.Elem() | ||||
| 	} | ||||
| 	if t.Kind() == reflect.Pointer { | ||||
| 		t = t.Elem() | ||||
| 	} | ||||
| 
 | ||||
| 	if resType.Kind() == reflect.Pointer { | ||||
| 		resType = resType.Elem() | ||||
| 	} | ||||
| 	resV := reflect.New(resType) | ||||
| 	var rve reflect.Value = resV | ||||
| 	if rve.Kind() == reflect.Pointer { | ||||
| 		rve = resV.Elem() | ||||
| 	} | ||||
| 
 | ||||
| 	if d, isD := v.Interface().(bson.D); isD { | ||||
| 		m := bson.M{} | ||||
| 		for _, el := range d { | ||||
| 			m[el.Key] = el.Value | ||||
| 		} | ||||
| 		input = m | ||||
| 		v = reflect.ValueOf(input) | ||||
| 	} | ||||
| 
 | ||||
| 	switch resType.Kind() { | ||||
| 	case reflect.Struct: | ||||
| 		shouldBreak := false | ||||
| 		mipmap, ok := v.Interface().(bson.M) | ||||
| 		if ok { | ||||
| 			for i := 0; i < resType.NumField(); i++ { | ||||
| 				ft := resType.Field(i) | ||||
| 				fv := rve.Field(i) | ||||
| 				if ft.Anonymous { | ||||
| 					fv.Set(handleAnon(input, ft.Type, fv)) | ||||
| 					continue | ||||
| 				} | ||||
| 				tags, err := structtag.Parse(string(ft.Tag)) | ||||
| 				panik(err) | ||||
| 				btag, err := tags.Get("bson") | ||||
| 				if err != nil { | ||||
| 					continue | ||||
| 				} | ||||
| 				if btag.Name == "-" { | ||||
| 					continue | ||||
| 				} | ||||
| 				intermediate := mipmap[btag.Name] | ||||
| 				_, err = tags.Get("ref") | ||||
| 				if err != nil { | ||||
| 					tmp := rerere(intermediate, ft.Type) | ||||
| 					fuck := reflect.ValueOf(tmp) | ||||
| 					if !fuck.IsZero() { | ||||
| 						if fuck.Type().Kind() == reflect.Pointer { | ||||
| 							fuck = fuck.Elem() | ||||
| 						} | ||||
| 					} | ||||
| 					fv.Set(fuck) | ||||
| 					shouldBreak = true | ||||
| 				} else { | ||||
| 					tt := ft.Type | ||||
| 					if tt.Kind() == reflect.Pointer { | ||||
| 						tt = tt.Elem() | ||||
| 					} | ||||
| 					tmp := rerere(intermediate, ft.Type) | ||||
| 					if tmp != nil { | ||||
| 						if reflect.ValueOf(tmp).Kind() == reflect.Pointer && fv.Kind() != reflect.Pointer { | ||||
| 							fv.Set(reflect.ValueOf(tmp).Elem()) | ||||
| 						} else { | ||||
| 							fv.Set(reflect.ValueOf(tmp)) | ||||
| 						} | ||||
| 					} else { | ||||
| 						fv.Set(reflect.Zero(ft.Type)) | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 			if shouldBreak { | ||||
| 				// break
 | ||||
| 			} | ||||
| 		} else { | ||||
| 			nunu := ModelRegistry.new_(resType.Name()) | ||||
| 			ider, ok := nunu.(HasID) | ||||
| 			if ok { | ||||
| 				ider.SetId(v.Interface()) | ||||
| 				if reflect.ValueOf(ider).Kind() == reflect.Pointer { | ||||
| 					nunu = reflect.ValueOf(ider).Elem().Interface() | ||||
| 				} | ||||
| 				rve.Set(reflect.ValueOf(nunu)) | ||||
| 			} | ||||
| 		} | ||||
| 	case reflect.Slice: | ||||
| 		arr := v.Interface().(bson.A) | ||||
| 		for _, it := range arr { | ||||
| 			if it != nil { | ||||
| 				tmp := reflect.ValueOf(rerere(it, rve.Type().Elem())) | ||||
| 				if tmp.Kind() == reflect.Pointer { | ||||
| 					tmp = tmp.Elem() | ||||
| 				} | ||||
| 				rve.Set(reflect.Append(rve, tmp)) | ||||
| 			} | ||||
| 		} | ||||
| 	default: | ||||
| 		if resType.AssignableTo(v.Type()) { | ||||
| 			rve.Set(reflect.ValueOf(input)) | ||||
| 		} else { | ||||
| 			switch rve.Interface().(type) { | ||||
| 			case int, int32, int64, uint, uint32, uint64: | ||||
| 				rve.Set(reflect.ValueOf(coerceInt(v, rve))) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return resV.Interface() | ||||
| } | ||||
| 
 | ||||
| func handleAnon(raw interface{}, rtype reflect.Type, rval reflect.Value) reflect.Value { | ||||
| 	f := rval | ||||
| 	g := rtype | ||||
| 	if rtype.Kind() == reflect.Pointer { | ||||
| 		g = rtype.Elem() | ||||
| 	} | ||||
| 	if !f.CanSet() { | ||||
| 		f = reflect.New(g) | ||||
| 		f.Elem().Set(rval) | ||||
| 	} | ||||
| 	if rtype.Kind() != reflect.Struct { | ||||
| 		return rval | ||||
| 	} | ||||
| 
 | ||||
| 	for i := 0; i < rtype.NumField(); i++ { | ||||
| 		typeField := rtype.Field(i) | ||||
| 		valueField := f.Field(i) | ||||
| 		tags, err := structtag.Parse(string(typeField.Tag)) | ||||
| 		if !typeField.IsExported() { | ||||
| 			continue | ||||
| 		} | ||||
| 		if err == nil { | ||||
| 			var btag *structtag.Tag | ||||
| 			btag, err = tags.Get("bson") | ||||
| 			if err != nil { | ||||
| 				continue | ||||
| 			} | ||||
| 			if btag.Name == "-" { | ||||
| 				continue | ||||
| 			} | ||||
| 			amap, ok := raw.(bson.M) | ||||
| 			if ok { | ||||
| 				fval := amap[btag.Name] | ||||
| 				if reflect.TypeOf(fval) == reflect.TypeFor[primitive.DateTime]() { | ||||
| 					fval = time.UnixMilli(int64(fval.(primitive.DateTime))) | ||||
| 				} | ||||
| 				if valueField.Kind() == reflect.Pointer { | ||||
| 					valueField.Elem().Set(reflect.ValueOf(fval)) | ||||
| 				} else { | ||||
| 					valueField.Set(reflect.ValueOf(fval)) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return f | ||||
| } | ||||
| 
 | ||||
| // JSON - marshals this Query's results into json format
 | ||||
| func (q *Query) JSON() (string, error) { | ||||
| 	res, err := json.MarshalIndent(q.doc, "\n", "\t") | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	return string(res[:]), nil | ||||
| } | ||||
| 
 | ||||
| // Exec - executes the query and puts its results into the
 | ||||
| // provided argument.
 | ||||
| //
 | ||||
| // Will panic if called more than once on the same Query instance.
 | ||||
| func (q *Query) Exec(result interface{}) { | ||||
| 	if q.done { | ||||
| 		panic("Exec() has already been called!") | ||||
| 	} | ||||
| 	reflect.ValueOf(result).Elem().Set(reflect.ValueOf(q.doc).Elem()) | ||||
| 	q.done = true | ||||
| } | ||||
							
								
								
									
										351
									
								
								registry.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										351
									
								
								registry.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,351 @@ | ||||
| package orm | ||||
| 
 | ||||
| import ( | ||||
| 	context2 "context" | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 
 | ||||
| 	"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" | ||||
| 	"golang.org/x/net/context" | ||||
| ) | ||||
| 
 | ||||
| // InternalModel, as the name suggests, is used
 | ||||
| // internally by the model registry
 | ||||
| type InternalModel struct { | ||||
| 	Idx        int | ||||
| 	Type       reflect.Type | ||||
| 	Collection string | ||||
| 	References map[string]Reference | ||||
| 	Indexes    map[string][]InternalIndex | ||||
| } | ||||
| 
 | ||||
| // Reference stores a typed document reference
 | ||||
| type Reference struct { | ||||
| 	// owning model name
 | ||||
| 	Model string | ||||
| 	// the name of the struct field
 | ||||
| 	FieldName string | ||||
| 	// index of field in owning struct
 | ||||
| 	Idx int | ||||
| 	// the type of the referenced object
 | ||||
| 	HydratedType reflect.Type | ||||
| 
 | ||||
| 	// field kind (struct, slice, ...)
 | ||||
| 	Kind reflect.Kind | ||||
| 
 | ||||
| 	Exists bool | ||||
| } | ||||
| 
 | ||||
| type TModelRegistry map[string]*InternalModel | ||||
| 
 | ||||
| // ModelRegistry - the ModelRegistry stores a map containing
 | ||||
| // pointers to InternalModel instances, keyed by an associated
 | ||||
| // model name
 | ||||
| var ModelRegistry = make(TModelRegistry, 0) | ||||
| 
 | ||||
| // DB - The mongodb database handle
 | ||||
| var DB *mongo.Database | ||||
| 
 | ||||
| // DBClient - The mongodb client
 | ||||
| var DBClient *mongo.Client | ||||
| 
 | ||||
| // NextStringID - Override this function with your own
 | ||||
| // string ID generator!
 | ||||
| var NextStringID func() string | ||||
| 
 | ||||
| var mutex sync.Mutex | ||||
| 
 | ||||
| func getRawTypeFromTag(tagOpt string, slice bool) reflect.Type { | ||||
| 	var t reflect.Type | ||||
| 	switch strings.ToLower(tagOpt) { | ||||
| 	case "int": | ||||
| 		var v int64 = 0 | ||||
| 		t = reflect.TypeOf(v) | ||||
| 	case "uint": | ||||
| 		var v uint = 0 | ||||
| 		t = reflect.TypeOf(v) | ||||
| 	case "string": | ||||
| 		var v string = "0" | ||||
| 		t = reflect.TypeOf(v) | ||||
| 
 | ||||
| 	} | ||||
| 	if slice { | ||||
| 		return reflect.SliceOf(t) | ||||
| 	} | ||||
| 	return t | ||||
| } | ||||
| 
 | ||||
| func makeRef(idx int, modelName string, fieldName string, ht reflect.Type) Reference { | ||||
| 	if modelName != "" { | ||||
| 		if ModelRegistry.Index(modelName) != -1 { | ||||
| 			return Reference{ | ||||
| 				Idx:          idx, | ||||
| 				Model:        modelName, | ||||
| 				HydratedType: ht, | ||||
| 				Kind:         ht.Kind(), | ||||
| 				Exists:       true, | ||||
| 				FieldName:    fieldName, | ||||
| 			} | ||||
| 		} | ||||
| 		return Reference{ | ||||
| 			Idx:          idx, | ||||
| 			Model:        modelName, | ||||
| 			FieldName:    fieldName, | ||||
| 			HydratedType: ht, | ||||
| 			Kind:         ht.Kind(), | ||||
| 			Exists:       true, | ||||
| 		} | ||||
| 	} | ||||
| 	panic("model name was empty") | ||||
| } | ||||
| 
 | ||||
| func parseTags(t reflect.Type, v reflect.Value) (map[string][]InternalIndex, map[string]Reference, string) { | ||||
| 	coll := "" | ||||
| 	refs := make(map[string]Reference, 0) | ||||
| 	idcs := make(map[string][]InternalIndex, 0) | ||||
| 
 | ||||
| 	for i := 0; i < v.NumField(); i++ { | ||||
| 		sft := t.Field(i) | ||||
| 		ft := sft.Type | ||||
| 		tags, err := structtag.Parse(string(sft.Tag)) | ||||
| 		panik(err) | ||||
| 		shouldContinue := true | ||||
| 		for { | ||||
| 			if !shouldContinue { | ||||
| 				break | ||||
| 			} | ||||
| 			switch ft.Kind() { | ||||
| 			case reflect.Slice: | ||||
| 				ft = ft.Elem() | ||||
| 				if _, ok := tags.Get("ref"); ok != nil { | ||||
| 					if ft.Kind() == reflect.Struct { | ||||
| 						ii2, rr2, _ := parseTags(ft, reflect.New(ft).Elem()) | ||||
| 						for k, v := range ii2 { | ||||
| 							idcs[sft.Name+"."+k] = v | ||||
| 						} | ||||
| 						for k, v := range rr2 { | ||||
| 							refs[sft.Name+"."+k] = v | ||||
| 						} | ||||
| 					} | ||||
| 
 | ||||
| 				} | ||||
| 				continue | ||||
| 			case reflect.Pointer: | ||||
| 				ft = ft.Elem() | ||||
| 				fallthrough | ||||
| 			case reflect.Struct: | ||||
| 				if ft.ConvertibleTo(reflect.TypeOf(Model{})) { | ||||
| 					collTag, err := tags.Get("coll") | ||||
| 					panik(err) | ||||
| 					coll = collTag.Name | ||||
| 					idxTag, err := tags.Get("idx") | ||||
| 					if err == nil { | ||||
| 						idcs[sft.Type.Name()] = scanIndex(idxTag.Value()) | ||||
| 					} | ||||
| 					shouldContinue = false | ||||
| 					break | ||||
| 				} | ||||
| 				if refTag, ok := tags.Get("ref"); ok == nil { | ||||
| 					// ref:"ModelName,refType"
 | ||||
| 					/* if len(refTag.Options) < 1 { | ||||
| 						panic("no raw type provided for ref") | ||||
| 					} */ | ||||
| 					sname := sft.Name + "@" + refTag.Name | ||||
| 					refs[sname] = makeRef(i, refTag.Name, sft.Name, sft.Type) | ||||
| 				} | ||||
| 				fallthrough | ||||
| 			default: | ||||
| 				idxTag, err := tags.Get("idx") | ||||
| 				if err == nil { | ||||
| 					idcs[sft.Name] = scanIndex(idxTag.Value()) | ||||
| 				} | ||||
| 				shouldContinue = false | ||||
| 			} | ||||
| 
 | ||||
| 		} | ||||
| 	} | ||||
| 	return idcs, refs, coll | ||||
| } | ||||
| 
 | ||||
| // Has returns the model typename and InternalModel instance corresponding
 | ||||
| // to the argument passed, as well as a boolean indicating whether it
 | ||||
| // was found. otherwise returns `"", nil, false`
 | ||||
| func (r TModelRegistry) Has(i interface{}) (string, *InternalModel, bool) { | ||||
| 	t := reflect.TypeOf(i) | ||||
| 	if t.Kind() == reflect.Ptr { | ||||
| 		t = t.Elem() | ||||
| 	} | ||||
| 	n := t.Name() | ||||
| 	if rT, ok := ModelRegistry[n]; ok { | ||||
| 		return n, rT, true | ||||
| 	} | ||||
| 	return "", nil, false | ||||
| } | ||||
| 
 | ||||
| // HasByName functions almost identically to Has,
 | ||||
| // except that it takes a string as its argument.
 | ||||
| func (t TModelRegistry) HasByName(n string) (string, *InternalModel, bool) { | ||||
| 	if t, ok := ModelRegistry[n]; ok { | ||||
| 		return n, t, true | ||||
| 	} | ||||
| 	return "", nil, false | ||||
| } | ||||
| 
 | ||||
| // Index returns the index at which the Model struct is embedded
 | ||||
| func (r TModelRegistry) Index(n string) int { | ||||
| 	if v, ok := ModelRegistry[n]; ok { | ||||
| 		return v.Idx | ||||
| 	} | ||||
| 	return -1 | ||||
| } | ||||
| 
 | ||||
| func (t TModelRegistry) new_(n string) interface{} { | ||||
| 	if n, m, ok := ModelRegistry.HasByName(n); ok { | ||||
| 		v := reflect.New(m.Type) | ||||
| 		df := v.Elem().Field(m.Idx) | ||||
| 		d := df.Interface().(Model) | ||||
| 		d.typeName = n | ||||
| 		df.Set(reflect.ValueOf(d)) | ||||
| 		return v.Interface() | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Model registers models in the ModelRegistry, where
 | ||||
| // they can be accessed via a model's struct name
 | ||||
| func (r TModelRegistry) Model(mdl ...any) { | ||||
| 	defer mutex.Unlock() | ||||
| 	mutex.Lock() | ||||
| 
 | ||||
| 	for _, m := range mdl { | ||||
| 		t := reflect.TypeOf(m) | ||||
| 		v := reflect.ValueOf(m) | ||||
| 		vp := v | ||||
| 		if vp.Kind() != reflect.Ptr { | ||||
| 			vp = reflect.New(v.Type()) | ||||
| 			vp.Elem().Set(v) | ||||
| 		} | ||||
| 		id, ok := vp.Interface().(HasID) | ||||
| 		if !ok { | ||||
| 			panic(fmt.Sprintf("you MUST implement the HasID interface!!! skipping...\n")) | ||||
| 		} | ||||
| 		switch (id).Id().(type) { | ||||
| 		case int, int64, int32, string, primitive.ObjectID, uint, uint32, uint64: | ||||
| 			break | ||||
| 		default: | ||||
| 			log.Printf("invalid ID type specified!!! skipping...\n") | ||||
| 		} | ||||
| 
 | ||||
| 		if t.Kind() == reflect.Ptr { | ||||
| 			t = reflect.Indirect(reflect.ValueOf(m)).Type() | ||||
| 			v = reflect.ValueOf(m).Elem() | ||||
| 		} | ||||
| 		n := t.Name() | ||||
| 		if t.Kind() != reflect.Struct { | ||||
| 			panic(fmt.Sprintf("Only structs can be passed to this function, silly! (passed type: %s)", n)) | ||||
| 		} | ||||
| 		idx := -1 | ||||
| 		for i := 0; i < v.NumField(); i++ { | ||||
| 			ft := t.Field(i) | ||||
| 			if (ft.Type.ConvertibleTo(reflect.TypeOf(Model{}))) { | ||||
| 				idx = i | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 		if idx < 0 { | ||||
| 			panic("A model must embed the Model struct!") | ||||
| 		} | ||||
| 		inds, refs, coll := parseTags(t, v) | ||||
| 		if coll == "" { | ||||
| 			panic(fmt.Sprintf("a model needs to be given a collection name! (passed type: %s)", n)) | ||||
| 		} | ||||
| 		ModelRegistry[n] = &InternalModel{ | ||||
| 			Idx:        idx, | ||||
| 			Type:       t, | ||||
| 			Collection: coll, | ||||
| 			Indexes:    inds, | ||||
| 			References: refs, | ||||
| 		} | ||||
| 	} | ||||
| 	for k, v := range ModelRegistry { | ||||
| 		for k2, v2 := range v.References { | ||||
| 			if !v2.Exists { | ||||
| 				if _, ok := ModelRegistry[v2.FieldName]; ok { | ||||
| 					tmp := ModelRegistry[k].References[k2] | ||||
| 					ModelRegistry[k].References[k2] = Reference{ | ||||
| 						Model:        k, | ||||
| 						Idx:          tmp.Idx, | ||||
| 						FieldName:    tmp.FieldName, | ||||
| 						Kind:         tmp.Kind, | ||||
| 						HydratedType: tmp.HydratedType, | ||||
| 						Exists:       true, | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func innerWatch(coll *mongo.Collection) { | ||||
| 	sspipeline := mongo.Pipeline{ | ||||
| 		bson.D{{"$match", bson.D{{"$or", | ||||
| 			bson.A{ | ||||
| 				bson.D{{ | ||||
| 					"operationType", "insert", | ||||
| 				}}, | ||||
| 				bson.D{{"operationType", "update"}}, | ||||
| 			}, | ||||
| 		}}, | ||||
| 		}}, | ||||
| 	} | ||||
| 
 | ||||
| 	stream, err := coll.Watch(context.TODO(), sspipeline, options.ChangeStream().SetFullDocument(options.UpdateLookup).SetFullDocumentBeforeChange(options.WhenAvailable)) | ||||
| 	panik(err) | ||||
| 	defer func(stream *mongo.ChangeStream, ctx context2.Context) { | ||||
| 		err := stream.Close(ctx) | ||||
| 		panik(err) | ||||
| 	}(stream, context.TODO()) | ||||
| 	for stream.Next(context.TODO()) { | ||||
| 		var data bson.M | ||||
| 		if err := stream.Decode(&data); err != nil { | ||||
| 			log.Fatal(err) | ||||
| 		} | ||||
| 
 | ||||
| 		var uid = data["documentKey"].(bson.M)["_id"] | ||||
| 
 | ||||
| 		if data["operationType"] == "insert" { | ||||
| 			counterColl := DB.Collection(COUNTER_COL) | ||||
| 			counterColl.UpdateOne(context.TODO(), bson.M{"collection": coll.Name()}, bson.M{"$set": bson.M{ | ||||
| 				"current": uid, | ||||
| 			}}, options.Update().SetUpsert(true)) | ||||
| 		} | ||||
| 
 | ||||
| 		fmt.Printf("%v\n", data) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func Connect(uri string, dbName string) { | ||||
| 	cli, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(uri)) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("failed to open database") | ||||
| 	} | ||||
| 	panik(err) | ||||
| 	DB = cli.Database(dbName) | ||||
| 	colls, err := DB.ListCollectionNames(context.TODO(), bson.M{"name": bson.M{"$ne": COUNTER_COL}}, options.ListCollections().SetNameOnly(true)) | ||||
| 
 | ||||
| 	for _, c := range colls { | ||||
| 		if c == COUNTER_COL { | ||||
| 			continue | ||||
| 		} | ||||
| 		go innerWatch(DB.Collection(c)) | ||||
| 	} | ||||
| 
 | ||||
| 	DBClient = cli | ||||
| } | ||||
							
								
								
									
										174
									
								
								testing.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								testing.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,174 @@ | ||||
| package orm | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/go-loremipsum/loremipsum" | ||||
| 	"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" | ||||
| ) | ||||
| 
 | ||||
| type chapter struct { | ||||
| 	ID            primitive.ObjectID `bson:"_id" json:"_id"` | ||||
| 	Title         string             `bson:"chapterTitle" json:"chapterTitle" form:"chapterTitle"` | ||||
| 	ChapterID     int                `bson:"id" json:"chapterID"` | ||||
| 	Index         int                `bson:"index" json:"index" form:"index"` | ||||
| 	Words         int                `bson:"words" json:"words"` | ||||
| 	Notes         string             `bson:"notes" json:"notes" form:"notes"` | ||||
| 	Genre         []string           `bson:"genre" json:"genre" form:"genre"` | ||||
| 	Bands         []band             `json:"bands" bson:"bands" ref:"band,bands"` | ||||
| 	Characters    []string           `bson:"characters" json:"characters" form:"characters"` | ||||
| 	Relationships [][]string         `bson:"relationships" json:"relationships" form:"relationships"` | ||||
| 	Adult         bool               `bson:"adult" json:"adult" form:"adult"` | ||||
| 	Summary       string             `bson:"summary" json:"summary" form:"summary"` | ||||
| 	Hidden        bool               `bson:"hidden" json:"hidden" form:"hidden"` | ||||
| 	LoggedInOnly  bool               `bson:"loggedInOnly" json:"loggedInOnly" form:"loggedInOnly"` | ||||
| 	Posted        time.Time          `bson:"datePosted,omitempty" json:"datePosted"` | ||||
| 	FileName      string             `json:"fileName" bson:"-"` | ||||
| 	Text          string             `json:"text" bson:"-"` | ||||
| } | ||||
| 
 | ||||
| type band struct { | ||||
| 	ID         int64 `bson:"_id" json:"_id"` | ||||
| 	Model      `bson:",inline" json:",inline" coll:"bands"` | ||||
| 	Name       string   `bson:"name" json:"name" form:"name"` | ||||
| 	Locked     bool     `bson:"locked" json:"locked" form:"locked"` | ||||
| 	Characters []string `bson:"characters" json:"characters" form:"characters"` | ||||
| } | ||||
| type user struct { | ||||
| 	ID       int64 `bson:"_id" json:"_id"` | ||||
| 	Model    `bson:",inline" json:",inline" coll:"users"` | ||||
| 	Username string `bson:"username" json:"username"` | ||||
| } | ||||
| type story struct { | ||||
| 	ID        int64 `bson:"_id" json:"_id"` | ||||
| 	Model     `bson:",inline" json:",inline" coll:"stories"` | ||||
| 	Title     string    `bson:"title" json:"title" form:"title"` | ||||
| 	Author    *user     `bson:"author" json:"author" ref:"user"` | ||||
| 	CoAuthor  *user     `bson:"coAuthor" json:"coAuthor" ref:"user"` | ||||
| 	Chapters  []chapter `bson:"chapters" json:"chapters"` | ||||
| 	Recs      int       `bson:"recs" json:"recs"` | ||||
| 	Favs      int       `bson:"favs" json:"favs"` | ||||
| 	Views     int       `bson:"views" json:"views"` | ||||
| 	Completed bool      `bson:"completed" json:"completed" form:"completed"` | ||||
| 	Downloads int       `bson:"downloads" json:"downloads"` | ||||
| } | ||||
| 
 | ||||
| func (s *story) Id() any { | ||||
| 	return s.ID | ||||
| } | ||||
| 
 | ||||
| func (s *band) Id() any { | ||||
| 	return s.ID | ||||
| } | ||||
| func (s *user) Id() any { | ||||
| 	return s.ID | ||||
| } | ||||
| 
 | ||||
| func (s *story) SetId(id any) { | ||||
| 	s.ID = id.(int64) | ||||
| 	//var t IModel = s
 | ||||
| } | ||||
| 
 | ||||
| func (s *band) SetId(id any) { | ||||
| 	s.ID = id.(int64) | ||||
| } | ||||
| 
 | ||||
| func (s *user) SetId(id any) { | ||||
| 	s.ID = id.(int64) | ||||
| } | ||||
| 
 | ||||
| var author = user{ | ||||
| 	Username: "tablet.exe", | ||||
| } | ||||
| 
 | ||||
| func genChaps(single bool) []chapter { | ||||
| 	var ret []chapter | ||||
| 	var ceil int | ||||
| 	if single { | ||||
| 		ceil = 1 | ||||
| 	} else { | ||||
| 		ceil = 5 | ||||
| 	} | ||||
| 	emptyRel := make([][]string, 0) | ||||
| 	emptyRel = append(emptyRel, make([]string, 0)) | ||||
| 	relMap := [][][]string{ | ||||
| 		{ | ||||
| 			{"Sean Harris", "Brian Tatler"}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			{"Sean Harris", "Brian Tatler"}, | ||||
| 			{"Duncan Scott", "Colin Kimberley"}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			{"Duncan Scott", "Colin Kimberley"}, | ||||
| 		}, | ||||
| 		emptyRel, | ||||
| 		{ | ||||
| 			{"Sean Harris", "Colin Kimberley", "Brian Tatler"}, | ||||
| 		}, | ||||
| 	} | ||||
| 	var b band = band{ | ||||
| 		Name:       "Diamond Head", | ||||
| 		Locked:     false, | ||||
| 		Characters: []string{"Brian Tatler", "Sean Harris", "Duncan Scott", "Colin Kimberley"}, | ||||
| 	} | ||||
| 	b.ID = 503 | ||||
| 	for i := 0; i < ceil; i++ { | ||||
| 		spf := fmt.Sprintf("%d.md", i+1) | ||||
| 		ret = append(ret, chapter{ | ||||
| 			Title:         fmt.Sprintf("-%d-", i+1), | ||||
| 			Index:         int(i + 1), | ||||
| 			Words:         50, | ||||
| 			Notes:         "notenotenote !!!", | ||||
| 			Genre:         []string{"Slash"}, | ||||
| 			Bands:         []band{b}, | ||||
| 			Characters:    []string{"Sean Harris", "Brian Tatler", "Duncan Scott", "Colin Kimberley"}, | ||||
| 			Relationships: relMap[i], | ||||
| 			Adult:         true, | ||||
| 			Summary:       loremipsum.New().Paragraph(), | ||||
| 			Hidden:        false, | ||||
| 			LoggedInOnly:  true, | ||||
| 			FileName:      spf, | ||||
| 		}) | ||||
| 	} | ||||
| 
 | ||||
| 	return ret | ||||
| } | ||||
| 
 | ||||
| var iti_single story = story{ | ||||
| 	Title:     "title", | ||||
| 	Completed: true, | ||||
| 	Chapters:  genChaps(true), | ||||
| } | ||||
| 
 | ||||
| var iti_multi story = story{ | ||||
| 	Title:     "Brian Tatler Fucked and Abused Sean Harris", | ||||
| 	Completed: false, | ||||
| 	Chapters:  genChaps(false), | ||||
| } | ||||
| 
 | ||||
| func initTest() { | ||||
| 	uri := "mongodb://127.0.0.1:27017" | ||||
| 	db := "rockfic_ormTest" | ||||
| 	ic, _ := mongo.Connect(context.TODO(), options.Client().ApplyURI(uri)) | ||||
| 	colls, _ := ic.Database(db).ListCollectionNames(context.TODO(), bson.M{}) | ||||
| 	if len(colls) < 1 { | ||||
| 		mdb := ic.Database(db) | ||||
| 		mdb.CreateCollection(context.TODO(), "bands") | ||||
| 		mdb.CreateCollection(context.TODO(), "stories") | ||||
| 		mdb.CreateCollection(context.TODO(), "users") | ||||
| 	} | ||||
| 	defer ic.Disconnect(context.TODO()) | ||||
| 	Connect(uri, db) | ||||
| 	author.ID = 696969 | ||||
| 	ModelRegistry.Model(band{}, user{}, story{}) | ||||
| } | ||||
| func after() { | ||||
| 	err := DBClient.Disconnect(context.TODO()) | ||||
| 	panik(err) | ||||
| } | ||||
							
								
								
									
										70
									
								
								util.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								util.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,70 @@ | ||||
| package orm | ||||
| 
 | ||||
| import "reflect" | ||||
| 
 | ||||
| func panik(err error) { | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func nameOf(i interface{}) string { | ||||
| 	v := reflect.ValueOf(i) | ||||
| 	var n string | ||||
| 	switch v.Kind() { | ||||
| 	case reflect.Slice, reflect.Map: | ||||
| 		if v.Type().Elem().Kind() == reflect.Pointer { | ||||
| 			n = v.Type().Elem().Elem().Name() | ||||
| 		} | ||||
| 	case reflect.Pointer: | ||||
| 		n = nameOf(reflect.Indirect(v).Interface()) | ||||
| 	default: | ||||
| 		n = v.Type().Name() | ||||
| 	} | ||||
| 	return n | ||||
| } | ||||
| 
 | ||||
| func valueOf(i interface{}) reflect.Value { | ||||
| 	v := reflect.ValueOf(i) | ||||
| 	if v.Type().Kind() == reflect.Slice || v.Type().Kind() == reflect.Map { | ||||
| 		in := v.Type().Elem() | ||||
| 		switch in.Kind() { | ||||
| 		case reflect.Pointer: | ||||
| 			v = reflect.New(in.Elem()).Elem() | ||||
| 		default: | ||||
| 			v = reflect.New(in).Elem() | ||||
| 		} | ||||
| 	} else if v.Type().Kind() == reflect.Pointer { | ||||
| 		v = valueOf(reflect.Indirect(v).Interface()) | ||||
| 	} | ||||
| 	return v | ||||
| } | ||||
| 
 | ||||
| func iFace(input interface{}) interface{} { | ||||
| 	return reflect.ValueOf(input).Interface() | ||||
| } | ||||
| 
 | ||||
| func iFaceSlice(input interface{}) []interface{} { | ||||
| 	ret := make([]interface{}, 0) | ||||
| 	fv := reflect.ValueOf(input) | ||||
| 	if fv.Type().Kind() != reflect.Slice { | ||||
| 		return ret | ||||
| 	} | ||||
| 	for i := 0; i < fv.Len(); i++ { | ||||
| 		ret = append(ret, fv.Index(i).Interface()) | ||||
| 	} | ||||
| 	return ret | ||||
| } | ||||
| 
 | ||||
| func coerceInt(input reflect.Value, dst reflect.Value) interface{} { | ||||
| 	if input.Type().Kind() == reflect.Pointer { | ||||
| 		input = input.Elem() | ||||
| 	} | ||||
| 	if dst.Type().Kind() == reflect.Pointer { | ||||
| 		dst = dst.Elem() | ||||
| 	} | ||||
| 	if input.Type().ConvertibleTo(dst.Type()) { | ||||
| 		return input.Convert(dst.Type()).Interface() | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user