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

main
Last change on this file since be22289 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
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";
[8fe7f1a]12import {addComponentToBuild} from "./components";
[1ebac59]13
14export async function getPendingBuilds(db: Database) {
[e7f1bfa]15 const pendingBuilds = await db
[1ebac59]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)
[dd5067a]26 )
27 .orderBy(
28 desc(buildsTable.createdAt)
[1ebac59]29 );
30
[e7f1bfa]31 return pendingBuilds;
[1ebac59]32}
33
34export async function getUserBuilds(db: Database, userId: number) {
[e7f1bfa]35 const userBuilds = await db
[1ebac59]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)
[dd5067a]46 )
47 .orderBy(
48 desc(buildsTable.createdAt)
[1ebac59]49 );
50
[e7f1bfa]51 return userBuilds;
[1ebac59]52}
53
54export async function setBuildApprovalStatus(db: Database, buildId: number, isApproved: boolean){
[83fb5e2]55 const [result] = await db
[1ebac59]56 .update(buildsTable)
57 .set({
58 isApproved: isApproved
59 })
60 .where(
[ea09e98]61 and(
62 eq(buildsTable.id, buildId),
63 eq(buildsTable.isApproved, !isApproved)
64 )
65 )
66 .returning({
67 id: buildsTable.id
68 })
[1ebac59]69
[83fb5e2]70 return result?.id ?? null;
[1ebac59]71}
72
73export async function getFavoriteBuilds(db: Database, userId: number) {
[e7f1bfa]74 const favoriteBuilds = await db
[1ebac59]75 .select({
76 id: buildsTable.id,
77 user_id: buildsTable.userId,
78 name: buildsTable.name,
79 created_at: buildsTable.createdAt,
[82aa6ae]80 total_price: buildsTable.totalPrice,
81 avgRating: sql<number>`COALESCE(AVG(${ratingBuildsTable.value}::float),0)`
[1ebac59]82 })
83 .from(buildsTable)
84 .innerJoin(
85 favoriteBuildsTable,
86 eq(buildsTable.id, favoriteBuildsTable.buildId)
87 )
[82aa6ae]88 .leftJoin(
89 ratingBuildsTable,
90 eq(buildsTable.id, ratingBuildsTable.buildId)
91 )
[1ebac59]92 .where(
93 eq(favoriteBuildsTable.userId, userId)
[dd5067a]94 )
[82aa6ae]95 .groupBy(
96 buildsTable.id,
97 buildsTable.userId,
98 buildsTable.name,
99 buildsTable.createdAt,
100 buildsTable.totalPrice
101 )
[dd5067a]102 .orderBy(
103 desc(sql<number>`COALESCE(AVG(${ratingBuildsTable.value}::float),0)`)
[1ebac59]104 );
105
[e7f1bfa]106 return favoriteBuilds;
[1ebac59]107}
108
[59f8f5a]109export async function getApprovedBuilds(db: Database, limit?: number, sort?: string, q?: string) {
[c4dd17d]110 let queryConditions = [eq(buildsTable.isApproved, true)];
[9854393]111 let sortCondition:any;
[1ebac59]112
113 if (q) {
114 queryConditions.push(
115 ilike(buildsTable.name, `%${q}%`)
116 );
117 }
118
[59f8f5a]119 switch(sort) {
120 case 'price_asc':
[9854393]121 sortCondition = asc(buildsTable.totalPrice)
[59f8f5a]122 break;
123 case 'price_desc':
[9854393]124 sortCondition = desc(buildsTable.totalPrice)
[59f8f5a]125 break;
126 case 'rating_desc':
[9854393]127 sortCondition = desc(sql<number>`COALESCE(AVG(${ratingBuildsTable.value}::float),0)`)
[59f8f5a]128 break;
129 case 'oldest':
[9854393]130 sortCondition = asc(buildsTable.createdAt)
[59f8f5a]131 break;
132 case 'newest':
[9854393]133 sortCondition = desc(buildsTable.createdAt)
[59f8f5a]134 break;
135 default:
[9854393]136 sortCondition = desc(buildsTable.createdAt)
[59f8f5a]137 break;
138 }
139
[e7f1bfa]140 const approvedBuilds = await db
[1ebac59]141 .select({
142 id: buildsTable.id,
143 user_id: buildsTable.userId,
144 name: buildsTable.name,
145 created_at: buildsTable.createdAt,
[e729b88]146 total_price: buildsTable.totalPrice,
[83fb5e2]147 avgRating: sql<number>`COALESCE(AVG(${ratingBuildsTable.value}::float),0)`
[1ebac59]148 })
149 .from(buildsTable)
[59f8f5a]150 .leftJoin(
151 ratingBuildsTable,
152 eq(buildsTable.id, ratingBuildsTable.buildId)
153 )
154 .groupBy(
[1bf6e1f]155 buildsTable.id,
[59f8f5a]156 buildsTable.userId,
157 buildsTable.name,
158 buildsTable.createdAt,
159 buildsTable.totalPrice
160 )
[1ebac59]161 .where(
162 and (
163 ...queryConditions
164 )
165 )
166 .orderBy(
[9854393]167 sortCondition
[1ebac59]168 )
169 .limit(limit || 100); // 100 placeholder
170
[e7f1bfa]171 return approvedBuilds;
[1ebac59]172}
173
[c4dd17d]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,
[e03e6fb]202 component: componentsTable,
203 quantity: buildComponentsTable.numComponents
[c4dd17d]204 })
205 .from(buildComponentsTable)
206 .innerJoin(
207 componentsTable,
208 eq(buildComponentsTable.componentId, componentsTable.id)
209 )
210 .where(
211 eq(buildComponentsTable.buildId, buildId)
212 );
[1ebac59]213
[c4dd17d]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,
[e03e6fb]295 components: components.map(c => ({
296 ...c.component,
297 quantity: c.quantity
298 })),
[ea09e98]299 reviews: reviews.map(r => ({
300 username: r.username,
301 content: r.content,
302 createdAt: r.createdAt
303 })),
[c4dd17d]304 ratingStatistics: ratingStatistics,
305 userRating,
306 userReview,
307 isFavorite
308 };
309 });
[1ebac59]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
[83fb5e2]324 if (existing.length > 0) {
[1ebac59]325 await db
326 .delete(favoriteBuildsTable)
327 .where(
328 and(
329 eq(favoriteBuildsTable.userId, userId),
330 eq(favoriteBuildsTable.buildId, buildId)
331 )
332 );
333
[83fb5e2]334 return null;
[1ebac59]335 }
336
337 await db
338 .insert(favoriteBuildsTable)
339 .values({
340 userId,
341 buildId,
342 });
343
[83fb5e2]344 return true;
[1ebac59]345}
346
347export async function setBuildRating(db: Database, userId: number, buildId: number, value: number) {
[ea09e98]348 const [result] = await db
[1ebac59]349 .insert(ratingBuildsTable)
350 .values({
351 userId,
352 buildId,
[c4dd17d]353 value: value
[1ebac59]354 })
355 .onConflictDoUpdate({
356 target: [ratingBuildsTable.userId, ratingBuildsTable.buildId],
357 set: {
[c4dd17d]358 value: value,
[1ebac59]359 },
[ea09e98]360 })
361 .returning({
362 userId: ratingBuildsTable.userId,
363 buildId: ratingBuildsTable.buildId,
364 value: ratingBuildsTable.value
365 })
366
[83fb5e2]367 return result ?? null;
[1ebac59]368}
369
370export async function setBuildReview(db: Database, userId: number, buildId: number, content: string) {
[ea09e98]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
[83fb5e2]393 return result ?? null;
[ea09e98]394}
395
396export async function cloneBuild(db: Database, userId: number, buildId: number) {
397 return db.transaction(async (tx) => {
398 const [buildToClone] = await tx
[8fe7f1a]399 .select()
[ea09e98]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
[8fe7f1a]422 if(!newBuild) return null;
[ea09e98]423
[8fe7f1a]424 const existing = await tx
[e03e6fb]425 .select({
426 componentId: buildComponentsTable.componentId,
427 numComponents: buildComponentsTable.numComponents
428 })
[ea09e98]429 .from(buildComponentsTable)
[8fe7f1a]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,
[e03e6fb]437 numComponents: r.numComponents
[8fe7f1a]438 })),
[ea09e98]439 );
[8fe7f1a]440 }
[ea09e98]441
[8fe7f1a]442 return newBuild.id;
[ea09e98]443 });
444}
445
446export async function deleteBuild(db: Database, userId: number, buildId: number) {
[83fb5e2]447 const [result] = await db
[ea09e98]448 .delete(buildsTable)
[1ebac59]449 .where(
450 and(
[ea09e98]451 eq(buildsTable.id, buildId),
452 eq(buildsTable.userId, userId)
[1ebac59]453 )
454 )
[ea09e98]455 .returning({
456 id: buildsTable.id
457 })
[1ebac59]458
[83fb5e2]459 return result?.id ?? null;
[ea09e98]460}
461
[e09fe6f]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 });
[1ebac59]476
[e09fe6f]477 return newBuild?.id ?? null;
[1ebac59]478}
479
[8fe7f1a]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)
[ea09e98]488 )
[8fe7f1a]489 )
490 .limit(1);
[1ebac59]491
[8fe7f1a]492 if (!buildToEdit || buildToEdit.isApproved) return null;
[1ebac59]493
494
[8fe7f1a]495 return buildToEdit?.id ?? null;
496}
[1ebac59]497
[8fe7f1a]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);
[1ebac59]512
[8fe7f1a]513 if (!build || build.isApproved) return null;
[ea09e98]514
[8fe7f1a]515 const [updated] = await db
516 .update(buildsTable)
517 .set({
518 name,
519 description
520 })
[ae5d054]521 .where(
522 eq(buildsTable.id, buildId)
523 )
524 .returning({
525 id: buildsTable.id
526 });
[ea09e98]527
[8fe7f1a]528 return updated?.id ?? null;
[1ebac59]529}
[8fe7f1a]530
531export async function getBuildState(db: Database, userId: number, buildId: number) {
[3870834]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 )
[8fe7f1a]548 )
[3870834]549 .limit(1);
[8fe7f1a]550
[3870834]551 if (!build || build.isApproved) return null;
[8fe7f1a]552
[3870834]553 const components = await tx
554 .select({
[e03e6fb]555 componentId: buildComponentsTable.componentId,
556 quantity: buildComponentsTable.numComponents
[8fe7f1a]557 })
[3870834]558 .from(buildComponentsTable)
559 .where(
560 eq(buildComponentsTable.buildId, buildId)
561 );
[8fe7f1a]562
[3870834]563 return {
564 build,
[e03e6fb]565 components: components.map(c => ({
566 id: c.componentId,
567 quantity: c.quantity
568 }))
[3870834]569 };
570 });
[8fe7f1a]571}
Note: See TracBrowser for help on using the repository browser.