Skip to content

Commit d99c8e6

Browse files
chore: wip
1 parent c45e218 commit d99c8e6

File tree

1 file changed

+256
-0
lines changed
  • storage/framework/core/commerce/src/categories

1 file changed

+256
-0
lines changed
Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
import type { CategoryRequestType } from '@stacksjs/orm'
2+
import type { CategoryJsonResponse } from '../../../../orm/src/models/Category'
3+
import { db } from '@stacksjs/database'
4+
import { fetchById } from './fetch'
5+
6+
/**
7+
* Update a category by ID
8+
*
9+
* @param id The ID of the category to update
10+
* @param request The updated category data
11+
* @returns The updated category record
12+
*/
13+
export async function update(id: number, request: CategoryRequestType): Promise<CategoryJsonResponse | undefined> {
14+
// Validate the request data
15+
await request.validate()
16+
17+
// Check if category exists
18+
const existingCategory = await fetchById(id)
19+
if (!existingCategory) {
20+
throw new Error(`Category with ID ${id} not found`)
21+
}
22+
23+
// Create update data object using request fields
24+
const updateData: Record<string, any> = {
25+
name: request.get('name'),
26+
description: request.get('description'),
27+
image_url: request.get('image_url'),
28+
is_active: request.get<boolean>('is_active'),
29+
parent_category_id: request.get('parent_category_id'),
30+
display_order: request.get<number>('display_order'),
31+
updated_at: new Date().toISOString(),
32+
}
33+
34+
// Remove undefined fields to avoid overwriting with null values
35+
Object.keys(updateData).forEach((key) => {
36+
if (updateData[key] === undefined) {
37+
delete updateData[key]
38+
}
39+
})
40+
41+
// If no fields to update, just return the existing category
42+
if (Object.keys(updateData).length === 0) {
43+
return existingCategory
44+
}
45+
46+
try {
47+
// Update the category
48+
await db
49+
.updateTable('categories')
50+
.set(updateData)
51+
.where('id', '=', id)
52+
.execute()
53+
54+
// Fetch and return the updated category
55+
return await fetchById(id)
56+
}
57+
catch (error) {
58+
if (error instanceof Error) {
59+
// Handle duplicate name error
60+
if (error.message.includes('Duplicate entry') && error.message.includes('name')) {
61+
throw new Error('A category with this name already exists')
62+
}
63+
64+
throw new Error(`Failed to update category: ${error.message}`)
65+
}
66+
67+
throw error
68+
}
69+
}
70+
71+
/**
72+
* Update category display order
73+
*
74+
* @param id The ID of the category
75+
* @param newOrder The new display order value
76+
* @returns The updated category
77+
*/
78+
export async function updateDisplayOrder(id: number, newOrder: number): Promise<CategoryJsonResponse | undefined> {
79+
// Check if category exists
80+
const category = await fetchById(id)
81+
82+
if (!category) {
83+
throw new Error(`Category with ID ${id} not found`)
84+
}
85+
86+
try {
87+
// Update the category's display order
88+
await db
89+
.updateTable('categories')
90+
.set({
91+
display_order: newOrder,
92+
updated_at: new Date().toISOString(),
93+
})
94+
.where('id', '=', id)
95+
.execute()
96+
97+
// Fetch the updated category
98+
return await fetchById(id)
99+
}
100+
catch (error) {
101+
if (error instanceof Error) {
102+
throw new Error(`Failed to update category display order: ${error.message}`)
103+
}
104+
105+
throw error
106+
}
107+
}
108+
109+
/**
110+
* Update category active status
111+
*
112+
* @param id The ID of the category
113+
* @param isActive Whether the category should be active
114+
* @returns The updated category
115+
*/
116+
export async function updateActiveStatus(id: number, isActive: boolean): Promise<CategoryJsonResponse | undefined> {
117+
// Check if category exists
118+
const category = await fetchById(id)
119+
120+
if (!category) {
121+
throw new Error(`Category with ID ${id} not found`)
122+
}
123+
124+
try {
125+
// Update the category's active status
126+
await db
127+
.updateTable('categories')
128+
.set({
129+
is_active: isActive,
130+
updated_at: new Date().toISOString(),
131+
})
132+
.where('id', '=', id)
133+
.execute()
134+
135+
// Fetch the updated category
136+
return await fetchById(id)
137+
}
138+
catch (error) {
139+
if (error instanceof Error) {
140+
throw new Error(`Failed to update category active status: ${error.message}`)
141+
}
142+
143+
throw error
144+
}
145+
}
146+
147+
/**
148+
* Move category to a different parent
149+
*
150+
* @param id The ID of the category to move
151+
* @param newParentId The ID of the new parent category, or null to make it a root category
152+
* @returns The updated category
153+
*/
154+
export async function updateParent(id: number, newParentId: string | null): Promise<CategoryJsonResponse | undefined> {
155+
// Check if category exists
156+
const category = await fetchById(id)
157+
158+
if (!category) {
159+
throw new Error(`Category with ID ${id} not found`)
160+
}
161+
162+
// If moving to a parent, check that the parent exists and is not the same category
163+
if (newParentId) {
164+
// Convert to number for comparison since id is number
165+
const newParentIdNum = Number(newParentId)
166+
167+
if (newParentIdNum === id) {
168+
throw new Error('A category cannot be its own parent')
169+
}
170+
171+
const parentCategory = await fetchById(newParentIdNum)
172+
if (!parentCategory) {
173+
throw new Error(`Parent category with ID ${newParentId} not found`)
174+
}
175+
176+
// Check for circular reference
177+
if (await wouldCreateCircularReference(id, newParentId)) {
178+
throw new Error('This operation would create a circular reference in the category hierarchy')
179+
}
180+
}
181+
182+
try {
183+
// Update the category's parent
184+
const updateObject = {
185+
updated_at: new Date().toISOString(),
186+
} as Record<string, any>
187+
188+
// Set parent_category_id explicitly based on whether newParentId is null
189+
if (newParentId === null) {
190+
await db
191+
.updateTable('categories')
192+
.set({
193+
...updateObject,
194+
parent_category_id: undefined,
195+
})
196+
.where('id', '=', id)
197+
.execute()
198+
} else {
199+
await db
200+
.updateTable('categories')
201+
.set({
202+
...updateObject,
203+
parent_category_id: newParentId,
204+
})
205+
.where('id', '=', id)
206+
.execute()
207+
}
208+
209+
// Fetch the updated category
210+
return await fetchById(id)
211+
}
212+
catch (error) {
213+
if (error instanceof Error) {
214+
throw new Error(`Failed to update category parent: ${error.message}`)
215+
}
216+
217+
throw error
218+
}
219+
}
220+
221+
/**
222+
* Helper function to check if changing a category's parent would create a circular reference
223+
* @param categoryId ID of the category being moved
224+
* @param newParentId ID of the new parent
225+
* @returns boolean indicating if a circular reference would be created
226+
*/
227+
async function wouldCreateCircularReference(categoryId: number, newParentId: string): Promise<boolean> {
228+
// Convert to number for consistency in the check
229+
let currentParentId = Number(newParentId)
230+
const visited = new Set<number>()
231+
232+
while (currentParentId) {
233+
// If we've seen this ID before, we have a cycle
234+
if (visited.has(currentParentId)) {
235+
return true
236+
}
237+
238+
// If we've reached the original category, we have a cycle
239+
if (currentParentId === categoryId) {
240+
return true
241+
}
242+
243+
visited.add(currentParentId)
244+
245+
// Get the parent's parent
246+
const parent = await fetchById(currentParentId)
247+
if (!parent || !parent.parent_category_id) {
248+
// We've reached a root category, no cycle
249+
return false
250+
}
251+
252+
currentParentId = Number(parent.parent_category_id)
253+
}
254+
255+
return false
256+
}

0 commit comments

Comments
 (0)