create-tinny-backend 1.0.0

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/index.js ADDED
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs-extra');
4
+ const path = require('path');
5
+ const { execSync } = require('child_process');
6
+ const prompts = require('prompts');
7
+ const { cyan, green, bold, yellow, red, gray } = require('kolorist');
8
+ const ora = require('ora');
9
+
10
+
11
+ const args = process.argv.slice(2);
12
+ const projectName = args[0] || 'myapp';
13
+ const targetDir = path.join(process.cwd(), projectName);
14
+
15
+
16
+ if (fs.existsSync(targetDir)) {
17
+ console.error(red(`✖ Directory "${projectName}" already exists.`));
18
+ process.exit(1);
19
+ }
20
+
21
+
22
+ (async function main() {
23
+ console.log(cyan('\nWelcome to create-tinny-backend\n'));
24
+
25
+ const response = await prompts([
26
+ {
27
+ type: 'text',
28
+ name: 'description',
29
+ message: 'Project description',
30
+ initial: 'A tinny-backend application'
31
+ },
32
+ {
33
+ type: 'text',
34
+ name: 'author',
35
+ message: 'Author name',
36
+ initial: 'Yassine Ajagrou'
37
+ }
38
+ ]);
39
+
40
+
41
+ const templateDir = path.join(__dirname, 'template');
42
+ if (!fs.existsSync(templateDir)) {
43
+ console.error(red(`✖ Template folder not found at ${templateDir}`));
44
+ process.exit(1);
45
+ }
46
+
47
+ const spinner = ora('Copying template files...').start();
48
+ try {
49
+ fs.copySync(templateDir, targetDir);
50
+ spinner.succeed(green('Template copied successfully'));
51
+ } catch (err) {
52
+ spinner.fail(red('Failed to copy template'));
53
+ console.error(err);
54
+ process.exit(1);
55
+ }
56
+
57
+
58
+ const pkgPath = path.join(targetDir, 'package.json');
59
+ if (fs.existsSync(pkgPath)) {
60
+ const pkg = fs.readJsonSync(pkgPath);
61
+ pkg.name = projectName;
62
+ pkg.description = response.description || pkg.description;
63
+ pkg.author = response.author || pkg.author;
64
+ fs.writeJsonSync(pkgPath, pkg, { spaces: 2 });
65
+ }
66
+
67
+ const installSpinner = ora('Installing dependencies (npm install)...').start();
68
+ try {
69
+ execSync(`cd "${targetDir}" && npm install`, { stdio: 'pipe' });
70
+ installSpinner.succeed(green('Dependencies installed'));
71
+ } catch (err) {
72
+ installSpinner.fail(red('npm install failed'));
73
+ console.error(err);
74
+ process.exit(1);
75
+ }
76
+
77
+ const buildSpinner = ora('Building project (npm run build)...').start();
78
+ try {
79
+ execSync(`cd "${targetDir}" && npm run build`, { stdio: 'pipe' });
80
+ buildSpinner.succeed(green('Build completed'));
81
+ } catch (err) {
82
+ buildSpinner.fail(red('Build failed'));
83
+ console.error(err);
84
+ process.exit(1);
85
+ }
86
+
87
+ console.log(cyan('\n▶ Starting the server (npm run start)...\n'));
88
+ console.log(gray('Press Ctrl+C to stop the server when done.\n'));
89
+
90
+ try {
91
+ execSync(`cd "${targetDir}" && npm run start`, { stdio: 'inherit' });
92
+ } catch (err) {
93
+ console.log(yellow('\nServer stopped.'));
94
+ process.exit(0);
95
+ }
96
+
97
+ console.log(green(`\n✅ Project "${bold(projectName)}" is ready!`));
98
+ console.log(cyan(` cd ${projectName}`));
99
+ console.log(cyan(' npm run dev # for development with hot-reload'));
100
+ console.log(cyan(' npm start # for production (already running)'));
101
+ console.log(gray('\n--------------------------------------------------'));
102
+ console.log(gray('Created by Yassine Ajagrou - https://github.com/iaceene'));
103
+ console.log(gray('--------------------------------------------------\n'));
104
+ })().catch((err) => {
105
+ console.error(red('Unexpected error:'), err);
106
+ process.exit(1);
107
+ });
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "create-tinny-backend",
3
+ "version": "1.0.0",
4
+ "description": "Scaffold a new tinny-backend project",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "create-tinny-backend": "./index.js"
8
+ },
9
+ "scripts": {
10
+ "test": "echo \"Error: no test specified\" && exit 1"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/iaceene/create-tinny-backend.git"
15
+ },
16
+ "keywords": [
17
+ "tinny-backend",
18
+ "scaffold",
19
+ "starter",
20
+ "backend"
21
+ ],
22
+ "author": "Yassine Ajagrou",
23
+ "license": "ISC",
24
+ "type": "commonjs",
25
+ "bugs": {
26
+ "url": "https://github.com/iaceene/create-tinny-backend/issues"
27
+ },
28
+ "homepage": "https://github.com/iaceene/create-tinny-backend#readme",
29
+ "dependencies": {
30
+ "fs-extra": "^11.2.0",
31
+ "prompts": "^2.4.2",
32
+ "kolorist": "^1.8.0",
33
+ "ora": "^8.0.1"
34
+ },
35
+ "engines": {
36
+ "node": ">=18.0.0"
37
+ }
38
+ }
@@ -0,0 +1,85 @@
1
+ import * as JWT from "jsonwebtoken"
2
+ import * as DOTENV from "dotenv"
3
+ import Server from "tinny-backend";
4
+ import type {
5
+ AdminSessions,
6
+ ServerReq,
7
+ ServerRes
8
+ } from "tinny-backend"
9
+
10
+ export type auth_t = {
11
+ Auth: (req: ServerReq, res: ServerRes) => Promise<void>;
12
+ sessions: AdminSessions[];
13
+ key: string;
14
+ SetUsername: (userName: string) => void;
15
+ SetPassword: (passWord: string) => void;
16
+ GetPasswd: () => string;
17
+ GetUser: () => string
18
+ }
19
+
20
+ export default function Authen(server: Server): auth_t{
21
+ DOTENV.config()
22
+
23
+ let username: string = process.env.ADMIN_USERNAME || ""
24
+ let password: string = process.env.ADMIN_PASSWORD || ""
25
+ const key: string = process.env.ADMIN_KEY || ""
26
+ const sessions: AdminSessions[] = []
27
+
28
+
29
+ const isAuthed = (res: ServerRes): boolean =>{
30
+ const TOKEN = res.getCookie("token")
31
+ if (!TOKEN)
32
+ return false
33
+ try {
34
+ const decoded: any = JWT.default.verify(TOKEN.value, key);
35
+
36
+ if (typeof decoded.id === undefined)
37
+ throw new Error("None valid token");
38
+ for (let i = 0; i < sessions.length; i++){
39
+ if (sessions[i]?.id === decoded.id){
40
+ if (!sessions[i]?.isValid)
41
+ throw new Error("None valid token");
42
+ (sessions[i] as AdminSessions).request += 1;
43
+ (res as any).currentID = decoded.id;
44
+ return true
45
+ }
46
+ }
47
+ throw new Error("None valid UUID");
48
+ } catch {
49
+ server.log("a user try to login to admin panel using none valid token", "error")
50
+ return false
51
+ }
52
+ return true
53
+ }
54
+
55
+ const Auth = async (req: ServerReq, res: ServerRes)=>{
56
+ if (!isAuthed(res)){
57
+ if (req.ReqUrl?.pathname == "/")
58
+ return await server.SendFile(res, "public/login/index.html", 401)
59
+ return await server.SendFile(res, "public/status/401.html", 401)
60
+ }
61
+
62
+ if (req.ReqUrl?.pathname == "/")
63
+ return await server.SendFile(res, "public/admin/index.html", 200)
64
+ }
65
+
66
+ const SetUsername = (userName: string)=>{
67
+ console.log("old pwd ", username)
68
+ username = userName
69
+ console.log("new pwd ", username)
70
+ }
71
+
72
+ const SetPassword = (passWord: string)=>{
73
+ password = passWord
74
+ }
75
+
76
+ const GetPasswd = ()=>{
77
+ return password
78
+ }
79
+
80
+ const GetUser = ()=>{
81
+ return username
82
+ }
83
+
84
+ return {Auth, sessions, key, SetUsername, SetPassword, GetPasswd, GetUser}
85
+ }
@@ -0,0 +1,102 @@
1
+ import Server from "tinny-backend";
2
+ import type {
3
+ ServerReq,
4
+ ServerRes
5
+ } from "tinny-backend"
6
+ import Authen from "./auth.js";
7
+ import Routes from "./routes.js";
8
+
9
+ export default function monitor(server: Server){
10
+ const {Auth, sessions, key, SetUsername, SetPassword, GetPasswd, GetUser} = Authen(server)
11
+ const { ChangeUser, LogoutSession, ChangePasswd, ChangePasswdAuth, CheckUsername, Status, Logout, Login } = Routes(server, {Auth, sessions, key, SetUsername, SetPassword, GetPasswd, GetUser})
12
+
13
+
14
+ server.add({
15
+ method: "POST",
16
+ handler: Login,
17
+ path: "/api/login"
18
+ })
19
+
20
+ server.add({
21
+ method: "GET",
22
+ middelWares: [Auth],
23
+ handler: async (req, res)=>{
24
+ return await server.SendFile(res, "public/login/index.html", 200)
25
+ },
26
+ path: "/"
27
+ })
28
+
29
+ server.add({
30
+ method: "GET",
31
+ middelWares: [Auth],
32
+ handler: Logout,
33
+ path: "/api/logout"
34
+ })
35
+
36
+ server.add({
37
+ method: "GET",
38
+ middelWares: [Auth],
39
+ path: "/admin/status",
40
+ handler: Status
41
+ })
42
+
43
+ server.add({
44
+ method: "GET",
45
+ middelWares: [Auth],
46
+ path: "/messages",
47
+ handler: async (req: ServerReq, res: ServerRes) => {
48
+ res.send(200, {
49
+ "logs" : server.getLogs()
50
+ })
51
+ }
52
+ })
53
+
54
+ server.add({
55
+ method: "GET",
56
+ path: "/passwd",
57
+ handler: async (req: ServerReq, res: ServerRes) => {
58
+ return await server.SendFile(res, "./public/login/new-password.html", 200)
59
+ }
60
+ })
61
+
62
+ server.add({
63
+ method: "GET",
64
+ path: "/api/passwd/:username",
65
+ handler: CheckUsername
66
+ })
67
+
68
+ server.add({
69
+ method: "POST",
70
+ path: "/api/passwd/change",
71
+ handler: ChangePasswd
72
+ })
73
+
74
+ server.add({
75
+ method: "POST",
76
+ middelWares: [Auth],
77
+ path: "/api/logout/:SessionId",
78
+ handler: LogoutSession
79
+ })
80
+
81
+ server.add({
82
+ method: "POST",
83
+ middelWares: [Auth],
84
+ path: "/passwd/change/auth",
85
+ handler: ChangePasswdAuth
86
+ })
87
+
88
+ server.add({
89
+ method: "POST",
90
+ middelWares: [Auth],
91
+ path: "/api/user/change",
92
+ handler: ChangeUser
93
+ })
94
+
95
+ server.servDir("./public/doc", "docs")
96
+ server.servDir("./public/imgs", "imgs")
97
+ server.servDir("./public/login", "/", [Auth])
98
+ server.servDir("./public/admin", "admin", [Auth])
99
+
100
+
101
+ server.listen(false)
102
+ }
@@ -0,0 +1,192 @@
1
+ import * as os from "node:os";
2
+ import * as JWT from "jsonwebtoken"
3
+ import Server from "tinny-backend";
4
+ import type {
5
+ AdminSessions,
6
+ ServerReq,
7
+ ServerRes
8
+ } from "tinny-backend"
9
+
10
+ import { type auth_t} from "./auth.js";
11
+
12
+
13
+
14
+ export default function Routes(server: Server, obj: auth_t){
15
+
16
+ const {sessions, key, SetUsername, SetPassword, GetPasswd, GetUser} = obj
17
+
18
+
19
+ const Login = async (req: ServerReq, res: ServerRes) => {
20
+ let token
21
+
22
+ try {
23
+ if (!req.body.username || req.body.username !== GetUser() || !req.body.password || req.body.password !== GetPasswd())
24
+ throw new Error("User has entred a none valid cridentials")
25
+ const SESSION_ID = crypto.randomUUID()
26
+ token = JWT.default.sign({ id: SESSION_ID }, key, { expiresIn: "1h" });
27
+ res.addCookie("token", token)
28
+ sessions.push({
29
+ id: SESSION_ID,
30
+ isValid: true,
31
+ creation: Date.now(),
32
+ userAgent: req.headers["user-agent"] ?? "DEVICE",
33
+ ip: (req as ServerReq).ip ?? "UNKNOWN",
34
+ request: 0
35
+ })
36
+ res.send(200, "./public/admin/index.html", { "set-cookie" : `token=${token}; Path=/; SameSite=Lax; HttpOnly` });
37
+ }
38
+ catch(err: any) {
39
+ res.server.log(err.message, "error")
40
+ res.server.log("A user entred a none valid cridentionl", "error")
41
+ res.send(403, {"Error" : "Access Denied"})
42
+ }
43
+ }
44
+
45
+ const Logout = async (req: ServerReq, res: ServerRes) => {
46
+ let payloud: any
47
+
48
+
49
+ const TOKEN = res.getCookie("token")
50
+ if (!TOKEN)
51
+ return server.SendFile(res, "public/login/index.html", 401)
52
+
53
+ try {
54
+ payloud = JWT.default.verify(TOKEN.value, key);
55
+ if (typeof payloud.id === "undefined")
56
+ throw new Error("an Error is happens");
57
+ for (let i = 0; i < sessions.length; i++){
58
+ if (sessions[i]?.id === payloud.id){
59
+ if (!sessions[i]?.isValid)
60
+ throw new Error("None valid token");
61
+ (sessions[i] as AdminSessions).request += 1;
62
+ (sessions[i] as AdminSessions).isValid = false;
63
+ break;
64
+ }
65
+ }
66
+ return await server.SendFile(res, "public/login/index.html", 200)
67
+ }
68
+ catch(err: any) {
69
+ server.log(err.message, "error")
70
+ res.send(403, {"Error" : "Access Denied"})
71
+ }
72
+ }
73
+
74
+ const Status = async (req: ServerReq, res: ServerRes) => {
75
+ const now: number = Date.now()
76
+ res.send(200, {
77
+ "Machin uptime": `${Math.floor(os.uptime() / 86400)}d ${Math.floor((os.uptime() % 86400) / 3600)}h ${Math.floor((os.uptime() % 3600) / 60)}m`,
78
+ "Server uptime": `${Math.floor((now - server.getUpTime()) / (1000 * 60 * 60 * 24)) % 60}D ${Math.floor((now - server.getUpTime()) / (1000 * 60 * 60)) % 60}H ${Math.floor((now - server.getUpTime()) / (1000 * 60)) % 60}M ${Math.floor((now - server.getUpTime()) / 1000) % 60}S`,
79
+ "Arch" : os.arch(),
80
+ "Platform" : os.platform(),
81
+ "Memory" : `${(os.totalmem() / (1024 ** 3)).toFixed(2)} GB`,
82
+ "Used Memory" : `${((os.totalmem() - os.freemem()) / (1024 ** 3)).toFixed(2)} GB`,
83
+ "Free Memory" : `${(os.freemem() / (1024 ** 3)).toFixed(2)} GB`,
84
+ "Cpu" : os.cpus()[0],
85
+ "Connected clients" : "0",
86
+ "Total requests" : server.getReqCount(),
87
+ "Currnet-session": (res as any).currentID,
88
+ "sessions": sessions,
89
+ "routes" : server.getMethodHandlers(),
90
+ "logs" : server.getLogs()
91
+ })
92
+ }
93
+
94
+ const CheckUsername = async (req: ServerReq, res: ServerRes) => {
95
+ let userName = req.params.username
96
+
97
+ if (typeof userName === undefined)
98
+ return res.send(404, {"user": userName, status: "not found"})
99
+
100
+ if (userName === GetUser())
101
+ return res.send(200, {"user": userName, status: "found"})
102
+
103
+ return res.send(404, {"user": userName, status: "not found"})
104
+ }
105
+
106
+ const ChangePasswd = async (req: ServerReq, res: ServerRes) => {
107
+ try {
108
+
109
+ const { userName, oldpwd, newpwd } = req.body;
110
+
111
+ if (!userName || !oldpwd || !newpwd)
112
+ return res.send(400, { "user": userName || 'unknown', status: 'missing fields', message: 'Username, old password, and new password are required' });
113
+
114
+ if (userName !== GetUser())
115
+ return res.send(404, {"user": userName, status: 'this username not found', message: `${userName} not found` });
116
+
117
+ if (oldpwd !== GetPasswd())
118
+ return res.send(401, {"user": userName, status: 'password not corect', message: `Password not corect for ${userName}` });
119
+
120
+ if (newpwd.length < 8)
121
+ return res.send(400, { "user": userName, status: 'password must be 8 char +' });
122
+
123
+ if (oldpwd === newpwd || newpwd === GetPasswd())
124
+ return res.send(400, {"user": userName, status: 'new password must be different', message: 'New password must be different from current password' });
125
+ SetPassword(newpwd)
126
+ return res.send(200, { "user": userName, status: 'password changed', message: 'Password updated successfully'})
127
+
128
+ } catch(error: any) {
129
+ server.log(`Password change error: ${error}`, "error");
130
+ return res.send(500, { "user": req.body?.username || 'unknown', status: 'error', message: 'Internal server error'});
131
+ }
132
+ }
133
+
134
+ const LogoutSession = async (req: ServerReq, res: ServerRes) => {
135
+ let SessionID = req.params.SessionId
136
+
137
+ if (typeof SessionID === undefined)
138
+ return res.send(404, {status: "not found"})
139
+
140
+ for (let i = 0; i < sessions.length; i++){
141
+ if (SessionID === sessions[i]?.id){
142
+ (sessions[i] as any).isValid = false;
143
+ return res.send(200, {status: `session ${SessionID} logouted`})
144
+ }
145
+ }
146
+ return res.send(404, {status: "not found"})
147
+ }
148
+
149
+ const ChangePasswdAuth = async (req: ServerReq, res: ServerRes) => {
150
+ try {
151
+ const { oldpwd, newpwd } = req.body
152
+ if (!oldpwd || !newpwd)
153
+ return res.send(401, {status: "all field are required !"})
154
+
155
+ if (oldpwd !== GetPasswd())
156
+ return res.send(401, {status: "incorect password"})
157
+
158
+ if (oldpwd === newpwd)
159
+ return res.send(400, {status: "old pwd must be defrent from new one"})
160
+
161
+ SetPassword(newpwd)
162
+ return res.send(200, {status: "password changed with secc"})
163
+
164
+ } catch {
165
+ res.send(500, {status: "internal server error"})
166
+ }
167
+ return res.send(401, {status: "?"})
168
+ }
169
+
170
+ const ChangeUser = async (req: ServerReq, res: ServerRes) => {
171
+ try {
172
+ const { userName } = req.body
173
+
174
+ if (!userName)
175
+ return res.send(401, {status: "all field are required !"})
176
+
177
+
178
+ if (userName.length <= 3 || userName.length >= 20)
179
+ return res.send(400, {status: "username must be between 4-19"})
180
+
181
+ SetUsername(userName)
182
+ return res.send(200, {status: "username changed with secc"})
183
+
184
+ } catch {
185
+ res.send(500, {status: "internal server error"})
186
+ }
187
+ return res.send(401, {status: "?"})
188
+ }
189
+
190
+ return {ChangeUser, LogoutSession, ChangePasswd, ChangePasswdAuth, CheckUsername, Status, Logout, Login}
191
+ }
192
+
@@ -0,0 +1,6 @@
1
+ import Server from "tinny-backend";
2
+ import CreateServer from "../lib/CreateSrv.js";
3
+
4
+ const srv = CreateServer(true, 3000);
5
+
6
+ srv.listen()
@@ -0,0 +1,9 @@
1
+ import monitor from "../admin/monitor.js"
2
+ import Server from "tinny-backend"
3
+
4
+ export default function CreateServer(monitoring: boolean, PORT: number){
5
+ const server = new Server({port: PORT});
6
+ if (monitoring)
7
+ monitor(new Server({port: (PORT + 1)}))
8
+ return server;
9
+ }
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "startup-tinny-backend",
3
+ "version": "1.0.0",
4
+ "description": "Backend starter using tinny-backend framework",
5
+ "private": false,
6
+ "main": "dist/app/index.js",
7
+ "type": "module",
8
+ "scripts": {
9
+ "build": "tsc",
10
+ "start": "node dist/app/index.js",
11
+ "dev": "nodemon dist/app/index.js",
12
+ "dev:watch": "ts-node-dev --respawn --transpile-only app/index.ts"
13
+ },
14
+ "engines": {
15
+ "node": ">=24.13.1",
16
+ "npm": ">=11.10.0"
17
+ },
18
+ "keywords": [
19
+ "backend",
20
+ "starter",
21
+ "tinny-backend",
22
+ "typescript"
23
+ ],
24
+ "author": "Yassine Ajagrou",
25
+ "license": "ISC",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/iaceene/startup-tinny-backend.git"
29
+ },
30
+ "bugs": {
31
+ "url": "https://github.com/iaceene/startup-tinny-backend/issues"
32
+ },
33
+ "homepage": "https://github.com/iaceene/startup-tinny-backend#readme",
34
+ "dependencies": {
35
+ "dotenv": "^17.4.2",
36
+ "jsonwebtoken": "^9.0.3",
37
+ "tinny-backend": "^1.0.3"
38
+ },
39
+ "devDependencies": {
40
+ "@types/jsonwebtoken": "^9.0.10",
41
+ "nodemon": "^3.1.14",
42
+ "ts-node-dev": "^2.0.0"
43
+ },
44
+ "directories": {
45
+ "lib": "lib"
46
+ },
47
+ "types": "./dist/app/index.d.ts"
48
+ }