Skip to content

Commit 9800937

Browse files
Benjvvpfforres
andauthored
added updateCommunity in schema and gql, change test to folders (comm… (#77)
Co-authored-by: Felipe Torres <felipe.torressepulveda@gmail.com>
1 parent dc99774 commit 9800937

16 files changed

Lines changed: 436 additions & 27 deletions

src/authz/index.ts

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,23 @@ export class IsSameUser extends PreExecutionRule {
2323
}
2424
}
2525

26+
export class IsTicketOwner extends PreExecutionRule {
27+
error = new UnauthorizedError("Not authorized");
28+
public async execute(
29+
{ USER, DB }: GraphqlContext,
30+
fieldArgs: { input: { id: string } },
31+
) {
32+
if (!USER || !fieldArgs.input.id) {
33+
return false;
34+
}
35+
const IsTicketOwner = await DB.query.userTicketsSchema.findFirst({
36+
where: (utc, { eq, and }) =>
37+
and(eq(utc.userId, USER.id), eq(utc.id, fieldArgs.input.id)),
38+
});
39+
return Boolean(IsTicketOwner);
40+
}
41+
}
42+
2643
export class IsSuperAdmin extends PreExecutionRule {
2744
public execute({ USER }: GraphqlContext, fieldArgs: { id?: string }) {
2845
if (!USER) {
@@ -31,3 +48,119 @@ export class IsSuperAdmin extends PreExecutionRule {
3148
return Boolean(USER.isSuperAdmin);
3249
}
3350
}
51+
52+
export class CanCreateEvent extends PreExecutionRule {
53+
public async execute(
54+
{ USER, DB }: GraphqlContext,
55+
fieldArgs: { input: { communityId: string } },
56+
) {
57+
if (!USER || !fieldArgs?.input?.communityId) {
58+
return false;
59+
}
60+
const user = await DB.query.usersToCommunitiesSchema.findFirst({
61+
where: (utc, { eq, and }) =>
62+
and(eq(utc.userId, USER.id), eq(utc.role, "admin")),
63+
});
64+
65+
return Boolean(user);
66+
}
67+
}
68+
69+
export class isCommunityCollaborator extends PreExecutionRule {
70+
public async execute(
71+
{ USER, DB }: GraphqlContext,
72+
fieldArgs: { input: { communityId: string } },
73+
) {
74+
if (!USER || !fieldArgs?.input?.communityId) {
75+
return false;
76+
}
77+
const user = await DB.query.communitySchema.findFirst({
78+
with: {
79+
usersToCommunities: {
80+
where: (utc, { eq, and }) =>
81+
and(eq(utc.userId, USER.id), eq(utc.role, "admin")),
82+
},
83+
},
84+
});
85+
return Boolean(user);
86+
}
87+
}
88+
89+
export class isCommunityAdmin extends PreExecutionRule {
90+
public async execute(
91+
{ USER, DB }: GraphqlContext,
92+
fieldArgs: { input: { communityId: string } },
93+
) {
94+
if (!USER || !fieldArgs?.input?.communityId) {
95+
return false;
96+
}
97+
const isCommunityAdmin = await DB.query.usersToCommunitiesSchema.findFirst({
98+
where: (utc, { eq, and }) =>
99+
and(
100+
eq(utc.communityId, fieldArgs.input.communityId),
101+
eq(utc.userId, USER.id),
102+
eq(utc.role, "admin"),
103+
),
104+
});
105+
106+
return Boolean(isCommunityAdmin);
107+
}
108+
}
109+
110+
export class isEventAdmin extends PreExecutionRule {
111+
public async execute(
112+
{ USER, DB }: GraphqlContext,
113+
fieldArgs: { input: { eventId: string } },
114+
) {
115+
if (!USER || !fieldArgs?.input?.eventId) {
116+
return false;
117+
}
118+
const isEventAdmin = await DB.query.eventsToUsersSchema.findFirst({
119+
where: (utc, { eq, and }) =>
120+
and(
121+
eq(utc.eventId, fieldArgs.input.eventId),
122+
eq(utc.userId, USER.id),
123+
eq(utc.role, "admin"),
124+
),
125+
});
126+
127+
return Boolean(isEventAdmin);
128+
}
129+
}
130+
131+
export class canApproveTicket extends PreExecutionRule {
132+
public async execute(
133+
{ USER, DB }: GraphqlContext,
134+
fieldArgs: { userTicketId: string },
135+
) {
136+
if (!USER || !fieldArgs?.userTicketId) {
137+
return false;
138+
}
139+
140+
const userTicket = await DB.query.userTicketsSchema.findFirst({
141+
where: (utc, { eq }) => eq(utc.id, fieldArgs.userTicketId),
142+
with: {
143+
ticketTemplate: true,
144+
},
145+
});
146+
147+
if (!userTicket) {
148+
throw new GraphQLError("Ticket not found");
149+
}
150+
151+
if (USER.isSuperAdmin) {
152+
return true;
153+
}
154+
155+
const isEventAdmin = await DB.query.eventsToUsersSchema.findFirst({
156+
where: (utc, { eq, and }) =>
157+
and(
158+
eq(utc.eventId, userTicket?.ticketTemplate.eventId),
159+
eq(utc.userId, USER.id),
160+
eq(utc.role, "admin"),
161+
),
162+
});
163+
164+
return Boolean(isEventAdmin);
165+
}
166+
}

src/generated/schema.gql

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,6 @@ type Community {
2323
users: [User!]!
2424
}
2525

26-
input CommunityCreateInput {
27-
description: String!
28-
name: String!
29-
slug: String!
30-
}
31-
3226
"""
3327
Representation of a workEmail
3428
"""
@@ -54,6 +48,12 @@ enum CompanyStatus {
5448
inactive
5549
}
5650

51+
input CreateCommunityInput {
52+
description: String!
53+
name: String!
54+
slug: String!
55+
}
56+
5757
input CreateCompanyInput {
5858
description: String
5959

@@ -185,7 +185,7 @@ type Mutation {
185185
"""
186186
Create an community
187187
"""
188-
createCommunity(input: CommunityCreateInput!): Community!
188+
createCommunity(input: CreateCommunityInput!): Community!
189189

190190
"""
191191
Create a company
@@ -202,6 +202,11 @@ type Mutation {
202202
"""
203203
createSalary(input: CreateSalaryInput!): Salary!
204204

205+
"""
206+
Edit an community
207+
"""
208+
editCommunity(input: UpdateCommunityInput!): Community!
209+
205210
"""
206211
Edit a ticket
207212
"""
@@ -414,6 +419,11 @@ enum TypeOfEmployment {
414419
partTime
415420
}
416421

422+
input UpdateCommunityInput {
423+
communityId: String!
424+
status: CommnunityStatus
425+
}
426+
417427
input UpdateCompanyInput {
418428
companyId: String!
419429
description: String

src/generated/types.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,6 @@ export type Community = {
5959
users: Array<User>;
6060
};
6161

62-
export type CommunityCreateInput = {
63-
description: Scalars["String"]["input"];
64-
name: Scalars["String"]["input"];
65-
slug: Scalars["String"]["input"];
66-
};
67-
6862
/** Representation of a workEmail */
6963
export type Company = {
7064
__typename?: "Company";
@@ -86,6 +80,12 @@ export enum CompanyStatus {
8680
Inactive = "inactive",
8781
}
8882

83+
export type CreateCommunityInput = {
84+
description: Scalars["String"]["input"];
85+
name: Scalars["String"]["input"];
86+
slug: Scalars["String"]["input"];
87+
};
88+
8989
export type CreateCompanyInput = {
9090
description?: InputMaybe<Scalars["String"]["input"]>;
9191
/** The email domain of the company (What we'll use to match the company to the user on account-creation) */
@@ -208,6 +208,8 @@ export type Mutation = {
208208
createEvent: Event;
209209
/** Create a salary */
210210
createSalary: Salary;
211+
/** Edit an community */
212+
editCommunity: Community;
211213
/** Edit a ticket */
212214
editTicket: Ticket;
213215
/** Redeem a ticket */
@@ -235,7 +237,7 @@ export type MutationCancelUserTicketArgs = {
235237
};
236238

237239
export type MutationCreateCommunityArgs = {
238-
input: CommunityCreateInput;
240+
input: CreateCommunityInput;
239241
};
240242

241243
export type MutationCreateCompanyArgs = {
@@ -250,6 +252,10 @@ export type MutationCreateSalaryArgs = {
250252
input: CreateSalaryInput;
251253
};
252254

255+
export type MutationEditCommunityArgs = {
256+
input: UpdateCommunityInput;
257+
};
258+
253259
export type MutationEditTicketArgs = {
254260
input: TicketEditInput;
255261
};
@@ -464,6 +470,11 @@ export enum TypeOfEmployment {
464470
PartTime = "partTime",
465471
}
466472

473+
export type UpdateCommunityInput = {
474+
communityId: Scalars["String"]["input"];
475+
status?: InputMaybe<CommnunityStatus>;
476+
};
477+
467478
export type UpdateCompanyInput = {
468479
companyId: Scalars["String"]["input"];
469480
description?: InputMaybe<Scalars["String"]["input"]>;

src/schema/community.ts

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ import {
88
import { SQL, eq, like } from "drizzle-orm";
99
import { CommunityRef, EventRef, UserRef } from "~/schema/shared/refs";
1010
import { builder } from "~/builder";
11-
import { canCreateCommunity } from "~/validations";
11+
import { canCreateCommunity, canEditCommunity } from "~/validations";
1212
import { v4 } from "uuid";
1313
import { GraphQLError } from "graphql";
14+
import { CommnunityStatus as CommunityStatusEnum } from "~/generated/types";
1415

1516
export const CommnunityStatus = builder.enumType("CommnunityStatus", {
1617
values: ["active", "inactive"] as const,
@@ -134,14 +135,25 @@ builder.queryFields((t) => ({
134135
}),
135136
}));
136137

137-
const CommunityCreateInput = builder.inputType("CommunityCreateInput", {
138+
const CreateCommunityInput = builder.inputType("CreateCommunityInput", {
138139
fields: (t) => ({
139140
name: t.string({ required: true }),
140141
slug: t.string({ required: true }),
141142
description: t.string({ required: true }),
142143
}),
143144
});
144-
145+
const UpdateCommunityInput = builder.inputType("UpdateCommunityInput", {
146+
fields: (t) => ({
147+
communityId: t.string({ required: true }),
148+
status: t.field({
149+
type: CommnunityStatus,
150+
required: false,
151+
}),
152+
name: t.string({ required: false }),
153+
slug: t.string({ required: false }),
154+
description: t.string({ required: false }),
155+
}),
156+
});
145157
builder.mutationFields((t) => ({
146158
createCommunity: t.field({
147159
description: "Create an community",
@@ -151,7 +163,7 @@ builder.mutationFields((t) => ({
151163
rules: ["IsAuthenticated"],
152164
},
153165
args: {
154-
input: t.arg({ type: CommunityCreateInput, required: true }),
166+
input: t.arg({ type: CreateCommunityInput, required: true }),
155167
},
156168
resolve: async (root, { input }, { USER, DB }) => {
157169
try {
@@ -188,4 +200,57 @@ builder.mutationFields((t) => ({
188200
}
189201
},
190202
}),
203+
editCommunity: t.field({
204+
description: "Edit an community",
205+
type: CommunityRef,
206+
nullable: false,
207+
authz: {
208+
rules: ["IsAuthenticated"],
209+
},
210+
args: {
211+
input: t.arg({ type: UpdateCommunityInput, required: true }),
212+
},
213+
resolve: async (root, { input }, { USER, DB }) => {
214+
try {
215+
const { communityId, status, description, name, slug } = input;
216+
if (!USER) {
217+
throw new Error("User not found");
218+
}
219+
if (!(await canEditCommunity(USER, communityId, DB))) {
220+
throw new Error("FORBIDDEN");
221+
}
222+
const dataToUpdate: Record<string, string | null | undefined> = {};
223+
224+
const foundCommunity = await DB.query.communitySchema.findFirst({
225+
where: (c, { eq }) => eq(c.id, communityId),
226+
});
227+
228+
if (!foundCommunity) {
229+
throw new Error("Community not found");
230+
}
231+
if (status) {
232+
dataToUpdate.status = status;
233+
}
234+
if (description) {
235+
dataToUpdate.description = description;
236+
}
237+
if (name) {
238+
dataToUpdate.name = name;
239+
}
240+
if (slug) {
241+
dataToUpdate.slug = slug;
242+
}
243+
const community = await DB.update(communitySchema)
244+
.set(dataToUpdate)
245+
.where(eq(communitySchema.id, communityId))
246+
.returning()
247+
.get();
248+
return selectCommunitySchema.parse(community);
249+
} catch (e) {
250+
throw new GraphQLError(
251+
e instanceof Error ? e.message : "Unknown error",
252+
);
253+
}
254+
},
255+
}),
191256
}));

src/tests/community/community.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import {
55
Communities,
66
CommunitiesQuery,
77
CommunitiesQueryVariables,
8-
} from "~/tests/community/getCommunities.generated";
8+
} from "~/tests/community/getCommunities/getCommunities.generated";
99
import {
1010
Community,
1111
CommunityQuery,
1212
CommunityQueryVariables,
13-
} from "~/tests/community/getCommunity.generated";
13+
} from "~/tests/community/getCommunity/getCommunity.generated";
1414
import { CommnunityStatus } from "~/generated/types";
1515

1616
afterEach(() => {

0 commit comments

Comments
 (0)