-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinit.js
More file actions
255 lines (246 loc) · 9.44 KB
/
init.js
File metadata and controls
255 lines (246 loc) · 9.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
import sqlite3 from 'sqlite3';
const runAsync = (db, sql, params = []) => {
return new Promise((resolve, reject) => {
db.run(sql, params, function(err) {
if (err) reject(err);
else resolve(this);
});
});
};
export function initDatabase(dbPath) {
return new Promise((resolve, reject) => {
const db = new sqlite3.Database(dbPath, (err) => {
if (err) {
console.error('Error opening database:', err);
reject(err);
return;
}
console.log('Initializing database...');
});
// Enable foreign keys
db.run('PRAGMA foreign_keys = ON');
// Create users table first
runAsync(db, `
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
email TEXT,
is_super_admin INTEGER DEFAULT 0,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
)
`)
.then(() => {
// Create tables sequentially
return runAsync(db, `
CREATE TABLE IF NOT EXISTS services (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
service_name TEXT NOT NULL,
type TEXT NOT NULL,
price REAL NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
UNIQUE(user_id, service_name)
)
`);
})
.then(() => runAsync(db, `
CREATE TABLE IF NOT EXISTS address_data (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
location_name TEXT NOT NULL,
address TEXT,
city_town TEXT,
post_code TEXT,
distance REAL,
contact_name TEXT,
email_address TEXT,
contact_details TEXT,
phone TEXT,
place_via_ludham TEXT,
mileage REAL,
notes TEXT,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
UNIQUE(user_id, location_name)
)
`))
.then(() => runAsync(db, `
CREATE TABLE IF NOT EXISTS appointments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
client_name TEXT NOT NULL,
service TEXT NOT NULL,
type TEXT NOT NULL,
date TEXT NOT NULL,
location TEXT NOT NULL,
price REAL NOT NULL,
paid INTEGER DEFAULT 0,
distance REAL,
payment_date TEXT,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (user_id, location) REFERENCES address_data(user_id, location_name)
)
`))
.then(() => {
// Create indexes for appointments table to improve query performance
return Promise.all([
runAsync(db, 'CREATE INDEX IF NOT EXISTS idx_appointments_user_id ON appointments(user_id)'),
runAsync(db, 'CREATE INDEX IF NOT EXISTS idx_appointments_date ON appointments(date)'),
runAsync(db, 'CREATE INDEX IF NOT EXISTS idx_appointments_user_date_id ON appointments(user_id, date DESC, id ASC)')
]);
})
.then(() => runAsync(db, `
CREATE TABLE IF NOT EXISTS email_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
invoice_number TEXT,
recipient_email TEXT NOT NULL,
subject TEXT,
status TEXT DEFAULT 'pending',
sendgrid_message_id TEXT,
sendgrid_event_id TEXT,
error_message TEXT,
pdf_file_path TEXT,
webhook_event_data TEXT,
sent_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
)
`))
.then(() => {
// Create indexes for email_logs table
return Promise.all([
runAsync(db, 'CREATE INDEX IF NOT EXISTS idx_email_logs_user_id ON email_logs(user_id)'),
runAsync(db, 'CREATE INDEX IF NOT EXISTS idx_email_logs_sendgrid_message_id ON email_logs(sendgrid_message_id)'),
runAsync(db, 'CREATE INDEX IF NOT EXISTS idx_email_logs_status ON email_logs(status)'),
runAsync(db, 'CREATE INDEX IF NOT EXISTS idx_email_logs_sent_at ON email_logs(sent_at)')
]);
})
.then(() => runAsync(db, `
CREATE TABLE IF NOT EXISTS webhook_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email_log_id INTEGER,
user_id INTEGER NOT NULL,
event_type TEXT NOT NULL,
sendgrid_message_id TEXT,
sendgrid_event_id TEXT,
raw_event_data TEXT NOT NULL,
processed_at TEXT DEFAULT CURRENT_TIMESTAMP,
event_timestamp INTEGER,
FOREIGN KEY (email_log_id) REFERENCES email_logs(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
)
`))
.then(() => {
return Promise.all([
runAsync(db, 'CREATE INDEX IF NOT EXISTS idx_webhook_events_email_log_id ON webhook_events(email_log_id)'),
runAsync(db, 'CREATE INDEX IF NOT EXISTS idx_webhook_events_sendgrid_message_id ON webhook_events(sendgrid_message_id)'),
runAsync(db, 'CREATE INDEX IF NOT EXISTS idx_webhook_events_processed_at ON webhook_events(processed_at)')
]);
})
.then(() => runAsync(db, `
CREATE TABLE IF NOT EXISTS admin_settings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL UNIQUE,
name TEXT,
phone TEXT,
email TEXT,
business_name TEXT,
bank_account_name TEXT,
sort_code TEXT,
account_number TEXT,
home_address TEXT,
home_postcode TEXT,
currency TEXT DEFAULT 'GBP',
postcode_resync_needed INTEGER DEFAULT 0,
google_maps_api_key TEXT,
email_password TEXT,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
)
`))
.then(() => {
// Insert default services if they don't exist
const defaultServices = [
{ service_name: 'Blow Dry', type: 'Hair', price: 15.00 },
{ service_name: 'Shampoo & Set', type: 'Hair', price: 14.00 },
{ service_name: 'Dry Cut', type: 'Hair', price: 14.00 },
{ service_name: 'Cut & Blow Dry', type: 'Hair', price: 25.00 },
{ service_name: 'Cut & Set', type: 'Hair', price: 24.00 },
{ service_name: 'Restyling', type: 'Hair', price: 30.00 },
{ service_name: 'Gents Dry Cut', type: 'Hair', price: 14.50 },
{ service_name: 'Clipper Cuts', type: 'Hair', price: 6.00 },
{ service_name: 'Beard Trim', type: 'Hair', price: 5.00 },
{ service_name: 'Child Cut', type: 'Hair', price: 10.00 },
{ service_name: 'Child Cut & Blow Dry', type: 'Hair', price: 18.00 },
{ service_name: 'Other', type: 'Hair', price: 0.00 },
{ service_name: 'File & Polish', type: 'Nails', price: 10.00 },
{ service_name: 'Manicure', type: 'Nails', price: 18.00 },
{ service_name: 'Gel Polish', type: 'Nails', price: 20.00 },
{ service_name: 'Removal', type: 'Nails', price: 6.00 },
{ service_name: 'Gel Removal & Re-Apply', type: 'Nails', price: 25.00 },
{ service_name: 'Pedicure', type: 'Nails', price: 20.00 },
{ service_name: 'Blow Dry & Fringe Trim', type: 'Hair', price: 17.00 },
{ service_name: 'Nails Cut & Filed', type: 'Nails', price: 6.00 },
{ service_name: 'Wash & Cut', type: 'Hair', price: 20.00 },
{ service_name: 'Colour', type: 'Hair', price: 60.00 },
{ service_name: 'Colour, cut & blow dry', type: 'Hair', price: 45.00 },
{ service_name: 'Hair wash', type: 'Hair', price: 5.00 }
];
// Note: Default services will be created per user on first login
// This function is kept for backward compatibility but won't insert without user_id
const insertService = (service) => {
// Skip default service insertion - services are now user-specific
return Promise.resolve();
};
// Don't insert default locations - let user import them
// This ensures IDs start from 1
const defaultLocations = [];
const insertLocation = (location) => {
return runAsync(
db,
`INSERT OR IGNORE INTO address_data
(location_name, address, city_town, post_code, distance, contact_name, email_address, contact_details, phone, place_via_ludham, mileage, notes)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[
location.location_name,
location.address || '',
location.city_town || '',
location.post_code || '',
location.distance || null,
location.contact_name || '',
location.email_address || '',
location.contact_details || '',
location.phone || '',
location.place_via_ludham || '',
location.mileage || null,
location.notes || ''
]
);
};
// Insert all default data
return Promise.all([
...defaultServices.map(insertService),
...defaultLocations.map(insertLocation)
]);
})
.then(() => {
console.log('Database initialized with default data');
db.close((err) => {
if (err) {
console.error('Error closing database:', err);
reject(err);
} else {
resolve();
}
});
})
.catch((err) => {
console.error('Error initializing database:', err);
db.close();
reject(err);
});
});
}