start reports/moderation in backend
This commit is contained in:
parent
41edaee8ea
commit
33f903b07d
|
@ -0,0 +1,47 @@
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"emperror.dev/errors"
|
||||||
|
"github.com/georgysavva/scany/pgxscan"
|
||||||
|
"github.com/rs/xid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Report struct {
|
||||||
|
ID int64
|
||||||
|
UserID xid.ID
|
||||||
|
MemberID *xid.ID
|
||||||
|
Reason string
|
||||||
|
ReporterID xid.ID
|
||||||
|
|
||||||
|
CreatedAt time.Time
|
||||||
|
ResolvedAt *time.Time
|
||||||
|
AdminID *xid.ID
|
||||||
|
AdminComment *string
|
||||||
|
}
|
||||||
|
|
||||||
|
const reportPageSize = 100
|
||||||
|
|
||||||
|
func (db *DB) Reports(ctx context.Context, closed bool, page int) (rs []Report, err error) {
|
||||||
|
builder := sq.Select("*").From("reports").Offset(uint64(reportPageSize * page)).Limit(reportPageSize).OrderBy("id ASC")
|
||||||
|
if closed {
|
||||||
|
builder = builder.Where("resolved_at IS NOT NULL")
|
||||||
|
} else {
|
||||||
|
builder = builder.Where("resolved_at IS NULL")
|
||||||
|
}
|
||||||
|
sql, args, err := builder.ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "building sql")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = pgxscan.Select(ctx, db, &rs, sql, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "executing query")
|
||||||
|
}
|
||||||
|
if len(rs) == 0 {
|
||||||
|
return []Report{}, nil
|
||||||
|
}
|
||||||
|
return rs, nil
|
||||||
|
}
|
|
@ -34,6 +34,7 @@ type User struct {
|
||||||
FediverseInstance *string
|
FediverseInstance *string
|
||||||
|
|
||||||
MaxInvites int
|
MaxInvites int
|
||||||
|
IsAdmin bool
|
||||||
|
|
||||||
DeletedAt *time.Time
|
DeletedAt *time.Time
|
||||||
SelfDelete *bool
|
SelfDelete *bool
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"codeberg.org/u1f320/pronouns.cc/backend/routes/bot"
|
"codeberg.org/u1f320/pronouns.cc/backend/routes/bot"
|
||||||
"codeberg.org/u1f320/pronouns.cc/backend/routes/member"
|
"codeberg.org/u1f320/pronouns.cc/backend/routes/member"
|
||||||
"codeberg.org/u1f320/pronouns.cc/backend/routes/meta"
|
"codeberg.org/u1f320/pronouns.cc/backend/routes/meta"
|
||||||
|
"codeberg.org/u1f320/pronouns.cc/backend/routes/mod"
|
||||||
"codeberg.org/u1f320/pronouns.cc/backend/routes/user"
|
"codeberg.org/u1f320/pronouns.cc/backend/routes/user"
|
||||||
"codeberg.org/u1f320/pronouns.cc/backend/server"
|
"codeberg.org/u1f320/pronouns.cc/backend/server"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
|
@ -20,5 +21,6 @@ func mountRoutes(s *server.Server) {
|
||||||
member.Mount(s, r)
|
member.Mount(s, r)
|
||||||
bot.Mount(s, r)
|
bot.Mount(s, r)
|
||||||
meta.Mount(s, r)
|
meta.Mount(s, r)
|
||||||
|
mod.Mount(s, r)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,7 +107,7 @@ func (s *Server) discordCallback(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
|
||||||
// TODO: implement user + token permissions
|
// TODO: implement user + token permissions
|
||||||
tokenID := xid.New()
|
tokenID := xid.New()
|
||||||
token, err := s.Auth.CreateToken(u.ID, tokenID, false, false, true)
|
token, err := s.Auth.CreateToken(u.ID, tokenID, u.IsAdmin, false, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -128,7 +128,7 @@ func (s *Server) mastodonCallback(w http.ResponseWriter, r *http.Request) error
|
||||||
|
|
||||||
// TODO: implement user + token permissions
|
// TODO: implement user + token permissions
|
||||||
tokenID := xid.New()
|
tokenID := xid.New()
|
||||||
token, err := s.Auth.CreateToken(u.ID, tokenID, false, false, true)
|
token, err := s.Auth.CreateToken(u.ID, tokenID, u.IsAdmin, false, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
package mod
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) getReports(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
showClosed := r.FormValue("closed") == "true"
|
||||||
|
|
||||||
|
fmt.Println("closed =", showClosed)
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package mod
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"codeberg.org/u1f320/pronouns.cc/backend/server"
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/go-chi/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
*server.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
func Mount(srv *server.Server, r chi.Router) {
|
||||||
|
s := &Server{Server: srv}
|
||||||
|
|
||||||
|
r.With(MustAdmin).Route("/admin", func(r chi.Router) {
|
||||||
|
r.Get("/reports", server.WrapHandler(s.getReports))
|
||||||
|
r.Get("/reports/by-user/{id}", nil)
|
||||||
|
r.Get("/reports/by-reporter/{id}", nil)
|
||||||
|
|
||||||
|
r.Patch("/reports/{id}", nil)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustAdmin(next http.Handler) http.Handler {
|
||||||
|
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
claims, ok := server.ClaimsFromContext(r.Context())
|
||||||
|
if !ok {
|
||||||
|
render.Status(r, http.StatusForbidden)
|
||||||
|
render.JSON(w, r, server.APIError{
|
||||||
|
Code: server.ErrForbidden,
|
||||||
|
Message: "Forbidden",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !claims.UserIsAdmin {
|
||||||
|
render.Status(r, http.StatusForbidden)
|
||||||
|
render.JSON(w, r, server.APIError{
|
||||||
|
Code: server.ErrForbidden,
|
||||||
|
Message: "Forbidden",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.HandlerFunc(fn)
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
-- +migrate Up
|
||||||
|
|
||||||
|
-- 2023-03-19: Add moderation-related tables
|
||||||
|
|
||||||
|
alter table users add column is_admin boolean not null default false;
|
||||||
|
|
||||||
|
create table reports (
|
||||||
|
id serial primary key,
|
||||||
|
-- we keep deleted users for 180 days after deletion, so it's fine to tie this to a user object
|
||||||
|
user_id text not null references users (id) on delete cascade,
|
||||||
|
member_id text null references members (id) on delete set null,
|
||||||
|
reason text not null,
|
||||||
|
reporter_id text not null,
|
||||||
|
|
||||||
|
created_at timestamptz not null default now(),
|
||||||
|
resolved_at timestamptz,
|
||||||
|
admin_id text null references users (id) on delete set null,
|
||||||
|
admin_comment text
|
||||||
|
);
|
Loading…
Reference in New Issue