1- import { existsSync , mkdirSync } from 'node:fs' ;
1+ import { existsSync , mkdirSync , readFileSync , writeFileSync } from 'node:fs' ;
22import { dirname , resolve } from 'node:path' ;
33import { randomBytes , randomUUID , scryptSync , timingSafeEqual } from 'node:crypto' ;
4- import { DatabaseSync } from 'node:sqlite' ;
54
65const defaultDbPath = resolve ( process . cwd ( ) , 'backend' , 'data' , 'brocode.sqlite' ) ;
76const dbPath = process . env . BROCODE_DB_PATH ? resolve ( process . env . BROCODE_DB_PATH ) : defaultDbPath ;
@@ -11,10 +10,6 @@ if (!existsSync(dbDirectory)) {
1110 mkdirSync ( dbDirectory , { recursive : true } ) ;
1211}
1312
14- const db = new DatabaseSync ( dbPath ) ;
15- db . exec ( 'PRAGMA journal_mode = WAL;' ) ;
16- db . exec ( 'PRAGMA foreign_keys = ON;' ) ;
17-
1813const HASH_PREFIX = 'scrypt$' ;
1914const SCRYPT_KEY_LENGTH = 64 ;
2015
@@ -112,6 +107,79 @@ if (!hasUsers) {
112107 VALUES (?, ?, ?, ?, ?, ?)`
113108 ) . run ( 'ord-1' , 'd-1' , 'Brocode Beer' , 2 , 180 , 360 ) ;
114109}
110+ const seedData = ( ) => ( {
111+ users : [
112+ { id : 'u-1' , username : 'brocode' , password : hashPassword ( 'changeme' ) , name : 'Ram' , role : 'admin' } ,
113+ { id : 'u-2' , username : 'dhanush' , password : hashPassword ( 'changeme' ) , name : 'Dhanush' , role : 'user' } ,
114+ ] ,
115+ spots : [
116+ {
117+ id : 'spot-2025-07-26' ,
118+ location : 'Attibele Toll Plaza' ,
119+ date : '2025-07-26T10:00:00.000Z' ,
120+ host_user_id : 'u-1' ,
121+ } ,
122+ ] ,
123+ catalog_items : [
124+ { id : 'd-1' , category : 'drinks' , name : 'Brocode Beer' , price : 180 } ,
125+ { id : 'd-2' , category : 'drinks' , name : 'Kingfisher Beer' , price : 170 } ,
126+ { id : 'f-1' , category : 'food' , name : 'Beef Biriyani' , price : 220 } ,
127+ { id : 'f-2' , category : 'food' , name : 'Parotta' , price : 30 } ,
128+ { id : 'c-1' , category : 'cigarettes' , name : 'Marlboro' , price : 25 } ,
129+ { id : 'c-2' , category : 'cigarettes' , name : 'Classic' , price : 20 } ,
130+ ] ,
131+ orders : [
132+ {
133+ id : 'ord-1' ,
134+ spot_id : 'spot-2025-07-26' ,
135+ user_id : 'u-2' ,
136+ total_amount : 360 ,
137+ created_at : '2025-07-26T10:30:00.000Z' ,
138+ } ,
139+ ] ,
140+ order_items : [
141+ {
142+ id : 1 ,
143+ order_id : 'ord-1' ,
144+ product_id : 'd-1' ,
145+ name : 'Brocode Beer' ,
146+ quantity : 2 ,
147+ unit_price : 180 ,
148+ total : 360 ,
149+ } ,
150+ ] ,
151+ next_order_item_id : 2 ,
152+ } ) ;
153+
154+ const loadData = ( ) => {
155+ if ( ! existsSync ( dbPath ) ) {
156+ const initial = seedData ( ) ;
157+ writeFileSync ( dbPath , JSON . stringify ( initial , null , 2 ) ) ;
158+ return initial ;
159+ }
160+
161+ try {
162+ const parsed = JSON . parse ( readFileSync ( dbPath , 'utf-8' ) ) ;
163+ return {
164+ users : parsed . users || [ ] ,
165+ spots : parsed . spots || [ ] ,
166+ catalog_items : parsed . catalog_items || [ ] ,
167+ orders : parsed . orders || [ ] ,
168+ order_items : parsed . order_items || [ ] ,
169+ next_order_item_id : parsed . next_order_item_id || 1 ,
170+ } ;
171+ } catch {
172+ const initial = seedData ( ) ;
173+ writeFileSync ( dbPath , JSON . stringify ( initial , null , 2 ) ) ;
174+ return initial ;
175+ }
176+ } ;
177+
178+ const state = loadData ( ) ;
179+
180+ const persist = ( ) => {
181+ writeFileSync ( dbPath , JSON . stringify ( state , null , 2 ) ) ;
182+ } ;
115183
116184const mapOrder = ( order , items ) => ( {
117185 id : order . id ,
@@ -166,14 +234,15 @@ const deleteSpotsByHostUserIdStatement = db.prepare('DELETE FROM spots WHERE hos
166234
167235export const database = {
168236 getUserByCredentials ( username , password ) {
169- const user = getUserByUsernameStatement . get ( username ) ;
237+ const user = state . users . find ( ( entry ) => entry . username === username ) ;
170238
171239 if ( ! user || ! verifyPassword ( password , user . password ) ) {
172240 return null ;
173241 }
174242
175243 if ( ! user . password . startsWith ( HASH_PREFIX ) ) {
176- updateUserPasswordStatement . run ( hashPassword ( password ) , user . id ) ;
244+ user . password = hashPassword ( password ) ;
245+ persist ( ) ;
177246 }
178247
179248 return {
@@ -185,8 +254,7 @@ export const database = {
185254 } ,
186255
187256 getCatalog ( ) {
188- const rows = db . prepare ( 'SELECT id, category, name, price FROM catalog_items ORDER BY category, id' ) . all ( ) ;
189- return rows . reduce (
257+ return state . catalog_items . reduce (
190258 ( acc , row ) => {
191259 if ( ! acc [ row . category ] ) {
192260 acc [ row . category ] = [ ] ;
@@ -204,52 +272,35 @@ export const database = {
204272 ) ;
205273 } ,
206274
207- getCatalogCategory ( category ) {
208- const rows = db . prepare ( 'SELECT id, name, price FROM catalog_items WHERE category = ? ORDER BY id' ) . all ( category ) ;
209- return rows ;
210- } ,
211-
212275 userExists ( userId ) {
213- return Boolean ( userExistsStatement . get ( userId ) ) ;
276+ return state . users . some ( ( user ) => user . id === userId ) ;
214277 } ,
215278
216279 spotExists ( spotId ) {
217- return Boolean ( spotExistsStatement . get ( spotId ) ) ;
280+ return state . spots . some ( ( spot ) => spot . id === spotId ) ;
218281 } ,
219282
220283 getSpots ( ) {
221- return db
222- . prepare ( 'SELECT id, location, date, host_user_id AS hostUserId FROM spots ORDER BY date DESC' )
223- . all ( ) ;
284+ return state . spots
285+ . map ( ( spot ) => ( {
286+ id : spot . id ,
287+ location : spot . location ,
288+ date : spot . date ,
289+ hostUserId : spot . host_user_id ,
290+ } ) )
291+ . sort ( ( a , b ) => b . date . localeCompare ( a . date ) ) ;
224292 } ,
225293
226294 getOrders ( { spotId, userId } ) {
227- const conditions = [ ] ;
228- const values = [ ] ;
229-
230- if ( spotId ) {
231- conditions . push ( 'spot_id = ?' ) ;
232- values . push ( spotId ) ;
233- }
234-
235- if ( userId ) {
236- conditions . push ( 'user_id = ?' ) ;
237- values . push ( userId ) ;
238- }
239-
240- const whereClause = conditions . length > 0 ? `WHERE ${ conditions . join ( ' AND ' ) } ` : '' ;
241- const orders = db
242- . prepare (
243- `SELECT id, spot_id, user_id, total_amount, created_at
244- FROM orders
245- ${ whereClause }
246- ORDER BY created_at DESC`
247- )
248- . all ( ...values ) ;
249-
250- const itemsByOrderId = fetchOrderItemsByOrderIds ( orders . map ( ( order ) => order . id ) ) ;
251-
252- return orders . map ( ( order ) => mapOrder ( order , itemsByOrderId . get ( order . id ) || [ ] ) ) ;
295+ const orders = state . orders
296+ . filter ( ( order ) => ! spotId || order . spot_id === spotId )
297+ . filter ( ( order ) => ! userId || order . user_id === userId )
298+ . sort ( ( a , b ) => b . created_at . localeCompare ( a . created_at ) ) ;
299+
300+ return orders . map ( ( order ) => {
301+ const orderItems = state . order_items . filter ( ( item ) => item . order_id === order . id ) ;
302+ return mapOrder ( order , orderItems ) ;
303+ } ) ;
253304 } ,
254305
255306 createOrder ( { spotId, userId, items } ) {
@@ -259,7 +310,7 @@ export const database = {
259310 throw new Error ( 'Each order item must include productId and a positive integer quantity' ) ;
260311 }
261312
262- const catalogItem = getCatalogItemByIdStatement . get ( item . productId ) ;
313+ const catalogItem = state . catalog_items . find ( ( entry ) => entry . id === item . productId ) ;
263314 if ( ! catalogItem ) {
264315 throw new Error ( `Unknown productId: ${ item . productId } ` ) ;
265316 }
@@ -277,24 +328,27 @@ export const database = {
277328 const orderId = randomUUID ( ) ;
278329 const createdAt = new Date ( ) . toISOString ( ) ;
279330
280- const insertOrder = db . prepare (
281- 'INSERT INTO orders (id, spot_id, user_id, total_amount, created_at) VALUES (?, ?, ?, ?, ?)'
282- ) ;
283- const insertOrderItem = db . prepare (
284- 'INSERT INTO order_items (order_id, product_id, name, quantity, unit_price, total) VALUES (?, ?, ?, ?, ?, ?)'
285- ) ;
331+ state . orders . push ( {
332+ id : orderId ,
333+ spot_id : spotId ,
334+ user_id : userId ,
335+ total_amount : totalAmount ,
336+ created_at : createdAt ,
337+ } ) ;
286338
287- db . exec ( 'BEGIN' ) ;
288- try {
289- insertOrder . run ( orderId , spotId , userId , totalAmount , createdAt ) ;
290- parsedItems . forEach ( ( item ) => {
291- insertOrderItem . run ( orderId , item . productId , item . name , item . quantity , item . unitPrice , item . total ) ;
339+ parsedItems . forEach ( ( item ) => {
340+ state . order_items . push ( {
341+ id : state . next_order_item_id ++ ,
342+ order_id : orderId ,
343+ product_id : item . productId ,
344+ name : item . name ,
345+ quantity : item . quantity ,
346+ unit_price : item . unitPrice ,
347+ total : item . total ,
292348 } ) ;
293- db . exec ( 'COMMIT' ) ;
294- } catch ( error ) {
295- db . exec ( 'ROLLBACK' ) ;
296- throw error ;
297- }
349+ } ) ;
350+
351+ persist ( ) ;
298352
299353 return {
300354 id : orderId ,
@@ -322,28 +376,19 @@ export const database = {
322376 } ,
323377
324378 getBillBySpotId ( spotId ) {
325- const summaryRows = db
326- . prepare (
327- `SELECT user_id, SUM(total_amount) AS total
328- FROM orders
329- WHERE spot_id = ?
330- GROUP BY user_id`
331- )
332- . all ( spotId ) ;
333-
334- const total = summaryRows . reduce ( ( sum , row ) => sum + row . total , 0 ) ;
379+ const summaryRows = state . orders . filter ( ( order ) => order . spot_id === spotId ) ;
380+
381+ const total = summaryRows . reduce ( ( sum , row ) => sum + row . total_amount , 0 ) ;
335382 const userTotals = summaryRows . reduce ( ( acc , row ) => {
336- acc [ row . user_id ] = row . total ;
383+ acc [ row . user_id ] = ( acc [ row . user_id ] || 0 ) + row . total_amount ;
337384 return acc ;
338385 } , { } ) ;
339386
340- const orderCount = db . prepare ( 'SELECT COUNT(*) AS count FROM orders WHERE spot_id = ?' ) . get ( spotId ) . count ;
341-
342387 return {
343388 spotId,
344389 total,
345390 userTotals,
346- orderCount,
391+ orderCount : summaryRows . length ,
347392 } ;
348393 } ,
349394} ;
0 commit comments