diff --git a/os/src/api/auth.ts b/os/src/api/auth.ts index 59946ab..e8de3c3 100644 --- a/os/src/api/auth.ts +++ b/os/src/api/auth.ts @@ -14,15 +14,9 @@ export function loginIn(params: any) { }) } export async function logout() { - return post('user/logout', { - method: 'POST', - }) + return get('user/logout') } export async function isLogin() { - const token = getToken() - if (!token) { - return false - } const res = await get('user/islogin') return res.success } @@ -31,15 +25,13 @@ export async function getDingConf() { "https://g.alicdn.com/dingding/h5-dingtalk-login/0.21.0/ddlogin.js" ); - const res = await fetch("user/ding/conf"); - return await res.json(); + return await get("user/ding/conf"); } export async function getThirdpartyList() { - const result = await fetch("/user/thirdparty/list" + const result = await get("/user/thirdparty/list" ); - if (result.ok) { - const data = await result.json(); - if (data.success) return data.data.list; + if (result.success) { + return result.data.list; } return []; }; @@ -48,23 +40,16 @@ export async function getEmailCode(email: string) { email: email, client_id: getClientId(), } - const res = await fetch('/user/emailcode', { - method: 'POST', - body: JSON.stringify(data), - }) - return await res.json() + return await post('/user/emailcode', data) } export async function getSmsCode(phone: string) { const data = { phone: phone, client_id: getClientId(), } - const res = await fetch('/user/smscode', { - method: 'POST', - body: JSON.stringify(data), - }) - return await res.json() + return await post('/user/smscode', data) } export async function register(params: any) { + console.log(params) return post('/user/register', params) } \ No newline at end of file diff --git a/os/src/components/auth/UserRegister.vue b/os/src/components/auth/UserRegister.vue index df47868..94eb8fc 100644 --- a/os/src/components/auth/UserRegister.vue +++ b/os/src/components/auth/UserRegister.vue @@ -104,9 +104,15 @@ const handleCaptchaConfirm = (success: boolean) => { //captchaPassed.value = true; showCaptcha.value = false; store.onRegister({ - loginType: "password", + login_type: "password", action: "register", - params: registerInfo.value, + param: { + username: registerInfo.value.username, + password: registerInfo.value.password, + // nickname: registerInfo.value.nickname, + // email: registerInfo.value.email, + // phone: registerInfo.value.phone, + }, }) // 隐藏验证码 } else { diff --git a/os/src/stores/login.ts b/os/src/stores/login.ts index abba90c..f34dcec 100644 --- a/os/src/stores/login.ts +++ b/os/src/stores/login.ts @@ -42,8 +42,8 @@ export const useLoginStore = defineStore('login', () => { thirdPartyLoginMethod.value = 'password' } const onRegister = async (params: any) => { - delete params.param.confirmPassword - + //delete params.param.confirmPassword + params.client_id = getClientId() const res = await auth.register(params) if (res.success) { successMsg('注册成功') diff --git a/os/src/utils/request.ts b/os/src/utils/request.ts index 6179d8d..6263067 100644 --- a/os/src/utils/request.ts +++ b/os/src/utils/request.ts @@ -84,7 +84,7 @@ request.interceptors.response.use( (err) => { // 处理错误信息 if (err) { - console.log(err) + //console.log(err) if (err.response) { const status = err.response.status; const message = err.response.data; @@ -134,7 +134,7 @@ export const get = ( url: string, params?: RequestParams ): Promise> => { - console.log('get', url, params) + //console.log('get', url, params) return request({ method: 'GET', url, diff --git a/server/app/user/auth.go b/server/app/user/auth.go index e65f708..4b01c81 100644 --- a/server/app/user/auth.go +++ b/server/app/user/auth.go @@ -18,8 +18,8 @@ import ( func init() { middleware.RegisterRouter("POST", "user/login", loginHandle, 0, "用户登录") - middleware.RegisterRouter("POST", "user/logout", logoutHandler, 1, "退出登录") - middleware.RegisterRouter("POST", "user/register", handleRegister, 0, "用户注册") + middleware.RegisterRouter("GET", "user/logout", logoutHandler, 1, "退出登录") + middleware.RegisterRouter("POST", "user/register", registerHandle, 0, "用户注册") middleware.RegisterRouter("GET", "user/islogin", isLogin, 1, "是否登录") } @@ -46,6 +46,7 @@ func loginHandle(c *gin.Context) { libs.Error(c, "登录失败:"+err.Error()) return } + // 验证用户 userDept, userRole, err := validateUser(c, user, req.ClientId) if err != nil { @@ -186,11 +187,11 @@ func logoutHandler(c *gin.Context) { } // handleRegister 处理注册请求 -func handleRegister(c *gin.Context) { +func registerHandle(c *gin.Context) { var req login.LoginRequest // 解析请求体 if err := c.ShouldBindJSON(&req); err != nil { - libs.Error(c, "参数错误,注册失败") + libs.Error(c, "参数错误,注册失败:"+err.Error()) return } diff --git a/server/app/user/login/password.go b/server/app/user/login/login_password.go similarity index 67% rename from server/app/user/login/password.go rename to server/app/user/login/login_password.go index a035c3c..051fa03 100644 --- a/server/app/user/login/password.go +++ b/server/app/user/login/login_password.go @@ -5,11 +5,9 @@ import ( "errors" "godocms/common" "godocms/model" - "godocms/pkg/db" + "godocms/service" "godocms/utils" "time" - - "gorm.io/gorm" ) type PasswordLoginHandler struct { @@ -26,9 +24,9 @@ func (h *PasswordLoginHandler) Login() (user *model.User, err error) { return nil, errors.New("登录已关闭") } //log.Printf("用户登录: %+v", h.loginParam.Password) - user = new(model.User) - if err := db.DB.Where("username = ?", h.loginParam.Username).First(&user).Error; err != nil { - return nil, errors.New("用户不存在") + user, err = service.GetUserByUsername(h.loginParam.Username) + if err != nil { + return nil, err } if utils.HashPassword(h.loginParam.Password, user.Salt) != user.Password { return nil, errors.New("密码错误") @@ -40,9 +38,11 @@ func (h *PasswordLoginHandler) Login() (user *model.User, err error) { func (h *PasswordLoginHandler) Register() (user *model.User, err error) { //log.Printf("用户登录: %+v", h.loginParam) // 检查用户名和邮箱是否已存在 - var existingUser model.User - result := db.DB.Where("username = ?", h.loginParam.Username).First(&existingUser) - if result.RowsAffected > 0 { + //log.Printf("=====用户登录: %+v", h.loginParam) + existingUser, err := service.GetUserByUsername(h.loginParam.Username) + // log.Printf("=====用户登录: %+v", existingUser) + // log.Printf("=====用户登录: %+v", err) + if err == nil || existingUser != nil { return nil, errors.New("用户名或邮箱/手机号已存在") } salt, err := utils.GenerateSalt(16) @@ -63,24 +63,12 @@ func (h *PasswordLoginHandler) Register() (user *model.User, err error) { AddTime: int32(time.Now().Unix()), UpTime: 0, } - - err = db.DB.Transaction(func(tx *gorm.DB) error { - if err := tx.Create(&user).Error; err != nil { - return err - } - - thirdInfo := &model.UserThird{ - UserID: user.ID, - Platform: LoginPlatformPassword, - ThirdUserID: h.loginParam.Username, - UnionID: h.loginParam.Username, - } - if err := tx.Create(thirdInfo).Error; err != nil { - return err - } - - return nil - }) + thirdInfo := &model.UserThird{ + Platform: LoginPlatformPassword, + ThirdUserID: h.loginParam.Username, + UnionID: h.loginParam.Username, + } + user, err = service.AddUser(user, thirdInfo) if err != nil { return user, err } diff --git a/server/app/user/login/sms.go b/server/app/user/login/login_sms.go similarity index 100% rename from server/app/user/login/sms.go rename to server/app/user/login/login_sms.go diff --git a/server/go.mod b/server/go.mod index 877a8e1..f85752a 100644 --- a/server/go.mod +++ b/server/go.mod @@ -3,6 +3,7 @@ module godocms go 1.23.8 require ( + github.com/DATA-DOG/go-sqlmock v1.5.2 github.com/EndFirstCorp/peekingReader v0.0.0-20171012052444-257fb6f1a1a6 github.com/btcsuite/btcutil v1.0.2 github.com/dgrijalva/jwt-go v3.2.0+incompatible diff --git a/server/go.sum b/server/go.sum index 4971fc3..4424d2c 100644 --- a/server/go.sum +++ b/server/go.sum @@ -1,4 +1,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= +github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/EndFirstCorp/peekingReader v0.0.0-20171012052444-257fb6f1a1a6 h1:t27CGFMv8DwGwqRPEa2VNof5I/aZwO6q2gfJhN8q0U4= github.com/EndFirstCorp/peekingReader v0.0.0-20171012052444-257fb6f1a1a6/go.mod h1:zpqkXxDsVfEIUZEWvT9yAo8OmRvSlRrcYQ3Zs8sSubA= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= @@ -140,6 +142,7 @@ github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlT github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= diff --git a/server/model/auto_migrate.go b/server/model/auto_migrate.go index 88c0da1..790be0d 100644 --- a/server/model/auto_migrate.go +++ b/server/model/auto_migrate.go @@ -16,6 +16,9 @@ func AutoMigrate() error { db.DB.AutoMigrate(&UserRole{}) db.DB.AutoMigrate(&UserDept{}) db.DB.AutoMigrate(&UserThird{}) + if err := EnsureDefaultUserRoleExists(); err != nil { + log.Fatalf("初始化用户角色失败: %v", err) + } return nil } func CreateIndexIfNotExists(Db *gorm.DB, model interface{}, field, indexName string) { diff --git a/server/model/user_role.go b/server/model/user_role.go index b76e112..3377b5a 100644 --- a/server/model/user_role.go +++ b/server/model/user_role.go @@ -1,5 +1,12 @@ package model +import ( + "fmt" + "godocms/pkg/db" + "log" + "time" +) + const TableNameUserRole = "user_role" // UserRole 用户角色表 @@ -21,3 +28,34 @@ type UserRole struct { func (*UserRole) TableName() string { return TableNameUserRole } +func EnsureDefaultUserRoleExists() error { + var count int64 + err := db.DB.Model(&UserRole{}).Count(&count).Error + if err != nil { + log.Fatalf("Count failed on %s table: %v", TableNameUserRole, err) + return err + } + //log.Printf("Total roles in %s table: %d", TableNameUserRole, count) + + if count == 0 { + defaultRole := UserRole{ + ID: 1, + GroupID: 1, + Name: "默认角色", + Rules: "-1", + Space: 1024, + Status: 0, + Remark: "-1", + MenuIDS: 123456, // 修正超出 int32 范围的问题 + DingRoleID: "", + AddTime: int32(time.Now().Unix()), + UpTime: 0, + } + + if err := db.DB.Create(&defaultRole).Error; err != nil { + return fmt.Errorf("failed to create default user role: %w", err) + } + } + + return nil +} diff --git a/server/pkg/db/database.go b/server/pkg/db/database.go index e4ea01e..41942c2 100644 --- a/server/pkg/db/database.go +++ b/server/pkg/db/database.go @@ -11,6 +11,7 @@ import ( "gorm.io/driver/postgres" "gorm.io/driver/sqlite" "gorm.io/gorm" + "gorm.io/gorm/logger" ) type MySQL struct { @@ -128,7 +129,9 @@ func InitMySQL() error { AppConfig.MySQL.Port, AppConfig.MySQL.DBName) - db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) + db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Info), + }) if err != nil { return err } @@ -147,7 +150,9 @@ func InitPostgreSQL() error { AppConfig.PostgreSQL.DBName, AppConfig.PostgreSQL.SSLMode) - db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) + db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Info), + }) if err != nil { return err } @@ -158,7 +163,9 @@ func InitPostgreSQL() error { } func InitSQLite() error { - db, err := gorm.Open(sqlite.Open(AppConfig.SQLite.Path), &gorm.Config{}) + db, err := gorm.Open(sqlite.Open(AppConfig.SQLite.Path), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Info), + }) if err != nil { return err } @@ -174,7 +181,9 @@ func InitMongoDB() error { Database: AppConfig.MongoDB.Database, } var err error - MongoDB, err = gorm.Open(dialector, &gorm.Config{}) + MongoDB, err = gorm.Open(dialector, &gorm.Config{ + Logger: logger.Default.LogMode(logger.Info), + }) if err != nil { return err } diff --git a/server/pkg/db/mongodm/adapter.go b/server/pkg/db/mongodm/adapter.go index 740818e..29a2f07 100644 --- a/server/pkg/db/mongodm/adapter.go +++ b/server/pkg/db/mongodm/adapter.go @@ -6,6 +6,7 @@ import ( "database/sql" "database/sql/driver" "fmt" + "reflect" "time" "go.mongodb.org/mongo-driver/bson" @@ -142,22 +143,114 @@ func InitCounter(client *mongo.Client, dbName, collectionName string) error { }) return err } -func GetNextID(client *mongo.Client, dbName, collectionName string) (int64, error) { + +// CounterDocument 用于表示 counters 集合中的文档 +type CounterDocument struct { + ID string `bson:"_id"` // 对应 collection name + Seq int64 `bson:"seq"` // 序列号 +} + +// GetNextID 获取指定 collection 的下一个自增 ID +func GetNextID(ctx context.Context, client *mongo.Client, dbName, collectionName string) (int64, error) { counterColl := client.Database(dbName).Collection("counters") + // 创建 filter 和 update filter := bson.M{"_id": collectionName} update := bson.M{"$inc": bson.M{"seq": 1}} + + // 设置 FindOneAndUpdate 选项:返回更新后的文档 opts := options.FindOneAndUpdate().SetReturnDocument(options.After) - var result struct { - ID string `bson:"_id"` - Seq int64 `bson:"seq"` - } + var result CounterDocument - err := counterColl.FindOneAndUpdate(context.Background(), filter, update, opts).Decode(&result) + err := counterColl.FindOneAndUpdate(ctx, filter, update, opts).Decode(&result) if err != nil { - return 0, err + if err == mongo.ErrNoDocuments { + // 如果计数器不存在,自动创建初始值 + _, insertErr := counterColl.InsertOne(ctx, bson.M{ + "_id": collectionName, + "seq": int64(1), + }) + if insertErr != nil { + return 0, fmt.Errorf("failed to initialize counter for %s: %w", collectionName, insertErr) + } + return 1, nil + } + // 其他错误 + return 0, fmt.Errorf("failed to get next id for %s: %w", collectionName, err) } return result.Seq, nil } + +// 判断是否是零值 +func isZeroValue(v reflect.Value) bool { + switch v.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.String: + return v.String() == "" + case reflect.Bool: + return !v.Bool() + case reflect.Ptr, reflect.Interface: + return v.IsNil() + default: + return false + } +} + +// 将字符串解析为对应类型 +func parseDefaultValue(typ reflect.Type, valueStr string) (interface{}, error) { + // 新增 CURRENT_TIMESTAMP 支持 + if valueStr == "CURRENT_TIMESTAMP" { + if typ.Kind() == reflect.Struct && typ == reflect.TypeOf(time.Time{}) { + return time.Now(), nil + } + return nil, fmt.Errorf("CURRENT_TIMESTAMP only supported for time.Time fields") + } + + switch typ.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + var val int64 + _, err := fmt.Sscanf(valueStr, "%d", &val) + if err != nil { + return nil, err + } + return reflect.ValueOf(val).Convert(typ).Interface(), nil + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + var val uint64 + _, err := fmt.Sscanf(valueStr, "%d", &val) + if err != nil { + return nil, err + } + return reflect.ValueOf(val).Convert(typ).Interface(), nil + + case reflect.String: + return valueStr, nil + + case reflect.Bool: + switch valueStr { + case "true", "1": + return true, nil + case "false", "0": + return false, nil + default: + return nil, fmt.Errorf("invalid boolean value: %s", valueStr) + } + + default: + return nil, fmt.Errorf("unsupported type: %s", typ.Kind()) + } +} +func mapBSONToStruct(src bson.M, dst interface{}) error { + data, err := bson.Marshal(src) + if err != nil { + return err + } + return bson.Unmarshal(data, dst) +} diff --git a/server/pkg/db/mongodm/dialector.go b/server/pkg/db/mongodm/dialector.go index b4e910f..70689d7 100644 --- a/server/pkg/db/mongodm/dialector.go +++ b/server/pkg/db/mongodm/dialector.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "reflect" - "strings" "time" "go.mongodb.org/mongo-driver/bson" @@ -51,7 +50,8 @@ func (d *MongoDBDialector) Initialize(db *gorm.DB) error { db.ClauseBuilders["WHERE"] = d.buildWhereClause db.ClauseBuilders["LIMIT"] = d.buildLimitClause db.ClauseBuilders["ORDER BY"] = d.buildOrderByClause - + db.ClauseBuilders["SELECT"] = d.buildSelectClause + db.ClauseBuilders["COUNT"] = d.buildCountClause // 注册 CURD 回调 db.Callback().Create().Replace("gorm:create", d.createCallback) db.Callback().Query().Replace("gorm:query", d.queryCallback) @@ -59,6 +59,18 @@ func (d *MongoDBDialector) Initialize(db *gorm.DB) error { db.Callback().Delete().Replace("gorm:delete", d.deleteCallback) db.Dialector = d + // 注册查询完成后的回调函数 + // db.Callback().Query().After("gorm:query").Register("track_count", func(db *gorm.DB) { + // // 检查是否为 count 查询 + // if isCountQuery(db.Statement.Clauses["SELECT"].Expression) { + // // 获取 count 值 + // if dest, ok := db.Statement.Dest.(*int64); ok { + // log.Printf("===Count result: %d", *dest) + // } else { + // log.Printf("db.Statement.Dest is not a pointer to int64") + // } + // } + // }) return nil } @@ -133,6 +145,9 @@ func (d MongoDBDialector) buildOrderByClause(c clause.Clause, builder clause.Bui } } } +func (d MongoDBDialector) buildCountClause(c clause.Clause, builder clause.Builder) { + builder.WriteString(" COUNT(*) ") +} // ======================= // CURD Callbacks @@ -145,14 +160,16 @@ func (d MongoDBDialector) createCallback(db *gorm.DB) { // 获取模型字段信息 stmt := db.Statement for _, field := range stmt.Schema.Fields { - if field.AutoIncrementIncrement == 0 && field.TagSettings["AUTOINCREMENT"] == "true" { + // log.Printf("TagSettings: %+v\n", field.TagSettings) + // log.Printf("FieldType: %+v\n", field.AutoIncrementIncrement) + if field.TagSettings["AUTOINCREMENT"] == "true" { // 如果是 autoIncrement 主键且值为空,则生成新 ID - nextID, err := GetNextID(d.Client, d.Database, collectionName) + nextID, err := GetNextID(context.Background(), d.Client, d.Database, collectionName) if err != nil { db.AddError(err) return } - + //log.Printf("Got next ID: %d for collection: %s", nextID, collectionName) // 设置自增 ID 到模型中(使用新的 Set 接口) val := reflect.ValueOf(nextID).Convert(field.FieldType).Interface() if err := field.Set(context.Background(), stmt.ReflectValue, val); err != nil { @@ -161,29 +178,42 @@ func (d MongoDBDialector) createCallback(db *gorm.DB) { } } } + // 第二步:处理 default tag + // 处理 default tag(包括 CURRENT_TIMESTAMP) + now := time.Now() + for _, field := range stmt.Schema.Fields { + defaultValueStr := field.TagSettings["DEFAULT"] + if defaultValueStr == "" { + continue + } - _, err := coll.InsertOne(context.Background(), stmt.Model) - if err != nil { - db.AddError(err) - } -} - -func (d MongoDBDialector) queryCallback(db *gorm.DB) { - collectionName := db.Statement.Table - coll := db.ConnPool.(*mongoConnPool).Collection(collectionName) - // 构建聚合管道(传入 dest) - dest := db.Statement.Dest - pipeline := buildMongoPipeline(db.Statement.Clauses, dest) + fieldValue := field.ReflectValueOf(context.Background(), stmt.ReflectValue) + if !fieldValue.IsValid() || isZeroValue(fieldValue) { + var defaultVal interface{} + if defaultValueStr == "CURRENT_TIMESTAMP" { + if field.FieldType == reflect.TypeOf(time.Time{}) { + defaultVal = now + } else { + db.AddError(fmt.Errorf("CURRENT_TIMESTAMP can only be used with time.Time fields")) + return + } + } else { + var err error + defaultVal, err = parseDefaultValue(field.FieldType, defaultValueStr) + if err != nil { + db.AddError(fmt.Errorf("failed to parse default value for %s: %w", field.Name, err)) + return + } + } - // 执行聚合查询 - cursor, err := coll.Aggregate(context.Background(), pipeline) - if err != nil { - db.AddError(err) - return + if err := field.Set(context.Background(), stmt.ReflectValue, defaultVal); err != nil { + db.AddError(err) + return + } + } } - // 将结果映射到目标结构体 - err = cursor.All(context.Background(), db.Statement.Dest) + _, err := coll.InsertOne(context.Background(), stmt.Model) if err != nil { db.AddError(err) } @@ -201,7 +231,24 @@ func (d MongoDBDialector) updateCallback(db *gorm.DB) { } } + // 构建更新数据 updateData := bson.M{"$set": db.Statement.Model} + + // 检查是否有字段标记为 CURRENT_TIMESTAMP + updateMap := updateData["$set"].(map[string]interface{}) + for _, field := range db.Statement.Schema.Fields { + tagDefault := field.TagSettings["DEFAULT"] + if tagDefault == "CURRENT_TIMESTAMP" { + if fieldValue := field.ReflectValueOf(context.Background(), db.Statement.ReflectValue); fieldValue.IsValid() { + if fieldValue.IsZero() || isZeroValue(fieldValue) { + updateMap[field.DBName] = time.Now() + } + } else { + updateMap[field.DBName] = time.Now() + } + } + } + _, err := coll.UpdateMany(context.Background(), filter, updateData) if err != nil { db.AddError(err) @@ -226,193 +273,6 @@ func (d MongoDBDialector) deleteCallback(db *gorm.DB) { } } -// ======================= -// 工具函数:将 Where/Join/GroupBy 等转换为聚合 Pipeline -// ======================= -func buildMongoPipeline(clauses map[string]clause.Clause, dest interface{}) []bson.M { - var pipeline []bson.M - - // 处理 WHERE 条件 - if whereClause, ok := clauses["WHERE"].Expression.(clause.Where); ok { - filter := convertWhereToBSON(whereClause) - if len(filter) > 0 { - pipeline = append(pipeline, bson.M{"$match": filter}) - } - } - // 处理 JOIN - if joinClause, ok := clauses["JOIN"].Expression.(clause.Join); ok { - lookupStage := buildJoinPipeline(joinClause, dest) - if lookupStage != nil { - pipeline = append(pipeline, lookupStage) - - // INNER JOIN 处理 - if joinClause.Type == clause.InnerJoin { - as := getNestedFieldName(dest, joinClause.Table.Name) - if as == "" { - as = strings.ToLower(joinClause.Table.Name) + "s" - } - unwindStage := bson.M{"$unwind": "$" + as} - pipeline = append(pipeline, unwindStage) - } - - // RIGHT JOIN 处理 - if joinClause.Type == clause.RightJoin { - as := getNestedFieldName(dest, joinClause.Table.Name) - if as == "" { - as = strings.ToLower(joinClause.Table.Name) + "s" - } - - // 展开嵌套字段 - unwindStage := bson.M{"$unwind": "$" + as} - - // 处理 NULL 值,确保右表记录始终存在 - addFieldsStage := bson.M{ - "$addFields": bson.M{ - as: bson.M{ - "$ifNull": []interface{}{ - "$" + as, - bson.M{"$literal": []interface{}{}}, - }, - }, - }, - } - - pipeline = append(pipeline, unwindStage) - pipeline = append(pipeline, addFieldsStage) - } - } - } - - // 可选:处理 GroupBy、Select、Sort 等聚合操作 - // 示例:GroupBy + Count - if groupByClause, ok := clauses["GROUP BY"].Expression.(clause.GroupBy); ok { - groupFields := bson.M{"_id": nil} - for _, item := range groupByClause.Columns { - groupFields["_id"] = "$" + getColumnName(item) - } - groupFields["count"] = bson.M{"$sum": 1} - pipeline = append(pipeline, bson.M{"$group": groupFields}) - } - - return pipeline -} -func buildJoinPipeline(join clause.Join, dest interface{}) bson.M { - // 默认值 - localField := "" - foreignField := "" - //as := join.As - from := join.Table.Name - - // 遍历 ON 条件 - for _, expr := range join.ON.Exprs { - if eq, ok := expr.(clause.Eq); ok { - // 处理左侧 Column - if lc, lok := eq.Column.(clause.Column); lok { - localField = lc.Name - } - - // 处理右侧 Value(假设是 string 类型的字段名) - if strVal, ok := eq.Value.(string); ok { - foreignField = strVal - } - } - } - - // 如果无法提取字段,则返回空 - if localField == "" || foreignField == "" { - return nil - } - // 从 dest 结构体中获取嵌套字段名(如 Posts) - as := getNestedFieldName(dest, from) - if as == "" { - as = strings.ToLower(from) + "s" // 默认命名规则 - } - // 返回 $lookup stage - return bson.M{ - "$lookup": bson.M{ - "from": from, - "localField": localField, - "foreignField": foreignField, - "as": as, - }, - } -} -func buildUnwindPipeline(as string) bson.M { - return bson.M{"$unwind": "$" + as} -} -func getNestedFieldName(dest interface{}, collectionName string) string { - destType := reflect.TypeOf(dest).Elem() - - for i := 0; i < destType.NumField(); i++ { - field := destType.Field(i) - tag := field.Tag.Get("gorm") - - if tag != "" { - gormTag := parseGormTag(tag) - if gormTag["foreignKey"] == collectionName { - return field.Name - } - } - } - - return "" -} - -// ======================= -// 工具函数:将 Where 转换为 BSON -// ======================= -func convertWhereToBSON(where clause.Where) bson.M { - filter := bson.M{} - - for _, cond := range where.Exprs { - switch expr := cond.(type) { - case clause.Eq: - if col, ok := expr.Column.(clause.Column); ok { - key := getColumnName(col) - filter[key] = expr.Value - } - case clause.Neq: - if col, ok := expr.Column.(clause.Column); ok { - key := getColumnName(col) - filter[key] = bson.M{"$ne": expr.Value} - } - case clause.Gt: - if col, ok := expr.Column.(clause.Column); ok { - key := getColumnName(col) - filter[key] = bson.M{"$gt": expr.Value} - } - case clause.Gte: - if col, ok := expr.Column.(clause.Column); ok { - key := getColumnName(col) - filter[key] = bson.M{"$gte": expr.Value} - } - case clause.Lt: - if col, ok := expr.Column.(clause.Column); ok { - key := getColumnName(col) - filter[key] = bson.M{"$lt": expr.Value} - } - case clause.Lte: - if col, ok := expr.Column.(clause.Column); ok { - key := getColumnName(col) - filter[key] = bson.M{"$lte": expr.Value} - } - case clause.IN: - if col, ok := expr.Column.(clause.Column); ok { - key := getColumnName(col) - filter[key] = bson.M{"$in": expr.Values} - } - case clause.Like: - if col, ok := expr.Column.(clause.Column); ok { - key := getColumnName(col) - filter[key] = bson.M{"$regex": expr.Value} - } - default: - // 可选:处理未知条件或记录日志 - } - } - return filter -} - // 辅助函数获取字段名(带表前缀) func getColumnName(column clause.Column) string { if column.Table == "" { @@ -451,3 +311,16 @@ func (d *MongoDBDialector) Rollback(db *gorm.DB) (err error) { sess.EndSession(context.Background()) return err } + +// buildSelectClause 构建 SELECT 投影表达式 +func (d MongoDBDialector) buildSelectClause(c clause.Clause, builder clause.Builder) { + if selectClause, ok := c.Expression.(clause.Select); ok { + builder.WriteString("SELECT ") + for i, column := range selectClause.Columns { + if i > 0 { + builder.WriteString(", ") + } + d.QuoteTo(builder, column.Name) + } + } +} diff --git a/server/pkg/db/mongodm/query.go b/server/pkg/db/mongodm/query.go new file mode 100644 index 0000000..7338bfe --- /dev/null +++ b/server/pkg/db/mongodm/query.go @@ -0,0 +1,330 @@ +package mongodm + +import ( + "context" + "fmt" + "log" + "reflect" + "strings" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +func (d MongoDBDialector) queryCallback(db *gorm.DB) { + collectionName := db.Statement.Table + coll := db.ConnPool.(*mongoConnPool).Collection(collectionName) + dest := db.Statement.Dest + stmt := db.Statement + // log.Printf("stmt.Clauses: %+v\n", stmt.Clauses) + //log.Printf("stmt.Clauses: %+v\n", stmt.Clauses["SELECT"].Expression) + // 检查是否有 GROUP BY 或 JOIN + hasGroupBy := false + if _, ok := stmt.Clauses["GROUP BY"]; ok { + hasGroupBy = true + } + if _, ok := stmt.Clauses["JOIN"].Expression.(clause.Join); ok { + // 所有类型的 JOIN(包括 LEFT JOIN)都触发聚合查询 + hasGroupBy = true + } + + var limit int + if limitClause, ok := stmt.Clauses["LIMIT"].Expression.(clause.Limit); ok { + if limitClause.Limit != nil { + limit = *limitClause.Limit + } else { + // First() 等隐式调用 LIMIT 1 + limit = 1 + } + } + + // 构建查询条件 + whereClause, hasWhere := stmt.Clauses["WHERE"].Expression.(clause.Where) + // log.Printf("Where: %+v\n", whereClause) + filter := bson.M{} + if hasWhere { + filter = convertWhereToBSON(whereClause) + + } + // 构建投影字段(SELECT) + var projection bson.M + if isCountQuery(stmt.Clauses["SELECT"].Expression) { + count, err := coll.CountDocuments(context.Background(), filter) + if err != nil { + db.AddError(fmt.Errorf("failed to count documents: %w", err)) + return + } + err = setValue(db.Statement.Dest, count) + if err != nil { + db.AddError(err) + return + } + db.RowsAffected = count + return + } + //log.Printf("stmt.Clauses: %+v\n", stmt.Clauses["SELECT"].Expression) + if selectClause, ok := stmt.Clauses["SELECT"].Expression.(clause.Select); ok { + // 判断是否是 count 查询 + exprStr := fmt.Sprintf("%v", selectClause) + log.Printf("exprStr: %s\n", exprStr) + + projection = convertSelectToBSON(selectClause) + } + + if hasGroupBy { + pipeline := buildMongoPipeline(stmt.Clauses, dest) + + cursor, err := coll.Aggregate(context.Background(), pipeline) + if err != nil { + db.AddError(err) + return + } + defer cursor.Close(context.Background()) + if limit == 1 { + // 聚合结果中取第一条 + if cursor.Next(context.Background()) { + if err := cursor.Decode(dest); err != nil { + db.AddError(err) + } + } else { + db.AddError(gorm.ErrRecordNotFound) + } + } else { + // 获取所有聚合结果 + if err := cursor.All(context.Background(), dest); err != nil { + db.AddError(err) + } + } + return + } + + if limit == 1 { + // 处理 .First() + opts := options.FindOne().SetProjection(projection) + var result bson.M + err := coll.FindOne(context.Background(), filter, opts).Decode(&result) + if err != nil { + if err == mongo.ErrNoDocuments { + db.AddError(gorm.ErrRecordNotFound) + } else { + db.AddError(err) + } + return + } + + // 将结果映射到原始 dest(例如 *User) + //log.Printf("first dest: %+v\n", dest) + if err := mapBSONToStruct(result, dest); err != nil { + db.AddError(err) + } + } else { + // 处理 .Find() + opts := options.Find().SetProjection(projection) + cursor, err := coll.Find(context.Background(), filter, opts) + if err != nil { + db.AddError(err) + return + } + defer cursor.Close(context.Background()) + + err = cursor.All(context.Background(), dest) + if err != nil { + db.AddError(err) + } + } +} + +// ======================= +// 工具函数:将 Where/Join/GroupBy 等转换为聚合 Pipeline +// ======================= +func buildMongoPipeline(clauses map[string]clause.Clause, dest interface{}) []bson.M { + var pipeline []bson.M + + // 处理 WHERE 条件 + if whereClause, ok := clauses["WHERE"].Expression.(clause.Where); ok { + filter := convertWhereToBSON(whereClause) + if len(filter) > 0 { + pipeline = append(pipeline, bson.M{"$match": filter}) + } + } + // 处理 JOIN + if joinClause, ok := clauses["JOIN"].Expression.(clause.Join); ok { + lookupStage := buildJoinPipeline(joinClause, dest) + if lookupStage != nil { + pipeline = append(pipeline, lookupStage) + + // INNER JOIN 处理 + if joinClause.Type == clause.InnerJoin { + as := getNestedFieldName(dest, joinClause.Table.Name) + if as == "" { + as = strings.ToLower(joinClause.Table.Name) + "s" + } + unwindStage := bson.M{"$unwind": "$" + as} + pipeline = append(pipeline, unwindStage) + } + + // RIGHT JOIN 处理 + if joinClause.Type == clause.RightJoin { + as := getNestedFieldName(dest, joinClause.Table.Name) + if as == "" { + as = strings.ToLower(joinClause.Table.Name) + "s" + } + + // 展开嵌套字段 + unwindStage := bson.M{"$unwind": "$" + as} + + // 处理 NULL 值,确保右表记录始终存在 + addFieldsStage := bson.M{ + "$addFields": bson.M{ + as: bson.M{ + "$ifNull": []interface{}{ + "$" + as, + bson.M{"$literal": []interface{}{}}, + }, + }, + }, + } + + pipeline = append(pipeline, unwindStage) + pipeline = append(pipeline, addFieldsStage) + } + } + } + // 可选:处理 GroupBy、Select、Sort 等聚合操作 + // 示例:GroupBy + Count + if groupByClause, ok := clauses["GROUP BY"].Expression.(clause.GroupBy); ok { + groupFields := bson.M{"_id": nil} + for _, item := range groupByClause.Columns { + groupFields["_id"] = "$" + getColumnName(item) + } + groupFields["count"] = bson.M{"$sum": 1} + pipeline = append(pipeline, bson.M{"$group": groupFields}) + } + + return pipeline +} +func buildJoinPipeline(join clause.Join, dest interface{}) bson.M { + // 默认值 + localField := "" + foreignField := "" + from := join.Table.Name + + for _, expr := range join.ON.Exprs { + if eq, ok := expr.(clause.Eq); ok { + if lc, lok := eq.Column.(clause.Column); lok { + localField = lc.Name + } + if strVal, ok := eq.Value.(string); ok { + foreignField = strVal + } + } + } + + if localField == "" || foreignField == "" { + return nil + } + + as := getNestedFieldName(dest, from) + if as == "" { + as = strings.ToLower(from) + "s" + } + + lookupStage := bson.M{ + "$lookup": bson.M{ + "from": from, + "localField": localField, + "foreignField": foreignField, + "as": as, + }, + } + + // 区分不同 JOIN 类型 + switch join.Type { + case clause.InnerJoin: + unwindStage := bson.M{"$unwind": "$" + as} + return bson.M{ + "$and": []bson.M{lookupStage, unwindStage}, + } + case clause.RightJoin: + addFieldsStage := bson.M{ + "$addFields": bson.M{ + as: bson.M{ + "$ifNull": []interface{}{ + "$" + as, + bson.M{"$literal": []interface{}{}}, + }, + }, + }, + } + unwindStage := bson.M{"$unwind": "$" + as} + return bson.M{ + "$and": []bson.M{lookupStage, addFieldsStage, unwindStage}, + } + case clause.LeftJoin: + // 左连接只需保留空数组作为默认值,不强制展开 + return lookupStage + default: + return lookupStage + } +} + +func getNestedFieldName(dest interface{}, collectionName string) string { + destType := reflect.TypeOf(dest).Elem() + + for i := 0; i < destType.NumField(); i++ { + field := destType.Field(i) + tag := field.Tag.Get("gorm") + + if tag != "" { + gormTag := parseGormTag(tag) + if gormTag["foreignKey"] == collectionName { + return field.Name + } + } + } + + return "" +} +func isCountQuery(expr clause.Expression) bool { + if exprStmt, ok := expr.(clause.Expr); ok { + exprStr := strings.ToLower(exprStmt.SQL) + //log.Printf("exprStr: %s\n", exprStr) + + if strings.Contains(exprStr, "count(") { + //log.Println("Intercepted COUNT(*) query") + return true + } + } + return false +} + +// 支持 *int64、*int、**int64、***int64 等各种嵌套指针 +func setValue(dest interface{}, count int64) error { + destVal := reflect.ValueOf(dest) + if destVal.Kind() != reflect.Ptr { + return fmt.Errorf("destination must be a pointer, got %s", destVal.Kind()) + } + + elem := destVal.Elem() + for elem.Kind() == reflect.Ptr { + if elem.IsNil() { + elem.Set(reflect.New(elem.Type().Elem())) + } + elem = elem.Elem() + } + + //log.Printf("Before SetInt: %v (type: %s)", elem.Interface(), elem.Kind()) + + switch elem.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + elem.SetInt(count) + default: + return fmt.Errorf("unsupported destination type: %s", elem.Kind()) + } + + //log.Printf("After SetInt: %v", elem.Interface()) + return nil +} diff --git a/server/pkg/db/mongodm/where.go b/server/pkg/db/mongodm/where.go new file mode 100644 index 0000000..4ff10a4 --- /dev/null +++ b/server/pkg/db/mongodm/where.go @@ -0,0 +1,221 @@ +package mongodm + +import ( + "reflect" + "strings" + + "go.mongodb.org/mongo-driver/bson" + "gorm.io/gorm/clause" +) + +// ======================= +// 工具函数:将 Where 转换为 BSON +// ======================= +func convertWhereToBSON(where clause.Where) bson.M { + filter := bson.M{} + + for _, cond := range where.Exprs { + switch expr := cond.(type) { + case clause.Eq: + if col, ok := expr.Column.(clause.Column); ok { + key := getColumnName(col) + filter[key] = expr.Value + } + case clause.Neq: + if col, ok := expr.Column.(clause.Column); ok { + key := getColumnName(col) + filter[key] = bson.M{"$ne": expr.Value} + } + case clause.Gt: + if col, ok := expr.Column.(clause.Column); ok { + key := getColumnName(col) + filter[key] = bson.M{"$gt": expr.Value} + } + case clause.Gte: + if col, ok := expr.Column.(clause.Column); ok { + key := getColumnName(col) + filter[key] = bson.M{"$gte": expr.Value} + } + case clause.Lt: + if col, ok := expr.Column.(clause.Column); ok { + key := getColumnName(col) + filter[key] = bson.M{"$lt": expr.Value} + } + case clause.Lte: + if col, ok := expr.Column.(clause.Column); ok { + key := getColumnName(col) + filter[key] = bson.M{"$lte": expr.Value} + } + case clause.IN: + if col, ok := expr.Column.(clause.Column); ok { + key := getColumnName(col) + filter[key] = bson.M{"$in": expr.Values} + } + case clause.Like: + if col, ok := expr.Column.(clause.Column); ok { + key := getColumnName(col) + filter[key] = bson.M{"$regex": expr.Value} + } + default: + if nativeCond, ok := cond.(clause.Expr); ok { + sqlStr := nativeCond.SQL + vals := nativeCond.Vars + + // 简单处理:支持形如 "column = ?" 或带 OR 的多个条件 + orClauses := parseNativeSQLConditions(sqlStr, vals) + + if len(orClauses) > 0 { + if _, exists := filter["$or"]; exists { + // 如果已有 $or,合并进去(用于 JOIN、GROUP BY 等复杂场景) + existingOr := filter["$or"].([]bson.M) + filter["$or"] = append(existingOr, orClauses...) + } else { + filter["$or"] = orClauses + } + } + } + } + } + return filter +} + +// convertSelectToBSON 将 Select clause 转换为 MongoDB 的 projection +func convertSelectToBSON(selectClause clause.Select) bson.M { + projection := bson.M{} + for _, col := range selectClause.Columns { + fieldName := getColumnName(col) + projection[fieldName] = 1 // 1 表示包含该字段 + } + return projection +} + +// 解析 SQL 片段,如 "username = ? OR email = ?",返回对应的 bson.M 列表 +// 支持更复杂的 SQL 表达式解析,包括:=, !=, >, <, >=, <=, IN, LIKE, IS NULL 等 +func parseNativeSQLConditions(sql string, values []interface{}) []bson.M { + var conditions []bson.M + sql = strings.TrimSpace(sql) + + // 分割 OR 条件 + orParts := splitSQLCondition(sql, "OR") + for _, orPart := range orParts { + orPart = strings.TrimSpace(orPart) + + // 处理单个 OR 子句中的 AND 条件(可嵌套) + andParts := splitSQLCondition(orPart, "AND") + var andConditions []bson.M + + for _, andPart := range andParts { + andPart = strings.TrimSpace(andPart) + if andPart == "" { + continue + } + + cond := parseSingleCondition(andPart, values, len(conditions)+len(andConditions)) + if cond != nil { + andConditions = append(andConditions, cond) + } + } + + if len(andConditions) > 1 { + // 如果有多个 AND 条件,合并为一个 bson.M + andFilter := bson.M{} + for _, c := range andConditions { + for k, v := range c { + andFilter[k] = v + } + } + conditions = append(conditions, andFilter) + } else if len(andConditions) == 1 { + conditions = append(conditions, andConditions[0]) + } + } + + return conditions +} + +// 分割 SQL 条件(支持 AND / OR) +func splitSQLCondition(sql string, separator string) []string { + // 简化处理,避免破坏带引号的字符串中可能含有的 AND/OR + // 实际生产环境可用 SQL 解析器或正则表达式加强匹配 + return strings.Split(strings.ToLower(sql), strings.ToLower(separator)) +} + +// 解析单个 SQL 条件,返回对应的 bson.M +func parseSingleCondition(part string, values []interface{}, index int) bson.M { + part = strings.TrimSpace(part) + + // 跳过空条件 + if part == "" { + return nil + } + + // 匹配 IS NULL / IS NOT NULL + if strings.Contains(strings.ToUpper(part), "IS NULL") { + fieldName := strings.TrimSpace(strings.Split(strings.ToUpper(part), "IS NULL")[0]) + return bson.M{fieldName: nil} + } + if strings.Contains(strings.ToUpper(part), "IS NOT NULL") { + fieldName := strings.TrimSpace(strings.Split(strings.ToUpper(part), "IS NOT NULL")[0]) + return bson.M{fieldName: bson.M{"$ne": nil}} + } + + // 匹配 LIKE + if strings.Contains(strings.ToUpper(part), "LIKE") { + parts := strings.SplitN(part, "LIKE", 2) + if len(parts) == 2 && index < len(values) { + fieldName := strings.TrimSpace(parts[0]) + value := values[index] + if strVal, ok := value.(string); ok { + // 支持 %xxx% 的模糊匹配 + regexStr := strings.ReplaceAll(strVal, "%", ".*") + return bson.M{fieldName: bson.M{"$regex": regexStr, "$options": "i"}} + } + } + } + + // 匹配 IN + if strings.Contains(strings.ToUpper(part), "IN") { + parts := strings.SplitN(part, "IN", 2) + if len(parts) == 2 && index < len(values) { + fieldName := strings.TrimSpace(parts[0]) + if reflect.TypeOf(values[index]).Kind() == reflect.Slice { + slice := reflect.ValueOf(values[index]) + inValues := make([]interface{}, slice.Len()) + for i := 0; i < slice.Len(); i++ { + inValues[i] = slice.Index(i).Interface() + } + return bson.M{fieldName: bson.M{"$in": inValues}} + } + } + } + + // 匹配比较运算符:>, >=, <, <=, != + comparisonOp := map[string]string{ + "!=": "$ne", + ">": "$gt", + ">=": "$gte", + "<": "$lt", + "<=": "$lte", + } + + for opStr, mongoOp := range comparisonOp { + if strings.Contains(part, opStr) { + parts := strings.Split(part, opStr) + if len(parts) >= 2 && index < len(values) { + fieldName := strings.TrimSpace(parts[0]) + return bson.M{fieldName: bson.M{mongoOp: values[index]}} + } + } + } + + // 默认:支持等值查询 = + if strings.Contains(part, "=") { + parts := strings.Split(part, "=") + if len(parts) >= 2 && index < len(values) { + fieldName := strings.TrimSpace(parts[0]) + return bson.M{fieldName: values[index]} + } + } + + return nil +} diff --git a/server/service/user.go b/server/service/user.go index 2932e3b..ca034c4 100644 --- a/server/service/user.go +++ b/server/service/user.go @@ -1,6 +1,7 @@ package service import ( + "errors" "godocms/model" "godocms/pkg/db" "godocms/utils" @@ -9,8 +10,19 @@ import ( "time" "github.com/gin-gonic/gin" + "gorm.io/gorm" ) +func GetUserByUsername(username string) (*model.User, error) { + var user model.User + if err := db.DB.Where("username = ?", username).First(&user).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, errors.New("用户不存在") + } + return nil, err + } + return &user, nil +} func GetUserAuthAndShare(user *model.User) ([]model.User, []model.User, error) { var shareList []model.User if err := db.DB.Where("dept_id = ?", user.DeptId).Select("id", "nickname", "username", "email").Find(&shareList).Error; err != nil { @@ -56,3 +68,18 @@ func GetUserRole(roleId int32) (model.UserRole, error) { } return role, nil } + +func AddUser(user *model.User, userThird *model.UserThird) (*model.User, error) { + db.DB.Begin() + if err := db.DB.Create(&user).Error; err != nil { + db.DB.Rollback() + return user, err + } + userThird.UserID = user.ID + if err := db.DB.Create(userThird).Error; err != nil { + db.DB.Rollback() + return user, err + } + db.DB.Commit() + return user, nil +} diff --git a/server/tmp/build-errors.log b/server/tmp/build-errors.log index c215497..d37f6a3 100644 --- a/server/tmp/build-errors.log +++ b/server/tmp/build-errors.log @@ -1 +1 @@ -exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1 \ No newline at end of file +exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1 \ No newline at end of file diff --git a/server/tmp/main.exe b/server/tmp/main.exe index 812582f..3eb6104 100644 Binary files a/server/tmp/main.exe and b/server/tmp/main.exe differ