report.go (6213B)
1 // GoToSocial 2 // Copyright (C) GoToSocial Authors admin@gotosocial.org 3 // SPDX-License-Identifier: AGPL-3.0-or-later 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package bundb 19 20 import ( 21 "context" 22 "errors" 23 "fmt" 24 "time" 25 26 "github.com/superseriousbusiness/gotosocial/internal/db" 27 "github.com/superseriousbusiness/gotosocial/internal/gtscontext" 28 "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" 29 "github.com/superseriousbusiness/gotosocial/internal/log" 30 "github.com/superseriousbusiness/gotosocial/internal/state" 31 "github.com/uptrace/bun" 32 ) 33 34 type reportDB struct { 35 conn *DBConn 36 state *state.State 37 } 38 39 func (r *reportDB) newReportQ(report interface{}) *bun.SelectQuery { 40 return r.conn.NewSelect().Model(report) 41 } 42 43 func (r *reportDB) GetReportByID(ctx context.Context, id string) (*gtsmodel.Report, db.Error) { 44 return r.getReport( 45 ctx, 46 "ID", 47 func(report *gtsmodel.Report) error { 48 return r.newReportQ(report).Where("? = ?", bun.Ident("report.id"), id).Scan(ctx) 49 }, 50 id, 51 ) 52 } 53 54 func (r *reportDB) GetReports(ctx context.Context, resolved *bool, accountID string, targetAccountID string, maxID string, sinceID string, minID string, limit int) ([]*gtsmodel.Report, db.Error) { 55 reportIDs := []string{} 56 57 q := r.conn. 58 NewSelect(). 59 TableExpr("? AS ?", bun.Ident("reports"), bun.Ident("report")). 60 Column("report.id"). 61 Order("report.id DESC") 62 63 if resolved != nil { 64 i := bun.Ident("report.action_taken_by_account_id") 65 if *resolved { 66 q = q.Where("? IS NOT NULL", i) 67 } else { 68 q = q.Where("? IS NULL", i) 69 } 70 } 71 72 if accountID != "" { 73 q = q.Where("? = ?", bun.Ident("report.account_id"), accountID) 74 } 75 76 if targetAccountID != "" { 77 q = q.Where("? = ?", bun.Ident("report.target_account_id"), targetAccountID) 78 } 79 80 if maxID != "" { 81 q = q.Where("? < ?", bun.Ident("report.id"), maxID) 82 } 83 84 if sinceID != "" { 85 q = q.Where("? > ?", bun.Ident("report.id"), minID) 86 } 87 88 if minID != "" { 89 q = q.Where("? > ?", bun.Ident("report.id"), minID) 90 } 91 92 if limit != 0 { 93 q = q.Limit(limit) 94 } 95 96 if err := q.Scan(ctx, &reportIDs); err != nil { 97 return nil, r.conn.ProcessError(err) 98 } 99 100 // Catch case of no reports early 101 if len(reportIDs) == 0 { 102 return nil, db.ErrNoEntries 103 } 104 105 // Allocate return slice (will be at most len reportIDs) 106 reports := make([]*gtsmodel.Report, 0, len(reportIDs)) 107 for _, id := range reportIDs { 108 report, err := r.GetReportByID(ctx, id) 109 if err != nil { 110 log.Errorf(ctx, "error getting report %q: %v", id, err) 111 continue 112 } 113 114 // Append to return slice 115 reports = append(reports, report) 116 } 117 118 return reports, nil 119 } 120 121 func (r *reportDB) getReport(ctx context.Context, lookup string, dbQuery func(*gtsmodel.Report) error, keyParts ...any) (*gtsmodel.Report, db.Error) { 122 // Fetch report from database cache with loader callback 123 report, err := r.state.Caches.GTS.Report().Load(lookup, func() (*gtsmodel.Report, error) { 124 var report gtsmodel.Report 125 126 // Not cached! Perform database query 127 if err := dbQuery(&report); err != nil { 128 return nil, r.conn.ProcessError(err) 129 } 130 131 return &report, nil 132 }, keyParts...) 133 if err != nil { 134 // error already processed 135 return nil, err 136 } 137 138 // Set the report author account 139 report.Account, err = r.state.DB.GetAccountByID(ctx, report.AccountID) 140 if err != nil { 141 return nil, fmt.Errorf("error getting report account: %w", err) 142 } 143 144 // Set the report target account 145 report.TargetAccount, err = r.state.DB.GetAccountByID(ctx, report.TargetAccountID) 146 if err != nil { 147 return nil, fmt.Errorf("error getting report target account: %w", err) 148 } 149 150 if len(report.StatusIDs) > 0 { 151 // Fetch reported statuses 152 report.Statuses, err = r.state.DB.GetStatuses(ctx, report.StatusIDs) 153 if err != nil { 154 return nil, fmt.Errorf("error getting status mentions: %w", err) 155 } 156 } 157 158 if report.ActionTakenByAccountID != "" { 159 // Set the report action taken by account 160 report.ActionTakenByAccount, err = r.state.DB.GetAccountByID(ctx, report.ActionTakenByAccountID) 161 if err != nil { 162 return nil, fmt.Errorf("error getting report action taken by account: %w", err) 163 } 164 } 165 166 return report, nil 167 } 168 169 func (r *reportDB) PutReport(ctx context.Context, report *gtsmodel.Report) db.Error { 170 return r.state.Caches.GTS.Report().Store(report, func() error { 171 _, err := r.conn.NewInsert().Model(report).Exec(ctx) 172 return r.conn.ProcessError(err) 173 }) 174 } 175 176 func (r *reportDB) UpdateReport(ctx context.Context, report *gtsmodel.Report, columns ...string) (*gtsmodel.Report, db.Error) { 177 // Update the report's last-updated 178 report.UpdatedAt = time.Now() 179 if len(columns) != 0 { 180 columns = append(columns, "updated_at") 181 } 182 183 if _, err := r.conn. 184 NewUpdate(). 185 Model(report). 186 Where("? = ?", bun.Ident("report.id"), report.ID). 187 Column(columns...). 188 Exec(ctx); err != nil { 189 return nil, r.conn.ProcessError(err) 190 } 191 192 r.state.Caches.GTS.Report().Invalidate("ID", report.ID) 193 return report, nil 194 } 195 196 func (r *reportDB) DeleteReportByID(ctx context.Context, id string) db.Error { 197 defer r.state.Caches.GTS.Report().Invalidate("ID", id) 198 199 // Load status into cache before attempting a delete, 200 // as we need it cached in order to trigger the invalidate 201 // callback. This in turn invalidates others. 202 _, err := r.GetReportByID(gtscontext.SetBarebones(ctx), id) 203 if err != nil { 204 if errors.Is(err, db.ErrNoEntries) { 205 // not an issue. 206 err = nil 207 } 208 return err 209 } 210 211 // Finally delete report from DB. 212 _, err = r.conn.NewDelete(). 213 TableExpr("? AS ?", bun.Ident("reports"), bun.Ident("report")). 214 Where("? = ?", bun.Ident("report.id"), id). 215 Exec(ctx) 216 return r.conn.ProcessError(err) 217 }