this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

labelmaker: initial progress on moderation reports and actions

+447 -4
+116
labeling/admin.go
··· 1 + package labeling 2 + 3 + import ( 4 + "context" 5 + "fmt" 6 + "time" 7 + 8 + comatproto "github.com/bluesky-social/indigo/api/atproto" 9 + "github.com/bluesky-social/indigo/models" 10 + ) 11 + 12 + // This is probably only a temporary method 13 + func (s *Server) hydrateRepoView(ctx context.Context, did, indexedAt string) *comatproto.AdminRepo_View { 14 + return &comatproto.AdminRepo_View{ 15 + // TODO(bnewbold): populate more, or more correctly, from some backend? 16 + Account: nil, 17 + Did: did, 18 + Handle: "TODO", 19 + IndexedAt: indexedAt, 20 + Moderation: nil, 21 + RelatedRecords: nil, 22 + } 23 + } 24 + 25 + // This is probably only a temporary method 26 + func (s *Server) hydrateRecordView(ctx context.Context, did string, uri, cid *string, indexedAt string) *comatproto.AdminRecord_View { 27 + repoView := s.hydrateRepoView(ctx, did, indexedAt) 28 + // TODO(bnewbold): populate more, or more correctly, from some backend? 29 + recordView := comatproto.AdminRecord_View{ 30 + BlobCids: []string{}, 31 + IndexedAt: indexedAt, 32 + Moderation: nil, 33 + Repo: repoView, 34 + // TODO: Value 35 + } 36 + if uri != nil { 37 + recordView.Uri = *uri 38 + } 39 + if cid != nil { 40 + recordView.Cid = *cid 41 + } 42 + return &recordView 43 + } 44 + 45 + func (s *Server) hydrateModerationActions(ctx context.Context, rows []models.ModerationAction) ([]*comatproto.AdminModerationAction_ViewDetail, error) { 46 + 47 + // TODO 48 + row := rows[0] 49 + 50 + // TODO 51 + resolvedReports := []*comatproto.AdminModerationReport_View{} 52 + subjectBlobs := []*comatproto.AdminBlob_View{} 53 + var reversal *comatproto.AdminModerationAction_Reversal 54 + if row.ReversedAt != nil { 55 + reversal = &comatproto.AdminModerationAction_Reversal{ 56 + CreatedAt: row.ReversedAt.Format(time.RFC3339), 57 + CreatedBy: *row.ReversedByDid, 58 + Reason: *row.ReversedReason, 59 + } 60 + } 61 + var subj *comatproto.AdminModerationAction_ViewDetail_Subject 62 + switch row.SubjectType { 63 + case "com.atproto.repo.repoRef": 64 + subj = &comatproto.AdminModerationAction_ViewDetail_Subject{ 65 + AdminRepo_View: s.hydrateRepoView(ctx, row.SubjectDid, row.CreatedAt.Format(time.RFC3339)), 66 + } 67 + case "com.atproto.repo.recordRef": 68 + subj = &comatproto.AdminModerationAction_ViewDetail_Subject{ 69 + AdminRecord_View: s.hydrateRecordView(ctx, row.SubjectDid, row.SubjectUri, row.SubjectCid, row.CreatedAt.Format(time.RFC3339)), 70 + } 71 + default: 72 + return nil, fmt.Errorf("unsupported moderation SubjectType: %v", row.SubjectType) 73 + } 74 + 75 + viewDetail := &comatproto.AdminModerationAction_ViewDetail{ 76 + Action: &row.Action, 77 + CreatedAt: row.CreatedAt.Format(time.RFC3339), 78 + CreatedBy: row.CreatedByDid, 79 + Id: int64(row.ID), 80 + Reason: row.Reason, 81 + ResolvedReports: resolvedReports, 82 + Reversal: reversal, 83 + Subject: subj, 84 + SubjectBlobs: subjectBlobs, 85 + } 86 + return []*comatproto.AdminModerationAction_ViewDetail{viewDetail}, nil 87 + } 88 + 89 + func (s *Server) hydrateModerationReports(ctx context.Context, rows []models.ModerationReport) ([]*comatproto.AdminModerationReport_ViewDetail, error) { 90 + // TODO 91 + row := rows[0] 92 + 93 + var subj *comatproto.AdminModerationReport_ViewDetail_Subject 94 + switch row.SubjectType { 95 + case "com.atproto.repo.repoRef": 96 + subj = &comatproto.AdminModerationReport_ViewDetail_Subject{ 97 + AdminRepo_View: s.hydrateRepoView(ctx, row.SubjectDid, row.CreatedAt.Format(time.RFC3339)), 98 + } 99 + case "com.atproto.repo.recordRef": 100 + subj = &comatproto.AdminModerationReport_ViewDetail_Subject{ 101 + AdminRecord_View: s.hydrateRecordView(ctx, row.SubjectDid, row.SubjectUri, row.SubjectCid, row.CreatedAt.Format(time.RFC3339)), 102 + } 103 + default: 104 + return nil, fmt.Errorf("unsupported moderation SubjectType: %v", row.SubjectType) 105 + } 106 + 107 + viewDetail := &comatproto.AdminModerationReport_ViewDetail{ 108 + Id: int64(row.ID), 109 + ReasonType: &row.ReasonType, 110 + Subject: subj, 111 + ReportedByDid: row.ReportedByDid, 112 + CreatedAt: row.CreatedAt.Format(time.RFC3339), 113 + // TODO: ResolvedByActions 114 + } 115 + return []*comatproto.AdminModerationReport_ViewDetail{viewDetail}, nil 116 + }
+3
labeling/service.go
··· 63 63 64 64 db.AutoMigrate(models.PDS{}) 65 65 db.AutoMigrate(models.Label{}) 66 + db.AutoMigrate(models.ModerationAction{}) 67 + db.AutoMigrate(models.ModerationReport{}) 68 + db.AutoMigrate(models.ModerationReportResolution{}) 66 69 67 70 didr := &api.PLCServer{Host: plcURL} 68 71 kmgr := indexer.NewKeyManager(didr, repoUser.SigningKey)
+182 -4
labeling/xrpc_endpoints.go
··· 1 1 package labeling 2 2 3 3 import ( 4 + "io" 4 5 "strconv" 5 6 6 7 atproto "github.com/bluesky-social/indigo/api/atproto" ··· 21 22 e.GET("/xrpc/com.atproto.label.query", s.HandleComAtprotoLabelQuery) 22 23 23 24 // minimal moderation reporting/actioning 24 - // TODO: com.atproto.admin.takeModerationAction,reverseModerationAction,getModerationAction(s) 25 - // TODO: com.atproto.report.create, com.atproto.admin.getModerationReport(s) 26 - // TODO: com.atproto.admin.resolveModerationReports 27 - // TODO: proxy all the rest to BGS? 25 + e.GET("/xrpc/com.atproto.admin.getModerationAction", s.HandleComAtprotoAdminGetModerationAction) 26 + e.GET("/xrpc/com.atproto.admin.getModerationActions", s.HandleComAtprotoAdminGetModerationActions) 27 + e.GET("/xrpc/com.atproto.admin.getModerationReport", s.HandleComAtprotoAdminGetModerationReport) 28 + e.GET("/xrpc/com.atproto.admin.getModerationReports", s.HandleComAtprotoAdminGetModerationReports) 29 + e.POST("/xrpc/com.atproto.admin.resolveModerationReports", s.HandleComAtprotoAdminResolveModerationReports) 30 + e.POST("/xrpc/com.atproto.admin.reverseModerationAction", s.HandleComAtprotoAdminReverseModerationAction) 31 + e.POST("/xrpc/com.atproto.admin.takeModerationAction", s.HandleComAtprotoAdminTakeModerationAction) 32 + e.POST("/xrpc/com.atproto.report.create", s.HandleComAtprotoReportCreate) 33 + // TODO: proxy the rest to BGS? 34 + //e.GET("/xrpc/com.atproto.admin.getRecord", s.HandleComAtprotoAdminGetRecord) 35 + //e.GET("/xrpc/com.atproto.admin.getRepo", s.HandleComAtprotoAdminGetRepo) 36 + //e.GET("/xrpc/com.atproto.admin.searchRepos", s.HandleComAtprotoAdminSearchRepos) 28 37 29 38 return nil 30 39 } ··· 152 161 } 153 162 return c.JSON(200, out) 154 163 } 164 + 165 + func (s *Server) HandleComAtprotoAdminGetModerationAction(c echo.Context) error { 166 + ctx, span := otel.Tracer("server").Start(c.Request().Context(), "HandleComAtprotoAdminGetModerationAction") 167 + defer span.End() 168 + 169 + id, err := strconv.Atoi(c.QueryParam("id")) 170 + if err != nil { 171 + return err 172 + } 173 + var out *atproto.AdminModerationAction_ViewDetail 174 + var handleErr error 175 + // func (s *Server) handleComAtprotoAdminGetModerationAction(ctx context.Context,id int) (*atproto.AdminModerationAction_ViewDetail, error) 176 + out, handleErr = s.handleComAtprotoAdminGetModerationAction(ctx, id) 177 + if handleErr != nil { 178 + return handleErr 179 + } 180 + return c.JSON(200, out) 181 + } 182 + 183 + func (s *Server) HandleComAtprotoAdminGetModerationActions(c echo.Context) error { 184 + ctx, span := otel.Tracer("server").Start(c.Request().Context(), "HandleComAtprotoAdminGetModerationActions") 185 + defer span.End() 186 + before := c.QueryParam("before") 187 + 188 + var limit int 189 + if p := c.QueryParam("limit"); p != "" { 190 + var err error 191 + limit, err = strconv.Atoi(p) 192 + if err != nil { 193 + return err 194 + } 195 + } else { 196 + limit = 50 197 + } 198 + subject := c.QueryParam("subject") 199 + var out *atproto.AdminGetModerationActions_Output 200 + var handleErr error 201 + // func (s *Server) handleComAtprotoAdminGetModerationActions(ctx context.Context,before string,limit int,subject string) (*atproto.AdminGetModerationActions_Output, error) 202 + out, handleErr = s.handleComAtprotoAdminGetModerationActions(ctx, before, limit, subject) 203 + if handleErr != nil { 204 + return handleErr 205 + } 206 + return c.JSON(200, out) 207 + } 208 + 209 + func (s *Server) HandleComAtprotoAdminGetModerationReport(c echo.Context) error { 210 + ctx, span := otel.Tracer("server").Start(c.Request().Context(), "HandleComAtprotoAdminGetModerationReport") 211 + defer span.End() 212 + 213 + id, err := strconv.Atoi(c.QueryParam("id")) 214 + if err != nil { 215 + return err 216 + } 217 + var out *atproto.AdminModerationReport_ViewDetail 218 + var handleErr error 219 + // func (s *Server) handleComAtprotoAdminGetModerationReport(ctx context.Context,id int) (*atproto.AdminModerationReport_ViewDetail, error) 220 + out, handleErr = s.handleComAtprotoAdminGetModerationReport(ctx, id) 221 + if handleErr != nil { 222 + return handleErr 223 + } 224 + return c.JSON(200, out) 225 + } 226 + 227 + func (s *Server) HandleComAtprotoAdminGetModerationReports(c echo.Context) error { 228 + ctx, span := otel.Tracer("server").Start(c.Request().Context(), "HandleComAtprotoAdminGetModerationReports") 229 + defer span.End() 230 + before := c.QueryParam("before") 231 + 232 + var limit int 233 + if p := c.QueryParam("limit"); p != "" { 234 + var err error 235 + limit, err = strconv.Atoi(p) 236 + if err != nil { 237 + return err 238 + } 239 + } else { 240 + limit = 50 241 + } 242 + 243 + var resolved *bool 244 + if p := c.QueryParam("resolved"); p != "" { 245 + resolved_val, err := strconv.ParseBool(p) 246 + if err != nil { 247 + return err 248 + } 249 + resolved = &resolved_val 250 + } 251 + subject := c.QueryParam("subject") 252 + var out *atproto.AdminGetModerationReports_Output 253 + var handleErr error 254 + // func (s *Server) handleComAtprotoAdminGetModerationReports(ctx context.Context,before string,limit int,resolved *bool,subject string) (*atproto.AdminGetModerationReports_Output, error) 255 + out, handleErr = s.handleComAtprotoAdminGetModerationReports(ctx, before, limit, resolved, subject) 256 + if handleErr != nil { 257 + return handleErr 258 + } 259 + return c.JSON(200, out) 260 + } 261 + 262 + func (s *Server) HandleComAtprotoAdminResolveModerationReports(c echo.Context) error { 263 + ctx, span := otel.Tracer("server").Start(c.Request().Context(), "HandleComAtprotoAdminResolveModerationReports") 264 + defer span.End() 265 + 266 + var body atproto.AdminResolveModerationReports_Input 267 + if err := c.Bind(&body); err != nil { 268 + return err 269 + } 270 + var out *atproto.AdminModerationAction_View 271 + var handleErr error 272 + // func (s *Server) handleComAtprotoAdminResolveModerationReports(ctx context.Context,body *atproto.AdminResolveModerationReports_Input) (*atproto.AdminModerationAction_View, error) 273 + out, handleErr = s.handleComAtprotoAdminResolveModerationReports(ctx, &body) 274 + if handleErr != nil { 275 + return handleErr 276 + } 277 + return c.JSON(200, out) 278 + } 279 + 280 + func (s *Server) HandleComAtprotoAdminReverseModerationAction(c echo.Context) error { 281 + ctx, span := otel.Tracer("server").Start(c.Request().Context(), "HandleComAtprotoAdminReverseModerationAction") 282 + defer span.End() 283 + 284 + var body atproto.AdminReverseModerationAction_Input 285 + if err := c.Bind(&body); err != nil { 286 + return err 287 + } 288 + var out *atproto.AdminModerationAction_View 289 + var handleErr error 290 + // func (s *Server) handleComAtprotoAdminReverseModerationAction(ctx context.Context,body *atproto.AdminReverseModerationAction_Input) (*atproto.AdminModerationAction_View, error) 291 + out, handleErr = s.handleComAtprotoAdminReverseModerationAction(ctx, &body) 292 + if handleErr != nil { 293 + return handleErr 294 + } 295 + return c.JSON(200, out) 296 + } 297 + 298 + func (s *Server) HandleComAtprotoAdminTakeModerationAction(c echo.Context) error { 299 + ctx, span := otel.Tracer("server").Start(c.Request().Context(), "HandleComAtprotoAdminTakeModerationAction") 300 + defer span.End() 301 + 302 + var body atproto.AdminTakeModerationAction_Input 303 + if err := c.Bind(&body); err != nil { 304 + return err 305 + } 306 + var out *atproto.AdminModerationAction_View 307 + var handleErr error 308 + // func (s *Server) handleComAtprotoAdminTakeModerationAction(ctx context.Context,body *atproto.AdminTakeModerationAction_Input) (*atproto.AdminModerationAction_View, error) 309 + out, handleErr = s.handleComAtprotoAdminTakeModerationAction(ctx, &body) 310 + if handleErr != nil { 311 + return handleErr 312 + } 313 + return c.JSON(200, out) 314 + } 315 + 316 + func (s *Server) HandleComAtprotoReportCreate(c echo.Context) error { 317 + ctx, span := otel.Tracer("server").Start(c.Request().Context(), "HandleComAtprotoReportCreate") 318 + defer span.End() 319 + 320 + var body atproto.ReportCreate_Input 321 + if err := c.Bind(&body); err != nil { 322 + return err 323 + } 324 + var out *atproto.ReportCreate_Output 325 + var handleErr error 326 + // func (s *Server) handleComAtprotoReportCreate(ctx context.Context,body *atproto.ReportCreate_Input) (*atproto.ReportCreate_Output, error) 327 + out, handleErr = s.handleComAtprotoReportCreate(ctx, &body) 328 + if handleErr != nil { 329 + return handleErr 330 + } 331 + return c.JSON(200, out) 332 + }
+107
labeling/xrpc_handlers.go
··· 7 7 "io" 8 8 "strconv" 9 9 "strings" 10 + "time" 10 11 11 12 atproto "github.com/bluesky-social/indigo/api/atproto" 12 13 label "github.com/bluesky-social/indigo/api/label" ··· 180 181 } 181 182 return &out, nil 182 183 } 184 + 185 + func (s *Server) handleComAtprotoAdminGetModerationAction(ctx context.Context, id int) (*atproto.AdminModerationAction_ViewDetail, error) { 186 + 187 + var row models.ModerationAction 188 + result := s.db.First(&row, id) 189 + if result.Error != nil { 190 + return nil, result.Error 191 + } 192 + 193 + full, err := s.hydrateModerationActions(ctx, []models.ModerationAction{row}) 194 + if err != nil { 195 + return nil, err 196 + } 197 + return full[0], nil 198 + } 199 + 200 + func (s *Server) handleComAtprotoAdminGetModerationActions(ctx context.Context, before string, limit int, subject string) (*atproto.AdminGetModerationActions_Output, error) { 201 + panic("nyi") 202 + } 203 + 204 + func (s *Server) handleComAtprotoAdminGetModerationReport(ctx context.Context, id int) (*atproto.AdminModerationReport_ViewDetail, error) { 205 + 206 + var row models.ModerationReport 207 + result := s.db.First(&row, id) 208 + if result.Error != nil { 209 + return nil, result.Error 210 + } 211 + 212 + full, err := s.hydrateModerationReports(ctx, []models.ModerationReport{row}) 213 + if err != nil { 214 + return nil, err 215 + } 216 + return full[0], nil 217 + } 218 + 219 + func (s *Server) handleComAtprotoAdminGetModerationReports(ctx context.Context, before string, limit int, resolved *bool, subject string) (*atproto.AdminGetModerationReports_Output, error) { 220 + panic("nyi") 221 + } 222 + 223 + func (s *Server) handleComAtprotoAdminResolveModerationReports(ctx context.Context, body *atproto.AdminResolveModerationReports_Input) (*atproto.AdminModerationAction_View, error) { 224 + panic("nyi") 225 + } 226 + 227 + func (s *Server) handleComAtprotoAdminReverseModerationAction(ctx context.Context, body *atproto.AdminReverseModerationAction_Input) (*atproto.AdminModerationAction_View, error) { 228 + panic("nyi") 229 + } 230 + 231 + func (s *Server) handleComAtprotoAdminTakeModerationAction(ctx context.Context, body *atproto.AdminTakeModerationAction_Input) (*atproto.AdminModerationAction_View, error) { 232 + panic("nyi") 233 + } 234 + 235 + func (s *Server) handleComAtprotoReportCreate(ctx context.Context, body *atproto.ReportCreate_Input) (*atproto.ReportCreate_Output, error) { 236 + 237 + // TODO: shouldn't lexgen and the endpoint handlers help with these already? both are required fields 238 + if body.ReasonType == nil { 239 + return nil, fmt.Errorf("ReasonType is required") 240 + } 241 + if body.Subject == nil { 242 + return nil, fmt.Errorf("Subject is required") 243 + } 244 + 245 + row := models.ModerationReport{ 246 + ReasonType: *body.ReasonType, 247 + Reason: body.Reason, 248 + // TODO(bnewbold): from auth, via context? as a new lexicon field? 249 + ReportedByDid: "did:plc:FAKE", 250 + } 251 + var outSubj atproto.ReportCreate_Output_Subject 252 + if body.Subject.RepoRepoRef != nil { 253 + row.SubjectType = "com.atproto.repo.repoRef" 254 + row.SubjectDid = body.Subject.RepoRepoRef.Did 255 + outSubj.RepoRepoRef = &atproto.RepoRepoRef{ 256 + LexiconTypeID: "com.atproto.repo.repoRef", 257 + Did: row.SubjectDid, 258 + } 259 + } else if body.Subject.RepoRecordRef != nil { 260 + if row.SubjectCid == nil { 261 + return nil, fmt.Errorf("this implementation requires a strong record ref (aka, with CID) in reports") 262 + } 263 + row.SubjectType = "com.atproto.repo.recordRef" 264 + // TODO: row.SubjectDid from URI? 265 + row.SubjectUri = &body.Subject.RepoRecordRef.Uri 266 + row.SubjectCid = body.Subject.RepoRecordRef.Cid 267 + outSubj.RepoStrongRef = &atproto.RepoStrongRef{ 268 + LexiconTypeID: "com.atproto.repo.strongRef", 269 + Uri: *row.SubjectUri, 270 + Cid: *row.SubjectCid, 271 + } 272 + } else { 273 + return nil, fmt.Errorf("report subject must be a repoRef or a recordRef") 274 + } 275 + 276 + result := s.db.Create(&row) 277 + if result.Error != nil { 278 + return nil, result.Error 279 + } 280 + 281 + out := atproto.ReportCreate_Output{ 282 + Id: int64(row.ID), 283 + CreatedAt: row.CreatedAt.Format(time.RFC3339), 284 + Reason: row.Reason, 285 + ReasonType: &row.ReasonType, 286 + Subject: &outSubj, 287 + } 288 + return &out, nil 289 + }
+39
models/moderation.go
··· 1 + package models 2 + 3 + import ( 4 + "time" 5 + ) 6 + 7 + type ModerationAction struct { 8 + ID uint64 `gorm:"primaryKey"` 9 + Action string `gorm:"not null"` 10 + SubjectType string `gorm:"not null"` 11 + SubjectDid string `gorm:"not null"` 12 + SubjectUri *string 13 + SubjectCid *string 14 + Reason string `gorm:"not null"` 15 + CreatedAt time.Time `gorm:"not null"` 16 + CreatedByDid string `gorm:"not null"` 17 + ReversedAt *time.Time 18 + ReversedByDid *string 19 + ReversedReason *string 20 + } 21 + 22 + type ModerationReport struct { 23 + ID uint64 `gorm:"primaryKey"` 24 + SubjectType string `gorm:"not null"` 25 + SubjectDid string `gorm:"not null"` 26 + SubjectUri *string 27 + SubjectCid *string 28 + ReasonType string `gorm:"not null"` 29 + Reason *string 30 + ReportedByDid string `gorm:"not null"` 31 + CreatedAt time.Time `gorm:"not null"` 32 + } 33 + 34 + type ModerationReportResolution struct { 35 + ReportId uint64 `gorm:"primaryKey"` 36 + ActionId uint64 `gorm:"primaryKey;index:"` 37 + CreatedAt time.Time `gorm:"not null"` 38 + CreatedByDid string `gorm:"not null"` 39 + }