source: database/drizzle/queries/builds.ts@ 9c87509

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

implement components query and filtering

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