diff --git a/backend/db/report.go b/backend/db/report.go index 8137c82..f16d1b1 100644 --- a/backend/db/report.go +++ b/backend/db/report.go @@ -10,16 +10,16 @@ import ( ) type Report struct { - ID int64 - UserID xid.ID - MemberID *xid.ID - Reason string - ReporterID xid.ID + ID int64 `json:"id"` + UserID xid.ID `json:"user_id"` + MemberID *xid.ID `json:"member_id"` + Reason string `json:"reason"` + ReporterID xid.ID `json:"reporter_id"` - CreatedAt time.Time - ResolvedAt *time.Time - AdminID *xid.ID - AdminComment *string + CreatedAt time.Time `json:"created_at"` + ResolvedAt *time.Time `json:"resolved_at"` + AdminID *xid.ID `json:"admin_id"` + AdminComment *string `json:"admin_comment"` } const ReportPageSize = 100 @@ -88,3 +88,21 @@ func (db *DB) ReportsByReporter(ctx context.Context, reporterID xid.ID, before i } return rs, nil } + +func (db *DB) CreateReport(ctx context.Context, reporterID, userID xid.ID, memberID *xid.ID, reason string) (r Report, err error) { + sql, args, err := sq.Insert("reports").SetMap(map[string]any{ + "user_id": userID, + "reporter_id": reporterID, + "member_id": memberID, + "reason": reason, + }).Suffix("RETURNING *").ToSql() + if err != nil { + return r, errors.Wrap(err, "building sql") + } + + err = pgxscan.Get(ctx, db, &r, sql, args...) + if err != nil { + return r, errors.Wrap(err, "executing query") + } + return r, nil +} diff --git a/backend/routes/mod/create_report.go b/backend/routes/mod/create_report.go new file mode 100644 index 0000000..8bc61ed --- /dev/null +++ b/backend/routes/mod/create_report.go @@ -0,0 +1,58 @@ +package mod + +import ( + "net/http" + + "codeberg.org/u1f320/pronouns.cc/backend/db" + "codeberg.org/u1f320/pronouns.cc/backend/log" + "codeberg.org/u1f320/pronouns.cc/backend/server" + "emperror.dev/errors" + "github.com/go-chi/chi/v5" + "github.com/go-chi/render" + "github.com/rs/xid" +) + +const MaxReasonLength = 2000 + +type CreateReportRequest struct { + Reason string `json:"reason"` +} + +func (s *Server) createUserReport(w http.ResponseWriter, r *http.Request) error { + ctx := r.Context() + claims, _ := server.ClaimsFromContext(ctx) + + userID, err := xid.FromString(chi.URLParam(r, "id")) + if err != nil { + return server.APIError{Code: server.ErrBadRequest, Details: "Invalid user ID"} + } + + u, err := s.DB.User(ctx, userID) + if err != nil { + if err == db.ErrUserNotFound { + return server.APIError{Code: server.ErrUserNotFound} + } + + log.Errorf("getting user %v: %v", userID, err) + return errors.Wrap(err, "getting user") + } + + var req CreateReportRequest + err = render.Decode(r, &req) + if err != nil { + return server.APIError{Code: server.ErrBadRequest} + } + + if len(req.Reason) > MaxReasonLength { + return server.APIError{Code: server.ErrBadRequest, Details: "Reason cannot exceed 2000 characters"} + } + + report, err := s.DB.CreateReport(ctx, claims.UserID, u.ID, nil, req.Reason) + if err != nil { + log.Errorf("creating report for %v: %v", u.ID, err) + return errors.Wrap(err, "creating report") + } + + render.JSON(w, r, map[string]any{"created": true, "created_at": report.CreatedAt}) + return nil +} diff --git a/backend/routes/mod/routes.go b/backend/routes/mod/routes.go index 88b9657..c7917f9 100644 --- a/backend/routes/mod/routes.go +++ b/backend/routes/mod/routes.go @@ -22,6 +22,9 @@ func Mount(srv *server.Server, r chi.Router) { r.Patch("/reports/{id}", nil) }) + + r.With(server.MustAuth).Post("/users/{id}/reports", server.WrapHandler(s.createUserReport)) + r.With(server.MustAuth).Post("/members/{id}/reports", nil) } func MustAdmin(next http.Handler) http.Handler {