-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.ts
More file actions
367 lines (316 loc) · 13.3 KB
/
Copy pathserver.ts
File metadata and controls
367 lines (316 loc) · 13.3 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
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
import express from "express";
import { createServer as createViteServer } from "vite";
import path from "path";
import Database from "better-sqlite3";
const db = new Database("library.db");
// Initialize DB
db.exec(`
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
username TEXT UNIQUE,
password TEXT,
role TEXT,
name TEXT,
studentId TEXT
);
CREATE TABLE IF NOT EXISTS books (
id TEXT PRIMARY KEY,
title TEXT,
author TEXT,
isbn TEXT,
genre TEXT,
publicationDate TEXT,
publisher TEXT,
quantity INTEGER,
available INTEGER,
location TEXT,
status TEXT
);
CREATE TABLE IF NOT EXISTS members (
id TEXT PRIMARY KEY,
studentId TEXT UNIQUE,
name TEXT,
email TEXT,
department TEXT,
joinedDate TEXT,
status TEXT
);
CREATE TABLE IF NOT EXISTS transactions (
id TEXT PRIMARY KEY,
bookId TEXT,
memberId TEXT,
issueDate TEXT,
dueDate TEXT,
returnDate TEXT,
status TEXT,
fine REAL DEFAULT 0,
renewalRequested INTEGER DEFAULT 0,
FOREIGN KEY(bookId) REFERENCES books(id),
FOREIGN KEY(memberId) REFERENCES members(id)
);
CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT
);
CREATE TABLE IF NOT EXISTS fine_payments (
id TEXT PRIMARY KEY,
transactionId TEXT,
amount REAL,
paymentDate TEXT,
paymentMethod TEXT,
librarianId TEXT,
FOREIGN KEY(transactionId) REFERENCES transactions(id)
);
`);
// Seed initial data if empty
const userCount = db.prepare("SELECT count(*) as count FROM users").get() as { count: number };
if (userCount.count === 0) {
db.prepare("INSERT INTO users (id, username, password, role, name) VALUES (?, ?, ?, ?, ?)").run(
"1", "admin", "admin123", "LIBRARIAN", "Head Librarian"
);
db.prepare("INSERT INTO users (id, username, password, role, name) VALUES (?, ?, ?, ?, ?)").run(
"2", "staff", "staff123", "STAFF", "Library Assistant"
);
db.prepare("INSERT INTO users (id, username, password, role, name, studentId) VALUES (?, ?, ?, ?, ?, ?)").run(
"3", "student", "student123", "STUDENT", "John Doe", "S1001"
);
db.prepare("INSERT INTO members (id, studentId, name, email, department, joinedDate, status) VALUES (?, ?, ?, ?, ?, ?, ?)").run(
"m1", "S1001", "John Doe", "john@college.edu", "Computer Science", new Date().toISOString(), "ACTIVE"
);
// Seed some books
const books = [
["1", "The Great Gatsby", "F. Scott Fitzgerald", "9780743273565", "Fiction", "1925-04-10", "Scribner", 5, 5, "Shelf A1", "AVAILABLE"],
["2", "Introduction to Algorithms", "Cormen et al.", "9780262033848", "Computer Science", "2009-07-31", "MIT Press", 10, 8, "Shelf C4", "AVAILABLE"],
["3", "Clean Code", "Robert C. Martin", "9780132350884", "Software Engineering", "2008-08-01", "Prentice Hall", 3, 3, "Shelf C2", "AVAILABLE"]
];
const insertBook = db.prepare("INSERT INTO books (id, title, author, isbn, genre, publicationDate, publisher, quantity, available, location, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
books.forEach(b => insertBook.run(...b));
}
// Ensure fine rate exists
const fineRate = db.prepare("SELECT value FROM settings WHERE key = 'daily_fine_rate'").get() as any;
if (!fineRate) {
db.prepare("INSERT INTO settings (key, value) VALUES (?, ?)").run("daily_fine_rate", "5.00");
}
async function startServer() {
const app = express();
const PORT = 3000;
app.use(express.json());
// Auth API
app.post("/api/login", (req, res) => {
const { username, password } = req.body;
const user = db.prepare("SELECT id, username, role, name, studentId FROM users WHERE username = ? AND password = ?").get(username, password) as any;
if (user) {
res.json({ success: true, user });
} else {
res.status(401).json({ success: false, message: "Invalid credentials" });
}
});
// Books API
app.get("/api/books", (req, res) => {
const books = db.prepare("SELECT * FROM books").all();
res.json(books);
});
app.post("/api/books", (req, res) => {
const { id, title, author, isbn, genre, publicationDate, publisher, quantity, location } = req.body;
const bookId = id || Math.random().toString(36).substr(2, 9);
db.prepare("INSERT INTO books (id, title, author, isbn, genre, publicationDate, publisher, quantity, available, location, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
.run(bookId, title, author, isbn, genre, publicationDate, publisher, quantity, quantity, location, "AVAILABLE");
res.json({ success: true });
});
app.put("/api/books/:id", (req, res) => {
const { title, author, isbn, genre, publicationDate, publisher, quantity, available, location } = req.body;
db.prepare(`
UPDATE books
SET title = ?, author = ?, isbn = ?, genre = ?, publicationDate = ?, publisher = ?, quantity = ?, available = ?, location = ?
WHERE id = ?
`).run(title, author, isbn, genre, publicationDate, publisher, quantity, available ?? quantity, location, req.params.id);
res.json({ success: true });
});
app.delete("/api/books/:id", (req, res) => {
db.prepare("DELETE FROM books WHERE id = ?").run(req.params.id);
res.json({ success: true });
});
// Staff Management API
app.get("/api/staff", (req, res) => {
const staff = db.prepare("SELECT id, username, role, name FROM users WHERE role = 'STAFF'").all();
res.json(staff);
});
app.post("/api/staff", (req, res) => {
const { username, password, name } = req.body;
const id = Math.random().toString(36).substr(2, 9);
try {
db.prepare("INSERT INTO users (id, username, password, role, name) VALUES (?, ?, ?, ?, ?)")
.run(id, username, password, "STAFF", name);
res.json({ success: true });
} catch (err) {
res.status(400).json({ success: false, message: "Username already exists" });
}
});
app.delete("/api/staff/:id", (req, res) => {
db.prepare("DELETE FROM users WHERE id = ? AND role = 'STAFF'").run(req.params.id);
res.json({ success: true });
});
// Members API
app.get("/api/members", (req, res) => {
const members = db.prepare("SELECT * FROM members").all();
res.json(members);
});
app.post("/api/members", (req, res) => {
const { studentId, name, email, department } = req.body;
const id = Math.random().toString(36).substr(2, 9);
db.prepare("INSERT INTO members (id, studentId, name, email, department, joinedDate, status) VALUES (?, ?, ?, ?, ?, ?, ?)")
.run(id, studentId, name, email, department, new Date().toISOString(), "ACTIVE");
res.json({ success: true });
});
// Transactions API
app.get("/api/transactions", (req, res) => {
const { studentId } = req.query;
let query = `
SELECT t.*, b.title as bookTitle, m.name as memberName
FROM transactions t
JOIN books b ON t.bookId = b.id
JOIN members m ON t.memberId = m.id
`;
let params = [];
if (studentId) {
query += " WHERE m.studentId = ?";
params.push(studentId);
}
const transactions = db.prepare(query).all(...params);
res.json(transactions);
});
app.post("/api/issue", (req, res) => {
const { bookId, studentId, days } = req.body;
// Case-insensitive search for studentId
const member = db.prepare("SELECT id FROM members WHERE LOWER(studentId) = LOWER(?)").get(studentId) as any;
if (!member) return res.status(404).json({ success: false, message: "Member not found. Please check the Student ID." });
const book = db.prepare("SELECT available FROM books WHERE id = ?").get(bookId) as any;
if (book && book.available > 0) {
const id = Math.random().toString(36).substr(2, 9);
const issueDate = new Date();
const dueDate = new Date();
dueDate.setDate(issueDate.getDate() + (days || 14));
db.prepare("INSERT INTO transactions (id, bookId, memberId, issueDate, dueDate, status, fine) VALUES (?, ?, ?, ?, ?, ?, ?)")
.run(id, bookId, member.id, issueDate.toISOString(), dueDate.toISOString(), "ISSUED", 0);
db.prepare("UPDATE books SET available = available - 1, status = CASE WHEN available - 1 = 0 THEN 'BORROWED' ELSE 'AVAILABLE' END WHERE id = ?").run(bookId);
res.json({ success: true });
} else {
res.status(400).json({ success: false, message: "Book not available" });
}
});
app.post("/api/return", (req, res) => {
const { transactionId } = req.body;
const trans = db.prepare("SELECT bookId FROM transactions WHERE id = ?").get(transactionId) as any;
if (trans) {
db.prepare("UPDATE transactions SET status = 'RETURNED', returnDate = ? WHERE id = ?")
.run(new Date().toISOString(), transactionId);
db.prepare("UPDATE books SET available = available + 1, status = 'AVAILABLE' WHERE id = ?").run(trans.bookId);
res.json({ success: true });
} else {
res.status(404).json({ success: false });
}
});
app.post("/api/renew-request", (req, res) => {
const { transactionId } = req.body;
db.prepare("UPDATE transactions SET renewalRequested = 1 WHERE id = ?").run(transactionId);
res.json({ success: true });
});
app.post("/api/renew-approve", (req, res) => {
const { transactionId } = req.body;
const trans = db.prepare("SELECT dueDate FROM transactions WHERE id = ?").get(transactionId) as any;
if (trans) {
const newDueDate = new Date(trans.dueDate);
newDueDate.setDate(newDueDate.getDate() + 7);
db.prepare("UPDATE transactions SET dueDate = ?, renewalRequested = 0 WHERE id = ?")
.run(newDueDate.toISOString(), transactionId);
res.json({ success: true });
} else {
res.status(404).json({ success: false });
}
});
// Background task to flag overdue and calculate fines
const calculateFines = () => {
const now = new Date();
const nowIso = now.toISOString();
// Get daily rate
const rateSetting = db.prepare("SELECT value FROM settings WHERE key = 'daily_fine_rate'").get() as any;
const dailyRate = parseFloat(rateSetting?.value || "5.00");
// Update status to OVERDUE
db.prepare("UPDATE transactions SET status = 'OVERDUE' WHERE status = 'ISSUED' AND dueDate < ?").run(nowIso);
// Calculate fines for all transactions that are OVERDUE or ISSUED but past due
const overdueTrans = db.prepare("SELECT id, dueDate, returnDate, status, fine FROM transactions WHERE status IN ('OVERDUE', 'ISSUED') AND dueDate < ?").all(nowIso) as any[];
for (const trans of overdueTrans) {
const dueDate = new Date(trans.dueDate);
const endDate = trans.returnDate ? new Date(trans.returnDate) : now;
const diffTime = Math.max(0, endDate.getTime() - dueDate.getTime());
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
const calculatedFine = diffDays * dailyRate;
if (calculatedFine > trans.fine) {
db.prepare("UPDATE transactions SET fine = ? WHERE id = ?").run(calculatedFine, trans.id);
}
}
};
app.get("/api/check-overdue", (req, res) => {
calculateFines();
res.json({ success: true });
});
// Fine Management API
app.get("/api/fines/stats", (req, res) => {
const totalFines = db.prepare("SELECT SUM(fine) as total FROM transactions").get() as any;
const collectedFines = db.prepare("SELECT SUM(amount) as total FROM fine_payments").get() as any;
res.json({
total: totalFines.total || 0,
collected: collectedFines.total || 0,
outstanding: (totalFines.total || 0) - (collectedFines.total || 0)
});
});
app.post("/api/fines/pay", (req, res) => {
const { transactionId, amount, paymentMethod, librarianId } = req.body;
const id = Math.random().toString(36).substr(2, 9);
db.prepare("INSERT INTO fine_payments (id, transactionId, amount, paymentDate, paymentMethod, librarianId) VALUES (?, ?, ?, ?, ?, ?)")
.run(id, transactionId, amount, new Date().toISOString(), paymentMethod, librarianId);
res.json({ success: true, paymentId: id });
});
app.get("/api/fines/receipt/:paymentId", (req, res) => {
const receipt = db.prepare(`
SELECT p.*, t.bookId, b.title as bookTitle, m.name as memberName, m.studentId
FROM fine_payments p
JOIN transactions t ON p.transactionId = t.id
JOIN books b ON t.bookId = b.id
JOIN members m ON t.memberId = m.id
WHERE p.id = ?
`).get(req.params.paymentId);
res.json(receipt);
});
app.get("/api/settings", (req, res) => {
const settings = db.prepare("SELECT * FROM settings").all();
res.json(settings);
});
app.post("/api/settings", (req, res) => {
const { key, value } = req.body;
db.prepare("INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)").run(key, value);
res.json({ success: true });
});
// Vite middleware for development
if (process.env.NODE_ENV !== "production") {
const vite = await createViteServer({
server: { middlewareMode: true },
appType: "spa",
});
app.use(vite.middlewares);
} else {
app.use(express.static(path.join(__dirname, "dist")));
app.get("*", (req, res) => {
res.sendFile(path.join(__dirname, "dist", "index.html"));
});
}
app.listen(PORT, "0.0.0.0", () => {
console.log(`Server running on http://localhost:${PORT}`);
// Check for overdue items and calculate fines every hour
setInterval(() => {
calculateFines();
console.log("Overdue items and fines check completed.");
}, 3600000);
});
}
startServer();