improved global-project-element scopes separation
This commit is contained in:
parent
baef1fca2f
commit
b8f2274572
@ -1,10 +1,10 @@
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const http = require("http");
|
||||
const https = require("https");
|
||||
const { URL } = require("url");
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const { URL } = require('url');
|
||||
|
||||
let CONFIG_CACHE = null;
|
||||
|
||||
@ -40,7 +40,7 @@ async function createResponse(params, options = {}) {
|
||||
if (!Array.isArray(payload.input) || payload.input.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: "input_missing",
|
||||
error: 'input_missing',
|
||||
message: 'Parameter "input" is required and must be a non-empty array.',
|
||||
};
|
||||
}
|
||||
@ -56,7 +56,7 @@ async function createResponse(params, options = {}) {
|
||||
}
|
||||
|
||||
const data = initial.data;
|
||||
if (data && typeof data === "object" && data.ai_request_id) {
|
||||
if (data && typeof data === 'object' && data.ai_request_id) {
|
||||
const pollTimeout = Number(options.poll_timeout ?? 300);
|
||||
const pollInterval = Number(options.poll_interval ?? 5);
|
||||
return await awaitResponse(data.ai_request_id, {
|
||||
@ -78,16 +78,16 @@ async function request(pathValue, payload = {}, options = {}) {
|
||||
if (!resolvedPath) {
|
||||
return {
|
||||
success: false,
|
||||
error: "project_id_missing",
|
||||
message: "PROJECT_ID is not defined; cannot resolve AI proxy endpoint.",
|
||||
error: 'project_id_missing',
|
||||
message: 'PROJECT_ID is not defined; cannot resolve AI proxy endpoint.',
|
||||
};
|
||||
}
|
||||
|
||||
if (!cfg.projectUuid) {
|
||||
return {
|
||||
success: false,
|
||||
error: "project_uuid_missing",
|
||||
message: "PROJECT_UUID is not defined; aborting AI request.",
|
||||
error: 'project_uuid_missing',
|
||||
message: 'PROJECT_UUID is not defined; aborting AI request.',
|
||||
};
|
||||
}
|
||||
|
||||
@ -101,21 +101,21 @@ async function request(pathValue, payload = {}, options = {}) {
|
||||
const verifyTls = resolveVerifyTls(options.verify_tls, cfg.verifyTls);
|
||||
|
||||
const headers = {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
[cfg.projectHeader]: cfg.projectUuid,
|
||||
};
|
||||
if (Array.isArray(options.headers)) {
|
||||
for (const header of options.headers) {
|
||||
if (typeof header === "string" && header.includes(":")) {
|
||||
const [name, value] = header.split(":", 2);
|
||||
if (typeof header === 'string' && header.includes(':')) {
|
||||
const [name, value] = header.split(':', 2);
|
||||
headers[name.trim()] = value.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const body = JSON.stringify(bodyPayload);
|
||||
return sendRequest(url, "POST", body, headers, timeout, verifyTls);
|
||||
return sendRequest(url, 'POST', body, headers, timeout, verifyTls);
|
||||
}
|
||||
|
||||
async function fetchStatus(aiRequestId, options = {}) {
|
||||
@ -123,8 +123,8 @@ async function fetchStatus(aiRequestId, options = {}) {
|
||||
if (!cfg.projectUuid) {
|
||||
return {
|
||||
success: false,
|
||||
error: "project_uuid_missing",
|
||||
message: "PROJECT_UUID is not defined; aborting status check.",
|
||||
error: 'project_uuid_missing',
|
||||
message: 'PROJECT_UUID is not defined; aborting status check.',
|
||||
};
|
||||
}
|
||||
|
||||
@ -134,19 +134,19 @@ async function fetchStatus(aiRequestId, options = {}) {
|
||||
const verifyTls = resolveVerifyTls(options.verify_tls, cfg.verifyTls);
|
||||
|
||||
const headers = {
|
||||
Accept: "application/json",
|
||||
Accept: 'application/json',
|
||||
[cfg.projectHeader]: cfg.projectUuid,
|
||||
};
|
||||
if (Array.isArray(options.headers)) {
|
||||
for (const header of options.headers) {
|
||||
if (typeof header === "string" && header.includes(":")) {
|
||||
const [name, value] = header.split(":", 2);
|
||||
if (typeof header === 'string' && header.includes(':')) {
|
||||
const [name, value] = header.split(':', 2);
|
||||
headers[name.trim()] = value.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sendRequest(url, "GET", null, headers, timeout, verifyTls);
|
||||
return sendRequest(url, 'GET', null, headers, timeout, verifyTls);
|
||||
}
|
||||
|
||||
async function awaitResponse(aiRequestId, options = {}) {
|
||||
@ -165,8 +165,8 @@ async function awaitResponse(aiRequestId, options = {}) {
|
||||
|
||||
if (statusResp.success) {
|
||||
const data = statusResp.data || {};
|
||||
if (data && typeof data === "object") {
|
||||
if (data.status === "success") {
|
||||
if (data && typeof data === 'object') {
|
||||
if (data.status === 'success') {
|
||||
isPending = false;
|
||||
return {
|
||||
success: true,
|
||||
@ -174,12 +174,12 @@ async function awaitResponse(aiRequestId, options = {}) {
|
||||
data: data.response || data,
|
||||
};
|
||||
}
|
||||
if (data.status === "failed") {
|
||||
if (data.status === 'failed') {
|
||||
isPending = false;
|
||||
return {
|
||||
success: false,
|
||||
status: 500,
|
||||
error: String(data.error || "AI request failed"),
|
||||
error: String(data.error || 'AI request failed'),
|
||||
data,
|
||||
};
|
||||
}
|
||||
@ -191,8 +191,8 @@ async function awaitResponse(aiRequestId, options = {}) {
|
||||
if (Date.now() >= deadline) {
|
||||
return {
|
||||
success: false,
|
||||
error: "timeout",
|
||||
message: "Timed out waiting for AI response.",
|
||||
error: 'timeout',
|
||||
message: 'Timed out waiting for AI response.',
|
||||
};
|
||||
}
|
||||
|
||||
@ -201,13 +201,14 @@ async function awaitResponse(aiRequestId, options = {}) {
|
||||
}
|
||||
|
||||
function extractText(response) {
|
||||
const payload = response && typeof response === "object" ? response.data || response : null;
|
||||
if (!payload || typeof payload !== "object") {
|
||||
return "";
|
||||
const payload =
|
||||
response && typeof response === 'object' ? response.data || response : null;
|
||||
if (!payload || typeof payload !== 'object') {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (Array.isArray(payload.output)) {
|
||||
let combined = "";
|
||||
let combined = '';
|
||||
for (const item of payload.output) {
|
||||
if (!item || !Array.isArray(item.content)) {
|
||||
continue;
|
||||
@ -215,9 +216,9 @@ function extractText(response) {
|
||||
for (const block of item.content) {
|
||||
if (
|
||||
block &&
|
||||
typeof block === "object" &&
|
||||
block.type === "output_text" &&
|
||||
typeof block.text === "string" &&
|
||||
typeof block === 'object' &&
|
||||
block.type === 'output_text' &&
|
||||
typeof block.text === 'string' &&
|
||||
block.text.length > 0
|
||||
) {
|
||||
combined += block.text;
|
||||
@ -233,32 +234,38 @@ function extractText(response) {
|
||||
payload.choices &&
|
||||
payload.choices[0] &&
|
||||
payload.choices[0].message &&
|
||||
typeof payload.choices[0].message.content === "string"
|
||||
typeof payload.choices[0].message.content === 'string'
|
||||
) {
|
||||
return payload.choices[0].message.content;
|
||||
}
|
||||
|
||||
return "";
|
||||
return '';
|
||||
}
|
||||
|
||||
function decodeJsonFromResponse(response) {
|
||||
const text = extractText(response);
|
||||
if (!text) {
|
||||
throw new Error("No text found in AI response.");
|
||||
throw new Error('No text found in AI response.');
|
||||
}
|
||||
|
||||
const parsed = parseJson(text);
|
||||
if (parsed.ok && parsed.value && typeof parsed.value === "object") {
|
||||
if (parsed.ok && parsed.value && typeof parsed.value === 'object') {
|
||||
return parsed.value;
|
||||
}
|
||||
|
||||
const stripped = stripJsonFence(text);
|
||||
if (stripped !== text) {
|
||||
const parsedStripped = parseJson(stripped);
|
||||
if (parsedStripped.ok && parsedStripped.value && typeof parsedStripped.value === "object") {
|
||||
if (
|
||||
parsedStripped.ok &&
|
||||
parsedStripped.value &&
|
||||
typeof parsedStripped.value === 'object'
|
||||
) {
|
||||
return parsedStripped.value;
|
||||
}
|
||||
throw new Error(`JSON parse failed after stripping fences: ${parsedStripped.error}`);
|
||||
throw new Error(
|
||||
`JSON parse failed after stripping fences: ${parsedStripped.error}`,
|
||||
);
|
||||
}
|
||||
|
||||
throw new Error(`JSON parse failed: ${parsed.error}`);
|
||||
@ -271,7 +278,7 @@ function config() {
|
||||
|
||||
ensureEnvLoaded();
|
||||
|
||||
const baseUrl = process.env.AI_PROXY_BASE_URL || "https://flatlogic.com";
|
||||
const baseUrl = process.env.AI_PROXY_BASE_URL || 'https://flatlogic.com';
|
||||
const projectId = process.env.PROJECT_ID || null;
|
||||
let responsesPath = process.env.AI_RESPONSES_PATH || null;
|
||||
if (!responsesPath && projectId) {
|
||||
@ -286,8 +293,8 @@ function config() {
|
||||
responsesPath,
|
||||
projectId,
|
||||
projectUuid: process.env.PROJECT_UUID || null,
|
||||
projectHeader: process.env.AI_PROJECT_HEADER || "project-uuid",
|
||||
defaultModel: process.env.AI_DEFAULT_MODEL || "gpt-5-mini",
|
||||
projectHeader: process.env.AI_PROJECT_HEADER || 'project-uuid',
|
||||
defaultModel: process.env.AI_DEFAULT_MODEL || 'gpt-5-mini',
|
||||
timeout,
|
||||
verifyTls,
|
||||
};
|
||||
@ -296,29 +303,38 @@ function config() {
|
||||
}
|
||||
|
||||
function buildUrl(pathValue, baseUrl) {
|
||||
const trimmed = String(pathValue || "").trim();
|
||||
if (trimmed === "") {
|
||||
const trimmed = String(pathValue || '').trim();
|
||||
if (trimmed === '') {
|
||||
return baseUrl;
|
||||
}
|
||||
if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) {
|
||||
if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) {
|
||||
return trimmed;
|
||||
}
|
||||
if (trimmed.startsWith("/")) {
|
||||
if (trimmed.startsWith('/')) {
|
||||
return `${baseUrl}${trimmed}`;
|
||||
}
|
||||
return `${baseUrl}/${trimmed}`;
|
||||
}
|
||||
|
||||
function resolveStatusPath(aiRequestId, cfg) {
|
||||
const basePath = (cfg.responsesPath || "").replace(/\/+$/, "");
|
||||
const basePath = (cfg.responsesPath || '').replace(/\/+$/, '');
|
||||
if (!basePath) {
|
||||
return `/ai-request/${encodeURIComponent(String(aiRequestId))}/status`;
|
||||
}
|
||||
const normalized = basePath.endsWith("/ai-request") ? basePath : `${basePath}/ai-request`;
|
||||
const normalized = basePath.endsWith('/ai-request')
|
||||
? basePath
|
||||
: `${basePath}/ai-request`;
|
||||
return `${normalized}/${encodeURIComponent(String(aiRequestId))}/status`;
|
||||
}
|
||||
|
||||
function sendRequest(urlString, method, body, headers, timeoutSeconds, verifyTls) {
|
||||
function sendRequest(
|
||||
urlString,
|
||||
method,
|
||||
body,
|
||||
headers,
|
||||
timeoutSeconds,
|
||||
verifyTls,
|
||||
) {
|
||||
return new Promise((resolve) => {
|
||||
let targetUrl;
|
||||
try {
|
||||
@ -326,13 +342,13 @@ function sendRequest(urlString, method, body, headers, timeoutSeconds, verifyTls
|
||||
} catch (err) {
|
||||
resolve({
|
||||
success: false,
|
||||
error: "invalid_url",
|
||||
error: 'invalid_url',
|
||||
message: err.message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const isHttps = targetUrl.protocol === "https:";
|
||||
const isHttps = targetUrl.protocol === 'https:';
|
||||
const requestFn = isHttps ? https.request : http.request;
|
||||
const options = {
|
||||
protocol: targetUrl.protocol,
|
||||
@ -348,12 +364,12 @@ function sendRequest(urlString, method, body, headers, timeoutSeconds, verifyTls
|
||||
}
|
||||
|
||||
const req = requestFn(options, (res) => {
|
||||
let responseBody = "";
|
||||
res.setEncoding("utf8");
|
||||
res.on("data", (chunk) => {
|
||||
let responseBody = '';
|
||||
res.setEncoding('utf8');
|
||||
res.on('data', (chunk) => {
|
||||
responseBody += chunk;
|
||||
});
|
||||
res.on("end", () => {
|
||||
res.on('end', () => {
|
||||
const status = res.statusCode || 0;
|
||||
const parsed = parseJson(responseBody);
|
||||
const payload = parsed.ok ? parsed.value : responseBody;
|
||||
@ -372,9 +388,11 @@ function sendRequest(urlString, method, body, headers, timeoutSeconds, verifyTls
|
||||
}
|
||||
|
||||
const errorMessage =
|
||||
parsed.ok && payload && typeof payload === "object"
|
||||
? String(payload.error || payload.message || "AI proxy request failed")
|
||||
: String(responseBody || "AI proxy request failed");
|
||||
parsed.ok && payload && typeof payload === 'object'
|
||||
? String(
|
||||
payload.error || payload.message || 'AI proxy request failed',
|
||||
)
|
||||
: String(responseBody || 'AI proxy request failed');
|
||||
|
||||
resolve({
|
||||
success: false,
|
||||
@ -386,14 +404,14 @@ function sendRequest(urlString, method, body, headers, timeoutSeconds, verifyTls
|
||||
});
|
||||
});
|
||||
|
||||
req.on("timeout", () => {
|
||||
req.destroy(new Error("request_timeout"));
|
||||
req.on('timeout', () => {
|
||||
req.destroy(new Error('request_timeout'));
|
||||
});
|
||||
|
||||
req.on("error", (err) => {
|
||||
req.on('error', (err) => {
|
||||
resolve({
|
||||
success: false,
|
||||
error: "request_failed",
|
||||
error: 'request_failed',
|
||||
message: err.message,
|
||||
});
|
||||
});
|
||||
@ -406,8 +424,8 @@ function sendRequest(urlString, method, body, headers, timeoutSeconds, verifyTls
|
||||
}
|
||||
|
||||
function parseJson(value) {
|
||||
if (typeof value !== "string" || value.trim() === "") {
|
||||
return { ok: false, error: "empty_response" };
|
||||
if (typeof value !== 'string' || value.trim() === '') {
|
||||
return { ok: false, error: 'empty_response' };
|
||||
}
|
||||
try {
|
||||
return { ok: true, value: JSON.parse(value) };
|
||||
@ -418,11 +436,14 @@ function parseJson(value) {
|
||||
|
||||
function stripJsonFence(text) {
|
||||
const trimmed = text.trim();
|
||||
if (trimmed.startsWith("```json")) {
|
||||
return trimmed.replace(/^```json/, "").replace(/```$/, "").trim();
|
||||
if (trimmed.startsWith('```json')) {
|
||||
return trimmed
|
||||
.replace(/^```json/, '')
|
||||
.replace(/```$/, '')
|
||||
.trim();
|
||||
}
|
||||
if (trimmed.startsWith("```")) {
|
||||
return trimmed.replace(/^```/, "").replace(/```$/, "").trim();
|
||||
if (trimmed.startsWith('```')) {
|
||||
return trimmed.replace(/^```/, '').replace(/```$/, '').trim();
|
||||
}
|
||||
return text;
|
||||
}
|
||||
@ -436,7 +457,7 @@ function resolveVerifyTls(value, fallback) {
|
||||
if (value === undefined || value === null) {
|
||||
return Boolean(fallback);
|
||||
}
|
||||
return String(value).toLowerCase() !== "false" && String(value) !== "0";
|
||||
return String(value).toLowerCase() !== 'false' && String(value) !== '0';
|
||||
}
|
||||
|
||||
function ensureEnvLoaded() {
|
||||
@ -444,29 +465,32 @@ function ensureEnvLoaded() {
|
||||
return;
|
||||
}
|
||||
|
||||
const envPath = path.resolve(__dirname, "../../../../.env");
|
||||
const envPath = path.resolve(__dirname, '../../../../.env');
|
||||
if (!fs.existsSync(envPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let content;
|
||||
try {
|
||||
content = fs.readFileSync(envPath, "utf8");
|
||||
content = fs.readFileSync(envPath, 'utf8');
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to read executor .env: ${err.message}`);
|
||||
}
|
||||
|
||||
for (const line of content.split(/\r?\n/)) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith("#") || !trimmed.includes("=")) {
|
||||
if (!trimmed || trimmed.startsWith('#') || !trimmed.includes('=')) {
|
||||
continue;
|
||||
}
|
||||
const [rawKey, ...rest] = trimmed.split("=");
|
||||
const [rawKey, ...rest] = trimmed.split('=');
|
||||
const key = rawKey.trim();
|
||||
if (!key) {
|
||||
continue;
|
||||
}
|
||||
const value = rest.join("=").trim().replace(/^['"]|['"]$/g, "");
|
||||
const value = rest
|
||||
.join('=')
|
||||
.trim()
|
||||
.replace(/^['"]|['"]$/g, '');
|
||||
if (!process.env[key]) {
|
||||
process.env[key] = value;
|
||||
}
|
||||
|
||||
@ -10,59 +10,68 @@ const GoogleStrategy = require('passport-google-oauth2').Strategy;
|
||||
const MicrosoftStrategy = require('passport-microsoft').Strategy;
|
||||
const UsersDBApi = require('../db/api/users');
|
||||
|
||||
passport.use(
|
||||
new JWTstrategy(
|
||||
{
|
||||
passReqToCallback: true,
|
||||
secretOrKey: config.secret_key,
|
||||
jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(),
|
||||
},
|
||||
async (req, token, done) => {
|
||||
try {
|
||||
const user = await UsersDBApi.findBy({ email: token.user.email });
|
||||
|
||||
passport.use(new JWTstrategy({
|
||||
passReqToCallback: true,
|
||||
secretOrKey: config.secret_key,
|
||||
jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken()
|
||||
}, async (req, token, done) => {
|
||||
try {
|
||||
const user = await UsersDBApi.findBy( {email: token.user.email});
|
||||
if (user && user.disabled) {
|
||||
return done(new Error(`User '${user.email}' is disabled`));
|
||||
}
|
||||
|
||||
if (user && user.disabled) {
|
||||
return done (new Error(`User '${user.email}' is disabled`));
|
||||
}
|
||||
req.currentUser = user;
|
||||
|
||||
req.currentUser = user;
|
||||
return done(null, user);
|
||||
} catch (error) {
|
||||
done(error);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
return done(null, user);
|
||||
} catch (error) {
|
||||
done(error);
|
||||
}
|
||||
}));
|
||||
passport.use(
|
||||
new GoogleStrategy(
|
||||
{
|
||||
clientID: config.google.clientId,
|
||||
clientSecret: config.google.clientSecret,
|
||||
callbackURL: config.apiUrl + '/auth/signin/google/callback',
|
||||
passReqToCallback: true,
|
||||
},
|
||||
function (request, accessToken, refreshToken, profile, done) {
|
||||
socialStrategy(profile.email, profile, providers.GOOGLE, done);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
passport.use(new GoogleStrategy({
|
||||
clientID: config.google.clientId,
|
||||
clientSecret: config.google.clientSecret,
|
||||
callbackURL: config.apiUrl + '/auth/signin/google/callback',
|
||||
passReqToCallback: true
|
||||
},
|
||||
function (request, accessToken, refreshToken, profile, done) {
|
||||
socialStrategy(profile.email, profile, providers.GOOGLE, done);
|
||||
}
|
||||
));
|
||||
|
||||
|
||||
passport.use(new MicrosoftStrategy({
|
||||
clientID: config.microsoft.clientId,
|
||||
clientSecret: config.microsoft.clientSecret,
|
||||
callbackURL: config.apiUrl + '/auth/signin/microsoft/callback',
|
||||
passReqToCallback: true
|
||||
},
|
||||
function (request, accessToken, refreshToken, profile, done) {
|
||||
const email = profile._json.mail || profile._json.userPrincipalName;
|
||||
socialStrategy(email, profile, providers.MICROSOFT, done);
|
||||
}
|
||||
));
|
||||
passport.use(
|
||||
new MicrosoftStrategy(
|
||||
{
|
||||
clientID: config.microsoft.clientId,
|
||||
clientSecret: config.microsoft.clientSecret,
|
||||
callbackURL: config.apiUrl + '/auth/signin/microsoft/callback',
|
||||
passReqToCallback: true,
|
||||
},
|
||||
function (request, accessToken, refreshToken, profile, done) {
|
||||
const email = profile._json.mail || profile._json.userPrincipalName;
|
||||
socialStrategy(email, profile, providers.MICROSOFT, done);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
function socialStrategy(email, profile, provider, done) {
|
||||
db.users.findOrCreate({where: {email, provider}}).then(([user]) => {
|
||||
db.users.findOrCreate({ where: { email, provider } }).then(([user]) => {
|
||||
const body = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: profile.displayName,
|
||||
};
|
||||
const token = helpers.jwtSign({user: body});
|
||||
return done(null, {token});
|
||||
const token = helpers.jwtSign({ user: body });
|
||||
return done(null, { token });
|
||||
});
|
||||
}
|
||||
|
||||
@ -8,8 +8,8 @@ validateEnv();
|
||||
|
||||
const config = {
|
||||
gcloud: {
|
||||
bucket: "fldemo-files",
|
||||
hash: "afeefb9d49f5b7977577876b99532ac7"
|
||||
bucket: 'fldemo-files',
|
||||
hash: 'afeefb9d49f5b7977577876b99532ac7',
|
||||
},
|
||||
s3: {
|
||||
bucket: process.env.AWS_S3_BUCKET || '',
|
||||
@ -19,33 +19,33 @@ const config = {
|
||||
prefix: process.env.AWS_S3_PREFIX || 'afeefb9d49f5b7977577876b99532ac7',
|
||||
},
|
||||
bcrypt: {
|
||||
saltRounds: 12
|
||||
saltRounds: 12,
|
||||
},
|
||||
admin_pass: process.env.ADMIN_PASS || "88dbeaf8",
|
||||
user_pass: process.env.USER_PASS || "c3baadeda5c6",
|
||||
admin_email: process.env.ADMIN_EMAIL || "admin@flatlogic.com",
|
||||
admin_pass: process.env.ADMIN_PASS || '88dbeaf8',
|
||||
user_pass: process.env.USER_PASS || 'c3baadeda5c6',
|
||||
admin_email: process.env.ADMIN_EMAIL || 'admin@flatlogic.com',
|
||||
providers: {
|
||||
LOCAL: 'local',
|
||||
GOOGLE: 'google',
|
||||
MICROSOFT: 'microsoft'
|
||||
MICROSOFT: 'microsoft',
|
||||
},
|
||||
secret_key: process.env.SECRET_KEY || '88dbeaf8-e906-405e-9e41-c3baadeda5c6',
|
||||
remote: '',
|
||||
port: process.env.NODE_ENV === "production" ? "" : "8080",
|
||||
hostUI: process.env.NODE_ENV === "production" ? "" : "http://localhost",
|
||||
portUI: process.env.NODE_ENV === "production" ? "" : "3000",
|
||||
port: process.env.NODE_ENV === 'production' ? '' : '8080',
|
||||
hostUI: process.env.NODE_ENV === 'production' ? '' : 'http://localhost',
|
||||
portUI: process.env.NODE_ENV === 'production' ? '' : '3000',
|
||||
|
||||
portUIProd: process.env.NODE_ENV === "production" ? "" : ":3000",
|
||||
portUIProd: process.env.NODE_ENV === 'production' ? '' : ':3000',
|
||||
|
||||
swaggerUI: process.env.NODE_ENV === "production" ? "" : "http://localhost",
|
||||
swaggerPort: process.env.NODE_ENV === "production" ? "" : ":8080",
|
||||
swaggerUI: process.env.NODE_ENV === 'production' ? '' : 'http://localhost',
|
||||
swaggerPort: process.env.NODE_ENV === 'production' ? '' : ':8080',
|
||||
google: {
|
||||
clientId: process.env.GOOGLE_CLIENT_ID || '',
|
||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET || '',
|
||||
},
|
||||
microsoft: {
|
||||
clientId: process.env.MS_CLIENT_ID || '',
|
||||
clientSecret: process.env.MS_CLIENT_SECRET || '',
|
||||
clientId: process.env.MS_CLIENT_ID || '',
|
||||
clientSecret: process.env.MS_CLIENT_SECRET || '',
|
||||
},
|
||||
uploadDir: os.tmpdir(),
|
||||
email: {
|
||||
@ -58,26 +58,26 @@ const config = {
|
||||
},
|
||||
tls: {
|
||||
rejectUnauthorized: process.env.EMAIL_TLS_REJECT_UNAUTHORIZED !== 'false',
|
||||
}
|
||||
},
|
||||
},
|
||||
roles: {
|
||||
|
||||
admin: 'Administrator',
|
||||
|
||||
|
||||
|
||||
user: 'Analytics Viewer',
|
||||
|
||||
user: 'Analytics Viewer',
|
||||
},
|
||||
|
||||
project_uuid: '88dbeaf8-e906-405e-9e41-c3baadeda5c6',
|
||||
flHost: process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'dev_stage' ? 'https://flatlogic.com/projects' : 'http://localhost:3000/projects',
|
||||
|
||||
flHost:
|
||||
process.env.NODE_ENV === 'production' ||
|
||||
process.env.NODE_ENV === 'dev_stage'
|
||||
? 'https://flatlogic.com/projects'
|
||||
: 'http://localhost:3000/projects',
|
||||
|
||||
gpt_key: process.env.GPT_KEY || '',
|
||||
};
|
||||
|
||||
config.host = process.env.NODE_ENV === "production" ? config.remote : "http://localhost";
|
||||
config.host =
|
||||
process.env.NODE_ENV === 'production' ? config.remote : 'http://localhost';
|
||||
config.apiUrl = `${config.host}${config.port ? `:${config.port}` : ``}/api`;
|
||||
config.swaggerUrl = `${config.swaggerUI}${config.swaggerPort}`;
|
||||
config.uiUrl = `${config.hostUI}${config.portUI ? `:${config.portUI}` : ``}/#`;
|
||||
|
||||
@ -27,7 +27,15 @@ class Access_logsDBApi extends GenericDBApi {
|
||||
}
|
||||
|
||||
static get CSV_FIELDS() {
|
||||
return ['id', 'environment', 'path', 'ip_address', 'user_agent', 'accessed_at', 'createdAt'];
|
||||
return [
|
||||
'id',
|
||||
'environment',
|
||||
'path',
|
||||
'ip_address',
|
||||
'user_agent',
|
||||
'accessed_at',
|
||||
'createdAt',
|
||||
];
|
||||
}
|
||||
|
||||
static get AUTOCOMPLETE_FIELD() {
|
||||
@ -42,10 +50,7 @@ class Access_logsDBApi extends GenericDBApi {
|
||||
}
|
||||
|
||||
static get FIND_BY_INCLUDES() {
|
||||
return [
|
||||
{ association: 'project' },
|
||||
{ association: 'user' },
|
||||
];
|
||||
return [{ association: 'project' }, { association: 'user' }];
|
||||
}
|
||||
|
||||
static getFieldMapping(data) {
|
||||
@ -71,30 +76,50 @@ class Access_logsDBApi extends GenericDBApi {
|
||||
{
|
||||
model: db.projects,
|
||||
as: 'project',
|
||||
where: filter.project ? {
|
||||
[Op.or]: [
|
||||
{ id: { [Op.in]: filter.project.split('|').map(term => Utils.uuid(term)) } },
|
||||
{
|
||||
name: {
|
||||
[Op.or]: filter.project.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
|
||||
}
|
||||
},
|
||||
]
|
||||
} : {},
|
||||
where: filter.project
|
||||
? {
|
||||
[Op.or]: [
|
||||
{
|
||||
id: {
|
||||
[Op.in]: filter.project
|
||||
.split('|')
|
||||
.map((term) => Utils.uuid(term)),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: {
|
||||
[Op.or]: filter.project
|
||||
.split('|')
|
||||
.map((term) => ({ [Op.iLike]: `%${term}%` })),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: {},
|
||||
},
|
||||
{
|
||||
model: db.users,
|
||||
as: 'user',
|
||||
where: filter.user ? {
|
||||
[Op.or]: [
|
||||
{ id: { [Op.in]: filter.user.split('|').map(term => Utils.uuid(term)) } },
|
||||
{
|
||||
firstName: {
|
||||
[Op.or]: filter.user.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
|
||||
}
|
||||
},
|
||||
]
|
||||
} : {},
|
||||
where: filter.user
|
||||
? {
|
||||
[Op.or]: [
|
||||
{
|
||||
id: {
|
||||
[Op.in]: filter.user
|
||||
.split('|')
|
||||
.map((term) => Utils.uuid(term)),
|
||||
},
|
||||
},
|
||||
{
|
||||
firstName: {
|
||||
[Op.or]: filter.user
|
||||
.split('|')
|
||||
.map((term) => ({ [Op.iLike]: `%${term}%` })),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: {},
|
||||
},
|
||||
];
|
||||
|
||||
@ -145,9 +170,10 @@ class Access_logsDBApi extends GenericDBApi {
|
||||
where,
|
||||
include,
|
||||
distinct: true,
|
||||
order: filter.field && filter.sort
|
||||
? [[filter.field, filter.sort]]
|
||||
: [['createdAt', 'desc']],
|
||||
order:
|
||||
filter.field && filter.sort
|
||||
? [[filter.field, filter.sort]]
|
||||
: [['createdAt', 'desc']],
|
||||
transaction: options.transaction,
|
||||
};
|
||||
|
||||
|
||||
@ -27,7 +27,15 @@ class Asset_variantsDBApi extends GenericDBApi {
|
||||
}
|
||||
|
||||
static get CSV_FIELDS() {
|
||||
return ['id', 'variant_type', 'cdn_url', 'width_px', 'height_px', 'size_mb', 'createdAt'];
|
||||
return [
|
||||
'id',
|
||||
'variant_type',
|
||||
'cdn_url',
|
||||
'width_px',
|
||||
'height_px',
|
||||
'size_mb',
|
||||
'createdAt',
|
||||
];
|
||||
}
|
||||
|
||||
static get AUTOCOMPLETE_FIELD() {
|
||||
@ -35,9 +43,7 @@ class Asset_variantsDBApi extends GenericDBApi {
|
||||
}
|
||||
|
||||
static get ASSOCIATIONS() {
|
||||
return [
|
||||
{ field: 'asset', setter: 'setAsset', isArray: false },
|
||||
];
|
||||
return [{ field: 'asset', setter: 'setAsset', isArray: false }];
|
||||
}
|
||||
|
||||
static get FIND_BY_INCLUDES() {
|
||||
@ -67,16 +73,26 @@ class Asset_variantsDBApi extends GenericDBApi {
|
||||
{
|
||||
model: db.assets,
|
||||
as: 'asset',
|
||||
where: filter.asset ? {
|
||||
[Op.or]: [
|
||||
{ id: { [Op.in]: filter.asset.split('|').map(term => Utils.uuid(term)) } },
|
||||
{
|
||||
name: {
|
||||
[Op.or]: filter.asset.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
|
||||
}
|
||||
},
|
||||
]
|
||||
} : {},
|
||||
where: filter.asset
|
||||
? {
|
||||
[Op.or]: [
|
||||
{
|
||||
id: {
|
||||
[Op.in]: filter.asset
|
||||
.split('|')
|
||||
.map((term) => Utils.uuid(term)),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: {
|
||||
[Op.or]: filter.asset
|
||||
.split('|')
|
||||
.map((term) => ({ [Op.iLike]: `%${term}%` })),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: {},
|
||||
},
|
||||
];
|
||||
|
||||
@ -127,9 +143,10 @@ class Asset_variantsDBApi extends GenericDBApi {
|
||||
where,
|
||||
include,
|
||||
distinct: true,
|
||||
order: filter.field && filter.sort
|
||||
? [[filter.field, filter.sort]]
|
||||
: [['createdAt', 'desc']],
|
||||
order:
|
||||
filter.field && filter.sort
|
||||
? [[filter.field, filter.sort]]
|
||||
: [['createdAt', 'desc']],
|
||||
transaction: options.transaction,
|
||||
};
|
||||
|
||||
|
||||
@ -27,7 +27,17 @@ class AssetsDBApi extends GenericDBApi {
|
||||
}
|
||||
|
||||
static get CSV_FIELDS() {
|
||||
return ['id', 'name', 'asset_type', 'type', 'cdn_url', 'storage_key', 'mime_type', 'size_mb', 'createdAt'];
|
||||
return [
|
||||
'id',
|
||||
'name',
|
||||
'asset_type',
|
||||
'type',
|
||||
'cdn_url',
|
||||
'storage_key',
|
||||
'mime_type',
|
||||
'size_mb',
|
||||
'createdAt',
|
||||
];
|
||||
}
|
||||
|
||||
static get AUTOCOMPLETE_FIELD() {
|
||||
@ -35,9 +45,7 @@ class AssetsDBApi extends GenericDBApi {
|
||||
}
|
||||
|
||||
static get ASSOCIATIONS() {
|
||||
return [
|
||||
{ field: 'project', setter: 'setProject', isArray: false },
|
||||
];
|
||||
return [{ field: 'project', setter: 'setProject', isArray: false }];
|
||||
}
|
||||
|
||||
static get FIND_BY_INCLUDES() {
|
||||
@ -49,7 +57,12 @@ class AssetsDBApi extends GenericDBApi {
|
||||
|
||||
static get RELATION_FILTERS() {
|
||||
return [
|
||||
{ filterKey: 'project', model: db.projects, as: 'project', searchField: 'name' },
|
||||
{
|
||||
filterKey: 'project',
|
||||
model: db.projects,
|
||||
as: 'project',
|
||||
searchField: 'name',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@ -83,16 +96,26 @@ class AssetsDBApi extends GenericDBApi {
|
||||
{
|
||||
model: db.projects,
|
||||
as: 'project',
|
||||
where: filter.project ? {
|
||||
[Op.or]: [
|
||||
{ id: { [Op.in]: filter.project.split('|').map(term => Utils.uuid(term)) } },
|
||||
{
|
||||
name: {
|
||||
[Op.or]: filter.project.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
|
||||
}
|
||||
},
|
||||
]
|
||||
} : {},
|
||||
where: filter.project
|
||||
? {
|
||||
[Op.or]: [
|
||||
{
|
||||
id: {
|
||||
[Op.in]: filter.project
|
||||
.split('|')
|
||||
.map((term) => Utils.uuid(term)),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: {
|
||||
[Op.or]: filter.project
|
||||
.split('|')
|
||||
.map((term) => ({ [Op.iLike]: `%${term}%` })),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: {},
|
||||
},
|
||||
];
|
||||
|
||||
@ -143,9 +166,10 @@ class AssetsDBApi extends GenericDBApi {
|
||||
where,
|
||||
include,
|
||||
distinct: true,
|
||||
order: filter.field && filter.sort
|
||||
? [[filter.field, filter.sort]]
|
||||
: [['createdAt', 'desc']],
|
||||
order:
|
||||
filter.field && filter.sort
|
||||
? [[filter.field, filter.sort]]
|
||||
: [['createdAt', 'desc']],
|
||||
transaction: options.transaction,
|
||||
};
|
||||
|
||||
|
||||
@ -67,12 +67,15 @@ class GenericDBApi {
|
||||
createdById: currentUser.id,
|
||||
updatedById: currentUser.id,
|
||||
},
|
||||
{ transaction }
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
for (const assoc of this.ASSOCIATIONS) {
|
||||
if (data[assoc.field] !== undefined) {
|
||||
await record[assoc.setter](data[assoc.field] || (assoc.isArray ? [] : null), { transaction });
|
||||
await record[assoc.setter](
|
||||
data[assoc.field] || (assoc.isArray ? [] : null),
|
||||
{ transaction },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -161,9 +164,8 @@ class GenericDBApi {
|
||||
|
||||
static async findBy(where, options = {}) {
|
||||
const transaction = options.transaction;
|
||||
const include = options.include !== undefined
|
||||
? options.include
|
||||
: this.FIND_BY_INCLUDES;
|
||||
const include =
|
||||
options.include !== undefined ? options.include : this.FIND_BY_INCLUDES;
|
||||
|
||||
const record = await this.MODEL.findOne({
|
||||
where,
|
||||
@ -237,16 +239,27 @@ class GenericDBApi {
|
||||
model: rel.model,
|
||||
as: rel.as,
|
||||
required: searchTerms.length > 0,
|
||||
where: searchTerms.length > 0 ? {
|
||||
[Op.or]: [
|
||||
{ id: { [Op.in]: searchTerms.map(term => Utils.uuid(term)) } },
|
||||
rel.searchField ? {
|
||||
[rel.searchField]: {
|
||||
[Op.or]: searchTerms.map(term => ({ [Op.iLike]: `%${term}%` }))
|
||||
where:
|
||||
searchTerms.length > 0
|
||||
? {
|
||||
[Op.or]: [
|
||||
{
|
||||
id: {
|
||||
[Op.in]: searchTerms.map((term) => Utils.uuid(term)),
|
||||
},
|
||||
},
|
||||
rel.searchField
|
||||
? {
|
||||
[rel.searchField]: {
|
||||
[Op.or]: searchTerms.map((term) => ({
|
||||
[Op.iLike]: `%${term}%`,
|
||||
})),
|
||||
},
|
||||
}
|
||||
: {},
|
||||
],
|
||||
}
|
||||
} : {}
|
||||
]
|
||||
} : undefined
|
||||
: undefined,
|
||||
};
|
||||
include = [relInclude, ...include];
|
||||
}
|
||||
@ -256,9 +269,10 @@ class GenericDBApi {
|
||||
where,
|
||||
include,
|
||||
distinct: true,
|
||||
order: filter.field && filter.sort
|
||||
? [[filter.field, filter.sort]]
|
||||
: [['createdAt', 'desc']],
|
||||
order:
|
||||
filter.field && filter.sort
|
||||
? [[filter.field, filter.sort]]
|
||||
: [['createdAt', 'desc']],
|
||||
transaction: options.transaction,
|
||||
};
|
||||
|
||||
|
||||
@ -23,7 +23,14 @@ class Element_type_defaultsDBApi extends GenericDBApi {
|
||||
}
|
||||
|
||||
static get CSV_FIELDS() {
|
||||
return ['id', 'element_type', 'name', 'sort_order', 'is_active', 'createdAt'];
|
||||
return [
|
||||
'id',
|
||||
'element_type',
|
||||
'name',
|
||||
'sort_order',
|
||||
'is_active',
|
||||
'createdAt',
|
||||
];
|
||||
}
|
||||
|
||||
static get AUTOCOMPLETE_FIELD() {
|
||||
@ -37,7 +44,8 @@ class Element_type_defaultsDBApi extends GenericDBApi {
|
||||
name: data.name ?? null,
|
||||
sort_order: data.sort_order ?? 0,
|
||||
default_settings_json:
|
||||
data.default_settings_json === null || data.default_settings_json === undefined
|
||||
data.default_settings_json === null ||
|
||||
data.default_settings_json === undefined
|
||||
? null
|
||||
: typeof data.default_settings_json === 'string'
|
||||
? data.default_settings_json
|
||||
|
||||
@ -3,16 +3,9 @@ const assert = require('assert');
|
||||
const services = require('../../services/file');
|
||||
|
||||
module.exports = class FileDBApi {
|
||||
static async replaceRelationFiles(
|
||||
relation,
|
||||
rawFiles,
|
||||
options,
|
||||
) {
|
||||
static async replaceRelationFiles(relation, rawFiles, options) {
|
||||
assert(relation.belongsTo, 'belongsTo is required');
|
||||
assert(
|
||||
relation.belongsToColumn,
|
||||
'belongsToColumn is required',
|
||||
);
|
||||
assert(relation.belongsToColumn, 'belongsToColumn is required');
|
||||
assert(relation.belongsToId, 'belongsToId is required');
|
||||
|
||||
let files = [];
|
||||
@ -29,11 +22,9 @@ module.exports = class FileDBApi {
|
||||
|
||||
static async _addFiles(relation, files, options) {
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
const currentUser = (options && options.currentUser) || {id: null};
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
|
||||
const inexistentFiles = files.filter(
|
||||
(file) => !!file.new,
|
||||
);
|
||||
const inexistentFiles = files.filter((file) => !!file.new);
|
||||
|
||||
for (const file of inexistentFiles) {
|
||||
await db.file.create(
|
||||
@ -55,11 +46,7 @@ module.exports = class FileDBApi {
|
||||
}
|
||||
}
|
||||
|
||||
static async _removeLegacyFiles(
|
||||
relation,
|
||||
files,
|
||||
options,
|
||||
) {
|
||||
static async _removeLegacyFiles(relation, files, options) {
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const filesToDelete = await db.file.findAll({
|
||||
@ -68,10 +55,9 @@ module.exports = class FileDBApi {
|
||||
belongsToId: relation.belongsToId,
|
||||
belongsToColumn: relation.belongsToColumn,
|
||||
id: {
|
||||
[db.Sequelize.Op
|
||||
.notIn]: files
|
||||
[db.Sequelize.Op.notIn]: files
|
||||
.filter((file) => !file.new)
|
||||
.map((file) => file.id)
|
||||
.map((file) => file.id),
|
||||
},
|
||||
},
|
||||
transaction,
|
||||
|
||||
@ -27,7 +27,15 @@ class Presigned_url_requestsDBApi extends GenericDBApi {
|
||||
}
|
||||
|
||||
static get CSV_FIELDS() {
|
||||
return ['id', 'purpose', 'asset_type', 'requested_key', 'mime_type', 'status', 'createdAt'];
|
||||
return [
|
||||
'id',
|
||||
'purpose',
|
||||
'asset_type',
|
||||
'requested_key',
|
||||
'mime_type',
|
||||
'status',
|
||||
'createdAt',
|
||||
];
|
||||
}
|
||||
|
||||
static get AUTOCOMPLETE_FIELD() {
|
||||
@ -42,10 +50,7 @@ class Presigned_url_requestsDBApi extends GenericDBApi {
|
||||
}
|
||||
|
||||
static get FIND_BY_INCLUDES() {
|
||||
return [
|
||||
{ association: 'project' },
|
||||
{ association: 'user' },
|
||||
];
|
||||
return [{ association: 'project' }, { association: 'user' }];
|
||||
}
|
||||
|
||||
static getFieldMapping(data) {
|
||||
@ -73,30 +78,50 @@ class Presigned_url_requestsDBApi extends GenericDBApi {
|
||||
{
|
||||
model: db.projects,
|
||||
as: 'project',
|
||||
where: filter.project ? {
|
||||
[Op.or]: [
|
||||
{ id: { [Op.in]: filter.project.split('|').map(term => Utils.uuid(term)) } },
|
||||
{
|
||||
name: {
|
||||
[Op.or]: filter.project.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
|
||||
}
|
||||
},
|
||||
]
|
||||
} : {},
|
||||
where: filter.project
|
||||
? {
|
||||
[Op.or]: [
|
||||
{
|
||||
id: {
|
||||
[Op.in]: filter.project
|
||||
.split('|')
|
||||
.map((term) => Utils.uuid(term)),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: {
|
||||
[Op.or]: filter.project
|
||||
.split('|')
|
||||
.map((term) => ({ [Op.iLike]: `%${term}%` })),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: {},
|
||||
},
|
||||
{
|
||||
model: db.users,
|
||||
as: 'user',
|
||||
where: filter.user ? {
|
||||
[Op.or]: [
|
||||
{ id: { [Op.in]: filter.user.split('|').map(term => Utils.uuid(term)) } },
|
||||
{
|
||||
firstName: {
|
||||
[Op.or]: filter.user.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
|
||||
}
|
||||
},
|
||||
]
|
||||
} : {},
|
||||
where: filter.user
|
||||
? {
|
||||
[Op.or]: [
|
||||
{
|
||||
id: {
|
||||
[Op.in]: filter.user
|
||||
.split('|')
|
||||
.map((term) => Utils.uuid(term)),
|
||||
},
|
||||
},
|
||||
{
|
||||
firstName: {
|
||||
[Op.or]: filter.user
|
||||
.split('|')
|
||||
.map((term) => ({ [Op.iLike]: `%${term}%` })),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: {},
|
||||
},
|
||||
];
|
||||
|
||||
@ -147,9 +172,10 @@ class Presigned_url_requestsDBApi extends GenericDBApi {
|
||||
where,
|
||||
include,
|
||||
distinct: true,
|
||||
order: filter.field && filter.sort
|
||||
? [[filter.field, filter.sort]]
|
||||
: [['createdAt', 'desc']],
|
||||
order:
|
||||
filter.field && filter.sort
|
||||
? [[filter.field, filter.sort]]
|
||||
: [['createdAt', 'desc']],
|
||||
transaction: options.transaction,
|
||||
};
|
||||
|
||||
|
||||
@ -31,7 +31,17 @@ class Project_audio_tracksDBApi extends GenericDBApi {
|
||||
}
|
||||
|
||||
static get CSV_FIELDS() {
|
||||
return ['id', 'environment', 'source_key', 'name', 'slug', 'url', 'loop', 'volume', 'createdAt'];
|
||||
return [
|
||||
'id',
|
||||
'environment',
|
||||
'source_key',
|
||||
'name',
|
||||
'slug',
|
||||
'url',
|
||||
'loop',
|
||||
'volume',
|
||||
'createdAt',
|
||||
];
|
||||
}
|
||||
|
||||
static get AUTOCOMPLETE_FIELD() {
|
||||
@ -39,9 +49,7 @@ class Project_audio_tracksDBApi extends GenericDBApi {
|
||||
}
|
||||
|
||||
static get ASSOCIATIONS() {
|
||||
return [
|
||||
{ field: 'project', setter: 'setProject', isArray: false },
|
||||
];
|
||||
return [{ field: 'project', setter: 'setProject', isArray: false }];
|
||||
}
|
||||
|
||||
static getFieldMapping(data) {
|
||||
@ -64,7 +72,7 @@ class Project_audio_tracksDBApi extends GenericDBApi {
|
||||
const queryWhere = applyRuntimeEnvironment({ ...where }, options);
|
||||
const projectInclude = applyRuntimeProjectFilter(
|
||||
{ model: db.projects, as: 'project' },
|
||||
options
|
||||
options,
|
||||
);
|
||||
|
||||
const record = await this.MODEL.findOne({
|
||||
@ -89,16 +97,26 @@ class Project_audio_tracksDBApi extends GenericDBApi {
|
||||
{
|
||||
model: db.projects,
|
||||
as: 'project',
|
||||
where: filter.project ? {
|
||||
[Op.or]: [
|
||||
{ id: { [Op.in]: filter.project.split('|').map(term => Utils.uuid(term)) } },
|
||||
{
|
||||
name: {
|
||||
[Op.or]: filter.project.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
|
||||
}
|
||||
},
|
||||
]
|
||||
} : {},
|
||||
where: filter.project
|
||||
? {
|
||||
[Op.or]: [
|
||||
{
|
||||
id: {
|
||||
[Op.in]: filter.project
|
||||
.split('|')
|
||||
.map((term) => Utils.uuid(term)),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: {
|
||||
[Op.or]: filter.project
|
||||
.split('|')
|
||||
.map((term) => ({ [Op.iLike]: `%${term}%` })),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: {},
|
||||
},
|
||||
];
|
||||
|
||||
@ -153,9 +171,10 @@ class Project_audio_tracksDBApi extends GenericDBApi {
|
||||
where,
|
||||
include,
|
||||
distinct: true,
|
||||
order: filter.field && filter.sort
|
||||
? [[filter.field, filter.sort]]
|
||||
: [['createdAt', 'desc']],
|
||||
order:
|
||||
filter.field && filter.sort
|
||||
? [[filter.field, filter.sort]]
|
||||
: [['createdAt', 'desc']],
|
||||
transaction: options.transaction,
|
||||
};
|
||||
|
||||
|
||||
@ -27,26 +27,34 @@ class Project_element_defaultsDBApi extends GenericDBApi {
|
||||
}
|
||||
|
||||
static get ASSOCIATIONS() {
|
||||
return [
|
||||
{ field: 'project', setter: 'setProject', isArray: false },
|
||||
];
|
||||
return [{ field: 'project', setter: 'setProject', isArray: false }];
|
||||
}
|
||||
|
||||
static get RELATION_FILTERS() {
|
||||
return [
|
||||
{ filterKey: 'project', model: db.projects, as: 'project', searchField: 'name' },
|
||||
{
|
||||
filterKey: 'project',
|
||||
model: db.projects,
|
||||
as: 'project',
|
||||
searchField: 'name',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
static get FIND_ALL_INCLUDES() {
|
||||
return [
|
||||
{ association: 'project' },
|
||||
{ association: 'source_element' },
|
||||
];
|
||||
return [{ association: 'project' }, { association: 'source_element' }];
|
||||
}
|
||||
|
||||
static get CSV_FIELDS() {
|
||||
return ['id', 'element_type', 'name', 'sort_order', 'projectId', 'snapshot_version', 'createdAt'];
|
||||
return [
|
||||
'id',
|
||||
'element_type',
|
||||
'name',
|
||||
'sort_order',
|
||||
'projectId',
|
||||
'snapshot_version',
|
||||
'createdAt',
|
||||
];
|
||||
}
|
||||
|
||||
static get AUTOCOMPLETE_FIELD() {
|
||||
@ -90,16 +98,26 @@ class Project_element_defaultsDBApi extends GenericDBApi {
|
||||
{
|
||||
model: db.projects,
|
||||
as: 'project',
|
||||
where: projectFilter ? {
|
||||
[Op.or]: [
|
||||
{ id: { [Op.in]: projectFilter.split('|').map(term => Utils.uuid(term)) } },
|
||||
{
|
||||
name: {
|
||||
[Op.or]: projectFilter.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
|
||||
}
|
||||
},
|
||||
]
|
||||
} : {},
|
||||
where: projectFilter
|
||||
? {
|
||||
[Op.or]: [
|
||||
{
|
||||
id: {
|
||||
[Op.in]: projectFilter
|
||||
.split('|')
|
||||
.map((term) => Utils.uuid(term)),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: {
|
||||
[Op.or]: projectFilter
|
||||
.split('|')
|
||||
.map((term) => ({ [Op.iLike]: `%${term}%` })),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: {},
|
||||
},
|
||||
{
|
||||
model: db.element_type_defaults,
|
||||
@ -151,9 +169,10 @@ class Project_element_defaultsDBApi extends GenericDBApi {
|
||||
where,
|
||||
include,
|
||||
distinct: true,
|
||||
order: filter.field && filter.sort
|
||||
? [[filter.field, filter.sort]]
|
||||
: [['sort_order', 'asc']],
|
||||
order:
|
||||
filter.field && filter.sort
|
||||
? [[filter.field, filter.sort]]
|
||||
: [['sort_order', 'asc']],
|
||||
transaction: options.transaction,
|
||||
};
|
||||
|
||||
@ -223,7 +242,7 @@ class Project_element_defaultsDBApi extends GenericDBApi {
|
||||
{
|
||||
transaction: options.transaction,
|
||||
returning: true,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return projectDefaults;
|
||||
@ -253,7 +272,9 @@ class Project_element_defaultsDBApi extends GenericDBApi {
|
||||
});
|
||||
|
||||
if (!globalDefault) {
|
||||
throw new Error(`No global default found for element type: ${projectDefault.element_type}`);
|
||||
throw new Error(
|
||||
`No global default found for element type: ${projectDefault.element_type}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Update with global settings and increment version
|
||||
@ -270,7 +291,7 @@ class Project_element_defaultsDBApi extends GenericDBApi {
|
||||
},
|
||||
{
|
||||
transaction: options.transaction,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return projectDefault.reload();
|
||||
@ -309,15 +330,18 @@ class Project_element_defaultsDBApi extends GenericDBApi {
|
||||
}
|
||||
|
||||
// Parse JSON settings for comparison
|
||||
const projectSettings = typeof projectDefault.settings_json === 'string'
|
||||
? JSON.parse(projectDefault.settings_json || '{}')
|
||||
: projectDefault.settings_json || {};
|
||||
const projectSettings =
|
||||
typeof projectDefault.settings_json === 'string'
|
||||
? JSON.parse(projectDefault.settings_json || '{}')
|
||||
: projectDefault.settings_json || {};
|
||||
|
||||
const globalSettings = typeof globalDefault.default_settings_json === 'string'
|
||||
? JSON.parse(globalDefault.default_settings_json || '{}')
|
||||
: globalDefault.default_settings_json || {};
|
||||
const globalSettings =
|
||||
typeof globalDefault.default_settings_json === 'string'
|
||||
? JSON.parse(globalDefault.default_settings_json || '{}')
|
||||
: globalDefault.default_settings_json || {};
|
||||
|
||||
const isDifferent = JSON.stringify(projectSettings) !== JSON.stringify(globalSettings) ||
|
||||
const isDifferent =
|
||||
JSON.stringify(projectSettings) !== JSON.stringify(globalSettings) ||
|
||||
projectDefault.name !== globalDefault.name ||
|
||||
projectDefault.sort_order !== globalDefault.sort_order;
|
||||
|
||||
|
||||
@ -27,7 +27,14 @@ class Project_membershipsDBApi extends GenericDBApi {
|
||||
}
|
||||
|
||||
static get CSV_FIELDS() {
|
||||
return ['id', 'access_level', 'is_active', 'invited_at', 'accepted_at', 'createdAt'];
|
||||
return [
|
||||
'id',
|
||||
'access_level',
|
||||
'is_active',
|
||||
'invited_at',
|
||||
'accepted_at',
|
||||
'createdAt',
|
||||
];
|
||||
}
|
||||
|
||||
static get AUTOCOMPLETE_FIELD() {
|
||||
@ -42,10 +49,7 @@ class Project_membershipsDBApi extends GenericDBApi {
|
||||
}
|
||||
|
||||
static get FIND_BY_INCLUDES() {
|
||||
return [
|
||||
{ association: 'project' },
|
||||
{ association: 'user' },
|
||||
];
|
||||
return [{ association: 'project' }, { association: 'user' }];
|
||||
}
|
||||
|
||||
static getFieldMapping(data) {
|
||||
@ -70,30 +74,50 @@ class Project_membershipsDBApi extends GenericDBApi {
|
||||
{
|
||||
model: db.projects,
|
||||
as: 'project',
|
||||
where: filter.project ? {
|
||||
[Op.or]: [
|
||||
{ id: { [Op.in]: filter.project.split('|').map(term => Utils.uuid(term)) } },
|
||||
{
|
||||
name: {
|
||||
[Op.or]: filter.project.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
|
||||
}
|
||||
},
|
||||
]
|
||||
} : {},
|
||||
where: filter.project
|
||||
? {
|
||||
[Op.or]: [
|
||||
{
|
||||
id: {
|
||||
[Op.in]: filter.project
|
||||
.split('|')
|
||||
.map((term) => Utils.uuid(term)),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: {
|
||||
[Op.or]: filter.project
|
||||
.split('|')
|
||||
.map((term) => ({ [Op.iLike]: `%${term}%` })),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: {},
|
||||
},
|
||||
{
|
||||
model: db.users,
|
||||
as: 'user',
|
||||
where: filter.user ? {
|
||||
[Op.or]: [
|
||||
{ id: { [Op.in]: filter.user.split('|').map(term => Utils.uuid(term)) } },
|
||||
{
|
||||
firstName: {
|
||||
[Op.or]: filter.user.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
|
||||
}
|
||||
},
|
||||
]
|
||||
} : {},
|
||||
where: filter.user
|
||||
? {
|
||||
[Op.or]: [
|
||||
{
|
||||
id: {
|
||||
[Op.in]: filter.user
|
||||
.split('|')
|
||||
.map((term) => Utils.uuid(term)),
|
||||
},
|
||||
},
|
||||
{
|
||||
firstName: {
|
||||
[Op.or]: filter.user
|
||||
.split('|')
|
||||
.map((term) => ({ [Op.iLike]: `%${term}%` })),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: {},
|
||||
},
|
||||
];
|
||||
|
||||
@ -138,9 +162,10 @@ class Project_membershipsDBApi extends GenericDBApi {
|
||||
where,
|
||||
include,
|
||||
distinct: true,
|
||||
order: filter.field && filter.sort
|
||||
? [[filter.field, filter.sort]]
|
||||
: [['createdAt', 'desc']],
|
||||
order:
|
||||
filter.field && filter.sort
|
||||
? [[filter.field, filter.sort]]
|
||||
: [['createdAt', 'desc']],
|
||||
transaction: options.transaction,
|
||||
};
|
||||
|
||||
|
||||
@ -16,7 +16,17 @@ class ProjectsDBApi extends GenericDBApi {
|
||||
}
|
||||
|
||||
static get SEARCHABLE_FIELDS() {
|
||||
return ['name', 'slug', 'description', 'logo_url', 'favicon_url', 'og_image_url', 'theme_config_json', 'custom_css_json', 'cdn_base_url'];
|
||||
return [
|
||||
'name',
|
||||
'slug',
|
||||
'description',
|
||||
'logo_url',
|
||||
'favicon_url',
|
||||
'og_image_url',
|
||||
'theme_config_json',
|
||||
'custom_css_json',
|
||||
'cdn_base_url',
|
||||
];
|
||||
}
|
||||
|
||||
static get RANGE_FIELDS() {
|
||||
@ -28,7 +38,15 @@ class ProjectsDBApi extends GenericDBApi {
|
||||
}
|
||||
|
||||
static get CSV_FIELDS() {
|
||||
return ['id', 'name', 'slug', 'description', 'logo_url', 'cdn_base_url', 'createdAt'];
|
||||
return [
|
||||
'id',
|
||||
'name',
|
||||
'slug',
|
||||
'description',
|
||||
'logo_url',
|
||||
'cdn_base_url',
|
||||
'createdAt',
|
||||
];
|
||||
}
|
||||
|
||||
static get AUTOCOMPLETE_FIELD() {
|
||||
@ -81,9 +99,8 @@ class ProjectsDBApi extends GenericDBApi {
|
||||
queryWhere.slug = runtimeProjectSlug;
|
||||
}
|
||||
|
||||
const include = options.include !== undefined
|
||||
? options.include
|
||||
: this.DEFAULT_INCLUDES;
|
||||
const include =
|
||||
options.include !== undefined ? options.include : this.DEFAULT_INCLUDES;
|
||||
|
||||
const record = await this.MODEL.findOne({
|
||||
where: queryWhere,
|
||||
@ -113,7 +130,10 @@ class ProjectsDBApi extends GenericDBApi {
|
||||
});
|
||||
} catch (error) {
|
||||
// Log but don't fail project creation if snapshot fails
|
||||
console.error('Failed to snapshot global element defaults to project:', error);
|
||||
console.error(
|
||||
'Failed to snapshot global element defaults to project:',
|
||||
error,
|
||||
);
|
||||
}
|
||||
|
||||
return project;
|
||||
@ -182,9 +202,10 @@ class ProjectsDBApi extends GenericDBApi {
|
||||
where,
|
||||
include,
|
||||
distinct: true,
|
||||
order: filter.field && filter.sort
|
||||
? [[filter.field, filter.sort]]
|
||||
: [['createdAt', 'desc']],
|
||||
order:
|
||||
filter.field && filter.sort
|
||||
? [[filter.field, filter.sort]]
|
||||
: [['createdAt', 'desc']],
|
||||
transaction: options.transaction,
|
||||
};
|
||||
|
||||
|
||||
@ -19,7 +19,13 @@ class Publish_eventsDBApi extends GenericDBApi {
|
||||
}
|
||||
|
||||
static get RANGE_FIELDS() {
|
||||
return ['started_at', 'finished_at', 'pages_copied', 'transitions_copied', 'audios_copied'];
|
||||
return [
|
||||
'started_at',
|
||||
'finished_at',
|
||||
'pages_copied',
|
||||
'transitions_copied',
|
||||
'audios_copied',
|
||||
];
|
||||
}
|
||||
|
||||
static get ENUM_FIELDS() {
|
||||
@ -27,7 +33,16 @@ class Publish_eventsDBApi extends GenericDBApi {
|
||||
}
|
||||
|
||||
static get CSV_FIELDS() {
|
||||
return ['id', 'title', 'description', 'from_environment', 'to_environment', 'status', 'pages_copied', 'createdAt'];
|
||||
return [
|
||||
'id',
|
||||
'title',
|
||||
'description',
|
||||
'from_environment',
|
||||
'to_environment',
|
||||
'status',
|
||||
'pages_copied',
|
||||
'createdAt',
|
||||
];
|
||||
}
|
||||
|
||||
static get AUTOCOMPLETE_FIELD() {
|
||||
@ -42,10 +57,7 @@ class Publish_eventsDBApi extends GenericDBApi {
|
||||
}
|
||||
|
||||
static get FIND_BY_INCLUDES() {
|
||||
return [
|
||||
{ association: 'project' },
|
||||
{ association: 'user' },
|
||||
];
|
||||
return [{ association: 'project' }, { association: 'user' }];
|
||||
}
|
||||
|
||||
static getFieldMapping(data) {
|
||||
@ -77,30 +89,50 @@ class Publish_eventsDBApi extends GenericDBApi {
|
||||
{
|
||||
model: db.projects,
|
||||
as: 'project',
|
||||
where: filter.project ? {
|
||||
[Op.or]: [
|
||||
{ id: { [Op.in]: filter.project.split('|').map(term => Utils.uuid(term)) } },
|
||||
{
|
||||
name: {
|
||||
[Op.or]: filter.project.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
|
||||
}
|
||||
},
|
||||
]
|
||||
} : {},
|
||||
where: filter.project
|
||||
? {
|
||||
[Op.or]: [
|
||||
{
|
||||
id: {
|
||||
[Op.in]: filter.project
|
||||
.split('|')
|
||||
.map((term) => Utils.uuid(term)),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: {
|
||||
[Op.or]: filter.project
|
||||
.split('|')
|
||||
.map((term) => ({ [Op.iLike]: `%${term}%` })),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: {},
|
||||
},
|
||||
{
|
||||
model: db.users,
|
||||
as: 'user',
|
||||
where: filter.user ? {
|
||||
[Op.or]: [
|
||||
{ id: { [Op.in]: filter.user.split('|').map(term => Utils.uuid(term)) } },
|
||||
{
|
||||
firstName: {
|
||||
[Op.or]: filter.user.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
|
||||
}
|
||||
},
|
||||
]
|
||||
} : {},
|
||||
where: filter.user
|
||||
? {
|
||||
[Op.or]: [
|
||||
{
|
||||
id: {
|
||||
[Op.in]: filter.user
|
||||
.split('|')
|
||||
.map((term) => Utils.uuid(term)),
|
||||
},
|
||||
},
|
||||
{
|
||||
firstName: {
|
||||
[Op.or]: filter.user
|
||||
.split('|')
|
||||
.map((term) => ({ [Op.iLike]: `%${term}%` })),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: {},
|
||||
},
|
||||
];
|
||||
|
||||
@ -151,9 +183,10 @@ class Publish_eventsDBApi extends GenericDBApi {
|
||||
where,
|
||||
include,
|
||||
distinct: true,
|
||||
order: filter.field && filter.sort
|
||||
? [[filter.field, filter.sort]]
|
||||
: [['createdAt', 'desc']],
|
||||
order:
|
||||
filter.field && filter.sort
|
||||
? [[filter.field, filter.sort]]
|
||||
: [['createdAt', 'desc']],
|
||||
transaction: options.transaction,
|
||||
};
|
||||
|
||||
|
||||
@ -27,7 +27,14 @@ class Pwa_cachesDBApi extends GenericDBApi {
|
||||
}
|
||||
|
||||
static get CSV_FIELDS() {
|
||||
return ['id', 'environment', 'cache_version', 'is_active', 'generated_at', 'createdAt'];
|
||||
return [
|
||||
'id',
|
||||
'environment',
|
||||
'cache_version',
|
||||
'is_active',
|
||||
'generated_at',
|
||||
'createdAt',
|
||||
];
|
||||
}
|
||||
|
||||
static get AUTOCOMPLETE_FIELD() {
|
||||
@ -35,9 +42,7 @@ class Pwa_cachesDBApi extends GenericDBApi {
|
||||
}
|
||||
|
||||
static get ASSOCIATIONS() {
|
||||
return [
|
||||
{ field: 'project', setter: 'setProject', isArray: false },
|
||||
];
|
||||
return [{ field: 'project', setter: 'setProject', isArray: false }];
|
||||
}
|
||||
|
||||
static get FIND_BY_INCLUDES() {
|
||||
@ -68,16 +73,26 @@ class Pwa_cachesDBApi extends GenericDBApi {
|
||||
{
|
||||
model: db.projects,
|
||||
as: 'project',
|
||||
where: filter.project ? {
|
||||
[Op.or]: [
|
||||
{ id: { [Op.in]: filter.project.split('|').map(term => Utils.uuid(term)) } },
|
||||
{
|
||||
name: {
|
||||
[Op.or]: filter.project.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
|
||||
}
|
||||
},
|
||||
]
|
||||
} : {},
|
||||
where: filter.project
|
||||
? {
|
||||
[Op.or]: [
|
||||
{
|
||||
id: {
|
||||
[Op.in]: filter.project
|
||||
.split('|')
|
||||
.map((term) => Utils.uuid(term)),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: {
|
||||
[Op.or]: filter.project
|
||||
.split('|')
|
||||
.map((term) => ({ [Op.iLike]: `%${term}%` })),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: {},
|
||||
},
|
||||
];
|
||||
|
||||
@ -128,9 +143,10 @@ class Pwa_cachesDBApi extends GenericDBApi {
|
||||
where,
|
||||
include,
|
||||
distinct: true,
|
||||
order: filter.field && filter.sort
|
||||
? [[filter.field, filter.sort]]
|
||||
: [['createdAt', 'desc']],
|
||||
order:
|
||||
filter.field && filter.sort
|
||||
? [[filter.field, filter.sort]]
|
||||
: [['createdAt', 'desc']],
|
||||
transaction: options.transaction,
|
||||
};
|
||||
|
||||
|
||||
@ -35,16 +35,11 @@ class RolesDBApi extends GenericDBApi {
|
||||
}
|
||||
|
||||
static get ASSOCIATIONS() {
|
||||
return [
|
||||
{ field: 'permissions', setter: 'setPermissions', isArray: true },
|
||||
];
|
||||
return [{ field: 'permissions', setter: 'setPermissions', isArray: true }];
|
||||
}
|
||||
|
||||
static get FIND_BY_INCLUDES() {
|
||||
return [
|
||||
{ association: 'users_app_role' },
|
||||
{ association: 'permissions' },
|
||||
];
|
||||
return [{ association: 'users_app_role' }, { association: 'permissions' }];
|
||||
}
|
||||
|
||||
static getFieldMapping(data) {
|
||||
@ -92,16 +87,25 @@ class RolesDBApi extends GenericDBApi {
|
||||
model: db.permissions,
|
||||
as: 'permissions_filter',
|
||||
required: searchTerms.length > 0,
|
||||
where: searchTerms.length > 0 ? {
|
||||
[Op.or]: [
|
||||
{ id: { [Op.in]: searchTerms.map(term => Utils.uuid(term)) } },
|
||||
{
|
||||
name: {
|
||||
[Op.or]: searchTerms.map(term => ({ [Op.iLike]: `%${term}%` }))
|
||||
where:
|
||||
searchTerms.length > 0
|
||||
? {
|
||||
[Op.or]: [
|
||||
{
|
||||
id: {
|
||||
[Op.in]: searchTerms.map((term) => Utils.uuid(term)),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: {
|
||||
[Op.or]: searchTerms.map((term) => ({
|
||||
[Op.iLike]: `%${term}%`,
|
||||
})),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
]
|
||||
} : undefined
|
||||
: undefined,
|
||||
},
|
||||
...include,
|
||||
];
|
||||
@ -121,9 +125,10 @@ class RolesDBApi extends GenericDBApi {
|
||||
where,
|
||||
include,
|
||||
distinct: true,
|
||||
order: filter.field && filter.sort
|
||||
? [[filter.field, filter.sort]]
|
||||
: [['createdAt', 'desc']],
|
||||
order:
|
||||
filter.field && filter.sort
|
||||
? [[filter.field, filter.sort]]
|
||||
: [['createdAt', 'desc']],
|
||||
transaction: options.transaction,
|
||||
};
|
||||
|
||||
|
||||
@ -19,7 +19,15 @@ class Tour_pagesDBApi extends GenericDBApi {
|
||||
}
|
||||
|
||||
static get SEARCHABLE_FIELDS() {
|
||||
return ['source_key', 'name', 'slug', 'background_image_url', 'background_video_url', 'background_audio_url', 'ui_schema_json'];
|
||||
return [
|
||||
'source_key',
|
||||
'name',
|
||||
'slug',
|
||||
'background_image_url',
|
||||
'background_video_url',
|
||||
'background_audio_url',
|
||||
'ui_schema_json',
|
||||
];
|
||||
}
|
||||
|
||||
static get RANGE_FIELDS() {
|
||||
@ -31,7 +39,15 @@ class Tour_pagesDBApi extends GenericDBApi {
|
||||
}
|
||||
|
||||
static get CSV_FIELDS() {
|
||||
return ['id', 'environment', 'source_key', 'name', 'slug', 'sort_order', 'createdAt'];
|
||||
return [
|
||||
'id',
|
||||
'environment',
|
||||
'source_key',
|
||||
'name',
|
||||
'slug',
|
||||
'sort_order',
|
||||
'createdAt',
|
||||
];
|
||||
}
|
||||
|
||||
static get AUTOCOMPLETE_FIELD() {
|
||||
@ -39,9 +55,7 @@ class Tour_pagesDBApi extends GenericDBApi {
|
||||
}
|
||||
|
||||
static get ASSOCIATIONS() {
|
||||
return [
|
||||
{ field: 'project', setter: 'setProject', isArray: false },
|
||||
];
|
||||
return [{ field: 'project', setter: 'setProject', isArray: false }];
|
||||
}
|
||||
|
||||
static getFieldMapping(data) {
|
||||
@ -74,7 +88,7 @@ class Tour_pagesDBApi extends GenericDBApi {
|
||||
createdById: currentUser.id,
|
||||
updatedById: currentUser.id,
|
||||
},
|
||||
{ transaction }
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await record.setProject(projectId, { transaction });
|
||||
@ -96,9 +110,7 @@ class Tour_pagesDBApi extends GenericDBApi {
|
||||
const record = await this.MODEL.findOne({
|
||||
where: queryWhere,
|
||||
transaction,
|
||||
include: [
|
||||
projectInclude,
|
||||
],
|
||||
include: [projectInclude],
|
||||
});
|
||||
|
||||
if (!record) return null;
|
||||
@ -117,16 +129,26 @@ class Tour_pagesDBApi extends GenericDBApi {
|
||||
{
|
||||
model: db.projects,
|
||||
as: 'project',
|
||||
where: filter.project ? {
|
||||
[Op.or]: [
|
||||
{ id: { [Op.in]: filter.project.split('|').map(term => Utils.uuid(term)) } },
|
||||
{
|
||||
name: {
|
||||
[Op.or]: filter.project.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
|
||||
}
|
||||
},
|
||||
]
|
||||
} : {},
|
||||
where: filter.project
|
||||
? {
|
||||
[Op.or]: [
|
||||
{
|
||||
id: {
|
||||
[Op.in]: filter.project
|
||||
.split('|')
|
||||
.map((term) => Utils.uuid(term)),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: {
|
||||
[Op.or]: filter.project
|
||||
.split('|')
|
||||
.map((term) => ({ [Op.iLike]: `%${term}%` })),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: {},
|
||||
},
|
||||
];
|
||||
|
||||
@ -181,9 +203,10 @@ class Tour_pagesDBApi extends GenericDBApi {
|
||||
where,
|
||||
include,
|
||||
distinct: true,
|
||||
order: filter.field && filter.sort
|
||||
? [[filter.field, filter.sort]]
|
||||
: [['createdAt', 'desc']],
|
||||
order:
|
||||
filter.field && filter.sort
|
||||
? [[filter.field, filter.sort]]
|
||||
: [['createdAt', 'desc']],
|
||||
transaction: options.transaction,
|
||||
};
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,3 @@
|
||||
|
||||
|
||||
module.exports = {
|
||||
production: {
|
||||
dialect: 'postgres',
|
||||
@ -35,5 +33,5 @@ module.exports = {
|
||||
seederStorage: 'sequelize',
|
||||
migrationStorage: 'sequelize',
|
||||
migrationStorageTableName: 'SequelizeMeta',
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@ -11,14 +11,20 @@ module.exports = {
|
||||
|
||||
try {
|
||||
// Helper to add FK constraint safely (checks if exists first)
|
||||
const addForeignKey = async (tableName, columnName, references, onDelete = 'CASCADE', onUpdate = 'CASCADE') => {
|
||||
const addForeignKey = async (
|
||||
tableName,
|
||||
columnName,
|
||||
references,
|
||||
onDelete = 'CASCADE',
|
||||
onUpdate = 'CASCADE',
|
||||
) => {
|
||||
const constraintName = `${tableName}_${columnName}_fkey`;
|
||||
|
||||
// Check if constraint already exists
|
||||
const [results] = await queryInterface.sequelize.query(
|
||||
`SELECT constraint_name FROM information_schema.table_constraints
|
||||
WHERE table_name = '${tableName}' AND constraint_name = '${constraintName}'`,
|
||||
{ transaction }
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
if (results.length === 0) {
|
||||
@ -41,61 +47,175 @@ module.exports = {
|
||||
};
|
||||
|
||||
// asset_variants -> assets
|
||||
await addForeignKey('asset_variants', 'assetId', { table: 'assets', field: 'id' }, 'CASCADE', 'CASCADE');
|
||||
await addForeignKey(
|
||||
'asset_variants',
|
||||
'assetId',
|
||||
{ table: 'assets', field: 'id' },
|
||||
'CASCADE',
|
||||
'CASCADE',
|
||||
);
|
||||
|
||||
// page_elements -> tour_pages
|
||||
await addForeignKey('page_elements', 'pageId', { table: 'tour_pages', field: 'id' }, 'CASCADE', 'CASCADE');
|
||||
await addForeignKey(
|
||||
'page_elements',
|
||||
'pageId',
|
||||
{ table: 'tour_pages', field: 'id' },
|
||||
'CASCADE',
|
||||
'CASCADE',
|
||||
);
|
||||
|
||||
// page_links -> tour_pages (from_page)
|
||||
await addForeignKey('page_links', 'from_pageId', { table: 'tour_pages', field: 'id' }, 'CASCADE', 'CASCADE');
|
||||
await addForeignKey(
|
||||
'page_links',
|
||||
'from_pageId',
|
||||
{ table: 'tour_pages', field: 'id' },
|
||||
'CASCADE',
|
||||
'CASCADE',
|
||||
);
|
||||
|
||||
// page_links -> tour_pages (to_page)
|
||||
await addForeignKey('page_links', 'to_pageId', { table: 'tour_pages', field: 'id' }, 'SET NULL', 'CASCADE');
|
||||
await addForeignKey(
|
||||
'page_links',
|
||||
'to_pageId',
|
||||
{ table: 'tour_pages', field: 'id' },
|
||||
'SET NULL',
|
||||
'CASCADE',
|
||||
);
|
||||
|
||||
// page_links -> transitions
|
||||
await addForeignKey('page_links', 'transitionId', { table: 'transitions', field: 'id' }, 'SET NULL', 'CASCADE');
|
||||
await addForeignKey(
|
||||
'page_links',
|
||||
'transitionId',
|
||||
{ table: 'transitions', field: 'id' },
|
||||
'SET NULL',
|
||||
'CASCADE',
|
||||
);
|
||||
|
||||
// assets -> projects
|
||||
await addForeignKey('assets', 'projectId', { table: 'projects', field: 'id' }, 'CASCADE', 'CASCADE');
|
||||
await addForeignKey(
|
||||
'assets',
|
||||
'projectId',
|
||||
{ table: 'projects', field: 'id' },
|
||||
'CASCADE',
|
||||
'CASCADE',
|
||||
);
|
||||
|
||||
// tour_pages -> projects
|
||||
await addForeignKey('tour_pages', 'projectId', { table: 'projects', field: 'id' }, 'CASCADE', 'CASCADE');
|
||||
await addForeignKey(
|
||||
'tour_pages',
|
||||
'projectId',
|
||||
{ table: 'projects', field: 'id' },
|
||||
'CASCADE',
|
||||
'CASCADE',
|
||||
);
|
||||
|
||||
// transitions -> projects
|
||||
await addForeignKey('transitions', 'projectId', { table: 'projects', field: 'id' }, 'CASCADE', 'CASCADE');
|
||||
await addForeignKey(
|
||||
'transitions',
|
||||
'projectId',
|
||||
{ table: 'projects', field: 'id' },
|
||||
'CASCADE',
|
||||
'CASCADE',
|
||||
);
|
||||
|
||||
// project_memberships -> projects
|
||||
await addForeignKey('project_memberships', 'projectId', { table: 'projects', field: 'id' }, 'CASCADE', 'CASCADE');
|
||||
await addForeignKey(
|
||||
'project_memberships',
|
||||
'projectId',
|
||||
{ table: 'projects', field: 'id' },
|
||||
'CASCADE',
|
||||
'CASCADE',
|
||||
);
|
||||
|
||||
// project_memberships -> users
|
||||
await addForeignKey('project_memberships', 'userId', { table: 'users', field: 'id' }, 'CASCADE', 'CASCADE');
|
||||
await addForeignKey(
|
||||
'project_memberships',
|
||||
'userId',
|
||||
{ table: 'users', field: 'id' },
|
||||
'CASCADE',
|
||||
'CASCADE',
|
||||
);
|
||||
|
||||
// presigned_url_requests -> projects
|
||||
await addForeignKey('presigned_url_requests', 'projectId', { table: 'projects', field: 'id' }, 'CASCADE', 'CASCADE');
|
||||
await addForeignKey(
|
||||
'presigned_url_requests',
|
||||
'projectId',
|
||||
{ table: 'projects', field: 'id' },
|
||||
'CASCADE',
|
||||
'CASCADE',
|
||||
);
|
||||
|
||||
// presigned_url_requests -> users
|
||||
await addForeignKey('presigned_url_requests', 'userId', { table: 'users', field: 'id' }, 'CASCADE', 'CASCADE');
|
||||
await addForeignKey(
|
||||
'presigned_url_requests',
|
||||
'userId',
|
||||
{ table: 'users', field: 'id' },
|
||||
'CASCADE',
|
||||
'CASCADE',
|
||||
);
|
||||
|
||||
// project_audio_tracks -> projects
|
||||
await addForeignKey('project_audio_tracks', 'projectId', { table: 'projects', field: 'id' }, 'CASCADE', 'CASCADE');
|
||||
await addForeignKey(
|
||||
'project_audio_tracks',
|
||||
'projectId',
|
||||
{ table: 'projects', field: 'id' },
|
||||
'CASCADE',
|
||||
'CASCADE',
|
||||
);
|
||||
|
||||
// publish_events -> projects
|
||||
await addForeignKey('publish_events', 'projectId', { table: 'projects', field: 'id' }, 'CASCADE', 'CASCADE');
|
||||
await addForeignKey(
|
||||
'publish_events',
|
||||
'projectId',
|
||||
{ table: 'projects', field: 'id' },
|
||||
'CASCADE',
|
||||
'CASCADE',
|
||||
);
|
||||
|
||||
// publish_events -> users (SET NULL to preserve audit trail)
|
||||
await addForeignKey('publish_events', 'userId', { table: 'users', field: 'id' }, 'SET NULL', 'CASCADE');
|
||||
await addForeignKey(
|
||||
'publish_events',
|
||||
'userId',
|
||||
{ table: 'users', field: 'id' },
|
||||
'SET NULL',
|
||||
'CASCADE',
|
||||
);
|
||||
|
||||
// pwa_caches -> projects
|
||||
await addForeignKey('pwa_caches', 'projectId', { table: 'projects', field: 'id' }, 'CASCADE', 'CASCADE');
|
||||
await addForeignKey(
|
||||
'pwa_caches',
|
||||
'projectId',
|
||||
{ table: 'projects', field: 'id' },
|
||||
'CASCADE',
|
||||
'CASCADE',
|
||||
);
|
||||
|
||||
// access_logs -> projects
|
||||
await addForeignKey('access_logs', 'projectId', { table: 'projects', field: 'id' }, 'CASCADE', 'CASCADE');
|
||||
await addForeignKey(
|
||||
'access_logs',
|
||||
'projectId',
|
||||
{ table: 'projects', field: 'id' },
|
||||
'CASCADE',
|
||||
'CASCADE',
|
||||
);
|
||||
|
||||
// access_logs -> users (SET NULL to preserve audit trail)
|
||||
await addForeignKey('access_logs', 'userId', { table: 'users', field: 'id' }, 'SET NULL', 'CASCADE');
|
||||
await addForeignKey(
|
||||
'access_logs',
|
||||
'userId',
|
||||
{ table: 'users', field: 'id' },
|
||||
'SET NULL',
|
||||
'CASCADE',
|
||||
);
|
||||
|
||||
// users -> roles (SET NULL so deleting role doesn't delete users)
|
||||
await addForeignKey('users', 'app_roleId', { table: 'roles', field: 'id' }, 'SET NULL', 'CASCADE');
|
||||
await addForeignKey(
|
||||
'users',
|
||||
'app_roleId',
|
||||
{ table: 'roles', field: 'id' },
|
||||
'SET NULL',
|
||||
'CASCADE',
|
||||
);
|
||||
|
||||
await transaction.commit();
|
||||
console.log('All FK constraints added successfully');
|
||||
@ -112,10 +232,14 @@ module.exports = {
|
||||
const dropForeignKey = async (tableName, columnName) => {
|
||||
const constraintName = `${tableName}_${columnName}_fkey`;
|
||||
try {
|
||||
await queryInterface.removeConstraint(tableName, constraintName, { transaction });
|
||||
await queryInterface.removeConstraint(tableName, constraintName, {
|
||||
transaction,
|
||||
});
|
||||
console.log(`Removed FK constraint: ${constraintName}`);
|
||||
} catch (error) {
|
||||
console.log(`FK constraint not found (may not exist): ${constraintName}`);
|
||||
console.log(
|
||||
`FK constraint not found (may not exist): ${constraintName}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@ -146,5 +270,5 @@ module.exports = {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@ -21,20 +21,26 @@ module.exports = {
|
||||
const [results] = await queryInterface.sequelize.query(
|
||||
`SELECT column_name FROM information_schema.columns
|
||||
WHERE table_name = '${tableName}' AND column_name = '${columnName}'`,
|
||||
{ transaction }
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
if (results.length > 0) {
|
||||
await queryInterface.removeColumn(tableName, columnName, { transaction });
|
||||
await queryInterface.removeColumn(tableName, columnName, {
|
||||
transaction,
|
||||
});
|
||||
console.log(`Removed column: ${tableName}.${columnName}`);
|
||||
} else {
|
||||
console.log(`Column does not exist (skipping): ${tableName}.${columnName}`);
|
||||
console.log(
|
||||
`Column does not exist (skipping): ${tableName}.${columnName}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Remove is_deleted index from assets first (if exists)
|
||||
try {
|
||||
await queryInterface.removeIndex('assets', 'assets_is_deleted', { transaction });
|
||||
await queryInterface.removeIndex('assets', 'assets_is_deleted', {
|
||||
transaction,
|
||||
});
|
||||
console.log('Removed index: assets_is_deleted');
|
||||
} catch (error) {
|
||||
console.log('Index assets_is_deleted not found (may not exist)');
|
||||
@ -61,16 +67,26 @@ module.exports = {
|
||||
|
||||
try {
|
||||
// Re-add columns to assets table
|
||||
await queryInterface.addColumn('assets', 'is_deleted', {
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
}, { transaction });
|
||||
await queryInterface.addColumn(
|
||||
'assets',
|
||||
'is_deleted',
|
||||
{
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await queryInterface.addColumn('assets', 'deleted_at_time', {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: true,
|
||||
}, { transaction });
|
||||
await queryInterface.addColumn(
|
||||
'assets',
|
||||
'deleted_at_time',
|
||||
{
|
||||
type: Sequelize.DATE,
|
||||
allowNull: true,
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
// Re-add index
|
||||
await queryInterface.addIndex('assets', ['is_deleted'], {
|
||||
@ -79,16 +95,26 @@ module.exports = {
|
||||
});
|
||||
|
||||
// Re-add columns to projects table
|
||||
await queryInterface.addColumn('projects', 'is_deleted', {
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
}, { transaction });
|
||||
await queryInterface.addColumn(
|
||||
'projects',
|
||||
'is_deleted',
|
||||
{
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await queryInterface.addColumn('projects', 'deleted_at_time', {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: true,
|
||||
}, { transaction });
|
||||
await queryInterface.addColumn(
|
||||
'projects',
|
||||
'deleted_at_time',
|
||||
{
|
||||
type: Sequelize.DATE,
|
||||
allowNull: true,
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await transaction.commit();
|
||||
console.log('Redundant deletion columns restored successfully');
|
||||
@ -96,5 +122,5 @@ module.exports = {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@ -17,7 +17,7 @@ module.exports = {
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = 'ui_elements'
|
||||
);`,
|
||||
{ type: Sequelize.QueryTypes.SELECT }
|
||||
{ type: Sequelize.QueryTypes.SELECT },
|
||||
);
|
||||
|
||||
if (!tableExists[0]?.exists) {
|
||||
@ -32,11 +32,13 @@ module.exports = {
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = 'element_type_defaults'
|
||||
);`,
|
||||
{ type: Sequelize.QueryTypes.SELECT }
|
||||
{ type: Sequelize.QueryTypes.SELECT },
|
||||
);
|
||||
|
||||
if (newTableExists[0]?.exists) {
|
||||
console.log('Table element_type_defaults already exists, skipping rename');
|
||||
console.log(
|
||||
'Table element_type_defaults already exists, skipping rename',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -57,17 +59,21 @@ module.exports = {
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = 'element_type_defaults'
|
||||
);`,
|
||||
{ type: Sequelize.QueryTypes.SELECT }
|
||||
{ type: Sequelize.QueryTypes.SELECT },
|
||||
);
|
||||
|
||||
if (!tableExists[0]?.exists) {
|
||||
console.log('Table element_type_defaults does not exist, skipping rollback');
|
||||
console.log(
|
||||
'Table element_type_defaults does not exist, skipping rollback',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Rename table back
|
||||
await queryInterface.renameTable('element_type_defaults', 'ui_elements');
|
||||
|
||||
console.log('Successfully rolled back: renamed element_type_defaults to ui_elements');
|
||||
console.log(
|
||||
'Successfully rolled back: renamed element_type_defaults to ui_elements',
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
@ -26,13 +26,13 @@ module.exports = {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
},
|
||||
{ transaction }
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
// Step 2: Copy ENUM values to TEXT column
|
||||
await queryInterface.sequelize.query(
|
||||
`UPDATE page_elements SET element_type_text = element_type::TEXT`,
|
||||
{ transaction }
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
// Step 3: Drop the old ENUM column
|
||||
@ -45,7 +45,7 @@ module.exports = {
|
||||
'page_elements',
|
||||
'element_type_text',
|
||||
'element_type',
|
||||
{ transaction }
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
// Step 5: Add NOT NULL constraint
|
||||
@ -56,7 +56,7 @@ module.exports = {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: false,
|
||||
},
|
||||
{ transaction }
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
// Step 6: Now map nav_button to specific navigation types (column is TEXT now)
|
||||
@ -70,7 +70,7 @@ module.exports = {
|
||||
OR content_json::jsonb->>'navType' = 'forward'
|
||||
OR content_json::jsonb->>'navType' IS NULL
|
||||
)`,
|
||||
{ transaction }
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
// Back navigation
|
||||
@ -80,17 +80,19 @@ module.exports = {
|
||||
WHERE element_type = 'nav_button'
|
||||
AND content_json IS NOT NULL
|
||||
AND content_json::jsonb->>'navType' = 'back'`,
|
||||
{ transaction }
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
// Step 7: Drop the old ENUM type if it exists
|
||||
await queryInterface.sequelize.query(
|
||||
`DROP TYPE IF EXISTS "enum_page_elements_element_type"`,
|
||||
{ transaction }
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await transaction.commit();
|
||||
console.log('Successfully converted element_type from ENUM to TEXT and mapped nav_button types');
|
||||
console.log(
|
||||
'Successfully converted element_type from ENUM to TEXT and mapped nav_button types',
|
||||
);
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
@ -106,17 +108,17 @@ module.exports = {
|
||||
`UPDATE page_elements
|
||||
SET element_type = 'nav_button'
|
||||
WHERE element_type IN ('navigation_next', 'navigation_prev')`,
|
||||
{ transaction }
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
// Step 2: Drop any existing ENUM types that might conflict
|
||||
await queryInterface.sequelize.query(
|
||||
`DROP TYPE IF EXISTS "enum_page_elements_element_type" CASCADE`,
|
||||
{ transaction }
|
||||
{ transaction },
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`DROP TYPE IF EXISTS "enum_page_elements_element_type_enum" CASCADE`,
|
||||
{ transaction }
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
// Step 3: Create the ENUM type with original values
|
||||
@ -132,19 +134,19 @@ module.exports = {
|
||||
'video_player',
|
||||
'popup'
|
||||
)`,
|
||||
{ transaction }
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
// Step 4: Add ENUM column directly via raw SQL to avoid Sequelize creating another type
|
||||
await queryInterface.sequelize.query(
|
||||
`ALTER TABLE page_elements ADD COLUMN element_type_enum "enum_page_elements_element_type"`,
|
||||
{ transaction }
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
// Step 5: Copy TEXT values to ENUM column
|
||||
await queryInterface.sequelize.query(
|
||||
`UPDATE page_elements SET element_type_enum = element_type::"enum_page_elements_element_type"`,
|
||||
{ transaction }
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
// Step 6: Drop TEXT column
|
||||
@ -157,13 +159,13 @@ module.exports = {
|
||||
'page_elements',
|
||||
'element_type_enum',
|
||||
'element_type',
|
||||
{ transaction }
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
// Step 8: Add NOT NULL constraint
|
||||
await queryInterface.sequelize.query(
|
||||
`ALTER TABLE page_elements ALTER COLUMN element_type SET NOT NULL`,
|
||||
{ transaction }
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await transaction.commit();
|
||||
|
||||
@ -21,11 +21,13 @@ module.exports = {
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = 'project_element_defaults'
|
||||
);`,
|
||||
{ type: Sequelize.QueryTypes.SELECT }
|
||||
{ type: Sequelize.QueryTypes.SELECT },
|
||||
);
|
||||
|
||||
if (tableExists[0]?.exists) {
|
||||
console.log('Table project_element_defaults already exists, skipping creation');
|
||||
console.log(
|
||||
'Table project_element_defaults already exists, skipping creation',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -121,19 +123,31 @@ module.exports = {
|
||||
name: 'project_element_defaults_projectId',
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('project_element_defaults', ['projectId', 'element_type'], {
|
||||
name: 'project_element_defaults_projectId_element_type',
|
||||
unique: true,
|
||||
where: { deletedAt: null },
|
||||
});
|
||||
await queryInterface.addIndex(
|
||||
'project_element_defaults',
|
||||
['projectId', 'element_type'],
|
||||
{
|
||||
name: 'project_element_defaults_projectId_element_type',
|
||||
unique: true,
|
||||
where: { deletedAt: null },
|
||||
},
|
||||
);
|
||||
|
||||
await queryInterface.addIndex('project_element_defaults', ['element_type'], {
|
||||
name: 'project_element_defaults_element_type',
|
||||
});
|
||||
await queryInterface.addIndex(
|
||||
'project_element_defaults',
|
||||
['element_type'],
|
||||
{
|
||||
name: 'project_element_defaults_element_type',
|
||||
},
|
||||
);
|
||||
|
||||
await queryInterface.addIndex('project_element_defaults', ['source_element_id'], {
|
||||
name: 'project_element_defaults_source_element_id',
|
||||
});
|
||||
await queryInterface.addIndex(
|
||||
'project_element_defaults',
|
||||
['source_element_id'],
|
||||
{
|
||||
name: 'project_element_defaults_source_element_id',
|
||||
},
|
||||
);
|
||||
|
||||
await queryInterface.addIndex('project_element_defaults', ['deletedAt'], {
|
||||
name: 'project_element_defaults_deletedAt',
|
||||
@ -144,11 +158,26 @@ module.exports = {
|
||||
|
||||
async down(queryInterface, _Sequelize) {
|
||||
// Drop indexes first
|
||||
await queryInterface.removeIndex('project_element_defaults', 'project_element_defaults_projectId');
|
||||
await queryInterface.removeIndex('project_element_defaults', 'project_element_defaults_projectId_element_type');
|
||||
await queryInterface.removeIndex('project_element_defaults', 'project_element_defaults_element_type');
|
||||
await queryInterface.removeIndex('project_element_defaults', 'project_element_defaults_source_element_id');
|
||||
await queryInterface.removeIndex('project_element_defaults', 'project_element_defaults_deletedAt');
|
||||
await queryInterface.removeIndex(
|
||||
'project_element_defaults',
|
||||
'project_element_defaults_projectId',
|
||||
);
|
||||
await queryInterface.removeIndex(
|
||||
'project_element_defaults',
|
||||
'project_element_defaults_projectId_element_type',
|
||||
);
|
||||
await queryInterface.removeIndex(
|
||||
'project_element_defaults',
|
||||
'project_element_defaults_element_type',
|
||||
);
|
||||
await queryInterface.removeIndex(
|
||||
'project_element_defaults',
|
||||
'project_element_defaults_source_element_id',
|
||||
);
|
||||
await queryInterface.removeIndex(
|
||||
'project_element_defaults',
|
||||
'project_element_defaults_deletedAt',
|
||||
);
|
||||
|
||||
// Drop table
|
||||
await queryInterface.dropTable('project_element_defaults');
|
||||
|
||||
@ -130,7 +130,7 @@ module.exports = {
|
||||
// This is needed because the API's lazy initialization won't have run yet during migration
|
||||
const [existingTypes] = await queryInterface.sequelize.query(
|
||||
`SELECT element_type FROM element_type_defaults WHERE "deletedAt" IS NULL`,
|
||||
{ type: Sequelize.QueryTypes.SELECT }
|
||||
{ type: Sequelize.QueryTypes.SELECT },
|
||||
);
|
||||
|
||||
const existingTypeSet = new Set(
|
||||
@ -138,7 +138,7 @@ module.exports = {
|
||||
? existingTypes.map((t) => t.element_type)
|
||||
: existingTypes
|
||||
? [existingTypes.element_type]
|
||||
: []
|
||||
: [],
|
||||
);
|
||||
|
||||
// Insert missing element types
|
||||
@ -154,16 +154,18 @@ module.exports = {
|
||||
sort_order: defaultType.sort_order,
|
||||
settings_json: defaultType.settings_json,
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
console.log(
|
||||
`Created missing element_type_default: ${defaultType.element_type}`,
|
||||
);
|
||||
console.log(`Created missing element_type_default: ${defaultType.element_type}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Get all existing projects
|
||||
const [projects] = await queryInterface.sequelize.query(
|
||||
`SELECT id FROM projects WHERE "deletedAt" IS NULL`,
|
||||
{ type: Sequelize.QueryTypes.SELECT }
|
||||
{ type: Sequelize.QueryTypes.SELECT },
|
||||
);
|
||||
|
||||
if (!projects || projects.length === 0) {
|
||||
@ -176,7 +178,7 @@ module.exports = {
|
||||
`SELECT id, element_type, name, sort_order, settings_json
|
||||
FROM element_type_defaults
|
||||
WHERE "deletedAt" IS NULL`,
|
||||
{ type: Sequelize.QueryTypes.SELECT }
|
||||
{ type: Sequelize.QueryTypes.SELECT },
|
||||
);
|
||||
|
||||
if (!globalDefaults || globalDefaults.length === 0) {
|
||||
@ -184,8 +186,12 @@ module.exports = {
|
||||
return;
|
||||
}
|
||||
|
||||
const projectIds = Array.isArray(projects) ? projects.map((p) => p.id) : [projects.id];
|
||||
const globalDefaultRows = Array.isArray(globalDefaults) ? globalDefaults : [globalDefaults];
|
||||
const projectIds = Array.isArray(projects)
|
||||
? projects.map((p) => p.id)
|
||||
: [projects.id];
|
||||
const globalDefaultRows = Array.isArray(globalDefaults)
|
||||
? globalDefaults
|
||||
: [globalDefaults];
|
||||
|
||||
// For each project, add any missing element type defaults
|
||||
for (const projectId of projectIds) {
|
||||
@ -196,7 +202,7 @@ module.exports = {
|
||||
{
|
||||
replacements: { projectId },
|
||||
type: Sequelize.QueryTypes.SELECT,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const existingProjectTypes = new Set(
|
||||
@ -204,7 +210,7 @@ module.exports = {
|
||||
? existingDefaults.map((d) => d.element_type)
|
||||
: existingDefaults
|
||||
? [existingDefaults.element_type]
|
||||
: []
|
||||
: [],
|
||||
);
|
||||
|
||||
// Create project element defaults for missing types
|
||||
@ -239,26 +245,30 @@ module.exports = {
|
||||
projectId,
|
||||
},
|
||||
type: Sequelize.QueryTypes.INSERT,
|
||||
}
|
||||
},
|
||||
);
|
||||
addedCount++;
|
||||
}
|
||||
|
||||
if (addedCount > 0) {
|
||||
console.log(`Backfilled ${addedCount} element defaults for project ${projectId}`);
|
||||
console.log(
|
||||
`Backfilled ${addedCount} element defaults for project ${projectId}`,
|
||||
);
|
||||
} else {
|
||||
console.log(`Project ${projectId} already has all element defaults`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Successfully backfilled project_element_defaults for existing projects');
|
||||
console.log(
|
||||
'Successfully backfilled project_element_defaults for existing projects',
|
||||
);
|
||||
},
|
||||
|
||||
async down(queryInterface, _Sequelize) {
|
||||
// Delete all project_element_defaults with snapshot_version = 1
|
||||
// (only the ones we created during backfill)
|
||||
await queryInterface.sequelize.query(
|
||||
`DELETE FROM project_element_defaults WHERE snapshot_version = 1`
|
||||
`DELETE FROM project_element_defaults WHERE snapshot_version = 1`,
|
||||
);
|
||||
|
||||
console.log('Successfully removed backfilled project_element_defaults');
|
||||
|
||||
@ -12,17 +12,19 @@ module.exports = {
|
||||
const [columns] = await queryInterface.sequelize.query(
|
||||
`SELECT column_name FROM information_schema.columns
|
||||
WHERE table_name = 'project_audio_tracks' AND column_name = 'environment'`,
|
||||
{ type: Sequelize.QueryTypes.SELECT }
|
||||
{ type: Sequelize.QueryTypes.SELECT },
|
||||
);
|
||||
|
||||
if (!columns) {
|
||||
console.log('Column project_audio_tracks.environment does not exist, skipping');
|
||||
console.log(
|
||||
'Column project_audio_tracks.environment does not exist, skipping',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set NULL values to 'dev'
|
||||
await queryInterface.sequelize.query(
|
||||
`UPDATE project_audio_tracks SET environment = 'dev' WHERE environment IS NULL`
|
||||
`UPDATE project_audio_tracks SET environment = 'dev' WHERE environment IS NULL`,
|
||||
);
|
||||
|
||||
// Alter column to NOT NULL with default
|
||||
@ -32,7 +34,9 @@ module.exports = {
|
||||
defaultValue: 'dev',
|
||||
});
|
||||
|
||||
console.log('Successfully fixed project_audio_tracks.environment to NOT NULL with default dev');
|
||||
console.log(
|
||||
'Successfully fixed project_audio_tracks.environment to NOT NULL with default dev',
|
||||
);
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
|
||||
@ -12,7 +12,7 @@ module.exports = {
|
||||
// Get all projects
|
||||
const projects = await queryInterface.sequelize.query(
|
||||
`SELECT id FROM projects WHERE "deletedAt" IS NULL`,
|
||||
{ type: Sequelize.QueryTypes.SELECT }
|
||||
{ type: Sequelize.QueryTypes.SELECT },
|
||||
);
|
||||
|
||||
if (!projects || projects.length === 0) {
|
||||
@ -27,7 +27,7 @@ module.exports = {
|
||||
const [stageCheck] = await queryInterface.sequelize.query(
|
||||
`SELECT COUNT(*)::int as count FROM tour_pages
|
||||
WHERE "projectId" = '${projectId}' AND environment = 'stage' AND "deletedAt" IS NULL`,
|
||||
{ type: Sequelize.QueryTypes.SELECT }
|
||||
{ type: Sequelize.QueryTypes.SELECT },
|
||||
);
|
||||
|
||||
if (stageCheck?.count > 0) {
|
||||
@ -39,7 +39,7 @@ module.exports = {
|
||||
const [devPageCount] = await queryInterface.sequelize.query(
|
||||
`SELECT COUNT(*)::int as count FROM tour_pages
|
||||
WHERE "projectId" = '${projectId}' AND environment = 'dev' AND "deletedAt" IS NULL`,
|
||||
{ type: Sequelize.QueryTypes.SELECT }
|
||||
{ type: Sequelize.QueryTypes.SELECT },
|
||||
);
|
||||
|
||||
if (!devPageCount || devPageCount.count === 0) {
|
||||
@ -188,19 +188,19 @@ module.exports = {
|
||||
async down(queryInterface, _Sequelize) {
|
||||
// Delete all stage content that has a source_key (meaning it was created by this migration)
|
||||
await queryInterface.sequelize.query(
|
||||
`DELETE FROM page_links WHERE "from_pageId" IN (SELECT id FROM tour_pages WHERE environment = 'stage' AND source_key IS NOT NULL)`
|
||||
`DELETE FROM page_links WHERE "from_pageId" IN (SELECT id FROM tour_pages WHERE environment = 'stage' AND source_key IS NOT NULL)`,
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`DELETE FROM page_elements WHERE "pageId" IN (SELECT id FROM tour_pages WHERE environment = 'stage' AND source_key IS NOT NULL)`
|
||||
`DELETE FROM page_elements WHERE "pageId" IN (SELECT id FROM tour_pages WHERE environment = 'stage' AND source_key IS NOT NULL)`,
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`DELETE FROM tour_pages WHERE environment = 'stage' AND source_key IS NOT NULL`
|
||||
`DELETE FROM tour_pages WHERE environment = 'stage' AND source_key IS NOT NULL`,
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`DELETE FROM transitions WHERE environment = 'stage' AND source_key IS NOT NULL`
|
||||
`DELETE FROM transitions WHERE environment = 'stage' AND source_key IS NOT NULL`,
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`DELETE FROM project_audio_tracks WHERE environment = 'stage' AND source_key IS NOT NULL`
|
||||
`DELETE FROM project_audio_tracks WHERE environment = 'stage' AND source_key IS NOT NULL`,
|
||||
);
|
||||
|
||||
console.log('Removed stage content created by migration');
|
||||
|
||||
@ -13,12 +13,12 @@ module.exports = {
|
||||
async up(queryInterface, _Sequelize) {
|
||||
// Fix any NULL environments in tour_pages
|
||||
await queryInterface.sequelize.query(
|
||||
`UPDATE tour_pages SET environment = 'dev' WHERE environment IS NULL`
|
||||
`UPDATE tour_pages SET environment = 'dev' WHERE environment IS NULL`,
|
||||
);
|
||||
|
||||
// Fix any NULL environments in transitions
|
||||
await queryInterface.sequelize.query(
|
||||
`UPDATE transitions SET environment = 'dev' WHERE environment IS NULL`
|
||||
`UPDATE transitions SET environment = 'dev' WHERE environment IS NULL`,
|
||||
);
|
||||
|
||||
// Add NOT NULL constraint with default to tour_pages.environment
|
||||
|
||||
@ -11,7 +11,7 @@ module.exports = {
|
||||
|
||||
// Drop the ENUM type
|
||||
await queryInterface.sequelize.query(
|
||||
`DROP TYPE IF EXISTS "enum_projects_phase";`
|
||||
`DROP TYPE IF EXISTS "enum_projects_phase";`,
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
@ -18,25 +18,26 @@ module.exports = {
|
||||
// Get all tour pages with their ui_schema_json
|
||||
const [tourPages] = await queryInterface.sequelize.query(
|
||||
`SELECT id, "projectId", environment, slug, ui_schema_json FROM tour_pages WHERE ui_schema_json IS NOT NULL`,
|
||||
{ transaction }
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
// Build a lookup map: pageId -> { projectId, environment, slug }
|
||||
const pageInfoById = new Map();
|
||||
tourPages.forEach(page => {
|
||||
tourPages.forEach((page) => {
|
||||
pageInfoById.set(page.id, {
|
||||
projectId: page.projectId,
|
||||
environment: page.environment,
|
||||
slug: page.slug
|
||||
slug: page.slug,
|
||||
});
|
||||
});
|
||||
|
||||
// Process each page and convert targetPageId to targetPageSlug
|
||||
for (const page of tourPages) {
|
||||
try {
|
||||
const uiSchema = typeof page.ui_schema_json === 'string'
|
||||
? JSON.parse(page.ui_schema_json)
|
||||
: page.ui_schema_json;
|
||||
const uiSchema =
|
||||
typeof page.ui_schema_json === 'string'
|
||||
? JSON.parse(page.ui_schema_json)
|
||||
: page.ui_schema_json;
|
||||
|
||||
if (!uiSchema || !Array.isArray(uiSchema.elements)) {
|
||||
continue;
|
||||
@ -44,14 +45,19 @@ module.exports = {
|
||||
|
||||
let hasChanges = false;
|
||||
|
||||
uiSchema.elements.forEach(element => {
|
||||
uiSchema.elements.forEach((element) => {
|
||||
// Convert targetPageId to targetPageSlug
|
||||
if (element.targetPageId && typeof element.targetPageId === 'string') {
|
||||
if (
|
||||
element.targetPageId &&
|
||||
typeof element.targetPageId === 'string'
|
||||
) {
|
||||
const targetPageInfo = pageInfoById.get(element.targetPageId);
|
||||
if (targetPageInfo && targetPageInfo.slug) {
|
||||
// Only convert if target page is in the same project and environment
|
||||
if (targetPageInfo.projectId === page.projectId &&
|
||||
targetPageInfo.environment === page.environment) {
|
||||
if (
|
||||
targetPageInfo.projectId === page.projectId &&
|
||||
targetPageInfo.environment === page.environment
|
||||
) {
|
||||
element.targetPageSlug = targetPageInfo.slug;
|
||||
delete element.targetPageId;
|
||||
hasChanges = true;
|
||||
@ -66,11 +72,11 @@ module.exports = {
|
||||
{
|
||||
replacements: {
|
||||
json: JSON.stringify(uiSchema),
|
||||
id: page.id
|
||||
id: page.id,
|
||||
},
|
||||
type: Sequelize.QueryTypes.UPDATE,
|
||||
transaction
|
||||
}
|
||||
transaction,
|
||||
},
|
||||
);
|
||||
}
|
||||
} catch (parseError) {
|
||||
@ -80,7 +86,9 @@ module.exports = {
|
||||
}
|
||||
|
||||
await transaction.commit();
|
||||
console.log('Migration complete: Converted targetPageId to targetPageSlug');
|
||||
console.log(
|
||||
'Migration complete: Converted targetPageId to targetPageSlug',
|
||||
);
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
@ -94,12 +102,12 @@ module.exports = {
|
||||
// Get all tour pages
|
||||
const [tourPages] = await queryInterface.sequelize.query(
|
||||
`SELECT id, "projectId", environment, slug, ui_schema_json FROM tour_pages WHERE ui_schema_json IS NOT NULL`,
|
||||
{ transaction }
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
// Build lookup: (projectId, environment, slug) -> pageId
|
||||
const pageIdByKey = new Map();
|
||||
tourPages.forEach(page => {
|
||||
tourPages.forEach((page) => {
|
||||
const key = `${page.projectId}:${page.environment}:${page.slug}`;
|
||||
pageIdByKey.set(key, page.id);
|
||||
});
|
||||
@ -107,9 +115,10 @@ module.exports = {
|
||||
// Process each page and convert targetPageSlug back to targetPageId
|
||||
for (const page of tourPages) {
|
||||
try {
|
||||
const uiSchema = typeof page.ui_schema_json === 'string'
|
||||
? JSON.parse(page.ui_schema_json)
|
||||
: page.ui_schema_json;
|
||||
const uiSchema =
|
||||
typeof page.ui_schema_json === 'string'
|
||||
? JSON.parse(page.ui_schema_json)
|
||||
: page.ui_schema_json;
|
||||
|
||||
if (!uiSchema || !Array.isArray(uiSchema.elements)) {
|
||||
continue;
|
||||
@ -117,8 +126,11 @@ module.exports = {
|
||||
|
||||
let hasChanges = false;
|
||||
|
||||
uiSchema.elements.forEach(element => {
|
||||
if (element.targetPageSlug && typeof element.targetPageSlug === 'string') {
|
||||
uiSchema.elements.forEach((element) => {
|
||||
if (
|
||||
element.targetPageSlug &&
|
||||
typeof element.targetPageSlug === 'string'
|
||||
) {
|
||||
const key = `${page.projectId}:${page.environment}:${element.targetPageSlug}`;
|
||||
const targetPageId = pageIdByKey.get(key);
|
||||
if (targetPageId) {
|
||||
@ -135,11 +147,11 @@ module.exports = {
|
||||
{
|
||||
replacements: {
|
||||
json: JSON.stringify(uiSchema),
|
||||
id: page.id
|
||||
id: page.id,
|
||||
},
|
||||
type: Sequelize.QueryTypes.UPDATE,
|
||||
transaction
|
||||
}
|
||||
transaction,
|
||||
},
|
||||
);
|
||||
}
|
||||
} catch (parseError) {
|
||||
@ -148,10 +160,12 @@ module.exports = {
|
||||
}
|
||||
|
||||
await transaction.commit();
|
||||
console.log('Rollback complete: Converted targetPageSlug back to targetPageId');
|
||||
console.log(
|
||||
'Rollback complete: Converted targetPageSlug back to targetPageId',
|
||||
);
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@ -10,12 +10,14 @@ module.exports = {
|
||||
async up(queryInterface, _Sequelize) {
|
||||
// Verify the table is empty before dropping
|
||||
const [results] = await queryInterface.sequelize.query(
|
||||
'SELECT COUNT(*) as count FROM page_elements'
|
||||
'SELECT COUNT(*) as count FROM page_elements',
|
||||
);
|
||||
const count = parseInt(results[0].count, 10);
|
||||
|
||||
if (count > 0) {
|
||||
throw new Error(`Cannot drop page_elements table: it contains ${count} records. Please migrate or delete them first.`);
|
||||
throw new Error(
|
||||
`Cannot drop page_elements table: it contains ${count} records. Please migrate or delete them first.`,
|
||||
);
|
||||
}
|
||||
|
||||
await queryInterface.dropTable('page_elements');
|
||||
@ -94,5 +96,5 @@ module.exports = {
|
||||
unique: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@ -10,12 +10,14 @@ module.exports = {
|
||||
async up(queryInterface, _Sequelize) {
|
||||
// Verify the table is empty before dropping
|
||||
const [results] = await queryInterface.sequelize.query(
|
||||
'SELECT COUNT(*) as count FROM page_links'
|
||||
'SELECT COUNT(*) as count FROM page_links',
|
||||
);
|
||||
const count = parseInt(results[0].count, 10);
|
||||
|
||||
if (count > 0) {
|
||||
throw new Error(`Cannot drop page_links table: it contains ${count} records. Please migrate or delete them first.`);
|
||||
throw new Error(
|
||||
`Cannot drop page_links table: it contains ${count} records. Please migrate or delete them first.`,
|
||||
);
|
||||
}
|
||||
|
||||
await queryInterface.dropTable('page_links');
|
||||
@ -102,5 +104,5 @@ module.exports = {
|
||||
unique: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@ -10,12 +10,14 @@ module.exports = {
|
||||
async up(queryInterface, _Sequelize) {
|
||||
// Verify the table is empty before dropping
|
||||
const [results] = await queryInterface.sequelize.query(
|
||||
'SELECT COUNT(*) as count FROM transitions'
|
||||
'SELECT COUNT(*) as count FROM transitions',
|
||||
);
|
||||
const count = parseInt(results[0].count, 10);
|
||||
|
||||
if (count > 0) {
|
||||
throw new Error(`Cannot drop transitions table: it contains ${count} records. Please migrate or delete them first.`);
|
||||
throw new Error(
|
||||
`Cannot drop transitions table: it contains ${count} records. Please migrate or delete them first.`,
|
||||
);
|
||||
}
|
||||
|
||||
await queryInterface.dropTable('transitions');
|
||||
@ -99,5 +101,5 @@ module.exports = {
|
||||
unique: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@ -67,14 +67,16 @@ module.exports = {
|
||||
{
|
||||
replacements: { element_type: elementType.element_type },
|
||||
type: Sequelize.QueryTypes.SELECT,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (!existing) {
|
||||
await queryInterface.bulkInsert('element_type_defaults', [elementType]);
|
||||
console.log(`Added global default for: ${elementType.element_type}`);
|
||||
} else {
|
||||
console.log(`Global default already exists for: ${elementType.element_type}`);
|
||||
console.log(
|
||||
`Global default already exists for: ${elementType.element_type}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,16 +85,18 @@ module.exports = {
|
||||
`SELECT id, element_type, name, sort_order, settings_json
|
||||
FROM element_type_defaults
|
||||
WHERE element_type IN ('spot', 'logo', 'popup') AND "deletedAt" IS NULL`,
|
||||
{ type: Sequelize.QueryTypes.SELECT }
|
||||
{ type: Sequelize.QueryTypes.SELECT },
|
||||
);
|
||||
|
||||
// Get all projects
|
||||
const projects = await queryInterface.sequelize.query(
|
||||
`SELECT id FROM projects WHERE "deletedAt" IS NULL`,
|
||||
{ type: Sequelize.QueryTypes.SELECT }
|
||||
{ type: Sequelize.QueryTypes.SELECT },
|
||||
);
|
||||
|
||||
console.log(`Backfilling ${globalDefaults.length} element types to ${projects.length} projects...`);
|
||||
console.log(
|
||||
`Backfilling ${globalDefaults.length} element types to ${projects.length} projects...`,
|
||||
);
|
||||
|
||||
// Backfill project_element_defaults for each project
|
||||
for (const project of projects) {
|
||||
@ -102,24 +106,29 @@ module.exports = {
|
||||
`SELECT id FROM project_element_defaults
|
||||
WHERE "projectId" = :projectId AND element_type = :element_type AND "deletedAt" IS NULL`,
|
||||
{
|
||||
replacements: { projectId: project.id, element_type: globalDefault.element_type },
|
||||
replacements: {
|
||||
projectId: project.id,
|
||||
element_type: globalDefault.element_type,
|
||||
},
|
||||
type: Sequelize.QueryTypes.SELECT,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (!existing) {
|
||||
await queryInterface.bulkInsert('project_element_defaults', [{
|
||||
id: uuidv4(),
|
||||
projectId: project.id,
|
||||
element_type: globalDefault.element_type,
|
||||
name: globalDefault.name,
|
||||
sort_order: globalDefault.sort_order,
|
||||
settings_json: globalDefault.settings_json,
|
||||
source_element_id: globalDefault.id,
|
||||
snapshot_version: 1,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
}]);
|
||||
await queryInterface.bulkInsert('project_element_defaults', [
|
||||
{
|
||||
id: uuidv4(),
|
||||
projectId: project.id,
|
||||
element_type: globalDefault.element_type,
|
||||
name: globalDefault.name,
|
||||
sort_order: globalDefault.sort_order,
|
||||
settings_json: globalDefault.settings_json,
|
||||
source_element_id: globalDefault.id,
|
||||
snapshot_version: 1,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
},
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -130,12 +139,12 @@ module.exports = {
|
||||
async down(queryInterface, _Sequelize) {
|
||||
// Remove the added element types from project_element_defaults
|
||||
await queryInterface.sequelize.query(
|
||||
`DELETE FROM project_element_defaults WHERE element_type IN ('spot', 'logo', 'popup')`
|
||||
`DELETE FROM project_element_defaults WHERE element_type IN ('spot', 'logo', 'popup')`,
|
||||
);
|
||||
|
||||
// Remove from element_type_defaults
|
||||
await queryInterface.sequelize.query(
|
||||
`DELETE FROM element_type_defaults WHERE element_type IN ('spot', 'logo', 'popup')`
|
||||
`DELETE FROM element_type_defaults WHERE element_type IN ('spot', 'logo', 'popup')`,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
@ -0,0 +1,295 @@
|
||||
'use strict';
|
||||
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
|
||||
/**
|
||||
* Sync all 11 element type defaults with correct sort_order.
|
||||
* This migration ensures all element types exist in element_type_defaults
|
||||
* and backfills any missing project_element_defaults for existing projects.
|
||||
*/
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
const now = new Date();
|
||||
|
||||
// Define all 11 element types with correct sort_order
|
||||
const DEFAULT_ELEMENT_TYPES = [
|
||||
{
|
||||
element_type: 'navigation_next',
|
||||
name: 'Navigation Forward Button',
|
||||
sort_order: 1,
|
||||
default_settings_json: {
|
||||
label: 'Navigation: Forward',
|
||||
navLabel: 'Forward',
|
||||
navType: 'forward',
|
||||
navDisabled: false,
|
||||
transitionReverseMode: 'auto_reverse',
|
||||
transitionDurationSec: 0.7,
|
||||
appearDelaySec: 0,
|
||||
appearDurationSec: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
element_type: 'navigation_prev',
|
||||
name: 'Navigation Back Button',
|
||||
sort_order: 2,
|
||||
default_settings_json: {
|
||||
label: 'Navigation: Back',
|
||||
navLabel: 'Back',
|
||||
navType: 'back',
|
||||
navDisabled: false,
|
||||
transitionReverseMode: 'auto_reverse',
|
||||
transitionDurationSec: 0.7,
|
||||
appearDelaySec: 0,
|
||||
appearDurationSec: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
element_type: 'tooltip',
|
||||
name: 'Tooltip',
|
||||
sort_order: 3,
|
||||
default_settings_json: {
|
||||
label: 'Tooltip',
|
||||
tooltipTitle: 'Tooltip title',
|
||||
tooltipText: 'Tooltip text',
|
||||
appearDelaySec: 0,
|
||||
appearDurationSec: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
element_type: 'description',
|
||||
name: 'Description',
|
||||
sort_order: 4,
|
||||
default_settings_json: {
|
||||
label: 'Description',
|
||||
descriptionTitle: 'TITLE',
|
||||
descriptionText: '',
|
||||
descriptionTitleFontSize: '48px',
|
||||
descriptionTextFontSize: '36px',
|
||||
descriptionTitleFontFamily: 'inherit',
|
||||
descriptionTextFontFamily: 'inherit',
|
||||
descriptionTitleColor: '#000000',
|
||||
descriptionTextColor: '#4B5563',
|
||||
descriptionBackgroundColor: 'transparent',
|
||||
appearDelaySec: 0,
|
||||
appearDurationSec: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
element_type: 'gallery',
|
||||
name: 'Gallery',
|
||||
sort_order: 5,
|
||||
default_settings_json: {
|
||||
label: 'Gallery',
|
||||
galleryCards: [{ imageUrl: '', title: 'Card 1', description: '' }],
|
||||
appearDelaySec: 0,
|
||||
appearDurationSec: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
element_type: 'carousel',
|
||||
name: 'Carousel',
|
||||
sort_order: 6,
|
||||
default_settings_json: {
|
||||
label: 'Carousel',
|
||||
carouselSlides: [{ imageUrl: '', caption: 'Slide 1' }],
|
||||
carouselPrevIconUrl: '',
|
||||
carouselNextIconUrl: '',
|
||||
appearDelaySec: 0,
|
||||
appearDurationSec: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
element_type: 'video_player',
|
||||
name: 'Video Player',
|
||||
sort_order: 7,
|
||||
default_settings_json: {
|
||||
label: 'Video Player',
|
||||
mediaUrl: '',
|
||||
mediaAutoplay: true,
|
||||
mediaLoop: true,
|
||||
mediaMuted: true,
|
||||
appearDelaySec: 0,
|
||||
appearDurationSec: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
element_type: 'audio_player',
|
||||
name: 'Audio Player',
|
||||
sort_order: 8,
|
||||
default_settings_json: {
|
||||
label: 'Audio Player',
|
||||
mediaUrl: '',
|
||||
mediaAutoplay: true,
|
||||
mediaLoop: true,
|
||||
mediaMuted: false,
|
||||
appearDelaySec: 0,
|
||||
appearDurationSec: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
element_type: 'spot',
|
||||
name: 'Hotspot',
|
||||
sort_order: 9,
|
||||
default_settings_json: {
|
||||
label: 'Hotspot',
|
||||
iconUrl: '',
|
||||
appearDelaySec: 0,
|
||||
appearDurationSec: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
element_type: 'logo',
|
||||
name: 'Logo',
|
||||
sort_order: 10,
|
||||
default_settings_json: {
|
||||
label: 'Logo',
|
||||
iconUrl: '',
|
||||
backgroundImageUrl: '',
|
||||
appearDelaySec: 0,
|
||||
appearDurationSec: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
element_type: 'popup',
|
||||
name: 'Popup',
|
||||
sort_order: 11,
|
||||
default_settings_json: {
|
||||
label: 'Popup',
|
||||
iconUrl: '',
|
||||
popupTitle: '',
|
||||
popupContent: '',
|
||||
appearDelaySec: 0,
|
||||
appearDurationSec: null,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
console.log('Syncing all 11 element type defaults...');
|
||||
|
||||
// Track inserted/updated global defaults for backfill
|
||||
const globalDefaultIds = new Map();
|
||||
|
||||
// For each element type: insert if not exists, update sort_order if wrong
|
||||
for (const elementType of DEFAULT_ELEMENT_TYPES) {
|
||||
const [existing] = await queryInterface.sequelize.query(
|
||||
`SELECT id, sort_order FROM element_type_defaults
|
||||
WHERE element_type = :element_type AND "deletedAt" IS NULL`,
|
||||
{
|
||||
replacements: { element_type: elementType.element_type },
|
||||
type: Sequelize.QueryTypes.SELECT,
|
||||
},
|
||||
);
|
||||
|
||||
if (!existing) {
|
||||
// Insert new element type
|
||||
const newId = uuidv4();
|
||||
await queryInterface.bulkInsert('element_type_defaults', [
|
||||
{
|
||||
id: newId,
|
||||
element_type: elementType.element_type,
|
||||
name: elementType.name,
|
||||
sort_order: elementType.sort_order,
|
||||
settings_json: JSON.stringify(elementType.default_settings_json),
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
},
|
||||
]);
|
||||
globalDefaultIds.set(elementType.element_type, newId);
|
||||
console.log(
|
||||
`Inserted: ${elementType.element_type} (sort_order: ${elementType.sort_order})`,
|
||||
);
|
||||
} else {
|
||||
globalDefaultIds.set(elementType.element_type, existing.id);
|
||||
// Update sort_order if different
|
||||
if (existing.sort_order !== elementType.sort_order) {
|
||||
await queryInterface.sequelize.query(
|
||||
`UPDATE element_type_defaults
|
||||
SET sort_order = :sort_order, "updatedAt" = :now
|
||||
WHERE id = :id`,
|
||||
{
|
||||
replacements: {
|
||||
sort_order: elementType.sort_order,
|
||||
now,
|
||||
id: existing.id,
|
||||
},
|
||||
},
|
||||
);
|
||||
console.log(
|
||||
`Updated sort_order for ${elementType.element_type}: ${existing.sort_order} -> ${elementType.sort_order}`,
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
`Already exists: ${elementType.element_type} (sort_order: ${elementType.sort_order})`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get all projects
|
||||
const projects = await queryInterface.sequelize.query(
|
||||
`SELECT id FROM projects WHERE "deletedAt" IS NULL`,
|
||||
{ type: Sequelize.QueryTypes.SELECT },
|
||||
);
|
||||
|
||||
console.log(
|
||||
`Backfilling missing project_element_defaults for ${projects.length} projects...`,
|
||||
);
|
||||
|
||||
// Get all global defaults for backfill
|
||||
const globalDefaults = await queryInterface.sequelize.query(
|
||||
`SELECT id, element_type, name, sort_order, settings_json
|
||||
FROM element_type_defaults
|
||||
WHERE "deletedAt" IS NULL`,
|
||||
{ type: Sequelize.QueryTypes.SELECT },
|
||||
);
|
||||
|
||||
let backfillCount = 0;
|
||||
|
||||
// Backfill project_element_defaults for each project
|
||||
for (const project of projects) {
|
||||
for (const globalDefault of globalDefaults) {
|
||||
// Check if project already has this element type
|
||||
const [existing] = await queryInterface.sequelize.query(
|
||||
`SELECT id FROM project_element_defaults
|
||||
WHERE "projectId" = :projectId AND element_type = :element_type AND "deletedAt" IS NULL`,
|
||||
{
|
||||
replacements: {
|
||||
projectId: project.id,
|
||||
element_type: globalDefault.element_type,
|
||||
},
|
||||
type: Sequelize.QueryTypes.SELECT,
|
||||
},
|
||||
);
|
||||
|
||||
if (!existing) {
|
||||
await queryInterface.bulkInsert('project_element_defaults', [
|
||||
{
|
||||
id: uuidv4(),
|
||||
projectId: project.id,
|
||||
element_type: globalDefault.element_type,
|
||||
name: globalDefault.name,
|
||||
sort_order: globalDefault.sort_order,
|
||||
settings_json: globalDefault.settings_json,
|
||||
source_element_id: globalDefault.id,
|
||||
snapshot_version: 1,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
},
|
||||
]);
|
||||
backfillCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Backfilled ${backfillCount} project element defaults.`);
|
||||
console.log('Sync complete.');
|
||||
},
|
||||
|
||||
async down(_queryInterface, _Sequelize) {
|
||||
// This migration is safe - it only adds missing data
|
||||
// No destructive down migration needed
|
||||
console.log(
|
||||
'No down migration needed - this migration only adds missing data.',
|
||||
);
|
||||
},
|
||||
};
|
||||
@ -1,4 +1,4 @@
|
||||
module.exports = function(sequelize, DataTypes) {
|
||||
module.exports = function (sequelize, DataTypes) {
|
||||
const access_logs = sequelize.define(
|
||||
'access_logs',
|
||||
{
|
||||
@ -8,50 +8,44 @@ module.exports = function(sequelize, DataTypes) {
|
||||
primaryKey: true,
|
||||
},
|
||||
|
||||
environment: {
|
||||
environment: {
|
||||
type: DataTypes.ENUM,
|
||||
allowNull: false,
|
||||
|
||||
values: [
|
||||
|
||||
"admin",
|
||||
|
||||
|
||||
"stage",
|
||||
|
||||
|
||||
"production"
|
||||
|
||||
],
|
||||
|
||||
values: ['admin', 'stage', 'production'],
|
||||
},
|
||||
|
||||
path: {
|
||||
path: {
|
||||
type: DataTypes.TEXT,
|
||||
validate: {
|
||||
len: { args: [0, 2048], msg: 'Path must be at most 2048 characters' },
|
||||
},
|
||||
},
|
||||
|
||||
ip_address: {
|
||||
ip_address: {
|
||||
type: DataTypes.TEXT,
|
||||
validate: {
|
||||
len: { args: [0, 45], msg: 'IP address must be at most 45 characters' },
|
||||
len: {
|
||||
args: [0, 45],
|
||||
msg: 'IP address must be at most 45 characters',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
user_agent: {
|
||||
user_agent: {
|
||||
type: DataTypes.TEXT,
|
||||
validate: {
|
||||
len: { args: [0, 1024], msg: 'User agent must be at most 1024 characters' },
|
||||
len: {
|
||||
args: [0, 1024],
|
||||
msg: 'User agent must be at most 1024 characters',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
accessed_at: {
|
||||
accessed_at: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW,
|
||||
|
||||
},
|
||||
|
||||
importHash: {
|
||||
@ -74,31 +68,9 @@ accessed_at: {
|
||||
);
|
||||
|
||||
access_logs.associate = (db) => {
|
||||
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
|
||||
|
||||
|
||||
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//end loop
|
||||
|
||||
|
||||
//end loop
|
||||
|
||||
db.access_logs.belongsTo(db.projects, {
|
||||
as: 'project',
|
||||
@ -120,9 +92,6 @@ accessed_at: {
|
||||
onUpdate: 'CASCADE',
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
db.access_logs.belongsTo(db.users, {
|
||||
as: 'createdBy',
|
||||
});
|
||||
@ -132,8 +101,5 @@ accessed_at: {
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
return access_logs;
|
||||
};
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
module.exports = function(sequelize, DataTypes) {
|
||||
module.exports = function (sequelize, DataTypes) {
|
||||
const asset_variants = sequelize.define(
|
||||
'asset_variants',
|
||||
{
|
||||
@ -8,38 +8,31 @@ module.exports = function(sequelize, DataTypes) {
|
||||
primaryKey: true,
|
||||
},
|
||||
|
||||
variant_type: {
|
||||
variant_type: {
|
||||
type: DataTypes.ENUM,
|
||||
|
||||
|
||||
|
||||
values: [
|
||||
'thumbnail',
|
||||
|
||||
"thumbnail",
|
||||
'preview',
|
||||
|
||||
'webp',
|
||||
|
||||
"preview",
|
||||
'mp4_low',
|
||||
|
||||
'mp4_high',
|
||||
|
||||
"webp",
|
||||
|
||||
|
||||
"mp4_low",
|
||||
|
||||
|
||||
"mp4_high",
|
||||
|
||||
|
||||
"original"
|
||||
|
||||
'original',
|
||||
],
|
||||
|
||||
},
|
||||
|
||||
cdn_url: {
|
||||
cdn_url: {
|
||||
type: DataTypes.TEXT,
|
||||
validate: {
|
||||
len: { args: [0, 2048], msg: 'CDN URL must be at most 2048 characters' },
|
||||
len: {
|
||||
args: [0, 2048],
|
||||
msg: 'CDN URL must be at most 2048 characters',
|
||||
},
|
||||
isUrlOrEmpty(value) {
|
||||
if (value && value.length > 0 && !/^https?:\/\/.+/.test(value)) {
|
||||
throw new Error('CDN URL must be a valid URL');
|
||||
@ -48,21 +41,21 @@ cdn_url: {
|
||||
},
|
||||
},
|
||||
|
||||
width_px: {
|
||||
width_px: {
|
||||
type: DataTypes.INTEGER,
|
||||
validate: {
|
||||
min: { args: [0], msg: 'Width must be a non-negative integer' },
|
||||
},
|
||||
},
|
||||
|
||||
height_px: {
|
||||
height_px: {
|
||||
type: DataTypes.INTEGER,
|
||||
validate: {
|
||||
min: { args: [0], msg: 'Height must be a non-negative integer' },
|
||||
},
|
||||
},
|
||||
|
||||
size_mb: {
|
||||
size_mb: {
|
||||
type: DataTypes.DECIMAL,
|
||||
validate: {
|
||||
min: { args: [0], msg: 'Size must be a non-negative number' },
|
||||
@ -83,31 +76,9 @@ size_mb: {
|
||||
);
|
||||
|
||||
asset_variants.associate = (db) => {
|
||||
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
|
||||
|
||||
|
||||
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//end loop
|
||||
|
||||
|
||||
//end loop
|
||||
|
||||
db.asset_variants.belongsTo(db.assets, {
|
||||
as: 'asset',
|
||||
@ -119,9 +90,6 @@ size_mb: {
|
||||
onUpdate: 'CASCADE',
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
db.asset_variants.belongsTo(db.users, {
|
||||
as: 'createdBy',
|
||||
});
|
||||
@ -131,9 +99,5 @@ size_mb: {
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
return asset_variants;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
module.exports = function(sequelize, DataTypes) {
|
||||
module.exports = function (sequelize, DataTypes) {
|
||||
const assets = sequelize.define(
|
||||
'assets',
|
||||
{
|
||||
@ -8,135 +8,92 @@ module.exports = function(sequelize, DataTypes) {
|
||||
primaryKey: true,
|
||||
},
|
||||
|
||||
name: {
|
||||
name: {
|
||||
type: DataTypes.TEXT,
|
||||
validate: {
|
||||
len: { args: [0, 255], msg: 'Asset name must be at most 255 characters' },
|
||||
len: {
|
||||
args: [0, 255],
|
||||
msg: 'Asset name must be at most 255 characters',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
asset_type: {
|
||||
asset_type: {
|
||||
type: DataTypes.ENUM,
|
||||
allowNull: false,
|
||||
|
||||
values: [
|
||||
|
||||
"image",
|
||||
|
||||
|
||||
"video",
|
||||
|
||||
|
||||
"audio",
|
||||
|
||||
|
||||
"file"
|
||||
|
||||
],
|
||||
|
||||
values: ['image', 'video', 'audio', 'file'],
|
||||
},
|
||||
|
||||
type: {
|
||||
type: {
|
||||
type: DataTypes.ENUM,
|
||||
allowNull: false,
|
||||
defaultValue: "general",
|
||||
defaultValue: 'general',
|
||||
|
||||
values: [
|
||||
'icon',
|
||||
|
||||
"icon",
|
||||
'background_image',
|
||||
|
||||
'audio',
|
||||
|
||||
"background_image",
|
||||
'video',
|
||||
|
||||
'transition',
|
||||
|
||||
"audio",
|
||||
'logo',
|
||||
|
||||
'favicon',
|
||||
|
||||
"video",
|
||||
|
||||
|
||||
"transition",
|
||||
|
||||
|
||||
"logo",
|
||||
|
||||
|
||||
"favicon",
|
||||
|
||||
|
||||
"document",
|
||||
|
||||
|
||||
"general"
|
||||
'document',
|
||||
|
||||
'general',
|
||||
],
|
||||
|
||||
},
|
||||
|
||||
cdn_url: {
|
||||
cdn_url: {
|
||||
type: DataTypes.TEXT,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
storage_key: {
|
||||
storage_key: {
|
||||
type: DataTypes.TEXT,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
mime_type: {
|
||||
mime_type: {
|
||||
type: DataTypes.TEXT,
|
||||
validate: {
|
||||
is: { args: /^[a-z0-9]+\/[a-z0-9.+-]+$/i, msg: 'Invalid MIME type format' },
|
||||
is: {
|
||||
args: /^[a-z0-9]+\/[a-z0-9.+-]+$/i,
|
||||
msg: 'Invalid MIME type format',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
size_mb: {
|
||||
size_mb: {
|
||||
type: DataTypes.DECIMAL,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
width_px: {
|
||||
width_px: {
|
||||
type: DataTypes.INTEGER,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
height_px: {
|
||||
height_px: {
|
||||
type: DataTypes.INTEGER,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
duration_sec: {
|
||||
duration_sec: {
|
||||
type: DataTypes.DECIMAL,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
checksum: {
|
||||
checksum: {
|
||||
type: DataTypes.TEXT,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
is_public: {
|
||||
is_public: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
|
||||
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
importHash: {
|
||||
@ -160,41 +117,19 @@ is_public: {
|
||||
);
|
||||
|
||||
assets.associate = (db) => {
|
||||
|
||||
|
||||
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
|
||||
|
||||
db.assets.hasMany(db.asset_variants, {
|
||||
as: 'asset_variants_asset',
|
||||
foreignKey: {
|
||||
name: 'assetId',
|
||||
name: 'assetId',
|
||||
},
|
||||
constraints: true,
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//end loop
|
||||
|
||||
|
||||
//end loop
|
||||
|
||||
db.assets.belongsTo(db.projects, {
|
||||
as: 'project',
|
||||
@ -206,9 +141,6 @@ is_public: {
|
||||
onUpdate: 'CASCADE',
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
db.assets.belongsTo(db.users, {
|
||||
as: 'createdBy',
|
||||
});
|
||||
@ -218,7 +150,5 @@ is_public: {
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
return assets;
|
||||
};
|
||||
|
||||
@ -13,7 +13,10 @@ module.exports = function (sequelize, DataTypes) {
|
||||
unique: true,
|
||||
validate: {
|
||||
notEmpty: { msg: 'Element type is required' },
|
||||
len: { args: [1, 100], msg: 'Element type must be between 1 and 100 characters' },
|
||||
len: {
|
||||
args: [1, 100],
|
||||
msg: 'Element type must be between 1 and 100 characters',
|
||||
},
|
||||
},
|
||||
},
|
||||
name: {
|
||||
@ -21,7 +24,10 @@ module.exports = function (sequelize, DataTypes) {
|
||||
allowNull: false,
|
||||
validate: {
|
||||
notEmpty: { msg: 'Name is required' },
|
||||
len: { args: [1, 255], msg: 'Name must be between 1 and 255 characters' },
|
||||
len: {
|
||||
args: [1, 255],
|
||||
msg: 'Name must be between 1 and 255 characters',
|
||||
},
|
||||
},
|
||||
},
|
||||
sort_order: {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
module.exports = function(sequelize, DataTypes) {
|
||||
module.exports = function (sequelize, DataTypes) {
|
||||
const file = sequelize.define(
|
||||
'file',
|
||||
{
|
||||
|
||||
@ -5,7 +5,7 @@ const path = require('path');
|
||||
const Sequelize = require('sequelize');
|
||||
const basename = path.basename(__filename);
|
||||
const env = process.env.NODE_ENV || 'development';
|
||||
const config = require("../db.config")[env];
|
||||
const config = require('../db.config')[env];
|
||||
const db = {};
|
||||
|
||||
let sequelize;
|
||||
@ -13,20 +13,29 @@ console.log(env);
|
||||
if (config.use_env_variable) {
|
||||
sequelize = new Sequelize(process.env[config.use_env_variable], config);
|
||||
} else {
|
||||
sequelize = new Sequelize(config.database, config.username, config.password, config);
|
||||
sequelize = new Sequelize(
|
||||
config.database,
|
||||
config.username,
|
||||
config.password,
|
||||
config,
|
||||
);
|
||||
}
|
||||
|
||||
fs
|
||||
.readdirSync(__dirname)
|
||||
.filter(file => {
|
||||
return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
|
||||
fs.readdirSync(__dirname)
|
||||
.filter((file) => {
|
||||
return (
|
||||
file.indexOf('.') !== 0 && file !== basename && file.slice(-3) === '.js'
|
||||
);
|
||||
})
|
||||
.forEach(file => {
|
||||
const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes)
|
||||
.forEach((file) => {
|
||||
const model = require(path.join(__dirname, file))(
|
||||
sequelize,
|
||||
Sequelize.DataTypes,
|
||||
);
|
||||
db[model.name] = model;
|
||||
});
|
||||
|
||||
Object.keys(db).forEach(modelName => {
|
||||
Object.keys(db).forEach((modelName) => {
|
||||
if (db[modelName].associate) {
|
||||
db[modelName].associate(db);
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
module.exports = function(sequelize, DataTypes) {
|
||||
module.exports = function (sequelize, DataTypes) {
|
||||
const permissions = sequelize.define(
|
||||
'permissions',
|
||||
{
|
||||
@ -8,13 +8,16 @@ module.exports = function(sequelize, DataTypes) {
|
||||
primaryKey: true,
|
||||
},
|
||||
|
||||
name: {
|
||||
name: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
validate: {
|
||||
notEmpty: { msg: 'Permission name is required' },
|
||||
len: { args: [1, 100], msg: 'Permission name must be between 1 and 100 characters' },
|
||||
len: {
|
||||
args: [1, 100],
|
||||
msg: 'Permission name must be between 1 and 100 characters',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -32,34 +35,9 @@ name: {
|
||||
);
|
||||
|
||||
permissions.associate = (db) => {
|
||||
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
|
||||
|
||||
|
||||
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//end loop
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//end loop
|
||||
|
||||
db.permissions.belongsTo(db.users, {
|
||||
as: 'createdBy',
|
||||
@ -70,9 +48,5 @@ name: {
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
return permissions;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
module.exports = function(sequelize, DataTypes) {
|
||||
module.exports = function (sequelize, DataTypes) {
|
||||
const presigned_url_requests = sequelize.define(
|
||||
'presigned_url_requests',
|
||||
{
|
||||
@ -8,82 +8,63 @@ module.exports = function(sequelize, DataTypes) {
|
||||
primaryKey: true,
|
||||
},
|
||||
|
||||
purpose: {
|
||||
purpose: {
|
||||
type: DataTypes.ENUM,
|
||||
|
||||
|
||||
|
||||
values: [
|
||||
|
||||
"upload",
|
||||
|
||||
|
||||
"download"
|
||||
|
||||
],
|
||||
|
||||
values: ['upload', 'download'],
|
||||
},
|
||||
|
||||
asset_type: {
|
||||
asset_type: {
|
||||
type: DataTypes.ENUM,
|
||||
|
||||
|
||||
|
||||
values: [
|
||||
|
||||
"image",
|
||||
|
||||
|
||||
"video",
|
||||
|
||||
|
||||
"audio",
|
||||
|
||||
|
||||
"file"
|
||||
|
||||
],
|
||||
|
||||
values: ['image', 'video', 'audio', 'file'],
|
||||
},
|
||||
|
||||
requested_key: {
|
||||
requested_key: {
|
||||
type: DataTypes.TEXT,
|
||||
validate: {
|
||||
len: { args: [0, 1024], msg: 'Requested key must be at most 1024 characters' },
|
||||
len: {
|
||||
args: [0, 1024],
|
||||
msg: 'Requested key must be at most 1024 characters',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
mime_type: {
|
||||
mime_type: {
|
||||
type: DataTypes.TEXT,
|
||||
validate: {
|
||||
len: { args: [0, 255], msg: 'MIME type must be at most 255 characters' },
|
||||
len: {
|
||||
args: [0, 255],
|
||||
msg: 'MIME type must be at most 255 characters',
|
||||
},
|
||||
isMimeTypeOrEmpty(value) {
|
||||
if (value && value.length > 0 && !/^[\w.-]+\/[\w.+-]+$/.test(value)) {
|
||||
if (
|
||||
value &&
|
||||
value.length > 0 &&
|
||||
!/^[\w.-]+\/[\w.+-]+$/.test(value)
|
||||
) {
|
||||
throw new Error('MIME type must be in format type/subtype');
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
requested_size_mb: {
|
||||
requested_size_mb: {
|
||||
type: DataTypes.DECIMAL,
|
||||
validate: {
|
||||
min: { args: [0], msg: 'Requested size must be a non-negative number' },
|
||||
min: {
|
||||
args: [0],
|
||||
msg: 'Requested size must be a non-negative number',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
expires_at: {
|
||||
expires_at: {
|
||||
type: DataTypes.DATE,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
status: {
|
||||
status: {
|
||||
type: DataTypes.TEXT,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
importHash: {
|
||||
@ -100,31 +81,9 @@ status: {
|
||||
);
|
||||
|
||||
presigned_url_requests.associate = (db) => {
|
||||
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
|
||||
|
||||
|
||||
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//end loop
|
||||
|
||||
|
||||
//end loop
|
||||
|
||||
db.presigned_url_requests.belongsTo(db.projects, {
|
||||
as: 'project',
|
||||
@ -146,9 +105,6 @@ status: {
|
||||
onUpdate: 'CASCADE',
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
db.presigned_url_requests.belongsTo(db.users, {
|
||||
as: 'createdBy',
|
||||
});
|
||||
@ -158,9 +114,5 @@ status: {
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
return presigned_url_requests;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
module.exports = function(sequelize, DataTypes) {
|
||||
module.exports = function (sequelize, DataTypes) {
|
||||
const project_audio_tracks = sequelize.define(
|
||||
'project_audio_tracks',
|
||||
{
|
||||
@ -8,64 +8,42 @@ module.exports = function(sequelize, DataTypes) {
|
||||
primaryKey: true,
|
||||
},
|
||||
|
||||
environment: {
|
||||
environment: {
|
||||
type: DataTypes.ENUM,
|
||||
|
||||
|
||||
|
||||
values: [
|
||||
|
||||
"dev",
|
||||
|
||||
|
||||
"stage",
|
||||
|
||||
|
||||
"production"
|
||||
|
||||
],
|
||||
|
||||
values: ['dev', 'stage', 'production'],
|
||||
},
|
||||
|
||||
source_key: {
|
||||
source_key: {
|
||||
type: DataTypes.TEXT,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
name: {
|
||||
name: {
|
||||
type: DataTypes.TEXT,
|
||||
validate: {
|
||||
len: { args: [0, 255], msg: 'Audio track name must be at most 255 characters' },
|
||||
len: {
|
||||
args: [0, 255],
|
||||
msg: 'Audio track name must be at most 255 characters',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
slug: {
|
||||
slug: {
|
||||
type: DataTypes.TEXT,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
url: {
|
||||
url: {
|
||||
type: DataTypes.TEXT,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
loop: {
|
||||
loop: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
|
||||
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
volume: {
|
||||
volume: {
|
||||
type: DataTypes.DECIMAL,
|
||||
validate: {
|
||||
min: { args: [0], msg: 'Volume must be at least 0' },
|
||||
@ -73,21 +51,15 @@ volume: {
|
||||
},
|
||||
},
|
||||
|
||||
sort_order: {
|
||||
sort_order: {
|
||||
type: DataTypes.INTEGER,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
is_enabled: {
|
||||
is_enabled: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
|
||||
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
importHash: {
|
||||
@ -104,31 +76,9 @@ is_enabled: {
|
||||
);
|
||||
|
||||
project_audio_tracks.associate = (db) => {
|
||||
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
|
||||
|
||||
|
||||
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//end loop
|
||||
|
||||
|
||||
//end loop
|
||||
|
||||
db.project_audio_tracks.belongsTo(db.projects, {
|
||||
as: 'project',
|
||||
@ -140,9 +90,6 @@ is_enabled: {
|
||||
onUpdate: 'CASCADE',
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
db.project_audio_tracks.belongsTo(db.users, {
|
||||
as: 'createdBy',
|
||||
});
|
||||
@ -152,9 +99,5 @@ is_enabled: {
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
return project_audio_tracks;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -13,7 +13,10 @@ module.exports = function (sequelize, DataTypes) {
|
||||
allowNull: false,
|
||||
validate: {
|
||||
notEmpty: { msg: 'Element type is required' },
|
||||
len: { args: [1, 100], msg: 'Element type must be between 1 and 100 characters' },
|
||||
len: {
|
||||
args: [1, 100],
|
||||
msg: 'Element type must be between 1 and 100 characters',
|
||||
},
|
||||
},
|
||||
},
|
||||
name: {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
module.exports = function(sequelize, DataTypes) {
|
||||
module.exports = function (sequelize, DataTypes) {
|
||||
const project_memberships = sequelize.define(
|
||||
'project_memberships',
|
||||
{
|
||||
@ -8,50 +8,27 @@ module.exports = function(sequelize, DataTypes) {
|
||||
primaryKey: true,
|
||||
},
|
||||
|
||||
access_level: {
|
||||
access_level: {
|
||||
type: DataTypes.ENUM,
|
||||
allowNull: false,
|
||||
defaultValue: 'viewer',
|
||||
|
||||
values: [
|
||||
|
||||
"owner",
|
||||
|
||||
|
||||
"editor",
|
||||
|
||||
|
||||
"reviewer",
|
||||
|
||||
|
||||
"viewer"
|
||||
|
||||
],
|
||||
|
||||
values: ['owner', 'editor', 'reviewer', 'viewer'],
|
||||
},
|
||||
|
||||
is_active: {
|
||||
is_active: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
|
||||
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
invited_at: {
|
||||
invited_at: {
|
||||
type: DataTypes.DATE,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
accepted_at: {
|
||||
accepted_at: {
|
||||
type: DataTypes.DATE,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
importHash: {
|
||||
@ -75,31 +52,9 @@ accepted_at: {
|
||||
);
|
||||
|
||||
project_memberships.associate = (db) => {
|
||||
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
|
||||
|
||||
|
||||
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//end loop
|
||||
|
||||
|
||||
//end loop
|
||||
|
||||
db.project_memberships.belongsTo(db.projects, {
|
||||
as: 'project',
|
||||
@ -121,9 +76,6 @@ accepted_at: {
|
||||
onUpdate: 'CASCADE',
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
db.project_memberships.belongsTo(db.users, {
|
||||
as: 'createdBy',
|
||||
});
|
||||
@ -133,8 +85,5 @@ accepted_at: {
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
return project_memberships;
|
||||
};
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
module.exports = function(sequelize, DataTypes) {
|
||||
module.exports = function (sequelize, DataTypes) {
|
||||
const projects = sequelize.define(
|
||||
'projects',
|
||||
{
|
||||
@ -8,67 +8,61 @@ module.exports = function(sequelize, DataTypes) {
|
||||
primaryKey: true,
|
||||
},
|
||||
|
||||
name: {
|
||||
name: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
notEmpty: { msg: 'Project name is required' },
|
||||
len: { args: [1, 255], msg: 'Project name must be between 1 and 255 characters' },
|
||||
len: {
|
||||
args: [1, 255],
|
||||
msg: 'Project name must be between 1 and 255 characters',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
slug: {
|
||||
slug: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
validate: {
|
||||
notEmpty: { msg: 'Slug is required' },
|
||||
is: { args: /^[a-z0-9_-]+$/i, msg: 'Slug can only contain letters, numbers, dashes, and underscores' },
|
||||
len: { args: [1, 255], msg: 'Slug must be between 1 and 255 characters' },
|
||||
is: {
|
||||
args: /^[a-z0-9_-]+$/i,
|
||||
msg: 'Slug can only contain letters, numbers, dashes, and underscores',
|
||||
},
|
||||
len: {
|
||||
args: [1, 255],
|
||||
msg: 'Slug must be between 1 and 255 characters',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
description: {
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
logo_url: {
|
||||
logo_url: {
|
||||
type: DataTypes.TEXT,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
favicon_url: {
|
||||
favicon_url: {
|
||||
type: DataTypes.TEXT,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
og_image_url: {
|
||||
og_image_url: {
|
||||
type: DataTypes.TEXT,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
theme_config_json: {
|
||||
theme_config_json: {
|
||||
type: DataTypes.JSON,
|
||||
},
|
||||
|
||||
custom_css_json: {
|
||||
custom_css_json: {
|
||||
type: DataTypes.JSON,
|
||||
},
|
||||
|
||||
cdn_base_url: {
|
||||
cdn_base_url: {
|
||||
type: DataTypes.TEXT,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
importHash: {
|
||||
@ -81,130 +75,104 @@ cdn_base_url: {
|
||||
timestamps: true,
|
||||
paranoid: true,
|
||||
freezeTableName: true,
|
||||
indexes: [
|
||||
{ fields: ['slug'], unique: true },
|
||||
{ fields: ['deletedAt'] },
|
||||
],
|
||||
indexes: [{ fields: ['slug'], unique: true }, { fields: ['deletedAt'] }],
|
||||
},
|
||||
);
|
||||
|
||||
projects.associate = (db) => {
|
||||
|
||||
|
||||
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
|
||||
|
||||
db.projects.hasMany(db.project_memberships, {
|
||||
as: 'project_memberships_project',
|
||||
foreignKey: {
|
||||
name: 'projectId',
|
||||
name: 'projectId',
|
||||
},
|
||||
constraints: true,
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
});
|
||||
|
||||
|
||||
db.projects.hasMany(db.assets, {
|
||||
as: 'assets_project',
|
||||
foreignKey: {
|
||||
name: 'projectId',
|
||||
name: 'projectId',
|
||||
},
|
||||
constraints: true,
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
});
|
||||
|
||||
|
||||
|
||||
db.projects.hasMany(db.presigned_url_requests, {
|
||||
as: 'presigned_url_requests_project',
|
||||
foreignKey: {
|
||||
name: 'projectId',
|
||||
name: 'projectId',
|
||||
},
|
||||
constraints: true,
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
});
|
||||
|
||||
|
||||
db.projects.hasMany(db.tour_pages, {
|
||||
as: 'tour_pages_project',
|
||||
foreignKey: {
|
||||
name: 'projectId',
|
||||
name: 'projectId',
|
||||
},
|
||||
constraints: true,
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
db.projects.hasMany(db.project_audio_tracks, {
|
||||
as: 'project_audio_tracks_project',
|
||||
foreignKey: {
|
||||
name: 'projectId',
|
||||
name: 'projectId',
|
||||
},
|
||||
constraints: true,
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
});
|
||||
|
||||
|
||||
db.projects.hasMany(db.publish_events, {
|
||||
as: 'publish_events_project',
|
||||
foreignKey: {
|
||||
name: 'projectId',
|
||||
name: 'projectId',
|
||||
},
|
||||
constraints: true,
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
});
|
||||
|
||||
|
||||
db.projects.hasMany(db.pwa_caches, {
|
||||
as: 'pwa_caches_project',
|
||||
foreignKey: {
|
||||
name: 'projectId',
|
||||
name: 'projectId',
|
||||
},
|
||||
constraints: true,
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
});
|
||||
|
||||
|
||||
db.projects.hasMany(db.access_logs, {
|
||||
as: 'access_logs_project',
|
||||
foreignKey: {
|
||||
name: 'projectId',
|
||||
name: 'projectId',
|
||||
},
|
||||
constraints: true,
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
});
|
||||
|
||||
|
||||
db.projects.hasMany(db.project_element_defaults, {
|
||||
as: 'project_element_defaults_project',
|
||||
foreignKey: {
|
||||
name: 'projectId',
|
||||
name: 'projectId',
|
||||
},
|
||||
constraints: true,
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
});
|
||||
|
||||
//end loop
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//end loop
|
||||
|
||||
db.projects.belongsTo(db.users, {
|
||||
as: 'createdBy',
|
||||
@ -215,8 +183,5 @@ cdn_base_url: {
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
return projects;
|
||||
};
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
module.exports = function(sequelize, DataTypes) {
|
||||
module.exports = function (sequelize, DataTypes) {
|
||||
const publish_events = sequelize.define(
|
||||
'publish_events',
|
||||
{
|
||||
@ -8,7 +8,7 @@ module.exports = function(sequelize, DataTypes) {
|
||||
primaryKey: true,
|
||||
},
|
||||
|
||||
title: {
|
||||
title: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
validate: {
|
||||
@ -16,111 +16,78 @@ title: {
|
||||
},
|
||||
},
|
||||
|
||||
description: {
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
validate: {
|
||||
len: { args: [0, 5000], msg: 'Description must be at most 5000 characters' },
|
||||
len: {
|
||||
args: [0, 5000],
|
||||
msg: 'Description must be at most 5000 characters',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
from_environment: {
|
||||
from_environment: {
|
||||
type: DataTypes.ENUM,
|
||||
allowNull: false,
|
||||
|
||||
values: [
|
||||
|
||||
"dev",
|
||||
|
||||
|
||||
"stage",
|
||||
|
||||
|
||||
"production"
|
||||
|
||||
],
|
||||
|
||||
values: ['dev', 'stage', 'production'],
|
||||
},
|
||||
|
||||
to_environment: {
|
||||
to_environment: {
|
||||
type: DataTypes.ENUM,
|
||||
allowNull: false,
|
||||
|
||||
values: [
|
||||
|
||||
"dev",
|
||||
|
||||
|
||||
"stage",
|
||||
|
||||
|
||||
"production"
|
||||
|
||||
],
|
||||
|
||||
values: ['dev', 'stage', 'production'],
|
||||
},
|
||||
|
||||
started_at: {
|
||||
started_at: {
|
||||
type: DataTypes.DATE,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
finished_at: {
|
||||
finished_at: {
|
||||
type: DataTypes.DATE,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
status: {
|
||||
status: {
|
||||
type: DataTypes.ENUM,
|
||||
allowNull: false,
|
||||
defaultValue: 'queued',
|
||||
|
||||
values: [
|
||||
|
||||
"queued",
|
||||
|
||||
|
||||
"running",
|
||||
|
||||
|
||||
"success",
|
||||
|
||||
|
||||
"failed"
|
||||
|
||||
],
|
||||
|
||||
values: ['queued', 'running', 'success', 'failed'],
|
||||
},
|
||||
|
||||
error_message: {
|
||||
error_message: {
|
||||
type: DataTypes.TEXT,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
pages_copied: {
|
||||
pages_copied: {
|
||||
type: DataTypes.INTEGER,
|
||||
validate: {
|
||||
min: { args: [0], msg: 'Pages copied must be a non-negative integer' },
|
||||
min: {
|
||||
args: [0],
|
||||
msg: 'Pages copied must be a non-negative integer',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
transitions_copied: {
|
||||
transitions_copied: {
|
||||
type: DataTypes.INTEGER,
|
||||
validate: {
|
||||
min: { args: [0], msg: 'Transitions copied must be a non-negative integer' },
|
||||
min: {
|
||||
args: [0],
|
||||
msg: 'Transitions copied must be a non-negative integer',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
audios_copied: {
|
||||
audios_copied: {
|
||||
type: DataTypes.INTEGER,
|
||||
validate: {
|
||||
min: { args: [0], msg: 'Audios copied must be a non-negative integer' },
|
||||
min: {
|
||||
args: [0],
|
||||
msg: 'Audios copied must be a non-negative integer',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -144,31 +111,9 @@ audios_copied: {
|
||||
);
|
||||
|
||||
publish_events.associate = (db) => {
|
||||
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
|
||||
|
||||
|
||||
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//end loop
|
||||
|
||||
|
||||
//end loop
|
||||
|
||||
db.publish_events.belongsTo(db.projects, {
|
||||
as: 'project',
|
||||
@ -190,9 +135,6 @@ audios_copied: {
|
||||
onUpdate: 'CASCADE',
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
db.publish_events.belongsTo(db.users, {
|
||||
as: 'createdBy',
|
||||
});
|
||||
@ -202,7 +144,5 @@ audios_copied: {
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
return publish_events;
|
||||
};
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
module.exports = function(sequelize, DataTypes) {
|
||||
module.exports = function (sequelize, DataTypes) {
|
||||
const pwa_caches = sequelize.define(
|
||||
'pwa_caches',
|
||||
{
|
||||
@ -8,55 +8,39 @@ module.exports = function(sequelize, DataTypes) {
|
||||
primaryKey: true,
|
||||
},
|
||||
|
||||
environment: {
|
||||
environment: {
|
||||
type: DataTypes.ENUM,
|
||||
|
||||
|
||||
|
||||
values: [
|
||||
|
||||
"dev",
|
||||
|
||||
|
||||
"stage",
|
||||
|
||||
|
||||
"production"
|
||||
|
||||
],
|
||||
|
||||
values: ['dev', 'stage', 'production'],
|
||||
},
|
||||
|
||||
cache_version: {
|
||||
cache_version: {
|
||||
type: DataTypes.TEXT,
|
||||
validate: {
|
||||
len: { args: [0, 255], msg: 'Cache version must be at most 255 characters' },
|
||||
len: {
|
||||
args: [0, 255],
|
||||
msg: 'Cache version must be at most 255 characters',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
manifest_json: {
|
||||
manifest_json: {
|
||||
type: DataTypes.JSON,
|
||||
},
|
||||
|
||||
asset_list_json: {
|
||||
asset_list_json: {
|
||||
type: DataTypes.JSON,
|
||||
},
|
||||
|
||||
generated_at: {
|
||||
generated_at: {
|
||||
type: DataTypes.DATE,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
is_active: {
|
||||
is_active: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
|
||||
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
importHash: {
|
||||
@ -73,31 +57,9 @@ is_active: {
|
||||
);
|
||||
|
||||
pwa_caches.associate = (db) => {
|
||||
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
|
||||
|
||||
|
||||
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//end loop
|
||||
|
||||
|
||||
//end loop
|
||||
|
||||
db.pwa_caches.belongsTo(db.projects, {
|
||||
as: 'project',
|
||||
@ -109,9 +71,6 @@ is_active: {
|
||||
onUpdate: 'CASCADE',
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
db.pwa_caches.belongsTo(db.users, {
|
||||
as: 'createdBy',
|
||||
});
|
||||
@ -121,9 +80,5 @@ is_active: {
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
return pwa_caches;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
module.exports = function(sequelize, DataTypes) {
|
||||
module.exports = function (sequelize, DataTypes) {
|
||||
const roles = sequelize.define(
|
||||
'roles',
|
||||
{
|
||||
@ -8,20 +8,20 @@ module.exports = function(sequelize, DataTypes) {
|
||||
primaryKey: true,
|
||||
},
|
||||
|
||||
name: {
|
||||
name: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
notEmpty: { msg: 'Role name is required' },
|
||||
len: { args: [1, 100], msg: 'Role name must be between 1 and 100 characters' },
|
||||
len: {
|
||||
args: [1, 100],
|
||||
msg: 'Role name must be between 1 and 100 characters',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
role_customization: {
|
||||
role_customization: {
|
||||
type: DataTypes.TEXT,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
importHash: {
|
||||
@ -38,7 +38,6 @@ role_customization: {
|
||||
);
|
||||
|
||||
roles.associate = (db) => {
|
||||
|
||||
db.roles.belongsToMany(db.permissions, {
|
||||
as: 'permissions',
|
||||
foreignKey: {
|
||||
@ -59,43 +58,19 @@ role_customization: {
|
||||
through: 'rolesPermissionsPermissions',
|
||||
});
|
||||
|
||||
|
||||
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
|
||||
|
||||
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
|
||||
|
||||
db.roles.hasMany(db.users, {
|
||||
as: 'users_app_role',
|
||||
foreignKey: {
|
||||
name: 'app_roleId',
|
||||
name: 'app_roleId',
|
||||
},
|
||||
constraints: true,
|
||||
onDelete: 'SET NULL',
|
||||
onUpdate: 'CASCADE',
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//end loop
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//end loop
|
||||
|
||||
db.roles.belongsTo(db.users, {
|
||||
as: 'createdBy',
|
||||
@ -106,9 +81,5 @@ role_customization: {
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
return roles;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
module.exports = function(sequelize, DataTypes) {
|
||||
module.exports = function (sequelize, DataTypes) {
|
||||
const tour_pages = sequelize.define(
|
||||
'tour_pages',
|
||||
{
|
||||
@ -8,100 +8,79 @@ module.exports = function(sequelize, DataTypes) {
|
||||
primaryKey: true,
|
||||
},
|
||||
|
||||
environment: {
|
||||
environment: {
|
||||
type: DataTypes.ENUM,
|
||||
allowNull: false,
|
||||
defaultValue: 'dev',
|
||||
|
||||
values: [
|
||||
|
||||
"dev",
|
||||
|
||||
|
||||
"stage",
|
||||
|
||||
|
||||
"production"
|
||||
|
||||
],
|
||||
|
||||
values: ['dev', 'stage', 'production'],
|
||||
},
|
||||
|
||||
source_key: {
|
||||
source_key: {
|
||||
type: DataTypes.TEXT,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
name: {
|
||||
name: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
notEmpty: { msg: 'Page name is required' },
|
||||
len: { args: [1, 255], msg: 'Page name must be between 1 and 255 characters' },
|
||||
len: {
|
||||
args: [1, 255],
|
||||
msg: 'Page name must be between 1 and 255 characters',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
slug: {
|
||||
slug: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
notEmpty: { msg: 'Slug is required' },
|
||||
is: { args: /^[a-z0-9_-]+$/i, msg: 'Slug can only contain letters, numbers, dashes, and underscores' },
|
||||
len: { args: [1, 255], msg: 'Slug must be between 1 and 255 characters' },
|
||||
is: {
|
||||
args: /^[a-z0-9_-]+$/i,
|
||||
msg: 'Slug can only contain letters, numbers, dashes, and underscores',
|
||||
},
|
||||
len: {
|
||||
args: [1, 255],
|
||||
msg: 'Slug must be between 1 and 255 characters',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
sort_order: {
|
||||
sort_order: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
|
||||
},
|
||||
|
||||
background_image_url: {
|
||||
background_image_url: {
|
||||
type: DataTypes.TEXT,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
background_video_url: {
|
||||
background_video_url: {
|
||||
type: DataTypes.TEXT,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
background_audio_url: {
|
||||
background_audio_url: {
|
||||
type: DataTypes.TEXT,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
background_loop: {
|
||||
background_loop: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
|
||||
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
requires_auth: {
|
||||
requires_auth: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
|
||||
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
ui_schema_json: {
|
||||
ui_schema_json: {
|
||||
type: DataTypes.JSON,
|
||||
},
|
||||
|
||||
@ -125,31 +104,9 @@ ui_schema_json: {
|
||||
);
|
||||
|
||||
tour_pages.associate = (db) => {
|
||||
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
|
||||
|
||||
|
||||
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//end loop
|
||||
|
||||
|
||||
//end loop
|
||||
|
||||
db.tour_pages.belongsTo(db.projects, {
|
||||
as: 'project',
|
||||
@ -161,9 +118,6 @@ ui_schema_json: {
|
||||
onUpdate: 'CASCADE',
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
db.tour_pages.belongsTo(db.users, {
|
||||
as: 'createdBy',
|
||||
});
|
||||
@ -173,8 +127,5 @@ ui_schema_json: {
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
return tour_pages;
|
||||
};
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ const providers = config.providers;
|
||||
const crypto = require('crypto');
|
||||
const bcrypt = require('bcrypt');
|
||||
|
||||
module.exports = function(sequelize, DataTypes) {
|
||||
module.exports = function (sequelize, DataTypes) {
|
||||
const users = sequelize.define(
|
||||
'users',
|
||||
{
|
||||
@ -13,28 +13,19 @@ module.exports = function(sequelize, DataTypes) {
|
||||
primaryKey: true,
|
||||
},
|
||||
|
||||
firstName: {
|
||||
firstName: {
|
||||
type: DataTypes.TEXT,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
lastName: {
|
||||
lastName: {
|
||||
type: DataTypes.TEXT,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
phoneNumber: {
|
||||
phoneNumber: {
|
||||
type: DataTypes.TEXT,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
email: {
|
||||
email: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
@ -44,65 +35,45 @@ email: {
|
||||
},
|
||||
},
|
||||
|
||||
disabled: {
|
||||
disabled: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
|
||||
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
password: {
|
||||
password: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
|
||||
},
|
||||
|
||||
emailVerified: {
|
||||
emailVerified: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
|
||||
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
emailVerificationToken: {
|
||||
emailVerificationToken: {
|
||||
type: DataTypes.TEXT,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
emailVerificationTokenExpiresAt: {
|
||||
emailVerificationTokenExpiresAt: {
|
||||
type: DataTypes.DATE,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
passwordResetToken: {
|
||||
passwordResetToken: {
|
||||
type: DataTypes.TEXT,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
passwordResetTokenExpiresAt: {
|
||||
passwordResetTokenExpiresAt: {
|
||||
type: DataTypes.DATE,
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
provider: {
|
||||
provider: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
defaultValue: providers.LOCAL,
|
||||
|
||||
},
|
||||
|
||||
importHash: {
|
||||
@ -124,7 +95,6 @@ provider: {
|
||||
);
|
||||
|
||||
users.associate = (db) => {
|
||||
|
||||
db.users.belongsToMany(db.permissions, {
|
||||
as: 'custom_permissions',
|
||||
foreignKey: {
|
||||
@ -145,70 +115,49 @@ provider: {
|
||||
through: 'usersCustom_permissionsPermissions',
|
||||
});
|
||||
|
||||
|
||||
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
|
||||
|
||||
db.users.hasMany(db.project_memberships, {
|
||||
as: 'project_memberships_user',
|
||||
foreignKey: {
|
||||
name: 'userId',
|
||||
name: 'userId',
|
||||
},
|
||||
constraints: true,
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
db.users.hasMany(db.presigned_url_requests, {
|
||||
as: 'presigned_url_requests_user',
|
||||
foreignKey: {
|
||||
name: 'userId',
|
||||
name: 'userId',
|
||||
},
|
||||
constraints: true,
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
db.users.hasMany(db.publish_events, {
|
||||
as: 'publish_events_user',
|
||||
foreignKey: {
|
||||
name: 'userId',
|
||||
name: 'userId',
|
||||
},
|
||||
constraints: true,
|
||||
onDelete: 'SET NULL',
|
||||
onUpdate: 'CASCADE',
|
||||
});
|
||||
|
||||
|
||||
|
||||
db.users.hasMany(db.access_logs, {
|
||||
as: 'access_logs_user',
|
||||
foreignKey: {
|
||||
name: 'userId',
|
||||
name: 'userId',
|
||||
},
|
||||
constraints: true,
|
||||
onDelete: 'SET NULL',
|
||||
onUpdate: 'CASCADE',
|
||||
});
|
||||
|
||||
|
||||
|
||||
//end loop
|
||||
|
||||
|
||||
//end loop
|
||||
|
||||
db.users.belongsTo(db.roles, {
|
||||
as: 'app_role',
|
||||
@ -220,8 +169,6 @@ provider: {
|
||||
onUpdate: 'CASCADE',
|
||||
});
|
||||
|
||||
|
||||
|
||||
db.users.hasMany(db.file, {
|
||||
as: 'avatar',
|
||||
foreignKey: 'belongsToId',
|
||||
@ -234,7 +181,6 @@ provider: {
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
db.users.belongsTo(db.users, {
|
||||
as: 'createdBy',
|
||||
});
|
||||
@ -244,47 +190,41 @@ provider: {
|
||||
});
|
||||
};
|
||||
|
||||
users.beforeCreate((users) => {
|
||||
users = trimStringFields(users);
|
||||
|
||||
users.beforeCreate((users) => {
|
||||
users = trimStringFields(users);
|
||||
if (
|
||||
users.provider !== providers.LOCAL &&
|
||||
Object.values(providers).indexOf(users.provider) > -1
|
||||
) {
|
||||
users.emailVerified = true;
|
||||
|
||||
if (users.provider !== providers.LOCAL && Object.values(providers).indexOf(users.provider) > -1) {
|
||||
users.emailVerified = true;
|
||||
if (!users.password) {
|
||||
const password = crypto.randomBytes(20).toString('hex');
|
||||
|
||||
if (!users.password) {
|
||||
const password = crypto
|
||||
.randomBytes(20)
|
||||
.toString('hex');
|
||||
|
||||
const hashedPassword = bcrypt.hashSync(
|
||||
password,
|
||||
config.bcrypt.saltRounds,
|
||||
const hashedPassword = bcrypt.hashSync(
|
||||
password,
|
||||
config.bcrypt.saltRounds,
|
||||
);
|
||||
|
||||
users.password = hashedPassword
|
||||
}
|
||||
}
|
||||
});
|
||||
users.password = hashedPassword;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
users.beforeUpdate((users) => {
|
||||
trimStringFields(users);
|
||||
});
|
||||
|
||||
|
||||
return users;
|
||||
};
|
||||
|
||||
|
||||
function trimStringFields(users) {
|
||||
users.email = users.email.trim();
|
||||
|
||||
users.firstName = users.firstName
|
||||
? users.firstName.trim()
|
||||
: null;
|
||||
users.firstName = users.firstName ? users.firstName.trim() : null;
|
||||
|
||||
users.lastName = users.lastName
|
||||
? users.lastName.trim()
|
||||
: null;
|
||||
users.lastName = users.lastName ? users.lastName.trim() : null;
|
||||
|
||||
return users;
|
||||
}
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
const db = require('./models');
|
||||
const {execSync} = require("child_process");
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
console.log('Resetting Database');
|
||||
|
||||
db.sequelize
|
||||
.sync({ force: true })
|
||||
.then(() => {
|
||||
execSync("sequelize db:seed:all");
|
||||
execSync('sequelize db:seed:all');
|
||||
console.log('OK');
|
||||
process.exit();
|
||||
})
|
||||
|
||||
@ -1,51 +1,54 @@
|
||||
'use strict';
|
||||
const bcrypt = require("bcrypt");
|
||||
const config = require("../../config");
|
||||
const bcrypt = require('bcrypt');
|
||||
const config = require('../../config');
|
||||
|
||||
const ids = [
|
||||
'193bf4b5-9f07-4bd5-9a43-e7e41f3e96af',
|
||||
'af5a87be-8f9c-4630-902a-37a60b7005ba',
|
||||
'5bc531ab-611f-41f3-9373-b7cc5d09c93d',
|
||||
]
|
||||
'193bf4b5-9f07-4bd5-9a43-e7e41f3e96af',
|
||||
'af5a87be-8f9c-4630-902a-37a60b7005ba',
|
||||
'5bc531ab-611f-41f3-9373-b7cc5d09c93d',
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface) => {
|
||||
let admin_hash = bcrypt.hashSync(config.admin_pass, config.bcrypt.saltRounds);
|
||||
let admin_hash = bcrypt.hashSync(
|
||||
config.admin_pass,
|
||||
config.bcrypt.saltRounds,
|
||||
);
|
||||
let user_hash = bcrypt.hashSync(config.user_pass, config.bcrypt.saltRounds);
|
||||
|
||||
try {
|
||||
await queryInterface.bulkInsert('users', [
|
||||
{
|
||||
id: ids[0],
|
||||
firstName: 'Admin',
|
||||
email: config.admin_email,
|
||||
emailVerified: true,
|
||||
provider: config.providers.LOCAL,
|
||||
password: admin_hash,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
},
|
||||
{
|
||||
id: ids[1],
|
||||
firstName: 'John',
|
||||
email: 'john@doe.com',
|
||||
emailVerified: true,
|
||||
provider: config.providers.LOCAL,
|
||||
password: user_hash,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
},
|
||||
{
|
||||
id: ids[2],
|
||||
firstName: 'Client',
|
||||
email: 'client@hello.com',
|
||||
emailVerified: true,
|
||||
provider: config.providers.LOCAL,
|
||||
password: user_hash,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
},
|
||||
]);
|
||||
await queryInterface.bulkInsert('users', [
|
||||
{
|
||||
id: ids[0],
|
||||
firstName: 'Admin',
|
||||
email: config.admin_email,
|
||||
emailVerified: true,
|
||||
provider: config.providers.LOCAL,
|
||||
password: admin_hash,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
{
|
||||
id: ids[1],
|
||||
firstName: 'John',
|
||||
email: 'john@doe.com',
|
||||
emailVerified: true,
|
||||
provider: config.providers.LOCAL,
|
||||
password: user_hash,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
{
|
||||
id: ids[2],
|
||||
firstName: 'Client',
|
||||
email: 'client@hello.com',
|
||||
emailVerified: true,
|
||||
provider: config.providers.LOCAL,
|
||||
password: user_hash,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
]);
|
||||
} catch (error) {
|
||||
console.error('Error during bulkInsert:', error);
|
||||
throw error;
|
||||
@ -53,14 +56,18 @@ module.exports = {
|
||||
},
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
try {
|
||||
await queryInterface.bulkDelete('users', {
|
||||
id: {
|
||||
[Sequelize.Op.in]: ids,
|
||||
},
|
||||
}, {});
|
||||
} catch (error) {
|
||||
console.error('Error during bulkDelete:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
await queryInterface.bulkDelete(
|
||||
'users',
|
||||
{
|
||||
id: {
|
||||
[Sequelize.Op.in]: ids,
|
||||
},
|
||||
},
|
||||
{},
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error during bulkDelete:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -2,7 +2,9 @@ const db = require('./models');
|
||||
|
||||
async function syncDatabase() {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
console.error('ERROR: sync.js should not be run in production. Use migrations instead.');
|
||||
console.error(
|
||||
'ERROR: sync.js should not be run in production. Use migrations instead.',
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
||||
@ -15,10 +15,7 @@ module.exports = class Utils {
|
||||
|
||||
static ilike(model, column, value) {
|
||||
return Sequelize.where(
|
||||
Sequelize.fn(
|
||||
'lower',
|
||||
Sequelize.col(`${model}.${column}`),
|
||||
),
|
||||
Sequelize.fn('lower', Sequelize.col(`${model}.${column}`)),
|
||||
{
|
||||
[Sequelize.Op.like]: `%${value}%`.toLowerCase(),
|
||||
},
|
||||
|
||||
@ -9,82 +9,129 @@ function createEntityRouter(entityName, Service, DBApi, options = {}) {
|
||||
const permissionEntity = options.permissionEntity || entityName;
|
||||
router.use(checkCrudPermissions(permissionEntity));
|
||||
|
||||
router.post('/', wrapAsync(async (req, res) => {
|
||||
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
|
||||
const link = new URL(referer);
|
||||
const payload = await Service.create(req.body.data, req.currentUser, true, link.host);
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
router.post('/bulk-import', wrapAsync(async (req, res) => {
|
||||
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
|
||||
const link = new URL(referer);
|
||||
await Service.bulkImport(req, res, true, link.host);
|
||||
res.status(200).send(true);
|
||||
}));
|
||||
|
||||
router.put('/:id', wrapAsync(async (req, res) => {
|
||||
await Service.update(req.body.data, req.body.id, req.currentUser);
|
||||
res.status(200).send(true);
|
||||
}));
|
||||
|
||||
router.delete('/:id', wrapAsync(async (req, res) => {
|
||||
await Service.remove(req.params.id, req.currentUser);
|
||||
res.status(200).send(true);
|
||||
}));
|
||||
|
||||
router.post('/deleteByIds', wrapAsync(async (req, res) => {
|
||||
await Service.deleteByIds(req.body.data, req.currentUser);
|
||||
res.status(200).send(true);
|
||||
}));
|
||||
|
||||
router.get('/', wrapAsync(async (req, res) => {
|
||||
const filetype = req.query.filetype;
|
||||
const currentUser = req.currentUser;
|
||||
const runtimeContext = req.runtimeContext;
|
||||
|
||||
const payload = await DBApi.findAll(req.query, { currentUser, runtimeContext });
|
||||
|
||||
if (filetype === 'csv') {
|
||||
const fields = options.csvFields || DBApi.CSV_FIELDS || ['id', 'createdAt'];
|
||||
const opts = { fields };
|
||||
try {
|
||||
const csv = parse(payload.rows, opts);
|
||||
res.status(200).attachment('export.csv').send(csv);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).send('CSV export error');
|
||||
}
|
||||
} else {
|
||||
router.post(
|
||||
'/',
|
||||
wrapAsync(async (req, res) => {
|
||||
const referer =
|
||||
req.headers.referer ||
|
||||
`${req.protocol}://${req.hostname}${req.originalUrl}`;
|
||||
const link = new URL(referer);
|
||||
const payload = await Service.create(
|
||||
req.body.data,
|
||||
req.currentUser,
|
||||
true,
|
||||
link.host,
|
||||
);
|
||||
res.status(200).send(payload);
|
||||
}
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
router.get('/count', wrapAsync(async (req, res) => {
|
||||
const currentUser = req.currentUser;
|
||||
const runtimeContext = req.runtimeContext;
|
||||
const payload = await DBApi.findAll(req.query, { countOnly: true, currentUser, runtimeContext });
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
router.post(
|
||||
'/bulk-import',
|
||||
wrapAsync(async (req, res) => {
|
||||
const referer =
|
||||
req.headers.referer ||
|
||||
`${req.protocol}://${req.hostname}${req.originalUrl}`;
|
||||
const link = new URL(referer);
|
||||
await Service.bulkImport(req, res, true, link.host);
|
||||
res.status(200).send(true);
|
||||
}),
|
||||
);
|
||||
|
||||
router.get('/autocomplete', wrapAsync(async (req, res) => {
|
||||
const payload = await DBApi.findAllAutocomplete(
|
||||
req.query.query,
|
||||
req.query.limit,
|
||||
req.query.offset
|
||||
);
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
router.put(
|
||||
'/:id',
|
||||
wrapAsync(async (req, res) => {
|
||||
await Service.update(req.body.data, req.body.id, req.currentUser);
|
||||
res.status(200).send(true);
|
||||
}),
|
||||
);
|
||||
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
if (!isUuidV4(req.params.id)) {
|
||||
return res.status(400).send(`Invalid ${entityName} id`);
|
||||
}
|
||||
router.delete(
|
||||
'/:id',
|
||||
wrapAsync(async (req, res) => {
|
||||
await Service.remove(req.params.id, req.currentUser);
|
||||
res.status(200).send(true);
|
||||
}),
|
||||
);
|
||||
|
||||
const runtimeContext = req.runtimeContext;
|
||||
const payload = await DBApi.findBy({ id: req.params.id }, { runtimeContext });
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
router.post(
|
||||
'/deleteByIds',
|
||||
wrapAsync(async (req, res) => {
|
||||
await Service.deleteByIds(req.body.data, req.currentUser);
|
||||
res.status(200).send(true);
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
wrapAsync(async (req, res) => {
|
||||
const filetype = req.query.filetype;
|
||||
const currentUser = req.currentUser;
|
||||
const runtimeContext = req.runtimeContext;
|
||||
|
||||
const payload = await DBApi.findAll(req.query, {
|
||||
currentUser,
|
||||
runtimeContext,
|
||||
});
|
||||
|
||||
if (filetype === 'csv') {
|
||||
const fields = options.csvFields ||
|
||||
DBApi.CSV_FIELDS || ['id', 'createdAt'];
|
||||
const opts = { fields };
|
||||
try {
|
||||
const csv = parse(payload.rows, opts);
|
||||
res.status(200).attachment('export.csv').send(csv);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).send('CSV export error');
|
||||
}
|
||||
} else {
|
||||
res.status(200).send(payload);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/count',
|
||||
wrapAsync(async (req, res) => {
|
||||
const currentUser = req.currentUser;
|
||||
const runtimeContext = req.runtimeContext;
|
||||
const payload = await DBApi.findAll(req.query, {
|
||||
countOnly: true,
|
||||
currentUser,
|
||||
runtimeContext,
|
||||
});
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/autocomplete',
|
||||
wrapAsync(async (req, res) => {
|
||||
const payload = await DBApi.findAllAutocomplete(
|
||||
req.query.query,
|
||||
req.query.limit,
|
||||
req.query.offset,
|
||||
);
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:id',
|
||||
wrapAsync(async (req, res) => {
|
||||
if (!isUuidV4(req.params.id)) {
|
||||
return res.status(400).send(`Invalid ${entityName} id`);
|
||||
}
|
||||
|
||||
const runtimeContext = req.runtimeContext;
|
||||
const payload = await DBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ runtimeContext },
|
||||
);
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
if (options.customRoutes) {
|
||||
options.customRoutes(router, Service, DBApi);
|
||||
|
||||
@ -61,7 +61,10 @@ function createEntityService(DBApi, options = {}) {
|
||||
throw new ValidationError(`${entityName}NotFound`);
|
||||
}
|
||||
|
||||
const updated = await DBApi.update(id, data, { currentUser, transaction });
|
||||
const updated = await DBApi.update(id, data, {
|
||||
currentUser,
|
||||
transaction,
|
||||
});
|
||||
await transaction.commit();
|
||||
return updated;
|
||||
} catch (error) {
|
||||
|
||||
@ -20,10 +20,12 @@ module.exports = class Helpers {
|
||||
}
|
||||
|
||||
static jwtSign(data) {
|
||||
return jwt.sign(data, config.secret_key, {expiresIn: '6h'});
|
||||
return jwt.sign(data, config.secret_key, { expiresIn: '6h' });
|
||||
}
|
||||
|
||||
static isUuidV4(value) {
|
||||
return /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
|
||||
return /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(
|
||||
value,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@ -18,8 +18,6 @@ const sqlRoutes = require('./routes/sql');
|
||||
|
||||
const openaiRoutes = require('./routes/openai');
|
||||
|
||||
|
||||
|
||||
const usersRoutes = require('./routes/users');
|
||||
|
||||
const rolesRoutes = require('./routes/roles');
|
||||
@ -56,7 +54,6 @@ const {
|
||||
sanitizePublicRuntimeListResponse,
|
||||
} = require('./middlewares/runtime-public');
|
||||
|
||||
|
||||
const getBaseUrl = (url) => {
|
||||
if (!url) return '';
|
||||
return url.endsWith('/api') ? url.slice(0, -4) : url;
|
||||
@ -64,17 +61,18 @@ const getBaseUrl = (url) => {
|
||||
|
||||
const options = {
|
||||
definition: {
|
||||
openapi: "3.0.0",
|
||||
info: {
|
||||
version: "1.0.0",
|
||||
title: "Tour Builder Platform",
|
||||
description: "Tour Builder Platform Online REST API for Testing and Prototyping application. You can perform all major operations with your entities - create, delete and etc.",
|
||||
},
|
||||
openapi: '3.0.0',
|
||||
info: {
|
||||
version: '1.0.0',
|
||||
title: 'Tour Builder Platform',
|
||||
description:
|
||||
'Tour Builder Platform Online REST API for Testing and Prototyping application. You can perform all major operations with your entities - create, delete and etc.',
|
||||
},
|
||||
servers: [
|
||||
{
|
||||
url: getBaseUrl(process.env.NEXT_PUBLIC_BACK_API) || config.swaggerUrl,
|
||||
description: "Development server",
|
||||
}
|
||||
description: 'Development server',
|
||||
},
|
||||
],
|
||||
components: {
|
||||
securitySchemes: {
|
||||
@ -82,26 +80,34 @@ const options = {
|
||||
type: 'http',
|
||||
scheme: 'bearer',
|
||||
bearerFormat: 'JWT',
|
||||
}
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
UnauthorizedError: {
|
||||
description: "Access token is missing or invalid"
|
||||
}
|
||||
}
|
||||
description: 'Access token is missing or invalid',
|
||||
},
|
||||
},
|
||||
},
|
||||
security: [{
|
||||
bearerAuth: []
|
||||
}]
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
apis: ["./src/routes/*.js"],
|
||||
apis: ['./src/routes/*.js'],
|
||||
};
|
||||
|
||||
const specs = swaggerJsDoc(options);
|
||||
app.use('/api-docs', function (req, res, next) {
|
||||
swaggerUI.host = getBaseUrl(process.env.NEXT_PUBLIC_BACK_API) || req.get('host');
|
||||
next()
|
||||
}, swaggerUI.serve, swaggerUI.setup(specs))
|
||||
app.use(
|
||||
'/api-docs',
|
||||
function (req, res, next) {
|
||||
swaggerUI.host =
|
||||
getBaseUrl(process.env.NEXT_PUBLIC_BACK_API) || req.get('host');
|
||||
next();
|
||||
},
|
||||
swaggerUI.serve,
|
||||
swaggerUI.setup(specs),
|
||||
);
|
||||
|
||||
app.enable('trust proxy');
|
||||
app.use(
|
||||
@ -110,7 +116,7 @@ app.use(
|
||||
crossOriginEmbedderPolicy: false,
|
||||
}),
|
||||
);
|
||||
app.use(cors({origin: true}));
|
||||
app.use(cors({ origin: true }));
|
||||
require('./auth/auth');
|
||||
|
||||
// Request logger applied early so all routes are logged
|
||||
@ -171,7 +177,6 @@ app.get('/api/health', async (req, res) => {
|
||||
app.use('/api/auth', authRoutes);
|
||||
app.use('/api/runtime-context', runtimeContextRoutes);
|
||||
|
||||
|
||||
app.use('/api/users', jwtAuth, usersRoutes);
|
||||
|
||||
app.use('/api/roles', jwtAuth, rolesRoutes);
|
||||
@ -200,7 +205,11 @@ app.use('/api/presigned_url_requests', jwtAuth, presigned_url_requestsRoutes);
|
||||
|
||||
mountRuntimeEntityRoute('/api/tour_pages', 'tour_pages', tour_pagesRoutes);
|
||||
|
||||
mountRuntimeEntityRoute('/api/project_audio_tracks', 'project_audio_tracks', project_audio_tracksRoutes);
|
||||
mountRuntimeEntityRoute(
|
||||
'/api/project_audio_tracks',
|
||||
'project_audio_tracks',
|
||||
project_audio_tracksRoutes,
|
||||
);
|
||||
|
||||
app.use('/api/publish_events', jwtAuth, publish_eventsRoutes);
|
||||
|
||||
@ -210,43 +219,27 @@ app.use('/api/access_logs', jwtAuth, access_logsRoutes);
|
||||
app.use('/api/element-type-defaults', jwtAuth, element_type_defaultsRoutes);
|
||||
// Backwards compatibility alias for old API endpoint
|
||||
app.use('/api/ui-elements', jwtAuth, element_type_defaultsRoutes);
|
||||
app.use('/api/project-element-defaults', jwtAuth, project_element_defaultsRoutes);
|
||||
app.use(
|
||||
'/api/project-element-defaults',
|
||||
jwtAuth,
|
||||
project_element_defaultsRoutes,
|
||||
);
|
||||
|
||||
app.use('/api/publish', jwtAuth, publishRoutes);
|
||||
|
||||
app.use(
|
||||
'/api/openai',
|
||||
jwtAuth,
|
||||
openaiRoutes,
|
||||
);
|
||||
app.use(
|
||||
'/api/ai',
|
||||
jwtAuth,
|
||||
openaiRoutes,
|
||||
);
|
||||
app.use('/api/openai', jwtAuth, openaiRoutes);
|
||||
app.use('/api/ai', jwtAuth, openaiRoutes);
|
||||
|
||||
app.use(
|
||||
'/api/search',
|
||||
jwtAuth,
|
||||
searchRoutes);
|
||||
app.use(
|
||||
'/api/sql',
|
||||
jwtAuth,
|
||||
sqlRoutes);
|
||||
app.use('/api/search', jwtAuth, searchRoutes);
|
||||
app.use('/api/sql', jwtAuth, sqlRoutes);
|
||||
|
||||
|
||||
const publicDir = path.join(
|
||||
__dirname,
|
||||
'../public',
|
||||
);
|
||||
const publicDir = path.join(__dirname, '../public');
|
||||
|
||||
if (fs.existsSync(publicDir)) {
|
||||
app.use('/', express.static(publicDir));
|
||||
|
||||
app.get('*', function(request, response) {
|
||||
response.sendFile(
|
||||
path.resolve(publicDir, 'index.html'),
|
||||
);
|
||||
app.get('*', function (request, response) {
|
||||
response.sendFile(path.resolve(publicDir, 'index.html'));
|
||||
});
|
||||
}
|
||||
|
||||
@ -260,8 +253,11 @@ app.use((err, req, res, _next) => {
|
||||
|
||||
const PORT = process.env.NODE_ENV === 'dev_stage' ? 3000 : 8080;
|
||||
|
||||
app.listen(PORT, () => {
|
||||
logger.info({ port: PORT, env: process.env.NODE_ENV || 'development' }, 'Server started');
|
||||
});
|
||||
app.listen(PORT, () => {
|
||||
logger.info(
|
||||
{ port: PORT, env: process.env.NODE_ENV || 'development' },
|
||||
'Server started',
|
||||
);
|
||||
});
|
||||
|
||||
module.exports = app;
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
|
||||
const ValidationError = require('../services/notifications/errors/validation');
|
||||
const RolesDBApi = require('../db/api/roles');
|
||||
|
||||
@ -7,30 +6,38 @@ let publicRoleCache = null;
|
||||
|
||||
// Function to asynchronously fetch and cache the 'Public' role
|
||||
async function fetchAndCachePublicRole() {
|
||||
try {
|
||||
// Use RolesDBApi to find the role by name 'Public'
|
||||
publicRoleCache = await RolesDBApi.findBy({ name: 'Public' });
|
||||
try {
|
||||
// Use RolesDBApi to find the role by name 'Public'
|
||||
publicRoleCache = await RolesDBApi.findBy({ name: 'Public' });
|
||||
|
||||
if (!publicRoleCache) {
|
||||
console.error("WARNING: Role 'Public' not found in database during middleware startup. Check your migrations.");
|
||||
// The system might not function correctly without this role. May need to throw an error or use a fallback stub.
|
||||
} else {
|
||||
console.log("'Public' role successfully loaded and cached.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching 'Public' role during middleware startup:", error);
|
||||
// Handle the error during startup fetch
|
||||
throw error; // Important to know if the app can proceed without the Public role
|
||||
if (!publicRoleCache) {
|
||||
console.error(
|
||||
"WARNING: Role 'Public' not found in database during middleware startup. Check your migrations.",
|
||||
);
|
||||
// The system might not function correctly without this role. May need to throw an error or use a fallback stub.
|
||||
} else {
|
||||
console.log("'Public' role successfully loaded and cached.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"Error fetching 'Public' role during middleware startup:",
|
||||
error,
|
||||
);
|
||||
// Handle the error during startup fetch
|
||||
throw error; // Important to know if the app can proceed without the Public role
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger the role fetching when the check-permissions.js module is imported/loaded
|
||||
// This should happen during application startup when routes are being configured.
|
||||
fetchAndCachePublicRole().catch(error => {
|
||||
// Handle the case where the fetchAndCachePublicRole promise is rejected
|
||||
console.error("Critical error during permissions middleware initialization:", error);
|
||||
// Decide here if the process should exit if the Public role is essential.
|
||||
// process.exit(1);
|
||||
fetchAndCachePublicRole().catch((error) => {
|
||||
// Handle the case where the fetchAndCachePublicRole promise is rejected
|
||||
console.error(
|
||||
'Critical error during permissions middleware initialization:',
|
||||
error,
|
||||
);
|
||||
// Decide here if the process should exit if the Public role is essential.
|
||||
// process.exit(1);
|
||||
});
|
||||
|
||||
/**
|
||||
@ -39,85 +46,106 @@ fetchAndCachePublicRole().catch(error => {
|
||||
* @return {import("express").RequestHandler} Express middleware function.
|
||||
*/
|
||||
function checkPermissions(permission) {
|
||||
return async (req, res, next) => {
|
||||
const { currentUser } = req;
|
||||
return async (req, res, next) => {
|
||||
const { currentUser } = req;
|
||||
|
||||
// 1. Check self-access bypass (only if the user is authenticated)
|
||||
if (currentUser && (currentUser.id === req.params.id || currentUser.id === req.body.id)) {
|
||||
return next(); // User has access to their own resource
|
||||
}
|
||||
// 1. Check self-access bypass (only if the user is authenticated)
|
||||
if (
|
||||
currentUser &&
|
||||
(currentUser.id === req.params.id || currentUser.id === req.body.id)
|
||||
) {
|
||||
return next(); // User has access to their own resource
|
||||
}
|
||||
|
||||
// 2. Check Custom Permissions (only if the user is authenticated)
|
||||
if (currentUser) {
|
||||
// Ensure custom_permissions is an array before using find
|
||||
const customPermissions = Array.isArray(currentUser.custom_permissions)
|
||||
? currentUser.custom_permissions
|
||||
: [];
|
||||
const userPermission = customPermissions.find(
|
||||
(cp) => cp.name === permission,
|
||||
// 2. Check Custom Permissions (only if the user is authenticated)
|
||||
if (currentUser) {
|
||||
// Ensure custom_permissions is an array before using find
|
||||
const customPermissions = Array.isArray(currentUser.custom_permissions)
|
||||
? currentUser.custom_permissions
|
||||
: [];
|
||||
const userPermission = customPermissions.find(
|
||||
(cp) => cp.name === permission,
|
||||
);
|
||||
if (userPermission) {
|
||||
return next(); // User has a custom permission
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Determine the "effective" role for permission check
|
||||
let effectiveRole = null;
|
||||
try {
|
||||
if (currentUser && currentUser.app_role) {
|
||||
// User is authenticated and has an assigned role
|
||||
effectiveRole = currentUser.app_role;
|
||||
} else {
|
||||
// User is NOT authenticated OR is authenticated but has no role
|
||||
// Use the cached 'Public' role
|
||||
if (!publicRoleCache) {
|
||||
// If the cache is unexpectedly empty (e.g., startup error caught),
|
||||
// we can try fetching the role again synchronously (less ideal) or just deny access.
|
||||
console.error(
|
||||
'Public role cache is empty. Attempting synchronous fetch...',
|
||||
);
|
||||
// Less efficient fallback option:
|
||||
effectiveRole = await RolesDBApi.findBy({ name: 'Public' }); // Could be slow
|
||||
if (!effectiveRole) {
|
||||
// If even the synchronous attempt failed
|
||||
return next(
|
||||
new Error(
|
||||
'Internal Server Error: Public role missing and cannot be fetched.',
|
||||
),
|
||||
);
|
||||
if (userPermission) {
|
||||
return next(); // User has a custom permission
|
||||
}
|
||||
}
|
||||
} else {
|
||||
effectiveRole = publicRoleCache; // Use the cached object
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Determine the "effective" role for permission check
|
||||
let effectiveRole = null;
|
||||
try {
|
||||
if (currentUser && currentUser.app_role) {
|
||||
// User is authenticated and has an assigned role
|
||||
effectiveRole = currentUser.app_role;
|
||||
} else {
|
||||
// User is NOT authenticated OR is authenticated but has no role
|
||||
// Use the cached 'Public' role
|
||||
if (!publicRoleCache) {
|
||||
// If the cache is unexpectedly empty (e.g., startup error caught),
|
||||
// we can try fetching the role again synchronously (less ideal) or just deny access.
|
||||
console.error("Public role cache is empty. Attempting synchronous fetch...");
|
||||
// Less efficient fallback option:
|
||||
effectiveRole = await RolesDBApi.findBy({ name: 'Public' }); // Could be slow
|
||||
if (!effectiveRole) {
|
||||
// If even the synchronous attempt failed
|
||||
return next(new Error("Internal Server Error: Public role missing and cannot be fetched."));
|
||||
}
|
||||
} else {
|
||||
effectiveRole = publicRoleCache; // Use the cached object
|
||||
}
|
||||
}
|
||||
// Check if we got a valid role object
|
||||
if (!effectiveRole) {
|
||||
return next(
|
||||
new Error(
|
||||
'Internal Server Error: Could not determine effective role.',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Check if we got a valid role object
|
||||
if (!effectiveRole) {
|
||||
return next(new Error("Internal Server Error: Could not determine effective role."));
|
||||
}
|
||||
// 4. Check Permissions on the "effective" role
|
||||
// Assume the effectiveRole object (from app_role or RolesDBApi) has a getPermissions() method
|
||||
// or a 'permissions' property (if permissions are eagerly loaded).
|
||||
let rolePermissions = [];
|
||||
if (typeof effectiveRole.getPermissions === 'function') {
|
||||
rolePermissions = await effectiveRole.getPermissions(); // Get permissions asynchronously if the method exists
|
||||
} else if (Array.isArray(effectiveRole.permissions)) {
|
||||
rolePermissions = effectiveRole.permissions; // Or take from property if permissions are pre-loaded
|
||||
} else {
|
||||
console.error(
|
||||
'Role object lacks getPermissions() method or permissions property:',
|
||||
effectiveRole,
|
||||
);
|
||||
return next(
|
||||
new Error('Internal Server Error: Invalid role object format.'),
|
||||
);
|
||||
}
|
||||
|
||||
// 4. Check Permissions on the "effective" role
|
||||
// Assume the effectiveRole object (from app_role or RolesDBApi) has a getPermissions() method
|
||||
// or a 'permissions' property (if permissions are eagerly loaded).
|
||||
let rolePermissions = [];
|
||||
if (typeof effectiveRole.getPermissions === 'function') {
|
||||
rolePermissions = await effectiveRole.getPermissions(); // Get permissions asynchronously if the method exists
|
||||
} else if (Array.isArray(effectiveRole.permissions)) {
|
||||
rolePermissions = effectiveRole.permissions; // Or take from property if permissions are pre-loaded
|
||||
} else {
|
||||
console.error("Role object lacks getPermissions() method or permissions property:", effectiveRole);
|
||||
return next(new Error("Internal Server Error: Invalid role object format."));
|
||||
}
|
||||
|
||||
|
||||
if (rolePermissions.find((p) => p.name === permission)) {
|
||||
next(); // The "effective" role has the required permission
|
||||
} else {
|
||||
// The "effective" role does not have the required permission
|
||||
const roleName = effectiveRole.name || 'unknown role';
|
||||
next(new ValidationError('auth.forbidden', `Role '${roleName}' denied access to '${permission}'.`));
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
// Handle errors during role or permission fetching
|
||||
console.error("Error during permission check:", e);
|
||||
next(e); // Pass the error to the next middleware
|
||||
}
|
||||
};
|
||||
if (rolePermissions.find((p) => p.name === permission)) {
|
||||
next(); // The "effective" role has the required permission
|
||||
} else {
|
||||
// The "effective" role does not have the required permission
|
||||
const roleName = effectiveRole.name || 'unknown role';
|
||||
next(
|
||||
new ValidationError(
|
||||
'auth.forbidden',
|
||||
`Role '${roleName}' denied access to '${permission}'.`,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
// Handle errors during role or permission fetching
|
||||
console.error('Error during permission check:', e);
|
||||
next(e); // Pass the error to the next middleware
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const METHOD_MAP = {
|
||||
@ -143,23 +171,24 @@ const RUNTIME_PUBLIC_READ_ENTITIES = new Set([
|
||||
* @return {import("express").RequestHandler} Express middleware function.
|
||||
*/
|
||||
function checkCrudPermissions(name) {
|
||||
return (req, res, next) => {
|
||||
const isRuntimePublicRead = req.isRuntimePublicRequest === true
|
||||
&& req.method === 'GET'
|
||||
&& RUNTIME_PUBLIC_READ_ENTITIES.has(name.toUpperCase());
|
||||
return (req, res, next) => {
|
||||
const isRuntimePublicRead =
|
||||
req.isRuntimePublicRequest === true &&
|
||||
req.method === 'GET' &&
|
||||
RUNTIME_PUBLIC_READ_ENTITIES.has(name.toUpperCase());
|
||||
|
||||
if (isRuntimePublicRead) {
|
||||
return next();
|
||||
}
|
||||
if (isRuntimePublicRead) {
|
||||
return next();
|
||||
}
|
||||
|
||||
// Dynamically determine the permission name (e.g., 'READ_USERS')
|
||||
const permissionName = `${METHOD_MAP[req.method]}_${name.toUpperCase()}`;
|
||||
// Call the checkPermissions middleware with the determined permission
|
||||
return checkPermissions(permissionName)(req, res, next);
|
||||
};
|
||||
// Dynamically determine the permission name (e.g., 'READ_USERS')
|
||||
const permissionName = `${METHOD_MAP[req.method]}_${name.toUpperCase()}`;
|
||||
// Call the checkPermissions middleware with the determined permission
|
||||
return checkPermissions(permissionName)(req, res, next);
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
checkPermissions,
|
||||
checkCrudPermissions,
|
||||
checkPermissions,
|
||||
checkCrudPermissions,
|
||||
};
|
||||
|
||||
@ -12,7 +12,10 @@ function runtimeContextMiddleware(req, res, next) {
|
||||
|
||||
// Read environment from header (X-Runtime-Environment)
|
||||
const headerEnvironment = req.headers['x-runtime-environment'];
|
||||
if (headerEnvironment && ['production', 'stage', 'dev'].includes(headerEnvironment)) {
|
||||
if (
|
||||
headerEnvironment &&
|
||||
['production', 'stage', 'dev'].includes(headerEnvironment)
|
||||
) {
|
||||
context.headerEnvironment = headerEnvironment;
|
||||
}
|
||||
|
||||
|
||||
@ -49,7 +49,8 @@ const pickFields = (record, fields) => {
|
||||
}
|
||||
|
||||
// Convert Sequelize instance to plain object if needed
|
||||
const plainRecord = typeof record.get === 'function' ? record.get({ plain: true }) : record;
|
||||
const plainRecord =
|
||||
typeof record.get === 'function' ? record.get({ plain: true }) : record;
|
||||
|
||||
return fields.reduce((acc, field) => {
|
||||
if (field in plainRecord && plainRecord[field] !== undefined) {
|
||||
@ -83,7 +84,11 @@ const sanitizePublicRuntimeListResponse = (entityName) => {
|
||||
const fields = PUBLIC_RUNTIME_ENTITY_FIELDS[entityName] || [];
|
||||
|
||||
return (req, res, next) => {
|
||||
if (!isPublicRuntimeReadRequest(req) || req.path !== PUBLIC_RUNTIME_ALLOWED_PATH || fields.length === 0) {
|
||||
if (
|
||||
!isPublicRuntimeReadRequest(req) ||
|
||||
req.path !== PUBLIC_RUNTIME_ALLOWED_PATH ||
|
||||
fields.length === 0
|
||||
) {
|
||||
return next();
|
||||
}
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ const Multer = require('multer');
|
||||
|
||||
let processFile = Multer({
|
||||
storage: Multer.memoryStorage(),
|
||||
}).single("file");
|
||||
}).single('file');
|
||||
|
||||
let processFileMiddleware = util.promisify(processFile);
|
||||
module.exports = processFileMiddleware;
|
||||
|
||||
@ -5,7 +5,7 @@ const handleValidationErrors = (req, res, next) => {
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
error: 'Validation failed',
|
||||
details: errors.array().map(err => ({
|
||||
details: errors.array().map((err) => ({
|
||||
field: err.path,
|
||||
message: err.msg,
|
||||
value: err.value,
|
||||
@ -24,48 +24,64 @@ const validators = {
|
||||
requiredString: (field, min = 1, max = 255) =>
|
||||
body(field)
|
||||
.trim()
|
||||
.notEmpty().withMessage(`${field} is required`)
|
||||
.isLength({ min, max }).withMessage(`${field} must be ${min}-${max} characters`),
|
||||
.notEmpty()
|
||||
.withMessage(`${field} is required`)
|
||||
.isLength({ min, max })
|
||||
.withMessage(`${field} must be ${min}-${max} characters`),
|
||||
|
||||
optionalString: (field, max = 255) =>
|
||||
body(field)
|
||||
.optional()
|
||||
.trim()
|
||||
.isLength({ max }).withMessage(`${field} must be at most ${max} characters`),
|
||||
.isLength({ max })
|
||||
.withMessage(`${field} must be at most ${max} characters`),
|
||||
|
||||
slug: (field) =>
|
||||
body(field)
|
||||
.optional()
|
||||
.trim()
|
||||
.matches(/^[a-z0-9_-]+$/i).withMessage(`${field} can only contain letters, numbers, dashes, underscores`),
|
||||
.matches(/^[a-z0-9_-]+$/i)
|
||||
.withMessage(
|
||||
`${field} can only contain letters, numbers, dashes, underscores`,
|
||||
),
|
||||
|
||||
email: (field) =>
|
||||
body(field)
|
||||
.trim()
|
||||
.isEmail().withMessage('Must be a valid email')
|
||||
.isEmail()
|
||||
.withMessage('Must be a valid email')
|
||||
.normalizeEmail(),
|
||||
|
||||
optionalEmail: (field) =>
|
||||
body(field)
|
||||
.optional()
|
||||
.trim()
|
||||
.isEmail().withMessage('Must be a valid email')
|
||||
.isEmail()
|
||||
.withMessage('Must be a valid email')
|
||||
.normalizeEmail(),
|
||||
|
||||
enum: (field, values) =>
|
||||
body(field)
|
||||
.optional()
|
||||
.isIn(values).withMessage(`${field} must be one of: ${values.join(', ')}`),
|
||||
.isIn(values)
|
||||
.withMessage(`${field} must be one of: ${values.join(', ')}`),
|
||||
|
||||
boolean: (field) =>
|
||||
body(field)
|
||||
.optional()
|
||||
.isBoolean().withMessage(`${field} must be a boolean`),
|
||||
.isBoolean()
|
||||
.withMessage(`${field} must be a boolean`),
|
||||
|
||||
integer: (field, min, max) => {
|
||||
let validator = body(field).optional().isInt();
|
||||
if (min !== undefined) validator = validator.custom(val => val >= min).withMessage(`${field} must be at least ${min}`);
|
||||
if (max !== undefined) validator = validator.custom(val => val <= max).withMessage(`${field} must be at most ${max}`);
|
||||
if (min !== undefined)
|
||||
validator = validator
|
||||
.custom((val) => val >= min)
|
||||
.withMessage(`${field} must be at least ${min}`);
|
||||
if (max !== undefined)
|
||||
validator = validator
|
||||
.custom((val) => val <= max)
|
||||
.withMessage(`${field} must be at most ${max}`);
|
||||
return validator;
|
||||
},
|
||||
|
||||
@ -78,15 +94,20 @@ const validators = {
|
||||
body(field)
|
||||
.optional()
|
||||
.trim()
|
||||
.isURL().withMessage(`${field} must be a valid URL`),
|
||||
.isURL()
|
||||
.withMessage(`${field} must be a valid URL`),
|
||||
};
|
||||
|
||||
function createEntityValidation(entityConfig = {}) {
|
||||
const { fields = [], requiredFields = [] } = entityConfig;
|
||||
|
||||
const fieldValidators = fields.map(field => {
|
||||
const fieldValidators = fields.map((field) => {
|
||||
if (requiredFields.includes(field.name)) {
|
||||
return validators.requiredString(`data.${field.name}`, field.min || 1, field.max || 255);
|
||||
return validators.requiredString(
|
||||
`data.${field.name}`,
|
||||
field.min || 1,
|
||||
field.max || 255,
|
||||
);
|
||||
}
|
||||
return validators.optionalString(`data.${field.name}`, field.max || 255);
|
||||
});
|
||||
|
||||
@ -138,4 +138,8 @@ const { createEntityRouter } = require('../factories/router.factory');
|
||||
* description: Some server error
|
||||
*/
|
||||
|
||||
module.exports = createEntityRouter('access_logs', Access_logsService, Access_logsDBApi);
|
||||
module.exports = createEntityRouter(
|
||||
'access_logs',
|
||||
Access_logsService,
|
||||
Access_logsDBApi,
|
||||
);
|
||||
|
||||
@ -140,4 +140,8 @@ const { createEntityRouter } = require('../factories/router.factory');
|
||||
* description: Some server error
|
||||
*/
|
||||
|
||||
module.exports = createEntityRouter('asset_variants', Asset_variantsService, Asset_variantsDBApi);
|
||||
module.exports = createEntityRouter(
|
||||
'asset_variants',
|
||||
Asset_variantsService,
|
||||
Asset_variantsDBApi,
|
||||
);
|
||||
|
||||
@ -10,30 +10,28 @@ const wrapAsync = require('../helpers').wrapAsync;
|
||||
const router = express.Router();
|
||||
const authRateLimitStore = new Map();
|
||||
|
||||
const createMemoryRateLimiter = ({
|
||||
keyPrefix,
|
||||
maxRequests,
|
||||
windowMs,
|
||||
}) => (req, res, next) => {
|
||||
const key = `${keyPrefix}:${req.ip || 'unknown'}`;
|
||||
const now = Date.now();
|
||||
const current = authRateLimitStore.get(key);
|
||||
const createMemoryRateLimiter =
|
||||
({ keyPrefix, maxRequests, windowMs }) =>
|
||||
(req, res, next) => {
|
||||
const key = `${keyPrefix}:${req.ip || 'unknown'}`;
|
||||
const now = Date.now();
|
||||
const current = authRateLimitStore.get(key);
|
||||
|
||||
if (!current || current.expiresAt <= now) {
|
||||
authRateLimitStore.set(key, { count: 1, expiresAt: now + windowMs });
|
||||
if (!current || current.expiresAt <= now) {
|
||||
authRateLimitStore.set(key, { count: 1, expiresAt: now + windowMs });
|
||||
return next();
|
||||
}
|
||||
|
||||
if (current.count >= maxRequests) {
|
||||
return res.status(429).send({
|
||||
message: 'Too many requests. Please try again later.',
|
||||
});
|
||||
}
|
||||
|
||||
current.count += 1;
|
||||
authRateLimitStore.set(key, current);
|
||||
return next();
|
||||
}
|
||||
|
||||
if (current.count >= maxRequests) {
|
||||
return res.status(429).send({
|
||||
message: 'Too many requests. Please try again later.',
|
||||
});
|
||||
}
|
||||
|
||||
current.count += 1;
|
||||
authRateLimitStore.set(key, current);
|
||||
return next();
|
||||
};
|
||||
};
|
||||
|
||||
const signinLimiter = createMemoryRateLimiter({
|
||||
keyPrefix: 'signin',
|
||||
@ -71,7 +69,9 @@ function safeParseUrl(value) {
|
||||
|
||||
function getRequestHost(req) {
|
||||
const uiUrl = safeParseUrl(config.uiUrl);
|
||||
const fallbackHost = uiUrl ? uiUrl.origin : config.backUrl || 'http://localhost:3000';
|
||||
const fallbackHost = uiUrl
|
||||
? uiUrl.origin
|
||||
: config.backUrl || 'http://localhost:3000';
|
||||
const origin = safeParseUrl(req.headers.origin);
|
||||
const referer = safeParseUrl(req.headers.referer);
|
||||
|
||||
@ -142,10 +142,18 @@ function getRequestHost(req) {
|
||||
* x-codegen-request-body-name: body
|
||||
*/
|
||||
|
||||
router.post('/signin/local', signinLimiter, wrapAsync(async (req, res) => {
|
||||
const payload = await AuthService.signin(req.body.email, req.body.password, req,);
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
router.post(
|
||||
'/signin/local',
|
||||
signinLimiter,
|
||||
wrapAsync(async (req, res) => {
|
||||
const payload = await AuthService.signin(
|
||||
req.body.email,
|
||||
req.body.password,
|
||||
req,
|
||||
);
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
@ -164,42 +172,69 @@ router.post('/signin/local', signinLimiter, wrapAsync(async (req, res) => {
|
||||
* x-codegen-request-body-name: body
|
||||
*/
|
||||
|
||||
router.get('/me', passport.authenticate('jwt', {session: false}), (req, res) => {
|
||||
if (!req.currentUser || !req.currentUser.id) {
|
||||
throw new ForbiddenError();
|
||||
}
|
||||
router.get(
|
||||
'/me',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
(req, res) => {
|
||||
if (!req.currentUser || !req.currentUser.id) {
|
||||
throw new ForbiddenError();
|
||||
}
|
||||
|
||||
const payload = req.currentUser;
|
||||
delete payload.password;
|
||||
res.status(200).send(payload);
|
||||
});
|
||||
const payload = req.currentUser;
|
||||
delete payload.password;
|
||||
res.status(200).send(payload);
|
||||
},
|
||||
);
|
||||
|
||||
router.put('/password-reset', wrapAsync(async (req, res) => {
|
||||
const payload = await AuthService.passwordReset(req.body.token, req.body.password, req,);
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
router.put(
|
||||
'/password-reset',
|
||||
wrapAsync(async (req, res) => {
|
||||
const payload = await AuthService.passwordReset(
|
||||
req.body.token,
|
||||
req.body.password,
|
||||
req,
|
||||
);
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
router.put('/password-update', passport.authenticate('jwt', {session: false}), wrapAsync(async (req, res) => {
|
||||
const payload = await AuthService.passwordUpdate(req.body.currentPassword, req.body.newPassword, req);
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
router.put(
|
||||
'/password-update',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
wrapAsync(async (req, res) => {
|
||||
const payload = await AuthService.passwordUpdate(
|
||||
req.body.currentPassword,
|
||||
req.body.newPassword,
|
||||
req,
|
||||
);
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
router.post('/send-email-address-verification-email', passport.authenticate('jwt', {session: false}), wrapAsync(async (req, res) => {
|
||||
if (!req.currentUser) {
|
||||
throw new ForbiddenError();
|
||||
}
|
||||
router.post(
|
||||
'/send-email-address-verification-email',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
wrapAsync(async (req, res) => {
|
||||
if (!req.currentUser) {
|
||||
throw new ForbiddenError();
|
||||
}
|
||||
|
||||
await AuthService.sendEmailAddressVerificationEmail(req.currentUser.email);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
await AuthService.sendEmailAddressVerificationEmail(req.currentUser.email);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
router.post('/send-password-reset-email', passwordResetLimiter, wrapAsync(async (req, res) => {
|
||||
const host = getRequestHost(req);
|
||||
await AuthService.sendPasswordResetEmail(req.body.email, 'register', host);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
router.post(
|
||||
'/send-password-reset-email',
|
||||
passwordResetLimiter,
|
||||
wrapAsync(async (req, res) => {
|
||||
const host = getRequestHost(req);
|
||||
await AuthService.sendPasswordResetEmail(req.body.email, 'register', host);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
@ -224,32 +259,47 @@ router.post('/send-password-reset-email', passwordResetLimiter, wrapAsync(async
|
||||
* x-codegen-request-body-name: body
|
||||
*/
|
||||
|
||||
router.post('/signup', signupLimiter, wrapAsync(async (req, res) => {
|
||||
const host = getRequestHost(req);
|
||||
const payload = await AuthService.signup(
|
||||
router.post(
|
||||
'/signup',
|
||||
signupLimiter,
|
||||
wrapAsync(async (req, res) => {
|
||||
const host = getRequestHost(req);
|
||||
const payload = await AuthService.signup(
|
||||
req.body.email,
|
||||
req.body.password,
|
||||
|
||||
|
||||
req,
|
||||
host,
|
||||
)
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
);
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
router.put('/profile', passport.authenticate('jwt', {session: false}), wrapAsync(async (req, res) => {
|
||||
if (!req.currentUser || !req.currentUser.id) {
|
||||
throw new ForbiddenError();
|
||||
}
|
||||
router.put(
|
||||
'/profile',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
wrapAsync(async (req, res) => {
|
||||
if (!req.currentUser || !req.currentUser.id) {
|
||||
throw new ForbiddenError();
|
||||
}
|
||||
|
||||
await AuthService.updateProfile(req.body.profile, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
await AuthService.updateProfile(req.body.profile, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
router.put('/verify-email', wrapAsync(async (req, res) => {
|
||||
const payload = await AuthService.verifyEmail(req.body.token, req, req.headers.referer)
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
router.put(
|
||||
'/verify-email',
|
||||
wrapAsync(async (req, res) => {
|
||||
const payload = await AuthService.verifyEmail(
|
||||
req.body.token,
|
||||
req,
|
||||
req.headers.referer,
|
||||
);
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
router.get('/email-configured', (req, res) => {
|
||||
const payload = EmailSender.isConfigured;
|
||||
@ -257,36 +307,46 @@ router.get('/email-configured', (req, res) => {
|
||||
});
|
||||
|
||||
router.get('/signin/google', (req, res, next) => {
|
||||
passport.authenticate("google", {scope: ["profile", "email"], state: req.query.app})(req, res, next);
|
||||
});
|
||||
|
||||
router.get('/signin/google/callback', passport.authenticate("google", {failureRedirect: "/login", session: false}),
|
||||
|
||||
function (req, res) {
|
||||
socialRedirect(res, req.query.state, req.user.token, config);
|
||||
}
|
||||
);
|
||||
|
||||
router.get('/signin/microsoft', (req, res, next) => {
|
||||
passport.authenticate("microsoft", {
|
||||
scope: ["https://graph.microsoft.com/user.read openid"],
|
||||
state: req.query.app
|
||||
passport.authenticate('google', {
|
||||
scope: ['profile', 'email'],
|
||||
state: req.query.app,
|
||||
})(req, res, next);
|
||||
});
|
||||
|
||||
router.get('/signin/microsoft/callback', passport.authenticate("microsoft", {
|
||||
failureRedirect: "/login",
|
||||
session: false
|
||||
router.get(
|
||||
'/signin/google/callback',
|
||||
passport.authenticate('google', {
|
||||
failureRedirect: '/login',
|
||||
session: false,
|
||||
}),
|
||||
|
||||
function (req, res) {
|
||||
socialRedirect(res, req.query.state, req.user.token, config);
|
||||
},
|
||||
);
|
||||
|
||||
router.get('/signin/microsoft', (req, res, next) => {
|
||||
passport.authenticate('microsoft', {
|
||||
scope: ['https://graph.microsoft.com/user.read openid'],
|
||||
state: req.query.app,
|
||||
})(req, res, next);
|
||||
});
|
||||
|
||||
router.get(
|
||||
'/signin/microsoft/callback',
|
||||
passport.authenticate('microsoft', {
|
||||
failureRedirect: '/login',
|
||||
session: false,
|
||||
}),
|
||||
function (req, res) {
|
||||
socialRedirect(res, req.query.state, req.user.token, config);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
router.use('/', require('../helpers').commonErrorHandler);
|
||||
|
||||
function socialRedirect(res, state, token, config) {
|
||||
res.redirect(config.uiUrl + "/login?token=" + token);
|
||||
res.redirect(config.uiUrl + '/login?token=' + token);
|
||||
}
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@ -2,6 +2,11 @@ const Element_type_defaultsService = require('../services/element_type_defaults'
|
||||
const Element_type_defaultsDBApi = require('../db/api/element_type_defaults');
|
||||
const { createEntityRouter } = require('../factories/router.factory');
|
||||
|
||||
module.exports = createEntityRouter('element_type_defaults', Element_type_defaultsService, Element_type_defaultsDBApi, {
|
||||
permissionEntity: 'page_elements',
|
||||
});
|
||||
module.exports = createEntityRouter(
|
||||
'element_type_defaults',
|
||||
Element_type_defaultsService,
|
||||
Element_type_defaultsDBApi,
|
||||
{
|
||||
permissionEntity: 'page_elements',
|
||||
},
|
||||
);
|
||||
|
||||
@ -31,9 +31,13 @@ router.post('/presign', jsonParser, async (req, res) => {
|
||||
}
|
||||
|
||||
// Validate that all URLs are strings
|
||||
const invalidUrls = urls.filter((url) => typeof url !== 'string' || !url.trim());
|
||||
const invalidUrls = urls.filter(
|
||||
(url) => typeof url !== 'string' || !url.trim(),
|
||||
);
|
||||
if (invalidUrls.length > 0) {
|
||||
return res.status(400).json({ error: 'All URLs must be non-empty strings' });
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: 'All URLs must be non-empty strings' });
|
||||
}
|
||||
|
||||
try {
|
||||
@ -45,11 +49,15 @@ router.post('/presign', jsonParser, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/upload/:table/:field', passport.authenticate('jwt', {session: false}), (req, res) => {
|
||||
const fileName = `${req.params.table}/${req.params.field}`;
|
||||
router.post(
|
||||
'/upload/:table/:field',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
(req, res) => {
|
||||
const fileName = `${req.params.table}/${req.params.field}`;
|
||||
|
||||
services.uploadFile(fileName, req, res);
|
||||
});
|
||||
services.uploadFile(fileName, req, res);
|
||||
},
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/upload-sessions/init',
|
||||
|
||||
@ -7,17 +7,22 @@ const { getWidget, askGpt } = require('../services/openai');
|
||||
const { LocalAIApi } = require('../ai/LocalAIApi');
|
||||
|
||||
const loadRolesModules = () => {
|
||||
try {
|
||||
return {
|
||||
RolesService: require('../services/roles'),
|
||||
RolesDBApi: require('../db/api/roles'),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Roles modules are missing. Advanced roles are required for this endpoint.', error);
|
||||
const err = new Error('Roles modules are missing. Advanced roles are required for this endpoint.');
|
||||
err.originalError = error;
|
||||
throw err;
|
||||
}
|
||||
try {
|
||||
return {
|
||||
RolesService: require('../services/roles'),
|
||||
RolesDBApi: require('../db/api/roles'),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'Roles modules are missing. Advanced roles are required for this endpoint.',
|
||||
error,
|
||||
);
|
||||
const err = new Error(
|
||||
'Roles modules are missing. Advanced roles are required for this endpoint.',
|
||||
);
|
||||
err.originalError = error;
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -70,18 +75,18 @@ const loadRolesModules = () => {
|
||||
*/
|
||||
|
||||
router.delete(
|
||||
'/roles-info/:infoId',
|
||||
wrapAsync(async (req, res) => {
|
||||
const { RolesService } = loadRolesModules();
|
||||
const role = await RolesService.removeRoleInfoById(
|
||||
req.query.infoId,
|
||||
req.query.roleId,
|
||||
req.query.key,
|
||||
req.currentUser,
|
||||
);
|
||||
'/roles-info/:infoId',
|
||||
wrapAsync(async (req, res) => {
|
||||
const { RolesService } = loadRolesModules();
|
||||
const role = await RolesService.removeRoleInfoById(
|
||||
req.query.infoId,
|
||||
req.query.roleId,
|
||||
req.query.key,
|
||||
req.currentUser,
|
||||
);
|
||||
|
||||
res.status(200).send(role);
|
||||
}),
|
||||
res.status(200).send(role);
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
@ -128,83 +133,80 @@ router.delete(
|
||||
*/
|
||||
|
||||
router.get(
|
||||
'/info-by-key',
|
||||
wrapAsync(async (req, res) => {
|
||||
const { RolesService, RolesDBApi } = loadRolesModules();
|
||||
const roleId = req.query.roleId;
|
||||
const key = req.query.key;
|
||||
const currentUser = req.currentUser;
|
||||
let info = await RolesService.getRoleInfoByKey(
|
||||
key,
|
||||
roleId,
|
||||
currentUser,
|
||||
);
|
||||
const role = await RolesDBApi.findBy({ id: roleId });
|
||||
if (!role?.role_customization) {
|
||||
try {
|
||||
await Promise.all(["pie", "bar"].map(async (e) => {
|
||||
const schema = await sjs.getSequelizeSchema(db.sequelize, {});
|
||||
const payload = {
|
||||
description: `Create some cool ${e} chart`,
|
||||
modelDefinition: schema.definitions,
|
||||
};
|
||||
const widgetId = await getWidget(payload, currentUser?.id, roleId);
|
||||
if (widgetId) {
|
||||
await RolesService.addRoleInfo(
|
||||
roleId,
|
||||
currentUser?.id,
|
||||
'widgets',
|
||||
widgetId,
|
||||
req.currentUser,
|
||||
);
|
||||
}
|
||||
}));
|
||||
info = await RolesService.getRoleInfoByKey(
|
||||
key,
|
||||
roleId,
|
||||
currentUser,
|
||||
);
|
||||
} catch (error) {
|
||||
console.warn('Widget creation skipped (external API unavailable):', error.message);
|
||||
// Return empty widgets when external API is unavailable
|
||||
if (key === 'widgets') {
|
||||
info = [];
|
||||
}
|
||||
'/info-by-key',
|
||||
wrapAsync(async (req, res) => {
|
||||
const { RolesService, RolesDBApi } = loadRolesModules();
|
||||
const roleId = req.query.roleId;
|
||||
const key = req.query.key;
|
||||
const currentUser = req.currentUser;
|
||||
let info = await RolesService.getRoleInfoByKey(key, roleId, currentUser);
|
||||
const role = await RolesDBApi.findBy({ id: roleId });
|
||||
if (!role?.role_customization) {
|
||||
try {
|
||||
await Promise.all(
|
||||
['pie', 'bar'].map(async (e) => {
|
||||
const schema = await sjs.getSequelizeSchema(db.sequelize, {});
|
||||
const payload = {
|
||||
description: `Create some cool ${e} chart`,
|
||||
modelDefinition: schema.definitions,
|
||||
};
|
||||
const widgetId = await getWidget(payload, currentUser?.id, roleId);
|
||||
if (widgetId) {
|
||||
await RolesService.addRoleInfo(
|
||||
roleId,
|
||||
currentUser?.id,
|
||||
'widgets',
|
||||
widgetId,
|
||||
req.currentUser,
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
info = await RolesService.getRoleInfoByKey(key, roleId, currentUser);
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
'Widget creation skipped (external API unavailable):',
|
||||
error.message,
|
||||
);
|
||||
// Return empty widgets when external API is unavailable
|
||||
if (key === 'widgets') {
|
||||
info = [];
|
||||
}
|
||||
res.status(200).send(info);
|
||||
}),
|
||||
}
|
||||
}
|
||||
res.status(200).send(info);
|
||||
}),
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/create_widget',
|
||||
wrapAsync(async (req, res) => {
|
||||
const { RolesService } = loadRolesModules();
|
||||
const { description, userId, roleId } = req.body;
|
||||
'/create_widget',
|
||||
wrapAsync(async (req, res) => {
|
||||
const { RolesService } = loadRolesModules();
|
||||
const { description, userId, roleId } = req.body;
|
||||
|
||||
const currentUser = req.currentUser;
|
||||
const schema = await sjs.getSequelizeSchema(db.sequelize, {});
|
||||
const payload = {
|
||||
description,
|
||||
modelDefinition: schema.definitions,
|
||||
};
|
||||
const currentUser = req.currentUser;
|
||||
const schema = await sjs.getSequelizeSchema(db.sequelize, {});
|
||||
const payload = {
|
||||
description,
|
||||
modelDefinition: schema.definitions,
|
||||
};
|
||||
|
||||
const widgetId = await getWidget(payload, userId, roleId);
|
||||
const widgetId = await getWidget(payload, userId, roleId);
|
||||
|
||||
if (widgetId) {
|
||||
await RolesService.addRoleInfo(
|
||||
roleId,
|
||||
userId,
|
||||
'widgets',
|
||||
widgetId,
|
||||
currentUser,
|
||||
);
|
||||
if (widgetId) {
|
||||
await RolesService.addRoleInfo(
|
||||
roleId,
|
||||
userId,
|
||||
'widgets',
|
||||
widgetId,
|
||||
currentUser,
|
||||
);
|
||||
|
||||
return res.status(200).send(widgetId);
|
||||
} else {
|
||||
return res.status(400).send(widgetId);
|
||||
}
|
||||
}),
|
||||
return res.status(200).send(widgetId);
|
||||
} else {
|
||||
return res.status(400).send(widgetId);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
@ -252,23 +254,23 @@ router.post(
|
||||
* description: Proxy error
|
||||
*/
|
||||
router.post(
|
||||
'/response',
|
||||
wrapAsync(async (req, res) => {
|
||||
const body = req.body || {};
|
||||
const options = body.options || {};
|
||||
const payload = { ...body };
|
||||
delete payload.options;
|
||||
'/response',
|
||||
wrapAsync(async (req, res) => {
|
||||
const body = req.body || {};
|
||||
const options = body.options || {};
|
||||
const payload = { ...body };
|
||||
delete payload.options;
|
||||
|
||||
const response = await LocalAIApi.createResponse(payload, options);
|
||||
const response = await LocalAIApi.createResponse(payload, options);
|
||||
|
||||
if (response.success) {
|
||||
return res.status(200).send(response);
|
||||
}
|
||||
if (response.success) {
|
||||
return res.status(200).send(response);
|
||||
}
|
||||
|
||||
console.error('AI proxy error:', response);
|
||||
const status = response.error === 'input_missing' ? 400 : 502;
|
||||
return res.status(status).send(response);
|
||||
}),
|
||||
console.error('AI proxy error:', response);
|
||||
const status = response.error === 'input_missing' ? 400 : 502;
|
||||
return res.status(status).send(response);
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
@ -312,25 +314,24 @@ router.post(
|
||||
* description: Some server error
|
||||
*/
|
||||
router.post(
|
||||
'/ask-gpt',
|
||||
wrapAsync(async (req, res) => {
|
||||
const { prompt } = req.body;
|
||||
if (!prompt) {
|
||||
return res.status(400).send({
|
||||
success: false,
|
||||
error: 'Prompt is required',
|
||||
});
|
||||
}
|
||||
'/ask-gpt',
|
||||
wrapAsync(async (req, res) => {
|
||||
const { prompt } = req.body;
|
||||
if (!prompt) {
|
||||
return res.status(400).send({
|
||||
success: false,
|
||||
error: 'Prompt is required',
|
||||
});
|
||||
}
|
||||
|
||||
const response = await askGpt(prompt);
|
||||
const response = await askGpt(prompt);
|
||||
|
||||
if (response.success) {
|
||||
return res.status(200).send(response);
|
||||
} else {
|
||||
return res.status(500).send(response);
|
||||
}
|
||||
}),
|
||||
if (response.success) {
|
||||
return res.status(200).send(response);
|
||||
} else {
|
||||
return res.status(500).send(response);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
|
||||
|
||||
@ -181,4 +181,8 @@ const { createEntityRouter } = require('../factories/router.factory');
|
||||
* description: Some server error
|
||||
*/
|
||||
|
||||
module.exports = createEntityRouter('permissions', PermissionsService, PermissionsDBApi);
|
||||
module.exports = createEntityRouter(
|
||||
'permissions',
|
||||
PermissionsService,
|
||||
PermissionsDBApi,
|
||||
);
|
||||
|
||||
@ -143,4 +143,8 @@ const { createEntityRouter } = require('../factories/router.factory');
|
||||
* description: Some server error
|
||||
*/
|
||||
|
||||
module.exports = createEntityRouter('presigned_url_requests', Presigned_url_requestsService, Presigned_url_requestsDBApi);
|
||||
module.exports = createEntityRouter(
|
||||
'presigned_url_requests',
|
||||
Presigned_url_requestsService,
|
||||
Presigned_url_requestsDBApi,
|
||||
);
|
||||
|
||||
@ -144,4 +144,8 @@ const { createEntityRouter } = require('../factories/router.factory');
|
||||
* description: Some server error
|
||||
*/
|
||||
|
||||
module.exports = createEntityRouter('project_audio_tracks', Project_audio_tracksService, Project_audio_tracksDBApi);
|
||||
module.exports = createEntityRouter(
|
||||
'project_audio_tracks',
|
||||
Project_audio_tracksService,
|
||||
Project_audio_tracksDBApi,
|
||||
);
|
||||
|
||||
@ -10,7 +10,7 @@ const baseRouter = createEntityRouter(
|
||||
Project_element_defaultsDBApi,
|
||||
{
|
||||
permissionEntity: 'page_elements',
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
@ -40,10 +40,10 @@ baseRouter.post(
|
||||
wrapAsync(async (req, res) => {
|
||||
const payload = await Project_element_defaultsService.resetToGlobal(
|
||||
req.params.id,
|
||||
{ currentUser: req.currentUser }
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
res.status(200).json(payload);
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
@ -72,10 +72,10 @@ baseRouter.get(
|
||||
'/:id/diff',
|
||||
wrapAsync(async (req, res) => {
|
||||
const payload = await Project_element_defaultsService.getDiffFromGlobal(
|
||||
req.params.id
|
||||
req.params.id,
|
||||
);
|
||||
res.status(200).json(payload);
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
module.exports = baseRouter;
|
||||
|
||||
@ -138,4 +138,8 @@ const { createEntityRouter } = require('../factories/router.factory');
|
||||
* description: Some server error
|
||||
*/
|
||||
|
||||
module.exports = createEntityRouter('project_memberships', Project_membershipsService, Project_membershipsDBApi);
|
||||
module.exports = createEntityRouter(
|
||||
'project_memberships',
|
||||
Project_membershipsService,
|
||||
Project_membershipsDBApi,
|
||||
);
|
||||
|
||||
@ -1,23 +1,17 @@
|
||||
|
||||
const express = require('express');
|
||||
|
||||
const ProjectsService = require('../services/projects');
|
||||
const ProjectsDBApi = require('../db/api/projects');
|
||||
const { wrapAsync, isUuidV4 } = require('../helpers');
|
||||
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
const { parse } = require('json2csv');
|
||||
|
||||
|
||||
const {
|
||||
checkCrudPermissions,
|
||||
} = require('../middlewares/check-permissions');
|
||||
const { checkCrudPermissions } = require('../middlewares/check-permissions');
|
||||
|
||||
router.use(checkCrudPermissions('projects'));
|
||||
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* components:
|
||||
@ -67,53 +61,69 @@ router.use(checkCrudPermissions('projects'));
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/projects:
|
||||
* post:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Projects]
|
||||
* summary: Add new item
|
||||
* description: Add new item
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* data:
|
||||
* description: Data of the updated item
|
||||
* type: object
|
||||
* $ref: "#/components/schemas/Projects"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The item was successfully added
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Projects"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 405:
|
||||
* description: Invalid input data
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.post('/', wrapAsync(async (req, res) => {
|
||||
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
|
||||
* @swagger
|
||||
* /api/projects:
|
||||
* post:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Projects]
|
||||
* summary: Add new item
|
||||
* description: Add new item
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* data:
|
||||
* description: Data of the updated item
|
||||
* type: object
|
||||
* $ref: "#/components/schemas/Projects"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The item was successfully added
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Projects"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 405:
|
||||
* description: Invalid input data
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.post(
|
||||
'/',
|
||||
wrapAsync(async (req, res) => {
|
||||
const referer =
|
||||
req.headers.referer ||
|
||||
`${req.protocol}://${req.hostname}${req.originalUrl}`;
|
||||
const link = new URL(referer);
|
||||
const payload = await ProjectsService.create(req.body.data, req.currentUser, true, link.host);
|
||||
const payload = await ProjectsService.create(
|
||||
req.body.data,
|
||||
req.currentUser,
|
||||
true,
|
||||
link.host,
|
||||
);
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
router.post('/:id/clone', wrapAsync(async (req, res) => {
|
||||
router.post(
|
||||
'/:id/clone',
|
||||
wrapAsync(async (req, res) => {
|
||||
if (!isUuidV4(req.params.id)) {
|
||||
return res.status(400).send('Invalid project id');
|
||||
}
|
||||
|
||||
const payload = await ProjectsService.cloneFromProject(req.params.id, req.currentUser);
|
||||
const payload = await ProjectsService.cloneFromProject(
|
||||
req.params.id,
|
||||
req.currentUser,
|
||||
);
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
@ -150,193 +160,220 @@ router.post('/:id/clone', wrapAsync(async (req, res) => {
|
||||
* description: Some server error
|
||||
*
|
||||
*/
|
||||
router.post('/bulk-import', wrapAsync(async (req, res) => {
|
||||
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
|
||||
router.post(
|
||||
'/bulk-import',
|
||||
wrapAsync(async (req, res) => {
|
||||
const referer =
|
||||
req.headers.referer ||
|
||||
`${req.protocol}://${req.hostname}${req.originalUrl}`;
|
||||
const link = new URL(referer);
|
||||
await ProjectsService.bulkImport(req, res, true, link.host);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/projects/{id}:
|
||||
* put:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Projects]
|
||||
* summary: Update the data of the selected item
|
||||
* description: Update the data of the selected item
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* description: Item ID to update
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* requestBody:
|
||||
* description: Set new item data
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* id:
|
||||
* description: ID of the updated item
|
||||
* type: string
|
||||
* data:
|
||||
* description: Data of the updated item
|
||||
* type: object
|
||||
* $ref: "#/components/schemas/Projects"
|
||||
* required:
|
||||
* - id
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The item data was successfully updated
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Projects"
|
||||
* 400:
|
||||
* description: Invalid ID supplied
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Item not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.put('/:id', wrapAsync(async (req, res) => {
|
||||
await ProjectsService.update(req.body.data, req.body.id, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
* @swagger
|
||||
* /api/projects/{id}:
|
||||
* put:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Projects]
|
||||
* summary: Update the data of the selected item
|
||||
* description: Update the data of the selected item
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* description: Item ID to update
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* requestBody:
|
||||
* description: Set new item data
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* id:
|
||||
* description: ID of the updated item
|
||||
* type: string
|
||||
* data:
|
||||
* description: Data of the updated item
|
||||
* type: object
|
||||
* $ref: "#/components/schemas/Projects"
|
||||
* required:
|
||||
* - id
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The item data was successfully updated
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Projects"
|
||||
* 400:
|
||||
* description: Invalid ID supplied
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Item not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.put(
|
||||
'/:id',
|
||||
wrapAsync(async (req, res) => {
|
||||
await ProjectsService.update(req.body.data, req.body.id, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/projects/{id}:
|
||||
* delete:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Projects]
|
||||
* summary: Delete the selected item
|
||||
* description: Delete the selected item
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* description: Item ID to delete
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The item was successfully deleted
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Projects"
|
||||
* 400:
|
||||
* description: Invalid ID supplied
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Item not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.delete('/:id', wrapAsync(async (req, res) => {
|
||||
await ProjectsService.remove(req.params.id, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
* @swagger
|
||||
* /api/projects/{id}:
|
||||
* delete:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Projects]
|
||||
* summary: Delete the selected item
|
||||
* description: Delete the selected item
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* description: Item ID to delete
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The item was successfully deleted
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Projects"
|
||||
* 400:
|
||||
* description: Invalid ID supplied
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Item not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.delete(
|
||||
'/:id',
|
||||
wrapAsync(async (req, res) => {
|
||||
await ProjectsService.remove(req.params.id, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/projects/deleteByIds:
|
||||
* post:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Projects]
|
||||
* summary: Delete the selected item list
|
||||
* description: Delete the selected item list
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* ids:
|
||||
* description: IDs of the updated items
|
||||
* type: array
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The items was successfully deleted
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Projects"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Items not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.post('/deleteByIds', wrapAsync(async (req, res) => {
|
||||
* @swagger
|
||||
* /api/projects/deleteByIds:
|
||||
* post:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Projects]
|
||||
* summary: Delete the selected item list
|
||||
* description: Delete the selected item list
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* ids:
|
||||
* description: IDs of the updated items
|
||||
* type: array
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The items was successfully deleted
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Projects"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Items not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.post(
|
||||
'/deleteByIds',
|
||||
wrapAsync(async (req, res) => {
|
||||
await ProjectsService.deleteByIds(req.body.data, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/projects:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Projects]
|
||||
* summary: Get all projects
|
||||
* description: Get all projects
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Projects list successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Projects"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Data not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.get('/', wrapAsync(async (req, res) => {
|
||||
const filetype = req.query.filetype
|
||||
|
||||
const currentUser = req.currentUser;
|
||||
const runtimeContext = req.runtimeContext;
|
||||
const payload = await ProjectsDBApi.findAll(
|
||||
req.query, { currentUser, runtimeContext }
|
||||
);
|
||||
if (filetype && filetype === 'csv') {
|
||||
const fields = ['id','name','slug','description','logo_url','favicon_url','og_image_url','theme_config_json','custom_css_json','cdn_base_url'];
|
||||
const opts = { fields };
|
||||
try {
|
||||
const csv = parse(payload.rows, opts);
|
||||
res.status(200).attachment(csv);
|
||||
res.send(csv)
|
||||
* @swagger
|
||||
* /api/projects:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Projects]
|
||||
* summary: Get all projects
|
||||
* description: Get all projects
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Projects list successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Projects"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Data not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.get(
|
||||
'/',
|
||||
wrapAsync(async (req, res) => {
|
||||
const filetype = req.query.filetype;
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
const currentUser = req.currentUser;
|
||||
const runtimeContext = req.runtimeContext;
|
||||
const payload = await ProjectsDBApi.findAll(req.query, {
|
||||
currentUser,
|
||||
runtimeContext,
|
||||
});
|
||||
if (filetype && filetype === 'csv') {
|
||||
const fields = [
|
||||
'id',
|
||||
'name',
|
||||
'slug',
|
||||
'description',
|
||||
'logo_url',
|
||||
'favicon_url',
|
||||
'og_image_url',
|
||||
'theme_config_json',
|
||||
'custom_css_json',
|
||||
'cdn_base_url',
|
||||
];
|
||||
const opts = { fields };
|
||||
try {
|
||||
const csv = parse(payload.rows, opts);
|
||||
res.status(200).attachment(csv);
|
||||
res.send(csv);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
} else {
|
||||
res.status(200).send(payload);
|
||||
}
|
||||
} else {
|
||||
res.status(200).send(payload);
|
||||
}
|
||||
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
@ -363,17 +400,20 @@ router.get('/', wrapAsync(async (req, res) => {
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.get('/count', wrapAsync(async (req, res) => {
|
||||
|
||||
router.get(
|
||||
'/count',
|
||||
wrapAsync(async (req, res) => {
|
||||
const currentUser = req.currentUser;
|
||||
const runtimeContext = req.runtimeContext;
|
||||
const payload = await ProjectsDBApi.findAll(
|
||||
req.query,
|
||||
{ countOnly: true, currentUser, runtimeContext }
|
||||
);
|
||||
const payload = await ProjectsDBApi.findAll(req.query, {
|
||||
countOnly: true,
|
||||
currentUser,
|
||||
runtimeContext,
|
||||
});
|
||||
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
@ -401,64 +441,63 @@ router.get('/count', wrapAsync(async (req, res) => {
|
||||
* description: Some server error
|
||||
*/
|
||||
router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const payload = await ProjectsDBApi.findAllAutocomplete(
|
||||
req.query.query,
|
||||
req.query.limit,
|
||||
req.query.offset,
|
||||
|
||||
);
|
||||
|
||||
res.status(200).send(payload);
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/projects/{id}:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Projects]
|
||||
* summary: Get selected item
|
||||
* description: Get selected item
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* description: ID of item to get
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Selected item successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Projects"
|
||||
* 400:
|
||||
* description: Invalid ID supplied
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Item not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
if (!isUuidV4(req.params.id)) {
|
||||
return res.status(400).send('Invalid project id');
|
||||
}
|
||||
* @swagger
|
||||
* /api/projects/{id}:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Projects]
|
||||
* summary: Get selected item
|
||||
* description: Get selected item
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* description: ID of item to get
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Selected item successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Projects"
|
||||
* 400:
|
||||
* description: Invalid ID supplied
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Item not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.get(
|
||||
'/:id',
|
||||
wrapAsync(async (req, res) => {
|
||||
if (!isUuidV4(req.params.id)) {
|
||||
return res.status(400).send('Invalid project id');
|
||||
}
|
||||
|
||||
const runtimeContext = req.runtimeContext;
|
||||
const payload = await ProjectsDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ runtimeContext },
|
||||
);
|
||||
const runtimeContext = req.runtimeContext;
|
||||
const payload = await ProjectsDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ runtimeContext },
|
||||
);
|
||||
|
||||
|
||||
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
@ -493,21 +532,24 @@ router.get('/:id', wrapAsync(async (req, res) => {
|
||||
* 500:
|
||||
* description: Server error
|
||||
*/
|
||||
router.get('/:id/offline-manifest', wrapAsync(async (req, res) => {
|
||||
if (!isUuidV4(req.params.id)) {
|
||||
return res.status(400).send('Invalid project id');
|
||||
}
|
||||
router.get(
|
||||
'/:id/offline-manifest',
|
||||
wrapAsync(async (req, res) => {
|
||||
if (!isUuidV4(req.params.id)) {
|
||||
return res.status(400).send('Invalid project id');
|
||||
}
|
||||
|
||||
const PWAManifestService = require('../services/pwa_manifest');
|
||||
const { variant = 'desktop' } = req.query;
|
||||
const PWAManifestService = require('../services/pwa_manifest');
|
||||
const { variant = 'desktop' } = req.query;
|
||||
|
||||
const manifest = await PWAManifestService.generateManifest(
|
||||
req.params.id,
|
||||
variant
|
||||
);
|
||||
const manifest = await PWAManifestService.generateManifest(
|
||||
req.params.id,
|
||||
variant,
|
||||
);
|
||||
|
||||
res.status(200).json(manifest);
|
||||
}));
|
||||
res.status(200).json(manifest);
|
||||
}),
|
||||
);
|
||||
|
||||
router.use('/', require('../helpers').commonErrorHandler);
|
||||
|
||||
|
||||
@ -9,7 +9,12 @@ router.use(checkCrudPermissions('publish_events'));
|
||||
|
||||
const publishHandler = wrapAsync(async (req, res) => {
|
||||
const { projectId, title, description } = req.body;
|
||||
const result = await PublishService.publishToProduction(projectId, req.currentUser, title, description);
|
||||
const result = await PublishService.publishToProduction(
|
||||
projectId,
|
||||
req.currentUser,
|
||||
title,
|
||||
description,
|
||||
);
|
||||
res.status(200).send(result);
|
||||
});
|
||||
|
||||
|
||||
@ -150,4 +150,8 @@ const { createEntityRouter } = require('../factories/router.factory');
|
||||
* description: Some server error
|
||||
*/
|
||||
|
||||
module.exports = createEntityRouter('publish_events', Publish_eventsService, Publish_eventsDBApi);
|
||||
module.exports = createEntityRouter(
|
||||
'publish_events',
|
||||
Publish_eventsService,
|
||||
Publish_eventsDBApi,
|
||||
);
|
||||
|
||||
@ -141,4 +141,8 @@ const { createEntityRouter } = require('../factories/router.factory');
|
||||
* description: Some server error
|
||||
*/
|
||||
|
||||
module.exports = createEntityRouter('pwa_caches', Pwa_cachesService, Pwa_cachesDBApi);
|
||||
module.exports = createEntityRouter(
|
||||
'pwa_caches',
|
||||
Pwa_cachesService,
|
||||
Pwa_cachesDBApi,
|
||||
);
|
||||
|
||||
@ -3,7 +3,9 @@ const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
res.status(200).send(req.runtimeContext || { mode: 'unknown', projectSlug: null });
|
||||
res
|
||||
.status(200)
|
||||
.send(req.runtimeContext || { mode: 'unknown', projectSlug: null });
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
const express = require('express');
|
||||
const SearchService = require('../services/search');
|
||||
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
const { checkCrudPermissions } = require('../middlewares/check-permissions');
|
||||
@ -34,19 +33,22 @@ router.use(checkCrudPermissions('search'));
|
||||
*/
|
||||
|
||||
router.post('/', async (req, res) => {
|
||||
const { searchQuery } = req.body;
|
||||
|
||||
if (!searchQuery) {
|
||||
return res.status(400).json({ error: 'Please enter a search query' });
|
||||
}
|
||||
|
||||
try {
|
||||
const foundMatches = await SearchService.search(searchQuery, req.currentUser );
|
||||
res.json(foundMatches);
|
||||
} catch (error) {
|
||||
console.error('Internal Server Error', error);
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
}
|
||||
});
|
||||
const { searchQuery } = req.body;
|
||||
|
||||
module.exports = router;
|
||||
if (!searchQuery) {
|
||||
return res.status(400).json({ error: 'Please enter a search query' });
|
||||
}
|
||||
|
||||
try {
|
||||
const foundMatches = await SearchService.search(
|
||||
searchQuery,
|
||||
req.currentUser,
|
||||
);
|
||||
res.json(foundMatches);
|
||||
} catch (error) {
|
||||
console.error('Internal Server Error', error);
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@ -71,13 +71,16 @@ router.post(
|
||||
wrapAsync(async (req, res) => {
|
||||
const { currentUser } = req;
|
||||
const isAdminUser = Boolean(
|
||||
currentUser
|
||||
&& currentUser.app_role
|
||||
&& (currentUser.app_role.name === 'Administrator' || currentUser.app_role.globalAccess === true),
|
||||
currentUser &&
|
||||
currentUser.app_role &&
|
||||
(currentUser.app_role.name === 'Administrator' ||
|
||||
currentUser.app_role.globalAccess === true),
|
||||
);
|
||||
|
||||
if (!isAdminUser) {
|
||||
return res.status(403).json({ error: 'Only administrators can execute SQL queries' });
|
||||
return res
|
||||
.status(403)
|
||||
.json({ error: 'Only administrators can execute SQL queries' });
|
||||
}
|
||||
|
||||
const { sql } = req.body;
|
||||
@ -90,7 +93,10 @@ router.post(
|
||||
const wrappedSql = `SELECT * FROM (${normalized}) AS query_result LIMIT ${MAX_SQL_ROWS}`;
|
||||
|
||||
const rows = await db.sequelize.transaction(async (transaction) => {
|
||||
await db.sequelize.query(`SET LOCAL statement_timeout = ${SQL_TIMEOUT_MS}`, { transaction });
|
||||
await db.sequelize.query(
|
||||
`SET LOCAL statement_timeout = ${SQL_TIMEOUT_MS}`,
|
||||
{ transaction },
|
||||
);
|
||||
return db.sequelize.query(wrappedSql, {
|
||||
transaction,
|
||||
type: db.Sequelize.QueryTypes.SELECT,
|
||||
|
||||
@ -148,4 +148,8 @@ const { createEntityRouter } = require('../factories/router.factory');
|
||||
* description: Some server error
|
||||
*/
|
||||
|
||||
module.exports = createEntityRouter('tour_pages', Tour_pagesService, Tour_pagesDBApi);
|
||||
module.exports = createEntityRouter(
|
||||
'tour_pages',
|
||||
Tour_pagesService,
|
||||
Tour_pagesDBApi,
|
||||
);
|
||||
|
||||
@ -1,23 +1,17 @@
|
||||
|
||||
const express = require('express');
|
||||
|
||||
const UsersService = require('../services/users');
|
||||
const UsersDBApi = require('../db/api/users');
|
||||
const wrapAsync = require('../helpers').wrapAsync;
|
||||
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
const { parse } = require('json2csv');
|
||||
|
||||
|
||||
const {
|
||||
checkCrudPermissions,
|
||||
} = require('../middlewares/check-permissions');
|
||||
const { checkCrudPermissions } = require('../middlewares/check-permissions');
|
||||
|
||||
router.use(checkCrudPermissions('users'));
|
||||
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* components:
|
||||
@ -51,45 +45,50 @@ router.use(checkCrudPermissions('users'));
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/users:
|
||||
* post:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Users]
|
||||
* summary: Add new item
|
||||
* description: Add new item
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* data:
|
||||
* description: Data of the updated item
|
||||
* type: object
|
||||
* $ref: "#/components/schemas/Users"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The item was successfully added
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Users"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 405:
|
||||
* description: Invalid input data
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.post('/', wrapAsync(async (req, res) => {
|
||||
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
|
||||
* @swagger
|
||||
* /api/users:
|
||||
* post:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Users]
|
||||
* summary: Add new item
|
||||
* description: Add new item
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* data:
|
||||
* description: Data of the updated item
|
||||
* type: object
|
||||
* $ref: "#/components/schemas/Users"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The item was successfully added
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Users"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 405:
|
||||
* description: Invalid input data
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.post(
|
||||
'/',
|
||||
wrapAsync(async (req, res) => {
|
||||
const referer =
|
||||
req.headers.referer ||
|
||||
`${req.protocol}://${req.hostname}${req.originalUrl}`;
|
||||
const link = new URL(referer);
|
||||
await UsersService.create(req.body.data, req.currentUser, true, link.host);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
@ -126,196 +125,205 @@ router.post('/', wrapAsync(async (req, res) => {
|
||||
* description: Some server error
|
||||
*
|
||||
*/
|
||||
router.post('/bulk-import', wrapAsync(async (req, res) => {
|
||||
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
|
||||
router.post(
|
||||
'/bulk-import',
|
||||
wrapAsync(async (req, res) => {
|
||||
const referer =
|
||||
req.headers.referer ||
|
||||
`${req.protocol}://${req.hostname}${req.originalUrl}`;
|
||||
const link = new URL(referer);
|
||||
await UsersService.bulkImport(req, res, true, link.host);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/users/{id}:
|
||||
* put:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Users]
|
||||
* summary: Update the data of the selected item
|
||||
* description: Update the data of the selected item
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* description: Item ID to update
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* requestBody:
|
||||
* description: Set new item data
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* id:
|
||||
* description: ID of the updated item
|
||||
* type: string
|
||||
* data:
|
||||
* description: Data of the updated item
|
||||
* type: object
|
||||
* $ref: "#/components/schemas/Users"
|
||||
* required:
|
||||
* - id
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The item data was successfully updated
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Users"
|
||||
* 400:
|
||||
* description: Invalid ID supplied
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Item not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.put('/:id', wrapAsync(async (req, res) => {
|
||||
await UsersService.update(req.body.data, req.body.id, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
* @swagger
|
||||
* /api/users/{id}:
|
||||
* put:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Users]
|
||||
* summary: Update the data of the selected item
|
||||
* description: Update the data of the selected item
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* description: Item ID to update
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* requestBody:
|
||||
* description: Set new item data
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* id:
|
||||
* description: ID of the updated item
|
||||
* type: string
|
||||
* data:
|
||||
* description: Data of the updated item
|
||||
* type: object
|
||||
* $ref: "#/components/schemas/Users"
|
||||
* required:
|
||||
* - id
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The item data was successfully updated
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Users"
|
||||
* 400:
|
||||
* description: Invalid ID supplied
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Item not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.put(
|
||||
'/:id',
|
||||
wrapAsync(async (req, res) => {
|
||||
await UsersService.update(req.body.data, req.body.id, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/users/{id}:
|
||||
* delete:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Users]
|
||||
* summary: Delete the selected item
|
||||
* description: Delete the selected item
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* description: Item ID to delete
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The item was successfully deleted
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Users"
|
||||
* 400:
|
||||
* description: Invalid ID supplied
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Item not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.delete('/:id', wrapAsync(async (req, res) => {
|
||||
await UsersService.remove(req.params.id, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
* @swagger
|
||||
* /api/users/{id}:
|
||||
* delete:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Users]
|
||||
* summary: Delete the selected item
|
||||
* description: Delete the selected item
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* description: Item ID to delete
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The item was successfully deleted
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Users"
|
||||
* 400:
|
||||
* description: Invalid ID supplied
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Item not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.delete(
|
||||
'/:id',
|
||||
wrapAsync(async (req, res) => {
|
||||
await UsersService.remove(req.params.id, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/users/deleteByIds:
|
||||
* post:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Users]
|
||||
* summary: Delete the selected item list
|
||||
* description: Delete the selected item list
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* ids:
|
||||
* description: IDs of the updated items
|
||||
* type: array
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The items was successfully deleted
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Users"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Items not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.post('/deleteByIds', wrapAsync(async (req, res) => {
|
||||
* @swagger
|
||||
* /api/users/deleteByIds:
|
||||
* post:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Users]
|
||||
* summary: Delete the selected item list
|
||||
* description: Delete the selected item list
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* ids:
|
||||
* description: IDs of the updated items
|
||||
* type: array
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The items was successfully deleted
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Users"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Items not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.post(
|
||||
'/deleteByIds',
|
||||
wrapAsync(async (req, res) => {
|
||||
await UsersService.deleteByIds(req.body.data, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/users:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Users]
|
||||
* summary: Get all users
|
||||
* description: Get all users
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Users list successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Users"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Data not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.get('/', wrapAsync(async (req, res) => {
|
||||
const filetype = req.query.filetype
|
||||
|
||||
const currentUser = req.currentUser;
|
||||
const payload = await UsersDBApi.findAll(
|
||||
req.query, { currentUser }
|
||||
);
|
||||
if (filetype && filetype === 'csv') {
|
||||
const fields = ['id','firstName','lastName','phoneNumber','email',
|
||||
|
||||
|
||||
|
||||
];
|
||||
const opts = { fields };
|
||||
try {
|
||||
const csv = parse(payload.rows, opts);
|
||||
res.status(200).attachment(csv);
|
||||
res.send(csv)
|
||||
* @swagger
|
||||
* /api/users:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Users]
|
||||
* summary: Get all users
|
||||
* description: Get all users
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Users list successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Users"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Data not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.get(
|
||||
'/',
|
||||
wrapAsync(async (req, res) => {
|
||||
const filetype = req.query.filetype;
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
const currentUser = req.currentUser;
|
||||
const payload = await UsersDBApi.findAll(req.query, { currentUser });
|
||||
if (filetype && filetype === 'csv') {
|
||||
const fields = ['id', 'firstName', 'lastName', 'phoneNumber', 'email'];
|
||||
const opts = { fields };
|
||||
try {
|
||||
const csv = parse(payload.rows, opts);
|
||||
res.status(200).attachment(csv);
|
||||
res.send(csv);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
} else {
|
||||
res.status(200).send(payload);
|
||||
}
|
||||
} else {
|
||||
res.status(200).send(payload);
|
||||
}
|
||||
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
@ -342,17 +350,18 @@ router.get('/', wrapAsync(async (req, res) => {
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.get('/count', wrapAsync(async (req, res) => {
|
||||
|
||||
router.get(
|
||||
'/count',
|
||||
wrapAsync(async (req, res) => {
|
||||
const currentUser = req.currentUser;
|
||||
const payload = await UsersDBApi.findAll(
|
||||
req.query,
|
||||
null,
|
||||
{ countOnly: true, currentUser }
|
||||
);
|
||||
const payload = await UsersDBApi.findAll(req.query, null, {
|
||||
countOnly: true,
|
||||
currentUser,
|
||||
});
|
||||
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
@ -380,60 +389,57 @@ router.get('/count', wrapAsync(async (req, res) => {
|
||||
* description: Some server error
|
||||
*/
|
||||
router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const payload = await UsersDBApi.findAllAutocomplete(
|
||||
req.query.query,
|
||||
req.query.limit,
|
||||
req.query.offset,
|
||||
|
||||
);
|
||||
|
||||
res.status(200).send(payload);
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/users/{id}:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Users]
|
||||
* summary: Get selected item
|
||||
* description: Get selected item
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* description: ID of item to get
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Selected item successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Users"
|
||||
* 400:
|
||||
* description: Invalid ID supplied
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Item not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await UsersDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
);
|
||||
|
||||
|
||||
delete payload.password;
|
||||
|
||||
* @swagger
|
||||
* /api/users/{id}:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Users]
|
||||
* summary: Get selected item
|
||||
* description: Get selected item
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* description: ID of item to get
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Selected item successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Users"
|
||||
* 400:
|
||||
* description: Invalid ID supplied
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Item not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.get(
|
||||
'/:id',
|
||||
wrapAsync(async (req, res) => {
|
||||
const payload = await UsersDBApi.findBy({ id: req.params.id });
|
||||
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
delete payload.password;
|
||||
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
router.use('/', require('../helpers').commonErrorHandler);
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ const ValidationError = require('./notifications/errors/validation');
|
||||
const ForbiddenError = require('./notifications/errors/forbidden');
|
||||
const bcrypt = require('bcrypt');
|
||||
const EmailAddressVerificationEmail = require('./email/list/addressVerification');
|
||||
const InvitationEmail = require("./email/list/invitation");
|
||||
const InvitationEmail = require('./email/list/invitation');
|
||||
const PasswordResetEmail = require('./email/list/passwordReset');
|
||||
const EmailSender = require('./email');
|
||||
const config = require('../config');
|
||||
@ -12,7 +12,7 @@ const helpers = require('../helpers');
|
||||
|
||||
class Auth {
|
||||
static async signup(email, password, options = {}, host) {
|
||||
const user = await UsersDBApi.findBy({email});
|
||||
const user = await UsersDBApi.findBy({ email });
|
||||
|
||||
const hashedPassword = await bcrypt.hash(
|
||||
password,
|
||||
@ -21,35 +21,24 @@ class Auth {
|
||||
|
||||
if (user) {
|
||||
if (user.authenticationUid) {
|
||||
throw new ValidationError(
|
||||
'auth.emailAlreadyInUse',
|
||||
);
|
||||
throw new ValidationError('auth.emailAlreadyInUse');
|
||||
}
|
||||
|
||||
if (user.disabled) {
|
||||
throw new ValidationError(
|
||||
'auth.userDisabled',
|
||||
);
|
||||
throw new ValidationError('auth.userDisabled');
|
||||
}
|
||||
|
||||
await UsersDBApi.updatePassword(
|
||||
user.id,
|
||||
hashedPassword,
|
||||
options,
|
||||
);
|
||||
await UsersDBApi.updatePassword(user.id, hashedPassword, options);
|
||||
|
||||
if (EmailSender.isConfigured) {
|
||||
await this.sendEmailAddressVerificationEmail(
|
||||
user.email,
|
||||
host,
|
||||
);
|
||||
await this.sendEmailAddressVerificationEmail(user.email, host);
|
||||
}
|
||||
|
||||
const data = {
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email
|
||||
}
|
||||
email: user.email,
|
||||
},
|
||||
};
|
||||
|
||||
return helpers.jwtSign(data);
|
||||
@ -60,47 +49,37 @@ class Auth {
|
||||
firstName: email.split('@')[0],
|
||||
password: hashedPassword,
|
||||
email: email,
|
||||
|
||||
},
|
||||
options,
|
||||
);
|
||||
|
||||
if (EmailSender.isConfigured) {
|
||||
await this.sendEmailAddressVerificationEmail(
|
||||
newUser.email,
|
||||
host,
|
||||
);
|
||||
await this.sendEmailAddressVerificationEmail(newUser.email, host);
|
||||
}
|
||||
|
||||
const data = {
|
||||
user: {
|
||||
id: newUser.id,
|
||||
email: newUser.email
|
||||
}
|
||||
email: newUser.email,
|
||||
},
|
||||
};
|
||||
|
||||
return helpers.jwtSign(data);
|
||||
}
|
||||
|
||||
static async signin(email, password) {
|
||||
const user = await UsersDBApi.findBy({email});
|
||||
const user = await UsersDBApi.findBy({ email });
|
||||
|
||||
if (!user) {
|
||||
throw new ValidationError(
|
||||
'auth.userNotFound',
|
||||
);
|
||||
throw new ValidationError('auth.userNotFound');
|
||||
}
|
||||
|
||||
if (user.disabled) {
|
||||
throw new ValidationError(
|
||||
'auth.userDisabled',
|
||||
);
|
||||
throw new ValidationError('auth.userDisabled');
|
||||
}
|
||||
|
||||
if (!user.password) {
|
||||
throw new ValidationError(
|
||||
'auth.wrongPassword',
|
||||
);
|
||||
throw new ValidationError('auth.wrongPassword');
|
||||
}
|
||||
|
||||
if (!EmailSender.isConfigured) {
|
||||
@ -108,49 +87,33 @@ class Auth {
|
||||
}
|
||||
|
||||
if (!user.emailVerified) {
|
||||
throw new ValidationError(
|
||||
'auth.userNotVerified',
|
||||
);
|
||||
throw new ValidationError('auth.userNotVerified');
|
||||
}
|
||||
|
||||
const passwordsMatch = await bcrypt.compare(
|
||||
password,
|
||||
user.password,
|
||||
);
|
||||
const passwordsMatch = await bcrypt.compare(password, user.password);
|
||||
|
||||
if (!passwordsMatch) {
|
||||
throw new ValidationError(
|
||||
'auth.wrongPassword',
|
||||
);
|
||||
throw new ValidationError('auth.wrongPassword');
|
||||
}
|
||||
|
||||
const data = {
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email
|
||||
}
|
||||
email: user.email,
|
||||
},
|
||||
};
|
||||
|
||||
return helpers.jwtSign(data);
|
||||
}
|
||||
|
||||
static async sendEmailAddressVerificationEmail(
|
||||
email,
|
||||
host,
|
||||
) {
|
||||
|
||||
|
||||
static async sendEmailAddressVerificationEmail(email, host) {
|
||||
let link;
|
||||
try {
|
||||
const token = await UsersDBApi.generateEmailVerificationToken(
|
||||
email,
|
||||
);
|
||||
const token = await UsersDBApi.generateEmailVerificationToken(email);
|
||||
link = `${host}/verify-email?token=${token}`;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw new ValidationError(
|
||||
'auth.emailAddressVerificationEmail.error',
|
||||
);
|
||||
throw new ValidationError('auth.emailAddressVerificationEmail.error');
|
||||
}
|
||||
|
||||
const emailAddressVerificationEmail = new EmailAddressVerificationEmail(
|
||||
@ -158,50 +121,33 @@ class Auth {
|
||||
link,
|
||||
);
|
||||
|
||||
return new EmailSender(
|
||||
emailAddressVerificationEmail,
|
||||
).send();
|
||||
return new EmailSender(emailAddressVerificationEmail).send();
|
||||
}
|
||||
|
||||
static async sendPasswordResetEmail(email, type = 'register', host) {
|
||||
|
||||
|
||||
let link;
|
||||
|
||||
try {
|
||||
const token = await UsersDBApi.generatePasswordResetToken(
|
||||
email,
|
||||
);
|
||||
const token = await UsersDBApi.generatePasswordResetToken(email);
|
||||
link = `${host}/password-reset?token=${token}`;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw new ValidationError(
|
||||
'auth.passwordReset.error',
|
||||
);
|
||||
throw new ValidationError('auth.passwordReset.error');
|
||||
}
|
||||
|
||||
let passwordResetEmail;
|
||||
if (type === 'register') {
|
||||
passwordResetEmail = new PasswordResetEmail(
|
||||
email,
|
||||
link,
|
||||
);
|
||||
passwordResetEmail = new PasswordResetEmail(email, link);
|
||||
}
|
||||
if (type === 'invitation') {
|
||||
passwordResetEmail = new InvitationEmail(
|
||||
email,
|
||||
link,
|
||||
);
|
||||
passwordResetEmail = new InvitationEmail(email, link);
|
||||
}
|
||||
|
||||
return new EmailSender(passwordResetEmail).send();
|
||||
}
|
||||
|
||||
static async verifyEmail(token, options = {}) {
|
||||
const user = await UsersDBApi.findByEmailVerificationToken(
|
||||
token,
|
||||
options,
|
||||
);
|
||||
const user = await UsersDBApi.findByEmailVerificationToken(token, options);
|
||||
|
||||
if (!user) {
|
||||
throw new ValidationError(
|
||||
@ -209,10 +155,7 @@ class Auth {
|
||||
);
|
||||
}
|
||||
|
||||
return UsersDBApi.markEmailVerified(
|
||||
user.id,
|
||||
options,
|
||||
);
|
||||
return UsersDBApi.markEmailVerified(user.id, options);
|
||||
}
|
||||
|
||||
static async passwordUpdate(currentPassword, newPassword, options) {
|
||||
@ -227,9 +170,7 @@ class Auth {
|
||||
);
|
||||
|
||||
if (!currentPasswordMatch) {
|
||||
throw new ValidationError(
|
||||
'auth.wrongPassword'
|
||||
)
|
||||
throw new ValidationError('auth.wrongPassword');
|
||||
}
|
||||
|
||||
const newPasswordMatch = await bcrypt.compare(
|
||||
@ -238,9 +179,7 @@ class Auth {
|
||||
);
|
||||
|
||||
if (newPasswordMatch) {
|
||||
throw new ValidationError(
|
||||
'auth.passwordUpdate.samePassword'
|
||||
)
|
||||
throw new ValidationError('auth.passwordUpdate.samePassword');
|
||||
}
|
||||
|
||||
const hashedPassword = await bcrypt.hash(
|
||||
@ -248,27 +187,14 @@ class Auth {
|
||||
config.bcrypt.saltRounds,
|
||||
);
|
||||
|
||||
return UsersDBApi.updatePassword(
|
||||
currentUser.id,
|
||||
hashedPassword,
|
||||
options,
|
||||
);
|
||||
return UsersDBApi.updatePassword(currentUser.id, hashedPassword, options);
|
||||
}
|
||||
|
||||
static async passwordReset(
|
||||
token,
|
||||
password,
|
||||
options = {},
|
||||
) {
|
||||
const user = await UsersDBApi.findByPasswordResetToken(
|
||||
token,
|
||||
options,
|
||||
);
|
||||
static async passwordReset(token, password, options = {}) {
|
||||
const user = await UsersDBApi.findByPasswordResetToken(token, options);
|
||||
|
||||
if (!user) {
|
||||
throw new ValidationError(
|
||||
'auth.passwordReset.invalidToken',
|
||||
);
|
||||
throw new ValidationError('auth.passwordReset.invalidToken');
|
||||
}
|
||||
|
||||
const hashedPassword = await bcrypt.hash(
|
||||
@ -276,31 +202,19 @@ class Auth {
|
||||
config.bcrypt.saltRounds,
|
||||
);
|
||||
|
||||
return UsersDBApi.updatePassword(
|
||||
user.id,
|
||||
hashedPassword,
|
||||
options,
|
||||
);
|
||||
return UsersDBApi.updatePassword(user.id, hashedPassword, options);
|
||||
}
|
||||
|
||||
static async updateProfile(data, currentUser) {
|
||||
let transaction = await db.sequelize.transaction();
|
||||
|
||||
try {
|
||||
await UsersDBApi.findBy(
|
||||
{id: currentUser.id},
|
||||
{transaction},
|
||||
);
|
||||
|
||||
await UsersDBApi.update(
|
||||
currentUser.id,
|
||||
data,
|
||||
{
|
||||
currentUser,
|
||||
transaction
|
||||
},
|
||||
);
|
||||
await UsersDBApi.findBy({ id: currentUser.id }, { transaction });
|
||||
|
||||
await UsersDBApi.update(currentUser.id, data, {
|
||||
currentUser,
|
||||
transaction,
|
||||
});
|
||||
|
||||
await transaction.commit();
|
||||
} catch (error) {
|
||||
|
||||
@ -10,23 +10,27 @@ module.exports = class EmailAddressVerificationEmail {
|
||||
|
||||
get subject() {
|
||||
return getNotification(
|
||||
'emails.emailAddressVerification.subject',
|
||||
getNotification('app.title'),
|
||||
'emails.emailAddressVerification.subject',
|
||||
getNotification('app.title'),
|
||||
);
|
||||
}
|
||||
|
||||
async html() {
|
||||
try {
|
||||
const templatePath = path.join(__dirname, '../../email/htmlTemplates/addressVerification/emailAddressVerification.html');
|
||||
const templatePath = path.join(
|
||||
__dirname,
|
||||
'../../email/htmlTemplates/addressVerification/emailAddressVerification.html',
|
||||
);
|
||||
|
||||
const template = await fs.readFile(templatePath, 'utf8');
|
||||
|
||||
const appTitle = getNotification('app.title');
|
||||
const signupUrl = this.link;
|
||||
|
||||
let html = template.replace(/{appTitle}/g, appTitle)
|
||||
.replace(/{signupUrl}/g, signupUrl)
|
||||
.replace(/{to}/g, this.to);
|
||||
let html = template
|
||||
.replace(/{appTitle}/g, appTitle)
|
||||
.replace(/{signupUrl}/g, signupUrl)
|
||||
.replace(/{to}/g, this.to);
|
||||
|
||||
return html;
|
||||
} catch (error) {
|
||||
@ -34,5 +38,4 @@ module.exports = class EmailAddressVerificationEmail {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@ -10,23 +10,27 @@ module.exports = class InvitationEmail {
|
||||
|
||||
get subject() {
|
||||
return getNotification(
|
||||
'emails.invitation.subject',
|
||||
getNotification('app.title'),
|
||||
'emails.invitation.subject',
|
||||
getNotification('app.title'),
|
||||
);
|
||||
}
|
||||
|
||||
async html() {
|
||||
try {
|
||||
const templatePath = path.join(__dirname, '../../email/htmlTemplates/invitation/invitationTemplate.html');
|
||||
const templatePath = path.join(
|
||||
__dirname,
|
||||
'../../email/htmlTemplates/invitation/invitationTemplate.html',
|
||||
);
|
||||
|
||||
const template = await fs.readFile(templatePath, 'utf8');
|
||||
|
||||
const appTitle = getNotification('app.title');
|
||||
const signupUrl = `${this.host}&invitation=true`;
|
||||
|
||||
let html = template.replace(/{appTitle}/g, appTitle)
|
||||
.replace(/{signupUrl}/g, signupUrl)
|
||||
.replace(/{to}/g, this.to);
|
||||
let html = template
|
||||
.replace(/{appTitle}/g, appTitle)
|
||||
.replace(/{signupUrl}/g, signupUrl)
|
||||
.replace(/{to}/g, this.to);
|
||||
|
||||
return html;
|
||||
} catch (error) {
|
||||
@ -34,4 +38,4 @@ module.exports = class InvitationEmail {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
const { getNotification } = require('../../notifications/helpers');
|
||||
const path = require("path");
|
||||
const {promises: fs} = require("fs");
|
||||
const path = require('path');
|
||||
const { promises: fs } = require('fs');
|
||||
|
||||
module.exports = class PasswordResetEmail {
|
||||
constructor(to, link) {
|
||||
@ -17,7 +17,10 @@ module.exports = class PasswordResetEmail {
|
||||
|
||||
async html() {
|
||||
try {
|
||||
const templatePath = path.join(__dirname, '../../email/htmlTemplates/passwordReset/passwordResetEmail.html');
|
||||
const templatePath = path.join(
|
||||
__dirname,
|
||||
'../../email/htmlTemplates/passwordReset/passwordResetEmail.html',
|
||||
);
|
||||
|
||||
const template = await fs.readFile(templatePath, 'utf8');
|
||||
|
||||
@ -25,9 +28,10 @@ module.exports = class PasswordResetEmail {
|
||||
const resetUrl = this.link;
|
||||
const accountName = this.to;
|
||||
|
||||
let html = template.replace(/{appTitle}/g, appTitle)
|
||||
.replace(/{resetUrl}/g, resetUrl)
|
||||
.replace(/{accountName}/g, accountName);
|
||||
let html = template
|
||||
.replace(/{appTitle}/g, appTitle)
|
||||
.replace(/{resetUrl}/g, resetUrl)
|
||||
.replace(/{accountName}/g, accountName);
|
||||
|
||||
return html;
|
||||
} catch (error) {
|
||||
|
||||
@ -4,7 +4,7 @@ const config = require('../config');
|
||||
const path = require('path');
|
||||
const { pipeline } = require('stream/promises');
|
||||
const { v4: uuid } = require('uuid');
|
||||
const { format } = require("util");
|
||||
const { format } = require('util');
|
||||
const {
|
||||
S3Client,
|
||||
PutObjectCommand,
|
||||
@ -24,7 +24,7 @@ const ensureDirectoryExistence = (filePath) => {
|
||||
|
||||
ensureDirectoryExistence(dirname);
|
||||
fs.mkdirSync(dirname);
|
||||
}
|
||||
};
|
||||
|
||||
const UPLOAD_SESSIONS_DIR = path.join(config.uploadDir, 'upload_sessions');
|
||||
const UPLOAD_SESSION_TTL_MS = 24 * 60 * 60 * 1000;
|
||||
@ -39,7 +39,7 @@ const sanitizeFolder = (folder) => {
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
const sanitizeFilename = (filename) => {
|
||||
const value = path.basename(String(filename || '').trim());
|
||||
@ -49,11 +49,13 @@ const sanitizeFilename = (filename) => {
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
const getSessionDir = (sessionId) => path.join(UPLOAD_SESSIONS_DIR, sessionId);
|
||||
const getSessionMetaPath = (sessionId) => path.join(getSessionDir(sessionId), 'meta.json');
|
||||
const getSessionChunksDir = (sessionId) => path.join(getSessionDir(sessionId), 'chunks');
|
||||
const getSessionMetaPath = (sessionId) =>
|
||||
path.join(getSessionDir(sessionId), 'meta.json');
|
||||
const getSessionChunksDir = (sessionId) =>
|
||||
path.join(getSessionDir(sessionId), 'chunks');
|
||||
const getSessionChunkPath = (sessionId, chunkIndex) =>
|
||||
path.join(getSessionChunksDir(sessionId), `${String(chunkIndex)}.part`);
|
||||
|
||||
@ -66,13 +68,13 @@ const readSessionMeta = (sessionId) => {
|
||||
|
||||
const raw = fs.readFileSync(metaPath, 'utf8');
|
||||
return JSON.parse(raw);
|
||||
}
|
||||
};
|
||||
|
||||
const writeSessionMeta = (sessionId, payload) => {
|
||||
const metaPath = getSessionMetaPath(sessionId);
|
||||
ensureDirectoryExistence(metaPath);
|
||||
fs.writeFileSync(metaPath, JSON.stringify(payload, null, 2), 'utf8');
|
||||
}
|
||||
};
|
||||
|
||||
const removeUploadSession = (sessionId) => {
|
||||
const sessionDir = getSessionDir(sessionId);
|
||||
@ -80,7 +82,7 @@ const removeUploadSession = (sessionId) => {
|
||||
if (fs.existsSync(sessionDir)) {
|
||||
fs.rmSync(sessionDir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const cleanupExpiredUploadSessions = () => {
|
||||
if (!fs.existsSync(UPLOAD_SESSIONS_DIR)) {
|
||||
@ -98,7 +100,9 @@ const cleanupExpiredUploadSessions = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedAt = new Date(meta.updatedAt || meta.createdAt || 0).getTime();
|
||||
const updatedAt = new Date(
|
||||
meta.updatedAt || meta.createdAt || 0,
|
||||
).getTime();
|
||||
if (!updatedAt || now - updatedAt > UPLOAD_SESSION_TTL_MS) {
|
||||
removeUploadSession(sessionId);
|
||||
}
|
||||
@ -107,7 +111,7 @@ const cleanupExpiredUploadSessions = () => {
|
||||
removeUploadSession(sessionId);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const streamAppendFile = async (targetPath, sourcePath) => {
|
||||
await new Promise((resolve, reject) => {
|
||||
@ -119,7 +123,7 @@ const streamAppendFile = async (targetPath, sourcePath) => {
|
||||
writeStream.on('finish', resolve);
|
||||
readStream.pipe(writeStream, { end: true });
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// S3 session storage helpers
|
||||
const S3_UPLOAD_SESSIONS_PREFIX = '_upload_sessions';
|
||||
@ -163,7 +167,13 @@ const readS3SessionMeta = async (client, bucket, prefix, sessionId) => {
|
||||
}
|
||||
};
|
||||
|
||||
const writeS3SessionMeta = async (client, bucket, prefix, sessionId, payload) => {
|
||||
const writeS3SessionMeta = async (
|
||||
client,
|
||||
bucket,
|
||||
prefix,
|
||||
sessionId,
|
||||
payload,
|
||||
) => {
|
||||
const key = getS3SessionMetaKey(prefix, sessionId);
|
||||
await client.send(
|
||||
new PutObjectCommand({
|
||||
@ -175,7 +185,14 @@ const writeS3SessionMeta = async (client, bucket, prefix, sessionId, payload) =>
|
||||
);
|
||||
};
|
||||
|
||||
const uploadS3Chunk = async (client, bucket, prefix, sessionId, chunkIndex, body) => {
|
||||
const uploadS3Chunk = async (
|
||||
client,
|
||||
bucket,
|
||||
prefix,
|
||||
sessionId,
|
||||
chunkIndex,
|
||||
body,
|
||||
) => {
|
||||
const key = getS3SessionChunkKey(prefix, sessionId, chunkIndex);
|
||||
await client.send(
|
||||
new PutObjectCommand({
|
||||
@ -186,7 +203,13 @@ const uploadS3Chunk = async (client, bucket, prefix, sessionId, chunkIndex, body
|
||||
);
|
||||
};
|
||||
|
||||
const downloadS3Chunk = async (client, bucket, prefix, sessionId, chunkIndex) => {
|
||||
const downloadS3Chunk = async (
|
||||
client,
|
||||
bucket,
|
||||
prefix,
|
||||
sessionId,
|
||||
chunkIndex,
|
||||
) => {
|
||||
const key = getS3SessionChunkKey(prefix, sessionId, chunkIndex);
|
||||
try {
|
||||
const output = await client.send(
|
||||
@ -236,7 +259,9 @@ const removeS3UploadSession = async (client, bucket, prefix, sessionId) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const objectsToDelete = listResult.Contents.map((obj) => ({ Key: obj.Key }));
|
||||
const objectsToDelete = listResult.Contents.map((obj) => ({
|
||||
Key: obj.Key,
|
||||
}));
|
||||
|
||||
await client.send(
|
||||
new DeleteObjectsCommand({
|
||||
@ -291,12 +316,17 @@ const cleanupExpiredS3UploadSessions = async () => {
|
||||
continue;
|
||||
}
|
||||
|
||||
const updatedAt = new Date(meta.updatedAt || meta.createdAt || 0).getTime();
|
||||
const updatedAt = new Date(
|
||||
meta.updatedAt || meta.createdAt || 0,
|
||||
).getTime();
|
||||
if (!updatedAt || now - updatedAt > UPLOAD_SESSION_TTL_MS) {
|
||||
await removeS3UploadSession(client, bucket, prefix, sessionId);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to cleanup S3 upload session ${sessionId}`, error);
|
||||
console.error(
|
||||
`Failed to cleanup S3 upload session ${sessionId}`,
|
||||
error,
|
||||
);
|
||||
await removeS3UploadSession(client, bucket, prefix, sessionId);
|
||||
}
|
||||
}
|
||||
@ -318,18 +348,13 @@ const uploadLocal = (
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
validations.entity
|
||||
) {
|
||||
if (validations.entity) {
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
if (validations.folderIncludesAuthenticationUid) {
|
||||
folder = folder.replace(
|
||||
':userId',
|
||||
req.currentUser.authenticationUid,
|
||||
);
|
||||
folder = folder.replace(':userId', req.currentUser.authenticationUid);
|
||||
if (
|
||||
!req.currentUser.authenticationUid ||
|
||||
!folder.includes(req.currentUser.authenticationUid)
|
||||
@ -352,11 +377,7 @@ const uploadLocal = (
|
||||
return;
|
||||
}
|
||||
|
||||
const privateUrl = path.join(
|
||||
form.uploadDir,
|
||||
folder,
|
||||
filename,
|
||||
);
|
||||
const privateUrl = path.join(form.uploadDir, folder, filename);
|
||||
ensureDirectoryExistence(privateUrl);
|
||||
fs.renameSync(fileTempUrl, privateUrl);
|
||||
res.sendStatus(200);
|
||||
@ -365,17 +386,17 @@ const uploadLocal = (
|
||||
form.on('error', function (err) {
|
||||
res.status(500).send(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const downloadLocal = async (req, res) => {
|
||||
const privateUrl = req.query.privateUrl;
|
||||
if (!privateUrl) {
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');
|
||||
res.download(path.join(config.uploadDir, privateUrl));
|
||||
}
|
||||
const privateUrl = req.query.privateUrl;
|
||||
if (!privateUrl) {
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');
|
||||
res.download(path.join(config.uploadDir, privateUrl));
|
||||
};
|
||||
|
||||
const deleteLocal = async (privateUrl) => {
|
||||
try {
|
||||
@ -392,30 +413,32 @@ const deleteLocal = async (privateUrl) => {
|
||||
console.error(`Cannot delete local file ${privateUrl}`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const initGCloud = () => {
|
||||
const processFile = require("../middlewares/upload");
|
||||
const { Storage } = require("@google-cloud/storage");
|
||||
const processFile = require('../middlewares/upload');
|
||||
const { Storage } = require('@google-cloud/storage');
|
||||
|
||||
const hash = config.gcloud.hash
|
||||
const hash = config.gcloud.hash;
|
||||
|
||||
const privateKey = process.env.GC_PRIVATE_KEY.replace(/\\\n/g, "\n");
|
||||
const privateKey = process.env.GC_PRIVATE_KEY.replace(/\\\n/g, '\n');
|
||||
|
||||
const storage = new Storage({
|
||||
projectId: process.env.GC_PROJECT_ID,
|
||||
credentials: {
|
||||
client_email: process.env.GC_CLIENT_EMAIL,
|
||||
private_key: privateKey
|
||||
}
|
||||
projectId: process.env.GC_PROJECT_ID,
|
||||
credentials: {
|
||||
client_email: process.env.GC_CLIENT_EMAIL,
|
||||
private_key: privateKey,
|
||||
},
|
||||
});
|
||||
|
||||
const bucket = storage.bucket(config.gcloud.bucket);
|
||||
return {hash, bucket, processFile};
|
||||
}
|
||||
return { hash, bucket, processFile };
|
||||
};
|
||||
|
||||
const getFileStorageProvider = () => {
|
||||
const provider = (process.env.FILE_STORAGE_PROVIDER || '').trim().toLowerCase();
|
||||
const provider = (process.env.FILE_STORAGE_PROVIDER || '')
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
|
||||
if (provider) {
|
||||
return provider;
|
||||
@ -445,7 +468,7 @@ const getFileStorageProvider = () => {
|
||||
}
|
||||
|
||||
return 'local';
|
||||
}
|
||||
};
|
||||
|
||||
const initS3 = () => {
|
||||
const processFile = require('../middlewares/upload');
|
||||
@ -464,7 +487,7 @@ const initS3 = () => {
|
||||
prefix: config.s3.prefix,
|
||||
processFile,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const buildStoragePath = (prefix, privateUrl) => {
|
||||
const cleanPrefix = (prefix || '').replace(/^\/+|\/+$/g, '');
|
||||
@ -475,17 +498,17 @@ const buildStoragePath = (prefix, privateUrl) => {
|
||||
}
|
||||
|
||||
return `${cleanPrefix}/${cleanPrivateUrl}`;
|
||||
}
|
||||
};
|
||||
|
||||
const uploadGCloud = async (folder, req, res) => {
|
||||
try {
|
||||
const {hash, bucket, processFile} = initGCloud();
|
||||
const { hash, bucket, processFile } = initGCloud();
|
||||
await processFile(req, res);
|
||||
let buffer = await req.file.buffer;
|
||||
let filename = await req.body.filename;
|
||||
|
||||
if (!req.file) {
|
||||
return res.status(400).send({ message: "Please upload a file!" });
|
||||
return res.status(400).send({ message: 'Please upload a file!' });
|
||||
}
|
||||
|
||||
let path = `${hash}/${folder}/${filename}`;
|
||||
@ -497,7 +520,7 @@ const uploadGCloud = async (folder, req, res) => {
|
||||
resumable: false,
|
||||
});
|
||||
|
||||
blobStream.on("error", (err) => {
|
||||
blobStream.on('error', (err) => {
|
||||
console.log('Upload error');
|
||||
console.log(err.message);
|
||||
res.status(500).send({ message: err.message });
|
||||
@ -505,52 +528,51 @@ const uploadGCloud = async (folder, req, res) => {
|
||||
|
||||
console.log(`https://storage.googleapis.com/${bucket.name}/${blob.name}`);
|
||||
|
||||
blobStream.on("finish", async () => {
|
||||
blobStream.on('finish', async () => {
|
||||
const publicUrl = format(
|
||||
`https://storage.googleapis.com/${bucket.name}/${blob.name}`
|
||||
`https://storage.googleapis.com/${bucket.name}/${blob.name}`,
|
||||
);
|
||||
|
||||
res.status(200).send({
|
||||
message: "Uploaded the file successfully: " + path,
|
||||
message: 'Uploaded the file successfully: ' + path,
|
||||
url: publicUrl,
|
||||
});
|
||||
});
|
||||
|
||||
blobStream.end(buffer)
|
||||
blobStream.end(buffer);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
|
||||
res.status(500).send({
|
||||
message: `Could not upload the file. ${err}`
|
||||
message: `Could not upload the file. ${err}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const downloadGCloud = async (req, res) => {
|
||||
try {
|
||||
const {hash, bucket} = initGCloud();
|
||||
const { hash, bucket } = initGCloud();
|
||||
|
||||
const privateUrl = await req.query.privateUrl;
|
||||
const filePath = `${hash}/${privateUrl}`;
|
||||
const file = bucket.file(filePath)
|
||||
const file = bucket.file(filePath);
|
||||
const fileExists = await file.exists();
|
||||
|
||||
if (fileExists[0]) {
|
||||
res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');
|
||||
const stream = file.createReadStream();
|
||||
stream.pipe(res);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
res.status(404).send({
|
||||
message: "Could not download the file.",
|
||||
});
|
||||
message: 'Could not download the file.',
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
res.status(404).send({
|
||||
message: "Could not download the file. " + err,
|
||||
message: 'Could not download the file. ' + err,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const uploadS3 = async (folder, req, res) => {
|
||||
try {
|
||||
@ -558,13 +580,13 @@ const uploadS3 = async (folder, req, res) => {
|
||||
await processFile(req, res);
|
||||
|
||||
if (!req.file) {
|
||||
return res.status(400).send({ message: "Please upload a file!" });
|
||||
return res.status(400).send({ message: 'Please upload a file!' });
|
||||
}
|
||||
|
||||
const filename = req.body.filename;
|
||||
|
||||
if (!filename) {
|
||||
return res.status(400).send({ message: "Missing filename" });
|
||||
return res.status(400).send({ message: 'Missing filename' });
|
||||
}
|
||||
|
||||
const privateUrl = `${folder}/${filename}`;
|
||||
@ -589,14 +611,14 @@ const uploadS3 = async (folder, req, res) => {
|
||||
message: `Could not upload the file. ${error.message || error}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const downloadS3 = async (req, res) => {
|
||||
try {
|
||||
const privateUrl = req.query.privateUrl;
|
||||
|
||||
if (!privateUrl) {
|
||||
return res.status(404).send({ message: "Missing privateUrl" });
|
||||
return res.status(404).send({ message: 'Missing privateUrl' });
|
||||
}
|
||||
|
||||
const { client, bucket, prefix } = initS3();
|
||||
@ -610,7 +632,7 @@ const downloadS3 = async (req, res) => {
|
||||
|
||||
if (!output || !output.Body) {
|
||||
return res.status(404).send({
|
||||
message: "Could not download the file.",
|
||||
message: 'Could not download the file.',
|
||||
});
|
||||
}
|
||||
|
||||
@ -637,14 +659,14 @@ const downloadS3 = async (req, res) => {
|
||||
message: `Could not download the file. ${error.message || error}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const deleteGCloud = async (privateUrl) => {
|
||||
try {
|
||||
const {hash, bucket} = initGCloud();
|
||||
const { hash, bucket } = initGCloud();
|
||||
const filePath = `${hash}/${privateUrl}`;
|
||||
|
||||
const file = bucket.file(filePath)
|
||||
const file = bucket.file(filePath);
|
||||
const fileExists = await file.exists();
|
||||
|
||||
if (fileExists[0]) {
|
||||
@ -653,7 +675,7 @@ const deleteGCloud = async (privateUrl) => {
|
||||
} catch (err) {
|
||||
console.log(`Cannot find the file ${privateUrl}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const deleteS3 = async (privateUrl) => {
|
||||
try {
|
||||
@ -673,7 +695,7 @@ const deleteS3 = async (privateUrl) => {
|
||||
console.error(`Cannot delete S3 file ${privateUrl}`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const uploadStreamToGCloud = async (privateUrl, sourcePath) => {
|
||||
const { hash, bucket } = initGCloud();
|
||||
@ -686,7 +708,7 @@ const uploadStreamToGCloud = async (privateUrl, sourcePath) => {
|
||||
);
|
||||
|
||||
return format(`https://storage.googleapis.com/${bucket.name}/${blob.name}`);
|
||||
}
|
||||
};
|
||||
|
||||
const initUploadSession = async (req, res) => {
|
||||
try {
|
||||
@ -755,9 +777,11 @@ const initUploadSession = async (req, res) => {
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize upload session', error);
|
||||
return res.status(500).send({ message: 'Failed to initialize upload session' });
|
||||
return res
|
||||
.status(500)
|
||||
.send({ message: 'Failed to initialize upload session' });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getUploadSession = async (req, res) => {
|
||||
try {
|
||||
@ -794,7 +818,7 @@ const getUploadSession = async (req, res) => {
|
||||
console.error('Failed to get upload session', error);
|
||||
return res.status(500).send({ message: 'Failed to get upload session' });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const uploadChunk = async (req, res) => {
|
||||
try {
|
||||
@ -818,7 +842,12 @@ const uploadChunk = async (req, res) => {
|
||||
s3Client = s3.client;
|
||||
s3Bucket = s3.bucket;
|
||||
s3Prefix = s3.prefix;
|
||||
session = await readS3SessionMeta(s3Client, s3Bucket, s3Prefix, sessionId);
|
||||
session = await readS3SessionMeta(
|
||||
s3Client,
|
||||
s3Bucket,
|
||||
s3Prefix,
|
||||
sessionId,
|
||||
);
|
||||
} else {
|
||||
session = readSessionMeta(sessionId);
|
||||
}
|
||||
@ -844,7 +873,14 @@ const uploadChunk = async (req, res) => {
|
||||
const chunkBuffer = Buffer.concat(chunks);
|
||||
|
||||
// Upload chunk directly to S3
|
||||
await uploadS3Chunk(s3Client, s3Bucket, s3Prefix, sessionId, chunkIndex, chunkBuffer);
|
||||
await uploadS3Chunk(
|
||||
s3Client,
|
||||
s3Bucket,
|
||||
s3Prefix,
|
||||
sessionId,
|
||||
chunkIndex,
|
||||
chunkBuffer,
|
||||
);
|
||||
} else {
|
||||
// Local storage - write to temp file then rename
|
||||
const chunkDir = getSessionChunksDir(sessionId);
|
||||
@ -869,7 +905,13 @@ const uploadChunk = async (req, res) => {
|
||||
session.updatedAt = new Date().toISOString();
|
||||
|
||||
if (provider === 's3') {
|
||||
await writeS3SessionMeta(s3Client, s3Bucket, s3Prefix, sessionId, session);
|
||||
await writeS3SessionMeta(
|
||||
s3Client,
|
||||
s3Bucket,
|
||||
s3Prefix,
|
||||
sessionId,
|
||||
session,
|
||||
);
|
||||
} else {
|
||||
writeSessionMeta(sessionId, session);
|
||||
}
|
||||
@ -884,7 +926,7 @@ const uploadChunk = async (req, res) => {
|
||||
console.error('Failed to upload chunk', error);
|
||||
return res.status(500).send({ message: 'Failed to upload chunk' });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const finalizeUploadSession = async (req, res) => {
|
||||
try {
|
||||
@ -903,7 +945,12 @@ const finalizeUploadSession = async (req, res) => {
|
||||
s3Bucket = s3.bucket;
|
||||
s3Prefix = s3.prefix;
|
||||
s3Region = s3.region;
|
||||
session = await readS3SessionMeta(s3Client, s3Bucket, s3Prefix, sessionId);
|
||||
session = await readS3SessionMeta(
|
||||
s3Client,
|
||||
s3Bucket,
|
||||
s3Prefix,
|
||||
sessionId,
|
||||
);
|
||||
} else {
|
||||
session = readSessionMeta(sessionId);
|
||||
}
|
||||
@ -921,7 +968,13 @@ const finalizeUploadSession = async (req, res) => {
|
||||
// Verify all chunks exist
|
||||
if (provider === 's3') {
|
||||
for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex += 1) {
|
||||
const exists = await s3ChunkExists(s3Client, s3Bucket, s3Prefix, sessionId, chunkIndex);
|
||||
const exists = await s3ChunkExists(
|
||||
s3Client,
|
||||
s3Bucket,
|
||||
s3Prefix,
|
||||
sessionId,
|
||||
chunkIndex,
|
||||
);
|
||||
if (!exists) {
|
||||
return res.status(400).send({
|
||||
message: `Missing chunk ${chunkIndex}`,
|
||||
@ -953,7 +1006,13 @@ const finalizeUploadSession = async (req, res) => {
|
||||
// Download and assemble chunks
|
||||
if (provider === 's3') {
|
||||
for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex += 1) {
|
||||
const chunkStream = await downloadS3Chunk(s3Client, s3Bucket, s3Prefix, sessionId, chunkIndex);
|
||||
const chunkStream = await downloadS3Chunk(
|
||||
s3Client,
|
||||
s3Bucket,
|
||||
s3Prefix,
|
||||
sessionId,
|
||||
chunkIndex,
|
||||
);
|
||||
if (!chunkStream) {
|
||||
// Cleanup and return error
|
||||
if (fs.existsSync(assembledPath)) fs.unlinkSync(assembledPath);
|
||||
@ -965,7 +1024,9 @@ const finalizeUploadSession = async (req, res) => {
|
||||
|
||||
// Write chunk to assembled file
|
||||
await new Promise((resolve, reject) => {
|
||||
const writeStream = fs.createWriteStream(assembledPath, { flags: 'a' });
|
||||
const writeStream = fs.createWriteStream(assembledPath, {
|
||||
flags: 'a',
|
||||
});
|
||||
writeStream.on('error', reject);
|
||||
writeStream.on('finish', resolve);
|
||||
|
||||
@ -973,7 +1034,8 @@ const finalizeUploadSession = async (req, res) => {
|
||||
chunkStream.on('error', reject);
|
||||
chunkStream.pipe(writeStream, { end: true });
|
||||
} else if (typeof chunkStream.transformToByteArray === 'function') {
|
||||
chunkStream.transformToByteArray()
|
||||
chunkStream
|
||||
.transformToByteArray()
|
||||
.then((bytes) => {
|
||||
writeStream.write(Buffer.from(bytes));
|
||||
writeStream.end();
|
||||
@ -993,7 +1055,10 @@ const finalizeUploadSession = async (req, res) => {
|
||||
}
|
||||
|
||||
const assembledStats = fs.statSync(assembledPath);
|
||||
if (Number.isFinite(Number(session.size)) && Number(session.size) !== assembledStats.size) {
|
||||
if (
|
||||
Number.isFinite(Number(session.size)) &&
|
||||
Number(session.size) !== assembledStats.size
|
||||
) {
|
||||
// Cleanup
|
||||
if (fs.existsSync(assembledPath)) fs.unlinkSync(assembledPath);
|
||||
return res.status(400).send({
|
||||
@ -1042,9 +1107,11 @@ const finalizeUploadSession = async (req, res) => {
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to finalize upload session', error);
|
||||
return res.status(500).send({ message: 'Failed to finalize upload session' });
|
||||
return res
|
||||
.status(500)
|
||||
.send({ message: 'Failed to finalize upload session' });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const uploadFile = async (folder, req, res) => {
|
||||
const provider = getFileStorageProvider();
|
||||
@ -1061,7 +1128,7 @@ const uploadFile = async (folder, req, res) => {
|
||||
entity: null,
|
||||
folderIncludesAuthenticationUid: false,
|
||||
})(req, res);
|
||||
}
|
||||
};
|
||||
|
||||
const downloadFile = async (req, res) => {
|
||||
const provider = getFileStorageProvider();
|
||||
@ -1075,7 +1142,7 @@ const downloadFile = async (req, res) => {
|
||||
}
|
||||
|
||||
return downloadLocal(req, res);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteFile = async (privateUrl) => {
|
||||
const provider = getFileStorageProvider();
|
||||
@ -1089,7 +1156,7 @@ const deleteFile = async (privateUrl) => {
|
||||
}
|
||||
|
||||
return deleteLocal(privateUrl);
|
||||
}
|
||||
};
|
||||
|
||||
const PRESIGN_EXPIRY_SECONDS = 3600; // 1 hour
|
||||
|
||||
@ -1123,7 +1190,7 @@ const generatePresignedUrls = async (urls) => {
|
||||
presignedUrls[url] = await getSignedUrl(client, command, {
|
||||
expiresIn: PRESIGN_EXPIRY_SECONDS,
|
||||
});
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
return presignedUrls;
|
||||
@ -1150,4 +1217,4 @@ module.exports = {
|
||||
uploadS3,
|
||||
downloadS3,
|
||||
generatePresignedUrls,
|
||||
}
|
||||
};
|
||||
|
||||
@ -8,8 +8,7 @@ module.exports = class ForbiddenError extends Error {
|
||||
message = getNotification(messageCode);
|
||||
}
|
||||
|
||||
message =
|
||||
message || getNotification('errors.forbidden.message');
|
||||
message = message || getNotification('errors.forbidden.message');
|
||||
|
||||
super(message);
|
||||
this.code = 403;
|
||||
|
||||
@ -8,9 +8,7 @@ module.exports = class ValidationError extends Error {
|
||||
message = getNotification(messageCode);
|
||||
}
|
||||
|
||||
message =
|
||||
message ||
|
||||
getNotification('errors.validation.message');
|
||||
message = message || getNotification('errors.validation.message');
|
||||
|
||||
super(message);
|
||||
this.code = 400;
|
||||
|
||||
@ -6,13 +6,8 @@ function format(message, args) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return message.replace(/{(\d+)}/g, function (
|
||||
match,
|
||||
number,
|
||||
) {
|
||||
return typeof args[number] != 'undefined'
|
||||
? args[number]
|
||||
: match;
|
||||
return message.replace(/{(\d+)}/g, function (match, number) {
|
||||
return typeof args[number] != 'undefined' ? args[number] : match;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -13,25 +13,22 @@ const errors = {
|
||||
emailAlreadyInUse: 'Email is already in use',
|
||||
invalidEmail: 'Please provide a valid email',
|
||||
passwordReset: {
|
||||
invalidToken:
|
||||
'Password reset link is invalid or has expired',
|
||||
invalidToken: 'Password reset link is invalid or has expired',
|
||||
error: `Email not recognized`,
|
||||
},
|
||||
passwordUpdate: {
|
||||
samePassword: `You can't use the same password. Please create new password`
|
||||
samePassword: `You can't use the same password. Please create new password`,
|
||||
},
|
||||
userNotVerified: `Sorry, your email has not been verified yet`,
|
||||
emailAddressVerificationEmail: {
|
||||
invalidToken:
|
||||
'Email verification link is invalid or has expired',
|
||||
invalidToken: 'Email verification link is invalid or has expired',
|
||||
error: `Email not recognized`,
|
||||
},
|
||||
},
|
||||
|
||||
iam: {
|
||||
errors: {
|
||||
userAlreadyExists:
|
||||
'User with this email already exists',
|
||||
userAlreadyExists: 'User with this email already exists',
|
||||
userNotFound: 'User not found',
|
||||
disablingHimself: `You can't disable yourself`,
|
||||
revokingOwnPermission: `You can't revoke your own owner permission`,
|
||||
@ -43,8 +40,7 @@ const errors = {
|
||||
importer: {
|
||||
errors: {
|
||||
invalidFileEmpty: 'The file is empty',
|
||||
invalidFileExcel:
|
||||
'Only excel (.xlsx) files are allowed',
|
||||
invalidFileExcel: 'Only excel (.xlsx) files are allowed',
|
||||
invalidFileUpload:
|
||||
'Invalid file. Make sure you are using the last version of the template.',
|
||||
importHashRequired: 'Import hash is required',
|
||||
@ -61,7 +57,7 @@ const errors = {
|
||||
message: 'An error occurred',
|
||||
},
|
||||
searchQueryRequired: {
|
||||
message: 'Search query is required',
|
||||
message: 'Search query is required',
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
@ -6,8 +6,13 @@ const loadRoleService = () => {
|
||||
try {
|
||||
return require('./roles');
|
||||
} catch (error) {
|
||||
console.error('Role service is missing. Advanced roles are required for this operation.', error);
|
||||
const err = new Error('Role service is missing. Advanced roles are required for this operation.');
|
||||
console.error(
|
||||
'Role service is missing. Advanced roles are required for this operation.',
|
||||
error,
|
||||
);
|
||||
const err = new Error(
|
||||
'Role service is missing. Advanced roles are required for this operation.',
|
||||
);
|
||||
err.originalError = error;
|
||||
throw err;
|
||||
}
|
||||
@ -35,7 +40,7 @@ module.exports = class OpenAiService {
|
||||
if (!prompt) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Prompt is required'
|
||||
error: 'Prompt is required',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,25 +1,18 @@
|
||||
const db = require('../db/models');
|
||||
const Project_audio_tracksDBApi = require('../db/api/project_audio_tracks');
|
||||
const processFile = require("../middlewares/upload");
|
||||
const processFile = require('../middlewares/upload');
|
||||
const ValidationError = require('./notifications/errors/validation');
|
||||
const csv = require('csv-parser');
|
||||
const stream = require('stream');
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
module.exports = class Project_audio_tracksService {
|
||||
static async create(data, currentUser) {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
try {
|
||||
const createdTrack = await Project_audio_tracksDBApi.create(
|
||||
data,
|
||||
{
|
||||
currentUser,
|
||||
transaction,
|
||||
},
|
||||
);
|
||||
const createdTrack = await Project_audio_tracksDBApi.create(data, {
|
||||
currentUser,
|
||||
transaction,
|
||||
});
|
||||
|
||||
await transaction.commit();
|
||||
return createdTrack;
|
||||
@ -37,7 +30,7 @@ module.exports = class Project_audio_tracksService {
|
||||
const bufferStream = new stream.PassThrough();
|
||||
const results = [];
|
||||
|
||||
await bufferStream.end(Buffer.from(req.file.buffer, "utf-8")); // convert Buffer to Stream
|
||||
await bufferStream.end(Buffer.from(req.file.buffer, 'utf-8')); // convert Buffer to Stream
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
bufferStream
|
||||
@ -48,13 +41,13 @@ module.exports = class Project_audio_tracksService {
|
||||
resolve();
|
||||
})
|
||||
.on('error', (error) => reject(error));
|
||||
})
|
||||
});
|
||||
|
||||
await Project_audio_tracksDBApi.bulkImport(results, {
|
||||
transaction,
|
||||
ignoreDuplicates: true,
|
||||
validate: true,
|
||||
currentUser: req.currentUser
|
||||
transaction,
|
||||
ignoreDuplicates: true,
|
||||
validate: true,
|
||||
currentUser: req.currentUser,
|
||||
});
|
||||
|
||||
await transaction.commit();
|
||||
@ -68,28 +61,22 @@ module.exports = class Project_audio_tracksService {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
try {
|
||||
let project_audio_tracks = await Project_audio_tracksDBApi.findBy(
|
||||
{id},
|
||||
{transaction},
|
||||
{ id },
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
if (!project_audio_tracks) {
|
||||
throw new ValidationError(
|
||||
'project_audio_tracksNotFound',
|
||||
);
|
||||
throw new ValidationError('project_audio_tracksNotFound');
|
||||
}
|
||||
|
||||
const updatedProject_audio_tracks = await Project_audio_tracksDBApi.update(
|
||||
id,
|
||||
data,
|
||||
{
|
||||
const updatedProject_audio_tracks =
|
||||
await Project_audio_tracksDBApi.update(id, data, {
|
||||
currentUser,
|
||||
transaction,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
await transaction.commit();
|
||||
return updatedProject_audio_tracks;
|
||||
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
@ -116,13 +103,10 @@ module.exports = class Project_audio_tracksService {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
|
||||
try {
|
||||
await Project_audio_tracksDBApi.remove(
|
||||
id,
|
||||
{
|
||||
currentUser,
|
||||
transaction,
|
||||
},
|
||||
);
|
||||
await Project_audio_tracksDBApi.remove(id, {
|
||||
currentUser,
|
||||
transaction,
|
||||
});
|
||||
|
||||
await transaction.commit();
|
||||
} catch (error) {
|
||||
@ -130,8 +114,4 @@ module.exports = class Project_audio_tracksService {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user