[feature] Implement Report database model and utility functions (#1310)
* implement report database model * implement report cache + config changes * implement report database functions * report uri / regex functions * update envparsing test * remove unnecessary uri index * remove unused function + cache lookup * process error when storing report
This commit is contained in:
parent
36aa6854bd
commit
d6487933c7
|
@ -207,6 +207,10 @@ cache:
|
|||
notification-ttl: "5m"
|
||||
notification-sweep-freq: "10s"
|
||||
|
||||
report-max-size: 100
|
||||
report-ttl: "5m"
|
||||
report-sweep-freq: "10s"
|
||||
|
||||
status-max-size: 500
|
||||
status-ttl: "5m"
|
||||
status-sweep-freq: "10s"
|
||||
|
|
|
@ -57,6 +57,9 @@ type GTSCaches interface {
|
|||
// Notification provides access to the gtsmodel Notification database cache.
|
||||
Notification() *result.Cache[*gtsmodel.Notification]
|
||||
|
||||
// Report provides access to the gtsmodel Report database cache.
|
||||
Report() *result.Cache[*gtsmodel.Report]
|
||||
|
||||
// Status provides access to the gtsmodel Status database cache.
|
||||
Status() *result.Cache[*gtsmodel.Status]
|
||||
|
||||
|
@ -80,6 +83,7 @@ type gtsCaches struct {
|
|||
emojiCategory *result.Cache[*gtsmodel.EmojiCategory]
|
||||
mention *result.Cache[*gtsmodel.Mention]
|
||||
notification *result.Cache[*gtsmodel.Notification]
|
||||
report *result.Cache[*gtsmodel.Report]
|
||||
status *result.Cache[*gtsmodel.Status]
|
||||
tombstone *result.Cache[*gtsmodel.Tombstone]
|
||||
user *result.Cache[*gtsmodel.User]
|
||||
|
@ -93,6 +97,7 @@ func (c *gtsCaches) Init() {
|
|||
c.initEmojiCategory()
|
||||
c.initMention()
|
||||
c.initNotification()
|
||||
c.initReport()
|
||||
c.initStatus()
|
||||
c.initTombstone()
|
||||
c.initUser()
|
||||
|
@ -120,6 +125,9 @@ func (c *gtsCaches) Start() {
|
|||
tryUntil("starting gtsmodel.Notification cache", 5, func() bool {
|
||||
return c.notification.Start(config.GetCacheGTSNotificationSweepFreq())
|
||||
})
|
||||
tryUntil("starting gtsmodel.Report cache", 5, func() bool {
|
||||
return c.report.Start(config.GetCacheGTSReportSweepFreq())
|
||||
})
|
||||
tryUntil("starting gtsmodel.Status cache", 5, func() bool {
|
||||
return c.status.Start(config.GetCacheGTSStatusSweepFreq())
|
||||
})
|
||||
|
@ -139,6 +147,7 @@ func (c *gtsCaches) Stop() {
|
|||
tryUntil("stopping gtsmodel.EmojiCategory cache", 5, c.emojiCategory.Stop)
|
||||
tryUntil("stopping gtsmodel.Mention cache", 5, c.mention.Stop)
|
||||
tryUntil("stopping gtsmodel.Notification cache", 5, c.notification.Stop)
|
||||
tryUntil("stopping gtsmodel.Report cache", 5, c.report.Stop)
|
||||
tryUntil("stopping gtsmodel.Status cache", 5, c.status.Stop)
|
||||
tryUntil("stopping gtsmodel.Tombstone cache", 5, c.tombstone.Stop)
|
||||
tryUntil("stopping gtsmodel.User cache", 5, c.user.Stop)
|
||||
|
@ -172,6 +181,10 @@ func (c *gtsCaches) Notification() *result.Cache[*gtsmodel.Notification] {
|
|||
return c.notification
|
||||
}
|
||||
|
||||
func (c *gtsCaches) Report() *result.Cache[*gtsmodel.Report] {
|
||||
return c.report
|
||||
}
|
||||
|
||||
func (c *gtsCaches) Status() *result.Cache[*gtsmodel.Status] {
|
||||
return c.status
|
||||
}
|
||||
|
@ -267,6 +280,17 @@ func (c *gtsCaches) initNotification() {
|
|||
c.notification.SetTTL(config.GetCacheGTSNotificationTTL(), true)
|
||||
}
|
||||
|
||||
func (c *gtsCaches) initReport() {
|
||||
c.report = result.New([]result.Lookup{
|
||||
{Name: "ID"},
|
||||
}, func(r1 *gtsmodel.Report) *gtsmodel.Report {
|
||||
r2 := new(gtsmodel.Report)
|
||||
*r2 = *r1
|
||||
return r2
|
||||
}, config.GetCacheGTSReportMaxSize())
|
||||
c.report.SetTTL(config.GetCacheGTSReportTTL(), true)
|
||||
}
|
||||
|
||||
func (c *gtsCaches) initStatus() {
|
||||
c.status = result.New([]result.Lookup{
|
||||
{Name: "ID"},
|
||||
|
|
|
@ -175,6 +175,10 @@ type GTSCacheConfiguration struct {
|
|||
NotificationTTL time.Duration `name:"notification-ttl"`
|
||||
NotificationSweepFreq time.Duration `name:"notification-sweep-freq"`
|
||||
|
||||
ReportMaxSize int `name:"report-max-size"`
|
||||
ReportTTL time.Duration `name:"report-ttl"`
|
||||
ReportSweepFreq time.Duration `name:"report-sweep-freq"`
|
||||
|
||||
StatusMaxSize int `name:"status-max-size"`
|
||||
StatusTTL time.Duration `name:"status-ttl"`
|
||||
StatusSweepFreq time.Duration `name:"status-sweep-freq"`
|
||||
|
|
|
@ -138,6 +138,10 @@ var Defaults = Configuration{
|
|||
NotificationTTL: time.Minute * 5,
|
||||
NotificationSweepFreq: time.Second * 10,
|
||||
|
||||
ReportMaxSize: 100,
|
||||
ReportTTL: time.Minute * 5,
|
||||
ReportSweepFreq: time.Second * 10,
|
||||
|
||||
StatusMaxSize: 500,
|
||||
StatusTTL: time.Minute * 5,
|
||||
StatusSweepFreq: time.Second * 10,
|
||||
|
|
|
@ -2378,6 +2378,81 @@ func GetCacheGTSNotificationSweepFreq() time.Duration {
|
|||
// SetCacheGTSNotificationSweepFreq safely sets the value for global configuration 'Cache.GTS.NotificationSweepFreq' field
|
||||
func SetCacheGTSNotificationSweepFreq(v time.Duration) { global.SetCacheGTSNotificationSweepFreq(v) }
|
||||
|
||||
// GetCacheGTSReportMaxSize safely fetches the Configuration value for state's 'Cache.GTS.ReportMaxSize' field
|
||||
func (st *ConfigState) GetCacheGTSReportMaxSize() (v int) {
|
||||
st.mutex.Lock()
|
||||
v = st.config.Cache.GTS.ReportMaxSize
|
||||
st.mutex.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// SetCacheGTSReportMaxSize safely sets the Configuration value for state's 'Cache.GTS.ReportMaxSize' field
|
||||
func (st *ConfigState) SetCacheGTSReportMaxSize(v int) {
|
||||
st.mutex.Lock()
|
||||
defer st.mutex.Unlock()
|
||||
st.config.Cache.GTS.ReportMaxSize = v
|
||||
st.reloadToViper()
|
||||
}
|
||||
|
||||
// CacheGTSReportMaxSizeFlag returns the flag name for the 'Cache.GTS.ReportMaxSize' field
|
||||
func CacheGTSReportMaxSizeFlag() string { return "cache-gts-report-max-size" }
|
||||
|
||||
// GetCacheGTSReportMaxSize safely fetches the value for global configuration 'Cache.GTS.ReportMaxSize' field
|
||||
func GetCacheGTSReportMaxSize() int { return global.GetCacheGTSReportMaxSize() }
|
||||
|
||||
// SetCacheGTSReportMaxSize safely sets the value for global configuration 'Cache.GTS.ReportMaxSize' field
|
||||
func SetCacheGTSReportMaxSize(v int) { global.SetCacheGTSReportMaxSize(v) }
|
||||
|
||||
// GetCacheGTSReportTTL safely fetches the Configuration value for state's 'Cache.GTS.ReportTTL' field
|
||||
func (st *ConfigState) GetCacheGTSReportTTL() (v time.Duration) {
|
||||
st.mutex.Lock()
|
||||
v = st.config.Cache.GTS.ReportTTL
|
||||
st.mutex.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// SetCacheGTSReportTTL safely sets the Configuration value for state's 'Cache.GTS.ReportTTL' field
|
||||
func (st *ConfigState) SetCacheGTSReportTTL(v time.Duration) {
|
||||
st.mutex.Lock()
|
||||
defer st.mutex.Unlock()
|
||||
st.config.Cache.GTS.ReportTTL = v
|
||||
st.reloadToViper()
|
||||
}
|
||||
|
||||
// CacheGTSReportTTLFlag returns the flag name for the 'Cache.GTS.ReportTTL' field
|
||||
func CacheGTSReportTTLFlag() string { return "cache-gts-report-ttl" }
|
||||
|
||||
// GetCacheGTSReportTTL safely fetches the value for global configuration 'Cache.GTS.ReportTTL' field
|
||||
func GetCacheGTSReportTTL() time.Duration { return global.GetCacheGTSReportTTL() }
|
||||
|
||||
// SetCacheGTSReportTTL safely sets the value for global configuration 'Cache.GTS.ReportTTL' field
|
||||
func SetCacheGTSReportTTL(v time.Duration) { global.SetCacheGTSReportTTL(v) }
|
||||
|
||||
// GetCacheGTSReportSweepFreq safely fetches the Configuration value for state's 'Cache.GTS.ReportSweepFreq' field
|
||||
func (st *ConfigState) GetCacheGTSReportSweepFreq() (v time.Duration) {
|
||||
st.mutex.Lock()
|
||||
v = st.config.Cache.GTS.ReportSweepFreq
|
||||
st.mutex.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// SetCacheGTSReportSweepFreq safely sets the Configuration value for state's 'Cache.GTS.ReportSweepFreq' field
|
||||
func (st *ConfigState) SetCacheGTSReportSweepFreq(v time.Duration) {
|
||||
st.mutex.Lock()
|
||||
defer st.mutex.Unlock()
|
||||
st.config.Cache.GTS.ReportSweepFreq = v
|
||||
st.reloadToViper()
|
||||
}
|
||||
|
||||
// CacheGTSReportSweepFreqFlag returns the flag name for the 'Cache.GTS.ReportSweepFreq' field
|
||||
func CacheGTSReportSweepFreqFlag() string { return "cache-gts-report-sweep-freq" }
|
||||
|
||||
// GetCacheGTSReportSweepFreq safely fetches the value for global configuration 'Cache.GTS.ReportSweepFreq' field
|
||||
func GetCacheGTSReportSweepFreq() time.Duration { return global.GetCacheGTSReportSweepFreq() }
|
||||
|
||||
// SetCacheGTSReportSweepFreq safely sets the value for global configuration 'Cache.GTS.ReportSweepFreq' field
|
||||
func SetCacheGTSReportSweepFreq(v time.Duration) { global.SetCacheGTSReportSweepFreq(v) }
|
||||
|
||||
// GetCacheGTSStatusMaxSize safely fetches the Configuration value for state's 'Cache.GTS.StatusMaxSize' field
|
||||
func (st *ConfigState) GetCacheGTSStatusMaxSize() (v int) {
|
||||
st.mutex.Lock()
|
||||
|
|
|
@ -83,6 +83,7 @@ type DBService struct {
|
|||
db.Mention
|
||||
db.Notification
|
||||
db.Relationship
|
||||
db.Report
|
||||
db.Session
|
||||
db.Status
|
||||
db.Timeline
|
||||
|
@ -197,6 +198,10 @@ func NewBunDBService(ctx context.Context, state *state.State) (db.DB, error) {
|
|||
conn: conn,
|
||||
state: state,
|
||||
},
|
||||
Report: &reportDB{
|
||||
conn: conn,
|
||||
state: state,
|
||||
},
|
||||
Session: &sessionDB{
|
||||
conn: conn,
|
||||
},
|
||||
|
|
|
@ -42,6 +42,7 @@ type BunDBStandardTestSuite struct {
|
|||
testMentions map[string]*gtsmodel.Mention
|
||||
testFollows map[string]*gtsmodel.Follow
|
||||
testEmojis map[string]*gtsmodel.Emoji
|
||||
testReports map[string]*gtsmodel.Report
|
||||
}
|
||||
|
||||
func (suite *BunDBStandardTestSuite) SetupSuite() {
|
||||
|
@ -56,6 +57,7 @@ func (suite *BunDBStandardTestSuite) SetupSuite() {
|
|||
suite.testMentions = testrig.NewTestMentions()
|
||||
suite.testFollows = testrig.NewTestFollows()
|
||||
suite.testEmojis = testrig.NewTestEmojis()
|
||||
suite.testReports = testrig.NewTestReports()
|
||||
}
|
||||
|
||||
func (suite *BunDBStandardTestSuite) SetupTest() {
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
GoToSocial
|
||||
Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
gtsmodel "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func init() {
|
||||
up := func(ctx context.Context, db *bun.DB) error {
|
||||
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
||||
if _, err := tx.NewCreateTable().Model(>smodel.Report{}).IfNotExists().Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := tx.
|
||||
NewCreateIndex().
|
||||
Model(>smodel.Report{}).
|
||||
Index("report_account_id_idx").
|
||||
Column("account_id").
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := tx.
|
||||
NewCreateIndex().
|
||||
Model(>smodel.Report{}).
|
||||
Index("report_target_account_id_idx").
|
||||
Column("target_account_id").
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
down := func(ctx context.Context, db *bun.DB) error {
|
||||
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
if err := Migrations.Register(up, down); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
GoToSocial
|
||||
Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bundb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/state"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
type reportDB struct {
|
||||
conn *DBConn
|
||||
state *state.State
|
||||
}
|
||||
|
||||
func (r *reportDB) newReportQ(report interface{}) *bun.SelectQuery {
|
||||
return r.conn.NewSelect().Model(report)
|
||||
}
|
||||
|
||||
func (r *reportDB) GetReportByID(ctx context.Context, id string) (*gtsmodel.Report, db.Error) {
|
||||
return r.getReport(
|
||||
ctx,
|
||||
"ID",
|
||||
func(report *gtsmodel.Report) error {
|
||||
return r.newReportQ(report).Where("? = ?", bun.Ident("report.id"), id).Scan(ctx)
|
||||
},
|
||||
id,
|
||||
)
|
||||
}
|
||||
|
||||
func (r *reportDB) getReport(ctx context.Context, lookup string, dbQuery func(*gtsmodel.Report) error, keyParts ...any) (*gtsmodel.Report, db.Error) {
|
||||
// Fetch report from database cache with loader callback
|
||||
report, err := r.state.Caches.GTS.Report().Load(lookup, func() (*gtsmodel.Report, error) {
|
||||
var report gtsmodel.Report
|
||||
|
||||
// Not cached! Perform database query
|
||||
if err := dbQuery(&report); err != nil {
|
||||
return nil, r.conn.ProcessError(err)
|
||||
}
|
||||
|
||||
return &report, nil
|
||||
}, keyParts...)
|
||||
if err != nil {
|
||||
// error already processed
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set the report author account
|
||||
report.Account, err = r.state.DB.GetAccountByID(ctx, report.AccountID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting report account: %w", err)
|
||||
}
|
||||
|
||||
// Set the report target account
|
||||
report.TargetAccount, err = r.state.DB.GetAccountByID(ctx, report.TargetAccountID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting report target account: %w", err)
|
||||
}
|
||||
|
||||
if len(report.StatusIDs) > 0 {
|
||||
// Fetch reported statuses
|
||||
report.Statuses, err = r.state.DB.GetStatuses(ctx, report.StatusIDs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting status mentions: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if report.ActionTakenByAccountID != "" {
|
||||
// Set the report action taken by account
|
||||
report.ActionTakenByAccount, err = r.state.DB.GetAccountByID(ctx, report.ActionTakenByAccountID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting report action taken by account: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return report, nil
|
||||
}
|
||||
|
||||
func (r *reportDB) PutReport(ctx context.Context, report *gtsmodel.Report) db.Error {
|
||||
return r.state.Caches.GTS.Report().Store(report, func() error {
|
||||
_, err := r.conn.NewInsert().Model(report).Exec(ctx)
|
||||
return r.conn.ProcessError(err)
|
||||
})
|
||||
}
|
||||
|
||||
func (r *reportDB) UpdateReport(ctx context.Context, report *gtsmodel.Report, columns ...string) (*gtsmodel.Report, db.Error) {
|
||||
// Update the report's last-updated
|
||||
report.UpdatedAt = time.Now()
|
||||
if len(columns) != 0 {
|
||||
columns = append(columns, "updated_at")
|
||||
}
|
||||
|
||||
if _, err := r.conn.
|
||||
NewUpdate().
|
||||
Model(report).
|
||||
Where("? = ?", bun.Ident("report.id"), report.ID).
|
||||
Column(columns...).
|
||||
Exec(ctx); err != nil {
|
||||
return nil, r.conn.ProcessError(err)
|
||||
}
|
||||
|
||||
r.state.Caches.GTS.Report().Invalidate("ID", report.ID)
|
||||
return report, nil
|
||||
}
|
||||
|
||||
func (r *reportDB) DeleteReportByID(ctx context.Context, id string) db.Error {
|
||||
if _, err := r.conn.
|
||||
NewDelete().
|
||||
TableExpr("? AS ?", bun.Ident("reports"), bun.Ident("report")).
|
||||
Where("? = ?", bun.Ident("report.id"), id).
|
||||
Exec(ctx); err != nil {
|
||||
return r.conn.ProcessError(err)
|
||||
}
|
||||
|
||||
r.state.Caches.GTS.Report().Invalidate("ID", id)
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
GoToSocial
|
||||
Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bundb_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
type ReportTestSuite struct {
|
||||
BunDBStandardTestSuite
|
||||
}
|
||||
|
||||
func (suite *ReportTestSuite) TestGetReportByID() {
|
||||
report, err := suite.db.GetReportByID(context.Background(), suite.testReports["local_account_2_report_remote_account_1"].ID)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
suite.NotNil(report)
|
||||
suite.NotNil(report.Account)
|
||||
suite.NotNil(report.TargetAccount)
|
||||
suite.Zero(report.ActionTakenAt)
|
||||
suite.Nil(report.ActionTakenByAccount)
|
||||
suite.Empty(report.ActionTakenByAccountID)
|
||||
suite.NotEmpty(report.URI)
|
||||
}
|
||||
|
||||
func (suite *ReportTestSuite) TestGetReportByURI() {
|
||||
report, err := suite.db.GetReportByID(context.Background(), suite.testReports["remote_account_1_report_local_account_2"].ID)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
suite.NotNil(report)
|
||||
suite.NotNil(report.Account)
|
||||
suite.NotNil(report.TargetAccount)
|
||||
suite.NotZero(report.ActionTakenAt)
|
||||
suite.NotNil(report.ActionTakenByAccount)
|
||||
suite.NotEmpty(report.ActionTakenByAccountID)
|
||||
suite.NotEmpty(report.URI)
|
||||
}
|
||||
|
||||
func (suite *ReportTestSuite) TestPutReport() {
|
||||
ctx := context.Background()
|
||||
|
||||
reportID := "01GP3ECY8QJD8DBJSS8B1CR0AX"
|
||||
report := >smodel.Report{
|
||||
ID: reportID,
|
||||
CreatedAt: testrig.TimeMustParse("2022-05-14T12:20:03+02:00"),
|
||||
UpdatedAt: testrig.TimeMustParse("2022-05-14T12:20:03+02:00"),
|
||||
URI: "http://localhost:8080/01GP3ECY8QJD8DBJSS8B1CR0AX",
|
||||
AccountID: "01F8MH5NBDF2MV7CTC4Q5128HF",
|
||||
TargetAccountID: "01F8MH5ZK5VRH73AKHQM6Y9VNX",
|
||||
Comment: "another report",
|
||||
StatusIDs: []string{"01FVW7JHQFSFK166WWKR8CBA6M"},
|
||||
Forwarded: testrig.TrueBool(),
|
||||
}
|
||||
|
||||
err := suite.db.PutReport(ctx, report)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *ReportTestSuite) TestUpdateReport() {
|
||||
ctx := context.Background()
|
||||
|
||||
report := >smodel.Report{}
|
||||
*report = *suite.testReports["local_account_2_report_remote_account_1"]
|
||||
report.ActionTaken = "nothing"
|
||||
report.ActionTakenByAccountID = suite.testAccounts["admin_account"].ID
|
||||
report.ActionTakenAt = testrig.TimeMustParse("2022-05-14T12:20:03+02:00")
|
||||
|
||||
if _, err := suite.db.UpdateReport(ctx, report, "action_taken", "action_taken_by_account_id", "action_taken_at"); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
dbReport, err := suite.db.GetReportByID(ctx, report.ID)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
suite.NotNil(dbReport)
|
||||
suite.NotNil(dbReport.Account)
|
||||
suite.NotNil(dbReport.TargetAccount)
|
||||
suite.NotZero(dbReport.ActionTakenAt)
|
||||
suite.NotNil(dbReport.ActionTakenByAccount)
|
||||
suite.NotEmpty(dbReport.ActionTakenByAccountID)
|
||||
suite.NotEmpty(dbReport.URI)
|
||||
}
|
||||
|
||||
func (suite *ReportTestSuite) TestUpdateReportAllColumns() {
|
||||
ctx := context.Background()
|
||||
|
||||
report := >smodel.Report{}
|
||||
*report = *suite.testReports["local_account_2_report_remote_account_1"]
|
||||
report.ActionTaken = "nothing"
|
||||
report.ActionTakenByAccountID = suite.testAccounts["admin_account"].ID
|
||||
report.ActionTakenAt = testrig.TimeMustParse("2022-05-14T12:20:03+02:00")
|
||||
|
||||
if _, err := suite.db.UpdateReport(ctx, report); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
dbReport, err := suite.db.GetReportByID(ctx, report.ID)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
suite.NotNil(dbReport)
|
||||
suite.NotNil(dbReport.Account)
|
||||
suite.NotNil(dbReport.TargetAccount)
|
||||
suite.NotZero(dbReport.ActionTakenAt)
|
||||
suite.NotNil(dbReport.ActionTakenByAccount)
|
||||
suite.NotEmpty(dbReport.ActionTakenByAccountID)
|
||||
suite.NotEmpty(dbReport.URI)
|
||||
}
|
||||
|
||||
func (suite *ReportTestSuite) TestDeleteReport() {
|
||||
if err := suite.db.DeleteReportByID(context.Background(), suite.testReports["remote_account_1_report_local_account_2"].ID); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
report, err := suite.db.GetReportByID(context.Background(), suite.testReports["remote_account_1_report_local_account_2"].ID)
|
||||
suite.ErrorIs(err, db.ErrNoEntries)
|
||||
suite.Nil(report)
|
||||
}
|
||||
|
||||
func TestReportTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(ReportTestSuite))
|
||||
}
|
|
@ -67,6 +67,24 @@ func (s *statusDB) GetStatusByID(ctx context.Context, id string) (*gtsmodel.Stat
|
|||
)
|
||||
}
|
||||
|
||||
func (s *statusDB) GetStatuses(ctx context.Context, ids []string) ([]*gtsmodel.Status, db.Error) {
|
||||
statuses := make([]*gtsmodel.Status, 0, len(ids))
|
||||
|
||||
for _, id := range ids {
|
||||
// Attempt fetch from DB
|
||||
status, err := s.GetStatusByID(ctx, id)
|
||||
if err != nil {
|
||||
log.Errorf("GetStatuses: error getting status %q: %v", id, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Append status
|
||||
statuses = append(statuses, status)
|
||||
}
|
||||
|
||||
return statuses, nil
|
||||
}
|
||||
|
||||
func (s *statusDB) GetStatusByURI(ctx context.Context, uri string) (*gtsmodel.Status, db.Error) {
|
||||
return s.getStatus(
|
||||
ctx,
|
||||
|
|
|
@ -50,6 +50,48 @@ func (suite *StatusTestSuite) TestGetStatusByID() {
|
|||
suite.True(*status.Likeable)
|
||||
}
|
||||
|
||||
func (suite *StatusTestSuite) TestGetStatusesByID() {
|
||||
ids := []string{
|
||||
suite.testStatuses["local_account_1_status_1"].ID,
|
||||
suite.testStatuses["local_account_2_status_3"].ID,
|
||||
}
|
||||
|
||||
statuses, err := suite.db.GetStatuses(context.Background(), ids)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
if len(statuses) != 2 {
|
||||
suite.FailNow("expected 2 statuses in slice")
|
||||
}
|
||||
|
||||
status1 := statuses[0]
|
||||
suite.NotNil(status1)
|
||||
suite.NotNil(status1.Account)
|
||||
suite.NotNil(status1.CreatedWithApplication)
|
||||
suite.Nil(status1.BoostOf)
|
||||
suite.Nil(status1.BoostOfAccount)
|
||||
suite.Nil(status1.InReplyTo)
|
||||
suite.Nil(status1.InReplyToAccount)
|
||||
suite.True(*status1.Federated)
|
||||
suite.True(*status1.Boostable)
|
||||
suite.True(*status1.Replyable)
|
||||
suite.True(*status1.Likeable)
|
||||
|
||||
status2 := statuses[1]
|
||||
suite.NotNil(status2)
|
||||
suite.NotNil(status2.Account)
|
||||
suite.NotNil(status2.CreatedWithApplication)
|
||||
suite.Nil(status2.BoostOf)
|
||||
suite.Nil(status2.BoostOfAccount)
|
||||
suite.Nil(status2.InReplyTo)
|
||||
suite.Nil(status2.InReplyToAccount)
|
||||
suite.True(*status2.Federated)
|
||||
suite.True(*status2.Boostable)
|
||||
suite.False(*status2.Replyable)
|
||||
suite.False(*status2.Likeable)
|
||||
}
|
||||
|
||||
func (suite *StatusTestSuite) TestGetStatusByURI() {
|
||||
status, err := suite.db.GetStatusByURI(context.Background(), suite.testStatuses["local_account_2_status_3"].URI)
|
||||
if err != nil {
|
||||
|
|
|
@ -41,6 +41,7 @@ type DB interface {
|
|||
Mention
|
||||
Notification
|
||||
Relationship
|
||||
Report
|
||||
Session
|
||||
Status
|
||||
Timeline
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
GoToSocial
|
||||
Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
)
|
||||
|
||||
// Report handles getting/creation/deletion/updating of user reports/flags.
|
||||
type Report interface {
|
||||
// GetReportByID gets one report by its db id
|
||||
GetReportByID(ctx context.Context, id string) (*gtsmodel.Report, Error)
|
||||
// PutReport puts the given report in the database.
|
||||
PutReport(ctx context.Context, report *gtsmodel.Report) Error
|
||||
// UpdateReport updates one report by its db id.
|
||||
// The given columns will be updated; if no columns are
|
||||
// provided, then all columns will be updated.
|
||||
// updated_at will also be updated, no need to pass this
|
||||
// as a specific column.
|
||||
UpdateReport(ctx context.Context, report *gtsmodel.Report, columns ...string) (*gtsmodel.Report, Error)
|
||||
// DeleteReportByID deletes report with the given id.
|
||||
DeleteReportByID(ctx context.Context, id string) Error
|
||||
}
|
|
@ -29,6 +29,9 @@ type Status interface {
|
|||
// GetStatusByID returns one status from the database, with no rel fields populated, only their linking ID / URIs
|
||||
GetStatusByID(ctx context.Context, id string) (*gtsmodel.Status, Error)
|
||||
|
||||
// GetStatuses gets a slice of statuses corresponding to the given status IDs.
|
||||
GetStatuses(ctx context.Context, ids []string) ([]*gtsmodel.Status, Error)
|
||||
|
||||
// GetStatusByURI returns one status from the database, with no rel fields populated, only their linking ID / URIs
|
||||
GetStatusByURI(ctx context.Context, uri string) (*gtsmodel.Status, Error)
|
||||
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
GoToSocial
|
||||
Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package gtsmodel
|
||||
|
||||
import "time"
|
||||
|
||||
// Report models a user-created reported about an account, which should be reviewed
|
||||
// and acted upon by instance admins.
|
||||
//
|
||||
// This can be either a report created locally (on this instance) about a user on this
|
||||
// or another instance, OR a report that was created remotely (on another instance)
|
||||
// about a user on this instance, and received via the federated (s2s) API.
|
||||
type Report struct {
|
||||
ID string `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
URI string `validate:"required,url" bun:",unique,nullzero,notnull"` // activitypub URI of this report
|
||||
AccountID string `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"` // which account created this report
|
||||
Account *Account `validate:"-" bun:"-"` // account corresponding to AccountID
|
||||
TargetAccountID string `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"` // which account is targeted by this report
|
||||
TargetAccount *Account `validate:"-" bun:"-"` // account corresponding to TargetAccountID
|
||||
Comment string `validate:"-" bun:",nullzero"` // comment / explanation for this report, by the reporter
|
||||
StatusIDs []string `validate:"dive,ulid" bun:"statuses,array"` // database IDs of any statuses referenced by this report
|
||||
Statuses []*Status `validate:"-" bun:"-"` // statuses corresponding to StatusIDs
|
||||
Forwarded *bool `validate:"-" bun:",nullzero,notnull,default:false"` // flag to indicate report should be forwarded to remote instance
|
||||
ActionTaken string `validate:"-" bun:",nullzero"` // string description of what action was taken in response to this report
|
||||
ActionTakenAt time.Time `validate:"-" bun:"type:timestamptz,nullzero"` // time at which action was taken, if any
|
||||
ActionTakenByAccountID string `validate:",omitempty,ulid" bun:"type:CHAR(26),nullzero"` // database ID of account which took action, if any
|
||||
ActionTakenByAccount *Account `validate:"-" bun:"-"` // account corresponding to ActionTakenByID, if any
|
||||
}
|
|
@ -36,12 +36,10 @@ const (
|
|||
followers = "followers"
|
||||
following = "following"
|
||||
liked = "liked"
|
||||
// collections = "collections"
|
||||
// featured = "featured"
|
||||
publicKey = "main-key"
|
||||
follow = "follow"
|
||||
// update = "updates"
|
||||
blocks = "blocks"
|
||||
blocks = "blocks"
|
||||
reports = "reports"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -141,6 +139,11 @@ var (
|
|||
// from eg /users/example_username/blocks/01F7XT5JZW1WMVSW1KADS8PVDH
|
||||
BlockPath = regexp.MustCompile(blockPath)
|
||||
|
||||
reportPath = fmt.Sprintf(`^/?%s/(%s)$`, reports, ulid)
|
||||
// ReportPath parses a path that validates and captures the ulid part
|
||||
// from eg /reports/01GP3AWY4CRDVRNZKW0TEAMB5R
|
||||
ReportPath = regexp.MustCompile(reportPath)
|
||||
|
||||
filePath = fmt.Sprintf(`^(%s)/([a-z]+)/([a-z]+)/(%s)\.([a-z]+)$`, ulid, ulid)
|
||||
// FilePath parses a file storage path of the form [ACCOUNT_ID]/[MEDIA_TYPE]/[MEDIA_SIZE]/[FILE_NAME]
|
||||
// eg 01F8MH1H7YV1Z7D2C8K2730QBF/attachment/small/01F8MH8RMYQ6MSNY3JM2XT1CQ5.jpeg
|
||||
|
|
|
@ -28,7 +28,6 @@ import (
|
|||
|
||||
const (
|
||||
UsersPath = "users" // UsersPath is for serving users info
|
||||
ActorsPath = "actors" // ActorsPath is for serving actors info
|
||||
StatusesPath = "statuses" // StatusesPath is for serving statuses
|
||||
InboxPath = "inbox" // InboxPath represents the activitypub inbox location
|
||||
OutboxPath = "outbox" // OutboxPath represents the activitypub outbox location
|
||||
|
@ -41,6 +40,7 @@ const (
|
|||
FollowPath = "follow" // FollowPath used to generate the URI for an individual follow or follow request
|
||||
UpdatePath = "updates" // UpdatePath is used to generate the URI for an account update
|
||||
BlocksPath = "blocks" // BlocksPath is used to generate the URI for a block
|
||||
ReportsPath = "reports" // ReportsPath is used to generate the URI for a report/flag
|
||||
ConfirmEmailPath = "confirm_email" // ConfirmEmailPath is used to generate the URI for an email confirmation link
|
||||
FileserverPath = "fileserver" // FileserverPath is a path component for serving attachments + media
|
||||
EmojiPath = "emoji" // EmojiPath represents the activitypub emoji location
|
||||
|
@ -107,6 +107,17 @@ func GenerateURIForBlock(username string, thisBlockID string) string {
|
|||
return fmt.Sprintf("%s://%s/%s/%s/%s/%s", protocol, host, UsersPath, username, BlocksPath, thisBlockID)
|
||||
}
|
||||
|
||||
// GenerateURIForReport returns the API URI for a new Flag activity -- something like:
|
||||
// https://example.org/reports/01GP3AWY4CRDVRNZKW0TEAMB5R
|
||||
//
|
||||
// This path specifically doesn't contain any info about the user who did the reporting,
|
||||
// to protect their privacy.
|
||||
func GenerateURIForReport(thisReportID string) string {
|
||||
protocol := config.GetProtocol()
|
||||
host := config.GetHost()
|
||||
return fmt.Sprintf("%s://%s/%s/%s", protocol, host, ReportsPath, thisReportID)
|
||||
}
|
||||
|
||||
// GenerateURIForEmailConfirm returns a link for email confirmation -- something like:
|
||||
// https://example.org/confirm_email?token=490e337c-0162-454f-ac48-4b22bb92a205
|
||||
func GenerateURIForEmailConfirm(token string) string {
|
||||
|
@ -228,6 +239,11 @@ func IsBlockPath(id *url.URL) bool {
|
|||
return regexes.BlockPath.MatchString(id.Path)
|
||||
}
|
||||
|
||||
// IsReportPath returns true if the given URL path corresponds to eg /reports/SOME_ULID_OF_A_REPORT
|
||||
func IsReportPath(id *url.URL) bool {
|
||||
return regexes.ReportPath.MatchString(id.Path)
|
||||
}
|
||||
|
||||
// ParseStatusesPath returns the username and ulid from a path such as /users/example_username/statuses/SOME_ULID_OF_A_STATUS
|
||||
func ParseStatusesPath(id *url.URL) (username string, ulid string, err error) {
|
||||
matches := regexes.StatusesPath.FindStringSubmatch(id.Path)
|
||||
|
@ -318,3 +334,14 @@ func ParseBlockPath(id *url.URL) (username string, ulid string, err error) {
|
|||
ulid = matches[2]
|
||||
return
|
||||
}
|
||||
|
||||
// ParseReportPath returns the ulid from a path such as /reports/SOME_ULID_OF_A_REPORT
|
||||
func ParseReportPath(id *url.URL) (ulid string, err error) {
|
||||
matches := regexes.ReportPath.FindStringSubmatch(id.Path)
|
||||
if len(matches) != 2 {
|
||||
err = fmt.Errorf("expected 2 matches but matches length was %d", len(matches))
|
||||
return
|
||||
}
|
||||
ulid = matches[1]
|
||||
return
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
set -eu
|
||||
|
||||
EXPECT='{"account-domain":"peepee","accounts-allow-custom-css":true,"accounts-approval-required":false,"accounts-reason-required":false,"accounts-registration-open":true,"advanced-cookies-samesite":"strict","advanced-rate-limit-requests":6969,"advanced-throttling-multiplier":-1,"application-name":"gts","bind-address":"127.0.0.1","cache":{"gts":{"account-max-size":99,"account-sweep-freq":1000000000,"account-ttl":10800000000000,"block-max-size":100,"block-sweep-freq":10000000000,"block-ttl":300000000000,"domain-block-max-size":1000,"domain-block-sweep-freq":60000000000,"domain-block-ttl":86400000000000,"emoji-category-max-size":100,"emoji-category-sweep-freq":10000000000,"emoji-category-ttl":300000000000,"emoji-max-size":500,"emoji-sweep-freq":10000000000,"emoji-ttl":300000000000,"mention-max-size":500,"mention-sweep-freq":10000000000,"mention-ttl":300000000000,"notification-max-size":500,"notification-sweep-freq":10000000000,"notification-ttl":300000000000,"status-max-size":500,"status-sweep-freq":10000000000,"status-ttl":300000000000,"tombstone-max-size":100,"tombstone-sweep-freq":10000000000,"tombstone-ttl":300000000000,"user-max-size":100,"user-sweep-freq":10000000000,"user-ttl":300000000000}},"config-path":"internal/config/testdata/test.yaml","db-address":":memory:","db-database":"gotosocial_prod","db-password":"hunter2","db-port":6969,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"sqlite","db-user":"sex-haver","dry-run":false,"email":"","host":"example.com","instance-deliver-to-shared-inboxes":false,"instance-expose-peers":true,"instance-expose-public-timeline":true,"instance-expose-suspended":true,"landing-page-user":"admin","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":true,"log-level":"info","media-description-max-chars":5000,"media-description-min-chars":69,"media-emoji-local-max-size":420,"media-emoji-remote-max-size":420,"media-image-max-size":420,"media-remote-cache-days":30,"media-video-max-size":420,"oidc-client-id":"1234","oidc-client-secret":"shhhh its a secret","oidc-enabled":true,"oidc-idp-name":"sex-haver","oidc-issuer":"whoknows","oidc-link-existing":true,"oidc-scopes":["read","write"],"oidc-skip-verification":true,"password":"","path":"","port":6969,"protocol":"http","smtp-from":"queen.rip.in.piss@terfisland.org","smtp-host":"example.com","smtp-password":"hunter2","smtp-port":4269,"smtp-username":"sex-haver","software-version":"","statuses-cw-max-chars":420,"statuses-max-chars":69,"statuses-media-max-files":1,"statuses-poll-max-options":1,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/root/store","storage-s3-access-key":"minio","storage-s3-bucket":"gts","storage-s3-endpoint":"localhost:9000","storage-s3-proxy":true,"storage-s3-secret-key":"miniostorage","storage-s3-use-ssl":false,"syslog-address":"127.0.0.1:6969","syslog-enabled":true,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","docker.host.local"],"username":"","web-asset-base-dir":"/root","web-template-base-dir":"/root"}'
|
||||
EXPECT='{"account-domain":"peepee","accounts-allow-custom-css":true,"accounts-approval-required":false,"accounts-reason-required":false,"accounts-registration-open":true,"advanced-cookies-samesite":"strict","advanced-rate-limit-requests":6969,"advanced-throttling-multiplier":-1,"application-name":"gts","bind-address":"127.0.0.1","cache":{"gts":{"account-max-size":99,"account-sweep-freq":1000000000,"account-ttl":10800000000000,"block-max-size":100,"block-sweep-freq":10000000000,"block-ttl":300000000000,"domain-block-max-size":1000,"domain-block-sweep-freq":60000000000,"domain-block-ttl":86400000000000,"emoji-category-max-size":100,"emoji-category-sweep-freq":10000000000,"emoji-category-ttl":300000000000,"emoji-max-size":500,"emoji-sweep-freq":10000000000,"emoji-ttl":300000000000,"mention-max-size":500,"mention-sweep-freq":10000000000,"mention-ttl":300000000000,"notification-max-size":500,"notification-sweep-freq":10000000000,"notification-ttl":300000000000,"report-max-size":100,"report-sweep-freq":10000000000,"report-ttl":300000000000,"status-max-size":500,"status-sweep-freq":10000000000,"status-ttl":300000000000,"tombstone-max-size":100,"tombstone-sweep-freq":10000000000,"tombstone-ttl":300000000000,"user-max-size":100,"user-sweep-freq":10000000000,"user-ttl":300000000000}},"config-path":"internal/config/testdata/test.yaml","db-address":":memory:","db-database":"gotosocial_prod","db-password":"hunter2","db-port":6969,"db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"sqlite","db-user":"sex-haver","dry-run":false,"email":"","host":"example.com","instance-deliver-to-shared-inboxes":false,"instance-expose-peers":true,"instance-expose-public-timeline":true,"instance-expose-suspended":true,"landing-page-user":"admin","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":true,"log-level":"info","media-description-max-chars":5000,"media-description-min-chars":69,"media-emoji-local-max-size":420,"media-emoji-remote-max-size":420,"media-image-max-size":420,"media-remote-cache-days":30,"media-video-max-size":420,"oidc-client-id":"1234","oidc-client-secret":"shhhh its a secret","oidc-enabled":true,"oidc-idp-name":"sex-haver","oidc-issuer":"whoknows","oidc-link-existing":true,"oidc-scopes":["read","write"],"oidc-skip-verification":true,"password":"","path":"","port":6969,"protocol":"http","smtp-from":"queen.rip.in.piss@terfisland.org","smtp-host":"example.com","smtp-password":"hunter2","smtp-port":4269,"smtp-username":"sex-haver","software-version":"","statuses-cw-max-chars":420,"statuses-max-chars":69,"statuses-media-max-files":1,"statuses-poll-max-options":1,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/root/store","storage-s3-access-key":"minio","storage-s3-bucket":"gts","storage-s3-endpoint":"localhost:9000","storage-s3-proxy":true,"storage-s3-secret-key":"miniostorage","storage-s3-use-ssl":false,"syslog-address":"127.0.0.1:6969","syslog-enabled":true,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","docker.host.local"],"username":"","web-asset-base-dir":"/root","web-template-base-dir":"/root"}'
|
||||
|
||||
# Set all the environment variables to
|
||||
# ensure that these are parsed without panic
|
||||
|
|
|
@ -58,6 +58,7 @@ var testModels = []interface{}{
|
|||
>smodel.Client{},
|
||||
>smodel.EmojiCategory{},
|
||||
>smodel.Tombstone{},
|
||||
>smodel.Report{},
|
||||
}
|
||||
|
||||
// NewTestDB returns a new initialized, empty database for testing.
|
||||
|
@ -157,6 +158,12 @@ func StandardDBSetup(db db.DB, accounts map[string]*gtsmodel.Account) {
|
|||
}
|
||||
}
|
||||
|
||||
for _, v := range NewTestReports() {
|
||||
if err := db.Put(ctx, v); err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range NewTestDomainBlocks() {
|
||||
if err := db.Put(ctx, v); err != nil {
|
||||
log.Panic(err)
|
||||
|
|
|
@ -1971,6 +1971,36 @@ func NewTestBlocks() map[string]*gtsmodel.Block {
|
|||
}
|
||||
}
|
||||
|
||||
func NewTestReports() map[string]*gtsmodel.Report {
|
||||
return map[string]*gtsmodel.Report{
|
||||
"local_account_2_report_remote_account_1": {
|
||||
ID: "01GP3AWY4CRDVRNZKW0TEAMB5R",
|
||||
CreatedAt: TimeMustParse("2022-05-14T12:20:03+02:00"),
|
||||
UpdatedAt: TimeMustParse("2022-05-14T12:20:03+02:00"),
|
||||
URI: "http://localhost:8080/01GP3AWY4CRDVRNZKW0TEAMB5R",
|
||||
AccountID: "01F8MH5NBDF2MV7CTC4Q5128HF",
|
||||
TargetAccountID: "01F8MH5ZK5VRH73AKHQM6Y9VNX",
|
||||
Comment: "dark souls sucks, please yeet this nerd",
|
||||
StatusIDs: []string{"01FVW7JHQFSFK166WWKR8CBA6M"},
|
||||
Forwarded: TrueBool(),
|
||||
},
|
||||
"remote_account_1_report_local_account_2": {
|
||||
ID: "01GP3DFY9XQ1TJMZT5BGAZPXX7",
|
||||
CreatedAt: TimeMustParse("2022-05-15T16:20:12+02:00"),
|
||||
UpdatedAt: TimeMustParse("2022-05-15T16:20:12+02:00"),
|
||||
URI: "http://fossbros-anonymous.io/87fb1478-ac46-406a-8463-96ce05645219",
|
||||
AccountID: "01F8MH5ZK5VRH73AKHQM6Y9VNX",
|
||||
TargetAccountID: "01F8MH5NBDF2MV7CTC4Q5128HF",
|
||||
Comment: "this is a turtle, not a person, therefore should not be a poster",
|
||||
StatusIDs: []string{},
|
||||
Forwarded: TrueBool(),
|
||||
ActionTaken: "user was warned not to be a turtle anymore",
|
||||
ActionTakenAt: TimeMustParse("2022-05-15T17:01:56+02:00"),
|
||||
ActionTakenByAccountID: "01AY6P665V14JJR0AFVRT7311Y",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ActivityWithSignature wraps a pub.Activity along with its signature headers, for testing.
|
||||
type ActivityWithSignature struct {
|
||||
Activity pub.Activity
|
||||
|
|
Loading…
Reference in New Issue