project-startup 1.1.1 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/cli.js CHANGED
@@ -1,30 +1,80 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const path = require("path")
4
- const prompts = require('prompts');
5
- const fs = require('fs-extra');
3
+ const path = require("path");
4
+ const prompts = require("prompts");
5
+ const fs = require("fs-extra");
6
6
 
7
7
  async function main() {
8
- const response = await prompts({
9
- type: "text",
10
- name: "projectName",
11
- message: "Project name",
12
- initial: "project-startup"
13
- });
8
+ const response = await prompts([
9
+ {
10
+ type: "text",
11
+ name: "projectName",
12
+ message: "Project name",
13
+ initial: "project-startup"
14
+ },
15
+ {
16
+ type: "multiselect",
17
+ name: "logics",
18
+ message: "Which logic folders do you want to install?",
19
+ choices: [
20
+ { title: "Booking", value: "Booking" },
21
+ { title: "RBAC", value: "rbac" },
22
+ { title: "Stocks", value: "Stocks" },
23
+ { title: "All", value: "all" }
24
+ ],
25
+ hint: "Use space to select, enter to confirm"
26
+ }
27
+ ]);
14
28
 
15
29
  const projectName = response.projectName?.trim();
16
30
  if (!projectName) process.exit(1);
17
31
 
18
32
  const targetDir = path.resolve(process.cwd(), projectName);
19
33
  const templateDir = path.resolve(__dirname, "../template");
34
+ const logicsDir = path.join(templateDir, "Logics");
20
35
 
21
36
  if (await fs.pathExists(targetDir)) {
22
37
  console.error(`Folder already exists: ${projectName}`);
23
38
  process.exit(1);
24
39
  }
25
40
 
26
- await fs.copy(templateDir, targetDir);
41
+ await fs.copy(templateDir, targetDir, {
42
+ filter: (src) => {
43
+ const rel = path.relative(templateDir, src);
44
+ if (!rel) return true;
45
+
46
+ if (!response.logics || response.logics.length === 0) return !rel.startsWith("Logics");
47
+
48
+ if (response.logics.includes("all")) return true;
49
+
50
+ if (rel.startsWith("Logics")) {
51
+ const parts = rel.split(path.sep);
52
+ if (parts.length >= 2) {
53
+ const folder = parts[1];
54
+ return response.logics.includes(folder);
55
+ }
56
+ }
57
+
58
+ return true;
59
+ }
60
+ });
61
+
62
+ if (!response.logics.includes("all")) {
63
+ const selected = response.logics || [];
64
+ for (const item of ["Booking", "rbac", "Stocks"]) {
65
+ if (!selected.includes(item)) {
66
+ await fs.remove(path.join(targetDir, "Logics", item));
67
+ }
68
+ }
69
+ const logicPath = path.join(targetDir, "Logics");
70
+ const remaining = await fs.readdir(logicPath);
71
+ if (remaining.length === 0) {
72
+ await fs.remove(logicPath);
73
+ }
74
+ }
75
+
27
76
  console.log(`Created ${projectName}`);
77
+ console.log(`Selected logics: ${response.logics?.join(", ") || "none"}`);
28
78
  console.log(`Next: cd ${projectName} && npm install && npm run dev`);
29
79
  }
30
80
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-startup",
3
- "version": "1.1.1",
3
+ "version": "1.2.1",
4
4
  "description": "Minimal session-based auth starter using Express, MySQL, React, Vite, and React Router.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -0,0 +1,52 @@
