Skip to content
This repository was archived by the owner on Dec 17, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ require (
github.com/h2non/filetype v1.1.3
github.com/json-iterator/go v1.1.12
github.com/prometheus/client_golang v1.12.2
github.com/seventv/common v0.0.0-20220723183359-3cfda790f9b8
github.com/seventv/common v0.0.0-20220725204002-4d8447b9673c
github.com/seventv/compactdisc v0.0.0-20220723184527-d3d767eadb5c
github.com/seventv/image-processor/go v0.0.0-20220717125033-bf54c62116c2
github.com/seventv/message-queue/go v0.0.0-20220623223012-800919900c0d
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -423,12 +423,12 @@ github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d h1:Q+gqLBOPkFGHyCJx
github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/seventv/common v0.0.0-20220723113936-4f27199129f8 h1:lpB0AszlSI2IOc9igG5CvQMf/akVPembBbg0gsO0nQI=
github.com/seventv/common v0.0.0-20220723113936-4f27199129f8/go.mod h1:++S6KoMQtZ4OIZmcDbN26W2/I6JrNP/T/5a+/oLSpM8=
github.com/seventv/common v0.0.0-20220723183359-3cfda790f9b8 h1:qMgsscKzrbPc7mqxMOLNmc2Ass28XqZuS6fcGZY8IrM=
github.com/seventv/common v0.0.0-20220723183359-3cfda790f9b8/go.mod h1:++S6KoMQtZ4OIZmcDbN26W2/I6JrNP/T/5a+/oLSpM8=
github.com/seventv/compactdisc v0.0.0-20220723124103-54cf35b6d79a h1:bYki5ep7+Wv6gmlhyVWvuezs9DskiN3Ivsxa2UbzPoU=
github.com/seventv/compactdisc v0.0.0-20220723124103-54cf35b6d79a/go.mod h1:9jcgyl29MWEtYkOrZLF9Q2gcfuP1eMb8bCvCQRteRkQ=
github.com/seventv/common v0.0.0-20220725144048-1b12f1900e7f h1:wxq4bHVLef253HFQqGFbzjsx+zffxFqnDzD8rRQZhqk=
github.com/seventv/common v0.0.0-20220725144048-1b12f1900e7f/go.mod h1:++S6KoMQtZ4OIZmcDbN26W2/I6JrNP/T/5a+/oLSpM8=
github.com/seventv/common v0.0.0-20220725204002-4d8447b9673c h1:RGigolwlaDaoJXoDEg3E72DX+UG0qxx04lNORbBHRWI=
github.com/seventv/common v0.0.0-20220725204002-4d8447b9673c/go.mod h1:++S6KoMQtZ4OIZmcDbN26W2/I6JrNP/T/5a+/oLSpM8=
github.com/seventv/compactdisc v0.0.0-20220723184527-d3d767eadb5c h1:8L3Dr12VNg03iIlFYsEoo4/kQJOcsEf2sjdnj8N3mus=
github.com/seventv/compactdisc v0.0.0-20220723184527-d3d767eadb5c/go.mod h1:f9JVdhYnBwWk8Rn2w0lL0rWXxZAkWuYBAdvMq1f+eno=
github.com/seventv/image-processor/go v0.0.0-20220717125033-bf54c62116c2 h1:yRuIABJ6SQCLYmRSyP20Vq/J9ak3lDxhPE6gYXBh2As=
Expand Down
2 changes: 1 addition & 1 deletion internal/gql/v2/helpers/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ func RoleStructureToModel(s structures.Role) *model.Role {
structures.RolePermissionCreateEmote: v2structures.RolePermissionEmoteCreate,
structures.RolePermissionEditEmote: v2structures.RolePermissionEmoteEditOwned,
structures.RolePermissionEditAnyEmote: v2structures.RolePermissionEmoteEditAll,
structures.RolePermissionReportCreate: v2structures.RolePermissionCreateReports,
structures.RolePermissionCreateReport: v2structures.RolePermissionCreateReports,
structures.RolePermissionManageBans: v2structures.RolePermissionBanUsers,
structures.RolePermissionManageUsers: v2structures.RolePermissionManageUsers,
structures.RolePermissionManageStack: v2structures.RolePermissionEditApplicationMeta,
Expand Down
25 changes: 25 additions & 0 deletions internal/gql/v3/helpers/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,10 @@ func EmoteStructureToModel(s structures.Emote, cdnURL string) *model.Emote {
}

files := ver.GetFiles("", true)
sort.Slice(files, func(i, j int) bool {
return files[i].Width < files[j].Width
})

vimages := make([]*model.Image, len(files))

for i, fi := range files {
Expand Down Expand Up @@ -376,6 +380,27 @@ func MessageStructureToModRequestModel(s structures.Message[structures.MessageDa
}
}

func ReportStructureToModel(s structures.Report) *model.Report {
assignees := make([]*model.User, len(s.AssigneeIDs))
for i, oid := range s.AssigneeIDs {
assignees[i] = &model.User{ID: oid}
}

return &model.Report{
ID: s.ID,
TargetKind: int(s.TargetKind),
TargetID: s.TargetID,
ActorID: s.ActorID,
Subject: s.Subject,
Body: s.Body,
Priority: int(s.Priority),
Status: model.ReportStatus(s.Status),
CreatedAt: s.CreatedAt,
Notes: []string{},
Assignees: assignees,
}
}

func BanStructureToModel(s structures.Ban) *model.Ban {
return &model.Ban{
ID: s.ID,
Expand Down
2 changes: 1 addition & 1 deletion internal/gql/v3/middleware/has-permission.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func hasPermission(gCtx global.Context) func(ctx context.Context, obj interface{
case model.PermissionManageUsers:
perms |= structures.RolePermissionManageUsers
case model.PermissionCreateReport:
perms |= structures.RolePermissionReportCreate
perms |= structures.RolePermissionCreateReport
case model.PermissionSendMessages:
perms |= structures.RolePermissionSendMessages
case model.PermissionSuperAdministrator:
Expand Down
10 changes: 0 additions & 10 deletions internal/gql/v3/resolvers/mutation/mutation.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,6 @@ func (r *Resolver) DeleteRole(ctx context.Context, roleID primitive.ObjectID) (s
return "", nil
}

func (r *Resolver) CreateReport(ctx context.Context, data model.CreateReportInput) (*model.Report, error) {
// TODO
return nil, nil
}

func (r *Resolver) EditReport(ctx context.Context, reportID primitive.ObjectID, data model.EditReportInput) (*model.Report, error) {
// primitive.ObjectID
return nil, nil
}

// Cosmetics implements generated.MutationResolver
func (*Resolver) Cosmetics(ctx context.Context, id primitive.ObjectID) (*model.CosmeticOps, error) {
return &model.CosmeticOps{
Expand Down
250 changes: 250 additions & 0 deletions internal/gql/v3/resolvers/mutation/mutation.reports.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
package mutation

import (
"context"
"fmt"
"strconv"
"time"

"github.com/99designs/gqlgen/graphql"
"github.com/seventv/api/internal/gql/v3/auth"
"github.com/seventv/api/internal/gql/v3/gen/model"
"github.com/seventv/api/internal/gql/v3/helpers"
"github.com/seventv/common/errors"
"github.com/seventv/common/mongo"
"github.com/seventv/common/structures/v3"
"github.com/seventv/common/structures/v3/mutations"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.uber.org/zap"
)

const (
REPORT_SUBJECT_MIN_LENGTH = 4
REPORT_SUBJECT_MAX_LENGTH = 72
REPORT_BODY_MIN_LENGTH = 4
REPORT_BODY_MAX_LENGTH = 2000
REPORT_ALLOWED_ACTIVE_PER_USER = 3
)

func (r *Resolver) CreateReport(ctx context.Context, data model.CreateReportInput) (*model.Report, error) {
actor := auth.For(ctx)
if actor.ID.IsZero() {
return nil, errors.ErrUnauthorized()
}

// Get and verify the target
var (
errType error
kind = structures.ObjectKind(data.TargetKind)
targetFilter bson.M
)

switch structures.ObjectKind(data.TargetKind) {
case structures.ObjectKindUser:
errType = errors.ErrUnknownUser()
targetFilter = bson.M{"_id": data.TargetID}
case structures.ObjectKindEmote:
errType = errors.ErrUnknownEmote()
targetFilter = bson.M{"versions.id": data.TargetID}
default:
return nil, errors.ErrEmoteNameInvalid().SetDetail("You cannot report type %s", kind.String())
}

if c, _ := r.Ctx.Inst().Mongo.Collection(mongo.CollectionName(kind.CollectionName())).CountDocuments(ctx, targetFilter); c == 0 {
return nil, errType
}

// Validate the input
if len(data.Subject) < REPORT_SUBJECT_MIN_LENGTH {
graphql.AddError(ctx, errors.ErrInvalidRequest().SetDetail(fmt.Sprintf("subject must be at least %d characters long", REPORT_SUBJECT_MIN_LENGTH)))
}

if len(data.Subject) > REPORT_SUBJECT_MAX_LENGTH {
graphql.AddError(ctx, errors.ErrInvalidRequest().SetDetail(fmt.Sprintf("subject must be at most %d characters long", REPORT_SUBJECT_MAX_LENGTH)))
}

if len(data.Body) < REPORT_BODY_MIN_LENGTH {
graphql.AddError(ctx, errors.ErrInvalidRequest().SetDetail(fmt.Sprintf("body must be at least %d characters long", REPORT_BODY_MIN_LENGTH)))
}

if len(data.Body) > REPORT_BODY_MAX_LENGTH {
graphql.AddError(ctx, errors.ErrInvalidRequest().SetDetail(fmt.Sprintf("body must be at most %d characters long", REPORT_BODY_MAX_LENGTH)))
}

if len(graphql.GetErrors(ctx)) > 0 {
return nil, errors.ErrValidationRejected().SetDetail("Some fields have been filled incorrectly")
}

// Create the report
t := time.Now()

l := kind.String()[:1]
yr := strconv.Itoa(t.Year())
mo := strconv.Itoa(int(t.Month()))
dy := strconv.Itoa(t.Day())
sc := int(time.Since(time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.UTC)).Seconds())

caseID := fmt.Sprintf("%s-%s%s%s%d", l, yr[len(yr)-2:], mo, dy, sc)

rb := structures.NewReportBuilder(structures.Report{
CaseID: caseID,
AssigneeIDs: []primitive.ObjectID{},
})
rb.Report.ID = primitive.NewObjectIDFromTimestamp(time.Now())
rb.SetTargetKind(kind).
SetTargetID(data.TargetID).
SetReporterID(actor.ID).
SetStatus(structures.ReportStatusOpen).
SetSubject(data.Subject).
SetBody(data.Body).
SetCreatedAt(t)

_, err := r.Ctx.Inst().Mongo.Collection(mongo.CollectionNameReports).InsertOne(ctx, rb.Report)
if err != nil {
zap.S().Errorw("mongo", "error", err)

return nil, errors.ErrInternalServerError().SetDetail("Report creation could not be completed")
}

// Create AuditLog
truncBody := data.Subject
if len(truncBody) > 128 {
truncBody = truncBody[:128] + "..."
}

alb := structures.NewAuditLogBuilder(structures.AuditLog{
Reason: data.Subject + ": " + truncBody,
}).
SetActor(actor.ID).
SetKind(structures.AuditLogKindCreateReport).
SetTargetKind(structures.ObjectKindReport).
SetTargetID(rb.Report.ID)

if _, err := r.Ctx.Inst().Mongo.Collection(mongo.CollectionNameAuditLogs).InsertOne(ctx, alb.AuditLog); err != nil {
zap.S().Errorw("mongo, failed to write audit log", "error", err)
}

return &model.Report{}, nil
}

func (r *Resolver) EditReport(ctx context.Context, reportID primitive.ObjectID, data model.EditReportInput) (*model.Report, error) {
actor := auth.For(ctx)
if actor.ID.IsZero() {
return nil, errors.ErrUnauthorized()
}

// Get the report
report := structures.Report{}
if err := r.Ctx.Inst().Mongo.Collection(mongo.CollectionNameReports).FindOne(ctx, bson.M{"_id": reportID}).Decode(&report); err != nil {
if err == mongo.ErrNoDocuments {
return nil, errors.ErrUnknownReport()
}

return nil, errors.ErrInternalServerError()
}

// Apply mutations
rb := structures.NewReportBuilder(report)

alb := structures.NewAuditLogBuilder(structures.AuditLog{}).
SetKind(structures.AuditLogKindCreateReport).
SetActor(actor.ID).
SetTargetKind(structures.ObjectKindReport).
SetTargetID(report.ID)

if data.Priority != nil {
p := *data.Priority

alb = alb.AddChanges((&structures.AuditLogChange{
Format: structures.AuditLogChangeFormatSingleValue,
Key: "priority",
}).WriteSingleValues(report.Priority, p))

rb.SetPriority(int32(p))
}

if data.Status != nil {
st := *data.Status

alb = alb.AddChanges((&structures.AuditLogChange{
Format: structures.AuditLogChangeFormatSingleValue,
Key: "status",
}).WriteSingleValues(report.Status, structures.ReportStatus(st)))

rb.SetStatus(structures.ReportStatus(st))

if st == model.ReportStatusClosed {
rb.SetClosedAt(time.Now())

// Send notification to the user that their report has been handled
mb := structures.NewMessageBuilder(structures.Message[structures.MessageDataInbox]{}).
SetKind(structures.MessageKindInbox).
SetAuthorID(actor.ID).
SetTimestamp(time.Now()).
SetAnonymous(false).
SetData(structures.MessageDataInbox{
Subject: "inbox.generic.report_closed.subject",
Content: "inbox.generic.report_closed.content",
Locale: true,
System: true,
Placeholders: map[string]string{
"CASE_ID": report.CaseID,
},
})

_ = r.Ctx.Inst().Mutate.SendInboxMessage(ctx, mb, mutations.SendInboxMessageOptions{
Actor: &actor,
Recipients: []primitive.ObjectID{report.ActorID},
ConsiderBlockedUsers: false,
})
} else {
rb.SetClosedAt(time.Time{})
}
}

if data.Assignee != nil {
a := *data.Assignee

c := &structures.AuditLogChange{
Format: structures.AuditLogChangeFormatArrayChange,
Key: "assignee_ids",
}

assigneeID, err := primitive.ObjectIDFromHex(a[1:])

if err != nil {
return nil, errors.ErrBadObjectID()
}

state := a[0]
switch state {
case '+':
rb.AddAssignee(assigneeID)
c.WriteArrayAdded(assigneeID)
case '-':
rb.RemoveAssignee(assigneeID)
c.WriteArrayRemoved(assigneeID)
default:
return nil, errors.ErrInvalidRequest().SetDetail("assignee must be prefixed with '+' or '-'")
}

alb = alb.AddChanges(c)
}

rb.SetLastUpdatedAt(time.Now())

// Write update
if _, err := r.Ctx.Inst().Mongo.Collection(mongo.CollectionNameReports).UpdateOne(ctx, bson.M{
"_id": reportID,
}, rb.Update); err != nil {
return nil, errors.ErrInternalServerError()
}

// Write audit log
if _, err := r.Ctx.Inst().Mongo.Collection(mongo.CollectionNameAuditLogs).InsertOne(ctx, alb.AuditLog); err != nil {
zap.S().Errorw("mongo, failed to write audit log", "error", err)
}

return helpers.ReportStructureToModel(rb.Report), nil
}
10 changes: 0 additions & 10 deletions internal/gql/v3/resolvers/query/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,16 +82,6 @@ func (r *Resolver) Role(ctx context.Context, id primitive.ObjectID) (*model.Role
return nil, nil
}

func (r *Resolver) Reports(ctx context.Context, status *model.ReportStatus, limit *int, afterID *string, beforeID *string) ([]*model.Report, error) {
// TODO
return nil, nil
}

func (r *Resolver) Report(ctx context.Context, id primitive.ObjectID) (*model.Report, error) {
// TODO
return nil, nil
}

type Sort struct {
Value string `json:"value"`
Order SortOrder `json:"order"`
Expand Down
Loading