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

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

implement forge features

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