1
+ const bcrypt = require("bcrypt");
2
+ const db = require("../db");
3
+
4
+ exports.login = async (req, res) => {
5
+ const { email, password } = req.body;
6
+
7
+ if (!email || !password) {
8
+ return res.status(400).json({ error: "Email and password are required." });
9
+ }
10
+
11
+ const [rows] = await db.query(
12
+ "SELECT * FROM users WHERE email = ?", [email]
13
+ );
14
+
15
+ if (rows.length === 0) {
16
+ return res.status(401).json({ error: "Invalid email or password." });
17
+ }
18
+
19
+ const user = rows[0];
20
+ const match = await bcrypt.compare(password, user.password);
21
+
22
+ if (!match) {
23
+ return res.status(401).json({ error: "Invalid email or password." });
24
+ }
25
+
26
+ // Store only the safe user fields in the session — never the password hash
27
+ req.session.user = {
28
+ id: user.id,
29
+ name: user.name,
30
+ email: user.email,
31
+ role: user.role,
32
+ };
33
+
34
+ // express-session saves the session and sends the Set-Cookie header
35
+ // automatically — we just respond with the user object for the frontend
36
+ res.json({ user: req.session.user });
37
+ };
38
+
39
+ exports.logout = (req, res) => {
40
+ // Destroys the session row in MySQL and clears the cookie
41
+ req.session.destroy(err => {
42
+ if (err) {
43
+ return res.status(500).json({ error: "Could not log out." });
44
+ }
45
+ res.clearCookie("connect.sid");
46
+ res.json({ message: "Logged out." });
47
+ });
48
+ };
49
+
50
+ exports.me = (req, res) => {
51
+ res.json(req.user);
52
+ };
@@ -0,0 +1,38 @@
1
+ // These are demo endpoints that exist purely to show RBAC in action.
2
+ // In a real app you'd replace these with your actual business routes.
3
+
4
+ // GET /api/demo/customer — any logged-in user
5
+ exports.customerArea = (req, res) => {
6
+ res.json({
7
+ message: `Hello ${req.user.name}! You reached the customer area.`,
8
+ access: "all authenticated users",
9
+ you: req.user,
10
+ });
11
+ };
12
+
13
+ // GET /api/demo/manager — manager or admin only
14
+ exports.managerArea = (req, res) => {
15
+ res.json({
16
+ message: `Hello ${req.user.name}! You reached the manager area.`,
17
+ access: "manager + admin",
18
+ you: req.user,
19
+ });
20
+ };
21
+
22
+ // GET /api/demo/admin — admin only
23
+ exports.adminArea = (req, res) => {
24
+ res.json({
25
+ message: `Hello ${req.user.name}! You reached the admin area.`,
26
+ access: "admin only",
27
+ you: req.user,
28
+ });
29
+ };
30
+
31
+ // GET /api/demo/users — admin only: list all users
32
+ exports.listUsers = async (req, res) => {
33
+ const db = require("../db");
34
+ const [rows] = await db.query(
35
+ "SELECT id, name, email, role, created_at FROM users ORDER BY id"
36
+ );
37
+ res.json(rows);
38
+ };
@@ -0,0 +1,12 @@
1
+ const mysql = require("mysql2/promise");
2
+
3
+ const pool = mysql.createPool({
4
+ host: process.env.DB_HOST || "localhost",
5
+ user: process.env.DB_USER || "root",
6
+ password: process.env.DB_PASSWORD || "root",
7
+ database: process.env.DB_NAME || "rbac_db",
8
+ waitForConnections: true,
9
+ connectionLimit: 10,
10
+ });
11
+
12
+ module.exports = pool;
@@ -0,0 +1,28 @@
1
+ // With express-session there is no token to look up.
2
+ // The session middleware already parsed the cookie and populated req.session
3
+ // before this function runs — we just check what's in it.
4
+
5
+ function requireAuth(req, res, next) {
6
+ if (!req.session.user) {
7
+ return res.status(401).json({ error: "Not authenticated. Please log in." });
8
+ }
9
+ // Attach user to req so controllers can use req.user (same API as before)
10
+ req.user = req.session.user;
11
+ next();
12
+ }
13
+
14
+ // Role guard — unchanged, still works exactly the same way
15
+ function requireRole(...roles) {
16
+ return (req, res, next) => {
17
+ if (!roles.includes(req.user.role)) {
18
+ return res.status(403).json({
19
+ error: "Access denied.",
20
+ yourRole: req.user.role,
21
+ required: roles,
22
+ });
23
+ }
24
+ next();
25
+ };
26
+ }
27
+
28
+ module.exports = { requireAuth, requireRole };
@@ -0,0 +1,10 @@
1
+ const express = require("express");
2
+ const router = express.Router();
3
+ const authController = require("../controllers/authController");
4
+ const { requireAuth } = require("../middleware/authMiddleware");
5
+
6
+ router.post("/login", authController.login);
7
+ router.post("/logout", requireAuth, authController.logout);
8
+ router.get("/me", requireAuth, authController.me);
9
+
10
+ module.exports = router;
@@ -0,0 +1,37 @@
1
+ const express = require("express");
2
+ const router = express.Router();
3
+ const demoController = require("../controllers/demoController");
4
+ const { requireAuth, requireRole } = require("../middleware/authMiddleware");
5
+
6
+ // Any logged-in user
7
+ router.get(
8
+ "/customer",
9
+ requireAuth,
10
+ demoController.customerArea
11
+ );
12
+
13
+ // Manager or Admin
14
+ router.get(
15
+ "/manager",
16
+ requireAuth,
17
+ requireRole("manager", "admin"),
18
+ demoController.managerArea
19
+ );
20
+
21
+ // Admin only
22
+ router.get(
23
+ "/admin",
24
+ requireAuth,
25
+ requireRole("admin"),
26
+ demoController.adminArea
27
+ );
28
+
29
+ // Admin only — lists all users
30
+ router.get(
31
+ "/users",
32
+ requireAuth,
33
+ requireRole("admin"),
34
+ demoController.listUsers
35
+ );
36
+
37
+ module.exports = router;