source: database/drizzle/queries/builds.ts@ 8849e3b

main
Last change on this file since 8849e3b was 1bf6e1f, checked in by Mihail <mihail2.naumov@…>, 7 months ago

Implemented part of the frontend (index, user, completedBuilds).

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