Skip to content

Commit 86775c1

Browse files
chore: wip
1 parent 9a36668 commit 86775c1

File tree

8 files changed

+330
-138
lines changed

8 files changed

+330
-138
lines changed
Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
import type { ProductReviewJsonResponse } from '../../../../orm/src/models/ProductReview'
2+
import { db } from '@stacksjs/database'
3+
4+
export interface FetchProductReviewsOptions {
5+
page?: number
6+
limit?: number
7+
}
8+
9+
export interface ProductReviewResponse {
10+
data: ProductReviewJsonResponse[]
11+
paging: {
12+
total_records: number
13+
page: number
14+
total_pages: number
15+
}
16+
next_cursor: number | null
17+
}
18+
19+
export interface ProductReviewStats {
20+
total: number
21+
average_rating: number
22+
rating_distribution: {
23+
one_star: number
24+
two_star: number
25+
three_star: number
26+
four_star: number
27+
five_star: number
28+
}
29+
recent_reviews: ProductReviewJsonResponse[]
30+
}
31+
32+
/**
33+
* Fetch product reviews with pagination
34+
*/
35+
export async function fetchPaginated(options: FetchProductReviewsOptions = {}): Promise<ProductReviewResponse> {
36+
// Set default values
37+
const page = options.page || 1
38+
const limit = options.limit || 10
39+
40+
// Start building the query
41+
const query = db.selectFrom('product_reviews')
42+
const countQuery = db.selectFrom('product_reviews')
43+
44+
// Get total count for pagination
45+
const countResult = await countQuery
46+
.select(eb => eb.fn.count('id').as('total'))
47+
.executeTakeFirst()
48+
49+
const total = Number(countResult?.total || 0)
50+
51+
// Apply pagination
52+
const productReviews = await query
53+
.selectAll()
54+
.limit(limit)
55+
.offset((page - 1) * limit)
56+
.execute()
57+
58+
// Calculate pagination info
59+
const totalPages = Math.ceil(total / limit)
60+
61+
return {
62+
data: productReviews,
63+
paging: {
64+
total_records: total,
65+
page,
66+
total_pages: totalPages,
67+
},
68+
next_cursor: page < totalPages ? page + 1 : null,
69+
}
70+
}
71+
72+
/**
73+
* Fetch a product review by ID
74+
*/
75+
export async function fetchById(id: number): Promise<ProductReviewJsonResponse | undefined> {
76+
return await db
77+
.selectFrom('product_reviews')
78+
.where('id', '=', id)
79+
.selectAll()
80+
.executeTakeFirst()
81+
}
82+
83+
/**
84+
* Fetch reviews for a specific product
85+
*/
86+
export async function fetchByProductId(productId: number, options: FetchProductReviewsOptions = {}): Promise<ProductReviewResponse> {
87+
// Set default values
88+
const page = options.page || 1
89+
const limit = options.limit || 10
90+
91+
// Start building the query
92+
const query = db.selectFrom('product_reviews')
93+
.where('product_id', '=', productId)
94+
95+
const countQuery = db.selectFrom('product_reviews')
96+
.where('product_id', '=', productId)
97+
98+
// Get total count for pagination
99+
const countResult = await countQuery
100+
.select(eb => eb.fn.count('id').as('total'))
101+
.executeTakeFirst()
102+
103+
const total = Number(countResult?.total || 0)
104+
105+
// Apply pagination
106+
const productReviews = await query
107+
.selectAll()
108+
.limit(limit)
109+
.offset((page - 1) * limit)
110+
.execute()
111+
112+
// Calculate pagination info
113+
const totalPages = Math.ceil(total / limit)
114+
115+
return {
116+
data: productReviews,
117+
paging: {
118+
total_records: total,
119+
page,
120+
total_pages: totalPages,
121+
},
122+
next_cursor: page < totalPages ? page + 1 : null,
123+
}
124+
}
125+
126+
/**
127+
* Fetch reviews by user ID
128+
*/
129+
export async function fetchByUserId(userId: number, options: FetchProductReviewsOptions = {}): Promise<ProductReviewResponse> {
130+
// Set default values
131+
const page = options.page || 1
132+
const limit = options.limit || 10
133+
134+
// Start building the query
135+
const query = db.selectFrom('product_reviews')
136+
.where('customer_id', '=', userId)
137+
138+
const countQuery = db.selectFrom('product_reviews')
139+
.where('customer_id', '=', userId)
140+
141+
// Get total count for pagination
142+
const countResult = await countQuery
143+
.select(eb => eb.fn.count('id').as('total'))
144+
.executeTakeFirst()
145+
146+
const total = Number(countResult?.total || 0)
147+
148+
// Apply pagination
149+
const productReviews = await query
150+
.selectAll()
151+
.limit(limit)
152+
.offset((page - 1) * limit)
153+
.execute()
154+
155+
// Calculate pagination info
156+
const totalPages = Math.ceil(total / limit)
157+
158+
return {
159+
data: productReviews,
160+
paging: {
161+
total_records: total,
162+
page,
163+
total_pages: totalPages,
164+
},
165+
next_cursor: page < totalPages ? page + 1 : null,
166+
}
167+
}
168+
169+
/**
170+
* Fetch approved reviews for a product
171+
*/
172+
export async function fetchApprovedByProductId(productId: number, options: FetchProductReviewsOptions = {}): Promise<ProductReviewResponse> {
173+
// Set default values
174+
const page = options.page || 1
175+
const limit = options.limit || 10
176+
177+
// Start building the query
178+
const query = db.selectFrom('product_reviews')
179+
.where('product_id', '=', productId)
180+
.where('is_approved', '=', true)
181+
182+
const countQuery = db.selectFrom('product_reviews')
183+
.where('product_id', '=', productId)
184+
.where('is_approved', '=', true)
185+
186+
// Get total count for pagination
187+
const countResult = await countQuery
188+
.select(eb => eb.fn.count('id').as('total'))
189+
.executeTakeFirst()
190+
191+
const total = Number(countResult?.total || 0)
192+
193+
// Apply pagination
194+
const productReviews = await query
195+
.selectAll()
196+
.limit(limit)
197+
.offset((page - 1) * limit)
198+
.execute()
199+
200+
// Calculate pagination info
201+
const totalPages = Math.ceil(total / limit)
202+
203+
return {
204+
data: productReviews,
205+
paging: {
206+
total_records: total,
207+
page,
208+
total_pages: totalPages,
209+
},
210+
next_cursor: page < totalPages ? page + 1 : null,
211+
}
212+
}
213+
214+
/**
215+
* Get review statistics for a product
216+
*/
217+
export async function fetchProductReviewStats(productId: number): Promise<ProductReviewStats> {
218+
// Total reviews for the product
219+
const totalReviews = await db
220+
.selectFrom('product_reviews')
221+
.where('product_id', '=', productId)
222+
.where('is_approved', '=', true)
223+
.select(eb => eb.fn.count('id').as('count'))
224+
.executeTakeFirst()
225+
226+
// Average rating
227+
const avgRating = await db
228+
.selectFrom('product_reviews')
229+
.where('product_id', '=', productId)
230+
.where('is_approved', '=', true)
231+
.select(eb => eb.fn.avg('rating').as('avg_rating'))
232+
.executeTakeFirst()
233+
234+
// Rating distribution
235+
const oneStarCount = await db
236+
.selectFrom('product_reviews')
237+
.where('product_id', '=', productId)
238+
.where('is_approved', '=', true)
239+
.where('rating', '=', 1)
240+
.select(eb => eb.fn.count('id').as('count'))
241+
.executeTakeFirst()
242+
243+
const twoStarCount = await db
244+
.selectFrom('product_reviews')
245+
.where('product_id', '=', productId)
246+
.where('is_approved', '=', true)
247+
.where('rating', '=', 2)
248+
.select(eb => eb.fn.count('id').as('count'))
249+
.executeTakeFirst()
250+
251+
const threeStarCount = await db
252+
.selectFrom('product_reviews')
253+
.where('product_id', '=', productId)
254+
.where('is_approved', '=', true)
255+
.where('rating', '=', 3)
256+
.select(eb => eb.fn.count('id').as('count'))
257+
.executeTakeFirst()
258+
259+
const fourStarCount = await db
260+
.selectFrom('product_reviews')
261+
.where('product_id', '=', productId)
262+
.where('is_approved', '=', true)
263+
.where('rating', '=', 4)
264+
.select(eb => eb.fn.count('id').as('count'))
265+
.executeTakeFirst()
266+
267+
const fiveStarCount = await db
268+
.selectFrom('product_reviews')
269+
.where('product_id', '=', productId)
270+
.where('is_approved', '=', true)
271+
.where('rating', '=', 5)
272+
.select(eb => eb.fn.count('id').as('count'))
273+
.executeTakeFirst()
274+
275+
// Recent reviews
276+
const recentReviews = await db
277+
.selectFrom('product_reviews')
278+
.where('product_id', '=', productId)
279+
.where('is_approved', '=', true)
280+
.selectAll()
281+
.orderBy('created_at', 'desc')
282+
.limit(5)
283+
.execute()
284+
285+
return {
286+
total: Number(totalReviews?.count || 0),
287+
average_rating: Number(avgRating?.avg_rating || 0),
288+
rating_distribution: {
289+
one_star: Number(oneStarCount?.count || 0),
290+
two_star: Number(twoStarCount?.count || 0),
291+
three_star: Number(threeStarCount?.count || 0),
292+
four_star: Number(fourStarCount?.count || 0),
293+
five_star: Number(fiveStarCount?.count || 0),
294+
},
295+
recent_reviews: recentReviews,
296+
}
297+
}
298+
299+
/**
300+
* Fetch most helpful reviews for a product
301+
*/
302+
export async function fetchMostHelpfulByProductId(productId: number, limit: number = 5): Promise<ProductReviewJsonResponse[]> {
303+
return await db
304+
.selectFrom('product_reviews')
305+
.where('product_id', '=', productId)
306+
.where('is_approved', '=', true)
307+
.selectAll()
308+
.orderBy('helpful_votes', 'desc')
309+
.limit(limit)
310+
.execute()
311+
}

