Skip to content

Commit c8db491

Browse files
pavleergDinoWw
andauthored
Feat/add gallery (#13)
* impl. galleryImage backend * added pages for gallery * finished mvp for gallery on frontend --------- Co-authored-by: Dino Plecko <[email protected]>
1 parent 12d071e commit c8db491

File tree

16 files changed

+1645
-43
lines changed

16 files changed

+1645
-43
lines changed
Lines changed: 386 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,386 @@
1+
import {
2+
GalleryImage, Image,
3+
} from "@generated/type-graphql";
4+
import {
5+
Arg, Authorized, Ctx, Field, FieldResolver, Info, InputType, Int, Mutation, ObjectType, Query, Resolver,
6+
Root,
7+
} from "type-graphql";
8+
import {
9+
type GraphQLResolveInfo,
10+
} from "graphql";
11+
import {
12+
type FileUpload, GraphQLUpload,
13+
} from "graphql-upload";
14+
import {
15+
type Context,
16+
} from "../../types/apollo-context";
17+
import {
18+
toSelect, transformSelectDefaults, transformSelectFor,
19+
} from "../helpers/resolver";
20+
import {
21+
GQLField,
22+
type Dict, type GQLResponse,
23+
} from "../../types/helpers";
24+
import {
25+
ValidationResponseFor,
26+
} from "../helpers/validation";
27+
import {
28+
hasAtLeastRole, Role,
29+
} from "../../helpers/auth";
30+
import {
31+
prisma,
32+
} from "../../providers/prisma";
33+
import {
34+
type ImageBase, ImageService,
35+
} from "../../services/image-service";
36+
import {
37+
EventsService,
38+
} from "../../services/events-service";
39+
import {
40+
swap,
41+
} from "../../services/helpers/orderable";
42+
import {
43+
transformSelect as transformSelectImage,
44+
} from "./image";
45+
46+
@Resolver(() => GalleryImage)
47+
export class GalleryImageFieldResolver {
48+
@FieldResolver(() => Image, { nullable: true })
49+
photo(
50+
@Root() galleryImage: GalleryImage,
51+
): GQLField<Image, "nullable"> {
52+
return galleryImage.photo ?? null;
53+
}
54+
}
55+
56+
export const transformSelect = transformSelectFor<GalleryImageFieldResolver>({
57+
...transformSelectDefaults({
58+
photo: transformSelectImage,
59+
}),
60+
});
61+
62+
@InputType()
63+
class GalleryImageFilter {
64+
@Field(() => Int, { nullable: true })
65+
take: number = 0;
66+
}
67+
68+
@InputType()
69+
export class GalleryImageCreateInput {
70+
@Field()
71+
name: string = "";
72+
73+
// @Field()
74+
// order: number = 0;
75+
76+
@Field()
77+
visible: boolean = false;
78+
79+
@Field(() => GraphQLUpload, { nullable: true })
80+
photo: FileUpload = null as unknown as FileUpload;
81+
}
82+
83+
84+
@ObjectType()
85+
class CreateGalleryImageResponse extends ValidationResponseFor(GalleryImage) {
86+
}
87+
88+
export class GalleryImageQueryResolver {
89+
@Query(() => [ GalleryImage ])
90+
galleryImages(
91+
@Ctx() ctx: Context,
92+
@Info() info: GraphQLResolveInfo,
93+
@Arg("filter", { nullable: true }) filter?: GalleryImageFilter,
94+
): GQLResponse<GalleryImage[]> {
95+
return ctx.prisma.galleryImage.findMany({
96+
take: filter?.take,
97+
where: {
98+
visible: true,
99+
},
100+
select: toSelect(info, transformSelect),
101+
orderBy: {
102+
order: "asc",
103+
},
104+
});
105+
}
106+
107+
@Query(() => [ GalleryImage ])
108+
allGalleryImages(
109+
@Ctx() ctx: Context,
110+
@Info() info: GraphQLResolveInfo,
111+
): GQLResponse<GalleryImage[]> {
112+
if (!hasAtLeastRole(Role.Admin, ctx.user)) {
113+
return Promise.resolve([]);
114+
}
115+
return ctx.prisma.galleryImage.findMany({
116+
select: toSelect(info, transformSelect),
117+
orderBy: {
118+
order: "asc",
119+
},
120+
});
121+
}
122+
123+
@Query(() => GalleryImage, { nullable: true })
124+
galleryImageItemByUid(
125+
@Ctx() ctx: Context,
126+
@Info() info: GraphQLResolveInfo,
127+
@Arg("uid") uid: string,
128+
): GQLResponse<GalleryImage, "nullable"> {
129+
// const { user } = ctx;
130+
131+
// if (!user) {
132+
// return null;
133+
// };
134+
135+
// if (!hasAtLeastRole(Role.Admin, user)) {
136+
// return null;
137+
// }
138+
139+
return ctx.prisma.galleryImage.findFirst({
140+
where: {
141+
uid,
142+
},
143+
select: toSelect(info, transformSelect),
144+
});
145+
}
146+
147+
148+
@Mutation(() => CreateGalleryImageResponse, { nullable: true })
149+
async createGalleryImage(
150+
@Ctx() ctx: Context,
151+
@Arg("info") info: GalleryImageCreateInput,
152+
): GQLResponse<CreateGalleryImageResponse, "nullable"> {
153+
const { user } = ctx;
154+
155+
if (!user) {
156+
return null;
157+
};
158+
159+
if (!hasAtLeastRole(Role.Admin, user)) {
160+
return null;
161+
}
162+
163+
try {
164+
const entity = await ctx.prisma.$transaction(async (prisma) => {
165+
const photo = await ImageService.uploadImage(
166+
"gallery" as ImageBase,
167+
await info.photo,
168+
user,
169+
prisma,
170+
);
171+
172+
if (!photo) {
173+
throw new Error("Couldn't upload photo");
174+
}
175+
176+
const latestGalleryImage = await prisma.galleryImage.findFirst({
177+
orderBy: {
178+
order: "desc",
179+
},
180+
});
181+
182+
return prisma.galleryImage.create({
183+
data: {
184+
name: info.name,
185+
visible: info.visible,
186+
photoId: photo.id,
187+
order: (latestGalleryImage?.order || 0) + 1,
188+
},
189+
});
190+
});
191+
192+
void EventsService.logEvent(
193+
"galleryImage:create",
194+
user.id,
195+
{
196+
name: info.name,
197+
photoUid: entity.uid,
198+
},
199+
);
200+
201+
return {
202+
entity,
203+
};
204+
} catch (e) {
205+
return {
206+
errors: [
207+
{
208+
field: "entity",
209+
message: (e as Error)?.message || "Something went wrong",
210+
},
211+
],
212+
};
213+
}
214+
}
215+
216+
@Mutation(() => CreateGalleryImageResponse, { nullable: true })
217+
async editGalleryImage(
218+
@Ctx() ctx: Context,
219+
@Info() gqlInfo: GraphQLResolveInfo,
220+
@Arg("uid") uid: string,
221+
@Arg("info") info: GalleryImageCreateInput,
222+
): GQLResponse<CreateGalleryImageResponse, "nullable"> {
223+
const { user } = ctx;
224+
225+
if (!user) {
226+
return null;
227+
};
228+
229+
if (!hasAtLeastRole(Role.Admin, user)) {
230+
return null;
231+
}
232+
233+
const ret = await ctx.prisma.$transaction(async (prisma) => {
234+
const oldGalleryImageExists = await prisma.galleryImage.findFirst({
235+
where: {
236+
uid,
237+
},
238+
select: {
239+
photo: {
240+
select: {
241+
uid: true,
242+
},
243+
},
244+
},
245+
});
246+
247+
if (!oldGalleryImageExists) {
248+
throw new Error(`No user with uid ${ uid }`);
249+
}
250+
251+
let photoConnect;
252+
const resPhoto = await info.photo;
253+
if (resPhoto) {
254+
const photo = await ImageService.uploadImage(
255+
"gallery" as ImageBase,
256+
resPhoto,
257+
user,
258+
prisma,
259+
);
260+
261+
if (!photo) {
262+
throw new Error("Something went wrong while uploading the photo");
263+
}
264+
265+
photoConnect = {
266+
connect: {
267+
id: photo.id,
268+
},
269+
};
270+
}
271+
272+
const res = await prisma.galleryImage.update({
273+
where: {
274+
uid,
275+
},
276+
data: {
277+
name: info.name,
278+
visible: info.visible,
279+
photo: photoConnect,
280+
},
281+
select: transformSelect(toSelect(gqlInfo, (x) => x).entity as Dict || { uid: true }),
282+
});
283+
284+
if (photoConnect) {
285+
await ImageService.deleteImage(
286+
oldGalleryImageExists.photo.uid,
287+
user,
288+
prisma,
289+
).catch(() => null);
290+
}
291+
292+
return res;
293+
}).then((res) => ({ success: true as const, res: res as unknown as GalleryImage })).catch((err: Error) => ({ success: false as const, err }));
294+
295+
if (ret.success) {
296+
return {
297+
entity: ret.res,
298+
};
299+
}
300+
301+
return {
302+
errors: [
303+
{
304+
field: "entity",
305+
message: ret.err.message,
306+
},
307+
],
308+
};
309+
}
310+
311+
@Mutation(() => Boolean)
312+
async deleteGalleryImage(
313+
@Ctx() ctx: Context,
314+
@Arg("uid") uid: string,
315+
) {
316+
const { user } = ctx;
317+
318+
if (!user) {
319+
return false;
320+
};
321+
322+
if (!hasAtLeastRole(Role.Admin, user)) {
323+
return false;
324+
}
325+
326+
const success = await ctx.prisma.$transaction(async (prisma) => {
327+
const galleryImage = await prisma.galleryImage.delete({
328+
where: {
329+
uid: uid,
330+
},
331+
select: {
332+
order: true,
333+
photo: {
334+
select: {
335+
uid: true,
336+
},
337+
},
338+
},
339+
}).catch(() => null);
340+
341+
if (!galleryImage) {
342+
throw new Error("GalleryImage does not exist");
343+
}
344+
345+
await prisma.galleryImage.updateMany({
346+
where: {
347+
order: {
348+
gt: galleryImage.order,
349+
},
350+
},
351+
data: {
352+
order: {
353+
decrement: 1,
354+
},
355+
},
356+
});
357+
358+
return ImageService.deleteImage(
359+
galleryImage.photo.uid,
360+
user,
361+
prisma,
362+
);
363+
}).catch(() => false);
364+
365+
return Promise.resolve(success);
366+
}
367+
368+
@Mutation(() => Boolean)
369+
async swapGalleryImageOrder(
370+
@Ctx() ctx: Context,
371+
@Arg("orderA", () => Int) orderA: number,
372+
@Arg("orderB", () => Int) orderB: number,
373+
) {
374+
await swap(
375+
"galleryImage",
376+
ctx.prisma,
377+
{
378+
a: orderA,
379+
b: orderB,
380+
},
381+
);
382+
383+
return true;
384+
}
385+
}
386+

0 commit comments

Comments
 (0)