From 12c9e17c4b14ce36612077b2565eebb9b976f614 Mon Sep 17 00:00:00 2001 From: mStar Date: Sun, 4 May 2025 22:08:06 +0200 Subject: [PATCH] Lots of progress on public AP interface - Read handler for create activities (notes only so far) - Read handler for note objects - Structure laid out for other objects, activities and collections - DB structure for activities created - Update access logging TODO: Create collections type in DB to describe a collection group --- go.mod | 7 +- go.sum | 4 + .../dbgen/activitystreams_activities.gen.go | 394 +++++++++++++++++ storage-new/dbgen/collections.gen.go | 390 +++++++++++++++++ storage-new/dbgen/gen.go | 402 +++++++++--------- storage-new/models/0allTypes.go | 1 + storage-new/models/ActivityCollection.go | 7 + .../models/ActivitystreamsActivityToObject.go | 1 + storage-new/storage.go | 1 + temp.toml | 4 +- web/debug/server.go | 10 +- web/public/api/activitypub/0_activitypub.go | 8 +- web/public/api/activitypub/0_user.go | 1 - web/public/api/activitypub/activityAccept.go | 4 + .../api/activitypub/activityAnnounce.go | 22 + web/public/api/activitypub/activityCreate.go | 93 ++++ web/public/api/activitypub/activityDelete.go | 4 + web/public/api/activitypub/activityReject.go | 4 + web/public/api/activitypub/activityUpdate.go | 4 + web/public/api/activitypub/collection.go | 30 ++ web/public/api/activitypub/objectNote.go | 101 +++++ web/public/middleware/authFetchCheck.go | 6 +- web/public/middleware/traceRequestInfo.go | 28 ++ web/public/server.go | 7 +- 24 files changed, 1327 insertions(+), 206 deletions(-) create mode 100644 storage-new/dbgen/activitystreams_activities.gen.go create mode 100644 storage-new/dbgen/collections.gen.go create mode 100644 storage-new/models/ActivityCollection.go create mode 100644 web/public/api/activitypub/collection.go create mode 100644 web/public/middleware/traceRequestInfo.go diff --git a/go.mod b/go.mod index 70035c6..73f574f 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,10 @@ module git.mstar.dev/mstar/linstrom -go 1.23.0 - -toolchain go1.23.7 +go 1.24.2 require ( git.mstar.dev/mstar/canvas v0.13.1 - git.mstar.dev/mstar/goutils v1.13.0 + git.mstar.dev/mstar/goutils v1.14.0 github.com/BurntSushi/toml v1.5.0 github.com/dgraph-io/ristretto v0.2.0 github.com/eko/gocache/lib/v4 v4.2.0 @@ -38,6 +36,7 @@ require ( require ( filippo.io/edwards25519 v1.1.0 // indirect + git.mstar.dev/mstar/treeificator v0.0.0-20250423153311-dc24f39ca3c9 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect diff --git a/go.sum b/go.sum index b2058b6..68a2d63 100644 --- a/go.sum +++ b/go.sum @@ -39,6 +39,10 @@ git.mstar.dev/mstar/goutils v1.12.3 h1:Wx7i8/a99Cp+Y/XcXgqQr0r9cSsJu7QkWBlKyprTH git.mstar.dev/mstar/goutils v1.12.3/go.mod h1:juxY0eZEMnA95fedRp2LVXvUBgEjz66nE8SEdGKcxMA= git.mstar.dev/mstar/goutils v1.13.0 h1:j2AA3izqTumZyUgC2wi/JdIZAtnDQLve2iexI5kWMgM= git.mstar.dev/mstar/goutils v1.13.0/go.mod h1:juxY0eZEMnA95fedRp2LVXvUBgEjz66nE8SEdGKcxMA= +git.mstar.dev/mstar/goutils v1.14.0 h1:XxhP528dwyaDnnVX3Si3UF3bQgNGZQOQxNTGD86mlEo= +git.mstar.dev/mstar/goutils v1.14.0/go.mod h1:juxY0eZEMnA95fedRp2LVXvUBgEjz66nE8SEdGKcxMA= +git.mstar.dev/mstar/treeificator v0.0.0-20250423153311-dc24f39ca3c9 h1:XXfafmvV6B0Yl6ucec0Gy+VgwWRI+tzLMSyuOgRpXDw= +git.mstar.dev/mstar/treeificator v0.0.0-20250423153311-dc24f39ca3c9/go.mod h1:7wp7WyRRR5gUSur0Ur7RheG1io2QdVoxJPRIrN3pxqo= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= diff --git a/storage-new/dbgen/activitystreams_activities.gen.go b/storage-new/dbgen/activitystreams_activities.gen.go new file mode 100644 index 0000000..b52f570 --- /dev/null +++ b/storage-new/dbgen/activitystreams_activities.gen.go @@ -0,0 +1,394 @@ +// 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 dbgen + +import ( + "context" + "database/sql" + + "git.mstar.dev/mstar/linstrom/storage-new/models" + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + + "gorm.io/gen" + "gorm.io/gen/field" + + "gorm.io/plugin/dbresolver" +) + +func newActivitystreamsActivity(db *gorm.DB, opts ...gen.DOOption) activitystreamsActivity { + _activitystreamsActivity := activitystreamsActivity{} + + _activitystreamsActivity.activitystreamsActivityDo.UseDB(db, opts...) + _activitystreamsActivity.activitystreamsActivityDo.UseModel(&models.ActivitystreamsActivity{}) + + tableName := _activitystreamsActivity.activitystreamsActivityDo.TableName() + _activitystreamsActivity.ALL = field.NewAsterisk(tableName) + _activitystreamsActivity.Id = field.NewString(tableName, "id") + _activitystreamsActivity.Type = field.NewString(tableName, "type") + _activitystreamsActivity.ObjectId = field.NewString(tableName, "object_id") + _activitystreamsActivity.ObjectType = field.NewUint32(tableName, "object_type") + + _activitystreamsActivity.fillFieldMap() + + return _activitystreamsActivity +} + +type activitystreamsActivity struct { + activitystreamsActivityDo + + ALL field.Asterisk + Id field.String + Type field.String + ObjectId field.String + ObjectType field.Uint32 + + fieldMap map[string]field.Expr +} + +func (a activitystreamsActivity) Table(newTableName string) *activitystreamsActivity { + a.activitystreamsActivityDo.UseTable(newTableName) + return a.updateTableName(newTableName) +} + +func (a activitystreamsActivity) As(alias string) *activitystreamsActivity { + a.activitystreamsActivityDo.DO = *(a.activitystreamsActivityDo.As(alias).(*gen.DO)) + return a.updateTableName(alias) +} + +func (a *activitystreamsActivity) updateTableName(table string) *activitystreamsActivity { + a.ALL = field.NewAsterisk(table) + a.Id = field.NewString(table, "id") + a.Type = field.NewString(table, "type") + a.ObjectId = field.NewString(table, "object_id") + a.ObjectType = field.NewUint32(table, "object_type") + + a.fillFieldMap() + + return a +} + +func (a *activitystreamsActivity) 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 *activitystreamsActivity) fillFieldMap() { + a.fieldMap = make(map[string]field.Expr, 4) + a.fieldMap["id"] = a.Id + a.fieldMap["type"] = a.Type + a.fieldMap["object_id"] = a.ObjectId + a.fieldMap["object_type"] = a.ObjectType +} + +func (a activitystreamsActivity) clone(db *gorm.DB) activitystreamsActivity { + a.activitystreamsActivityDo.ReplaceConnPool(db.Statement.ConnPool) + return a +} + +func (a activitystreamsActivity) replaceDB(db *gorm.DB) activitystreamsActivity { + a.activitystreamsActivityDo.ReplaceDB(db) + return a +} + +type activitystreamsActivityDo struct{ gen.DO } + +type IActivitystreamsActivityDo interface { + gen.SubQuery + Debug() IActivitystreamsActivityDo + WithContext(ctx context.Context) IActivitystreamsActivityDo + WithResult(fc func(tx gen.Dao)) gen.ResultInfo + ReplaceDB(db *gorm.DB) + ReadDB() IActivitystreamsActivityDo + WriteDB() IActivitystreamsActivityDo + As(alias string) gen.Dao + Session(config *gorm.Session) IActivitystreamsActivityDo + Columns(cols ...field.Expr) gen.Columns + Clauses(conds ...clause.Expression) IActivitystreamsActivityDo + Not(conds ...gen.Condition) IActivitystreamsActivityDo + Or(conds ...gen.Condition) IActivitystreamsActivityDo + Select(conds ...field.Expr) IActivitystreamsActivityDo + Where(conds ...gen.Condition) IActivitystreamsActivityDo + Order(conds ...field.Expr) IActivitystreamsActivityDo + Distinct(cols ...field.Expr) IActivitystreamsActivityDo + Omit(cols ...field.Expr) IActivitystreamsActivityDo + Join(table schema.Tabler, on ...field.Expr) IActivitystreamsActivityDo + LeftJoin(table schema.Tabler, on ...field.Expr) IActivitystreamsActivityDo + RightJoin(table schema.Tabler, on ...field.Expr) IActivitystreamsActivityDo + Group(cols ...field.Expr) IActivitystreamsActivityDo + Having(conds ...gen.Condition) IActivitystreamsActivityDo + Limit(limit int) IActivitystreamsActivityDo + Offset(offset int) IActivitystreamsActivityDo + Count() (count int64, err error) + Scopes(funcs ...func(gen.Dao) gen.Dao) IActivitystreamsActivityDo + Unscoped() IActivitystreamsActivityDo + Create(values ...*models.ActivitystreamsActivity) error + CreateInBatches(values []*models.ActivitystreamsActivity, batchSize int) error + Save(values ...*models.ActivitystreamsActivity) error + First() (*models.ActivitystreamsActivity, error) + Take() (*models.ActivitystreamsActivity, error) + Last() (*models.ActivitystreamsActivity, error) + Find() ([]*models.ActivitystreamsActivity, error) + FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*models.ActivitystreamsActivity, err error) + FindInBatches(result *[]*models.ActivitystreamsActivity, batchSize int, fc func(tx gen.Dao, batch int) error) error + Pluck(column field.Expr, dest interface{}) error + Delete(...*models.ActivitystreamsActivity) (info gen.ResultInfo, err error) + Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error) + UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) + Updates(value interface{}) (info gen.ResultInfo, err error) + UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error) + UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) + UpdateColumns(value interface{}) (info gen.ResultInfo, err error) + UpdateFrom(q gen.SubQuery) gen.Dao + Attrs(attrs ...field.AssignExpr) IActivitystreamsActivityDo + Assign(attrs ...field.AssignExpr) IActivitystreamsActivityDo + Joins(fields ...field.RelationField) IActivitystreamsActivityDo + Preload(fields ...field.RelationField) IActivitystreamsActivityDo + FirstOrInit() (*models.ActivitystreamsActivity, error) + FirstOrCreate() (*models.ActivitystreamsActivity, error) + FindByPage(offset int, limit int) (result []*models.ActivitystreamsActivity, count int64, err error) + ScanByPage(result interface{}, offset int, limit int) (count int64, err error) + Rows() (*sql.Rows, error) + Row() *sql.Row + Scan(result interface{}) (err error) + Returning(value interface{}, columns ...string) IActivitystreamsActivityDo + UnderlyingDB() *gorm.DB + schema.Tabler +} + +func (a activitystreamsActivityDo) Debug() IActivitystreamsActivityDo { + return a.withDO(a.DO.Debug()) +} + +func (a activitystreamsActivityDo) WithContext(ctx context.Context) IActivitystreamsActivityDo { + return a.withDO(a.DO.WithContext(ctx)) +} + +func (a activitystreamsActivityDo) ReadDB() IActivitystreamsActivityDo { + return a.Clauses(dbresolver.Read) +} + +func (a activitystreamsActivityDo) WriteDB() IActivitystreamsActivityDo { + return a.Clauses(dbresolver.Write) +} + +func (a activitystreamsActivityDo) Session(config *gorm.Session) IActivitystreamsActivityDo { + return a.withDO(a.DO.Session(config)) +} + +func (a activitystreamsActivityDo) Clauses(conds ...clause.Expression) IActivitystreamsActivityDo { + return a.withDO(a.DO.Clauses(conds...)) +} + +func (a activitystreamsActivityDo) Returning(value interface{}, columns ...string) IActivitystreamsActivityDo { + return a.withDO(a.DO.Returning(value, columns...)) +} + +func (a activitystreamsActivityDo) Not(conds ...gen.Condition) IActivitystreamsActivityDo { + return a.withDO(a.DO.Not(conds...)) +} + +func (a activitystreamsActivityDo) Or(conds ...gen.Condition) IActivitystreamsActivityDo { + return a.withDO(a.DO.Or(conds...)) +} + +func (a activitystreamsActivityDo) Select(conds ...field.Expr) IActivitystreamsActivityDo { + return a.withDO(a.DO.Select(conds...)) +} + +func (a activitystreamsActivityDo) Where(conds ...gen.Condition) IActivitystreamsActivityDo { + return a.withDO(a.DO.Where(conds...)) +} + +func (a activitystreamsActivityDo) Order(conds ...field.Expr) IActivitystreamsActivityDo { + return a.withDO(a.DO.Order(conds...)) +} + +func (a activitystreamsActivityDo) Distinct(cols ...field.Expr) IActivitystreamsActivityDo { + return a.withDO(a.DO.Distinct(cols...)) +} + +func (a activitystreamsActivityDo) Omit(cols ...field.Expr) IActivitystreamsActivityDo { + return a.withDO(a.DO.Omit(cols...)) +} + +func (a activitystreamsActivityDo) Join(table schema.Tabler, on ...field.Expr) IActivitystreamsActivityDo { + return a.withDO(a.DO.Join(table, on...)) +} + +func (a activitystreamsActivityDo) LeftJoin(table schema.Tabler, on ...field.Expr) IActivitystreamsActivityDo { + return a.withDO(a.DO.LeftJoin(table, on...)) +} + +func (a activitystreamsActivityDo) RightJoin(table schema.Tabler, on ...field.Expr) IActivitystreamsActivityDo { + return a.withDO(a.DO.RightJoin(table, on...)) +} + +func (a activitystreamsActivityDo) Group(cols ...field.Expr) IActivitystreamsActivityDo { + return a.withDO(a.DO.Group(cols...)) +} + +func (a activitystreamsActivityDo) Having(conds ...gen.Condition) IActivitystreamsActivityDo { + return a.withDO(a.DO.Having(conds...)) +} + +func (a activitystreamsActivityDo) Limit(limit int) IActivitystreamsActivityDo { + return a.withDO(a.DO.Limit(limit)) +} + +func (a activitystreamsActivityDo) Offset(offset int) IActivitystreamsActivityDo { + return a.withDO(a.DO.Offset(offset)) +} + +func (a activitystreamsActivityDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IActivitystreamsActivityDo { + return a.withDO(a.DO.Scopes(funcs...)) +} + +func (a activitystreamsActivityDo) Unscoped() IActivitystreamsActivityDo { + return a.withDO(a.DO.Unscoped()) +} + +func (a activitystreamsActivityDo) Create(values ...*models.ActivitystreamsActivity) error { + if len(values) == 0 { + return nil + } + return a.DO.Create(values) +} + +func (a activitystreamsActivityDo) CreateInBatches(values []*models.ActivitystreamsActivity, 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 activitystreamsActivityDo) Save(values ...*models.ActivitystreamsActivity) error { + if len(values) == 0 { + return nil + } + return a.DO.Save(values) +} + +func (a activitystreamsActivityDo) First() (*models.ActivitystreamsActivity, error) { + if result, err := a.DO.First(); err != nil { + return nil, err + } else { + return result.(*models.ActivitystreamsActivity), nil + } +} + +func (a activitystreamsActivityDo) Take() (*models.ActivitystreamsActivity, error) { + if result, err := a.DO.Take(); err != nil { + return nil, err + } else { + return result.(*models.ActivitystreamsActivity), nil + } +} + +func (a activitystreamsActivityDo) Last() (*models.ActivitystreamsActivity, error) { + if result, err := a.DO.Last(); err != nil { + return nil, err + } else { + return result.(*models.ActivitystreamsActivity), nil + } +} + +func (a activitystreamsActivityDo) Find() ([]*models.ActivitystreamsActivity, error) { + result, err := a.DO.Find() + return result.([]*models.ActivitystreamsActivity), err +} + +func (a activitystreamsActivityDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*models.ActivitystreamsActivity, err error) { + buf := make([]*models.ActivitystreamsActivity, 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 activitystreamsActivityDo) FindInBatches(result *[]*models.ActivitystreamsActivity, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return a.DO.FindInBatches(result, batchSize, fc) +} + +func (a activitystreamsActivityDo) Attrs(attrs ...field.AssignExpr) IActivitystreamsActivityDo { + return a.withDO(a.DO.Attrs(attrs...)) +} + +func (a activitystreamsActivityDo) Assign(attrs ...field.AssignExpr) IActivitystreamsActivityDo { + return a.withDO(a.DO.Assign(attrs...)) +} + +func (a activitystreamsActivityDo) Joins(fields ...field.RelationField) IActivitystreamsActivityDo { + for _, _f := range fields { + a = *a.withDO(a.DO.Joins(_f)) + } + return &a +} + +func (a activitystreamsActivityDo) Preload(fields ...field.RelationField) IActivitystreamsActivityDo { + for _, _f := range fields { + a = *a.withDO(a.DO.Preload(_f)) + } + return &a +} + +func (a activitystreamsActivityDo) FirstOrInit() (*models.ActivitystreamsActivity, error) { + if result, err := a.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*models.ActivitystreamsActivity), nil + } +} + +func (a activitystreamsActivityDo) FirstOrCreate() (*models.ActivitystreamsActivity, error) { + if result, err := a.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*models.ActivitystreamsActivity), nil + } +} + +func (a activitystreamsActivityDo) FindByPage(offset int, limit int) (result []*models.ActivitystreamsActivity, 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 activitystreamsActivityDo) 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 activitystreamsActivityDo) Scan(result interface{}) (err error) { + return a.DO.Scan(result) +} + +func (a activitystreamsActivityDo) Delete(models ...*models.ActivitystreamsActivity) (result gen.ResultInfo, err error) { + return a.DO.Delete(models) +} + +func (a *activitystreamsActivityDo) withDO(do gen.Dao) *activitystreamsActivityDo { + a.DO = *do.(*gen.DO) + return a +} diff --git a/storage-new/dbgen/collections.gen.go b/storage-new/dbgen/collections.gen.go new file mode 100644 index 0000000..7629edf --- /dev/null +++ b/storage-new/dbgen/collections.gen.go @@ -0,0 +1,390 @@ +// 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 dbgen + +import ( + "context" + "database/sql" + + "git.mstar.dev/mstar/linstrom/storage-new/models" + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + + "gorm.io/gen" + "gorm.io/gen/field" + + "gorm.io/plugin/dbresolver" +) + +func newCollection(db *gorm.DB, opts ...gen.DOOption) collection { + _collection := collection{} + + _collection.collectionDo.UseDB(db, opts...) + _collection.collectionDo.UseModel(&models.Collection{}) + + tableName := _collection.collectionDo.TableName() + _collection.ALL = field.NewAsterisk(tableName) + _collection.Id = field.NewString(tableName, "id") + _collection.TargetId = field.NewString(tableName, "target_id") + _collection.TargetType = field.NewUint32(tableName, "target_type") + + _collection.fillFieldMap() + + return _collection +} + +type collection struct { + collectionDo + + ALL field.Asterisk + Id field.String + TargetId field.String + TargetType field.Uint32 + + fieldMap map[string]field.Expr +} + +func (c collection) Table(newTableName string) *collection { + c.collectionDo.UseTable(newTableName) + return c.updateTableName(newTableName) +} + +func (c collection) As(alias string) *collection { + c.collectionDo.DO = *(c.collectionDo.As(alias).(*gen.DO)) + return c.updateTableName(alias) +} + +func (c *collection) updateTableName(table string) *collection { + c.ALL = field.NewAsterisk(table) + c.Id = field.NewString(table, "id") + c.TargetId = field.NewString(table, "target_id") + c.TargetType = field.NewUint32(table, "target_type") + + c.fillFieldMap() + + return c +} + +func (c *collection) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := c.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (c *collection) fillFieldMap() { + c.fieldMap = make(map[string]field.Expr, 3) + c.fieldMap["id"] = c.Id + c.fieldMap["target_id"] = c.TargetId + c.fieldMap["target_type"] = c.TargetType +} + +func (c collection) clone(db *gorm.DB) collection { + c.collectionDo.ReplaceConnPool(db.Statement.ConnPool) + return c +} + +func (c collection) replaceDB(db *gorm.DB) collection { + c.collectionDo.ReplaceDB(db) + return c +} + +type collectionDo struct{ gen.DO } + +type ICollectionDo interface { + gen.SubQuery + Debug() ICollectionDo + WithContext(ctx context.Context) ICollectionDo + WithResult(fc func(tx gen.Dao)) gen.ResultInfo + ReplaceDB(db *gorm.DB) + ReadDB() ICollectionDo + WriteDB() ICollectionDo + As(alias string) gen.Dao + Session(config *gorm.Session) ICollectionDo + Columns(cols ...field.Expr) gen.Columns + Clauses(conds ...clause.Expression) ICollectionDo + Not(conds ...gen.Condition) ICollectionDo + Or(conds ...gen.Condition) ICollectionDo + Select(conds ...field.Expr) ICollectionDo + Where(conds ...gen.Condition) ICollectionDo + Order(conds ...field.Expr) ICollectionDo + Distinct(cols ...field.Expr) ICollectionDo + Omit(cols ...field.Expr) ICollectionDo + Join(table schema.Tabler, on ...field.Expr) ICollectionDo + LeftJoin(table schema.Tabler, on ...field.Expr) ICollectionDo + RightJoin(table schema.Tabler, on ...field.Expr) ICollectionDo + Group(cols ...field.Expr) ICollectionDo + Having(conds ...gen.Condition) ICollectionDo + Limit(limit int) ICollectionDo + Offset(offset int) ICollectionDo + Count() (count int64, err error) + Scopes(funcs ...func(gen.Dao) gen.Dao) ICollectionDo + Unscoped() ICollectionDo + Create(values ...*models.Collection) error + CreateInBatches(values []*models.Collection, batchSize int) error + Save(values ...*models.Collection) error + First() (*models.Collection, error) + Take() (*models.Collection, error) + Last() (*models.Collection, error) + Find() ([]*models.Collection, error) + FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*models.Collection, err error) + FindInBatches(result *[]*models.Collection, batchSize int, fc func(tx gen.Dao, batch int) error) error + Pluck(column field.Expr, dest interface{}) error + Delete(...*models.Collection) (info gen.ResultInfo, err error) + Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error) + UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) + Updates(value interface{}) (info gen.ResultInfo, err error) + UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error) + UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) + UpdateColumns(value interface{}) (info gen.ResultInfo, err error) + UpdateFrom(q gen.SubQuery) gen.Dao + Attrs(attrs ...field.AssignExpr) ICollectionDo + Assign(attrs ...field.AssignExpr) ICollectionDo + Joins(fields ...field.RelationField) ICollectionDo + Preload(fields ...field.RelationField) ICollectionDo + FirstOrInit() (*models.Collection, error) + FirstOrCreate() (*models.Collection, error) + FindByPage(offset int, limit int) (result []*models.Collection, count int64, err error) + ScanByPage(result interface{}, offset int, limit int) (count int64, err error) + Rows() (*sql.Rows, error) + Row() *sql.Row + Scan(result interface{}) (err error) + Returning(value interface{}, columns ...string) ICollectionDo + UnderlyingDB() *gorm.DB + schema.Tabler +} + +func (c collectionDo) Debug() ICollectionDo { + return c.withDO(c.DO.Debug()) +} + +func (c collectionDo) WithContext(ctx context.Context) ICollectionDo { + return c.withDO(c.DO.WithContext(ctx)) +} + +func (c collectionDo) ReadDB() ICollectionDo { + return c.Clauses(dbresolver.Read) +} + +func (c collectionDo) WriteDB() ICollectionDo { + return c.Clauses(dbresolver.Write) +} + +func (c collectionDo) Session(config *gorm.Session) ICollectionDo { + return c.withDO(c.DO.Session(config)) +} + +func (c collectionDo) Clauses(conds ...clause.Expression) ICollectionDo { + return c.withDO(c.DO.Clauses(conds...)) +} + +func (c collectionDo) Returning(value interface{}, columns ...string) ICollectionDo { + return c.withDO(c.DO.Returning(value, columns...)) +} + +func (c collectionDo) Not(conds ...gen.Condition) ICollectionDo { + return c.withDO(c.DO.Not(conds...)) +} + +func (c collectionDo) Or(conds ...gen.Condition) ICollectionDo { + return c.withDO(c.DO.Or(conds...)) +} + +func (c collectionDo) Select(conds ...field.Expr) ICollectionDo { + return c.withDO(c.DO.Select(conds...)) +} + +func (c collectionDo) Where(conds ...gen.Condition) ICollectionDo { + return c.withDO(c.DO.Where(conds...)) +} + +func (c collectionDo) Order(conds ...field.Expr) ICollectionDo { + return c.withDO(c.DO.Order(conds...)) +} + +func (c collectionDo) Distinct(cols ...field.Expr) ICollectionDo { + return c.withDO(c.DO.Distinct(cols...)) +} + +func (c collectionDo) Omit(cols ...field.Expr) ICollectionDo { + return c.withDO(c.DO.Omit(cols...)) +} + +func (c collectionDo) Join(table schema.Tabler, on ...field.Expr) ICollectionDo { + return c.withDO(c.DO.Join(table, on...)) +} + +func (c collectionDo) LeftJoin(table schema.Tabler, on ...field.Expr) ICollectionDo { + return c.withDO(c.DO.LeftJoin(table, on...)) +} + +func (c collectionDo) RightJoin(table schema.Tabler, on ...field.Expr) ICollectionDo { + return c.withDO(c.DO.RightJoin(table, on...)) +} + +func (c collectionDo) Group(cols ...field.Expr) ICollectionDo { + return c.withDO(c.DO.Group(cols...)) +} + +func (c collectionDo) Having(conds ...gen.Condition) ICollectionDo { + return c.withDO(c.DO.Having(conds...)) +} + +func (c collectionDo) Limit(limit int) ICollectionDo { + return c.withDO(c.DO.Limit(limit)) +} + +func (c collectionDo) Offset(offset int) ICollectionDo { + return c.withDO(c.DO.Offset(offset)) +} + +func (c collectionDo) Scopes(funcs ...func(gen.Dao) gen.Dao) ICollectionDo { + return c.withDO(c.DO.Scopes(funcs...)) +} + +func (c collectionDo) Unscoped() ICollectionDo { + return c.withDO(c.DO.Unscoped()) +} + +func (c collectionDo) Create(values ...*models.Collection) error { + if len(values) == 0 { + return nil + } + return c.DO.Create(values) +} + +func (c collectionDo) CreateInBatches(values []*models.Collection, batchSize int) error { + return c.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 (c collectionDo) Save(values ...*models.Collection) error { + if len(values) == 0 { + return nil + } + return c.DO.Save(values) +} + +func (c collectionDo) First() (*models.Collection, error) { + if result, err := c.DO.First(); err != nil { + return nil, err + } else { + return result.(*models.Collection), nil + } +} + +func (c collectionDo) Take() (*models.Collection, error) { + if result, err := c.DO.Take(); err != nil { + return nil, err + } else { + return result.(*models.Collection), nil + } +} + +func (c collectionDo) Last() (*models.Collection, error) { + if result, err := c.DO.Last(); err != nil { + return nil, err + } else { + return result.(*models.Collection), nil + } +} + +func (c collectionDo) Find() ([]*models.Collection, error) { + result, err := c.DO.Find() + return result.([]*models.Collection), err +} + +func (c collectionDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*models.Collection, err error) { + buf := make([]*models.Collection, 0, batchSize) + err = c.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 (c collectionDo) FindInBatches(result *[]*models.Collection, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return c.DO.FindInBatches(result, batchSize, fc) +} + +func (c collectionDo) Attrs(attrs ...field.AssignExpr) ICollectionDo { + return c.withDO(c.DO.Attrs(attrs...)) +} + +func (c collectionDo) Assign(attrs ...field.AssignExpr) ICollectionDo { + return c.withDO(c.DO.Assign(attrs...)) +} + +func (c collectionDo) Joins(fields ...field.RelationField) ICollectionDo { + for _, _f := range fields { + c = *c.withDO(c.DO.Joins(_f)) + } + return &c +} + +func (c collectionDo) Preload(fields ...field.RelationField) ICollectionDo { + for _, _f := range fields { + c = *c.withDO(c.DO.Preload(_f)) + } + return &c +} + +func (c collectionDo) FirstOrInit() (*models.Collection, error) { + if result, err := c.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*models.Collection), nil + } +} + +func (c collectionDo) FirstOrCreate() (*models.Collection, error) { + if result, err := c.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*models.Collection), nil + } +} + +func (c collectionDo) FindByPage(offset int, limit int) (result []*models.Collection, count int64, err error) { + result, err = c.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 = c.Offset(-1).Limit(-1).Count() + return +} + +func (c collectionDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = c.Count() + if err != nil { + return + } + + err = c.Offset(offset).Limit(limit).Scan(result) + return +} + +func (c collectionDo) Scan(result interface{}) (err error) { + return c.DO.Scan(result) +} + +func (c collectionDo) Delete(models ...*models.Collection) (result gen.ResultInfo, err error) { + return c.DO.Delete(models) +} + +func (c *collectionDo) withDO(do gen.Dao) *collectionDo { + c.DO = *do.(*gen.DO) + return c +} diff --git a/storage-new/dbgen/gen.go b/storage-new/dbgen/gen.go index 239051c..1a5caa9 100644 --- a/storage-new/dbgen/gen.go +++ b/storage-new/dbgen/gen.go @@ -16,39 +16,43 @@ import ( ) var ( - Q = new(Query) - AccessToken *accessToken - Emote *emote - Feed *feed - LoginProcessToken *loginProcessToken - MediaMetadata *mediaMetadata - Note *note - NoteEdit *noteEdit - NoteTag *noteTag - NoteToAttachment *noteToAttachment - NoteToBoost *noteToBoost - NoteToEmote *noteToEmote - NoteToFeed *noteToFeed - NoteToPing *noteToPing - Notification *notification - Reaction *reaction - RemoteServer *remoteServer - RemoteServerMetadata *remoteServerMetadata - Role *role - User *user - UserAuthMethod *userAuthMethod - UserInfoField *userInfoField - UserRemoteLinks *userRemoteLinks - UserToBeing *userToBeing - UserToPronoun *userToPronoun - UserToRole *userToRole - UserToTag *userToTag - UserToUserRelation *userToUserRelation + Q = new(Query) + AccessToken *accessToken + ActivitystreamsActivity *activitystreamsActivity + Collection *collection + Emote *emote + Feed *feed + LoginProcessToken *loginProcessToken + MediaMetadata *mediaMetadata + Note *note + NoteEdit *noteEdit + NoteTag *noteTag + NoteToAttachment *noteToAttachment + NoteToBoost *noteToBoost + NoteToEmote *noteToEmote + NoteToFeed *noteToFeed + NoteToPing *noteToPing + Notification *notification + Reaction *reaction + RemoteServer *remoteServer + RemoteServerMetadata *remoteServerMetadata + Role *role + User *user + UserAuthMethod *userAuthMethod + UserInfoField *userInfoField + UserRemoteLinks *userRemoteLinks + UserToBeing *userToBeing + UserToPronoun *userToPronoun + UserToRole *userToRole + UserToTag *userToTag + UserToUserRelation *userToUserRelation ) func SetDefault(db *gorm.DB, opts ...gen.DOOption) { *Q = *Use(db, opts...) AccessToken = &Q.AccessToken + ActivitystreamsActivity = &Q.ActivitystreamsActivity + Collection = &Q.Collection Emote = &Q.Emote Feed = &Q.Feed LoginProcessToken = &Q.LoginProcessToken @@ -79,101 +83,107 @@ func SetDefault(db *gorm.DB, opts ...gen.DOOption) { func Use(db *gorm.DB, opts ...gen.DOOption) *Query { return &Query{ - db: db, - AccessToken: newAccessToken(db, opts...), - Emote: newEmote(db, opts...), - Feed: newFeed(db, opts...), - LoginProcessToken: newLoginProcessToken(db, opts...), - MediaMetadata: newMediaMetadata(db, opts...), - Note: newNote(db, opts...), - NoteEdit: newNoteEdit(db, opts...), - NoteTag: newNoteTag(db, opts...), - NoteToAttachment: newNoteToAttachment(db, opts...), - NoteToBoost: newNoteToBoost(db, opts...), - NoteToEmote: newNoteToEmote(db, opts...), - NoteToFeed: newNoteToFeed(db, opts...), - NoteToPing: newNoteToPing(db, opts...), - Notification: newNotification(db, opts...), - Reaction: newReaction(db, opts...), - RemoteServer: newRemoteServer(db, opts...), - RemoteServerMetadata: newRemoteServerMetadata(db, opts...), - Role: newRole(db, opts...), - User: newUser(db, opts...), - UserAuthMethod: newUserAuthMethod(db, opts...), - UserInfoField: newUserInfoField(db, opts...), - UserRemoteLinks: newUserRemoteLinks(db, opts...), - UserToBeing: newUserToBeing(db, opts...), - UserToPronoun: newUserToPronoun(db, opts...), - UserToRole: newUserToRole(db, opts...), - UserToTag: newUserToTag(db, opts...), - UserToUserRelation: newUserToUserRelation(db, opts...), + db: db, + AccessToken: newAccessToken(db, opts...), + ActivitystreamsActivity: newActivitystreamsActivity(db, opts...), + Collection: newCollection(db, opts...), + Emote: newEmote(db, opts...), + Feed: newFeed(db, opts...), + LoginProcessToken: newLoginProcessToken(db, opts...), + MediaMetadata: newMediaMetadata(db, opts...), + Note: newNote(db, opts...), + NoteEdit: newNoteEdit(db, opts...), + NoteTag: newNoteTag(db, opts...), + NoteToAttachment: newNoteToAttachment(db, opts...), + NoteToBoost: newNoteToBoost(db, opts...), + NoteToEmote: newNoteToEmote(db, opts...), + NoteToFeed: newNoteToFeed(db, opts...), + NoteToPing: newNoteToPing(db, opts...), + Notification: newNotification(db, opts...), + Reaction: newReaction(db, opts...), + RemoteServer: newRemoteServer(db, opts...), + RemoteServerMetadata: newRemoteServerMetadata(db, opts...), + Role: newRole(db, opts...), + User: newUser(db, opts...), + UserAuthMethod: newUserAuthMethod(db, opts...), + UserInfoField: newUserInfoField(db, opts...), + UserRemoteLinks: newUserRemoteLinks(db, opts...), + UserToBeing: newUserToBeing(db, opts...), + UserToPronoun: newUserToPronoun(db, opts...), + UserToRole: newUserToRole(db, opts...), + UserToTag: newUserToTag(db, opts...), + UserToUserRelation: newUserToUserRelation(db, opts...), } } type Query struct { db *gorm.DB - AccessToken accessToken - Emote emote - Feed feed - LoginProcessToken loginProcessToken - MediaMetadata mediaMetadata - Note note - NoteEdit noteEdit - NoteTag noteTag - NoteToAttachment noteToAttachment - NoteToBoost noteToBoost - NoteToEmote noteToEmote - NoteToFeed noteToFeed - NoteToPing noteToPing - Notification notification - Reaction reaction - RemoteServer remoteServer - RemoteServerMetadata remoteServerMetadata - Role role - User user - UserAuthMethod userAuthMethod - UserInfoField userInfoField - UserRemoteLinks userRemoteLinks - UserToBeing userToBeing - UserToPronoun userToPronoun - UserToRole userToRole - UserToTag userToTag - UserToUserRelation userToUserRelation + AccessToken accessToken + ActivitystreamsActivity activitystreamsActivity + Collection collection + Emote emote + Feed feed + LoginProcessToken loginProcessToken + MediaMetadata mediaMetadata + Note note + NoteEdit noteEdit + NoteTag noteTag + NoteToAttachment noteToAttachment + NoteToBoost noteToBoost + NoteToEmote noteToEmote + NoteToFeed noteToFeed + NoteToPing noteToPing + Notification notification + Reaction reaction + RemoteServer remoteServer + RemoteServerMetadata remoteServerMetadata + Role role + User user + UserAuthMethod userAuthMethod + UserInfoField userInfoField + UserRemoteLinks userRemoteLinks + UserToBeing userToBeing + UserToPronoun userToPronoun + UserToRole userToRole + UserToTag userToTag + UserToUserRelation userToUserRelation } func (q *Query) Available() bool { return q.db != nil } func (q *Query) clone(db *gorm.DB) *Query { return &Query{ - db: db, - AccessToken: q.AccessToken.clone(db), - Emote: q.Emote.clone(db), - Feed: q.Feed.clone(db), - LoginProcessToken: q.LoginProcessToken.clone(db), - MediaMetadata: q.MediaMetadata.clone(db), - Note: q.Note.clone(db), - NoteEdit: q.NoteEdit.clone(db), - NoteTag: q.NoteTag.clone(db), - NoteToAttachment: q.NoteToAttachment.clone(db), - NoteToBoost: q.NoteToBoost.clone(db), - NoteToEmote: q.NoteToEmote.clone(db), - NoteToFeed: q.NoteToFeed.clone(db), - NoteToPing: q.NoteToPing.clone(db), - Notification: q.Notification.clone(db), - Reaction: q.Reaction.clone(db), - RemoteServer: q.RemoteServer.clone(db), - RemoteServerMetadata: q.RemoteServerMetadata.clone(db), - Role: q.Role.clone(db), - User: q.User.clone(db), - UserAuthMethod: q.UserAuthMethod.clone(db), - UserInfoField: q.UserInfoField.clone(db), - UserRemoteLinks: q.UserRemoteLinks.clone(db), - UserToBeing: q.UserToBeing.clone(db), - UserToPronoun: q.UserToPronoun.clone(db), - UserToRole: q.UserToRole.clone(db), - UserToTag: q.UserToTag.clone(db), - UserToUserRelation: q.UserToUserRelation.clone(db), + db: db, + AccessToken: q.AccessToken.clone(db), + ActivitystreamsActivity: q.ActivitystreamsActivity.clone(db), + Collection: q.Collection.clone(db), + Emote: q.Emote.clone(db), + Feed: q.Feed.clone(db), + LoginProcessToken: q.LoginProcessToken.clone(db), + MediaMetadata: q.MediaMetadata.clone(db), + Note: q.Note.clone(db), + NoteEdit: q.NoteEdit.clone(db), + NoteTag: q.NoteTag.clone(db), + NoteToAttachment: q.NoteToAttachment.clone(db), + NoteToBoost: q.NoteToBoost.clone(db), + NoteToEmote: q.NoteToEmote.clone(db), + NoteToFeed: q.NoteToFeed.clone(db), + NoteToPing: q.NoteToPing.clone(db), + Notification: q.Notification.clone(db), + Reaction: q.Reaction.clone(db), + RemoteServer: q.RemoteServer.clone(db), + RemoteServerMetadata: q.RemoteServerMetadata.clone(db), + Role: q.Role.clone(db), + User: q.User.clone(db), + UserAuthMethod: q.UserAuthMethod.clone(db), + UserInfoField: q.UserInfoField.clone(db), + UserRemoteLinks: q.UserRemoteLinks.clone(db), + UserToBeing: q.UserToBeing.clone(db), + UserToPronoun: q.UserToPronoun.clone(db), + UserToRole: q.UserToRole.clone(db), + UserToTag: q.UserToTag.clone(db), + UserToUserRelation: q.UserToUserRelation.clone(db), } } @@ -187,96 +197,102 @@ func (q *Query) WriteDB() *Query { func (q *Query) ReplaceDB(db *gorm.DB) *Query { return &Query{ - db: db, - AccessToken: q.AccessToken.replaceDB(db), - Emote: q.Emote.replaceDB(db), - Feed: q.Feed.replaceDB(db), - LoginProcessToken: q.LoginProcessToken.replaceDB(db), - MediaMetadata: q.MediaMetadata.replaceDB(db), - Note: q.Note.replaceDB(db), - NoteEdit: q.NoteEdit.replaceDB(db), - NoteTag: q.NoteTag.replaceDB(db), - NoteToAttachment: q.NoteToAttachment.replaceDB(db), - NoteToBoost: q.NoteToBoost.replaceDB(db), - NoteToEmote: q.NoteToEmote.replaceDB(db), - NoteToFeed: q.NoteToFeed.replaceDB(db), - NoteToPing: q.NoteToPing.replaceDB(db), - Notification: q.Notification.replaceDB(db), - Reaction: q.Reaction.replaceDB(db), - RemoteServer: q.RemoteServer.replaceDB(db), - RemoteServerMetadata: q.RemoteServerMetadata.replaceDB(db), - Role: q.Role.replaceDB(db), - User: q.User.replaceDB(db), - UserAuthMethod: q.UserAuthMethod.replaceDB(db), - UserInfoField: q.UserInfoField.replaceDB(db), - UserRemoteLinks: q.UserRemoteLinks.replaceDB(db), - UserToBeing: q.UserToBeing.replaceDB(db), - UserToPronoun: q.UserToPronoun.replaceDB(db), - UserToRole: q.UserToRole.replaceDB(db), - UserToTag: q.UserToTag.replaceDB(db), - UserToUserRelation: q.UserToUserRelation.replaceDB(db), + db: db, + AccessToken: q.AccessToken.replaceDB(db), + ActivitystreamsActivity: q.ActivitystreamsActivity.replaceDB(db), + Collection: q.Collection.replaceDB(db), + Emote: q.Emote.replaceDB(db), + Feed: q.Feed.replaceDB(db), + LoginProcessToken: q.LoginProcessToken.replaceDB(db), + MediaMetadata: q.MediaMetadata.replaceDB(db), + Note: q.Note.replaceDB(db), + NoteEdit: q.NoteEdit.replaceDB(db), + NoteTag: q.NoteTag.replaceDB(db), + NoteToAttachment: q.NoteToAttachment.replaceDB(db), + NoteToBoost: q.NoteToBoost.replaceDB(db), + NoteToEmote: q.NoteToEmote.replaceDB(db), + NoteToFeed: q.NoteToFeed.replaceDB(db), + NoteToPing: q.NoteToPing.replaceDB(db), + Notification: q.Notification.replaceDB(db), + Reaction: q.Reaction.replaceDB(db), + RemoteServer: q.RemoteServer.replaceDB(db), + RemoteServerMetadata: q.RemoteServerMetadata.replaceDB(db), + Role: q.Role.replaceDB(db), + User: q.User.replaceDB(db), + UserAuthMethod: q.UserAuthMethod.replaceDB(db), + UserInfoField: q.UserInfoField.replaceDB(db), + UserRemoteLinks: q.UserRemoteLinks.replaceDB(db), + UserToBeing: q.UserToBeing.replaceDB(db), + UserToPronoun: q.UserToPronoun.replaceDB(db), + UserToRole: q.UserToRole.replaceDB(db), + UserToTag: q.UserToTag.replaceDB(db), + UserToUserRelation: q.UserToUserRelation.replaceDB(db), } } type queryCtx struct { - AccessToken IAccessTokenDo - Emote IEmoteDo - Feed IFeedDo - LoginProcessToken ILoginProcessTokenDo - MediaMetadata IMediaMetadataDo - Note INoteDo - NoteEdit INoteEditDo - NoteTag INoteTagDo - NoteToAttachment INoteToAttachmentDo - NoteToBoost INoteToBoostDo - NoteToEmote INoteToEmoteDo - NoteToFeed INoteToFeedDo - NoteToPing INoteToPingDo - Notification INotificationDo - Reaction IReactionDo - RemoteServer IRemoteServerDo - RemoteServerMetadata IRemoteServerMetadataDo - Role IRoleDo - User IUserDo - UserAuthMethod IUserAuthMethodDo - UserInfoField IUserInfoFieldDo - UserRemoteLinks IUserRemoteLinksDo - UserToBeing IUserToBeingDo - UserToPronoun IUserToPronounDo - UserToRole IUserToRoleDo - UserToTag IUserToTagDo - UserToUserRelation IUserToUserRelationDo + AccessToken IAccessTokenDo + ActivitystreamsActivity IActivitystreamsActivityDo + Collection ICollectionDo + Emote IEmoteDo + Feed IFeedDo + LoginProcessToken ILoginProcessTokenDo + MediaMetadata IMediaMetadataDo + Note INoteDo + NoteEdit INoteEditDo + NoteTag INoteTagDo + NoteToAttachment INoteToAttachmentDo + NoteToBoost INoteToBoostDo + NoteToEmote INoteToEmoteDo + NoteToFeed INoteToFeedDo + NoteToPing INoteToPingDo + Notification INotificationDo + Reaction IReactionDo + RemoteServer IRemoteServerDo + RemoteServerMetadata IRemoteServerMetadataDo + Role IRoleDo + User IUserDo + UserAuthMethod IUserAuthMethodDo + UserInfoField IUserInfoFieldDo + UserRemoteLinks IUserRemoteLinksDo + UserToBeing IUserToBeingDo + UserToPronoun IUserToPronounDo + UserToRole IUserToRoleDo + UserToTag IUserToTagDo + UserToUserRelation IUserToUserRelationDo } func (q *Query) WithContext(ctx context.Context) *queryCtx { return &queryCtx{ - AccessToken: q.AccessToken.WithContext(ctx), - Emote: q.Emote.WithContext(ctx), - Feed: q.Feed.WithContext(ctx), - LoginProcessToken: q.LoginProcessToken.WithContext(ctx), - MediaMetadata: q.MediaMetadata.WithContext(ctx), - Note: q.Note.WithContext(ctx), - NoteEdit: q.NoteEdit.WithContext(ctx), - NoteTag: q.NoteTag.WithContext(ctx), - NoteToAttachment: q.NoteToAttachment.WithContext(ctx), - NoteToBoost: q.NoteToBoost.WithContext(ctx), - NoteToEmote: q.NoteToEmote.WithContext(ctx), - NoteToFeed: q.NoteToFeed.WithContext(ctx), - NoteToPing: q.NoteToPing.WithContext(ctx), - Notification: q.Notification.WithContext(ctx), - Reaction: q.Reaction.WithContext(ctx), - RemoteServer: q.RemoteServer.WithContext(ctx), - RemoteServerMetadata: q.RemoteServerMetadata.WithContext(ctx), - Role: q.Role.WithContext(ctx), - User: q.User.WithContext(ctx), - UserAuthMethod: q.UserAuthMethod.WithContext(ctx), - UserInfoField: q.UserInfoField.WithContext(ctx), - UserRemoteLinks: q.UserRemoteLinks.WithContext(ctx), - UserToBeing: q.UserToBeing.WithContext(ctx), - UserToPronoun: q.UserToPronoun.WithContext(ctx), - UserToRole: q.UserToRole.WithContext(ctx), - UserToTag: q.UserToTag.WithContext(ctx), - UserToUserRelation: q.UserToUserRelation.WithContext(ctx), + AccessToken: q.AccessToken.WithContext(ctx), + ActivitystreamsActivity: q.ActivitystreamsActivity.WithContext(ctx), + Collection: q.Collection.WithContext(ctx), + Emote: q.Emote.WithContext(ctx), + Feed: q.Feed.WithContext(ctx), + LoginProcessToken: q.LoginProcessToken.WithContext(ctx), + MediaMetadata: q.MediaMetadata.WithContext(ctx), + Note: q.Note.WithContext(ctx), + NoteEdit: q.NoteEdit.WithContext(ctx), + NoteTag: q.NoteTag.WithContext(ctx), + NoteToAttachment: q.NoteToAttachment.WithContext(ctx), + NoteToBoost: q.NoteToBoost.WithContext(ctx), + NoteToEmote: q.NoteToEmote.WithContext(ctx), + NoteToFeed: q.NoteToFeed.WithContext(ctx), + NoteToPing: q.NoteToPing.WithContext(ctx), + Notification: q.Notification.WithContext(ctx), + Reaction: q.Reaction.WithContext(ctx), + RemoteServer: q.RemoteServer.WithContext(ctx), + RemoteServerMetadata: q.RemoteServerMetadata.WithContext(ctx), + Role: q.Role.WithContext(ctx), + User: q.User.WithContext(ctx), + UserAuthMethod: q.UserAuthMethod.WithContext(ctx), + UserInfoField: q.UserInfoField.WithContext(ctx), + UserRemoteLinks: q.UserRemoteLinks.WithContext(ctx), + UserToBeing: q.UserToBeing.WithContext(ctx), + UserToPronoun: q.UserToPronoun.WithContext(ctx), + UserToRole: q.UserToRole.WithContext(ctx), + UserToTag: q.UserToTag.WithContext(ctx), + UserToUserRelation: q.UserToUserRelation.WithContext(ctx), } } diff --git a/storage-new/models/0allTypes.go b/storage-new/models/0allTypes.go index b67d171..dc77365 100644 --- a/storage-new/models/0allTypes.go +++ b/storage-new/models/0allTypes.go @@ -2,6 +2,7 @@ package models // A list of all models stored in the database var AllTypes = []any{ + &Collection{}, &ActivitystreamsActivity{}, &Emote{}, &Feed{}, diff --git a/storage-new/models/ActivityCollection.go b/storage-new/models/ActivityCollection.go new file mode 100644 index 0000000..065af66 --- /dev/null +++ b/storage-new/models/ActivityCollection.go @@ -0,0 +1,7 @@ +package models + +type Collection struct { + Id string `gorm:"primarykey"` + TargetId string + TargetType uint32 +} diff --git a/storage-new/models/ActivitystreamsActivityToObject.go b/storage-new/models/ActivitystreamsActivityToObject.go index 36ac409..a08797a 100644 --- a/storage-new/models/ActivitystreamsActivityToObject.go +++ b/storage-new/models/ActivitystreamsActivityToObject.go @@ -1,6 +1,7 @@ package models type ActivitystreamsActivity struct { + Id string `gorm:"primarykey"` Type string `gorm:"type:activitystreams_activity_type"` ObjectId string ObjectType uint32 // Target type: ActivitystreamsActivityTargetType diff --git a/storage-new/storage.go b/storage-new/storage.go index 8632b32..c51611c 100644 --- a/storage-new/storage.go +++ b/storage-new/storage.go @@ -68,6 +68,7 @@ func AttemptReconnect() { // HandleReconnectError checks if the given error requires // a reconnect attempt. If it does, it also starts // a reconnection attempt. +// Returns true if a reconnect has been started func HandleReconnectError(errToCheck error) bool { if _, ok := errToCheck.(*pgconn.ConnectError); ok { go AttemptReconnect() diff --git a/temp.toml b/temp.toml index 55dee49..dd4bba2 100644 --- a/temp.toml +++ b/temp.toml @@ -1,7 +1,7 @@ [general] protocol = "https" - domain = "serveo.net" - subdomain = "e8f7573481d6b2311fb076f4190ac852" + domain = "lhr.life" + subdomain = "61f6e346a99d58" private_port = 8080 public_port = 443 diff --git a/web/debug/server.go b/web/debug/server.go index 490e9fa..31388a3 100644 --- a/web/debug/server.go +++ b/web/debug/server.go @@ -14,6 +14,8 @@ import ( "net/http" webutils "git.mstar.dev/mstar/goutils/http" + + webmiddleware "git.mstar.dev/mstar/linstrom/web/public/middleware" ) type Server struct { @@ -36,7 +38,13 @@ func New(addr string) *Server { Addr: addr, Handler: webutils.ChainMiddlewares( handler, - webutils.BuildLoggingMiddleware(map[string]string{"server": "debug"}), + webutils.BuildLoggingMiddleware( + true, + []string{"/assets"}, + map[string]string{"server": "debug"}, + ), + webmiddleware.AppendFullPathMiddleware, + webmiddleware.TraceRequestInfoMiddleware, ), } return &Server{&web} diff --git a/web/public/api/activitypub/0_activitypub.go b/web/public/api/activitypub/0_activitypub.go index d1bae44..f955fb7 100644 --- a/web/public/api/activitypub/0_activitypub.go +++ b/web/public/api/activitypub/0_activitypub.go @@ -9,8 +9,14 @@ func BuildActivitypubRouter() http.Handler { router := http.NewServeMux() router.HandleFunc("/user/{id}", users) router.HandleFunc("/user/{id}/inbox", userInbox) + router.HandleFunc("/activity/accept/{id}", activityAccept) + router.HandleFunc("/activity/create/{id}", activityCreate) + router.HandleFunc("/activity/delete/{id}", activityDelete) + router.HandleFunc("/activity/reject/{id}", activityReject) + router.HandleFunc("/activity/update/{id}", activityUpdate) + router.HandleFunc("/note/{id}", objectNote) router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, "in ap") + _, _ = fmt.Fprint(w, "in ap") }) return router } diff --git a/web/public/api/activitypub/0_user.go b/web/public/api/activitypub/0_user.go index 13ce667..3ad6824 100644 --- a/web/public/api/activitypub/0_user.go +++ b/web/public/api/activitypub/0_user.go @@ -54,7 +54,6 @@ func users(w http.ResponseWriter, r *http.Request) { } log := hlog.FromRequest(r) userId := r.PathValue("id") - log.Debug().Any("headers", r.Header).Msg("request headers") user, err := dbgen.User.Where(dbgen.User.ID.Eq(userId)). Preload(dbgen.User.Icon).Preload(dbgen.User.Banner). Preload(dbgen.User.BeingTypes). diff --git a/web/public/api/activitypub/activityAccept.go b/web/public/api/activitypub/activityAccept.go index 7c30024..e920807 100644 --- a/web/public/api/activitypub/activityAccept.go +++ b/web/public/api/activitypub/activityAccept.go @@ -1 +1,5 @@ package activitypub + +import "net/http" + +func activityAccept(w http.ResponseWriter, r *http.Request) {} diff --git a/web/public/api/activitypub/activityAnnounce.go b/web/public/api/activitypub/activityAnnounce.go index 7c30024..b8b64da 100644 --- a/web/public/api/activitypub/activityAnnounce.go +++ b/web/public/api/activitypub/activityAnnounce.go @@ -1 +1,23 @@ package activitypub + +import ( + "context" + "time" +) + +// Announce is boost + +type activityAnnounceOut struct { + Context any `json:"@context,omitempty"` + Id string + Type string // Always "Announce" + Actor string // The one doing the boost + Published time.Time + To []string + CC []string + Object string // Link to object being boosted +} + +func announceFromStorage(ctx context.Context, id string) (*activityAnnounceOut, error) { + panic("not implemented") +} diff --git a/web/public/api/activitypub/activityCreate.go b/web/public/api/activitypub/activityCreate.go index 7c30024..365ee8c 100644 --- a/web/public/api/activitypub/activityCreate.go +++ b/web/public/api/activitypub/activityCreate.go @@ -1 +1,94 @@ package activitypub + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + webutils "git.mstar.dev/mstar/goutils/http" + "github.com/rs/zerolog/hlog" + "gorm.io/gorm" + + "git.mstar.dev/mstar/linstrom/activitypub" + "git.mstar.dev/mstar/linstrom/config" + "git.mstar.dev/mstar/linstrom/storage-new" + "git.mstar.dev/mstar/linstrom/storage-new/dbgen" + "git.mstar.dev/mstar/linstrom/storage-new/models" +) + +type activityCreateOut struct { + Context any `json:"@context,omitempty"` + Id string `json:"id"` + Type string `json:"type"` + Actor string `json:"actor"` + Object any `json:"object"` +} + +func activityCreate(w http.ResponseWriter, r *http.Request) { + log := hlog.FromRequest(r) + id := r.PathValue("id") + activity, err := createFromStorage(r.Context(), id) + switch err { + case gorm.ErrRecordNotFound: + webutils.ProblemDetailsStatusOnly(w, http.StatusNotFound) + case nil: + activity.Context = activitypub.BaseLdContext + data, err := json.Marshal(activity) + if err != nil { + log.Error().Err(err).Any("activity", activity).Msg("Failed to marshal create activity") + webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError) + return + } + w.Header().Add("Content-Type", "application/activity+json") + fmt.Fprint(w, string(data)) + default: + if storage.HandleReconnectError(err) { + log.Error().Err(err).Msg("Connection failed, restart attempt started") + } else { + log.Error().Err(err).Msg("Failed to get create activity from db") + } + webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError) + } +} + +// Find a create activity from the db and format it for activitypub reads +// Does not set the context for the activity, in case the activity is embedded +// in another activity or object. That's the responsibility of the handler +// getting the final result +func createFromStorage(ctx context.Context, id string) (*activityCreateOut, error) { + // log := log.Ctx(ctx) + asa := dbgen.ActivitystreamsActivity + activity, err := asa.Where(asa.Type.Eq(string(models.ActivityCreate))). + Where(asa.Id.Eq(id)). + First() + if err != nil { + return nil, err + } + switch models.ActivitystreamsActivityTargetType(activity.ObjectType) { + case models.ActivitystreamsActivityTargetNote: + note, err := noteFromStorage(ctx, activity.ObjectId) + if err != nil { + return nil, err + } + out := activityCreateOut{ + Id: config.GlobalConfig.General.GetFullPublicUrl() + "/api/activitypub/create/" + id, + Type: "Create", + Actor: note.AttributedTo, + Object: note, + } + return &out, nil + case models.ActivitystreamsActivityTargetBoost: + panic("Not implemented") + case models.ActivitystreamsActivityTargetReaction: + panic("Not implemented") + case models.ActivitystreamsActivityTargetActivity: + panic("Not implemented") + case models.ActivitystreamsActivityTargetUser: + panic("Not implemented") + case models.ActivitystreamsActivityTargetUnknown: + panic("Not implemented") + default: + panic("Not implemented") + } +} diff --git a/web/public/api/activitypub/activityDelete.go b/web/public/api/activitypub/activityDelete.go index 7c30024..e939bf2 100644 --- a/web/public/api/activitypub/activityDelete.go +++ b/web/public/api/activitypub/activityDelete.go @@ -1 +1,5 @@ package activitypub + +import "net/http" + +func activityDelete(w http.ResponseWriter, r *http.Request) {} diff --git a/web/public/api/activitypub/activityReject.go b/web/public/api/activitypub/activityReject.go index 7c30024..824ccf7 100644 --- a/web/public/api/activitypub/activityReject.go +++ b/web/public/api/activitypub/activityReject.go @@ -1 +1,5 @@ package activitypub + +import "net/http" + +func activityReject(w http.ResponseWriter, r *http.Request) {} diff --git a/web/public/api/activitypub/activityUpdate.go b/web/public/api/activitypub/activityUpdate.go index 7c30024..03614fd 100644 --- a/web/public/api/activitypub/activityUpdate.go +++ b/web/public/api/activitypub/activityUpdate.go @@ -1 +1,5 @@ package activitypub + +import "net/http" + +func activityUpdate(w http.ResponseWriter, r *http.Request) {} diff --git a/web/public/api/activitypub/collection.go b/web/public/api/activitypub/collection.go new file mode 100644 index 0000000..f996e23 --- /dev/null +++ b/web/public/api/activitypub/collection.go @@ -0,0 +1,30 @@ +package activitypub + +import "net/http" + +// Used for both unordered and ordered +type collectionOut struct { + Context any + Summary string + Type string + Items []any + Id string + TotalItems int + First *collectionPageOut +} + +// Used for both unordered and ordered +type collectionPageOut struct { + Context any + Type string + Id string + PartOf string + Next string + Items []any +} + +// Unordered collections handler +func collections(w http.ResponseWriter, r *http.Request) {} + +// Ordered collections handler +func orderedCollections(w http.ResponseWriter, r *http.Request) {} diff --git a/web/public/api/activitypub/objectNote.go b/web/public/api/activitypub/objectNote.go index 7c30024..9ca67c7 100644 --- a/web/public/api/activitypub/objectNote.go +++ b/web/public/api/activitypub/objectNote.go @@ -1 +1,102 @@ package activitypub + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + webutils "git.mstar.dev/mstar/goutils/http" + "github.com/rs/zerolog/hlog" + "gorm.io/gorm" + + "git.mstar.dev/mstar/linstrom/activitypub" + "git.mstar.dev/mstar/linstrom/config" + "git.mstar.dev/mstar/linstrom/storage-new" + "git.mstar.dev/mstar/linstrom/storage-new/dbgen" +) + +type objectNoteOut struct { + // Context should be set, if needed, by the endpoint handler + Context any `json:"@context,omitempty"` + + // Attributes below set from storage + + Id string `json:"id"` + Type string `json:"type"` + Summary *string `json:"summary"` + InReplyTo *string `json:"inReplyTo"` + Published time.Time `json:"published"` + Url string `json:"url"` + AttributedTo string `json:"attributedTo"` + To []string `json:"to"` + // CC []string `json:"cc"` // FIXME: Uncomment once followers collection implemented + Sensitive bool `json:"sensitive"` + AtomUri string `json:"atomUri"` + InReplyToAtomUri *string `json:"inReplyToAtomUri"` + // Conversation string `json:"conversation"` // FIXME: Uncomment once understood what this field wants + Content string `json:"content"` + // ContentMap map[string]string `json:"content_map"` // TODO: Uncomment once/if support for multiple languages available + // Attachments []string `json:"attachments"` // FIXME: Change this to document type + // Tags []string `json:"tags"` // FIXME: Change this to hashtag type + // Replies any `json:"replies"` // FIXME: Change this to collection type embedding first page + // Likes any `json:"likes"` // FIXME: Change this to collection + // Shares any `json:"shares"` // FIXME: Change this to collection, is boosts +} + +func objectNote(w http.ResponseWriter, r *http.Request) { + id := r.PathValue("id") + log := hlog.FromRequest(r) + note, err := noteFromStorage(r.Context(), id) + switch err { + case gorm.ErrRecordNotFound: + webutils.ProblemDetailsStatusOnly(w, http.StatusNotFound) + return + case nil: + note.Context = activitypub.BaseLdContext + data, err := json.Marshal(note) + if err != nil { + log.Error().Err(err).Any("activity", note).Msg("Failed to marshal create activity") + webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError) + return + } + w.Header().Add("Content-Type", "application/activity+json") + fmt.Fprint(w, string(data)) + default: + if storage.HandleReconnectError(err) { + log.Error().Err(err).Msg("Connection failed, restart attempt started") + } else { + log.Error().Err(err).Msg("Failed to get create activity from db") + } + webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError) + } +} + +func noteFromStorage(ctx context.Context, id string) (*objectNoteOut, error) { + note, err := dbgen.Note.Where(dbgen.Note.ID.Eq(id)).Preload(dbgen.Note.Creator).First() + if err != nil { + return nil, err + } + data := &objectNoteOut{ + Id: config.GlobalConfig.General.GetFullPublicUrl() + "/api/activitypub/note/" + id, + Type: "Note", + Published: note.CreatedAt, + AttributedTo: config.GlobalConfig.General.GetFullPublicUrl() + "/api/activitypub/user/" + note.CreatorId, + Content: note.RawContent, // FIXME: Escape content + Url: config.GlobalConfig.General.GetFullPublicUrl() + "/@" + note.Creator.Username + "/" + id, + To: []string{ + "https://www.w3.org/ns/activitystreams#Public", + }, // FIXME: Replace with proper targets, not always public + AtomUri: config.GlobalConfig.General.GetFullPublicUrl() + "/api/activitypub/object/" + id, + } + if note.RepliesTo.Valid { + data.InReplyTo = ¬e.RepliesTo.String + data.InReplyToAtomUri = ¬e.RepliesTo.String + } + if note.ContentWarning.Valid { + data.Summary = ¬e.ContentWarning.String + data.Sensitive = true + } + return data, nil +} diff --git a/web/public/middleware/authFetchCheck.go b/web/public/middleware/authFetchCheck.go index 1e39f5c..fb679c2 100644 --- a/web/public/middleware/authFetchCheck.go +++ b/web/public/middleware/authFetchCheck.go @@ -64,13 +64,13 @@ func BuildAuthorizedFetchCheck(forNonGet bool, forGet bool) webutils.HandlerBuil if !config.GlobalConfig.Experimental.AuthFetchForServerActor && strings.Contains(path, storage.ServerActorId) { - log.Info().Msg("Server actor requested, no auth") + log.Debug().Msg("Server actor requested, no auth") h.ServeHTTP(w, r) return } // Not an always open path, check methods if r.Method == "GET" && !forGet { - log.Info().Msg("Get request to AP resources don't need signature") + log.Debug().Msg("Get request to AP resources don't need signature") h.ServeHTTP(w, r) return } else if !forGet && !forNonGet { @@ -78,7 +78,7 @@ func BuildAuthorizedFetchCheck(forNonGet bool, forGet bool) webutils.HandlerBuil h.ServeHTTP(w, r) return } - log.Info().Msg("Need signature for AP request") + log.Debug().Msg("Need signature for AP request") rawDate := r.Header.Get("Date") date, err := http.ParseTime(rawDate) if err != nil { diff --git a/web/public/middleware/traceRequestInfo.go b/web/public/middleware/traceRequestInfo.go new file mode 100644 index 0000000..a6c7731 --- /dev/null +++ b/web/public/middleware/traceRequestInfo.go @@ -0,0 +1,28 @@ +package webmiddleware + +import ( + "bytes" + "io" + "net/http" + "time" + + webutils "git.mstar.dev/mstar/goutils/http" + "github.com/rs/zerolog/hlog" +) + +func TraceRequestInfoMiddleware(h http.Handler) http.Handler { + return webutils.ChainMiddlewares( + h, + hlog.AccessHandler(func(r *http.Request, status, size int, duration time.Duration) { + body, _ := io.ReadAll(r.Body) + r.Body = io.NopCloser(bytes.NewReader(body)) + hlog.FromRequest(r).Trace().Any("headers", r.Header). + Bytes("body", body). + Str("method", r.Method). + Stringer("url", r.URL). + Int("status", status). + Int("size", size). + Dur("duration", duration) + }), + ) +} diff --git a/web/public/server.go b/web/public/server.go index 065472e..7dbd68f 100644 --- a/web/public/server.go +++ b/web/public/server.go @@ -59,8 +59,13 @@ func New(addr string, duckImg *string, duckFs fs.FS) *Server { server := http.Server{ Handler: webutils.ChainMiddlewares( handler, - webutils.BuildLoggingMiddleware(map[string]string{"server": "public"}), + webutils.BuildLoggingMiddleware( + true, + []string{"/assets"}, + map[string]string{"server": "public"}, + ), webmiddleware.AppendFullPathMiddleware, + webmiddleware.TraceRequestInfoMiddleware, ), Addr: addr, }