storage/framework/defaults/models/ProductReview.ts

Lines changed: 1 addition & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -29,32 +29,9 @@ export default {
2929
observe: true,
3030
},
3131

32-
belongsTo: ['Product', 'User'],
32+
belongsTo: ['Product', 'Customer'],
3333

3434
attributes: {
35-
product_id: {
36-
required: true,
37-
order: 1,
38-
fillable: true,
39-
validation: {
40-
rule: schema.string().uuid(),
41-
message: {
42-
uuid: 'Product ID must be a valid UUID',
43-
},
44-
},
45-
factory: faker => faker.string.uuid(),
46-
},
47-
48-
user_id: {
49-
required: true,
50-
order: 2,
51-
fillable: true,
52-
validation: {
53-
rule: schema.number(),
54-
},
55-
factory: faker => faker.number.int({ min: 1, max: 1000 }),
56-
},
57-
5835
rating: {
5936
required: true,
6037
order: 3,
@@ -162,42 +139,6 @@ export default {
162139
return JSON.stringify([])
163140
},
164141
},
165-
166-
pros: {
167-
required: false,
168-
order: 12,
169-
fillable: true,
170-
validation: {
171-
rule: schema.string(),
172-
},
173-
factory: (faker) => {
174-
const hasPros = faker.datatype.boolean({ probability: 0.7 })
175-
if (hasPros) {
176-
const count = faker.number.int({ min: 1, max: 4 })
177-
const pros = Array.from({ length: count }, () => faker.lorem.sentence({ min: 3, max: 8 }))
178-
return JSON.stringify(pros)
179-
}
180-
return JSON.stringify([])
181-
},
182-
},
183-
184-
cons: {
185-
required: false,
186-
order: 13,
187-
fillable: true,
188-
validation: {
189-
rule: schema.string(),
190-
},
191-
factory: (faker) => {
192-
const hasCons = faker.datatype.boolean({ probability: 0.6 })
193-
if (hasCons) {
194-
const count = faker.number.int({ min: 0, max: 3 })
195-
const cons = Array.from({ length: count }, () => faker.lorem.sentence({ min: 3, max: 8 }))
196-
return JSON.stringify(cons)
197-
}
198-
return JSON.stringify([])
199-
},
200-
},
201142
},
202143

203144
dashboard: {

0 commit comments

Comments
 (0)