source: database/drizzle/queries/builds.ts@ e09fe6f

main
Last change on this file since e09fe6f was e09fe6f, checked in by Tome <gjorgievtome@…>, 7 months ago

Properly implement onAddNewBuild

  • Property mode set to 100644
File size: 16.6 KB
Line 
1import type { Database } from "../db";
2import {
3 buildComponentsTable,
4 buildsTable,
5 componentsTable,
6 favoriteBuildsTable,
7 ratingBuildsTable,
8 reviewsTable, usersTable
9} from "../schema";
10import {eq, desc, and, sql, ilike, asc} from "drizzle-orm";
11import {inArray} from "drizzle-orm/sql/expressions/conditions";
12
13export async function getPendingBuilds(db: Database) {
14 const pendingBuilds = await db
15 .select({
16 id: buildsTable.id,
17 user_id: buildsTable.userId,
18 name: buildsTable.name,
19 created_at: buildsTable.createdAt,
20 total_price: buildsTable.totalPrice
21 })
22 .from(buildsTable)
23 .where(
24 eq(buildsTable.isApproved, false)
25 )
26 .orderBy(
27 desc(buildsTable.createdAt)
28 );
29
30 return pendingBuilds;
31}
32
33export async function getUserBuilds(db: Database, userId: number) {
34 const userBuilds = await db
35 .select({
36 id: buildsTable.id,
37 user_id: buildsTable.userId,
38 name: buildsTable.name,
39 created_at: buildsTable.createdAt,
40 total_price: buildsTable.totalPrice
41 })
42 .from(buildsTable)
43 .where(
44 eq(buildsTable.userId, userId)
45 )
46 .orderBy(
47 desc(buildsTable.createdAt)
48 );
49
50 return userBuilds;
51}
52
53export async function setBuildApprovalStatus(db: Database, buildId: number, isApproved: boolean){
54 const [result] = await db
55 .update(buildsTable)
56 .set({
57 isApproved: isApproved
58 })
59 .where(
60 and(
61 eq(buildsTable.id, buildId),
62 eq(buildsTable.isApproved, !isApproved)
63 )
64 )
65 .returning({
66 id: buildsTable.id
67 })
68
69 return result?.id ?? null;
70}
71
72export async function getFavoriteBuilds(db: Database, userId: number) {
73 const favoriteBuilds = await db
74 .select({
75 id: buildsTable.id,
76 user_id: buildsTable.userId,
77 name: buildsTable.name,
78 created_at: buildsTable.createdAt,
79 total_price: buildsTable.totalPrice,
80 avgRating: sql<number>`COALESCE(AVG(${ratingBuildsTable.value}::float),0)`
81 })
82 .from(buildsTable)
83 .innerJoin(
84 favoriteBuildsTable,
85 eq(buildsTable.id, favoriteBuildsTable.buildId)
86 )
87 .leftJoin(
88 ratingBuildsTable,
89 eq(buildsTable.id, ratingBuildsTable.buildId)
90 )
91 .where(
92 eq(favoriteBuildsTable.userId, userId)
93 )
94 .groupBy(
95 buildsTable.id,
96 buildsTable.userId,
97 buildsTable.name,
98 buildsTable.createdAt,
99 buildsTable.totalPrice
100 )
101 .orderBy(
102 desc(sql<number>`COALESCE(AVG(${ratingBuildsTable.value}::float),0)`)
103 );
104
105 return favoriteBuilds;
106}
107
108export async function getApprovedBuilds(db: Database, limit?: number, sort?: string, q?: string) {
109 let queryConditions = [eq(buildsTable.isApproved, true)];
110 let sortCondition:any;
111
112 if (q) {
113 queryConditions.push(
114 ilike(buildsTable.name, `%${q}%`)
115 );
116 }
117
118 switch(sort) {
119 case 'price_asc':
120 sortCondition = asc(buildsTable.totalPrice)
121 break;
122 case 'price_desc':
123 sortCondition = desc(buildsTable.totalPrice)
124 break;
125 case 'rating_desc':
126 sortCondition = desc(sql<number>`COALESCE(AVG(${ratingBuildsTable.value}::float),0)`)
127 break;
128 case 'oldest':
129 sortCondition = asc(buildsTable.createdAt)
130 break;
131 case 'newest':
132 sortCondition = desc(buildsTable.createdAt)
133 break;
134 default:
135 sortCondition = desc(buildsTable.createdAt)
136 break;
137 }
138
139 const approvedBuilds = await db
140 .select({
141 id: buildsTable.id,
142 user_id: buildsTable.userId,
143 name: buildsTable.name,
144 created_at: buildsTable.createdAt,
145 total_price: buildsTable.totalPrice,
146 avgRating: sql<number>`COALESCE(AVG(${ratingBuildsTable.value}::float),0)`
147 })
148 .from(buildsTable)
149 .leftJoin(
150 ratingBuildsTable,
151 eq(buildsTable.id, ratingBuildsTable.buildId)
152 )
153 .groupBy(
154 buildsTable.id,
155 buildsTable.userId,
156 buildsTable.name,
157 buildsTable.createdAt,
158 buildsTable.totalPrice
159 )
160 .where(
161 and (
162 ...queryConditions
163 )
164 )
165 .orderBy(
166 sortCondition
167 )
168 .limit(limit || 100); // 100 placeholder
169
170 return approvedBuilds;
171}
172
173export async function getBuildDetails(db: Database, buildId: number, userId?: number) {
174 return db.transaction(async (tx) => {
175 const [buildDetails] = await tx
176 .select({
177 id: buildsTable.id,
178 userId: buildsTable.userId,
179 name: buildsTable.name,
180 createdAt: buildsTable.createdAt,
181 description: buildsTable.description,
182 totalPrice: buildsTable.totalPrice,
183 isApproved: buildsTable.isApproved,
184 creator: usersTable.username
185 })
186 .from(buildsTable)
187 .innerJoin(
188 usersTable,
189 eq(buildsTable.userId, usersTable.id)
190 )
191 .where(
192 eq(buildsTable.id, buildId)
193 )
194 .limit(1);
195
196 if (!buildDetails) return null;
197
198 const components = await tx
199 .select({
200 componentId: buildComponentsTable.componentId,
201 component: componentsTable
202 })
203 .from(buildComponentsTable)
204 .innerJoin(
205 componentsTable,
206 eq(buildComponentsTable.componentId, componentsTable.id)
207 )
208 .where(
209 eq(buildComponentsTable.buildId, buildId)
210 );
211
212 const reviews = await tx
213 .select({
214 username: usersTable.username,
215 content: reviewsTable.content,
216 createdAt: reviewsTable.createdAt
217 })
218 .from(reviewsTable)
219 .innerJoin(
220 usersTable,
221 eq(reviewsTable.userId, usersTable.id)
222 )
223 .where(
224 eq(reviewsTable.buildId, buildId)
225 )
226 .orderBy(
227 desc(reviewsTable.createdAt)
228 );
229
230 let [ratingStatistics] = await tx
231 .select({
232 averageRating: sql<number>`COALESCE(AVG(${ratingBuildsTable.value}::float), 0)`.as("averageRating"),
233 ratingCount: sql<number>`COUNT(${ratingBuildsTable.value})`.as("ratingCount")
234 })
235 .from(ratingBuildsTable)
236 .where(
237 eq(ratingBuildsTable.buildId, buildId)
238 )
239 .groupBy(ratingBuildsTable.buildId);
240
241 ratingStatistics = {
242 averageRating: Number(ratingStatistics?.averageRating ?? 0),
243 ratingCount: Number(ratingStatistics?.ratingCount ?? 0),
244 }
245
246 let userRating = null;
247 let isFavorite = false;
248 let userReview = null;
249
250 if(userId) {
251 const [rating] = await tx
252 .select()
253 .from(ratingBuildsTable)
254 .where(
255 and(
256 eq(ratingBuildsTable.buildId, buildId),
257 eq(ratingBuildsTable.userId, userId)
258 )
259 )
260 .limit(1);
261
262 userRating = rating?.value ? Number(rating.value) : null;
263
264 const [favorite] = await tx
265 .select()
266 .from(favoriteBuildsTable)
267 .where(
268 and(
269 eq(favoriteBuildsTable.buildId, buildId),
270 eq(favoriteBuildsTable.userId, userId)
271 )
272 )
273 .limit(1);
274
275 isFavorite = !!favorite;
276
277 const [review] = await tx
278 .select()
279 .from(reviewsTable)
280 .where(
281 and(
282 eq(reviewsTable.buildId, buildId),
283 eq(reviewsTable.userId, userId)
284 )
285 )
286 .limit(1);
287
288 userReview = review?.content;
289 }
290
291 return {
292 ...buildDetails,
293 components: components.map(c => c.component),
294 reviews: reviews.map(r => ({
295 username: r.username,
296 content: r.content,
297 createdAt: r.createdAt
298 })),
299 ratingStatistics: ratingStatistics,
300 userRating,
301 userReview,
302 isFavorite
303 };
304 });
305}
306
307export async function toggleFavoriteBuild(db: Database, userId: number, buildId: number) {
308 const existing = await db
309 .select()
310 .from(favoriteBuildsTable)
311 .where(
312 and(
313 eq(favoriteBuildsTable.userId, userId),
314 eq(favoriteBuildsTable.buildId, buildId)
315 )
316 )
317 .limit(1);
318
319 if (existing.length > 0) {
320 await db
321 .delete(favoriteBuildsTable)
322 .where(
323 and(
324 eq(favoriteBuildsTable.userId, userId),
325 eq(favoriteBuildsTable.buildId, buildId)
326 )
327 );
328
329 return null;
330 }
331
332 await db
333 .insert(favoriteBuildsTable)
334 .values({
335 userId,
336 buildId,
337 });
338
339 return true;
340}
341
342export async function setBuildRating(db: Database, userId: number, buildId: number, value: number) {
343 const [result] = await db
344 .insert(ratingBuildsTable)
345 .values({
346 userId,
347 buildId,
348 value: value
349 })
350 .onConflictDoUpdate({
351 target: [ratingBuildsTable.userId, ratingBuildsTable.buildId],
352 set: {
353 value: value,
354 },
355 })
356 .returning({
357 userId: ratingBuildsTable.userId,
358 buildId: ratingBuildsTable.buildId,
359 value: ratingBuildsTable.value
360 })
361
362 return result ?? null;
363}
364
365export async function setBuildReview(db: Database, userId: number, buildId: number, content: string) {
366 const [result] = await db
367 .insert(reviewsTable)
368 .values({
369 userId,
370 buildId,
371 content,
372 createdAt: new Date().toISOString().split('T')[0]
373 })
374 .onConflictDoUpdate({
375 target: [reviewsTable.userId, reviewsTable.buildId],
376 set: {
377 content: content,
378 createdAt: new Date().toISOString().split('T')[0]
379 },
380 })
381 .returning({
382 userId: reviewsTable.userId,
383 buildId: reviewsTable.buildId,
384 content: reviewsTable.content,
385 createdAt: reviewsTable.createdAt
386 })
387
388 return result ?? null;
389}
390
391export async function cloneBuild(db: Database, userId: number, buildId: number) {
392 return db.transaction(async (tx) => {
393 const [buildToClone] = await tx
394 .select({
395 id: buildsTable.id,
396 userId: buildsTable.userId,
397 name: buildsTable.name,
398 description: buildsTable.description,
399 totalPrice: buildsTable.totalPrice,
400 })
401 .from(buildsTable)
402 .where(
403 eq(buildsTable.id, buildId)
404 )
405 .limit(1);
406
407 if (!buildToClone) return null;
408
409 const [newBuild] = await tx
410 .insert(buildsTable)
411 .values({
412 userId: userId,
413 name: `${buildToClone.name} (copy)`,
414 createdAt: new Date().toISOString().split('T')[0],
415 description: buildToClone.description,
416 totalPrice: buildToClone.totalPrice,
417 isApproved: false
418 })
419 .returning({
420 id: buildsTable.id
421 });
422
423 const components = await tx
424 .select()
425 .from(buildComponentsTable)
426 .where(
427 eq(buildComponentsTable.buildId, buildId)
428 );
429
430 if(components.length) {
431 await tx
432 .insert(buildComponentsTable)
433 .values(
434 components.map(component => ({
435 buildId: newBuild.id,
436 componentId: component.componentId
437 }))
438 );
439 }
440
441 const [clonedBuild] = await tx
442 .select({
443 id: buildsTable.id,
444 userId: buildsTable.userId,
445 name: buildsTable.name,
446 createdAt: buildsTable.createdAt,
447 description: buildsTable.description,
448 totalPrice: buildsTable.totalPrice
449 })
450 .from(buildsTable)
451 .innerJoin(
452 usersTable,
453 eq(buildsTable.userId, usersTable.id)
454 )
455 .where(
456 eq(buildsTable.id, newBuild.id)
457 )
458 .limit(1);
459
460 const clonedComponents = await tx
461 .select({
462 componentId: buildComponentsTable.componentId,
463 component: componentsTable
464 })
465 .from(buildComponentsTable)
466 .innerJoin(
467 componentsTable,
468 eq(buildComponentsTable.componentId, componentsTable.id)
469 )
470 .where(
471 eq(buildComponentsTable.buildId, newBuild.id)
472 );
473
474 return {
475 ...clonedBuild,
476 components: clonedComponents.map(c => c.component)
477 };
478 });
479}
480
481export async function deleteBuild(db: Database, userId: number, buildId: number) {
482 const [result] = await db
483 .delete(buildsTable)
484 .where(
485 and(
486 eq(buildsTable.id, buildId),
487 eq(buildsTable.userId, userId)
488 )
489 )
490 .returning({
491 id: buildsTable.id
492 })
493
494 return result?.id ?? null;
495}
496
497export async function addNewBuild(db: Database, userId: number, name: string, description: string) {
498 const [newBuild] = await db
499 .insert(buildsTable)
500 .values({
501 userId: userId,
502 name: name,
503 createdAt: new Date().toISOString().split('T')[0],
504 description: description,
505 totalPrice: Number(0).toFixed(2),
506 isApproved: false
507 })
508 .returning({
509 id: buildsTable.id
510 });
511
512 return newBuild?.id ?? null;
513}
514
515export async function editBuild(db: Database, userId: number, buildId: number, name: string, description: string, componentIds: number[]) {
516 return db.transaction(async (tx) => {
517 const [build] = await tx
518 .select({
519 id: buildsTable.id,
520 userId: buildsTable.userId,
521 isApproved: buildsTable.isApproved
522 })
523 .from(buildsTable)
524 .where(
525 and(
526 eq(buildsTable.id, buildId),
527 eq(buildsTable.userId, userId)
528 )
529 )
530 .limit(1);
531
532 if (!build) return null;
533 if (build.isApproved) return null;
534
535 const components = await tx
536 .select({
537 id: componentsTable.id,
538 price: componentsTable.price
539 })
540 .from(componentsTable)
541 .where(
542 inArray(componentsTable.id, componentIds)
543 );
544
545 const totalPrice = components.reduce((sum, c) => sum + Number(c.price), 0);
546
547 await tx
548 .update(buildsTable)
549 .set({
550 name: name,
551 description: description,
552 totalPrice: totalPrice.toFixed(2),
553 })
554 .where(
555 and(
556 eq(buildsTable.id, buildId),
557 eq(buildsTable.userId, userId)
558 )
559 );
560
561 await tx
562 .delete(buildComponentsTable)
563 .where(
564 eq(buildComponentsTable.buildId, buildId)
565 );
566
567 if(components.length) {
568 await tx.insert(buildComponentsTable)
569 .values(
570 componentIds.map(componentId => ({
571 buildId: buildId,
572 componentId: componentId
573 }))
574 );
575 }
576
577 return build.id;
578 });
579}
Note: See TracBrowser for help on using the repository browser.