source: database/drizzle/queries/builds.ts@ 76b980b

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

update getApprovedBuilds

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