diff --git a/cmd/generate/generate.go b/cmd/generate/generate.go index d12fdf69..a98332b8 100644 --- a/cmd/generate/generate.go +++ b/cmd/generate/generate.go @@ -43,7 +43,6 @@ func main() { g.GenerateModel("category", gen.FieldType("type", "consts.CategoryType")), g.GenerateModel("comment", gen.FieldType("type", "consts.CommentType"), gen.FieldType("status", "consts.CommentStatus")), g.GenerateModel("comment_black"), - g.GenerateModel("flyway_schema_history"), g.GenerateModel("journal", gen.FieldType("type", "consts.JournalType")), g.GenerateModel("link"), g.GenerateModel("log", gen.FieldType("type", "consts.LogType")), @@ -57,6 +56,7 @@ func main() { g.GenerateModel("tag"), g.GenerateModel("theme_setting"), g.GenerateModel("user", gen.FieldType("mfa_type", "consts.MFAType")), + g.GenerateModel("application_password"), ) // apply diy interfaces on structs or table models diff --git a/consts/consts.go b/consts/consts.go index 58fd491e..9f503f06 100644 --- a/consts/consts.go +++ b/consts/consts.go @@ -46,3 +46,7 @@ var ( BuildTime string BuildCommit string ) + +const ( + LocalDateTimeFormat = "2006-01-02T15:04:05" +) diff --git a/dal/application_password.gen.go b/dal/application_password.gen.go new file mode 100644 index 00000000..e21aa7be --- /dev/null +++ b/dal/application_password.gen.go @@ -0,0 +1,359 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package dal + +import ( + "context" + + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + + "gorm.io/gen" + "gorm.io/gen/field" + + "gorm.io/plugin/dbresolver" + + "github.com/go-sonic/sonic/model/entity" +) + +func newApplicationPassword(db *gorm.DB, opts ...gen.DOOption) applicationPassword { + _applicationPassword := applicationPassword{} + + _applicationPassword.applicationPasswordDo.UseDB(db, opts...) + _applicationPassword.applicationPasswordDo.UseModel(&entity.ApplicationPassword{}) + + tableName := _applicationPassword.applicationPasswordDo.TableName() + _applicationPassword.ALL = field.NewAsterisk(tableName) + _applicationPassword.ID = field.NewInt32(tableName, "id") + _applicationPassword.CreateTime = field.NewTime(tableName, "create_time") + _applicationPassword.UpdateTime = field.NewTime(tableName, "update_time") + _applicationPassword.Name = field.NewString(tableName, "name") + _applicationPassword.Password = field.NewString(tableName, "password") + _applicationPassword.UserID = field.NewInt32(tableName, "user_id") + _applicationPassword.LastActivateTime = field.NewTime(tableName, "last_activate_time") + _applicationPassword.LastActivateIP = field.NewString(tableName, "last_activate_ip") + + _applicationPassword.fillFieldMap() + + return _applicationPassword +} + +type applicationPassword struct { + applicationPasswordDo applicationPasswordDo + + ALL field.Asterisk + ID field.Int32 + CreateTime field.Time + UpdateTime field.Time + Name field.String + Password field.String + UserID field.Int32 + LastActivateTime field.Time + LastActivateIP field.String + + fieldMap map[string]field.Expr +} + +func (a applicationPassword) Table(newTableName string) *applicationPassword { + a.applicationPasswordDo.UseTable(newTableName) + return a.updateTableName(newTableName) +} + +func (a applicationPassword) As(alias string) *applicationPassword { + a.applicationPasswordDo.DO = *(a.applicationPasswordDo.As(alias).(*gen.DO)) + return a.updateTableName(alias) +} + +func (a *applicationPassword) updateTableName(table string) *applicationPassword { + a.ALL = field.NewAsterisk(table) + a.ID = field.NewInt32(table, "id") + a.CreateTime = field.NewTime(table, "create_time") + a.UpdateTime = field.NewTime(table, "update_time") + a.Name = field.NewString(table, "name") + a.Password = field.NewString(table, "password") + a.UserID = field.NewInt32(table, "user_id") + a.LastActivateTime = field.NewTime(table, "last_activate_time") + a.LastActivateIP = field.NewString(table, "last_activate_ip") + + a.fillFieldMap() + + return a +} + +func (a *applicationPassword) WithContext(ctx context.Context) *applicationPasswordDo { + return a.applicationPasswordDo.WithContext(ctx) +} + +func (a applicationPassword) TableName() string { return a.applicationPasswordDo.TableName() } + +func (a applicationPassword) Alias() string { return a.applicationPasswordDo.Alias() } + +func (a applicationPassword) Columns(cols ...field.Expr) gen.Columns { + return a.applicationPasswordDo.Columns(cols...) +} + +func (a *applicationPassword) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := a.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (a *applicationPassword) fillFieldMap() { + a.fieldMap = make(map[string]field.Expr, 8) + a.fieldMap["id"] = a.ID + a.fieldMap["create_time"] = a.CreateTime + a.fieldMap["update_time"] = a.UpdateTime + a.fieldMap["name"] = a.Name + a.fieldMap["password"] = a.Password + a.fieldMap["user_id"] = a.UserID + a.fieldMap["last_activate_time"] = a.LastActivateTime + a.fieldMap["last_activate_ip"] = a.LastActivateIP +} + +func (a applicationPassword) clone(db *gorm.DB) applicationPassword { + a.applicationPasswordDo.ReplaceConnPool(db.Statement.ConnPool) + return a +} + +func (a applicationPassword) replaceDB(db *gorm.DB) applicationPassword { + a.applicationPasswordDo.ReplaceDB(db) + return a +} + +type applicationPasswordDo struct{ gen.DO } + +func (a applicationPasswordDo) Debug() *applicationPasswordDo { + return a.withDO(a.DO.Debug()) +} + +func (a applicationPasswordDo) WithContext(ctx context.Context) *applicationPasswordDo { + return a.withDO(a.DO.WithContext(ctx)) +} + +func (a applicationPasswordDo) ReadDB() *applicationPasswordDo { + return a.Clauses(dbresolver.Read) +} + +func (a applicationPasswordDo) WriteDB() *applicationPasswordDo { + return a.Clauses(dbresolver.Write) +} + +func (a applicationPasswordDo) Session(config *gorm.Session) *applicationPasswordDo { + return a.withDO(a.DO.Session(config)) +} + +func (a applicationPasswordDo) Clauses(conds ...clause.Expression) *applicationPasswordDo { + return a.withDO(a.DO.Clauses(conds...)) +} + +func (a applicationPasswordDo) Returning(value interface{}, columns ...string) *applicationPasswordDo { + return a.withDO(a.DO.Returning(value, columns...)) +} + +func (a applicationPasswordDo) Not(conds ...gen.Condition) *applicationPasswordDo { + return a.withDO(a.DO.Not(conds...)) +} + +func (a applicationPasswordDo) Or(conds ...gen.Condition) *applicationPasswordDo { + return a.withDO(a.DO.Or(conds...)) +} + +func (a applicationPasswordDo) Select(conds ...field.Expr) *applicationPasswordDo { + return a.withDO(a.DO.Select(conds...)) +} + +func (a applicationPasswordDo) Where(conds ...gen.Condition) *applicationPasswordDo { + return a.withDO(a.DO.Where(conds...)) +} + +func (a applicationPasswordDo) Order(conds ...field.Expr) *applicationPasswordDo { + return a.withDO(a.DO.Order(conds...)) +} + +func (a applicationPasswordDo) Distinct(cols ...field.Expr) *applicationPasswordDo { + return a.withDO(a.DO.Distinct(cols...)) +} + +func (a applicationPasswordDo) Omit(cols ...field.Expr) *applicationPasswordDo { + return a.withDO(a.DO.Omit(cols...)) +} + +func (a applicationPasswordDo) Join(table schema.Tabler, on ...field.Expr) *applicationPasswordDo { + return a.withDO(a.DO.Join(table, on...)) +} + +func (a applicationPasswordDo) LeftJoin(table schema.Tabler, on ...field.Expr) *applicationPasswordDo { + return a.withDO(a.DO.LeftJoin(table, on...)) +} + +func (a applicationPasswordDo) RightJoin(table schema.Tabler, on ...field.Expr) *applicationPasswordDo { + return a.withDO(a.DO.RightJoin(table, on...)) +} + +func (a applicationPasswordDo) Group(cols ...field.Expr) *applicationPasswordDo { + return a.withDO(a.DO.Group(cols...)) +} + +func (a applicationPasswordDo) Having(conds ...gen.Condition) *applicationPasswordDo { + return a.withDO(a.DO.Having(conds...)) +} + +func (a applicationPasswordDo) Limit(limit int) *applicationPasswordDo { + return a.withDO(a.DO.Limit(limit)) +} + +func (a applicationPasswordDo) Offset(offset int) *applicationPasswordDo { + return a.withDO(a.DO.Offset(offset)) +} + +func (a applicationPasswordDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *applicationPasswordDo { + return a.withDO(a.DO.Scopes(funcs...)) +} + +func (a applicationPasswordDo) Unscoped() *applicationPasswordDo { + return a.withDO(a.DO.Unscoped()) +} + +func (a applicationPasswordDo) Create(values ...*entity.ApplicationPassword) error { + if len(values) == 0 { + return nil + } + return a.DO.Create(values) +} + +func (a applicationPasswordDo) CreateInBatches(values []*entity.ApplicationPassword, batchSize int) error { + return a.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (a applicationPasswordDo) Save(values ...*entity.ApplicationPassword) error { + if len(values) == 0 { + return nil + } + return a.DO.Save(values) +} + +func (a applicationPasswordDo) First() (*entity.ApplicationPassword, error) { + if result, err := a.DO.First(); err != nil { + return nil, err + } else { + return result.(*entity.ApplicationPassword), nil + } +} + +func (a applicationPasswordDo) Take() (*entity.ApplicationPassword, error) { + if result, err := a.DO.Take(); err != nil { + return nil, err + } else { + return result.(*entity.ApplicationPassword), nil + } +} + +func (a applicationPasswordDo) Last() (*entity.ApplicationPassword, error) { + if result, err := a.DO.Last(); err != nil { + return nil, err + } else { + return result.(*entity.ApplicationPassword), nil + } +} + +func (a applicationPasswordDo) Find() ([]*entity.ApplicationPassword, error) { + result, err := a.DO.Find() + return result.([]*entity.ApplicationPassword), err +} + +func (a applicationPasswordDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*entity.ApplicationPassword, err error) { + buf := make([]*entity.ApplicationPassword, 0, batchSize) + err = a.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (a applicationPasswordDo) FindInBatches(result *[]*entity.ApplicationPassword, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return a.DO.FindInBatches(result, batchSize, fc) +} + +func (a applicationPasswordDo) Attrs(attrs ...field.AssignExpr) *applicationPasswordDo { + return a.withDO(a.DO.Attrs(attrs...)) +} + +func (a applicationPasswordDo) Assign(attrs ...field.AssignExpr) *applicationPasswordDo { + return a.withDO(a.DO.Assign(attrs...)) +} + +func (a applicationPasswordDo) Joins(fields ...field.RelationField) *applicationPasswordDo { + for _, _f := range fields { + a = *a.withDO(a.DO.Joins(_f)) + } + return &a +} + +func (a applicationPasswordDo) Preload(fields ...field.RelationField) *applicationPasswordDo { + for _, _f := range fields { + a = *a.withDO(a.DO.Preload(_f)) + } + return &a +} + +func (a applicationPasswordDo) FirstOrInit() (*entity.ApplicationPassword, error) { + if result, err := a.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*entity.ApplicationPassword), nil + } +} + +func (a applicationPasswordDo) FirstOrCreate() (*entity.ApplicationPassword, error) { + if result, err := a.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*entity.ApplicationPassword), nil + } +} + +func (a applicationPasswordDo) FindByPage(offset int, limit int) (result []*entity.ApplicationPassword, count int64, err error) { + result, err = a.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = a.Offset(-1).Limit(-1).Count() + return +} + +func (a applicationPasswordDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = a.Count() + if err != nil { + return + } + + err = a.Offset(offset).Limit(limit).Scan(result) + return +} + +func (a applicationPasswordDo) Scan(result interface{}) (err error) { + return a.DO.Scan(result) +} + +func (a applicationPasswordDo) Delete(models ...*entity.ApplicationPassword) (result gen.ResultInfo, err error) { + return a.DO.Delete(models) +} + +func (a *applicationPasswordDo) withDO(do gen.Dao) *applicationPasswordDo { + a.DO = *do.(*gen.DO) + return a +} diff --git a/dal/gen.go b/dal/gen.go index 4fc226c9..1e7ea8c5 100644 --- a/dal/gen.go +++ b/dal/gen.go @@ -8,18 +8,20 @@ import ( "context" "database/sql" - "gorm.io/gen" "gorm.io/gorm" + + "gorm.io/gen" + "gorm.io/plugin/dbresolver" ) var ( Q = new(Query) + ApplicationPassword *applicationPassword Attachment *attachment Category *category Comment *comment CommentBlack *commentBlack - FlywaySchemaHistory *flywaySchemaHistory Journal *journal Link *link Log *log @@ -37,11 +39,11 @@ var ( func SetDefault(db *gorm.DB, opts ...gen.DOOption) { *Q = *Use(db, opts...) + ApplicationPassword = &Q.ApplicationPassword Attachment = &Q.Attachment Category = &Q.Category Comment = &Q.Comment CommentBlack = &Q.CommentBlack - FlywaySchemaHistory = &Q.FlywaySchemaHistory Journal = &Q.Journal Link = &Q.Link Log = &Q.Log @@ -60,11 +62,11 @@ func SetDefault(db *gorm.DB, opts ...gen.DOOption) { func Use(db *gorm.DB, opts ...gen.DOOption) *Query { return &Query{ db: db, + ApplicationPassword: newApplicationPassword(db, opts...), Attachment: newAttachment(db, opts...), Category: newCategory(db, opts...), Comment: newComment(db, opts...), CommentBlack: newCommentBlack(db, opts...), - FlywaySchemaHistory: newFlywaySchemaHistory(db, opts...), Journal: newJournal(db, opts...), Link: newLink(db, opts...), Log: newLog(db, opts...), @@ -84,11 +86,11 @@ func Use(db *gorm.DB, opts ...gen.DOOption) *Query { type Query struct { db *gorm.DB + ApplicationPassword applicationPassword Attachment attachment Category category Comment comment CommentBlack commentBlack - FlywaySchemaHistory flywaySchemaHistory Journal journal Link link Log log @@ -109,11 +111,11 @@ func (q *Query) Available() bool { return q.db != nil } func (q *Query) clone(db *gorm.DB) *Query { return &Query{ db: db, + ApplicationPassword: q.ApplicationPassword.clone(db), Attachment: q.Attachment.clone(db), Category: q.Category.clone(db), Comment: q.Comment.clone(db), CommentBlack: q.CommentBlack.clone(db), - FlywaySchemaHistory: q.FlywaySchemaHistory.clone(db), Journal: q.Journal.clone(db), Link: q.Link.clone(db), Log: q.Log.clone(db), @@ -141,11 +143,11 @@ func (q *Query) WriteDB() *Query { func (q *Query) ReplaceDB(db *gorm.DB) *Query { return &Query{ db: db, + ApplicationPassword: q.ApplicationPassword.replaceDB(db), Attachment: q.Attachment.replaceDB(db), Category: q.Category.replaceDB(db), Comment: q.Comment.replaceDB(db), CommentBlack: q.CommentBlack.replaceDB(db), - FlywaySchemaHistory: q.FlywaySchemaHistory.replaceDB(db), Journal: q.Journal.replaceDB(db), Link: q.Link.replaceDB(db), Log: q.Log.replaceDB(db), @@ -163,11 +165,11 @@ func (q *Query) ReplaceDB(db *gorm.DB) *Query { } type queryCtx struct { + ApplicationPassword *applicationPasswordDo Attachment *attachmentDo Category *categoryDo Comment *commentDo CommentBlack *commentBlackDo - FlywaySchemaHistory *flywaySchemaHistoryDo Journal *journalDo Link *linkDo Log *logDo @@ -185,11 +187,11 @@ type queryCtx struct { func (q *Query) WithContext(ctx context.Context) *queryCtx { return &queryCtx{ + ApplicationPassword: q.ApplicationPassword.WithContext(ctx), Attachment: q.Attachment.WithContext(ctx), Category: q.Category.WithContext(ctx), Comment: q.Comment.WithContext(ctx), CommentBlack: q.CommentBlack.WithContext(ctx), - FlywaySchemaHistory: q.FlywaySchemaHistory.WithContext(ctx), Journal: q.Journal.WithContext(ctx), Link: q.Link.WithContext(ctx), Log: q.Log.WithContext(ctx), diff --git a/go.mod b/go.mod index 4093fc76..4540e40d 100644 --- a/go.mod +++ b/go.mod @@ -31,7 +31,7 @@ require ( go.uber.org/dig v1.17.1 go.uber.org/fx v1.20.1 go.uber.org/zap v1.26.0 - golang.org/x/crypto v0.14.0 + golang.org/x/crypto v0.15.0 golang.org/x/image v0.13.0 gopkg.in/yaml.v2 v2.4.0 gorm.io/driver/mysql v1.5.2 @@ -105,12 +105,12 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/arch v0.5.0 // indirect golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect - golang.org/x/mod v0.13.0 // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/net v0.18.0 // indirect + golang.org/x/sys v0.14.0 // indirect + golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.14.0 // indirect + golang.org/x/tools v0.15.0 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect diff --git a/go.sum b/go.sum index 52b12af1..4b880e46 100644 --- a/go.sum +++ b/go.sum @@ -350,6 +350,8 @@ github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6g github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sethvargo/go-password v0.2.0 h1:BTDl4CC/gjf/axHMaDQtw507ogrXLci6XRiLc7i/UHI= +github.com/sethvargo/go-password v0.2.0/go.mod h1:Ym4Mr9JXLBycr02MFuVQ/0JHidNetSgbzutTr3zsYXE= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= @@ -441,6 +443,8 @@ golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2Uz golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= +golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -483,6 +487,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -521,6 +527,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -544,6 +552,7 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -593,6 +602,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= @@ -600,6 +611,7 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -613,6 +625,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -669,6 +683,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= +golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/handler/admin/application_password.go b/handler/admin/application_password.go new file mode 100644 index 00000000..ea01d383 --- /dev/null +++ b/handler/admin/application_password.go @@ -0,0 +1,61 @@ +package admin + +import ( + "errors" + + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + + "github.com/go-sonic/sonic/handler/trans" + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/util" + "github.com/go-sonic/sonic/util/xerr" +) + +type ApplicationPasswordHandler struct { + ApplicationPasswordService service.ApplicationPasswordService +} + +func NewApplicationPasswordHandler(applicationPasswordService service.ApplicationPasswordService) *ApplicationPasswordHandler { + return &ApplicationPasswordHandler{ + ApplicationPasswordService: applicationPasswordService, + } +} + +func (a *ApplicationPasswordHandler) Create(ctx *gin.Context) (interface{}, error) { + appPwdParam, err := parseAppPwdParam(ctx) + if err != nil { + return nil, err + } + + return a.ApplicationPasswordService.CreatePwd(ctx, appPwdParam) +} + +func (a *ApplicationPasswordHandler) Delete(ctx *gin.Context) (interface{}, error) { + name, err := util.ParamString(ctx, "name") + if err != nil { + return nil, err + } + + appPwdParam := ¶m.ApplicationPasswordParam{Name: name} + + return nil, a.ApplicationPasswordService.DeletePwd(ctx, appPwdParam) +} + +func (a *ApplicationPasswordHandler) List(ctx *gin.Context) (interface{}, error) { + return a.ApplicationPasswordService.List(ctx) +} + +func parseAppPwdParam(ctx *gin.Context) (*param.ApplicationPasswordParam, error) { + var appPwdParam param.ApplicationPasswordParam + err := ctx.ShouldBindJSON(&appPwdParam) + if err != nil { + e := validator.ValidationErrors{} + if errors.As(err, &e) { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.WithStatus(err, xerr.StatusBadRequest) + } + return &appPwdParam, nil +} diff --git a/handler/admin/init.go b/handler/admin/init.go index f1a9d542..a7cfc800 100644 --- a/handler/admin/init.go +++ b/handler/admin/init.go @@ -25,5 +25,6 @@ func init() { NewThemeHandler, NewUserHandler, NewEmailHandler, + NewApplicationPasswordHandler, ) } diff --git a/handler/content/wp/category.go b/handler/content/wp/category.go new file mode 100644 index 00000000..3cc8dc7c --- /dev/null +++ b/handler/content/wp/category.go @@ -0,0 +1,52 @@ +package wp + +import ( + "github.com/gin-gonic/gin" + + "github.com/go-sonic/sonic/model/dto/wp" + "github.com/go-sonic/sonic/model/entity" + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/service" +) + +type CategoryHandler struct { + CategoryService service.CategoryService +} + +func NewCategoryHandler(categoryService service.CategoryService) *CategoryHandler { + return &CategoryHandler{ + CategoryService: categoryService, + } +} + +func (c *CategoryHandler) List(ctx *gin.Context) (interface{}, error) { + sort := ¶m.Sort{ + Fields: []string{"name,desc"}, + } + categoryEntities, err := c.CategoryService.ListAll(ctx, sort) + if err != nil { + return nil, err + } + + categoryDTOList := make([]*wp.CategoryDTO, 0, len(categoryEntities)) + for _, categoryEntity := range categoryEntities { + categoryDTOList = append(categoryDTOList, convertToCategoryDTO(categoryEntity)) + } + + return categoryDTOList, nil +} + +func convertToCategoryDTO(categoryEntity *entity.Category) *wp.CategoryDTO { + categoryDTO := &wp.CategoryDTO{ + ID: categoryEntity.ID, + Count: 0, + Description: categoryEntity.Description, + Link: "", + Name: categoryEntity.Name, + Slug: categoryEntity.Slug, + Taxonomy: "", + Parent: categoryEntity.ParentID, + Meta: nil, + } + return categoryDTO +} diff --git a/handler/content/wp/init.go b/handler/content/wp/init.go new file mode 100644 index 00000000..93096671 --- /dev/null +++ b/handler/content/wp/init.go @@ -0,0 +1,12 @@ +package wp + +import "github.com/go-sonic/sonic/injection" + +func init() { + injection.Provide( + NewPostHandler, + NewUserHandler, + NewCategoryHandler, + NewTagHandler, + ) +} diff --git a/handler/content/wp/post.go b/handler/content/wp/post.go new file mode 100644 index 00000000..90024005 --- /dev/null +++ b/handler/content/wp/post.go @@ -0,0 +1,219 @@ +package wp + +import ( + "encoding/json" + "strconv" + "strings" + "time" + + "github.com/gin-gonic/gin" + + sonicconst "github.com/go-sonic/sonic/consts" + "github.com/go-sonic/sonic/log" + "github.com/go-sonic/sonic/model/dto/wp" + "github.com/go-sonic/sonic/model/entity" + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/util" + "github.com/go-sonic/sonic/util/xerr" +) + +type PostHandler struct { + PostService service.PostService +} + +func NewPostHandler(postService service.PostService) *PostHandler { + return &PostHandler{ + PostService: postService, + } +} + +func (handler *PostHandler) List(ctx *gin.Context) (interface{}, error) { + var wpPostQuery param.WpPostQuery + if err := ctx.ShouldBind(&wpPostQuery); err != nil { + return nil, util.WrapJSONBindErr(err) + } + + var postQuery param.PostQuery + postQuery.PageSize = wpPostQuery.Page + postQuery.PageNum = wpPostQuery.PerPage + entities, _, err := handler.PostService.Page(ctx, postQuery) + if err != nil { + return nil, err + } + + wpPostList := make([]*wp.PostDTO, 0, len(entities)) + for _, postEntity := range entities { + wpPostList = append(wpPostList, convertToWpPost(postEntity)) + } + + return wpPostList, nil +} + +func (handler *PostHandler) Create(ctx *gin.Context) (interface{}, error) { + postParam, err := parsePostParam(ctx) + if err != nil { + return nil, util.WrapJSONBindErr(err) + } + + create, err := handler.PostService.Create(ctx, postParam) + if err != nil { + return nil, err + } + return convertToWpPost(create), nil +} + +func (handler *PostHandler) Update(ctx *gin.Context) (interface{}, error) { + postParam, err := parsePostParam(ctx) + if err != nil { + return nil, util.WrapJSONBindErr(err) + } + + postIDStr := ctx.Param("postID") + postID, err := strconv.ParseInt(postIDStr, 10, 32) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("Parameter error") + } + + postDetail, err := handler.PostService.Update(ctx, int32(postID), postParam) + if err != nil { + return nil, err + } + + return convertToWpPost(postDetail), nil +} + +func (handler *PostHandler) Delete(ctx *gin.Context) (interface{}, error) { + postIDStr := ctx.Param("postID") + postID, err := strconv.ParseInt(postIDStr, 10, 32) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("Parameter error") + } + + postEntity, err := handler.PostService.GetByPostID(ctx, int32(postID)) + if err != nil { + return nil, err + } + + if err = handler.PostService.Delete(ctx, int32(postID)); err != nil { + return nil, err + } + + return convertToWpPost(postEntity), nil +} + +func parsePostParam(ctx *gin.Context) (*param.Post, error) { + var wpPost param.WpPost + err := ctx.ShouldBindJSON(&wpPost) + if err != nil { + return nil, util.WrapJSONBindErr(err) + } + + bytes, err := json.Marshal(wpPost) + if err != nil { + return nil, err + } + log.CtxInfo(ctx, "wpPost: "+string(bytes)) + + return convertToPostParam(&wpPost) +} + +func convertToPostParam(wpPost *param.WpPost) (*param.Post, error) { + paramPostStatus := sonicconst.PostStatusPublished + if strings.ToLower(wpPost.Status) == "draft" { + paramPostStatus = sonicconst.PostStatusDraft + } + + editorType := sonicconst.EditorTypeRichText + disallowComment := false + if strings.ToLower(wpPost.CommentStatus) == "closed" { + disallowComment = true + } + + var createTime *int64 + + if strings.TrimSpace(wpPost.Date) != "" { + datetime, err := time.Parse(sonicconst.LocalDateTimeFormat, wpPost.Date) + if err != nil { + return nil, err + } + dateTimeMills := datetime.UnixMilli() + createTime = &dateTimeMills + } + + return ¶m.Post{ + Title: wpPost.Title, + Status: paramPostStatus, + Slug: wpPost.Slug, + EditorType: &editorType, + OriginalContent: wpPost.Content, + Summary: "", + Thumbnail: "", + DisallowComment: disallowComment, + Password: wpPost.Password, + Template: "", + TopPriority: 0, + CreateTime: createTime, + MetaKeywords: "", + MetaDescription: "", + TagIDs: make([]int32, 0), + CategoryIDs: make([]int32, 0), + MetaParam: nil, + Content: wpPost.Content, + EditTime: nil, + UpdateTime: nil, + }, nil +} + +func convertToWpPost(postEntity *entity.Post) *wp.PostDTO { + timeFormat := time.RFC3339 + wpStatus := "publish" + wpCommentStatus := "open" + wpContent := make(map[string]interface{}) + + if postEntity.Status == sonicconst.PostStatusDraft { + wpStatus = "draft" + } + + if postEntity.DisallowComment { + wpCommentStatus = "close" + } + + wpContent["rendered"] = postEntity.OriginalContent + wpContent["protected"] = false + + postDTO := &wp.PostDTO{ + Date: postEntity.CreateTime.Format(timeFormat), + DateGmt: postEntity.CreateTime.UTC().Format(timeFormat), + GUID: nil, + ID: postEntity.ID, + Link: "", + Modified: "", + ModifiedGmt: "", + Slug: "", + Status: wpStatus, + Type: "post", + Password: "standard", + PermalinkTemplate: "", + GeneratedSlug: "", + Title: postEntity.Title, + Content: wpContent, + Author: 0, + Excerpt: nil, + FeaturedMedia: 0, + CommentStatus: wpCommentStatus, + PingStatus: "open", + Format: "standard", + Meta: nil, + Sticky: false, + Template: "", + Categories: make([]int32, 0), + Tags: make([]int32, 0), + } + + if postEntity.UpdateTime != nil { + postDTO.Modified = postEntity.UpdateTime.Format(timeFormat) + postDTO.ModifiedGmt = postEntity.UpdateTime.UTC().Format(timeFormat) + } + return postDTO +} diff --git a/handler/content/wp/tag.go b/handler/content/wp/tag.go new file mode 100644 index 00000000..1e850cea --- /dev/null +++ b/handler/content/wp/tag.go @@ -0,0 +1,91 @@ +package wp + +import ( + "strings" + + "github.com/gin-gonic/gin" + + "github.com/go-sonic/sonic/model/dto/wp" + "github.com/go-sonic/sonic/model/entity" + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/util/xerr" +) + +type TagHandler struct { + TagService service.TagService +} + +func NewTagHandler(tagService service.TagService) *TagHandler { + return &TagHandler{ + TagService: tagService, + } +} + +func (handler *TagHandler) List(ctx *gin.Context) (interface{}, error) { + var err error + var listParam param.TagListParam + if err = ctx.ShouldBind(&listParam); err != nil { + return nil, err + } + + entities, err := handler.TagService.ListByOption(ctx, &listParam) + if err != nil { + return nil, err + } + + tagDTOList := make([]*wp.TagDTO, 0, len(entities)) + for _, tagEntity := range entities { + tagDTOList = append(tagDTOList, convertToWpTag(tagEntity)) + } + + return tagDTOList, nil +} + +func (handler *TagHandler) Create(ctx *gin.Context) (interface{}, error) { + var err error + var createParam param.TagCreateParam + if err = ctx.ShouldBindJSON(&createParam); err != nil { + return nil, err + } + + createParam.Name = strings.TrimSpace(createParam.Name) + if createParam.Name == "" { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("blank name") + } + + tagEntity, err := handler.TagService.GetByName(ctx, createParam.Name) + if err != nil { + return nil, err + } + + if tagEntity != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("tag exists") + } + + tagParam := ¶m.Tag{ + Name: createParam.Name, + Slug: createParam.Slug, + Thumbnail: "", + Color: "", + } + create, err := handler.TagService.Create(ctx, tagParam) + if err != nil { + return nil, err + } + return convertToWpTag(create), nil +} + +func convertToWpTag(tagEntity *entity.Tag) *wp.TagDTO { + tagDTO := &wp.TagDTO{ + ID: tagEntity.ID, + Count: 0, + Description: "", + Link: "", + Name: tagEntity.Name, + Slug: tagEntity.Slug, + Taxonomy: "", + Meta: nil, + } + return tagDTO +} diff --git a/handler/content/wp/user.go b/handler/content/wp/user.go new file mode 100644 index 00000000..4f08370f --- /dev/null +++ b/handler/content/wp/user.go @@ -0,0 +1,32 @@ +package wp + +import ( + "github.com/gin-gonic/gin" + + "github.com/go-sonic/sonic/model/dto" + "github.com/go-sonic/sonic/service" +) + +type UserHandler struct { + UserService service.UserService +} + +func NewUserHandler(userService service.UserService) *UserHandler { + return &UserHandler{ + UserService: userService, + } +} + +func (u *UserHandler) List(ctx *gin.Context) (interface{}, error) { + allUser, err := u.UserService.GetAllUser(ctx) + if err != nil { + return nil, err + } + + userDTOList := make([]*dto.User, 0, len(allUser)) + for _, user := range allUser { + userDTO := u.UserService.ConvertToDTO(ctx, user) + userDTOList = append(userDTOList, userDTO) + } + return userDTOList, nil +} diff --git a/handler/middleware/application_password.go b/handler/middleware/application_password.go new file mode 100644 index 00000000..0ef3c190 --- /dev/null +++ b/handler/middleware/application_password.go @@ -0,0 +1,98 @@ +package middleware + +import ( + "encoding/base64" + "fmt" + "net/http" + "regexp" + "strings" + + "github.com/gin-gonic/gin" + + "github.com/go-sonic/sonic/consts" + "github.com/go-sonic/sonic/model/dto" + "github.com/go-sonic/sonic/service" +) + +var basicAuthRegexp = regexp.MustCompile(`^Basic [a-z\\d/+]*={0,2}`) + +type ApplicationPasswordMiddleware struct { + PasswordService service.ApplicationPasswordService + UserService service.UserService +} + +func NewApplicationPasswordMiddleware(passwordService service.ApplicationPasswordService, userService service.UserService) *ApplicationPasswordMiddleware { + m := &ApplicationPasswordMiddleware{ + PasswordService: passwordService, + UserService: userService, + } + return m +} + +func (a *ApplicationPasswordMiddleware) Get() error { + return nil +} + +func (a *ApplicationPasswordMiddleware) GetWrapHandler() gin.HandlerFunc { + return func(ctx *gin.Context) { + header := ctx.GetHeader("Authorization") + if len(header) == 0 { + abortUnauthorized(ctx) + return + } + + match := verifyHeader(header) + if !match { + abortUnauthorized(ctx) + return + } + + bytes, err := base64.StdEncoding.DecodeString(header[6:]) + if err != nil { + abortUnauthorized(ctx) + return + } + + userPass := string(bytes) + + if !strings.Contains(userPass, ":") { + abortUnauthorized(ctx) + return + } + + splits := strings.SplitN(userPass, ":", 2) + + userEntity, err := a.UserService.GetByUsername(ctx, splits[0]) + if err != nil { + abortUnauthorized(ctx) + return + } + + pwdEntity, err := a.PasswordService.Verify(ctx, userEntity.ID, splits[1]) + if err != nil || pwdEntity == nil { + abortUnauthorized(ctx) + return + } + + err = a.PasswordService.Update(ctx, *pwdEntity.ID, ctx.ClientIP()) + if err != nil { + ctx.AbortWithStatusJSON(http.StatusInternalServerError, &dto.BaseDTO{ + Status: http.StatusInternalServerError, + Message: fmt.Sprintf("Update application password entity error, err=%s", err), + }) + return + } + ctx.Set(consts.AuthorizedUser, userEntity) + } +} + +func abortUnauthorized(ctx *gin.Context) { + ctx.AbortWithStatusJSON(http.StatusUnauthorized, &dto.BaseDTO{ + Status: http.StatusUnauthorized, + Message: "Unauthorized", + }) +} + +func verifyHeader(header string) bool { + return basicAuthRegexp.MatchString(header) +} diff --git a/handler/router.go b/handler/router.go index 4b00aa4d..5086b95a 100644 --- a/handler/router.go +++ b/handler/router.go @@ -44,6 +44,26 @@ func (s *Server) RegisterRouters() { StaticFS(consts.SonicUploadDir, gin.Dir(s.Config.Sonic.UploadDir, false)) staticRouter.StaticFS("/themes/", gin.Dir(s.Config.Sonic.ThemeDir, false)) } + { + wpCompatibleRouter := router.Group("/wp-json/wp/v2") + wpCompatibleRouter.Use(s.ApplicationPasswordMiddleware.GetWrapHandler()) + { + wpCompatibleRouter.GET("/posts", s.wrapWpHandler(s.WpPostHandler.List)) + wpCompatibleRouter.POST("/posts", s.wrapWpHandler(s.WpPostHandler.Create)) + wpCompatibleRouter.POST("/posts/:postID", s.wrapWpHandler(s.WpPostHandler.Update)) + wpCompatibleRouter.DELETE("/posts/:postID", s.wrapWpHandler(s.WpPostHandler.Delete)) + } + { + wpCompatibleRouter.GET("/tags", s.wrapWpHandler(s.WpTagHandler.List)) + wpCompatibleRouter.POST("/tags", s.wrapWpHandler(s.WpTagHandler.Create)) + } + { + wpCompatibleRouter.GET("/users", s.wrapWpHandler(s.WpUserHandler.List)) + } + { + wpCompatibleRouter.GET("/categories", s.wrapWpHandler(s.WpCategoryHandler.List)) + } + } { adminAPIRouter := router.Group("/api/admin") adminAPIRouter.Use(s.LogMiddleware.LoggerWithConfig(middleware.GinLoggerConfig{}), s.RecoveryMiddleware.RecoveryWithLogger(), s.InstallRedirectMiddleware.InstallRedirect()) @@ -271,6 +291,12 @@ func (s *Server) RegisterRouters() { themeRouter.POST("reload", s.wrapHandler(s.ThemeHandler.ReloadTheme)) themeRouter.GET("activation/template/exists", s.wrapHandler(s.ThemeHandler.TemplateExist)) } + { + appPwdRouter := authRouter.Group("application_password") + appPwdRouter.POST("", s.wrapHandler(s.ApplicationPasswordHandler.Create)) + appPwdRouter.DELETE("/:name", s.wrapHandler(s.ApplicationPasswordHandler.Delete)) + appPwdRouter.GET("", s.wrapHandler(s.ApplicationPasswordHandler.List)) + } { emailRouter := authRouter.Group("/mails") emailRouter.POST("/test", s.wrapHandler(s.EmailHandler.Test)) diff --git a/handler/server.go b/handler/server.go index 24e2dfc4..0ccec9f6 100644 --- a/handler/server.go +++ b/handler/server.go @@ -7,6 +7,8 @@ import ( "os" "strconv" + "github.com/go-sonic/sonic/handler/content/wp" + "github.com/gin-gonic/gin" "go.uber.org/dig" "go.uber.org/fx" @@ -25,113 +27,125 @@ import ( ) type Server struct { - logger *zap.Logger - Config *config.Config - HTTPServer *http.Server - Router *gin.Engine - Template *template.Template - AuthMiddleware *middleware.AuthMiddleware - LogMiddleware *middleware.GinLoggerMiddleware - RecoveryMiddleware *middleware.RecoveryMiddleware - InstallRedirectMiddleware *middleware.InstallRedirectMiddleware - OptionService service.OptionService - ThemeService service.ThemeService - SheetService service.SheetService - AdminHandler *admin.AdminHandler - AttachmentHandler *admin.AttachmentHandler - BackupHandler *admin.BackupHandler - CategoryHandler *admin.CategoryHandler - InstallHandler *admin.InstallHandler - JournalHandler *admin.JournalHandler - JournalCommentHandler *admin.JournalCommentHandler - LinkHandler *admin.LinkHandler - LogHandler *admin.LogHandler - MenuHandler *admin.MenuHandler - OptionHandler *admin.OptionHandler - PhotoHandler *admin.PhotoHandler - PostHandler *admin.PostHandler - PostCommentHandler *admin.PostCommentHandler - SheetHandler *admin.SheetHandler - SheetCommentHandler *admin.SheetCommentHandler - StatisticHandler *admin.StatisticHandler - TagHandler *admin.TagHandler - ThemeHandler *admin.ThemeHandler - UserHandler *admin.UserHandler - EmailHandler *admin.EmailHandler - IndexHandler *content.IndexHandler - FeedHandler *content.FeedHandler - ArchiveHandler *content.ArchiveHandler - ViewHandler *content.ViewHandler - ContentCategoryHandler *content.CategoryHandler - ContentSheetHandler *content.SheetHandler - ContentTagHandler *content.TagHandler - ContentLinkHandler *content.LinkHandler - ContentPhotoHandler *content.PhotoHandler - ContentJournalHandler *content.JournalHandler - ContentSearchHandler *content.SearchHandler - ContentAPIArchiveHandler *api.ArchiveHandler - ContentAPICategoryHandler *api.CategoryHandler - ContentAPIJournalHandler *api.JournalHandler - ContentAPILinkHandler *api.LinkHandler - ContentAPIPostHandler *api.PostHandler - ContentAPISheetHandler *api.SheetHandler - ContentAPIOptionHandler *api.OptionHandler - ContentAPIPhotoHandler *api.PhotoHandler + logger *zap.Logger + Config *config.Config + HTTPServer *http.Server + Router *gin.Engine + Template *template.Template + AuthMiddleware *middleware.AuthMiddleware + LogMiddleware *middleware.GinLoggerMiddleware + RecoveryMiddleware *middleware.RecoveryMiddleware + InstallRedirectMiddleware *middleware.InstallRedirectMiddleware + ApplicationPasswordMiddleware *middleware.ApplicationPasswordMiddleware + OptionService service.OptionService + ThemeService service.ThemeService + SheetService service.SheetService + AdminHandler *admin.AdminHandler + AttachmentHandler *admin.AttachmentHandler + BackupHandler *admin.BackupHandler + CategoryHandler *admin.CategoryHandler + InstallHandler *admin.InstallHandler + JournalHandler *admin.JournalHandler + JournalCommentHandler *admin.JournalCommentHandler + LinkHandler *admin.LinkHandler + LogHandler *admin.LogHandler + MenuHandler *admin.MenuHandler + OptionHandler *admin.OptionHandler + PhotoHandler *admin.PhotoHandler + PostHandler *admin.PostHandler + PostCommentHandler *admin.PostCommentHandler + SheetHandler *admin.SheetHandler + SheetCommentHandler *admin.SheetCommentHandler + StatisticHandler *admin.StatisticHandler + TagHandler *admin.TagHandler + ThemeHandler *admin.ThemeHandler + UserHandler *admin.UserHandler + EmailHandler *admin.EmailHandler + ApplicationPasswordHandler *admin.ApplicationPasswordHandler + IndexHandler *content.IndexHandler + FeedHandler *content.FeedHandler + ArchiveHandler *content.ArchiveHandler + ViewHandler *content.ViewHandler + ContentCategoryHandler *content.CategoryHandler + ContentSheetHandler *content.SheetHandler + ContentTagHandler *content.TagHandler + ContentLinkHandler *content.LinkHandler + ContentPhotoHandler *content.PhotoHandler + ContentJournalHandler *content.JournalHandler + ContentSearchHandler *content.SearchHandler + ContentAPIArchiveHandler *api.ArchiveHandler + ContentAPICategoryHandler *api.CategoryHandler + ContentAPIJournalHandler *api.JournalHandler + ContentAPILinkHandler *api.LinkHandler + ContentAPIPostHandler *api.PostHandler + ContentAPISheetHandler *api.SheetHandler + ContentAPIOptionHandler *api.OptionHandler + ContentAPIPhotoHandler *api.PhotoHandler + WpPostHandler *wp.PostHandler + WpUserHandler *wp.UserHandler + WpCategoryHandler *wp.CategoryHandler + WpTagHandler *wp.TagHandler } type ServerParams struct { dig.In - Config *config.Config - Logger *zap.Logger - Event event.Bus - Template *template.Template - AuthMiddleware *middleware.AuthMiddleware - LogMiddleware *middleware.GinLoggerMiddleware - RecoveryMiddleware *middleware.RecoveryMiddleware - InstallRedirectMiddleware *middleware.InstallRedirectMiddleware - OptionService service.OptionService - ThemeService service.ThemeService - SheetService service.SheetService - AdminHandler *admin.AdminHandler - AttachmentHandler *admin.AttachmentHandler - BackupHandler *admin.BackupHandler - CategoryHandler *admin.CategoryHandler - InstallHandler *admin.InstallHandler - JournalHandler *admin.JournalHandler - JournalCommentHandler *admin.JournalCommentHandler - LinkHandler *admin.LinkHandler - LogHandler *admin.LogHandler - MenuHandler *admin.MenuHandler - OptionHandler *admin.OptionHandler - PhotoHandler *admin.PhotoHandler - PostHandler *admin.PostHandler - PostCommentHandler *admin.PostCommentHandler - SheetHandler *admin.SheetHandler - SheetCommentHandler *admin.SheetCommentHandler - StatisticHandler *admin.StatisticHandler - TagHandler *admin.TagHandler - ThemeHandler *admin.ThemeHandler - UserHandler *admin.UserHandler - EmailHandler *admin.EmailHandler - IndexHandler *content.IndexHandler - FeedHandler *content.FeedHandler - ArchiveHandler *content.ArchiveHandler - ViewHandler *content.ViewHandler - ContentCategoryHandler *content.CategoryHandler - ContentSheetHandler *content.SheetHandler - ContentTagHandler *content.TagHandler - ContentLinkHandler *content.LinkHandler - ContentPhotoHandler *content.PhotoHandler - ContentJournalHandler *content.JournalHandler - ContentSearchHandler *content.SearchHandler - ContentAPIArchiveHandler *api.ArchiveHandler - ContentAPICategoryHandler *api.CategoryHandler - ContentAPIJournalHandler *api.JournalHandler - ContentAPILinkHandler *api.LinkHandler - ContentAPIPostHandler *api.PostHandler - ContentAPISheetHandler *api.SheetHandler - ContentAPIOptionHandler *api.OptionHandler - ContentAPIPhotoHandler *api.PhotoHandler + Config *config.Config + Logger *zap.Logger + Event event.Bus + Template *template.Template + AuthMiddleware *middleware.AuthMiddleware + LogMiddleware *middleware.GinLoggerMiddleware + RecoveryMiddleware *middleware.RecoveryMiddleware + InstallRedirectMiddleware *middleware.InstallRedirectMiddleware + ApplicationPasswordMiddleware *middleware.ApplicationPasswordMiddleware + OptionService service.OptionService + ThemeService service.ThemeService + SheetService service.SheetService + AdminHandler *admin.AdminHandler + AttachmentHandler *admin.AttachmentHandler + BackupHandler *admin.BackupHandler + CategoryHandler *admin.CategoryHandler + InstallHandler *admin.InstallHandler + JournalHandler *admin.JournalHandler + JournalCommentHandler *admin.JournalCommentHandler + LinkHandler *admin.LinkHandler + LogHandler *admin.LogHandler + MenuHandler *admin.MenuHandler + OptionHandler *admin.OptionHandler + PhotoHandler *admin.PhotoHandler + PostHandler *admin.PostHandler + PostCommentHandler *admin.PostCommentHandler + SheetHandler *admin.SheetHandler + SheetCommentHandler *admin.SheetCommentHandler + StatisticHandler *admin.StatisticHandler + TagHandler *admin.TagHandler + ThemeHandler *admin.ThemeHandler + UserHandler *admin.UserHandler + EmailHandler *admin.EmailHandler + ApplicationPasswordHandler *admin.ApplicationPasswordHandler + IndexHandler *content.IndexHandler + FeedHandler *content.FeedHandler + ArchiveHandler *content.ArchiveHandler + ViewHandler *content.ViewHandler + ContentCategoryHandler *content.CategoryHandler + ContentSheetHandler *content.SheetHandler + ContentTagHandler *content.TagHandler + ContentLinkHandler *content.LinkHandler + ContentPhotoHandler *content.PhotoHandler + ContentJournalHandler *content.JournalHandler + ContentSearchHandler *content.SearchHandler + ContentAPIArchiveHandler *api.ArchiveHandler + ContentAPICategoryHandler *api.CategoryHandler + ContentAPIJournalHandler *api.JournalHandler + ContentAPILinkHandler *api.LinkHandler + ContentAPIPostHandler *api.PostHandler + ContentAPISheetHandler *api.SheetHandler + ContentAPIOptionHandler *api.OptionHandler + ContentAPIPhotoHandler *api.PhotoHandler + WpPostHandler *wp.PostHandler + WpUserHandler *wp.UserHandler + WpCategoryHandler *wp.CategoryHandler + WpTagHandler *wp.TagHandler } func NewServer(param ServerParams, lifecycle fx.Lifecycle) *Server { @@ -145,58 +159,64 @@ func NewServer(param ServerParams, lifecycle fx.Lifecycle) *Server { } s := &Server{ - logger: param.Logger, - Config: param.Config, - HTTPServer: httpServer, - Router: router, - Template: param.Template, - AuthMiddleware: param.AuthMiddleware, - LogMiddleware: param.LogMiddleware, - RecoveryMiddleware: param.RecoveryMiddleware, - InstallRedirectMiddleware: param.InstallRedirectMiddleware, - AdminHandler: param.AdminHandler, - AttachmentHandler: param.AttachmentHandler, - BackupHandler: param.BackupHandler, - CategoryHandler: param.CategoryHandler, - InstallHandler: param.InstallHandler, - JournalHandler: param.JournalHandler, - JournalCommentHandler: param.JournalCommentHandler, - LinkHandler: param.LinkHandler, - LogHandler: param.LogHandler, - MenuHandler: param.MenuHandler, - OptionHandler: param.OptionHandler, - PhotoHandler: param.PhotoHandler, - PostHandler: param.PostHandler, - PostCommentHandler: param.PostCommentHandler, - SheetHandler: param.SheetHandler, - SheetCommentHandler: param.SheetCommentHandler, - StatisticHandler: param.StatisticHandler, - TagHandler: param.TagHandler, - ThemeHandler: param.ThemeHandler, - UserHandler: param.UserHandler, - EmailHandler: param.EmailHandler, - OptionService: param.OptionService, - ThemeService: param.ThemeService, - SheetService: param.SheetService, - IndexHandler: param.IndexHandler, - FeedHandler: param.FeedHandler, - ArchiveHandler: param.ArchiveHandler, - ViewHandler: param.ViewHandler, - ContentCategoryHandler: param.ContentCategoryHandler, - ContentSheetHandler: param.ContentSheetHandler, - ContentTagHandler: param.ContentTagHandler, - ContentLinkHandler: param.ContentLinkHandler, - ContentPhotoHandler: param.ContentPhotoHandler, - ContentJournalHandler: param.ContentJournalHandler, - ContentAPIArchiveHandler: param.ContentAPIArchiveHandler, - ContentAPICategoryHandler: param.ContentAPICategoryHandler, - ContentAPIJournalHandler: param.ContentAPIJournalHandler, - ContentAPILinkHandler: param.ContentAPILinkHandler, - ContentAPIPostHandler: param.ContentAPIPostHandler, - ContentAPISheetHandler: param.ContentAPISheetHandler, - ContentAPIOptionHandler: param.ContentAPIOptionHandler, - ContentSearchHandler: param.ContentSearchHandler, - ContentAPIPhotoHandler: param.ContentAPIPhotoHandler, + logger: param.Logger, + Config: param.Config, + HTTPServer: httpServer, + Router: router, + Template: param.Template, + AuthMiddleware: param.AuthMiddleware, + LogMiddleware: param.LogMiddleware, + RecoveryMiddleware: param.RecoveryMiddleware, + InstallRedirectMiddleware: param.InstallRedirectMiddleware, + ApplicationPasswordMiddleware: param.ApplicationPasswordMiddleware, + AdminHandler: param.AdminHandler, + AttachmentHandler: param.AttachmentHandler, + BackupHandler: param.BackupHandler, + CategoryHandler: param.CategoryHandler, + InstallHandler: param.InstallHandler, + JournalHandler: param.JournalHandler, + JournalCommentHandler: param.JournalCommentHandler, + LinkHandler: param.LinkHandler, + LogHandler: param.LogHandler, + MenuHandler: param.MenuHandler, + OptionHandler: param.OptionHandler, + PhotoHandler: param.PhotoHandler, + PostHandler: param.PostHandler, + PostCommentHandler: param.PostCommentHandler, + SheetHandler: param.SheetHandler, + SheetCommentHandler: param.SheetCommentHandler, + StatisticHandler: param.StatisticHandler, + TagHandler: param.TagHandler, + ThemeHandler: param.ThemeHandler, + UserHandler: param.UserHandler, + EmailHandler: param.EmailHandler, + OptionService: param.OptionService, + ThemeService: param.ThemeService, + SheetService: param.SheetService, + IndexHandler: param.IndexHandler, + FeedHandler: param.FeedHandler, + ArchiveHandler: param.ArchiveHandler, + ViewHandler: param.ViewHandler, + ContentCategoryHandler: param.ContentCategoryHandler, + ContentSheetHandler: param.ContentSheetHandler, + ContentTagHandler: param.ContentTagHandler, + ContentLinkHandler: param.ContentLinkHandler, + ContentPhotoHandler: param.ContentPhotoHandler, + ContentJournalHandler: param.ContentJournalHandler, + ContentAPIArchiveHandler: param.ContentAPIArchiveHandler, + ContentAPICategoryHandler: param.ContentAPICategoryHandler, + ContentAPIJournalHandler: param.ContentAPIJournalHandler, + ContentAPILinkHandler: param.ContentAPILinkHandler, + ContentAPIPostHandler: param.ContentAPIPostHandler, + ContentAPISheetHandler: param.ContentAPISheetHandler, + ContentAPIOptionHandler: param.ContentAPIOptionHandler, + ContentSearchHandler: param.ContentSearchHandler, + ContentAPIPhotoHandler: param.ContentAPIPhotoHandler, + ApplicationPasswordHandler: param.ApplicationPasswordHandler, + WpPostHandler: param.WpPostHandler, + WpUserHandler: param.WpUserHandler, + WpCategoryHandler: param.WpCategoryHandler, + WpTagHandler: param.WpTagHandler, } lifecycle.Append(fx.Hook{ OnStop: httpServer.Shutdown, @@ -240,6 +260,20 @@ func (s *Server) wrapHandler(handler wrapperHandler) gin.HandlerFunc { } } +func (s *Server) wrapWpHandler(handler wrapperHandler) gin.HandlerFunc { + return func(ctx *gin.Context) { + data, err := handler(ctx) + if err != nil { + s.logger.Error("handler error", zap.Error(err)) + status := xerr.GetHTTPStatus(err) + ctx.JSON(status, &dto.BaseWpDTO{Code: status, Message: xerr.GetMessage(err), Data: map[string]interface{}{"status": status}}) + return + } + + ctx.JSON(http.StatusOK, data) + } +} + type wrapperHTMLHandler func(ctx *gin.Context, model template.Model) (templateName string, err error) var ( diff --git a/main.go b/main.go index 66ae8a33..ca526cba 100644 --- a/main.go +++ b/main.go @@ -47,6 +47,7 @@ func InitApp() *fx.App { middleware.NewGinLoggerMiddleware, middleware.NewRecoveryMiddleware, middleware.NewInstallRedirectMiddleware, + middleware.NewApplicationPasswordMiddleware, ), fx.Populate(&dal.DB), fx.Populate(&eventBus), diff --git a/model/dto/application_password.go b/model/dto/application_password.go new file mode 100644 index 00000000..fd9ffba2 --- /dev/null +++ b/model/dto/application_password.go @@ -0,0 +1,9 @@ +package dto + +type ApplicationPasswordDTO struct { + Name string `json:"name"` + Password string `json:"password"` + LastActivateTime int64 `json:"last_activate_time"` + LastActiveIP string `json:"last_activate_ip"` + CreateTime int64 `json:"create_time"` +} diff --git a/model/dto/base.go b/model/dto/base.go index 7b72b15a..8b61c722 100644 --- a/model/dto/base.go +++ b/model/dto/base.go @@ -14,6 +14,12 @@ type BaseDTO struct { Data interface{} `json:"data"` } +type BaseWpDTO struct { + Code int `json:"code"` + Message string `json:"message"` + Data interface{} `json:"data"` +} + type Page struct { Content interface{} `json:"content"` Pages int `json:"pages"` diff --git a/model/dto/wp/category.go b/model/dto/wp/category.go new file mode 100644 index 00000000..1fdfd59d --- /dev/null +++ b/model/dto/wp/category.go @@ -0,0 +1,13 @@ +package wp + +type CategoryDTO struct { + ID int32 `json:"id"` + Count int32 `json:"count"` + Description string `json:"description"` + Link string `json:"link"` + Name string `json:"name"` + Slug string `json:"slug"` + Taxonomy string `json:"taxonomy"` + Parent int32 `json:"parent"` + Meta map[string]interface{} `json:"meta"` +} diff --git a/model/dto/wp/post.go b/model/dto/wp/post.go new file mode 100644 index 00000000..c06fae12 --- /dev/null +++ b/model/dto/wp/post.go @@ -0,0 +1,30 @@ +package wp + +type PostDTO struct { + Date string `json:"date"` + DateGmt string `json:"date_gmt"` + GUID map[string]interface{} `json:"guid"` + ID int32 `json:"id"` + Link string `json:"link"` + Modified string `json:"modified"` + ModifiedGmt string `json:"modified_gmt"` + Slug string `json:"slug"` + Status string `json:"status"` + Type string `json:"type"` + Password string `json:"password"` + PermalinkTemplate string `json:"permalink_template"` + GeneratedSlug string `json:"generated_slug"` + Title string `json:"title"` + Content map[string]interface{} `json:"content"` + Author int32 `json:"author"` + Excerpt map[string]interface{} `json:"excerpt"` + FeaturedMedia int32 `json:"featured_media"` + CommentStatus string `json:"comment_status"` + PingStatus string `json:"ping_status"` + Format string `json:"format"` + Meta map[string]interface{} `json:"meta"` + Sticky bool `json:"sticky"` + Template string `json:"template"` + Categories []int32 `json:"categories"` + Tags []int32 `json:"tags"` +} diff --git a/model/dto/wp/tag.go b/model/dto/wp/tag.go new file mode 100644 index 00000000..d11c7ca7 --- /dev/null +++ b/model/dto/wp/tag.go @@ -0,0 +1,12 @@ +package wp + +type TagDTO struct { + ID int32 `json:"id"` + Count int32 `json:"count"` + Description string `json:"description"` + Link string `json:"link"` + Name string `json:"name"` + Slug string `json:"slug"` + Taxonomy string `json:"taxonomy"` + Meta map[string]interface{} `json:"meta"` +} diff --git a/model/entity/application_password.gen.go b/model/entity/application_password.gen.go new file mode 100644 index 00000000..769f054f --- /dev/null +++ b/model/entity/application_password.gen.go @@ -0,0 +1,28 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package entity + +import ( + "time" +) + +const TableNameApplicationPassword = "application_password" + +// ApplicationPassword mapped from table +type ApplicationPassword struct { + ID *int32 `gorm:"column:id;type:INTEGER" json:"id"` + CreateTime time.Time `gorm:"column:create_time;type:datetime;not null" json:"create_time"` + UpdateTime *time.Time `gorm:"column:update_time;type:datetime" json:"update_time"` + Name string `gorm:"column:name;type:varchar(32);not null" json:"name"` + Password string `gorm:"column:password;type:varchar(256);not null" json:"password"` + UserID int32 `gorm:"column:user_id;type:integer;not null" json:"user_id"` + LastActivateTime *time.Time `gorm:"column:last_activate_time;type:datetime" json:"last_activate_time"` + LastActivateIP string `gorm:"column:last_activate_ip;type:varchar(128);not null;default:'' not null" json:"last_activate_ip"` +} + +// TableName ApplicationPassword's table name +func (*ApplicationPassword) TableName() string { + return TableNameApplicationPassword +} diff --git a/model/param/application_password.go b/model/param/application_password.go new file mode 100644 index 00000000..e056c496 --- /dev/null +++ b/model/param/application_password.go @@ -0,0 +1,5 @@ +package param + +type ApplicationPasswordParam struct { + Name string `json:"name" binding:"required"` +} diff --git a/model/param/wp_post.go b/model/param/wp_post.go new file mode 100644 index 00000000..7ec01fe5 --- /dev/null +++ b/model/param/wp_post.go @@ -0,0 +1,35 @@ +package param + +type WpPost struct { + Date string `json:"date"` + DateGmt string `json:"date_gmt"` + GUID map[string]interface{} `json:"guid"` + ID int32 `json:"id"` + Link string `json:"link"` + Modified string `json:"modified"` + ModifiedGmt string `json:"modified_gmt"` + Slug string `json:"slug"` + Status string `json:"status"` + Type string `json:"type"` + Password string `json:"password"` + PermalinkTemplate string `json:"permalink_template"` + GeneratedSlug string `json:"generated_slug"` + Title string `json:"title"` + Content string `json:"content"` + Author int32 `json:"author"` + Excerpt map[string]interface{} `json:"excerpt"` + FeaturedMedia int32 `json:"featured_media"` + CommentStatus string `json:"comment_status"` + PingStatus string `json:"ping_status"` + Format string `json:"format"` + Meta map[string]interface{} `json:"meta"` + Sticky bool `json:"sticky"` + Template string `json:"template"` + Categories []int32 `json:"categories"` + Tags []int32 `json:"tags"` +} + +type WpPostQuery struct { + Page int `form:"page" default:"1"` + PerPage int `form:"per_page" default:"10"` +} diff --git a/model/param/wp_tag.go b/model/param/wp_tag.go new file mode 100644 index 00000000..4672a11c --- /dev/null +++ b/model/param/wp_tag.go @@ -0,0 +1,12 @@ +package param + +type TagListParam struct { + Search string `form:"search" json:"search"` +} + +type TagCreateParam struct { + Description string `json:"description"` + Name string `json:"name"` + Slug string `json:"slug"` + Meta map[string]interface{} `json:"meta"` +} diff --git a/scripts/table.sql b/scripts/table.sql index 50449429..72b58b05 100644 --- a/scripts/table.sql +++ b/scripts/table.sql @@ -294,3 +294,15 @@ create table if not exists user ) ENGINE = INNODB DEFAULT charset = utf8mb4; +create table if not exists application_password +( + id int auto_increment primary key, + create_time datetime(6) not null, + update_time datetime(6) null, + name varchar(32) not null, + password varchar(256) not null, + user_id int not null, + last_activate_time datetime(6) null, + last_activate_ip varchar(128) default '' not null +) ENGINE = INNODB + DEFAULT charset = utf8mb4; diff --git a/service/application_password.go b/service/application_password.go new file mode 100644 index 00000000..aec45f64 --- /dev/null +++ b/service/application_password.go @@ -0,0 +1,17 @@ +package service + +import ( + "context" + + "github.com/go-sonic/sonic/model/dto" + "github.com/go-sonic/sonic/model/entity" + "github.com/go-sonic/sonic/model/param" +) + +type ApplicationPasswordService interface { + CreatePwd(ctx context.Context, appPwdParam *param.ApplicationPasswordParam) (*dto.ApplicationPasswordDTO, error) + DeletePwd(ctx context.Context, appPwdParam *param.ApplicationPasswordParam) error + List(ctx context.Context) ([]*dto.ApplicationPasswordDTO, error) + Verify(ctx context.Context, userID int32, pwd string) (*entity.ApplicationPassword, error) + Update(ctx context.Context, entityID int32, ip string) error +} diff --git a/service/impl/application_password.go b/service/impl/application_password.go new file mode 100644 index 00000000..8572c4ed --- /dev/null +++ b/service/impl/application_password.go @@ -0,0 +1,172 @@ +package impl + +import ( + "context" + "strings" + "time" + + "github.com/go-sonic/sonic/model/dto" + "github.com/go-sonic/sonic/model/entity" + "github.com/go-sonic/sonic/util" + "github.com/go-sonic/sonic/util/xerr" + + "github.com/go-sonic/sonic/dal" + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/service" +) + +type applicationPasswordServiceImpl struct { + OptionService service.OptionService + AuthenticateService service.AuthenticateService +} + +func NewApplicationPasswordService(optionService service.OptionService, authenticateService service.AuthenticateService) service.ApplicationPasswordService { + return &applicationPasswordServiceImpl{ + OptionService: optionService, + AuthenticateService: authenticateService, + } +} + +func (a *applicationPasswordServiceImpl) CreatePwd(ctx context.Context, param *param.ApplicationPasswordParam) (*dto.ApplicationPasswordDTO, error) { + var err error + appPwdDTO := &dto.ApplicationPasswordDTO{ + Name: param.Name, + } + + user, err := MustGetAuthorizedUser(ctx) + if err != nil { + return nil, err + } + + err = dal.GetQueryByCtx(ctx).Transaction(func(tx *dal.Query) error { + appPwdDAL := tx.ApplicationPassword + + count, err := appPwdDAL.WithContext(ctx).Select().Where(appPwdDAL.Name.Eq(param.Name), appPwdDAL.UserID.Eq(user.ID)).Count() + if err != nil { + return WrapDBErr(err) + } + + if count > 0 { + return xerr.BadParam.New("").WithMsg("名称已经存在(Application password name already exists)").WithStatus(xerr.StatusBadRequest) + } + + token := util.GenUUIDWithOutDash() + tokenMd5 := util.Md5(token) + // pass claim token to frond, but save in db after md5 + appPwdDTO.Password = token + + currentTime := time.Now() + + appPwdEntity := &entity.ApplicationPassword{ + CreateTime: currentTime, + UpdateTime: ¤tTime, + Name: param.Name, + Password: tokenMd5, + UserID: user.ID, + LastActivateTime: nil, + LastActivateIP: "", + } + err = appPwdDAL.WithContext(ctx).Create(appPwdEntity) + if err != nil { + return WrapDBErr(err) + } + + return nil + }) + + if err != nil { + return nil, err + } + + return appPwdDTO, nil +} + +func (a *applicationPasswordServiceImpl) DeletePwd(ctx context.Context, appPwdParam *param.ApplicationPasswordParam) error { + var err error + user, err := MustGetAuthorizedUser(ctx) + if err != nil { + return err + } + + if appPwdParam == nil || len(strings.TrimSpace(appPwdParam.Name)) == 0 { + return xerr.BadParam.New("name参数为空").WithStatus(xerr.StatusBadRequest). + WithMsg("name 参数不能为空") + } + + appPwdParam.Name = strings.TrimSpace(appPwdParam.Name) + + appPwdDAL := dal.GetQueryByCtx(ctx).ApplicationPassword + if _, err = appPwdDAL.WithContext(ctx).Where(appPwdDAL.UserID.Eq(user.ID), appPwdDAL.Name.Eq(appPwdParam.Name)).Delete(); err != nil { + return WrapDBErr(err) + } + + return nil +} + +func (a *applicationPasswordServiceImpl) List(ctx context.Context) ([]*dto.ApplicationPasswordDTO, error) { + user, err := MustGetAuthorizedUser(ctx) + if err != nil { + return nil, err + } + + appPwdDAL := dal.GetQueryByCtx(ctx).ApplicationPassword + entities, err := appPwdDAL.WithContext(ctx).Where(appPwdDAL.UserID.Eq(user.ID)).Find() + if err != nil { + return nil, WrapDBErr(err) + } + + appPwdDTOList := make([]*dto.ApplicationPasswordDTO, len(entities)) + + for _, appPwdEntity := range entities { + appPwdDTOList = append(appPwdDTOList, a.ConvertToDTO(appPwdEntity)) + } + + return appPwdDTOList, nil +} + +func (a *applicationPasswordServiceImpl) Verify(ctx context.Context, userID int32, pwd string) (*entity.ApplicationPassword, error) { + appPwdDAL := dal.GetQueryByCtx(ctx).ApplicationPassword + entityList, err := appPwdDAL.WithContext(ctx).Where(appPwdDAL.UserID.Eq(userID)).Find() + if err != nil { + return nil, WrapDBErr(err) + } + + pwdMd5 := util.Md5(pwd) + + for _, appPwdEntity := range entityList { + if appPwdEntity.Password == pwdMd5 { + return appPwdEntity, nil + } + } + return nil, nil +} + +func (a *applicationPasswordServiceImpl) Update(ctx context.Context, entityID int32, ip string) error { + appPwdDAL := dal.GetQueryByCtx(ctx).ApplicationPassword + now := time.Now() + + _, err := appPwdDAL.WithContext(ctx).Where(appPwdDAL.ID.Eq(entityID)).Updates(entity.ApplicationPassword{ + LastActivateIP: ip, + LastActivateTime: &now, + }) + if err != nil { + return WrapDBErr(err) + } + + return nil +} + +func (a *applicationPasswordServiceImpl) ConvertToDTO(appPwdEntity *entity.ApplicationPassword) *dto.ApplicationPasswordDTO { + var lastActivateTime int64 + if appPwdEntity.LastActivateTime == nil { + lastActivateTime = appPwdEntity.LastActivateTime.Unix() + } + appPwdDTO := &dto.ApplicationPasswordDTO{ + Name: appPwdEntity.Name, + LastActiveIP: appPwdEntity.LastActivateIP, + LastActivateTime: lastActivateTime, + CreateTime: appPwdEntity.CreateTime.Unix(), + } + + return appPwdDTO +} diff --git a/service/impl/init.go b/service/impl/init.go index 826cf752..d7fb9378 100644 --- a/service/impl/init.go +++ b/service/impl/init.go @@ -40,5 +40,6 @@ func init() { NewUserService, NewExportImport, storage.NewFileStorageComposite, + NewApplicationPasswordService, ) } diff --git a/service/impl/tag.go b/service/impl/tag.go index 9b349d15..726ee750 100644 --- a/service/impl/tag.go +++ b/service/impl/tag.go @@ -230,3 +230,19 @@ func (t tagServiceImpl) GetByName(ctx context.Context, name string) (*entity.Tag tag, err := tagDAL.WithContext(ctx).Where(tagDAL.Name.Eq(name)).First() return tag, WrapDBErr(err) } + +func (t tagServiceImpl) ListByOption(ctx context.Context, option *param.TagListParam) ([]*entity.Tag, error) { + tagDAL := dal.GetQueryByCtx(ctx).Tag + if option == nil { + return tagDAL.WithContext(ctx).Where().Find() + } + + query := tagDAL.WithContext(ctx) + + search := strings.TrimSpace(option.Search) + if search != "" { + query = query.Where(tagDAL.Name.Like("%" + search + "%")) + } + + return query.Find() +} diff --git a/service/tag.go b/service/tag.go index 0a2800bf..299c6dd7 100644 --- a/service/tag.go +++ b/service/tag.go @@ -20,4 +20,5 @@ type TagService interface { Update(ctx context.Context, id int32, tagParam *param.Tag) (*entity.Tag, error) Delete(ctx context.Context, id int32) error CountAllTag(ctx context.Context) (int64, error) + ListByOption(ctx context.Context, option *param.TagListParam) ([]*entity.Tag, error) } diff --git a/util/common.go b/util/common.go index 36a4a6f2..0de5c223 100644 --- a/util/common.go +++ b/util/common.go @@ -1,9 +1,15 @@ package util import ( + "errors" "regexp" "strings" "unicode/utf8" + + "github.com/go-playground/validator/v10" + + "github.com/go-sonic/sonic/handler/trans" + "github.com/go-sonic/sonic/util/xerr" ) func IfElse(condition bool, a, b interface{}) interface{} { @@ -45,3 +51,11 @@ func HTMLFormatWordCount(html string) int64 { text := CleanHTMLTag(html) return int64(utf8.RuneCountInString(text) - len(blankRegexp.FindSubmatchIndex(StringToBytes(text)))) } + +func WrapJSONBindErr(err error) error { + e := validator.ValidationErrors{} + if errors.As(err, &e) { + return xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return xerr.WithStatus(err, xerr.StatusBadRequest) +} diff --git a/util/encrypt.go b/util/encrypt.go new file mode 100644 index 00000000..adf28f97 --- /dev/null +++ b/util/encrypt.go @@ -0,0 +1,13 @@ +package util + +import ( + "crypto/md5" + "encoding/hex" +) + +func Md5(s string) string { + d := []byte(s) + m := md5.New() + m.Write(d) + return hex.EncodeToString(m.Sum(nil)) +}