@liorandb/db 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/README.md +172 -0
- package/dist/app.js +31 -0
- package/dist/config/database.js +13 -0
- package/dist/controllers/auth.controller.js +59 -0
- package/dist/controllers/collection.controller.js +66 -0
- package/dist/controllers/database.controller.js +73 -0
- package/dist/controllers/document.controller.js +46 -0
- package/dist/middleware/auth.middleware.js +23 -0
- package/dist/middleware/requestLogger.middleware.js +54 -0
- package/dist/routes/auth.routes.js +8 -0
- package/dist/routes/collection.routes.js +12 -0
- package/dist/routes/database.routes.js +12 -0
- package/dist/routes/document.routes.js +13 -0
- package/dist/server.js +27 -0
- package/dist/types/auth-user.js +2 -0
- package/dist/utils/hostLogger.js +26 -0
- package/dist/utils/token.js +40 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# LioranDB Server
|
|
2
|
+
|
|
3
|
+
LioranDB Server is a Node.js + TypeScript REST API server built on top of **LioranDB Core**, designed to manage databases, collections, and documents, while providing user authentication. It is intended as the backend for applications using the LioranDB database engine.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
* [Features](#features)
|
|
10
|
+
* [Tech Stack](#tech-stack)
|
|
11
|
+
* [Setup](#setup)
|
|
12
|
+
* [Environment Variables](#environment-variables)
|
|
13
|
+
* [Scripts](#scripts)
|
|
14
|
+
* [API Endpoints](#api-endpoints)
|
|
15
|
+
* [Project Structure](#project-structure)
|
|
16
|
+
* [License](#license)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
* User authentication (register/login) with JWT tokens
|
|
23
|
+
* CRUD operations for databases, collections, and documents
|
|
24
|
+
* Health check endpoint
|
|
25
|
+
* TypeScript support with strict typing
|
|
26
|
+
* Middleware for authentication
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Tech Stack
|
|
31
|
+
|
|
32
|
+
* Node.js
|
|
33
|
+
* TypeScript
|
|
34
|
+
* Express.js
|
|
35
|
+
* LioranDB Core
|
|
36
|
+
* bcryptjs
|
|
37
|
+
* jsonwebtoken
|
|
38
|
+
* dotenv
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Setup
|
|
43
|
+
|
|
44
|
+
1. Clone the repository
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
git clone <repo_url>
|
|
48
|
+
cd server
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
2. Install dependencies
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npm install
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
3. Create a `.env` file at the root:
|
|
58
|
+
|
|
59
|
+
```env
|
|
60
|
+
PORT=4000
|
|
61
|
+
JWT_SECRET=<your_jwt_secret>
|
|
62
|
+
JWT_EXPIRES_IN=7d
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
4. Run the development server
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npm run dev
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The server will start on `http://localhost:4000` (or your configured PORT).
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Environment Variables
|
|
76
|
+
|
|
77
|
+
* `PORT` – Port number for the server (default: 4000)
|
|
78
|
+
* `JWT_SECRET` – Secret key used for signing JWT tokens **(required)**
|
|
79
|
+
* `JWT_EXPIRES_IN` – JWT expiration time (default: `7d`)
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Scripts
|
|
84
|
+
|
|
85
|
+
* `npm run dev` – Start the development server with hot reload
|
|
86
|
+
* `npm run build` – Compile TypeScript to JavaScript (`dist/` folder)
|
|
87
|
+
* `npm start` – Run the compiled server
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## API Endpoints
|
|
92
|
+
|
|
93
|
+
### Health Check
|
|
94
|
+
|
|
95
|
+
* `GET /health` – Returns server status
|
|
96
|
+
|
|
97
|
+
### Authentication
|
|
98
|
+
|
|
99
|
+
* `POST /auth/register` – Register a new user
|
|
100
|
+
* `POST /auth/login` – Login and receive JWT token
|
|
101
|
+
|
|
102
|
+
### Databases
|
|
103
|
+
|
|
104
|
+
* `GET /databases` – List all databases
|
|
105
|
+
* `POST /databases` – Create a new database
|
|
106
|
+
* `DELETE /databases/:db` – Delete a database
|
|
107
|
+
|
|
108
|
+
### Collections
|
|
109
|
+
|
|
110
|
+
* `GET /db/:db/collections` – List all collections in a database
|
|
111
|
+
* `POST /db/:db/collections` – Create a new collection
|
|
112
|
+
|
|
113
|
+
### Documents
|
|
114
|
+
|
|
115
|
+
* `POST /db/:db/collections/:col` – Insert a document
|
|
116
|
+
* `POST /db/:db/collections/:col/find` – Find documents by query
|
|
117
|
+
* `GET /db/:db/collections/:col/:id` – Get a single document by ID
|
|
118
|
+
* `PATCH /db/:db/collections/:col/:id` – Update a document by ID
|
|
119
|
+
* `DELETE /db/:db/collections/:col/:id` – Delete a document by ID
|
|
120
|
+
|
|
121
|
+
> All database, collection, and document endpoints require **Bearer JWT authentication**
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Project Structure
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
server/
|
|
129
|
+
├─ src/
|
|
130
|
+
│ ├─ config/
|
|
131
|
+
│ │ └─ database.ts # LioranDB manager and auth collection
|
|
132
|
+
│ ├─ controllers/
|
|
133
|
+
│ │ ├─ auth.controller.ts
|
|
134
|
+
│ │ ├─ collection.controller.ts
|
|
135
|
+
│ │ ├─ database.controller.ts
|
|
136
|
+
│ │ └─ document.controller.ts
|
|
137
|
+
│ ├─ middleware/
|
|
138
|
+
│ │ └─ auth.middleware.ts
|
|
139
|
+
│ ├─ routes/
|
|
140
|
+
│ │ ├─ auth.routes.ts
|
|
141
|
+
│ │ ├─ collection.routes.ts
|
|
142
|
+
│ │ ├─ database.routes.ts
|
|
143
|
+
│ │ └─ document.routes.ts
|
|
144
|
+
│ ├─ types/
|
|
145
|
+
│ │ ├─ auth-user.ts
|
|
146
|
+
│ │ └─ express.d.ts
|
|
147
|
+
│ ├─ utils/
|
|
148
|
+
│ │ └─ token.ts
|
|
149
|
+
│ ├─ app.ts
|
|
150
|
+
│ └─ server.ts
|
|
151
|
+
├─ package.json
|
|
152
|
+
├─ tsconfig.json
|
|
153
|
+
└─ .env
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
This project is licensed under the **ISC License**.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Notes
|
|
165
|
+
|
|
166
|
+
* Make sure to set a strong `JWT_SECRET` in `.env`
|
|
167
|
+
* The server is designed to work with **LioranDB Core**, which is a peer-to-peer database engine.
|
|
168
|
+
* TypeScript strict mode is enabled for safer coding.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
For further documentation on **LioranDB Core**, refer to its repository: [LioranDB Core](https://www.npmjs.com/package/@liorandb/core)
|
package/dist/app.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const cors_1 = __importDefault(require("cors"));
|
|
7
|
+
const express_1 = __importDefault(require("express"));
|
|
8
|
+
const auth_routes_1 = __importDefault(require("./routes/auth.routes"));
|
|
9
|
+
const database_routes_1 = __importDefault(require("./routes/database.routes"));
|
|
10
|
+
const collection_routes_1 = __importDefault(require("./routes/collection.routes"));
|
|
11
|
+
const document_routes_1 = __importDefault(require("./routes/document.routes"));
|
|
12
|
+
const requestLogger_middleware_1 = require("./middleware/requestLogger.middleware");
|
|
13
|
+
const app = (0, express_1.default)();
|
|
14
|
+
app.use(express_1.default.json());
|
|
15
|
+
app.use((0, cors_1.default)());
|
|
16
|
+
app.use(requestLogger_middleware_1.requestLogger);
|
|
17
|
+
// health check
|
|
18
|
+
app.get("/health", (req, res) => res.json({ ok: true, time: new Date().toISOString() }));
|
|
19
|
+
app.get("/", (_, res) => {
|
|
20
|
+
res.json({
|
|
21
|
+
name: "LioranDB",
|
|
22
|
+
role: "Database Host",
|
|
23
|
+
status: "online"
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
// routes
|
|
27
|
+
app.use("/auth", auth_routes_1.default);
|
|
28
|
+
app.use("/databases", database_routes_1.default);
|
|
29
|
+
app.use("/db/:db/collections", collection_routes_1.default);
|
|
30
|
+
app.use("/db/:db/collections/:col", document_routes_1.default);
|
|
31
|
+
exports.default = app;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.manager = void 0;
|
|
4
|
+
exports.getAuthCollection = getAuthCollection;
|
|
5
|
+
// src/config/database.ts
|
|
6
|
+
const core_1 = require("@liorandb/core");
|
|
7
|
+
exports.manager = new core_1.LioranManager();
|
|
8
|
+
async function getAuthCollection() {
|
|
9
|
+
const db = await exports.manager.db("_auth");
|
|
10
|
+
await db.createCollection("users").catch(() => { });
|
|
11
|
+
// explicitly type this collection as AuthUser so TS knows the fields
|
|
12
|
+
return db.collection("users");
|
|
13
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.login = exports.register = void 0;
|
|
7
|
+
const bcryptjs_1 = __importDefault(require("bcryptjs"));
|
|
8
|
+
const database_1 = require("../config/database");
|
|
9
|
+
const token_1 = require("../utils/token");
|
|
10
|
+
const register = async (req, res) => {
|
|
11
|
+
try {
|
|
12
|
+
const { username, password } = req.body;
|
|
13
|
+
if (!username || !password)
|
|
14
|
+
return res.status(400).json({ error: "username and password required" });
|
|
15
|
+
if (typeof username !== "string" || typeof password !== "string")
|
|
16
|
+
return res.status(400).json({ error: "invalid types" });
|
|
17
|
+
if (password.length < 6)
|
|
18
|
+
return res.status(400).json({ error: "password must be at least 6 characters" });
|
|
19
|
+
const users = await (0, database_1.getAuthCollection)(); // typed as AuthUser collection
|
|
20
|
+
const existing = await users.findOne({ username });
|
|
21
|
+
if (existing)
|
|
22
|
+
return res.status(409).json({ error: "username already exists" });
|
|
23
|
+
const hashed = await bcryptjs_1.default.hash(password, 10);
|
|
24
|
+
const created = await users.insertOne({
|
|
25
|
+
username,
|
|
26
|
+
password: hashed,
|
|
27
|
+
createdAt: new Date().toISOString(),
|
|
28
|
+
});
|
|
29
|
+
const token = (0, token_1.signToken)({ id: created._id, username });
|
|
30
|
+
res.json({ user: { id: created._id, username }, token });
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
console.error(err);
|
|
34
|
+
res.status(500).json({ error: "server error" });
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
exports.register = register;
|
|
38
|
+
const login = async (req, res) => {
|
|
39
|
+
try {
|
|
40
|
+
const { username, password } = req.body;
|
|
41
|
+
if (!username || !password)
|
|
42
|
+
return res.status(400).json({ error: "username and password required" });
|
|
43
|
+
const users = await (0, database_1.getAuthCollection)();
|
|
44
|
+
const user = await users.findOne({ username });
|
|
45
|
+
if (!user)
|
|
46
|
+
return res.status(401).json({ error: "invalid credentials" });
|
|
47
|
+
// user is typed as AuthUser so .password exists
|
|
48
|
+
const ok = await bcryptjs_1.default.compare(password, user.password);
|
|
49
|
+
if (!ok)
|
|
50
|
+
return res.status(401).json({ error: "invalid credentials" });
|
|
51
|
+
const token = (0, token_1.signToken)({ id: user._id, username });
|
|
52
|
+
res.json({ user: { id: user._id, username }, token });
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
console.error(err);
|
|
56
|
+
res.status(500).json({ error: "server error" });
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
exports.login = login;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.collectionStats = exports.renameCollection = exports.deleteCollection = exports.createCollection = exports.listCollections = void 0;
|
|
4
|
+
const database_1 = require("../config/database");
|
|
5
|
+
const listCollections = async (req, res) => {
|
|
6
|
+
try {
|
|
7
|
+
const database = await database_1.manager.db(req.params.db);
|
|
8
|
+
const collections = await database.listCollections();
|
|
9
|
+
res.json({ collections });
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
res.status(500).json({ error: "server error" });
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
exports.listCollections = listCollections;
|
|
16
|
+
const createCollection = async (req, res) => {
|
|
17
|
+
try {
|
|
18
|
+
const database = await database_1.manager.db(req.params.db);
|
|
19
|
+
await database.createCollection(req.body.name);
|
|
20
|
+
res.json({ ok: true });
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
res.status(500).json({ error: "server error" });
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
exports.createCollection = createCollection;
|
|
27
|
+
const deleteCollection = async (req, res) => {
|
|
28
|
+
try {
|
|
29
|
+
const database = await database_1.manager.db(req.params.db);
|
|
30
|
+
await database.deleteCollection(req.params.col);
|
|
31
|
+
res.json({ ok: true });
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
res.status(500).json({ error: "server error" });
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
exports.deleteCollection = deleteCollection;
|
|
38
|
+
const renameCollection = async (req, res) => {
|
|
39
|
+
try {
|
|
40
|
+
const { db, col } = req.params;
|
|
41
|
+
const { newName } = req.body;
|
|
42
|
+
const database = await database_1.manager.db(db);
|
|
43
|
+
await database.renameCollection(col, newName);
|
|
44
|
+
res.json({ ok: true, old: col, new: newName });
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
res.status(500).json({ error: "server error" });
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
exports.renameCollection = renameCollection;
|
|
51
|
+
const collectionStats = async (req, res) => {
|
|
52
|
+
try {
|
|
53
|
+
const { db, col } = req.params;
|
|
54
|
+
const database = await database_1.manager.db(db);
|
|
55
|
+
const collection = database.collection(col);
|
|
56
|
+
const count = await collection.countDocuments();
|
|
57
|
+
res.json({
|
|
58
|
+
name: col,
|
|
59
|
+
documents: count,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
res.status(500).json({ error: "server error" });
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
exports.collectionStats = collectionStats;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.databaseStats = exports.renameDatabase = exports.deleteDatabase = exports.createDatabase = exports.listDatabases = void 0;
|
|
4
|
+
const database_1 = require("../config/database");
|
|
5
|
+
const listDatabases = async (_, res) => {
|
|
6
|
+
try {
|
|
7
|
+
const list = await database_1.manager.listDatabases();
|
|
8
|
+
res.json({ databases: list });
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
res.status(500).json({ error: "server error" });
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
exports.listDatabases = listDatabases;
|
|
15
|
+
const createDatabase = async (req, res) => {
|
|
16
|
+
try {
|
|
17
|
+
const { name } = req.body;
|
|
18
|
+
if (!name)
|
|
19
|
+
return res.status(400).json({ error: "database name required" });
|
|
20
|
+
await database_1.manager.createDatabase(name);
|
|
21
|
+
res.json({ ok: true, db: name });
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
res.status(500).json({ error: "server error" });
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
exports.createDatabase = createDatabase;
|
|
28
|
+
const deleteDatabase = async (req, res) => {
|
|
29
|
+
try {
|
|
30
|
+
const { db } = req.params;
|
|
31
|
+
const ok = await database_1.manager.deleteDatabase(db);
|
|
32
|
+
res.json({ ok: !!ok });
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
res.status(500).json({ error: "server error" });
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
exports.deleteDatabase = deleteDatabase;
|
|
39
|
+
const renameDatabase = async (req, res) => {
|
|
40
|
+
try {
|
|
41
|
+
const { db } = req.params;
|
|
42
|
+
const { newName } = req.body;
|
|
43
|
+
if (!newName)
|
|
44
|
+
return res.status(400).json({ error: "newName required" });
|
|
45
|
+
await database_1.manager.renameDatabase(db, newName);
|
|
46
|
+
res.json({ ok: true, old: db, new: newName });
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
res.status(500).json({ error: "server error" });
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
exports.renameDatabase = renameDatabase;
|
|
53
|
+
const databaseStats = async (req, res) => {
|
|
54
|
+
try {
|
|
55
|
+
const { db } = req.params;
|
|
56
|
+
const database = await database_1.manager.db(db);
|
|
57
|
+
const cols = await database.listCollections();
|
|
58
|
+
let totalDocs = 0;
|
|
59
|
+
for (const colName of cols) {
|
|
60
|
+
const col = database.collection(colName);
|
|
61
|
+
totalDocs += await col.countDocuments();
|
|
62
|
+
}
|
|
63
|
+
res.json({
|
|
64
|
+
name: db,
|
|
65
|
+
collections: cols.length,
|
|
66
|
+
documents: totalDocs,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
res.status(500).json({ error: "server error" });
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
exports.databaseStats = databaseStats;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.countDocuments = exports.deleteMany = exports.updateMany = exports.findDocuments = exports.insertMany = exports.insertDocument = void 0;
|
|
4
|
+
const database_1 = require("../config/database");
|
|
5
|
+
const insertDocument = async (req, res) => {
|
|
6
|
+
const collection = (await database_1.manager.db(req.params.db))
|
|
7
|
+
.collection(req.params.col);
|
|
8
|
+
const doc = await collection.insertOne(req.body);
|
|
9
|
+
res.json({ ok: true, doc });
|
|
10
|
+
};
|
|
11
|
+
exports.insertDocument = insertDocument;
|
|
12
|
+
const insertMany = async (req, res) => {
|
|
13
|
+
const collection = (await database_1.manager.db(req.params.db))
|
|
14
|
+
.collection(req.params.col);
|
|
15
|
+
const docs = await collection.insertMany(req.body.docs || []);
|
|
16
|
+
res.json({ ok: true, docs });
|
|
17
|
+
};
|
|
18
|
+
exports.insertMany = insertMany;
|
|
19
|
+
const findDocuments = async (req, res) => {
|
|
20
|
+
const collection = (await database_1.manager.db(req.params.db))
|
|
21
|
+
.collection(req.params.col);
|
|
22
|
+
const results = await collection.find(req.body.query || {});
|
|
23
|
+
res.json({ results });
|
|
24
|
+
};
|
|
25
|
+
exports.findDocuments = findDocuments;
|
|
26
|
+
const updateMany = async (req, res) => {
|
|
27
|
+
const collection = (await database_1.manager.db(req.params.db))
|
|
28
|
+
.collection(req.params.col);
|
|
29
|
+
const docs = await collection.updateMany(req.body.filter, req.body.update);
|
|
30
|
+
res.json({ updated: docs });
|
|
31
|
+
};
|
|
32
|
+
exports.updateMany = updateMany;
|
|
33
|
+
const deleteMany = async (req, res) => {
|
|
34
|
+
const collection = (await database_1.manager.db(req.params.db))
|
|
35
|
+
.collection(req.params.col);
|
|
36
|
+
const count = await collection.deleteMany(req.body.filter || {});
|
|
37
|
+
res.json({ deleted: count });
|
|
38
|
+
};
|
|
39
|
+
exports.deleteMany = deleteMany;
|
|
40
|
+
const countDocuments = async (req, res) => {
|
|
41
|
+
const collection = (await database_1.manager.db(req.params.db))
|
|
42
|
+
.collection(req.params.col);
|
|
43
|
+
const count = await collection.countDocuments(req.body.filter || {});
|
|
44
|
+
res.json({ count });
|
|
45
|
+
};
|
|
46
|
+
exports.countDocuments = countDocuments;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.authMiddleware = authMiddleware;
|
|
7
|
+
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
8
|
+
const token_1 = require("../utils/token");
|
|
9
|
+
function authMiddleware(req, res, next) {
|
|
10
|
+
const auth = req.headers.authorization;
|
|
11
|
+
if (!auth || !auth.startsWith("Bearer ")) {
|
|
12
|
+
return res.status(401).json({ error: "Missing token" });
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const token = auth.split(" ")[1];
|
|
16
|
+
const decoded = jsonwebtoken_1.default.verify(token, token_1.JWT_SECRET);
|
|
17
|
+
req.user = decoded;
|
|
18
|
+
next();
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return res.status(401).json({ error: "Invalid token" });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.requestLogger = requestLogger;
|
|
4
|
+
const hostLogger_1 = require("../utils/hostLogger");
|
|
5
|
+
function formatResponse(body) {
|
|
6
|
+
if (!body)
|
|
7
|
+
return "null";
|
|
8
|
+
// If already an object
|
|
9
|
+
if (typeof body === "object") {
|
|
10
|
+
return JSON.stringify(body, null, 2);
|
|
11
|
+
}
|
|
12
|
+
// If string, try parsing JSON
|
|
13
|
+
if (typeof body === "string") {
|
|
14
|
+
try {
|
|
15
|
+
const parsed = JSON.parse(body);
|
|
16
|
+
return JSON.stringify(parsed, null, 2);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return body; // normal string (HTML, text, etc.)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return String(body);
|
|
23
|
+
}
|
|
24
|
+
function requestLogger(req, res, next) {
|
|
25
|
+
const start = Date.now();
|
|
26
|
+
const oldSend = res.send.bind(res);
|
|
27
|
+
let responseBody;
|
|
28
|
+
res.send = (body) => {
|
|
29
|
+
responseBody = body;
|
|
30
|
+
return oldSend(body);
|
|
31
|
+
};
|
|
32
|
+
res.on("finish", () => {
|
|
33
|
+
const duration = Date.now() - start;
|
|
34
|
+
const logData = `
|
|
35
|
+
──────────────────────────────────────
|
|
36
|
+
INCOMING REQUEST
|
|
37
|
+
Method : ${req.method}
|
|
38
|
+
URL : ${req.originalUrl}
|
|
39
|
+
IP : ${req.ip}
|
|
40
|
+
Auth : ${req.headers.authorization ? "Yes" : "No"}
|
|
41
|
+
RequestBody :
|
|
42
|
+
${JSON.stringify(req.body || {}, null, 2)}
|
|
43
|
+
|
|
44
|
+
RESPONSE
|
|
45
|
+
Status : ${res.statusCode}
|
|
46
|
+
Duration : ${duration}ms
|
|
47
|
+
Response :
|
|
48
|
+
${formatResponse(responseBody)}
|
|
49
|
+
──────────────────────────────────────
|
|
50
|
+
`;
|
|
51
|
+
(0, hostLogger_1.hostLog)(logData.trim());
|
|
52
|
+
});
|
|
53
|
+
next();
|
|
54
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const express_1 = require("express");
|
|
4
|
+
const auth_controller_1 = require("../controllers/auth.controller");
|
|
5
|
+
const router = (0, express_1.Router)();
|
|
6
|
+
router.post("/register", auth_controller_1.register);
|
|
7
|
+
router.post("/login", auth_controller_1.login);
|
|
8
|
+
exports.default = router;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const express_1 = require("express");
|
|
4
|
+
const collection_controller_1 = require("../controllers/collection.controller");
|
|
5
|
+
const auth_middleware_1 = require("../middleware/auth.middleware");
|
|
6
|
+
const router = (0, express_1.Router)({ mergeParams: true });
|
|
7
|
+
router.get("/", auth_middleware_1.authMiddleware, collection_controller_1.listCollections);
|
|
8
|
+
router.post("/", auth_middleware_1.authMiddleware, collection_controller_1.createCollection);
|
|
9
|
+
router.delete("/:col", auth_middleware_1.authMiddleware, collection_controller_1.deleteCollection);
|
|
10
|
+
router.patch("/:col/rename", auth_middleware_1.authMiddleware, collection_controller_1.renameCollection);
|
|
11
|
+
router.get("/:col/stats", auth_middleware_1.authMiddleware, collection_controller_1.collectionStats);
|
|
12
|
+
exports.default = router;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const express_1 = require("express");
|
|
4
|
+
const database_controller_1 = require("../controllers/database.controller");
|
|
5
|
+
const auth_middleware_1 = require("../middleware/auth.middleware");
|
|
6
|
+
const router = (0, express_1.Router)();
|
|
7
|
+
router.get("/", auth_middleware_1.authMiddleware, database_controller_1.listDatabases);
|
|
8
|
+
router.post("/", auth_middleware_1.authMiddleware, database_controller_1.createDatabase);
|
|
9
|
+
router.delete("/:db", auth_middleware_1.authMiddleware, database_controller_1.deleteDatabase);
|
|
10
|
+
router.patch("/:db/rename", auth_middleware_1.authMiddleware, database_controller_1.renameDatabase);
|
|
11
|
+
router.get("/:db/stats", auth_middleware_1.authMiddleware, database_controller_1.databaseStats);
|
|
12
|
+
exports.default = router;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const express_1 = require("express");
|
|
4
|
+
const document_controller_1 = require("../controllers/document.controller");
|
|
5
|
+
const auth_middleware_1 = require("../middleware/auth.middleware");
|
|
6
|
+
const router = (0, express_1.Router)({ mergeParams: true });
|
|
7
|
+
router.post("/", auth_middleware_1.authMiddleware, document_controller_1.insertDocument);
|
|
8
|
+
router.post("/bulk", auth_middleware_1.authMiddleware, document_controller_1.insertMany);
|
|
9
|
+
router.post("/find", auth_middleware_1.authMiddleware, document_controller_1.findDocuments);
|
|
10
|
+
router.patch("/updateMany", auth_middleware_1.authMiddleware, document_controller_1.updateMany);
|
|
11
|
+
router.post("/deleteMany", auth_middleware_1.authMiddleware, document_controller_1.deleteMany);
|
|
12
|
+
router.post("/count", auth_middleware_1.authMiddleware, document_controller_1.countDocuments);
|
|
13
|
+
exports.default = router;
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
// src/server.ts
|
|
7
|
+
const app_1 = __importDefault(require("./app"));
|
|
8
|
+
const PORT = 4000;
|
|
9
|
+
app_1.default.listen(PORT, "0.0.0.0", () => {
|
|
10
|
+
console.log("======================================");
|
|
11
|
+
console.log("🚀 LioranDB Host is LIVE");
|
|
12
|
+
console.log(`📡 Listening on port: ${PORT}`);
|
|
13
|
+
// print all running host IPs
|
|
14
|
+
console.log(`🌐 Host Address: localhost:4000`);
|
|
15
|
+
const os = require("os");
|
|
16
|
+
const networkInterfaces = os.networkInterfaces();
|
|
17
|
+
for (const interfaceName in networkInterfaces) {
|
|
18
|
+
const interfaceInfo = networkInterfaces[interfaceName];
|
|
19
|
+
for (const addressInfo of interfaceInfo) {
|
|
20
|
+
if (addressInfo.family === "IPv4" && !addressInfo.internal) {
|
|
21
|
+
console.log(`🌐 Host Address: ${addressInfo.address}:4000`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// console.log(`🧠 Mode: ${process.env.NODE_ENV || "development"}`);
|
|
26
|
+
console.log("======================================");
|
|
27
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.hostLog = hostLog;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const LOG_DIR = path_1.default.join(process.cwd(), "logs");
|
|
10
|
+
if (!fs_1.default.existsSync(LOG_DIR)) {
|
|
11
|
+
fs_1.default.mkdirSync(LOG_DIR, { recursive: true });
|
|
12
|
+
}
|
|
13
|
+
function getLogFilePath() {
|
|
14
|
+
const now = new Date();
|
|
15
|
+
const date = now.toISOString().split("T")[0]; // YYYY-MM-DD
|
|
16
|
+
const hour = String(now.getHours()).padStart(2, "0"); // HH
|
|
17
|
+
return path_1.default.join(LOG_DIR, `${date}_${hour}.log`);
|
|
18
|
+
}
|
|
19
|
+
function hostLog(message) {
|
|
20
|
+
const timestamp = new Date().toISOString();
|
|
21
|
+
const logLine = `[${timestamp}] ${message}\n`;
|
|
22
|
+
// Write to file
|
|
23
|
+
fs_1.default.appendFileSync(getLogFilePath(), logLine, "utf8");
|
|
24
|
+
// Print to console
|
|
25
|
+
console.log(logLine.trim());
|
|
26
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.JWT_EXPIRES_IN = exports.JWT_SECRET = void 0;
|
|
7
|
+
exports.signToken = signToken;
|
|
8
|
+
exports.verifyToken = verifyToken;
|
|
9
|
+
// src/utils/token.ts
|
|
10
|
+
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
11
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
12
|
+
const os_1 = __importDefault(require("os"));
|
|
13
|
+
/**
|
|
14
|
+
* Derive a stable, machine-bound secret
|
|
15
|
+
*/
|
|
16
|
+
function getHardwareSecret() {
|
|
17
|
+
const fingerprint = [
|
|
18
|
+
os_1.default.hostname(),
|
|
19
|
+
os_1.default.platform(),
|
|
20
|
+
os_1.default.arch(),
|
|
21
|
+
os_1.default.cpus()?.[0]?.model || "unknown-cpu",
|
|
22
|
+
os_1.default.cpus()?.length.toString() || "0",
|
|
23
|
+
os_1.default.totalmem().toString()
|
|
24
|
+
].join("|");
|
|
25
|
+
return crypto_1.default
|
|
26
|
+
.createHash("sha256")
|
|
27
|
+
.update(fingerprint)
|
|
28
|
+
.digest("hex"); // JWT expects string | Buffer
|
|
29
|
+
}
|
|
30
|
+
exports.JWT_SECRET = getHardwareSecret();
|
|
31
|
+
// Safe default, still configurable if needed
|
|
32
|
+
exports.JWT_EXPIRES_IN = "7d";
|
|
33
|
+
function signToken(payload) {
|
|
34
|
+
return jsonwebtoken_1.default.sign(payload, exports.JWT_SECRET, {
|
|
35
|
+
expiresIn: exports.JWT_EXPIRES_IN,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function verifyToken(token) {
|
|
39
|
+
return jsonwebtoken_1.default.verify(token, exports.JWT_SECRET);
|
|
40
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@liorandb/db",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "LioranDB server and CLI",
|
|
5
|
+
"main": "dist/server.js",
|
|
6
|
+
"types": "dist/server.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"dbi": "dist/cli.js",
|
|
9
|
+
"serve": "dist/server.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"dev": "ts-node-dev --respawn --transpile-only src/server.ts",
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"start": "node dist/server.js",
|
|
15
|
+
"cli": "ts-node cli/index.ts"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"database",
|
|
22
|
+
"liorandb",
|
|
23
|
+
"server",
|
|
24
|
+
"cli"
|
|
25
|
+
],
|
|
26
|
+
"author": "Your Name",
|
|
27
|
+
"license": "ISC",
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/bcryptjs": "^2.4.6",
|
|
30
|
+
"@types/commander": "^2.12.0",
|
|
31
|
+
"@types/cors": "^2.8.19",
|
|
32
|
+
"@types/express": "^5.0.6",
|
|
33
|
+
"@types/jsonwebtoken": "^9.0.10",
|
|
34
|
+
"@types/node": "^25.0.1",
|
|
35
|
+
"commander": "^14.0.2",
|
|
36
|
+
"nodemon": "^3.1.11",
|
|
37
|
+
"ts-node": "^10.9.2",
|
|
38
|
+
"ts-node-dev": "^2.0.0",
|
|
39
|
+
"typescript": "^5.9.3"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@liorandb/core": "^1.0.9",
|
|
43
|
+
"bcryptjs": "^3.0.3",
|
|
44
|
+
"cors": "^2.8.6",
|
|
45
|
+
"express": "^5.2.1",
|
|
46
|
+
"jsonwebtoken": "^9.0.3"
|
|
47
|
+
}
|
|
48
|
+
}
|