308 lines
6.0 KiB
Go
308 lines
6.0 KiB
Go
// 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.StructToStruct(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
|
|
}
|
|
|
|
var ErrCall error = errors.New("failed to call")
|
|
|
|
func Call(p any, name string, args... any) ([]reflect.Value, error) {
|
|
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.Printf("Failed to find %s field", name)
|
|
|
|
return nil, ErrCall
|
|
}
|
|
|
|
if fn.Kind() != reflect.Func {
|
|
log.Printf("%s is not a function", name)
|
|
|
|
return nil, ErrCall
|
|
}
|
|
|
|
if fn.IsNil() {
|
|
log.Printf("%s is nil", name)
|
|
|
|
return nil, ErrCall
|
|
}
|
|
|
|
// 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), nil
|
|
}
|
|
|
|
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
|
|
}
|