+70
-14
labeling/admin.go
+70
-14
labeling/admin.go
···
14
14
// This is probably only a temporary method
15
15
func (s *Server) hydrateRepoView(ctx context.Context, did, indexedAt string) *comatproto.AdminDefs_RepoView {
16
16
return &comatproto.AdminDefs_RepoView{
17
-
// TODO(bnewbold): populate more, or more correctly, from some backend?
17
+
// XXX(bnewbold): populate more, or more correctly, from some backend?
18
18
Did: did,
19
19
Email: nil,
20
-
Handle: "TODO",
20
+
Handle: "XXX",
21
21
IndexedAt: indexedAt,
22
22
Moderation: nil,
23
23
RelatedRecords: nil,
···
27
27
// This is probably only a temporary method
28
28
func (s *Server) hydrateRecordView(ctx context.Context, did string, uri, cid *string, indexedAt string) *comatproto.AdminDefs_RecordView {
29
29
repoView := s.hydrateRepoView(ctx, did, indexedAt)
30
-
// TODO(bnewbold): populate more, or more correctly, from some backend?
30
+
// XXX(bnewbold): populate more, or more correctly, from some backend?
31
31
recordView := comatproto.AdminDefs_RecordView{
32
32
BlobCids: []string{},
33
33
IndexedAt: indexedAt,
···
45
45
return &recordView
46
46
}
47
47
48
-
func (s *Server) hydrateModerationActions(ctx context.Context, rows []models.ModerationAction) ([]*comatproto.AdminDefs_ActionView, error) {
48
+
func (s *Server) hydrateModerationActionViews(ctx context.Context, rows []models.ModerationAction) ([]*comatproto.AdminDefs_ActionView, error) {
49
49
50
50
var out []*comatproto.AdminDefs_ActionView
51
51
52
52
for _, row := range rows {
53
-
// TODO(bnewbold): resolve these
53
+
54
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
+
55
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
+
}
56
73
57
74
var reversal *comatproto.AdminDefs_ActionReversal
58
75
if row.ReversedAt != nil {
···
104
121
var out []*comatproto.AdminDefs_ActionViewDetail
105
122
for _, row := range rows {
106
123
107
-
// TODO(bnewbold): resolve these
108
-
resolvedReports := []*comatproto.AdminDefs_ReportView{}
109
-
subjectBlobs := []*comatproto.AdminDefs_BlobView{}
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
+
}
110
152
111
153
var reversal *comatproto.AdminDefs_ActionReversal
112
154
if row.ReversedAt != nil {
···
139
181
ResolvedReports: resolvedReports,
140
182
Reversal: reversal,
141
183
Subject: subj,
142
-
SubjectBlobs: subjectBlobs,
184
+
SubjectBlobs: subjectBlobViews,
143
185
}
144
186
out = append(out, viewDetail)
145
187
}
146
188
return out, nil
147
189
}
148
190
149
-
func (s *Server) hydrateModerationReports(ctx context.Context, rows []models.ModerationReport) ([]*comatproto.AdminDefs_ReportView, error) {
191
+
func (s *Server) hydrateModerationReportViews(ctx context.Context, rows []models.ModerationReport) ([]*comatproto.AdminDefs_ReportView, error) {
150
192
151
193
var out []*comatproto.AdminDefs_ReportView
152
194
for _, row := range rows {
153
-
// TODO(bnewbold): fetch these IDs
154
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
+
}
155
204
156
205
var subj *comatproto.AdminDefs_ReportView_Subject
157
206
switch row.SubjectType {
···
192
241
193
242
var out []*comatproto.AdminDefs_ReportViewDetail
194
243
for _, row := range rows {
195
-
// TODO(bnewbold): fetch these objects
196
-
var resolvedByActions []*comatproto.AdminDefs_ActionView
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
+
}
197
253
198
254
var subj *comatproto.AdminDefs_ReportViewDetail_Subject
199
255
switch row.SubjectType {
···
216
272
Subject: subj,
217
273
ReportedBy: row.ReportedByDid,
218
274
CreatedAt: row.CreatedAt.Format(time.RFC3339),
219
-
ResolvedByActions: resolvedByActions,
275
+
ResolvedByActions: resolvedByActionViews,
220
276
}
221
277
out = append(out, viewDetail)
222
278
}
+126
-2
labeling/helpers_test.go
+126
-2
labeling/helpers_test.go
···
105
105
reportViewDetail := testGetReport(t, e, lm, out.Id)
106
106
assert.Equal(out.Id, reportViewDetail.Id)
107
107
assert.Equal(out.CreatedAt, reportViewDetail.CreatedAt)
108
+
assert.Equal(out.ReportedBy, reportViewDetail.ReportedBy)
108
109
assert.Equal(out.Reason, reportViewDetail.Reason)
109
110
assert.Equal(out.ReasonType, reportViewDetail.ReasonType)
110
111
assert.Equal(0, len(reportViewDetail.ResolvedByActions))
111
-
// XXX: Subject
112
-
// XXX: ReportedBy
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)
113
237
114
238
return out
115
239
}
+1
labeling/service.go
+1
labeling/service.go
+30
-15
labeling/xrpc_handlers.go
+30
-15
labeling/xrpc_handlers.go
···
2
2
3
3
import (
4
4
"context"
5
+
"errors"
5
6
"strconv"
6
7
"strings"
7
8
"time"
8
-
"errors"
9
9
10
10
atproto "github.com/bluesky-social/indigo/api/atproto"
11
11
label "github.com/bluesky-social/indigo/api/label"
···
158
158
nextCursor = strconv.FormatUint(actionRows[len(actionRows)-1].ID, 10)
159
159
}
160
160
161
-
actionObjs, err := s.hydrateModerationActions(ctx, actionRows)
161
+
actionObjs, err := s.hydrateModerationActionViews(ctx, actionRows)
162
162
if err != nil {
163
163
return nil, err
164
164
}
···
220
220
nextCursor = strconv.FormatUint(reportRows[len(reportRows)-1].ID, 10)
221
221
}
222
222
223
-
reportObjs, err := s.hydrateModerationReports(ctx, reportRows)
223
+
reportObjs, err := s.hydrateModerationReportViews(ctx, reportRows)
224
224
if err != nil {
225
225
return nil, err
226
226
}
···
282
282
return nil, result.Error
283
283
}
284
284
285
-
actionObjs, err := s.hydrateModerationActions(ctx, []models.ModerationAction{actionRow})
285
+
actionObjs, err := s.hydrateModerationActionViews(ctx, []models.ModerationAction{actionRow})
286
286
if err != nil {
287
287
return nil, err
288
288
}
···
337
337
return nil, echo.NewHTTPError(400, "reason param was provided, but empty string")
338
338
}
339
339
340
-
// XXX: SubjectBlobCids (how does atproto do it? array in postgresql?)
341
340
row := models.ModerationAction{
342
341
Action: body.Action,
343
342
Reason: body.Reason,
···
357
356
return nil, echo.NewHTTPError(400, "this implementation requires a strong record ref (aka, with CID) in reports")
358
357
}
359
358
row.SubjectType = "com.atproto.repo.recordRef"
360
-
// TODO: row.SubjectDid from URI?
359
+
// XXX: row.SubjectDid from URI?
361
360
row.SubjectUri = &body.Subject.RepoStrongRef.Uri
362
361
row.SubjectCid = &body.Subject.RepoStrongRef.Cid
363
362
outSubj.RepoStrongRef = &atproto.RepoStrongRef{
···
374
373
return nil, result.Error
375
374
}
376
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
+
377
391
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
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,
385
399
}
386
400
return &out, nil
387
401
}
···
398
412
row := models.ModerationReport{
399
413
ReasonType: *body.ReasonType,
400
414
Reason: body.Reason,
401
-
// TODO(bnewbold): from auth, via context? as a new lexicon field?
415
+
// XXX(bnewbold): from auth, via context? as a new lexicon field?
402
416
ReportedByDid: "did:plc:FAKE",
403
417
}
404
418
var outSubj atproto.ModerationCreateReport_Output_Subject
···
420
434
return nil, echo.NewHTTPError(400, "this implementation requires a strong record ref (aka, with CID) in reports")
421
435
}
422
436
row.SubjectType = "com.atproto.repo.recordRef"
423
-
// TODO: row.SubjectDid from URI?
437
+
// XXX: row.SubjectDid from URI?
424
438
row.SubjectUri = &body.Subject.RepoStrongRef.Uri
425
439
row.SubjectCid = &body.Subject.RepoStrongRef.Cid
426
440
outSubj.RepoStrongRef = &atproto.RepoStrongRef{
···
442
456
CreatedAt: row.CreatedAt.Format(time.RFC3339),
443
457
Reason: row.Reason,
444
458
ReasonType: &row.ReasonType,
459
+
ReportedBy: row.ReportedByDid,
445
460
Subject: &outSubj,
446
461
}
447
462
return &out, nil
+112
-17
labeling/xrpc_test.go
+112
-17
labeling/xrpc_test.go
···
2
2
3
3
import (
4
4
"encoding/json"
5
+
"fmt"
5
6
"net/http"
6
7
"net/http/httptest"
7
8
"strings"
···
101
102
},
102
103
}
103
104
out := testCreateReport(t, e, lm, &report)
104
-
assert.Equal(rt, *out.ReasonType)
105
-
assert.Equal(reason, *out.Reason)
106
-
// XXX: more fields
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)
107
111
}
108
112
109
113
func TestLabelMakerXRPCReportRecordBad(t *testing.T) {
···
161
165
e := echo.New()
162
166
lm := testLabelMaker(t)
163
167
164
-
// create a report
165
-
rt := "spam"
166
-
reason := "I just don't like it!"
168
+
// create report
169
+
reasonType := "spam"
170
+
reportReason := "I just don't like it!"
167
171
uri := "at://did:plc:123/com.example.record/bcd234"
168
172
cid := "bafyreie5cvv4h45feadgeuwhbcutmh6t2ceseocckahdoe6uat64zmz454"
169
173
report := comatproto.ModerationCreateReport_Input{
170
-
Reason: &reason,
171
-
ReasonType: &rt,
174
+
Reason: &reportReason,
175
+
ReasonType: &reasonType,
172
176
Subject: &comatproto.ModerationCreateReport_Input_Subject{
173
177
RepoStrongRef: &comatproto.RepoStrongRef{
174
178
//com.atproto.repo.strongRef
···
178
182
},
179
183
}
180
184
reportOut := testCreateReport(t, e, lm, &report)
185
+
reportId := reportOut.Id
181
186
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)
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])
186
236
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)
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)
192
287
}
+9
-1
models/moderation.go
+9
-1
models/moderation.go
···
19
19
ReversedReason *string
20
20
}
21
21
22
+
type ModerationActionSubjectBlobCid struct {
23
+
// TODO: foreign key
24
+
ActionId uint64 `gorm:"primaryKey"`
25
+
Cid string `gorm:"primaryKey"`
26
+
}
27
+
22
28
type ModerationReport struct {
23
29
ID uint64 `gorm:"primaryKey"`
24
30
SubjectType string `gorm:"not null"`
···
32
38
}
33
39
34
40
type ModerationReportResolution struct {
35
-
ReportId uint64 `gorm:"primaryKey"`
41
+
// TODO: foreign key
42
+
ReportId uint64 `gorm:"primaryKey"`
43
+
// TODO: foreign key
36
44
ActionId uint64 `gorm:"primaryKey;index:"`
37
45
CreatedAt time.Time `gorm:"not null"`
38
46
CreatedByDid string `gorm:"not null"`