@dnax/core 0.0.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/README.md +15 -0
- package/ai/gemini.ts +97 -0
- package/ai/index.ts +2 -0
- package/ai/mistral.ts +63 -0
- package/app/ctrl.ts +26 -0
- package/app/hono.ts +398 -0
- package/app/index.ts +72 -0
- package/config/index.ts +68 -0
- package/define/index.ts +35 -0
- package/driver/index.ts +19 -0
- package/driver/mongo/@types.ts +44 -0
- package/driver/mongo/connect.ts +26 -0
- package/driver/mongo/index.ts +4 -0
- package/driver/mongo/rest.ts +1214 -0
- package/driver/mongo/utils.ts +361 -0
- package/index.ts +11 -0
- package/lib/asyncLocalStorage.ts +47 -0
- package/lib/collection.ts +191 -0
- package/lib/endpoint.ts +36 -0
- package/lib/index.ts +26 -0
- package/lib/media.ts +74 -0
- package/lib/schema.ts +112 -0
- package/lib/service.ts +43 -0
- package/lib/socket.ts +51 -0
- package/lib/studio.ts +12 -0
- package/lib/tenant.ts +9 -0
- package/package.json +38 -0
- package/tsconfig.json +27 -0
- package/types/index.ts +377 -0
- package/utils/index.ts +251 -0
package/README.md
ADDED
package/ai/gemini.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { GoogleGenerativeAI } from "@google/generative-ai";
|
|
2
|
+
import { Cfg } from "../config";
|
|
3
|
+
import { getCollection } from "../lib/collection";
|
|
4
|
+
|
|
5
|
+
class Gemini {
|
|
6
|
+
#genAI: InstanceType<typeof GoogleGenerativeAI>;
|
|
7
|
+
#api_key: any;
|
|
8
|
+
#tenant_id: any;
|
|
9
|
+
#systemInstruction: string | undefined = "";
|
|
10
|
+
#model: string = "gemini-1.5-flash";
|
|
11
|
+
|
|
12
|
+
constructor(
|
|
13
|
+
opts: {
|
|
14
|
+
tenant_id?: string;
|
|
15
|
+
key?: string;
|
|
16
|
+
systemInstruction?: string;
|
|
17
|
+
model?: string;
|
|
18
|
+
} = {
|
|
19
|
+
model: "gemini-1.5-flash",
|
|
20
|
+
}
|
|
21
|
+
) {
|
|
22
|
+
this.#genAI = new GoogleGenerativeAI(
|
|
23
|
+
opts.key || Cfg.ai?.key || process.env.AI_KEY || ""
|
|
24
|
+
);
|
|
25
|
+
this.#systemInstruction = opts.systemInstruction;
|
|
26
|
+
this.#tenant_id = opts?.tenant_id || null;
|
|
27
|
+
this.#api_key = process.env.key || Cfg.ai?.key || "";
|
|
28
|
+
this.#model = opts.model || "gemini-1.5-flash";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async generateContent(
|
|
32
|
+
content: string,
|
|
33
|
+
options: {
|
|
34
|
+
systemInstruction: string;
|
|
35
|
+
generationConfig?: {
|
|
36
|
+
responseMimeType: string;
|
|
37
|
+
};
|
|
38
|
+
} = {
|
|
39
|
+
systemInstruction: "",
|
|
40
|
+
}
|
|
41
|
+
) {
|
|
42
|
+
let model = this.#genAI.getGenerativeModel({
|
|
43
|
+
model: this.#model,
|
|
44
|
+
systemInstruction: options.systemInstruction || this.#systemInstruction,
|
|
45
|
+
generationConfig: options.generationConfig || {},
|
|
46
|
+
});
|
|
47
|
+
let result = await model.generateContent(content);
|
|
48
|
+
let response = await result.response.text();
|
|
49
|
+
return response;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* async generateFilter(opts: { collection: string; prompt: string }) {
|
|
53
|
+
let col = getCollection(opts.collection, this.#tenant_id);
|
|
54
|
+
let model = this.#genAI.getGenerativeModel({
|
|
55
|
+
model: "gemini-1.5-flash",
|
|
56
|
+
systemInstruction: `
|
|
57
|
+
|
|
58
|
+
En te basant les informations json suivantes :
|
|
59
|
+
Nom de la base de donnée : ${col?.slug}
|
|
60
|
+
et de la structure des champs suivants :
|
|
61
|
+
${col?.fields}
|
|
62
|
+
|
|
63
|
+
Merci de fourni un filtre $match(aggregate) de mongodb qui correspond à la demande du
|
|
64
|
+
l'utilisateur et de retourner ce $match sous forme de JSON parse
|
|
65
|
+
|
|
66
|
+
- Si la demande ne correspond pas à une generation de filter $match
|
|
67
|
+
merci de retourner "{}" comme réponse en json parse .
|
|
68
|
+
|
|
69
|
+
Exemple de format json de retour : {"$match":{"nom":"John"}}
|
|
70
|
+
|
|
71
|
+
`,
|
|
72
|
+
});
|
|
73
|
+
let result = await model.generateContent(opts.prompt);
|
|
74
|
+
let response = await result.response.text();
|
|
75
|
+
|
|
76
|
+
return extractJson(response);
|
|
77
|
+
} */
|
|
78
|
+
}
|
|
79
|
+
function extractJson(chaine: string) {
|
|
80
|
+
try {
|
|
81
|
+
// Utiliser une expression régulière pour extraire le contenu JSON
|
|
82
|
+
const match = chaine.match(/\{.*\}/);
|
|
83
|
+
if (match) {
|
|
84
|
+
// Extraire le contenu JSON
|
|
85
|
+
const jsonStr = match[0];
|
|
86
|
+
// Convertir la chaîne JSON en objet JavaScript
|
|
87
|
+
const jsonData = JSON.parse(jsonStr);
|
|
88
|
+
return jsonData;
|
|
89
|
+
} else {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
} catch (err: any) {
|
|
93
|
+
console.log(err?.message);
|
|
94
|
+
return {};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
export { Gemini };
|
package/ai/index.ts
ADDED
package/ai/mistral.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { GoogleGenerativeAI } from "@google/generative-ai";
|
|
2
|
+
import { Cfg } from "../config";
|
|
3
|
+
import { getCollection } from "../lib/collection";
|
|
4
|
+
|
|
5
|
+
class Mistral {
|
|
6
|
+
#genAI: InstanceType<typeof GoogleGenerativeAI>;
|
|
7
|
+
#api_key: any;
|
|
8
|
+
#tenant_id: any;
|
|
9
|
+
|
|
10
|
+
constructor(opts: { tenant_id?: string; key?: string } = {}) {
|
|
11
|
+
this.#genAI = new GoogleGenerativeAI(
|
|
12
|
+
opts.key || Cfg.ai?.key || process.env.API_KEY || ""
|
|
13
|
+
);
|
|
14
|
+
this.#tenant_id = opts?.tenant_id || null;
|
|
15
|
+
this.#api_key = process.env.key || Cfg.ai?.key || "";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async generateFilter(opts: { collection: string; prompt: string }) {
|
|
19
|
+
let col = getCollection(opts.collection, this.#tenant_id);
|
|
20
|
+
let model = this.#genAI.getGenerativeModel({
|
|
21
|
+
model: "gemini-1.5-flash",
|
|
22
|
+
systemInstruction: `
|
|
23
|
+
|
|
24
|
+
En te basant les informations json suivantes :
|
|
25
|
+
Nom de la base de donnée : ${col?.slug}
|
|
26
|
+
et de la structure des champs suivants :
|
|
27
|
+
${col?.fields}
|
|
28
|
+
|
|
29
|
+
Merci de fourni un filtre $match(aggregate) de mongodb qui correspond à la demande du
|
|
30
|
+
l'utilisateur et de retourner ce $match sous forme de JSON parse
|
|
31
|
+
|
|
32
|
+
- Si la demande ne correspond pas à une generation de filter $match
|
|
33
|
+
merci de retourner "{}" comme réponse en json parse .
|
|
34
|
+
|
|
35
|
+
Exemple de format json de retour : {"$match":{"nom":"John"}}
|
|
36
|
+
|
|
37
|
+
`,
|
|
38
|
+
});
|
|
39
|
+
let result = await model.generateContent(opts.prompt);
|
|
40
|
+
let response = await result.response.text();
|
|
41
|
+
|
|
42
|
+
return extractJson(response);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function extractJson(chaine: string) {
|
|
46
|
+
try {
|
|
47
|
+
// Utiliser une expression régulière pour extraire le contenu JSON
|
|
48
|
+
const match = chaine.match(/\{.*\}/);
|
|
49
|
+
if (match) {
|
|
50
|
+
// Extraire le contenu JSON
|
|
51
|
+
const jsonStr = match[0];
|
|
52
|
+
// Convertir la chaîne JSON en objet JavaScript
|
|
53
|
+
const jsonData = JSON.parse(jsonStr);
|
|
54
|
+
return jsonData;
|
|
55
|
+
} else {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
} catch (err: any) {
|
|
59
|
+
console.log(err?.message);
|
|
60
|
+
return {};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export { Mistral };
|
package/app/ctrl.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Q } from "../types/";
|
|
2
|
+
const actions = [
|
|
3
|
+
"findOneAndUpdate",
|
|
4
|
+
"findOne",
|
|
5
|
+
"find",
|
|
6
|
+
"insertOne",
|
|
7
|
+
"insertMany",
|
|
8
|
+
"updateOne",
|
|
9
|
+
"updateMany",
|
|
10
|
+
"deleteOne",
|
|
11
|
+
"deleteMany",
|
|
12
|
+
"aggregate",
|
|
13
|
+
"authCollection",
|
|
14
|
+
"auth",
|
|
15
|
+
"execService",
|
|
16
|
+
"upload",
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
function getAction(action: string) {
|
|
20
|
+
if (actions.includes(action)) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { getAction };
|
package/app/hono.ts
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import type { Q, Tenant, sessionCtx } from "../types/";
|
|
2
|
+
|
|
3
|
+
import { Hono } from "hono";
|
|
4
|
+
import { getCookie, setCookie } from "hono/cookie";
|
|
5
|
+
|
|
6
|
+
import { serveStatic, getConnInfo } from "hono/bun";
|
|
7
|
+
import { consola } from "consola";
|
|
8
|
+
import { cors } from "hono/cors";
|
|
9
|
+
import { asyncLocalStorage, sessionStorage } from "../lib/asyncLocalStorage";
|
|
10
|
+
import { cleanPath, contextError, jwt, fn, omit } from "../utils";
|
|
11
|
+
import { getAction } from "./ctrl";
|
|
12
|
+
import { getCollection } from "../lib/collection";
|
|
13
|
+
import { useRest } from "../driver/mongo/rest";
|
|
14
|
+
import moment from "moment";
|
|
15
|
+
import { Cfg } from "../config";
|
|
16
|
+
import { getService } from "../lib/service";
|
|
17
|
+
import { MediaDrive } from "../lib/media";
|
|
18
|
+
import { isStudio } from "../lib/studio";
|
|
19
|
+
import { pick } from "radash";
|
|
20
|
+
import { secureHeaders } from "hono/secure-headers";
|
|
21
|
+
const app = new Hono();
|
|
22
|
+
|
|
23
|
+
const API_PATH = "/api";
|
|
24
|
+
function HonoInstance(): typeof app {
|
|
25
|
+
app.use(secureHeaders());
|
|
26
|
+
|
|
27
|
+
app.use(
|
|
28
|
+
cors({
|
|
29
|
+
origin: Cfg.server?.cors?.origin || [],
|
|
30
|
+
credentials: true,
|
|
31
|
+
})
|
|
32
|
+
);
|
|
33
|
+
//eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoibm9tIiwiaWF0IjoxNzE3Nzc0MDQzLCJleHAiOjE3MTc3NzQxMDN9.Ud4-0y8pa4SMIcSn8PU1A-sjC-hT4ZVe_u3AdChyIJU
|
|
34
|
+
// Middleware
|
|
35
|
+
app.use(async (c, next) => {
|
|
36
|
+
return asyncLocalStorage.run(new Map(), async () => {
|
|
37
|
+
let cookie = getCookie(c);
|
|
38
|
+
let secretKeyStudio = cookie["_STUDIO_SECRET_KEY_"] || null;
|
|
39
|
+
let session = sessionStorage();
|
|
40
|
+
var token = jwt.getToken("Bearer", c.req.header()["authorization"]) || "";
|
|
41
|
+
var { decode, valid, error } = jwt.verify(token);
|
|
42
|
+
c.set("_STUDIO_SECRET_KEY_", secretKeyStudio);
|
|
43
|
+
c.set("isStudio", isStudio(secretKeyStudio));
|
|
44
|
+
c.set("_v", {
|
|
45
|
+
token: token || null,
|
|
46
|
+
ip: c.req.raw.headers.get("CF-Connecting-IP"),
|
|
47
|
+
isAuth: decode?._v?.isAuth ? true : false,
|
|
48
|
+
});
|
|
49
|
+
c.set("tenant-id", c.req.header()["tenant-id"]);
|
|
50
|
+
if (token && valid) {
|
|
51
|
+
session.set({
|
|
52
|
+
state: decode,
|
|
53
|
+
_v: c.var?._v || {},
|
|
54
|
+
token: token,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// If token wrong or break
|
|
59
|
+
if (token && !valid) {
|
|
60
|
+
c.status(401);
|
|
61
|
+
return c.json({ error });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return await next();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Public assets
|
|
69
|
+
app.get(
|
|
70
|
+
"/assets/*",
|
|
71
|
+
serveStatic({
|
|
72
|
+
root: "uploads",
|
|
73
|
+
//rewriteRequestPath: (path) => path?.replace(/^\/assets/, ""),
|
|
74
|
+
})
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
app.post("/api/studio", (c, next) => {
|
|
78
|
+
let cookie = getCookie(c);
|
|
79
|
+
let secretKeyStudio = cookie["_STUDIO_SECRET_KEY_"];
|
|
80
|
+
let canPerform = isStudio(secretKeyStudio);
|
|
81
|
+
let cf = {
|
|
82
|
+
collections: Cfg.collections?.map((c) =>
|
|
83
|
+
pick(c, ["fields", "slug", "tenant_id", "type"])
|
|
84
|
+
),
|
|
85
|
+
tenants: Cfg.tenants.map((t) => t?.id),
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
if (canPerform) {
|
|
89
|
+
return c.json({
|
|
90
|
+
auth: true,
|
|
91
|
+
data: cf,
|
|
92
|
+
});
|
|
93
|
+
} else {
|
|
94
|
+
c.status(403);
|
|
95
|
+
return c.json({ message: "Unauthorized" });
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
Cfg.collections?.map((c) => {
|
|
100
|
+
if (c?.type == "media" && c.media?.enabled) {
|
|
101
|
+
let mediaPath = cleanPath(
|
|
102
|
+
"/files/" + (c?.media?.overwriteFolderName || c?.slug) + "/public/*"
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
//console.log("Media Path :", mediaPath);
|
|
106
|
+
app.get(
|
|
107
|
+
mediaPath,
|
|
108
|
+
serveStatic({
|
|
109
|
+
root: cleanPath("uploads"),
|
|
110
|
+
rewriteRequestPath: (path) => path?.replace(/^\/files/, ""),
|
|
111
|
+
onNotFound: (path, c) => {
|
|
112
|
+
console.log(`${path} is not found, you access ${c.req.path}`);
|
|
113
|
+
},
|
|
114
|
+
})
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// access controle for api
|
|
120
|
+
app.use(cleanPath(API_PATH), async (c, next) => {
|
|
121
|
+
let session = sessionStorage();
|
|
122
|
+
const { action, collection, cleanDeep, useCache } = c.req.query() as Q;
|
|
123
|
+
|
|
124
|
+
let tenant_id = c.var["tenant-id"];
|
|
125
|
+
let col = getCollection(collection, tenant_id);
|
|
126
|
+
let colAccess = col?.access?.hasOwnProperty(action) || null;
|
|
127
|
+
let nextLifecyle: any = false;
|
|
128
|
+
|
|
129
|
+
if (col && col?.access?.beforeAction) {
|
|
130
|
+
await col?.access?.beforeAction({
|
|
131
|
+
token: c.var["token"] || null,
|
|
132
|
+
action: action,
|
|
133
|
+
c: c,
|
|
134
|
+
isAuth: c.var?._v?.isAuth || false,
|
|
135
|
+
rest: new useRest({ tenant_id: tenant_id }),
|
|
136
|
+
session: session as any,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (
|
|
141
|
+
action == "auth" ||
|
|
142
|
+
action == "authCollection" ||
|
|
143
|
+
action == "execService" ||
|
|
144
|
+
action == "upload"
|
|
145
|
+
) {
|
|
146
|
+
return await next();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let performFromStudio = c.var["isStudio"] || false;
|
|
150
|
+
|
|
151
|
+
if (col && col?.access?.allAction) {
|
|
152
|
+
nextLifecyle = await col?.access?.allAction({
|
|
153
|
+
token: c.var["token"] || null,
|
|
154
|
+
action: action,
|
|
155
|
+
c: c,
|
|
156
|
+
isAuth: false,
|
|
157
|
+
rest: new useRest({ tenant_id: tenant_id }),
|
|
158
|
+
session: sessionStorage(),
|
|
159
|
+
});
|
|
160
|
+
} else {
|
|
161
|
+
if ((col && action && colAccess) ?? false) {
|
|
162
|
+
nextLifecyle = await col?.access[action]({
|
|
163
|
+
session: sessionStorage(),
|
|
164
|
+
action: action,
|
|
165
|
+
c: c,
|
|
166
|
+
isAuth: c.var?._v?.isAuth || false,
|
|
167
|
+
rest: new useRest({ tenant_id: tenant_id }),
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (nextLifecyle || performFromStudio) {
|
|
173
|
+
return await next();
|
|
174
|
+
} else {
|
|
175
|
+
c.status(403);
|
|
176
|
+
return c.json({
|
|
177
|
+
message: "Unauthorized",
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// API REST
|
|
183
|
+
app.post(cleanPath(API_PATH), async (c) => {
|
|
184
|
+
try {
|
|
185
|
+
var response;
|
|
186
|
+
var parseBody;
|
|
187
|
+
const { action, collection, cleanDeep, useCache, name } =
|
|
188
|
+
c.req.query() as Q;
|
|
189
|
+
const service = getService(name, c.var["tenant-id"]);
|
|
190
|
+
const col = getCollection(collection, c.var["tenant-id"]);
|
|
191
|
+
|
|
192
|
+
if (
|
|
193
|
+
c.req.raw?.headers
|
|
194
|
+
?.get("content-type")
|
|
195
|
+
?.match(/(multipart\/form-data)/) ||
|
|
196
|
+
c.req?.raw?.headers.get("content-type") ==
|
|
197
|
+
"application/x-www-form-urlencoded"
|
|
198
|
+
) {
|
|
199
|
+
parseBody = (await c?.req.parseBody()) || {};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const body = await c?.req
|
|
203
|
+
?.json()
|
|
204
|
+
.then((e) => e)
|
|
205
|
+
.catch((err) => null);
|
|
206
|
+
|
|
207
|
+
const rest = new useRest({
|
|
208
|
+
tenant_id: c.var["tenant-id"],
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Controlle action
|
|
212
|
+
if (!getAction(action)) {
|
|
213
|
+
throw new contextError(`Action ${action} not found`, 400);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Exec service
|
|
217
|
+
if (action == "execService") {
|
|
218
|
+
if (!service) return fn.error("Service not found", 404);
|
|
219
|
+
response = await service.fx({
|
|
220
|
+
data: body?.data || {},
|
|
221
|
+
rest: rest,
|
|
222
|
+
error: fn.error,
|
|
223
|
+
session: sessionStorage(),
|
|
224
|
+
isAuth: c.var?._v?.isAuth || false,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Controler on collection
|
|
229
|
+
if (
|
|
230
|
+
!getCollection(collection, c.var["tenant-id"]) &&
|
|
231
|
+
getAction(action) &&
|
|
232
|
+
collection
|
|
233
|
+
) {
|
|
234
|
+
throw new contextError(`Collection ${collection} not found`, 404);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// if action and collection
|
|
238
|
+
//getCollection(collection, c.var["tenant-id"]
|
|
239
|
+
if (
|
|
240
|
+
getAction(action) &&
|
|
241
|
+
getCollection(collection, c.var["tenant-id"]) &&
|
|
242
|
+
collection
|
|
243
|
+
) {
|
|
244
|
+
if (action == "authCollection") {
|
|
245
|
+
let reponse;
|
|
246
|
+
if (!col?.auth?.enabled) fn.error("Auth not enabled", 401);
|
|
247
|
+
if (!col?.auth?.handler) fn.error("Auth handler not set", 401);
|
|
248
|
+
if (col?.auth?.handler) {
|
|
249
|
+
let responseAuth = await col?.auth?.handler({
|
|
250
|
+
data: body,
|
|
251
|
+
c: c,
|
|
252
|
+
rest: rest,
|
|
253
|
+
error: fn.error,
|
|
254
|
+
session: sessionStorage(),
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
if (
|
|
258
|
+
responseAuth !== undefined &&
|
|
259
|
+
responseAuth !== null &&
|
|
260
|
+
responseAuth
|
|
261
|
+
) {
|
|
262
|
+
return c.json({
|
|
263
|
+
auth: true,
|
|
264
|
+
data: responseAuth,
|
|
265
|
+
token: jwt.sign(sessionStorage().get().state),
|
|
266
|
+
});
|
|
267
|
+
} else {
|
|
268
|
+
c.status(401);
|
|
269
|
+
return c.json({ message: "Authentification failed" });
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// upload
|
|
275
|
+
if (action == "upload") {
|
|
276
|
+
if (!parseBody?.file) return fn.error("File not found", 404);
|
|
277
|
+
if (col?.slug && col?.media?.enabled) {
|
|
278
|
+
let data = JSON.parse(parseBody?.data) || {};
|
|
279
|
+
rest.startTransaction();
|
|
280
|
+
const drive = new MediaDrive({
|
|
281
|
+
location: col?.media?.overwriteFolderName ?? col?.slug,
|
|
282
|
+
visibility: col?.media?.visibility ?? "public",
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
if (Array.isArray(parseBody?.file)) {
|
|
286
|
+
let insertedFiles = [];
|
|
287
|
+
for await (let file of parseBody?.file) {
|
|
288
|
+
let insertedFile = await drive.write(file.name, file);
|
|
289
|
+
insertedFiles.push(insertedFile);
|
|
290
|
+
}
|
|
291
|
+
response = await rest.insertMany(col?.slug, insertedFiles);
|
|
292
|
+
} else if (typeof parseBody?.file == "object") {
|
|
293
|
+
let insertedFile = await drive.write(
|
|
294
|
+
parseBody.file?.name,
|
|
295
|
+
parseBody.file
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
response = await rest.insertOne(col?.slug, {
|
|
299
|
+
...data,
|
|
300
|
+
_file: insertedFile,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
await rest?.commitTransaction();
|
|
304
|
+
// console.log("File is ", insertedFile);
|
|
305
|
+
} else {
|
|
306
|
+
return fn.error("Media config not set or not enabled", 404);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// aggregate
|
|
311
|
+
if (action == "aggregate") {
|
|
312
|
+
response = await rest.aggregate(collection, body.pipeline || []);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// udpateOne
|
|
316
|
+
|
|
317
|
+
if (action == "find") {
|
|
318
|
+
response = await rest.find(collection, body?.params || {});
|
|
319
|
+
}
|
|
320
|
+
if (action == "findOne") {
|
|
321
|
+
response = await rest.findOne(
|
|
322
|
+
collection,
|
|
323
|
+
body?.id || body._id,
|
|
324
|
+
body?.params || {}
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
if (action == "insertOne") {
|
|
328
|
+
response = await rest.insertOne(collection, body?.data || {});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (action == "insertMany") {
|
|
332
|
+
response = await rest.insertMany(collection, body?.data || {});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (action == "updateOne") {
|
|
336
|
+
response = await rest.updateOne(
|
|
337
|
+
collection,
|
|
338
|
+
body?.id || body?._id,
|
|
339
|
+
body?.update || {},
|
|
340
|
+
body?.options
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
if (action == "findOneAndUpdate") {
|
|
344
|
+
response = await rest.findOneAndUpdate(
|
|
345
|
+
collection,
|
|
346
|
+
body?.filter || {},
|
|
347
|
+
body?.update || {},
|
|
348
|
+
body?.options
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
if (action == "updateMany") {
|
|
352
|
+
response = await rest.updateMany(
|
|
353
|
+
collection,
|
|
354
|
+
body?.ids,
|
|
355
|
+
body?.update,
|
|
356
|
+
body?.options
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (action == "deleteOne") {
|
|
361
|
+
response = await rest.deleteOne(collection, body?.id || body._id);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (action == "deleteMany") {
|
|
365
|
+
response = await rest.deleteMany(collection, body?.ids);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (col?.privateFields?.length) {
|
|
370
|
+
response = omit(response, col?.privateFields || []);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return c.json(response);
|
|
374
|
+
} catch (err: any) {
|
|
375
|
+
if (Cfg?.debug) console.log(err?.message);
|
|
376
|
+
if (err?.code && (err?.code < 200 || err?.code > 599)) {
|
|
377
|
+
err.code = 409;
|
|
378
|
+
}
|
|
379
|
+
c.status(err.code || 500);
|
|
380
|
+
return c.json({
|
|
381
|
+
message: err?.message || err,
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
// Endpoint
|
|
387
|
+
Cfg?.endpoints?.map((e) => {
|
|
388
|
+
if (e?.enabled) {
|
|
389
|
+
e.handler({
|
|
390
|
+
router: app,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
return app;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export { HonoInstance };
|
package/app/index.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import consola from "consola";
|
|
2
|
+
import { Cfg, loadCfg } from "../config";
|
|
3
|
+
import { init } from "../lib";
|
|
4
|
+
import boxen from "boxen";
|
|
5
|
+
import "@colors/colors";
|
|
6
|
+
import pkg from "../package.json";
|
|
7
|
+
import findPort from "find-open-port";
|
|
8
|
+
import { HonoInstance } from "./hono";
|
|
9
|
+
async function runApp(config?: any, clb?: Function) {
|
|
10
|
+
console.log("\n");
|
|
11
|
+
|
|
12
|
+
await loadCfg();
|
|
13
|
+
//
|
|
14
|
+
|
|
15
|
+
// Load all ressouce
|
|
16
|
+
await init();
|
|
17
|
+
|
|
18
|
+
// Load Hono App and route api
|
|
19
|
+
const HonoApp = HonoInstance();
|
|
20
|
+
const PORT = Cfg.server?.port || process.env?.PORT || 4000;
|
|
21
|
+
|
|
22
|
+
await findPort.isAvailable(PORT).then((available: boolean) => {
|
|
23
|
+
// Start App server
|
|
24
|
+
|
|
25
|
+
if (available) {
|
|
26
|
+
Bun.serve({
|
|
27
|
+
port: PORT,
|
|
28
|
+
fetch: HonoApp.fetch,
|
|
29
|
+
maxRequestBodySize: 1024 * 1024 * 900,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
console.log();
|
|
33
|
+
let info = "";
|
|
34
|
+
info += "SERVER DETAILS :".gray.italic.underline;
|
|
35
|
+
info += `\n`;
|
|
36
|
+
info += `\n`;
|
|
37
|
+
info +=
|
|
38
|
+
`Env: ${
|
|
39
|
+
process.env.NODE_ENV === "production" ? "production" : "development"
|
|
40
|
+
}`.italic.blue.green + "\n";
|
|
41
|
+
info += `Server: http://localhost:${PORT}\n`.italic.blue;
|
|
42
|
+
info += `\n`;
|
|
43
|
+
info += "TENANTS DETAILS :".gray.italic.underline;
|
|
44
|
+
info += `\n`;
|
|
45
|
+
Cfg.tenants?.map((t: any) => {
|
|
46
|
+
info += `\n${t?.name?.blue || "_"} : ${t?.id?.green}`.italic;
|
|
47
|
+
});
|
|
48
|
+
info += `\n`;
|
|
49
|
+
|
|
50
|
+
if (clb) clb();
|
|
51
|
+
|
|
52
|
+
console.log(
|
|
53
|
+
boxen(info, {
|
|
54
|
+
borderStyle: "round",
|
|
55
|
+
title: `@libv/server ${pkg.version}`.italic,
|
|
56
|
+
padding: 1,
|
|
57
|
+
dimBorder: true,
|
|
58
|
+
titleAlignment: "center",
|
|
59
|
+
//float: "center",
|
|
60
|
+
//margin: 1,
|
|
61
|
+
borderColor: "gray",
|
|
62
|
+
})
|
|
63
|
+
);
|
|
64
|
+
} else {
|
|
65
|
+
console.error(`⚠️ Port ${PORT} is not available`);
|
|
66
|
+
console.log("");
|
|
67
|
+
process.exit();
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export { runApp };
|