source: database/drizzle/queries/builds.ts@ 82aa6ae

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

fix getFavoriteBuildings

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