porting all github actions from bluesky-social/indigo to tangled CI

labelmaker: progress on admin XRPC endpoints

+70 -14
labeling/admin.go
··· 14 // This is probably only a temporary method 15 func (s *Server) hydrateRepoView(ctx context.Context, did, indexedAt string) *comatproto.AdminDefs_RepoView { 16 return &comatproto.AdminDefs_RepoView{ 17 - // TODO(bnewbold): populate more, or more correctly, from some backend? 18 Did: did, 19 Email: nil, 20 - Handle: "TODO", 21 IndexedAt: indexedAt, 22 Moderation: nil, 23 RelatedRecords: nil, ··· 27 // This is probably only a temporary method 28 func (s *Server) hydrateRecordView(ctx context.Context, did string, uri, cid *string, indexedAt string) *comatproto.AdminDefs_RecordView { 29 repoView := s.hydrateRepoView(ctx, did, indexedAt) 30 - // TODO(bnewbold): populate more, or more correctly, from some backend? 31 recordView := comatproto.AdminDefs_RecordView{ 32 BlobCids: []string{}, 33 IndexedAt: indexedAt, ··· 45 return &recordView 46 } 47 48 - func (s *Server) hydrateModerationActions(ctx context.Context, rows []models.ModerationAction) ([]*comatproto.AdminDefs_ActionView, error) { 49 50 var out []*comatproto.AdminDefs_ActionView 51 52 for _, row := range rows { 53 - // TODO(bnewbold): resolve these 54 resolvedReportIds := []int64{} 55 subjectBlobCIDs := []string{} 56 57 var reversal *comatproto.AdminDefs_ActionReversal 58 if row.ReversedAt != nil { ··· 104 var out []*comatproto.AdminDefs_ActionViewDetail 105 for _, row := range rows { 106 107 - // TODO(bnewbold): resolve these 108 - resolvedReports := []*comatproto.AdminDefs_ReportView{} 109 - subjectBlobs := []*comatproto.AdminDefs_BlobView{} 110 111 var reversal *comatproto.AdminDefs_ActionReversal 112 if row.ReversedAt != nil { ··· 139 ResolvedReports: resolvedReports, 140 Reversal: reversal, 141 Subject: subj, 142 - SubjectBlobs: subjectBlobs, 143 } 144 out = append(out, viewDetail) 145 } 146 return out, nil 147 } 148 149 - func (s *Server) hydrateModerationReports(ctx context.Context, rows []models.ModerationReport) ([]*comatproto.AdminDefs_ReportView, error) { 150 151 var out []*comatproto.AdminDefs_ReportView 152 for _, row := range rows { 153 - // TODO(bnewbold): fetch these IDs 154 var resolvedByActionIds []int64 155 156 var subj *comatproto.AdminDefs_ReportView_Subject 157 switch row.SubjectType { ··· 192 193 var out []*comatproto.AdminDefs_ReportViewDetail 194 for _, row := range rows { 195 - // TODO(bnewbold): fetch these objects 196 - var resolvedByActions []*comatproto.AdminDefs_ActionView 197 198 var subj *comatproto.AdminDefs_ReportViewDetail_Subject 199 switch row.SubjectType { ··· 216 Subject: subj, 217 ReportedBy: row.ReportedByDid, 218 CreatedAt: row.CreatedAt.Format(time.RFC3339), 219 - ResolvedByActions: resolvedByActions, 220 } 221 out = append(out, viewDetail) 222 }
··· 14 // This is probably only a temporary method 15 func (s *Server) hydrateRepoView(ctx context.Context, did, indexedAt string) *comatproto.AdminDefs_RepoView { 16 return &comatproto.AdminDefs_RepoView{ 17 + // XXX(bnewbold): populate more, or more correctly, from some backend? 18 Did: did, 19 Email: nil, 20 + Handle: "XXX", 21 IndexedAt: indexedAt, 22 Moderation: nil, 23 RelatedRecords: nil, ··· 27 // This is probably only a temporary method 28 func (s *Server) hydrateRecordView(ctx context.Context, did string, uri, cid *string, indexedAt string) *comatproto.AdminDefs_RecordView { 29 repoView := s.hydrateRepoView(ctx, did, indexedAt) 30 + // XXX(bnewbold): populate more, or more correctly, from some backend? 31 recordView := comatproto.AdminDefs_RecordView{ 32 BlobCids: []string{}, 33 IndexedAt: indexedAt, ··· 45 return &recordView 46 } 47 48 + func (s *Server) hydrateModerationActionViews(ctx context.Context, rows []models.ModerationAction) ([]*comatproto.AdminDefs_ActionView, error) { 49 50 var out []*comatproto.AdminDefs_ActionView 51 52 for _, row := range rows { 53 + 54 resolvedReportIds := []int64{} 55 + var resolutionRows []models.ModerationReportResolution 56 + result := s.db.Where("action_id = ?", row.ID).Find(&resolutionRows) 57 + if result.Error != nil { 58 + return nil, result.Error 59 + } 60 + for _, row := range resolutionRows { 61 + resolvedReportIds = append(resolvedReportIds, int64(row.ReportId)) 62 + } 63 + 64 subjectBlobCIDs := []string{} 65 + var cidRows []models.ModerationActionSubjectBlobCid 66 + result = s.db.Where("action_id = ?", row.ID).Find(&cidRows) 67 + if result.Error != nil { 68 + return nil, result.Error 69 + } 70 + for _, row := range cidRows { 71 + subjectBlobCIDs = append(subjectBlobCIDs, row.Cid) 72 + } 73 74 var reversal *comatproto.AdminDefs_ActionReversal 75 if row.ReversedAt != nil { ··· 121 var out []*comatproto.AdminDefs_ActionViewDetail 122 for _, row := range rows { 123 124 + var reportRows []models.ModerationReport 125 + result := s.db.Joins("left join moderation_report_resolutions on moderation_report_resolutions.report_id = moderation_reports.id").Where("moderation_report_resolutions.action_id = ?", row.ID).Find(&reportRows) 126 + if result.Error != nil { 127 + return nil, result.Error 128 + } 129 + resolvedReports, err := s.hydrateModerationReportViews(ctx, reportRows) 130 + if err != nil { 131 + return nil, err 132 + } 133 + 134 + subjectBlobViews := []*comatproto.AdminDefs_BlobView{} 135 + var cidRows []models.ModerationActionSubjectBlobCid 136 + result = s.db.Where("action_id = ?", row.ID).Find(&cidRows) 137 + if result.Error != nil { 138 + return nil, result.Error 139 + } 140 + for _, row := range cidRows { 141 + subjectBlobViews = append(subjectBlobViews, &comatproto.AdminDefs_BlobView{ 142 + Cid: row.Cid, 143 + /* XXX: all these other fields 144 + CreatedAt string 145 + Details *AdminDefs_BlobView_Details 146 + MimeType string 147 + Moderation *AdminDefs_Moderation 148 + Size int64 149 + */ 150 + }) 151 + } 152 153 var reversal *comatproto.AdminDefs_ActionReversal 154 if row.ReversedAt != nil { ··· 181 ResolvedReports: resolvedReports, 182 Reversal: reversal, 183 Subject: subj, 184 + SubjectBlobs: subjectBlobViews, 185 } 186 out = append(out, viewDetail) 187 } 188 return out, nil 189 } 190 191 + func (s *Server) hydrateModerationReportViews(ctx context.Context, rows []models.ModerationReport) ([]*comatproto.AdminDefs_ReportView, error) { 192 193 var out []*comatproto.AdminDefs_ReportView 194 for _, row := range rows { 195 var resolvedByActionIds []int64 196 + var actionRows []models.ModerationAction 197 + result := s.db.Joins("left join moderation_report_resolutions on moderation_report_resolutions.action_id = moderation_actions.id").Where("moderation_report_resolutions.report_id = ?", row.ID).Where("moderation_actions.reversed_at IS NULL").Find(&actionRows) 198 + if result.Error != nil { 199 + return nil, result.Error 200 + } 201 + for _, actionRow := range actionRows { 202 + resolvedByActionIds = append(resolvedByActionIds, int64(actionRow.ID)) 203 + } 204 205 var subj *comatproto.AdminDefs_ReportView_Subject 206 switch row.SubjectType { ··· 241 242 var out []*comatproto.AdminDefs_ReportViewDetail 243 for _, row := range rows { 244 + var actionRows []models.ModerationAction 245 + result := s.db.Joins("left join moderation_report_resolutions on moderation_report_resolutions.action_id = moderation_actions.id").Where("moderation_report_resolutions.report_id = ?", row.ID).Where("moderation_actions.reversed_at IS NULL").Find(&actionRows) 246 + if result.Error != nil { 247 + return nil, result.Error 248 + } 249 + resolvedByActionViews, err := s.hydrateModerationActionViews(ctx, actionRows) 250 + if err != nil { 251 + return nil, err 252 + } 253 254 var subj *comatproto.AdminDefs_ReportViewDetail_Subject 255 switch row.SubjectType { ··· 272 Subject: subj, 273 ReportedBy: row.ReportedByDid, 274 CreatedAt: row.CreatedAt.Format(time.RFC3339), 275 + ResolvedByActions: resolvedByActionViews, 276 } 277 out = append(out, viewDetail) 278 }
+126 -2
labeling/helpers_test.go
··· 105 reportViewDetail := testGetReport(t, e, lm, out.Id) 106 assert.Equal(out.Id, reportViewDetail.Id) 107 assert.Equal(out.CreatedAt, reportViewDetail.CreatedAt) 108 assert.Equal(out.Reason, reportViewDetail.Reason) 109 assert.Equal(out.ReasonType, reportViewDetail.ReasonType) 110 assert.Equal(0, len(reportViewDetail.ResolvedByActions)) 111 - // XXX: Subject 112 - // XXX: ReportedBy 113 114 return out 115 }
··· 105 reportViewDetail := testGetReport(t, e, lm, out.Id) 106 assert.Equal(out.Id, reportViewDetail.Id) 107 assert.Equal(out.CreatedAt, reportViewDetail.CreatedAt) 108 + assert.Equal(out.ReportedBy, reportViewDetail.ReportedBy) 109 assert.Equal(out.Reason, reportViewDetail.Reason) 110 assert.Equal(out.ReasonType, reportViewDetail.ReasonType) 111 assert.Equal(0, len(reportViewDetail.ResolvedByActions)) 112 + if out.Subject.AdminDefs_RepoRef != nil { 113 + assert.Equal(out.Subject.AdminDefs_RepoRef.Did, reportViewDetail.Subject.AdminDefs_RepoView.Did) 114 + } else if out.Subject.RepoStrongRef != nil { 115 + assert.Equal(out.Subject.RepoStrongRef.Uri, reportViewDetail.Subject.AdminDefs_RecordView.Uri) 116 + assert.Equal(out.Subject.RepoStrongRef.Cid, reportViewDetail.Subject.AdminDefs_RecordView.Cid) 117 + } else { 118 + t.Fatal("expected non-empty actionviewdetail.subject enum") 119 + } 120 + 121 + return out 122 + } 123 + 124 + // fetches action, both getModerationAction and getModerationActions; verifies match 125 + func testGetAction(t *testing.T, e *echo.Echo, lm *Server, actionId int64) comatproto.AdminDefs_ActionViewDetail { 126 + assert := assert.New(t) 127 + 128 + params := make(url.Values) 129 + params.Set("id", strconv.Itoa(int(actionId))) 130 + req := httptest.NewRequest(http.MethodGet, "/xrpc/com.atproto.admin.getModerationAction?"+params.Encode(), nil) 131 + recorder := httptest.NewRecorder() 132 + c := e.NewContext(req, recorder) 133 + assert.NoError(lm.HandleComAtprotoAdminGetModerationAction(c)) 134 + assert.Equal(200, recorder.Code) 135 + var actionViewDetail comatproto.AdminDefs_ActionViewDetail 136 + if err := json.Unmarshal([]byte(recorder.Body.String()), &actionViewDetail); err != nil { 137 + t.Fatal(err) 138 + } 139 + assert.Equal(actionId, actionViewDetail.Id) 140 + 141 + // read back (getModerationActions) and verify output 142 + // TODO: include 'subject' param 143 + req = httptest.NewRequest(http.MethodGet, "/xrpc/com.atproto.admin.getModerationActions", nil) 144 + recorder = httptest.NewRecorder() 145 + c = e.NewContext(req, recorder) 146 + assert.NoError(lm.HandleComAtprotoAdminGetModerationActions(c)) 147 + assert.Equal(200, recorder.Code) 148 + var actionsOut comatproto.AdminGetModerationActions_Output 149 + if err := json.Unmarshal([]byte(recorder.Body.String()), &actionsOut); err != nil { 150 + t.Fatal(err) 151 + } 152 + var actionView *comatproto.AdminDefs_ActionView 153 + for _, rv := range actionsOut.Actions { 154 + if rv.Id == actionId { 155 + actionView = rv 156 + break 157 + } 158 + } 159 + if actionView == nil { 160 + t.Fatal("expected to find action by subject") 161 + } 162 + 163 + assert.Equal(actionViewDetail.Id, actionView.Id) 164 + assert.Equal(actionViewDetail.CreatedAt, actionView.CreatedAt) 165 + assert.Equal(actionViewDetail.Action, actionView.Action) 166 + assert.Equal(actionViewDetail.Reason, actionView.Reason) 167 + assert.Equal(actionViewDetail.CreatedBy, actionView.CreatedBy) 168 + assert.Equal(actionViewDetail.Reversal, actionView.Reversal) 169 + assert.Equal(len(actionViewDetail.ResolvedReports), len(actionView.ResolvedReportIds)) 170 + for i, reportId := range actionView.ResolvedReportIds { 171 + assert.Equal(reportId, actionViewDetail.ResolvedReports[i].Id) 172 + } 173 + for i, blobCid := range actionView.SubjectBlobCids { 174 + assert.Equal(blobCid, actionViewDetail.SubjectBlobs[i].Cid) 175 + } 176 + if actionViewDetail.Subject.AdminDefs_RepoView != nil { 177 + assert.Equal(actionViewDetail.Subject.AdminDefs_RepoView.Did, actionView.Subject.AdminDefs_RepoRef.Did) 178 + } else if actionViewDetail.Subject.AdminDefs_RecordView != nil { 179 + assert.Equal(actionViewDetail.Subject.AdminDefs_RecordView.Uri, actionView.Subject.RepoStrongRef.Uri) 180 + assert.Equal(actionViewDetail.Subject.AdminDefs_RecordView.Cid, actionView.Subject.RepoStrongRef.Cid) 181 + } else { 182 + t.Fatal("expected non-empty actionviewdetail.subject enum") 183 + } 184 + 185 + return actionViewDetail 186 + } 187 + 188 + // "happy path" test helper. creates a action, reads it back 2x ways, verifies match, then returns the original output 189 + func testCreateAction(t *testing.T, e *echo.Echo, lm *Server, input *comatproto.AdminTakeModerationAction_Input) comatproto.AdminDefs_ActionView { 190 + assert := assert.New(t) 191 + 192 + // create action and verify output 193 + actionJSON, err := json.Marshal(input) 194 + if err != nil { 195 + t.Fatal(err) 196 + } 197 + req := httptest.NewRequest(http.MethodPost, "/xrpc/com.atproto.action.create", strings.NewReader(string(actionJSON))) 198 + req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) 199 + recorder := httptest.NewRecorder() 200 + c := e.NewContext(req, recorder) 201 + 202 + assert.NoError(lm.HandleComAtprotoAdminTakeModerationAction(c)) 203 + assert.Equal(200, recorder.Code) 204 + 205 + var out comatproto.AdminDefs_ActionView 206 + if err := json.Unmarshal([]byte(recorder.Body.String()), &out); err != nil { 207 + t.Fatal(err) 208 + } 209 + assert.Equal(input.Action, *out.Action) 210 + assert.Equal(input.CreatedBy, out.CreatedBy) 211 + assert.Equal(input.Reason, out.Reason) 212 + assert.Equal(input.Subject.RepoStrongRef, out.Subject.RepoStrongRef) 213 + assert.Equal(input.Subject.AdminDefs_RepoRef, out.Subject.AdminDefs_RepoRef) 214 + assert.Equal(input.SubjectBlobCids, out.SubjectBlobCids) 215 + 216 + // read it back and verify output 217 + actionViewDetail := testGetAction(t, e, lm, out.Id) 218 + assert.Equal(out.Id, actionViewDetail.Id) 219 + assert.Equal(out.CreatedAt, actionViewDetail.CreatedAt) 220 + 221 + assert.Equal(out.Action, actionViewDetail.Action) 222 + assert.Equal(out.CreatedBy, actionViewDetail.CreatedBy) 223 + assert.Equal(out.Reason, actionViewDetail.Reason) 224 + if out.Subject.AdminDefs_RepoRef != nil { 225 + assert.Equal(out.Subject.AdminDefs_RepoRef.Did, actionViewDetail.Subject.AdminDefs_RepoView.Did) 226 + } else if out.Subject.RepoStrongRef != nil { 227 + assert.Equal(out.Subject.RepoStrongRef.Uri, actionViewDetail.Subject.AdminDefs_RecordView.Uri) 228 + assert.Equal(out.Subject.RepoStrongRef.Cid, actionViewDetail.Subject.AdminDefs_RecordView.Cid) 229 + } else { 230 + t.Fatal("expected non-empty actionviewdetail.subject enum") 231 + } 232 + for i, blobCid := range out.SubjectBlobCids { 233 + assert.Equal(blobCid, actionViewDetail.SubjectBlobs[i].Cid) 234 + } 235 + assert.Equal(0, len(actionViewDetail.ResolvedReports)) 236 + assert.Nil(actionViewDetail.Reversal) 237 238 return out 239 }
+1
labeling/service.go
··· 66 db.AutoMigrate(models.PDS{}) 67 db.AutoMigrate(models.Label{}) 68 db.AutoMigrate(models.ModerationAction{}) 69 db.AutoMigrate(models.ModerationReport{}) 70 db.AutoMigrate(models.ModerationReportResolution{}) 71
··· 66 db.AutoMigrate(models.PDS{}) 67 db.AutoMigrate(models.Label{}) 68 db.AutoMigrate(models.ModerationAction{}) 69 + db.AutoMigrate(models.ModerationActionSubjectBlobCid{}) 70 db.AutoMigrate(models.ModerationReport{}) 71 db.AutoMigrate(models.ModerationReportResolution{}) 72
+30 -15
labeling/xrpc_handlers.go
··· 2 3 import ( 4 "context" 5 "strconv" 6 "strings" 7 "time" 8 - "errors" 9 10 atproto "github.com/bluesky-social/indigo/api/atproto" 11 label "github.com/bluesky-social/indigo/api/label" ··· 158 nextCursor = strconv.FormatUint(actionRows[len(actionRows)-1].ID, 10) 159 } 160 161 - actionObjs, err := s.hydrateModerationActions(ctx, actionRows) 162 if err != nil { 163 return nil, err 164 } ··· 220 nextCursor = strconv.FormatUint(reportRows[len(reportRows)-1].ID, 10) 221 } 222 223 - reportObjs, err := s.hydrateModerationReports(ctx, reportRows) 224 if err != nil { 225 return nil, err 226 } ··· 282 return nil, result.Error 283 } 284 285 - actionObjs, err := s.hydrateModerationActions(ctx, []models.ModerationAction{actionRow}) 286 if err != nil { 287 return nil, err 288 } ··· 337 return nil, echo.NewHTTPError(400, "reason param was provided, but empty string") 338 } 339 340 - // XXX: SubjectBlobCids (how does atproto do it? array in postgresql?) 341 row := models.ModerationAction{ 342 Action: body.Action, 343 Reason: body.Reason, ··· 357 return nil, echo.NewHTTPError(400, "this implementation requires a strong record ref (aka, with CID) in reports") 358 } 359 row.SubjectType = "com.atproto.repo.recordRef" 360 - // TODO: row.SubjectDid from URI? 361 row.SubjectUri = &body.Subject.RepoStrongRef.Uri 362 row.SubjectCid = &body.Subject.RepoStrongRef.Cid 363 outSubj.RepoStrongRef = &atproto.RepoStrongRef{ ··· 374 return nil, result.Error 375 } 376 377 out := atproto.AdminDefs_ActionView{ 378 - Id: int64(row.ID), 379 - Action: &row.Action, 380 - Reason: row.Reason, 381 - CreatedBy: row.CreatedByDid, 382 - CreatedAt: row.CreatedAt.Format(time.RFC3339), 383 - Subject: &outSubj, 384 - // XXX: SubjectBlobCids 385 } 386 return &out, nil 387 } ··· 398 row := models.ModerationReport{ 399 ReasonType: *body.ReasonType, 400 Reason: body.Reason, 401 - // TODO(bnewbold): from auth, via context? as a new lexicon field? 402 ReportedByDid: "did:plc:FAKE", 403 } 404 var outSubj atproto.ModerationCreateReport_Output_Subject ··· 420 return nil, echo.NewHTTPError(400, "this implementation requires a strong record ref (aka, with CID) in reports") 421 } 422 row.SubjectType = "com.atproto.repo.recordRef" 423 - // TODO: row.SubjectDid from URI? 424 row.SubjectUri = &body.Subject.RepoStrongRef.Uri 425 row.SubjectCid = &body.Subject.RepoStrongRef.Cid 426 outSubj.RepoStrongRef = &atproto.RepoStrongRef{ ··· 442 CreatedAt: row.CreatedAt.Format(time.RFC3339), 443 Reason: row.Reason, 444 ReasonType: &row.ReasonType, 445 Subject: &outSubj, 446 } 447 return &out, nil
··· 2 3 import ( 4 "context" 5 + "errors" 6 "strconv" 7 "strings" 8 "time" 9 10 atproto "github.com/bluesky-social/indigo/api/atproto" 11 label "github.com/bluesky-social/indigo/api/label" ··· 158 nextCursor = strconv.FormatUint(actionRows[len(actionRows)-1].ID, 10) 159 } 160 161 + actionObjs, err := s.hydrateModerationActionViews(ctx, actionRows) 162 if err != nil { 163 return nil, err 164 } ··· 220 nextCursor = strconv.FormatUint(reportRows[len(reportRows)-1].ID, 10) 221 } 222 223 + reportObjs, err := s.hydrateModerationReportViews(ctx, reportRows) 224 if err != nil { 225 return nil, err 226 } ··· 282 return nil, result.Error 283 } 284 285 + actionObjs, err := s.hydrateModerationActionViews(ctx, []models.ModerationAction{actionRow}) 286 if err != nil { 287 return nil, err 288 } ··· 337 return nil, echo.NewHTTPError(400, "reason param was provided, but empty string") 338 } 339 340 row := models.ModerationAction{ 341 Action: body.Action, 342 Reason: body.Reason, ··· 356 return nil, echo.NewHTTPError(400, "this implementation requires a strong record ref (aka, with CID) in reports") 357 } 358 row.SubjectType = "com.atproto.repo.recordRef" 359 + // XXX: row.SubjectDid from URI? 360 row.SubjectUri = &body.Subject.RepoStrongRef.Uri 361 row.SubjectCid = &body.Subject.RepoStrongRef.Cid 362 outSubj.RepoStrongRef = &atproto.RepoStrongRef{ ··· 373 return nil, result.Error 374 } 375 376 + var cidRows []models.ModerationActionSubjectBlobCid 377 + for _, sbc := range body.SubjectBlobCids { 378 + cidRows = append(cidRows, models.ModerationActionSubjectBlobCid{ 379 + ActionId: row.ID, 380 + Cid: sbc, 381 + }) 382 + } 383 + 384 + if len(cidRows) > 0 { 385 + result = s.db.Create(&cidRows) 386 + if result.Error != nil { 387 + return nil, result.Error 388 + } 389 + } 390 + 391 out := atproto.AdminDefs_ActionView{ 392 + Id: int64(row.ID), 393 + Action: &row.Action, 394 + Reason: row.Reason, 395 + CreatedBy: row.CreatedByDid, 396 + CreatedAt: row.CreatedAt.Format(time.RFC3339), 397 + Subject: &outSubj, 398 + SubjectBlobCids: body.SubjectBlobCids, 399 } 400 return &out, nil 401 } ··· 412 row := models.ModerationReport{ 413 ReasonType: *body.ReasonType, 414 Reason: body.Reason, 415 + // XXX(bnewbold): from auth, via context? as a new lexicon field? 416 ReportedByDid: "did:plc:FAKE", 417 } 418 var outSubj atproto.ModerationCreateReport_Output_Subject ··· 434 return nil, echo.NewHTTPError(400, "this implementation requires a strong record ref (aka, with CID) in reports") 435 } 436 row.SubjectType = "com.atproto.repo.recordRef" 437 + // XXX: row.SubjectDid from URI? 438 row.SubjectUri = &body.Subject.RepoStrongRef.Uri 439 row.SubjectCid = &body.Subject.RepoStrongRef.Cid 440 outSubj.RepoStrongRef = &atproto.RepoStrongRef{ ··· 456 CreatedAt: row.CreatedAt.Format(time.RFC3339), 457 Reason: row.Reason, 458 ReasonType: &row.ReasonType, 459 + ReportedBy: row.ReportedByDid, 460 Subject: &outSubj, 461 } 462 return &out, nil
+112 -17
labeling/xrpc_test.go
··· 2 3 import ( 4 "encoding/json" 5 "net/http" 6 "net/http/httptest" 7 "strings" ··· 101 }, 102 } 103 out := testCreateReport(t, e, lm, &report) 104 - assert.Equal(rt, *out.ReasonType) 105 - assert.Equal(reason, *out.Reason) 106 - // XXX: more fields 107 } 108 109 func TestLabelMakerXRPCReportRecordBad(t *testing.T) { ··· 161 e := echo.New() 162 lm := testLabelMaker(t) 163 164 - // create a report 165 - rt := "spam" 166 - reason := "I just don't like it!" 167 uri := "at://did:plc:123/com.example.record/bcd234" 168 cid := "bafyreie5cvv4h45feadgeuwhbcutmh6t2ceseocckahdoe6uat64zmz454" 169 report := comatproto.ModerationCreateReport_Input{ 170 - Reason: &reason, 171 - ReasonType: &rt, 172 Subject: &comatproto.ModerationCreateReport_Input_Subject{ 173 RepoStrongRef: &comatproto.RepoStrongRef{ 174 //com.atproto.repo.strongRef ··· 178 }, 179 } 180 reportOut := testCreateReport(t, e, lm, &report) 181 182 - _ = assert 183 - _ = reportOut 184 - // TODO: getReport helper (does single and multi, verifies equal, returns single) 185 - // TODO: getAction helper (does single and multi, verifies equal, returns single) 186 187 - // XXX: create action (including get, get plural, verifications) 188 - // XXX: get report (should have action included) 189 - // XXX: reverse action 190 - // XXX: get action (single and plural) 191 - // XXX: get report (should not have action included) 192 }
··· 2 3 import ( 4 "encoding/json" 5 + "fmt" 6 "net/http" 7 "net/http/httptest" 8 "strings" ··· 102 }, 103 } 104 out := testCreateReport(t, e, lm, &report) 105 + assert.Equal(report.ReasonType, out.ReasonType) 106 + assert.Equal(report.Reason, out.Reason) 107 + assert.NotNil(out.CreatedAt) 108 + assert.NotNil(out.ReportedBy) 109 + assert.Equal(report.Subject.AdminDefs_RepoRef, out.Subject.AdminDefs_RepoRef) 110 + assert.Equal(report.Subject.RepoStrongRef, out.Subject.RepoStrongRef) 111 } 112 113 func TestLabelMakerXRPCReportRecordBad(t *testing.T) { ··· 165 e := echo.New() 166 lm := testLabelMaker(t) 167 168 + // create report 169 + reasonType := "spam" 170 + reportReason := "I just don't like it!" 171 uri := "at://did:plc:123/com.example.record/bcd234" 172 cid := "bafyreie5cvv4h45feadgeuwhbcutmh6t2ceseocckahdoe6uat64zmz454" 173 report := comatproto.ModerationCreateReport_Input{ 174 + Reason: &reportReason, 175 + ReasonType: &reasonType, 176 Subject: &comatproto.ModerationCreateReport_Input_Subject{ 177 RepoStrongRef: &comatproto.RepoStrongRef{ 178 //com.atproto.repo.strongRef ··· 182 }, 183 } 184 reportOut := testCreateReport(t, e, lm, &report) 185 + reportId := reportOut.Id 186 187 + // create action 188 + actionVerb := "acknowledge" 189 + actionDid := "did:plc:ADMIN" 190 + actionReason := "chaos reigns" 191 + action := comatproto.AdminTakeModerationAction_Input{ 192 + Action: actionVerb, 193 + CreatedBy: actionDid, 194 + Reason: actionReason, 195 + Subject: &comatproto.AdminTakeModerationAction_Input_Subject{ 196 + RepoStrongRef: &comatproto.RepoStrongRef{ 197 + //com.atproto.repo.strongRef 198 + Uri: uri, 199 + Cid: cid, 200 + }, 201 + }, 202 + // XXX: cid support 203 + /* 204 + SubjectBlobCids: []string{ 205 + "abc", 206 + "onetwothree", 207 + }, 208 + */ 209 + } 210 + actionOut := testCreateAction(t, e, lm, &action) 211 + actionId := actionOut.Id 212 + 213 + // resolve report with action 214 + resolution := comatproto.AdminResolveModerationReports_Input{ 215 + ActionId: actionId, 216 + CreatedBy: actionDid, 217 + ReportIds: []int64{reportId}, 218 + } 219 + resolutionJSON, err := json.Marshal(resolution) 220 + if err != nil { 221 + t.Fatal(err) 222 + } 223 + req := httptest.NewRequest(http.MethodPost, "/xrpc/com.atproto.report.resolveModerationReports", strings.NewReader(string(resolutionJSON))) 224 + req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) 225 + recorder := httptest.NewRecorder() 226 + c := e.NewContext(req, recorder) 227 + assert.NoError(lm.HandleComAtprotoAdminResolveModerationReports(c)) 228 + var resolutionOut comatproto.AdminDefs_ActionView 229 + if err := json.Unmarshal([]byte(recorder.Body.String()), &resolutionOut); err != nil { 230 + t.Fatal(err) 231 + } 232 + fmt.Println(recorder.Body.String()) 233 + assert.Equal(actionId, resolutionOut.Id) 234 + assert.Equal(1, len(resolutionOut.ResolvedReportIds)) 235 + assert.Equal(reportId, resolutionOut.ResolvedReportIds[0]) 236 237 + // get report (should have action included) 238 + reportOutDetail := testGetReport(t, e, lm, reportId) 239 + assert.Equal(reportId, reportOutDetail.Id) 240 + assert.Equal(1, len(reportOutDetail.ResolvedByActions)) 241 + assert.Equal(actionId, reportOutDetail.ResolvedByActions[0].Id) 242 + 243 + // get action (should have report included) 244 + actionOutDetail := testGetAction(t, e, lm, actionId) 245 + assert.Equal(actionId, actionOutDetail.Id) 246 + assert.Equal(1, len(actionOutDetail.ResolvedReports)) 247 + assert.Equal(reportId, actionOutDetail.ResolvedReports[0].Id) 248 + 249 + // reverse action 250 + reversalReason := "changed my mind" 251 + reversal := comatproto.AdminReverseModerationAction_Input{ 252 + Id: actionId, 253 + CreatedBy: actionDid, 254 + Reason: reversalReason, 255 + } 256 + reversalJSON, err := json.Marshal(reversal) 257 + if err != nil { 258 + t.Fatal(err) 259 + } 260 + req = httptest.NewRequest(http.MethodPost, "/xrpc/com.atproto.report.reverseModerationAction", strings.NewReader(string(reversalJSON))) 261 + req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) 262 + recorder = httptest.NewRecorder() 263 + c = e.NewContext(req, recorder) 264 + assert.NoError(lm.HandleComAtprotoAdminReverseModerationAction(c)) 265 + var reversalOut comatproto.AdminDefs_ActionView 266 + if err := json.Unmarshal([]byte(recorder.Body.String()), &reversalOut); err != nil { 267 + t.Fatal(err) 268 + } 269 + assert.Equal(actionId, reversalOut.Id) 270 + assert.Equal(1, len(reversalOut.ResolvedReportIds)) 271 + assert.Equal(reportId, reversalOut.ResolvedReportIds[0]) 272 + assert.Equal(reversal.Reason, reversalOut.Reversal.Reason) 273 + assert.Equal(reversal.CreatedBy, reversalOut.Reversal.CreatedBy) 274 + assert.NotNil(reversalOut.Reversal.CreatedAt) 275 + 276 + // get report (should *not* have action included) 277 + reportOutDetail = testGetReport(t, e, lm, reportId) 278 + assert.Equal(reportId, reportOutDetail.Id) 279 + assert.Equal(0, len(reportOutDetail.ResolvedByActions)) 280 + 281 + // get action (should still have report included) 282 + actionOutDetail = testGetAction(t, e, lm, actionId) 283 + assert.Equal(actionId, actionOutDetail.Id) 284 + assert.Equal(1, len(actionOutDetail.ResolvedReports)) 285 + assert.Equal(reportId, actionOutDetail.ResolvedReports[0].Id) 286 + assert.Equal(reversalOut.Reversal, actionOutDetail.Reversal) 287 }
+9 -1
models/moderation.go
··· 19 ReversedReason *string 20 } 21 22 type ModerationReport struct { 23 ID uint64 `gorm:"primaryKey"` 24 SubjectType string `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"`
··· 19 ReversedReason *string 20 } 21 22 + type ModerationActionSubjectBlobCid struct { 23 + // TODO: foreign key 24 + ActionId uint64 `gorm:"primaryKey"` 25 + Cid string `gorm:"primaryKey"` 26 + } 27 + 28 type ModerationReport struct { 29 ID uint64 `gorm:"primaryKey"` 30 SubjectType string `gorm:"not null"` ··· 38 } 39 40 type ModerationReportResolution struct { 41 + // TODO: foreign key 42 + ReportId uint64 `gorm:"primaryKey"` 43 + // TODO: foreign key 44 ActionId uint64 `gorm:"primaryKey;index:"` 45 CreatedAt time.Time `gorm:"not null"` 46 CreatedByDid string `gorm:"not null"`