source: database/drizzle/queries/builds.ts@ 4ceb725

main
Last change on this file since 4ceb725 was e03e6fb, checked in by Tome <gjorgievtome@…>, 5 months ago

Fix missing attribute in buildComponents relation

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