/
emoteset.set_emote.mutation.go
267 lines (247 loc) · 7.99 KB
/
emoteset.set_emote.mutation.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
package mutations
import (
"context"
"time"
"github.com/SevenTV/Common/errors"
"github.com/SevenTV/Common/mongo"
"github.com/SevenTV/Common/structures/v3"
"github.com/SevenTV/Common/structures/v3/aggregations"
"github.com/SevenTV/Common/utils"
"github.com/hashicorp/go-multierror"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
)
// SetEmote: enable, edit or disable active emotes in the set
func (m *Mutate) EditEmotesInSet(ctx context.Context, esb *structures.EmoteSetBuilder, opt EmoteSetMutationSetEmoteOptions) error {
if esb == nil {
return errors.ErrInternalIncompleteMutation()
} else if esb.IsTainted() {
return errors.ErrMutateTaintedObject()
}
if len(opt.Emotes) == 0 {
return errors.ErrMissingRequiredField().SetDetail("EmoteIDs")
}
// Can actor do this?
actor := opt.Actor
if actor == nil || !actor.HasPermission(structures.RolePermissionEditEmoteSet) {
return errors.ErrInsufficientPrivilege().SetFields(errors.Fields{"MISSING_PERMISSION": "EDIT_EMOTE_SET"})
}
// Get relevant data
targetEmoteIDs := []primitive.ObjectID{}
targetEmoteMap := map[primitive.ObjectID]EmoteSetMutationSetEmoteItem{}
set := esb.EmoteSet
{
// Find emote set owner
if set.Owner == nil {
set.Owner = &structures.User{}
cur, err := m.mongo.Collection(mongo.CollectionNameUsers).Aggregate(ctx, append(mongo.Pipeline{
{{Key: "$match", Value: bson.M{"_id": set.OwnerID}}},
}, aggregations.UserRelationEditors...))
cur.Next(ctx)
if err = multierror.Append(err, cur.Decode(set.Owner), cur.Close(ctx)).ErrorOrNil(); err != nil {
if err == mongo.ErrNoDocuments {
return errors.ErrUnknownUser().SetDetail("emote set owner")
}
return err
}
}
// Fetch set emotes
if len(set.Emotes) == 0 {
cur, err := m.mongo.Collection(mongo.CollectionNameEmoteSets).Aggregate(ctx, append(mongo.Pipeline{
// Match only the target set
{{Key: "$match", Value: bson.M{"_id": set.ID}}},
}, aggregations.EmoteSetRelationActiveEmotes...))
if err = multierror.Append(err, cur.All(ctx, &set.Emotes)).ErrorOrNil(); err != nil {
return err
}
}
// Fetch target emotes
for _, e := range opt.Emotes {
targetEmoteIDs = append(targetEmoteIDs, e.ID)
targetEmoteMap[e.ID] = e
}
targetEmotes := []*structures.Emote{}
cur, err := m.mongo.Collection(mongo.CollectionNameEmotes).Aggregate(ctx, append(mongo.Pipeline{
{{Key: "$match", Value: bson.M{"versions.id": bson.M{"$in": targetEmoteIDs}}}},
}, aggregations.GetEmoteRelationshipOwner(aggregations.UserRelationshipOptions{Roles: true, Editors: true})...))
err = multierror.Append(err, cur.All(ctx, &targetEmotes)).ErrorOrNil()
if err != nil {
return err
}
for _, e := range targetEmotes {
for _, ver := range e.Versions {
if v, ok := targetEmoteMap[ver.ID]; ok {
v.emote = e
targetEmoteMap[e.ID] = v
}
}
}
}
// The actor must have access to the emote set
if set.OwnerID != actor.ID && !actor.HasPermission(structures.RolePermissionEditAnyEmoteSet) {
if set.Privileged && !actor.HasPermission(structures.RolePermissionSuperAdministrator) {
return errors.ErrInsufficientPrivilege().SetDetail("emote set is privileged")
}
if set.Owner != nil {
for _, ed := range set.Owner.Editors {
if ed.ID != actor.ID {
continue
}
if !ed.HasPermission(structures.UserEditorPermissionModifyEmotes) {
return errors.ErrInsufficientPrivilege().SetFields(errors.Fields{
"MISSING_EDITOR_PERMISSION": "MODIFY_EMOTES",
})
}
break
}
}
}
// Make a map of active set emotes
activeEmotes := map[primitive.ObjectID]*structures.Emote{}
for _, e := range set.Emotes {
activeEmotes[e.ID] = e.Emote
}
// Set up audit log entry
c := &structures.AuditLogChange{
Format: structures.AuditLogChangeFormatArrayChange,
Key: "emotes",
}
log := structures.NewAuditLogBuilder(structures.AuditLog{}).
SetKind(structures.AuditLogKindUpdateEmoteSet).
SetActor(actor.ID).
SetTargetKind(structures.ObjectKindEmoteSet).
SetTargetID(set.ID).
AddChanges(c)
// Iterate through the target emotes
// Check for permissions
for _, tgt := range targetEmoteMap {
if tgt.emote == nil {
continue
}
tgt.Name = utils.Ternary(tgt.Name != "", tgt.Name, tgt.emote.Name)
tgt.emote.Name = tgt.Name
if err := tgt.emote.Validator().Name(); err != nil {
return err
}
switch tgt.Action {
// ADD EMOTE
case structures.ListItemActionAdd:
// Handle emote privacy
if utils.BitField.HasBits(int64(tgt.emote.Flags), int64(structures.EmoteFlagsPrivate)) {
usable := false
// Usable if actor has Bypass Privacy permission
if actor.HasPermission(structures.RolePermissionBypassPrivacy) {
usable = true
}
// Usable if actor is an editor of emote owner
// and has the correct permission
if tgt.emote.Owner != nil {
var editor structures.UserEditor
for _, ed := range tgt.emote.Owner.Editors {
if opt.Actor.ID == ed.ID {
editor = ed
break
}
}
if !editor.ID.IsZero() && editor.HasPermission(structures.UserEditorPermissionUsePrivateEmotes) {
usable = true
}
}
if !usable {
return errors.ErrInsufficientPrivilege().SetFields(errors.Fields{
"EMOTE_ID": tgt.ID.Hex(),
}).SetDetail("emote is private")
}
}
// Verify that the set has available slots
if !actor.HasPermission(structures.RolePermissionEditAnyEmoteSet) {
if len(set.Emotes) >= int(set.EmoteSlots) {
return errors.ErrNoSpaceAvailable().
SetDetail("This set does not have enough slots").
SetFields(errors.Fields{"SLOTS": set.EmoteSlots})
}
}
// Check for conflicts with existing emotes
for _, e := range set.Emotes {
// Cannot enable the same emote twice
if tgt.ID == e.ID {
return errors.ErrEmoteAlreadyEnabled()
}
// Cannot have the same emote name as another active emote
if tgt.Name == e.Name {
return errors.ErrEmoteNameConflict()
}
}
// Add active emote
at := time.Now()
esb.AddActiveEmote(tgt.ID, tgt.Name, at, &actor.ID)
c.WriteArrayAdded(structures.ActiveEmote{
ID: tgt.ID,
Name: tgt.Name,
Flags: tgt.Flags,
Timestamp: at,
ActorID: actor.ID,
})
case structures.ListItemActionUpdate, structures.ListItemActionRemove:
// The emote must already be active
found := false
for _, e := range set.Emotes {
if tgt.Action == structures.ListItemActionUpdate && e.Name == tgt.Name {
return errors.ErrEmoteNameConflict().SetFields(errors.Fields{
"EMOTE_ID": tgt.ID.Hex(),
"CONFLICT_EMOTE_ID": tgt.ID.Hex(),
})
}
if e.ID == tgt.ID {
found = true
break
}
}
if !found {
return errors.ErrEmoteNotEnabled().SetFields(errors.Fields{
"EMOTE_ID": tgt.ID.Hex(),
})
}
if tgt.Action == structures.ListItemActionUpdate {
ae, ind := esb.EmoteSet.GetEmote(tgt.ID)
if !ae.ID.IsZero() {
c.WriteArrayUpdated(structures.AuditLogChangeSingleValue{
New: structures.ActiveEmote{
ID: tgt.ID,
Name: tgt.Name,
Flags: tgt.Flags,
Timestamp: ae.Timestamp,
},
Old: ae,
Position: int32(ind),
})
esb.UpdateActiveEmote(tgt.ID, tgt.Name)
}
} else if tgt.Action == structures.ListItemActionRemove {
esb.RemoveActiveEmote(tgt.ID)
c.WriteArrayRemoved(structures.ActiveEmote{
ID: tgt.ID,
})
}
}
}
// Update the document
if len(esb.Update) == 0 {
return errors.ErrUnknownEmote().SetDetail("no target emotes found")
}
if err := m.mongo.Collection(mongo.CollectionNameEmoteSets).FindOneAndUpdate(
ctx,
bson.M{"_id": set.ID},
esb.Update,
options.FindOneAndUpdate().SetReturnDocument(options.After),
).Decode(&esb.EmoteSet); err != nil {
return errors.ErrInternalServerError().SetDetail(err.Error())
}
// Write audit log entry
if _, err := m.mongo.Collection(mongo.CollectionNameAuditLogs).InsertOne(ctx, log.AuditLog); err != nil {
return err
}
esb.MarkAsTainted()
return nil
}