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
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)
[dd5067a]25 )
26 .orderBy(
27 desc(buildsTable.createdAt)
[1ebac59]28 );
29
[e7f1bfa]30 return pendingBuilds;
[1ebac59]31}
32
33export async function getUserBuilds(db: Database, userId: number) {
[e7f1bfa]34 const userBuilds = await db
[1ebac59]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)
[dd5067a]45 )
46 .orderBy(
47 desc(buildsTable.createdAt)
[1ebac59]48 );
49
[e7f1bfa]50 return userBuilds;
[1ebac59]51}
52
53export async function setBuildApprovalStatus(db: Database, buildId: number, isApproved: boolean){
[83fb5e2]54 const [result] = await db
[1ebac59]55 .update(buildsTable)
56 .set({
57 isApproved: isApproved
58 })
59 .where(
[ea09e98]60 and(
61 eq(buildsTable.id, buildId),
62 eq(buildsTable.isApproved, !isApproved)
63 )
64 )
65 .returning({
66 id: buildsTable.id
67 })
[1ebac59]68
[83fb5e2]69 return result?.id ?? null;
[1ebac59]70}
71
72export async function getFavoriteBuilds(db: Database, userId: number) {
[e7f1bfa]73 const favoriteBuilds = await db
[1ebac59]74 .select({
75 id: buildsTable.id,
76 user_id: buildsTable.userId,
77 name: buildsTable.name,
78 created_at: buildsTable.createdAt,
[82aa6ae]79 total_price: buildsTable.totalPrice,
80 avgRating: sql<number>`COALESCE(AVG(${ratingBuildsTable.value}::float),0)`
[1ebac59]81 })
82 .from(buildsTable)
83 .innerJoin(
84 favoriteBuildsTable,
85 eq(buildsTable.id, favoriteBuildsTable.buildId)
86 )
[82aa6ae]87 .leftJoin(
88 ratingBuildsTable,
89 eq(buildsTable.id, ratingBuildsTable.buildId)
90 )
[1ebac59]91 .where(
92 eq(favoriteBuildsTable.userId, userId)
[dd5067a]93 )
[82aa6ae]94 .groupBy(
95 buildsTable.id,
96 buildsTable.userId,
97 buildsTable.name,
98 buildsTable.createdAt,
99 buildsTable.totalPrice
100 )
[dd5067a]101 .orderBy(
102 desc(sql<number>`COALESCE(AVG(${ratingBuildsTable.value}::float),0)`)
[1ebac59]103 );
104
[e7f1bfa]105 return favoriteBuilds;
[1ebac59]106}
107
[59f8f5a]108export async function getApprovedBuilds(db: Database, limit?: number, sort?: string, q?: string) {
[c4dd17d]109 let queryConditions = [eq(buildsTable.isApproved, true)];
[59f8f5a]110 let sortConditions = [];
[1ebac59]111
112 if (q) {
113 queryConditions.push(
114 ilike(buildsTable.name, `%${q}%`)
115 );
116 }
117
[59f8f5a]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
[e7f1bfa]151 const approvedBuilds = await db
[1ebac59]152 .select({
153 id: buildsTable.id,
154 user_id: buildsTable.userId,
155 name: buildsTable.name,
156 created_at: buildsTable.createdAt,
[e729b88]157 total_price: buildsTable.totalPrice,
[83fb5e2]158 avgRating: sql<number>`COALESCE(AVG(${ratingBuildsTable.value}::float),0)`
[1ebac59]159 })
160 .from(buildsTable)
[59f8f5a]161 .leftJoin(
162 ratingBuildsTable,
163 eq(buildsTable.id, ratingBuildsTable.buildId)
164 )
165 .groupBy(
[1bf6e1f]166 buildsTable.id,
[59f8f5a]167 buildsTable.userId,
168 buildsTable.name,
169 buildsTable.createdAt,
170 buildsTable.totalPrice
171 )
[1ebac59]172 .where(
173 and (
174 ...queryConditions
175 )
176 )
177 .orderBy(
[59f8f5a]178 ...sortConditions
[1ebac59]179 )
180 .limit(limit || 100); // 100 placeholder
181
[e7f1bfa]182 return approvedBuilds;
[1ebac59]183}
184
[c4dd17d]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 );
[1ebac59]223
[c4dd17d]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),
[ea09e98]306 reviews: reviews.map(r => ({
307 username: r.username,
308 content: r.content,
309 createdAt: r.createdAt
310 })),
[c4dd17d]311 ratingStatistics: ratingStatistics,
312 userRating,
313 userReview,
314 isFavorite
315 };
316 });
[1ebac59]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
[83fb5e2]331 if (existing.length > 0) {
[1ebac59]332 await db
333 .delete(favoriteBuildsTable)
334 .where(
335 and(
336 eq(favoriteBuildsTable.userId, userId),
337 eq(favoriteBuildsTable.buildId, buildId)
338 )
339 );
340
[83fb5e2]341 return null;
[1ebac59]342 }
343
344 await db
345 .insert(favoriteBuildsTable)
346 .values({
347 userId,
348 buildId,
349 });
350
[83fb5e2]351 return true;
[1ebac59]352}
353
354export async function setBuildRating(db: Database, userId: number, buildId: number, value: number) {
[ea09e98]355 const [result] = await db
[1ebac59]356 .insert(ratingBuildsTable)
357 .values({
358 userId,
359 buildId,
[c4dd17d]360 value: value
[1ebac59]361 })
362 .onConflictDoUpdate({
363 target: [ratingBuildsTable.userId, ratingBuildsTable.buildId],
364 set: {
[c4dd17d]365 value: value,
[1ebac59]366 },
[ea09e98]367 })
368 .returning({
369 userId: ratingBuildsTable.userId,
370 buildId: ratingBuildsTable.buildId,
371 value: ratingBuildsTable.value
372 })
373
[83fb5e2]374 return result ?? null;
[1ebac59]375}
376
377export async function setBuildReview(db: Database, userId: number, buildId: number, content: string) {
[ea09e98]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
[83fb5e2]400 return result ?? null;
[ea09e98]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) {
[83fb5e2]494 const [result] = await db
[ea09e98]495 .delete(buildsTable)
[1ebac59]496 .where(
497 and(
[ea09e98]498 eq(buildsTable.id, buildId),
499 eq(buildsTable.userId, userId)
[1ebac59]500 )
501 )
[ea09e98]502 .returning({
503 id: buildsTable.id
504 })
[1ebac59]505
[83fb5e2]506 return result?.id ?? null;
[ea09e98]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
[1ebac59]514 })
[ea09e98]515 .from(componentsTable)
[1ebac59]516 .where(
[ea09e98]517 inArray(componentsTable.id, componentIds)
[1ebac59]518 );
519
[ea09e98]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 }
[1ebac59]545
[83fb5e2]546 return newBuild?.id ?? null;
[ea09e98]547 });
[1ebac59]548}
549
[ea09e98]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);
[1ebac59]566
[ea09e98]567 if (!build) return null;
568 if (build.isApproved) return null;
[1ebac59]569
[ea09e98]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 );
[1ebac59]579
[ea09e98]580 const totalPrice = components.reduce((sum, c) => sum + Number(c.price), 0);
[1ebac59]581
[ea09e98]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 )
[1ebac59]594 );
595
[ea09e98]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 });
[1ebac59]614}
Note: See TracBrowser for help on using the repository browser.