Initial commit
This commit is contained in:
299
persist.go
Normal file
299
persist.go
Normal file
@@ -0,0 +1,299 @@
|
||||
// Package persist offers a convinient way to interact with the database. It takes into consideration multitenancy.
|
||||
package persist
|
||||
|
||||
import (
|
||||
"log"
|
||||
"context"
|
||||
"fmt"
|
||||
"errors"
|
||||
"reflect"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
|
||||
"git.gsuntres.com/general/commons"
|
||||
"git.gsuntres.com/general/mongo"
|
||||
"git.gsuntres.com/general/structful"
|
||||
)
|
||||
|
||||
type PersistProps struct {
|
||||
MongoSysDb string
|
||||
}
|
||||
|
||||
// Persist is the root object for accessing data.
|
||||
type Persist struct {
|
||||
// IPersist
|
||||
|
||||
SysDb string
|
||||
}
|
||||
|
||||
var fields []reflect.StructField
|
||||
|
||||
var sysDb string
|
||||
|
||||
func NewPersist(props *PersistProps) *Persist {
|
||||
p := &Persist{
|
||||
}
|
||||
|
||||
p.SysDb = props.MongoSysDb
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
|
||||
var persistOriginal *Persist
|
||||
|
||||
var deferedFuncs map[string]any = make(map[string]any, 0)
|
||||
|
||||
var persist any
|
||||
|
||||
var caseString cases.Caser = cases.Title(language.English)
|
||||
|
||||
type InitProps struct {
|
||||
MongoSysDb string
|
||||
}
|
||||
|
||||
// Init runs on boot, reads system_collections from structful and registers them. No changes after boot are going to be applied.
|
||||
func Init(props *InitProps) {
|
||||
var persistProps PersistProps
|
||||
commons.CopyToStruct(props, &persistProps)
|
||||
persist = NewPersist(&persistProps)
|
||||
|
||||
sysDb = props.MongoSysDb
|
||||
|
||||
persistOriginal = NewPersist(&persistProps)
|
||||
|
||||
// Load structful
|
||||
s := structful.Current()
|
||||
|
||||
filter := map[string]any{}
|
||||
scol, err := s.FilterByGroup("system_collections", filter)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get system collections: %v", err)
|
||||
}
|
||||
|
||||
// TODO(me): WIP
|
||||
// Build a new struct similar to Persist
|
||||
|
||||
origType := reflect.TypeOf(persist)
|
||||
if origType.Kind() == reflect.Ptr {
|
||||
origType = origType.Elem()
|
||||
}
|
||||
|
||||
// 1. Copy all existing fields
|
||||
for i := 0; i < origType.NumField(); i++ {
|
||||
log.Printf("Add existing field: %v", origType.Field(i))
|
||||
fields = append(fields, origType.Field(i))
|
||||
}
|
||||
|
||||
client := mongo.GetMongoClient()
|
||||
// Take system_collection from structful and build the calls.
|
||||
for _, col := range scol {
|
||||
BuildInsertOne(col)
|
||||
BuildFindOne(col)
|
||||
BuildFind(col)
|
||||
BuildGetOne(col)
|
||||
BuildDeleteOne(col)
|
||||
|
||||
client.AddDefinition(col)
|
||||
}
|
||||
|
||||
// 3. Create the new struct type
|
||||
newStructType := reflect.StructOf(fields)
|
||||
|
||||
// 4. Create a new instance
|
||||
newStruct := reflect.New(newStructType).Elem()
|
||||
|
||||
for _, field := range fields {
|
||||
// log.Printf("FIELD %s", field.Name)
|
||||
if actualFunc, ok := deferedFuncs[field.Name]; ok {
|
||||
f := newStruct.FieldByName(field.Name)
|
||||
f.Set(reflect.ValueOf(actualFunc))
|
||||
}
|
||||
}
|
||||
|
||||
persist = newStruct.Addr().Interface()
|
||||
}
|
||||
|
||||
func GetCurrent() any {
|
||||
return persist
|
||||
}
|
||||
|
||||
func Orig() *Persist {
|
||||
return persistOriginal
|
||||
}
|
||||
|
||||
func Call(p any, name string, args... any) []reflect.Value {
|
||||
v := reflect.ValueOf(p)
|
||||
|
||||
// 1. Make sure it's the right type
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
// 2. Get field
|
||||
fn := v.FieldByName(name)
|
||||
|
||||
// 3. Validate
|
||||
if !fn.IsValid() {
|
||||
log.Fatalf("Failed to find %s field", name)
|
||||
}
|
||||
|
||||
if fn.Kind() != reflect.Func {
|
||||
log.Fatalf("%s is not a function", name)
|
||||
}
|
||||
|
||||
if fn.IsNil() {
|
||||
log.Fatalf("%s is nil", name)
|
||||
}
|
||||
|
||||
// 4. Prepare arguments
|
||||
vArgs := make([]reflect.Value, len(args))
|
||||
|
||||
for i, arg := range args {
|
||||
vArgs[i] = reflect.ValueOf(arg)
|
||||
}
|
||||
|
||||
// 5. Call and return results
|
||||
return fn.Call(vArgs)
|
||||
}
|
||||
|
||||
const SAVE_USER_DATA = `
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Fullname": {
|
||||
"type": "string"
|
||||
},
|
||||
"Firstname": {
|
||||
"type": "string"
|
||||
},
|
||||
"Username": {
|
||||
"type": "string",
|
||||
"minLength": 3
|
||||
},
|
||||
"Password": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"Email": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["Username", "Password"]
|
||||
}
|
||||
`
|
||||
|
||||
// SaveUser will encrypt the password before saving the user.
|
||||
func (p *Persist) SaveUser(ctx context.Context, data *User) (*User, error) {
|
||||
if valid := commons.Validate(SAVE_USER_DATA, data); valid != nil {
|
||||
return nil, errors.New(fmt.Sprintf("%s", valid.Error()))
|
||||
}
|
||||
|
||||
data.EncryptPassword()
|
||||
|
||||
client := mongo.GetMongoClient()
|
||||
|
||||
dataNew, err := client.InsertOneFromStruct(ctx, p.SysDb, USER_COLLECTION, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d, err := bson.Marshal(dataNew)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Unmarshal into struct
|
||||
var u User
|
||||
err = bson.Unmarshal(d, &u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &u, nil
|
||||
}
|
||||
|
||||
func (p *Persist) CheckUser(ctx context.Context, usernameOrEmail string, password string) (*User, error) {
|
||||
if len(usernameOrEmail) < 3 {
|
||||
return nil, errors.New("username or email too short")
|
||||
}
|
||||
|
||||
client := mongo.GetMongoClient()
|
||||
|
||||
filter := bson.M{
|
||||
"$or": bson.A{
|
||||
bson.M{"username": usernameOrEmail},
|
||||
bson.M{"email": usernameOrEmail},
|
||||
},
|
||||
}
|
||||
|
||||
found, err := client.FindOne(ctx, p.SysDb, USER_COLLECTION, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var user User
|
||||
if err := mongo.ToStruct(found, &user); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !user.CheckPassword(password) {
|
||||
return nil, errors.New("invalid credentials")
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
const SAVE_ACCOUNT_DATA = `
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Code": {
|
||||
"type": "string",
|
||||
"minLength": 6
|
||||
},
|
||||
"Owner": {
|
||||
"type": "string",
|
||||
"minLength": 6
|
||||
}
|
||||
},
|
||||
"required": ["Code", "Owner"]
|
||||
}
|
||||
`
|
||||
|
||||
func (p *Persist) SaveAccount(ctx context.Context, data *Account) (*Account, error) {
|
||||
if valid := commons.Validate(SAVE_ACCOUNT_DATA, data); valid != nil {
|
||||
return nil, errors.New(fmt.Sprintf("%s", valid.Error()))
|
||||
}
|
||||
|
||||
client := mongo.GetMongoClient()
|
||||
|
||||
dataNew, err := client.InsertOneFromStruct(ctx, p.SysDb, ACCOUNT_COLLECTION, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var o Account
|
||||
mapstructure.Decode(dataNew, &o)
|
||||
|
||||
return &o, nil
|
||||
}
|
||||
|
||||
func (p *Persist) GetAccountByCode(ctx context.Context, code string) (*Account, error) {
|
||||
client := mongo.GetMongoClient()
|
||||
|
||||
filter := bson.M{"code": code}
|
||||
found, err := client.FindOne(ctx, p.SysDb, ACCOUNT_COLLECTION, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var acc Account
|
||||
if err := mongo.ToStruct(found, &acc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &acc, nil
|
||||
}
|
||||
Reference in New Issue
Block a user