diff --git a/database.sql b/database.sql index 314bf41..a58e462 100644 --- a/database.sql +++ b/database.sql @@ -10,6 +10,7 @@ CREATE TABLE IF NOT EXISTS users ( email VARCHAR(160) NOT NULL UNIQUE, whatsapp VARCHAR(30) NOT NULL, password_hash VARCHAR(255) NOT NULL, + role ENUM('user', 'superadmin') NOT NULL DEFAULT 'user', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); diff --git a/server/server.js b/server/server.js index 7a6ce23..f937b28 100644 --- a/server/server.js +++ b/server/server.js @@ -58,7 +58,8 @@ function publicUser(user) { id: user.id, name: user.name, email: user.email, - whatsapp: user.whatsapp + whatsapp: user.whatsapp, + role: user.role || "user" } } @@ -116,7 +117,7 @@ app.post("/api/auth/register", async (req, res, next) => { [name, email.toLowerCase(), whatsapp, passwordHash] ) - const user = { id: result.insertId, name, email: email.toLowerCase(), whatsapp } + const user = { id: result.insertId, name, email: email.toLowerCase(), whatsapp, role: "user" } res.status(201).json({ token: signUser(user), user: publicUser(user) }) } catch (error) { if (error.code === "ER_DUP_ENTRY") { @@ -152,7 +153,7 @@ app.post("/api/auth/login", async (req, res, next) => { app.get("/api/auth/me", auth, async (req, res, next) => { try { - const [rows] = await pool.query("SELECT id, name, email, whatsapp FROM users WHERE id = ?", [req.user.id]) + const [rows] = await pool.query("SELECT id, name, email, whatsapp, role FROM users WHERE id = ?", [req.user.id]) if (!rows[0]) { res.status(404).json({ message: "User tidak ditemukan." }) return @@ -261,6 +262,28 @@ app.delete("/api/products/:id", auth, async (req, res, next) => { } }) +app.delete("/api/admin/products/:id", auth, superadminOnly, async (req, res, next) => { + try { + const product = await findProductById(req.params.id) + + if (!product) { + res.status(404).json({ message: "Produk tidak ditemukan." }) + return + } + + await pool.query("DELETE FROM products WHERE id = ?", [req.params.id]) + + if (product.image.startsWith("/uploads/")) { + fs.rm(path.join(uploadsDir, path.basename(product.image)), { force: true }, () => {}) + } + + res.json({ message: "Produk berhasil dihapus oleh superadmin." }) + } catch (error) { + next(error) + } +}) + + app.use((error, req, res, next) => { if (error instanceof multer.MulterError) { res.status(400).json({ message: "Upload gagal. Ukuran foto maksimal 3MB." }) @@ -279,3 +302,22 @@ app.use((error, req, res, next) => { app.listen(port, () => { console.log(`SecondTech API berjalan di http://localhost:${port}`) }) + + +async function superadminOnly(req, res, next) { + try { + const [rows] = await pool.query( + "SELECT role FROM users WHERE id = ?", + [req.user.id] + ) + + if (!rows[0] || rows[0].role !== "superadmin") { + res.status(403).json({ message: "Hanya superadmin yang boleh mengakses fitur ini." }) + return + } + + next() + } catch (error) { + next(error) + } +} diff --git a/server/uploads/1778558868134-709165590.jpg b/server/uploads/1778558868134-709165590.jpg deleted file mode 100644 index 831745a..0000000 Binary files a/server/uploads/1778558868134-709165590.jpg and /dev/null differ diff --git a/src/components/DashboardSidebar.vue b/src/components/DashboardSidebar.vue index 377bc3b..5820201 100644 --- a/src/components/DashboardSidebar.vue +++ b/src/components/DashboardSidebar.vue @@ -1,8 +1,25 @@