Initial version

This commit is contained in:
Flatlogic Bot 2025-09-18 02:43:49 +00:00
commit cf01a0e962
468 changed files with 87147 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
node_modules/
*/node_modules/
*/build/

187
502.html Normal file
View File

@ -0,0 +1,187 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Service Starting</title>
<style>
body {
font-family: sans-serif;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: #EFF2FF;
margin: 0;
padding: 20px;
}
.container {
text-align: center;
padding: 30px 40px;
background-color: #fff;
border-radius: 20px;
margin-bottom: 20px;
max-width: 538px;
width: 100%;
box-shadow: 0 13px 34px 0 rgba(167, 187, 242, 0.2);
box-sizing: border-box;
}
#status-heading {
font-size: 24px;
font-weight: 700;
color: #02004E;
margin-bottom: 20px;
}
h2 {
color: #333;
margin-bottom: 15px;
}
p {
color: #666;
font-size: 1.1em;
margin-bottom: 10px;
}
.tip {
font-weight: 300;
font-size: 17px;
line-height: 150%;
letter-spacing: 0;
text-align: center;
margin-top: 30px;
}
.loader-container {
position: relative;
display: flex;
justify-content: center;
align-items: center;
}
.loader {
width: 100px;
aspect-ratio: 1;
border-radius: 50%;
background:
radial-gradient(farthest-side, #5C7EF1 94%, #0000) top/8px 8px no-repeat,
conic-gradient(#0000 30%, #5C7EF1);
-webkit-mask: radial-gradient(farthest-side, #0000 calc(100% - 8px), #000 0);
animation: l13 2s infinite linear;
}
@keyframes l13 {
100% {
transform: rotate(1turn)
}
}
.app-logo {
position: absolute;
width: 36px;
}
.panel {
padding: 0 18px;
display: none;
background-color: white;
overflow: hidden;
margin-top: 10px;
}
.show {
display: block;
}
.project-info {
border: 1px solid #8C9DFF;
border-radius: 10px;
padding: 12px 16px;
max-width: 600px;
margin: 40px auto;
background-color: #FBFCFF;
}
.project-info h2 {
color: #02004E;
font-size: 14px;
font-weight: 500;
margin-bottom: 10px;
text-align: left;
}
.project-info p {
color: #686791;
font-size: 12px;
font-weight: 400;
text-align: left;
}
</style>
</head>
<body>
<div class="container">
<h2 id="status-heading">Loading the app, just a moment…</h2>
<p class="tip">The application is currently launching. The page will automatically refresh once site is
available.</p>
<div class="project-info">
<h2>I want a web app that imports Twitter DMs extract</h2>
<p>Web app for Twitter DMs and CRM sync.</p>
</div>
<div class="loader-container">
<img src="https://flatlogic.com/blog/wp-content/uploads/2025/05/logo-bot-1.png" alt="App Logo"
class="app-logo">
<div class="loader"></div>
</div>
<div class="panel">
<video width="100%" height="315" controls loop>
<source
src="https://flatlogic.com/blog/wp-content/uploads/2025/04/20250430_1336_professional_dynamo_spinner_simple_compose_01jt349yvtenxt7xhg8hhr85j8.mp4"
type="video/mp4">
Your browser does not support the video tag.
</video>
</div>
</div>
<script>
function checkAvailability() {
fetch('/')
.then(response => {
if (response.ok) {
window.location.reload();
} else {
setTimeout(checkAvailability, 5000);
}
})
.catch(() => {
setTimeout(checkAvailability, 5000);
});
}
document.addEventListener('DOMContentLoaded', checkAvailability);
document.addEventListener('DOMContentLoaded', function () {
const appTitle = document.querySelector('#status-heading');
const panel = document.querySelector('.panel');
const video = panel.querySelector('video');
let clickCount = 0;
appTitle.addEventListener('click', function () {
clickCount++;
if (clickCount === 5) {
panel.classList.toggle('show');
if (panel.classList.contains('show')) {
video.play();
} else {
video.pause();
}
clickCount = 0;
}
});
});
</script>
</body>
</html>

17
Dockerfile Normal file
View File

@ -0,0 +1,17 @@
FROM node:20.15.1-alpine AS builder
RUN apk add --no-cache git
WORKDIR /app
COPY frontend/package.json frontend/yarn.lock ./
RUN yarn install --pure-lockfile
COPY frontend .
RUN yarn build
FROM node:20.15.1-alpine
WORKDIR /app
COPY backend/package.json backend/yarn.lock ./
RUN yarn install --pure-lockfile
COPY backend .
COPY --from=builder /app/build /app/public
CMD ["yarn", "start"]

85
Dockerfile.dev Normal file
View File

@ -0,0 +1,85 @@
# Base image for Node.js dependencies
FROM node:20.15.1-alpine AS frontend-deps
RUN apk add --no-cache git
WORKDIR /app/frontend
COPY frontend/package.json frontend/yarn.lock ./
RUN yarn install --pure-lockfile
FROM node:20.15.1-alpine AS backend-deps
RUN apk add --no-cache git
WORKDIR /app/backend
COPY backend/package.json backend/yarn.lock ./
RUN yarn install --pure-lockfile
FROM node:20.15.1-alpine AS app-shell-deps
RUN apk add --no-cache git
WORKDIR /app/app-shell
COPY app-shell/package.json app-shell/yarn.lock ./
RUN yarn install --pure-lockfile
# Nginx setup and application build
FROM node:20.15.1-alpine AS build
RUN apk add --no-cache git nginx curl
RUN apk add --no-cache lsof procps
RUN yarn global add concurrently
RUN apk add --no-cache \
chromium \
nss \
freetype \
harfbuzz \
ttf-freefont \
fontconfig
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
RUN mkdir -p /app/pids
# Make sure to add yarn global bin to PATH
ENV PATH /root/.yarn/bin:/root/.config/yarn/global/node_modules/.bin:$PATH
# Copy dependencies
WORKDIR /app
COPY --from=frontend-deps /app/frontend /app/frontend
COPY --from=backend-deps /app/backend /app/backend
COPY --from=app-shell-deps /app/app-shell /app/app-shell
COPY frontend /app/frontend
COPY backend /app/backend
COPY app-shell /app/app-shell
COPY docker /app/docker
# Copy all files from root to /app
COPY . /app
# Copy Nginx configuration
COPY nginx.conf /etc/nginx/nginx.conf
# Copy custom error page
COPY 502.html /usr/share/nginx/html/502.html
# Change owner and permissions of the error page
RUN chown nginx:nginx /usr/share/nginx/html/502.html && \
chmod 644 /usr/share/nginx/html/502.html
# Expose the port the app runs on
EXPOSE 8080
ENV NODE_ENV=dev_stage
ENV FRONT_PORT=3001
ENV BACKEND_PORT=3000
ENV APP_SHELL_PORT=4000
CMD ["sh", "-c", "\
yarn --cwd /app/frontend dev & echo $! > /app/pids/frontend.pid && \
yarn --cwd /app/backend start & echo $! > /app/pids/backend.pid && \
sleep 10 && nginx -g 'daemon off;' & \
NGINX_PID=$! && \
echo 'Waiting for backend (port 3000) to be available...' && \
while ! nc -z localhost ${BACKEND_PORT}; do \
sleep 2; \
done && \
echo 'Backend is up. Starting app_shell for Git check...' && \
yarn --cwd /app/app-shell start && \
wait $NGINX_PID"]

1
LICENSE Normal file
View File

@ -0,0 +1 @@
https://flatlogic.com/

200
README.md Normal file
View File

@ -0,0 +1,200 @@
# I want a web app that imports Twitter DMs extract
## This project was generated by [Flatlogic Platform](https://flatlogic.com).
- Frontend: [React.js](https://flatlogic.com/templates?framework%5B%5D=react&sort=default)
- Backend: [NodeJS](https://flatlogic.com/templates?backend%5B%5D=nodejs&sort=default)
<details><summary>Backend Folder Structure</summary>
The generated application has the following backend folder structure:
`src` folder which contains your working files that will be used later to create the build. The src folder contains folders as:
- `auth` - config the library for authentication and authorization;
- `db` - contains such folders as:
- `api` - documentation that is automatically generated by jsdoc or other tools;
- `migrations` - is a skeleton of the database or all the actions that users do with the database;
- `models`- what will represent the database for the backend;
- `seeders` - the entity that creates the data for the database.
- `routes` - this folder would contain all the routes that you have created using Express Router and what they do would be exported from a Controller file;
- `services` - contains such folders as `emails` and `notifications`.
</details>
- Database: PostgreSQL
- app-shel: Core application framework that provides essential infrastructure services
for the entire application.
-----------------------
### We offer 2 ways how to start the project locally: by running Frontend and Backend or with Docker.
-----------------------
## To start the project:
### Backend:
> Please change current folder: `cd backend`
#### Install local dependencies:
`yarn install`
------------
#### Adjust local db:
##### 1. Install postgres:
MacOS:
`brew install postgres`
> if you dont have brew please install it (https://brew.sh) and repeat step `brew install postgres`.
Ubuntu:
`sudo apt update`
`sudo apt install postgresql postgresql-contrib`
##### 2. Create db and admin user:
Before run and test connection, make sure you have created a database as described in the above configuration. You can use the `psql` command to create a user and database.
`psql postgres --u postgres`
Next, type this command for creating a new user with password then give access for creating the database.
`postgres-# CREATE ROLE admin WITH LOGIN PASSWORD 'admin_pass';`
`postgres-# ALTER ROLE admin CREATEDB;`
Quit `psql` then log in again using the new user that previously created.
`postgres-# \q`
`psql postgres -U admin`
Type this command to creating a new database.
`postgres=> CREATE DATABASE db_{your_project_name};`
Then give that new user privileges to the new database then quit the `psql`.
`postgres=> GRANT ALL PRIVILEGES ON DATABASE db_{your_project_name} TO admin;`
`postgres=> \q`
------------
#### Create database:
`yarn db:create`
#### Start production build:
`yarn start`
### Frontend:
> Please change current folder: `cd frontend`
## To start the project with Docker:
### Description:
The project contains the **docker folder** and the `Dockerfile`.
The `Dockerfile` is used to Deploy the project to Google Cloud.
The **docker folder** contains a couple of helper scripts:
- `docker-compose.yml` (all our services: web, backend, db are described here)
- `start-backend.sh` (starts backend, but only after the database)
- `wait-for-it.sh` (imported from https://github.com/vishnubob/wait-for-it)
> To avoid breaking the application, we recommend you don't edit the following files: everything that includes the **docker folder** and `Dokerfile`.
## Run services:
1. Install docker compose (https://docs.docker.com/compose/install/)
2. Move to `docker` folder. All next steps should be done from this folder.
``` cd docker ```
3. Make executables from `wait-for-it.sh` and `start-backend.sh`:
``` chmod +x start-backend.sh && chmod +x wait-for-it.sh ```
4. Download dependend projects for services.
5. Review the docker-compose.yml file. Make sure that all services have Dockerfiles. Only db service doesn't require a Dockerfile.
6. Make sure you have needed ports (see them in `ports`) available on your local machine.
7. Start services:
7.1. With an empty database `rm -rf data && docker-compose up`
7.2. With a stored (from previus runs) database data `docker-compose up`
8. Check http://localhost:3000
9. Stop services:
9.1. Just press `Ctr+C`
## Most common errors:
1. `connection refused`
There could be many reasons, but the most common are:
- The port is not open on the destination machine.
- The port is open on the destination machine, but its backlog of pending connections is full.
- A firewall between the client and server is blocking access (also check local firewalls).
After checking for firewalls and that the port is open, use telnet to connect to the IP/port to test connectivity. This removes any potential issues from your application.
***MacOS:***
If you suspect that your SSH service might be down, you can run this command to find out:
`sudo service ssh status`
If the command line returns a status of down, then youve likely found the reason behind your connectivity error.
***Ubuntu:***
Sometimes a connection refused error can also indicate that there is an IP address conflict on your network. You can search for possible IP conflicts by running:
`arp-scan -I eth0 -l | grep <ipaddress>`
`arp-scan -I eth0 -l | grep <ipaddress>`
and
`arping <ipaddress>`
2. `yarn db:create` creates database with the assembled tables (on MacOS with Postgres database)
The workaround - put the next commands to your Postgres database terminal:
`DROP SCHEMA public CASCADE;`
`CREATE SCHEMA public;`
`GRANT ALL ON SCHEMA public TO postgres;`
`GRANT ALL ON SCHEMA public TO public;`
Afterwards, continue to start your project in the backend directory by running:
`yarn start`

26
app-shell/.eslintrc.cjs Normal file
View File

@ -0,0 +1,26 @@
const globals = require('globals');
module.exports = [
{
files: ['**/*.js', '**/*.ts', '**/*.tsx'],
languageOptions: {
ecmaVersion: 2021,
sourceType: 'module',
globals: {
...globals.browser,
...globals.node,
},
parser: '@typescript-eslint/parser',
},
plugins: ['@typescript-eslint'],
rules: {
'no-unused-vars': 'warn',
'no-console': 'off',
'indent': ['error', 2],
'quotes': ['error', 'single'],
'semi': ['error', 'always'],
'@typescript-eslint/no-unused-vars': 'warn',
},
},
];

11
app-shell/.prettierrc Normal file
View File

@ -0,0 +1,11 @@
{
"singleQuote": true,
"tabWidth": 2,
"printWidth": 80,
"trailingComma": "all",
"quoteProps": "as-needed",
"jsxSingleQuote": true,
"bracketSpacing": true,
"bracketSameLine": false,
"arrowParens": "always"
}

7
app-shell/.sequelizerc Normal file
View File

@ -0,0 +1,7 @@
const path = require('path');
module.exports = {
"config": path.resolve("src", "db", "db.config.js"),
"models-path": path.resolve("src", "db", "models"),
"seeders-path": path.resolve("src", "db", "seeders"),
"migrations-path": path.resolve("src", "db", "migrations")
};

23
app-shell/Dockerfile Normal file
View File

@ -0,0 +1,23 @@
FROM node:20.15.1-alpine
RUN apk update && apk add bash
# Create app directory
WORKDIR /usr/src/app
# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
COPY package*.json ./
RUN yarn install
# If you are building your code for production
# RUN npm ci --only=production
# Bundle app source
COPY . .
EXPOSE 4000
CMD [ "yarn", "start" ]

13
app-shell/README.md Normal file
View File

@ -0,0 +1,13 @@
#test - template backend,
#### Run App on local machine:
##### Install local dependencies:
- `yarn install`
---
##### Start build:
- `yarn start`

43
app-shell/package.json Normal file
View File

@ -0,0 +1,43 @@
{
"name": "app-shell",
"description": "app-shell",
"scripts": {
"start": "node ./src/index.js"
},
"dependencies": {
"@babel/parser": "^7.26.7",
"adm-zip": "^0.5.16",
"axios": "^1.6.7",
"bcrypt": "5.1.1",
"cors": "2.8.5",
"eslint": "^9.13.0",
"express": "4.18.2",
"formidable": "1.2.2",
"helmet": "4.1.1",
"json2csv": "^5.0.7",
"jsonwebtoken": "8.5.1",
"lodash": "4.17.21",
"moment": "2.30.1",
"multer": "^1.4.4",
"passport": "^0.7.0",
"passport-google-oauth2": "^0.2.0",
"passport-jwt": "^4.0.1",
"passport-microsoft": "^0.1.0",
"postcss": "^8.5.1",
"puppeteer": "^24.10.0",
"sequelize-json-schema": "^2.1.1",
"pg": "^8.13.3"
},
"engines": {
"node": ">=18"
},
"private": true,
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^8.12.2",
"@typescript-eslint/parser": "^8.12.2",
"cross-env": "7.0.3",
"mocha": "8.1.3",
"nodemon": "^3.1.7",
"sequelize-cli": "6.6.2"
}
}

File diff suppressed because one or more lines are too long

18
app-shell/src/config.js Normal file
View File

@ -0,0 +1,18 @@
const config = {
admin_pass: "bca3b053",
admin_email: "admin@flatlogic.com",
schema_encryption_key: process.env.SCHEMA_ENCRYPTION_KEY || '',
project_uuid: 'bca3b053-bda6-4b6f-8e5d-5c271be0bb9d',
flHost: process.env.NODE_ENV === 'production' ? 'https://flatlogic.com/projects' : 'http://localhost:3000/projects',
gitea_domain: process.env.GITEA_DOMAIN || 'gitea.flatlogic.app',
gitea_username: process.env.GITEA_USERNAME || 'admin',
gitea_api_token: process.env.GITEA_API_TOKEN || null,
github_repo_url: process.env.GITHUB_REPO_URL || null,
github_token: process.env.GITHUB_TOKEN || null,
};
module.exports = config;

23
app-shell/src/helpers.js Normal file
View File

@ -0,0 +1,23 @@
const jwt = require('jsonwebtoken');
const config = require('./config');
module.exports = class Helpers {
static wrapAsync(fn) {
return function (req, res, next) {
fn(req, res, next).catch(next);
};
}
static commonErrorHandler(error, req, res, next) {
if ([400, 403, 404].includes(error.code)) {
return res.status(error.code).send(error.message);
}
console.error(error);
return res.status(500).send(error.message);
}
static jwtSign(data) {
return jwt.sign(data, config.secret_key, { expiresIn: '6h' });
}
};

54
app-shell/src/index.js Normal file
View File

@ -0,0 +1,54 @@
const express = require('express');
const cors = require('cors');
const app = express();
const bodyParser = require('body-parser');
const checkPermissions = require('./middlewares/check-permissions');
const modifyPath = require('./middlewares/modify-path');
const VCS = require('./services/vcs');
const executorRoutes = require('./routes/executor');
const vcsRoutes = require('./routes/vcs');
// Function to initialize the Git repository
function initRepo() {
const projectId = '34182';
return VCS.initRepo(projectId);
}
// Start the Express app on APP_SHELL_PORT (4000)
function startServer() {
const PORT = 4000;
app.listen(PORT, () => {
console.log(`Listening on port ${PORT}`);
});
}
// Run Git check after the server is up
function runGitCheck() {
initRepo()
.then(result => {
console.log(result?.message ? result.message : result);
// Here you can add additional logic if needed
})
.catch(err => {
console.error('Error during repo initialization:', err);
// Optionally exit the process if Git check is critical:
// process.exit(1);
});
}
app.use(cors({ origin: true }));
app.use(bodyParser.json());
app.use(checkPermissions);
app.use(modifyPath);
app.use('/executor', executorRoutes);
app.use('/vcs', vcsRoutes);
// Start the app_shell server
startServer();
// Now perform Git check
runGitCheck();
module.exports = app;

View File

@ -0,0 +1,17 @@
const config = require('../config');
function checkPermissions(req, res, next) {
const project_uuid = config.project_uuid;
const requiredHeader = 'X-Project-UUID';
const headerValue = req.headers[requiredHeader.toLowerCase()];
// Logging whatever request we're getting
console.log('Request:', req.url, req.method, req.body, req.headers);
if (headerValue && headerValue === project_uuid) {
next();
} else {
res.status(403).send({ error: 'Stop right there, criminal scum! Your project UUID is invalid or missing.' });
}
}
module.exports = checkPermissions;

View File

@ -0,0 +1,8 @@
function modifyPath(req, res, next) {
if (req.body && req.body.path) {
req.body.path = '../../../' + req.body.path;
}
next();
}
module.exports = modifyPath;

View File

@ -0,0 +1,346 @@
const express = require('express');
const multer = require('multer');
const upload = multer({ dest: 'uploads/' });
const fs = require('fs');
const ExecutorService = require('../services/executor');
const { takeScreenshot } = require("../services/screenshot_service");
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
router.post(
'/read_project_tree',
wrapAsync(async (req, res) => {
const { path } = req.body;
const tree = await ExecutorService.readProjectTree(path);
res.status(200).send(tree);
}),
);
router.post(
'/read_file',
wrapAsync(async (req, res) => {
const { path, showLines } = req.body;
const content = await ExecutorService.readFileContents(path, showLines);
res.status(200).send(content);
}),
);
router.post(
'/count_file_lines',
wrapAsync(async (req, res) => {
const { path } = req.body;
const content = await ExecutorService.countFileLines(path);
res.status(200).send(content);
}),
);
// router.post(
// '/read_file_header',
// wrapAsync(async (req, res) => {
// const { path, N } = req.body;
// try {
// const header = await ExecutorService.readFileHeader(path, N);
// res.status(200).send(header);
// } catch (error) {
// res.status(500).send({
// error: true,
// message: error.message,
// details: error.details || error.stack,
// validation: error.validation
// });
// }
// }),
// );
router.post(
'/read_file_line_context',
wrapAsync(async (req, res) => {
const { path, lineNumber, windowSize, showLines } = req.body;
try {
const context = await ExecutorService.readFileLineContext(path, lineNumber, windowSize, showLines);
res.status(200).send(context);
} catch (error) {
res.status(500).send({
error: true,
message: error.message,
details: error.details || error.stack,
validation: error.validation
});
}
}),
);
router.post(
'/write_file',
wrapAsync(async (req, res) => {
const { path, fileContents, comment } = req.body;
try {
await ExecutorService.writeFile(path, fileContents, comment);
res.status(200).send({ message: 'File written successfully' });
} catch (error) {
res.status(500).send({
error: true,
message: error.message,
details: error.details || error.stack,
validation: error.validation
});
}
}),
);
router.post(
'/insert_file_content',
wrapAsync(async (req, res) => {
const { path, lineNumber, newContent, message } = req.body;
try {
await ExecutorService.insertFileContent(path, lineNumber, newContent, message);
res.status(200).send({ message: 'File written successfully' });
} catch (error) {
res.status(500).send({
error: true,
message: error.message,
details: error.details || error.stack,
validation: error.validation
});
}
}),
);
router.post(
'/replace_file_line',
wrapAsync(async (req, res) => {
const { path, lineNumber, newText } = req.body;
try {
const result = await ExecutorService.replaceFileLine(path, lineNumber, newText);
res.status(200).send(result);
} catch (error) {
res.status(500).send({
error: true,
message: error.message,
details: error.details || error.stack,
validation: error.validation
});
}
}),
);
router.post(
'/replace_file_chunk',
wrapAsync(async (req, res) => {
const { path, startLine, endLine, newCode } = req.body;
try {
const result = await ExecutorService.replaceFileChunk(path, startLine, endLine, newCode);
res.status(200).send(result);
} catch (error) {
res.status(500).send({
error: true,
message: error.message,
details: error.details || error.stack,
validation: error.validation
});
}
}),
);
router.post(
'/delete_file_lines',
wrapAsync(async (req, res) => {
const { path, startLine, endLine, message } = req.body;
try {
const result = await ExecutorService.deleteFileLines(path, startLine, endLine, message);
res.status(200).send(result);
} catch (error) {
res.status(500).send({
error: true,
message: error.message,
details: error.details || error.stack,
validation: error.validation
});
}
}),
);
router.post(
'/validate_file',
wrapAsync(async (req, res) => {
const { path } = req.body;
try {
const validationResult = await ExecutorService.validateFile(path);
res.status(200).send({ validationResult });
} catch (error) {
res.status(500).send({
error: true,
message: error.message,
details: error.details || error.stack,
validation: error.validation
});
}
}),
);
router.post(
'/check_frontend_runtime_error',
wrapAsync(async (req, res) => {
try {
const result = await ExecutorService.checkFrontendRuntimeLogs();
res.status(200).send(result);
} catch (error) {
res.status(500).send({ error: error });
}
}),
);
router.post(
'/replace_code_block',
wrapAsync(async (req, res) => {
const {path, oldCode, newCode, message} = req.body;
try {
const response = await ExecutorService.replaceCodeBlock(path, oldCode, newCode, message);
res.status(200).send(response);
} catch (error) {
res.status(500).send({
error: true,
message: error.message,
details: error.details || error.stack,
validation: error.validation
})
}
})
)
router.post('/update_project_files_from_scheme',
upload.single('file'), // 'file' - name of the field in the form
async (req, res) => {
console.log('Request received');
console.log('Headers:', req.headers);
if (!req.file) {
return res.status(400).json({ error: 'No file uploaded' });
}
console.log('File info:', {
originalname: req.file.originalname,
path: req.file.path,
size: req.file.size,
mimetype: req.file.mimetype
});
try {
console.log('Starting update process...');
const result = await ExecutorService.updateProjectFilesFromScheme(req.file.path);
console.log('Update completed, result:', result);
console.log('Removing temp file...');
fs.unlinkSync(req.file.path);
console.log('Temp file removed');
console.log('Sending response...');
return res.json(result);
} catch (error) {
console.error('Error in route handler:', error);
if (req.file) {
try {
fs.unlinkSync(req.file.path);
console.log('Temp file removed after error');
} catch (unlinkError) {
console.error('Error removing temp file:', unlinkError);
}
}
console.error('Update project files error:', error);
return res.status(500).json({
error: error.message,
stack: process.env.NODE_ENV === 'development' ? error.stack : undefined
});
}
}
);
router.post(
'/get_db_schema',
wrapAsync(async (req, res) => {
try {
const jsonSchema = await ExecutorService.getDBSchema();
res.status(200).send({ jsonSchema });
} catch (error) {
res.status(500).send({ error: error });
}
}),
);
router.post(
'/execute_sql',
wrapAsync(async (req, res) => {
try {
const { query } = req.body;
const result = await ExecutorService.executeSQL(query);
res.status(200).send(result);
} catch (error) {
res.status(500).send({ error: error });
}
}),
);
router.post(
'/search_files',
wrapAsync(async (req, res) => {
try {
const { searchStrings } = req.body;
if (
typeof searchStrings !== 'string' &&
!(
Array.isArray(searchStrings) &&
searchStrings.every(item => typeof item === 'string')
)
) {
return res.status(400).send({ error: 'searchStrings must be a string or an array of strings' });
}
const result = await ExecutorService.searchFiles(searchStrings);
res.status(200).send(result);
} catch (error) {
res.status(500).send({ error: error.message });
}
}),
);
router.post(
'/screenshot',
wrapAsync(async (req, res) => {
const FRONT_PORT = process.env.FRONT_PORT || 3001;
const targetUrl = `http://localhost:${FRONT_PORT}${req.body.url || '/'}`;
const filename = req.query.filename || `screenshot-${Date.now()}.png`;
const fullPage = req.query.fullPage !== 'false';
try {
console.log(`[App-Shell/Screenshot Route]: request to take screenshot of ${targetUrl} with filename ${filename} and fullPage=${fullPage}`);
const outputPath = await takeScreenshot(targetUrl, filename, fullPage);
res.sendFile(outputPath, async (err) => {
if (err) {
console.error(`[App-Shell/Screenshot Route]: error sending screenshot ${outputPath}:`, err);
if (err.code === 'ENOENT') {
res.status(404).send('Screenshot not found.');
} else {
res.status(500).send('Error sending screenshot: ' + err.message);
}
} else {
console.log(`[App-Shell/Screenshot Route]: Screenshot sent successfully: ${outputPath}`);
}
});
} catch (error) {
console.error(`[App-Shell/Screenshot Route]: Could not take screenshot of ${targetUrl}:`, error);
res.status(500).send(`Error taking screenshot: ${error.message}`);
}
})
)
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,40 @@
const express = require('express');
const wrapAsync = require('../helpers').wrapAsync; // Ваша обёртка для обработки асинхронных маршрутов
const VSC = require('../services/vcs');
const router = express.Router();
router.post('/init', wrapAsync(async (req, res) => {
const result = await VSC.initRepo();
res.status(200).send(result);
}));
router.post('/commit', wrapAsync(async (req, res) => {
const { message, files, dev_schema } = req.body;
const result = await VSC.commitChanges(message, files, dev_schema);
res.status(200).send(result);
}));
router.post('/log', wrapAsync(async (req, res) => {
const result = await VSC.getLog();
res.status(200).send(result);
}));
router.post('/rollback', wrapAsync(async (req, res) => {
const { ref } = req.body;
// const result = await VSC.checkout(ref);
const result = await VSC.revert(ref);
res.status(200).send(result);
}));
router.post('/sync-to-stable', wrapAsync(async (req, res) => {
const result = await VSC.mergeDevIntoMaster();
res.status(200).send(result);
}));
router.post('/reset-dev', wrapAsync(async (req, res) => {
const result = await VSC.resetDevBranch();
res.status(200).send(result);
}));
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,88 @@
// Database.js
const { Client } = require('pg');
const config = require('../../../backend/src/db/db.config');
const env = process.env.NODE_ENV || 'development';
const dbConfig = config[env];
class Database {
constructor() {
this.client = new Client({
user: dbConfig.username,
password: dbConfig.password,
database: dbConfig.database,
host: dbConfig.host,
port: dbConfig.port
});
// Connect once, reuse the client
this.client.connect().catch(err => {
console.error('Error connecting to the database:', err);
throw err;
});
}
async executeSQL(query) {
try {
const result = await this.client.query(query);
return {
success: true,
rows: result.rows
};
} catch (error) {
console.error('Error executing query:', error);
throw error;
}
}
// Method to fetch simple table/column info from 'information_schema'
// (You can expand this to handle constraints, indexes, etc.)
async getDBSchema(schemaName = 'public') {
try {
const tableQuery = `
SELECT table_name
FROM information_schema.tables
WHERE table_schema = $1
AND table_type = 'BASE TABLE'
ORDER BY table_name
`;
const columnQuery = `
SELECT table_name, column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_schema = $1
ORDER BY table_name, ordinal_position
`;
const [tablesResult, columnsResult] = await Promise.all([
this.client.query(tableQuery, [schemaName]),
this.client.query(columnQuery, [schemaName]),
]);
// Build a simple schema object:
const tables = tablesResult.rows.map(row => row.table_name);
const columnsByTable = {};
columnsResult.rows.forEach(row => {
const { table_name, column_name, data_type, is_nullable } = row;
if (!columnsByTable[table_name]) columnsByTable[table_name] = [];
columnsByTable[table_name].push({ column_name, data_type, is_nullable });
});
// Combine tables with their columns
return tables.map(table => ({
table,
columns: columnsByTable[table] || [],
}));
} catch (error) {
console.error('Error fetching schema:', error);
throw error;
}
}
async close() {
await this.client.end();
}
}
module.exports = new Database();

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,16 @@
const { getNotification, isNotification } = require('../helpers');
module.exports = class ForbiddenError extends Error {
constructor(messageCode) {
let message;
if (messageCode && isNotification(messageCode)) {
message = getNotification(messageCode);
}
message = message || getNotification('errors.forbidden.message');
super(message);
this.code = 403;
}
};

View File

@ -0,0 +1,16 @@
const { getNotification, isNotification } = require('../helpers');
module.exports = class ValidationError extends Error {
constructor(messageCode) {
let message;
if (messageCode && isNotification(messageCode)) {
message = getNotification(messageCode);
}
message = message || getNotification('errors.validation.message');
super(message);
this.code = 400;
}
};

View File

@ -0,0 +1,30 @@
const _get = require('lodash/get');
const errors = require('./list');
function format(message, args) {
if (!message) {
return null;
}
return message.replace(/{(\d+)}/g, function (match, number) {
return typeof args[number] != 'undefined' ? args[number] : match;
});
}
const isNotification = (key) => {
const message = _get(errors, key);
return !!message;
};
const getNotification = (key, ...args) => {
const message = _get(errors, key);
if (!message) {
return key;
}
return format(message, args);
};
exports.getNotification = getNotification;
exports.isNotification = isNotification;

View File

@ -0,0 +1,100 @@
const errors = {
app: {
title: 'test',
},
auth: {
userDisabled: 'Your account is disabled',
forbidden: 'Forbidden',
unauthorized: 'Unauthorized',
userNotFound: `Sorry, we don't recognize your credentials`,
wrongPassword: `Sorry, we don't recognize your credentials`,
weakPassword: 'This password is too weak',
emailAlreadyInUse: 'Email is already in use',
invalidEmail: 'Please provide a valid email',
passwordReset: {
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`,
},
userNotVerified: `Sorry, your email has not been verified yet`,
emailAddressVerificationEmail: {
invalidToken: 'Email verification link is invalid or has expired',
error: `Email not recognized`,
},
},
iam: {
errors: {
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`,
deletingHimself: `You can't delete yourself`,
emailRequired: 'Email is required',
},
},
importer: {
errors: {
invalidFileEmpty: 'The file is empty',
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',
importHashExistent: 'Data has already been imported',
userEmailMissing: 'Some items in the CSV do not have an email',
},
},
errors: {
forbidden: {
message: 'Forbidden',
},
validation: {
message: 'An error occurred',
},
searchQueryRequired: {
message: 'Search query is required',
},
},
emails: {
invitation: {
subject: `You've been invited to {0}`,
body: `
<p>Hello,</p>
<p>You've been invited to {0} set password for your {1} account.</p>
<p><a href='{2}'>{2}</a></p>
<p>Thanks,</p>
<p>Your {0} team</p>
`,
},
emailAddressVerification: {
subject: `Verify your email for {0}`,
body: `
<p>Hello,</p>
<p>Follow this link to verify your email address.</p>
<p><a href='{0}'>{0}</a></p>
<p>If you didn't ask to verify this address, you can ignore this email.</p>
<p>Thanks,</p>
<p>Your {1} team</p>
`,
},
passwordReset: {
subject: `Reset your password for {0}`,
body: `
<p>Hello,</p>
<p>Follow this link to reset your {0} password for your {1} account.</p>
<p><a href='{2}'>{2}</a></p>
<p>If you didn't ask to reset your password, you can ignore this email.</p>
<p>Thanks,</p>
<p>Your {0} team</p>
`,
},
},
};
module.exports = errors;

View File

@ -0,0 +1,67 @@
const axios = require('axios');
const config = require('../config.js');
class ProjectEventsService {
/**
* Sends a project event to the Rails backend
*
* @param {string} eventType - Type of the event
* @param {object} payload - Event payload data
* @param {object} options - Additional options
* @param {string} [options.conversationId] - Optional conversation ID
* @param {boolean} [options.isError=false] - Whether this is an error event
* @returns {Promise<object>} - Response from the webhook
*/
static async sendEvent(eventType, payload = {}, options = {}) {
try {
console.log(`[DEBUG] Sending project event: ${eventType}`);
const webhookUrl = `https://flatlogic.com/projects/events_webhook`;
// Prepare the event data
const eventData = {
project_uuid: config.project_uuid,
event_type: eventType,
payload: {
...payload,
message: `[APP] ${payload.message}`,
is_error: options.isError || false,
system_message: true,
is_command_info: true
}
};
// Add conversation ID if provided
if (options.conversationId) {
eventData.conversation_id = options.conversationId;
}
const headers = {
'Content-Type': 'application/json',
'x-project-uuid': config.project_uuid
};
console.log(`[DEBUG] Event data: ${JSON.stringify(eventData)}`);
const response = await axios.post(webhookUrl, eventData, { headers });
console.log(`[DEBUG] Event sent successfully, status: ${response.status}`);
return response.data;
} catch (error) {
console.error(`[ERROR] Failed to send project event: ${error.message}`);
if (error.response) {
console.error(`[ERROR] Response status: ${error.response.status}`);
console.error(`[ERROR] Response data: ${JSON.stringify(error.response.data)}`);
}
// Don't throw the error, just return a failed status
// This prevents errors in the event service from breaking app functionality
return {
success: false,
error: error.message
};
}
}
}
module.exports = ProjectEventsService;

View File

@ -0,0 +1,83 @@
const puppeteer = require('puppeteer');
const path = require('path');
const fs = require('fs/promises');
const config = require('../config');
const BACKEND_URL = process.env.BACKEND_URL || 'http://localhost:3000';
const SCREENSHOT_DIR = process.env.SCREENSHOT_DIR || '/tmp/screenshots';
fs.mkdir(SCREENSHOT_DIR, { recursive: true }).catch(console.error);
async function takeScreenshot(url, filename = `screenshot-${Date.now()}.png`, fullPage = true) {
let browser;
const response = await axios.post(
`${BACKEND_URL}/api/auth/signin/local`,
{ email: config.admin_email, password: config.admin_pass },
{
headers: {
'Content-Type': 'application/json',
},
}
);
const token = response.data;
const outputPath = path.join(SCREENSHOT_DIR, filename);
try {
browser = await puppeteer.launch({
headless: true,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-accelerated-2d-canvas',
'--disable-gpu',
'window-size=1920,1080'
]
});
const page = await browser.newPage();
await page.setViewport({ width: 1920, height: 1080 });
if (token) {
await page.setRequestInterception(true);
page.on('request', interceptedRequest => {
if (interceptedRequest.isInterceptResolutionHandled()) {
return;
}
const headers = interceptedRequest.headers();
if (!headers['authorization'] && !headers['Authorization']) {
headers['Authorization'] = `Bearer ${token}`;
}
interceptedRequest.continue({ headers });
});
page.on('requestfailed', request => {
console.error(`[ScreenshotService]: Request failed: ${request.url()} - ${request.failure().errorText}`);
});
}
await page.goto(url, { waitUntil: 'load', timeout: 60000 });
await page.screenshot({
path: outputPath,
fullPage: true,
});
console.log(`[ScreenshotService]: Screenshot saved to ${outputPath}`);
return outputPath;
} catch (error) {
console.error(`[ScreenshotService]: Error taking screenshot: ${error.message}`);
throw error;
} finally {
if (browser) {
await browser.close();
}
}
}
module.exports = { takeScreenshot };

File diff suppressed because it is too large Load Diff

3044
app-shell/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

11
backend/.prettierrc Normal file
View File

@ -0,0 +1,11 @@
{
"singleQuote": true,
"tabWidth": 2,
"printWidth": 80,
"trailingComma": "all",
"quoteProps": "as-needed",
"jsxSingleQuote": true,
"bracketSpacing": true,
"bracketSameLine": false,
"arrowParens": "always"
}

7
backend/.sequelizerc Normal file
View File

@ -0,0 +1,7 @@
const path = require('path');
module.exports = {
"config": path.resolve("src", "db", "db.config.js"),
"models-path": path.resolve("src", "db", "models"),
"seeders-path": path.resolve("src", "db", "seeders"),
"migrations-path": path.resolve("src", "db", "migrations")
};

23
backend/Dockerfile Normal file
View File

@ -0,0 +1,23 @@
FROM node:20.15.1-alpine
RUN apk update && apk add bash
# Create app directory
WORKDIR /usr/src/app
# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
COPY package*.json ./
RUN yarn install
# If you are building your code for production
# RUN npm ci --only=production
# Bundle app source
COPY . .
EXPOSE 8080
CMD [ "yarn", "start" ]

67
backend/README.md Normal file
View File

@ -0,0 +1,67 @@
#I want a web app that imports Twitter DMs extract - template backend,
#### Run App on local machine:
##### Install local dependencies:
- `yarn install`
---
##### Adjust local db:
###### 1. Install postgres:
- MacOS:
- `brew install postgres`
- Ubuntu:
- `sudo apt update`
- `sudo apt install postgresql postgresql-contrib`
###### 2. Create db and admin user:
- Before run and test connection, make sure you have created a database as described in the above configuration. You can use the `psql` command to create a user and database.
- `psql postgres --u postgres`
- Next, type this command for creating a new user with password then give access for creating the database.
- `postgres-# CREATE ROLE admin WITH LOGIN PASSWORD 'admin_pass';`
- `postgres-# ALTER ROLE admin CREATEDB;`
- Quit `psql` then log in again using the new user that previously created.
- `postgres-# \q`
- `psql postgres -U admin`
- Type this command to creating a new database.
- `postgres=> CREATE DATABASE db_i_want_a_web_app_that_imports_twitter_dms_extract;`
- Then give that new user privileges to the new database then quit the `psql`.
- `postgres=> GRANT ALL PRIVILEGES ON DATABASE db_i_want_a_web_app_that_imports_twitter_dms_extract TO admin;`
- `postgres=> \q`
---
#### Api Documentation (Swagger)
http://localhost:8080/api-docs (local host)
http://host_name/api-docs
---
##### Setup database tables or update after schema change
- `yarn db:migrate`
##### Seed the initial data (admin accounts, relevant for the first setup):
- `yarn db:seed`
##### Start build:
- `yarn start`

53
backend/package.json Normal file
View File

@ -0,0 +1,53 @@
{
"name": "iwantawebappthatimportstwitterdmsextract",
"description": "I want a web app that imports Twitter DMs extract - template backend",
"scripts": {
"start": "npm run db:migrate && npm run db:seed && npm run watch",
"db:migrate": "sequelize-cli db:migrate",
"db:seed": "sequelize-cli db:seed:all",
"db:drop": "sequelize-cli db:drop",
"db:create": "sequelize-cli db:create",
"watch": "node watcher.js"
},
"dependencies": {
"@google-cloud/storage": "^5.18.2",
"axios": "^1.6.7",
"bcrypt": "5.1.1",
"chokidar": "^4.0.3",
"cors": "2.8.5",
"csv-parser": "^3.0.0",
"express": "4.18.2",
"formidable": "1.2.2",
"helmet": "4.1.1",
"json2csv": "^5.0.7",
"jsonwebtoken": "8.5.1",
"lodash": "4.17.21",
"moment": "2.30.1",
"multer": "^1.4.4",
"mysql2": "2.2.5",
"nodemailer": "6.9.9",
"passport": "^0.7.0",
"passport-google-oauth2": "^0.2.0",
"passport-jwt": "^4.0.1",
"passport-microsoft": "^0.1.0",
"pg": "8.4.1",
"pg-hstore": "2.3.4",
"sequelize": "6.35.2",
"sequelize-json-schema": "^2.1.1",
"sqlite": "4.0.15",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.0",
"tedious": "^18.2.4"
},
"engines": {
"node": ">=18"
},
"private": true,
"devDependencies": {
"cross-env": "7.0.3",
"mocha": "8.1.3",
"node-mocks-http": "1.9.0",
"nodemon": "2.0.5",
"sequelize-cli": "6.6.2"
}
}

79
backend/src/auth/auth.js Normal file
View File

@ -0,0 +1,79 @@
const config = require('../config');
const providers = config.providers;
const helpers = require('../helpers');
const db = require('../db/models');
const passport = require('passport');
const JWTstrategy = require('passport-jwt').Strategy;
const ExtractJWT = require('passport-jwt').ExtractJwt;
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 });
if (user && user.disabled) {
return done(new Error(`User '${user.email}' is disabled`));
}
req.currentUser = user;
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 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, created]) => {
const body = {
id: user.id,
email: user.email,
name: profile.displayName,
};
const token = helpers.jwtSign({ user: body });
return done(null, { token });
});
}

77
backend/src/config.js Normal file
View File

@ -0,0 +1,77 @@
const os = require('os');
const config = {
gcloud: {
bucket: 'fldemo-files',
hash: '5fd0663bcf1d7fcacdefedb5f4f2f424',
},
bcrypt: {
saltRounds: 12,
},
admin_pass: 'bca3b053',
user_pass: '5c271be0bb9d',
admin_email: 'admin@flatlogic.com',
providers: {
LOCAL: 'local',
GOOGLE: 'google',
MICROSOFT: 'microsoft',
},
secret_key: process.env.SECRET_KEY || '',
remote: '',
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',
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 || '',
},
uploadDir: os.tmpdir(),
email: {
from: 'I want a web app that imports Twitter DMs extract <app@flatlogic.app>',
host: 'email-smtp.us-east-1.amazonaws.com',
port: 587,
auth: {
user: process.env.EMAIL_USER || '',
pass: process.env.EMAIL_PASS,
},
tls: {
rejectUnauthorized: false,
},
},
roles: {
super_admin: 'Super Administrator',
admin: 'Administrator',
user: 'Audit Viewer',
},
project_uuid: 'bca3b053-bda6-4b6f-8e5d-5c271be0bb9d',
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.pexelsKey = process.env.PEXELS_KEY || '';
config.pexelsQuery = 'Abstract digital network connections';
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}` : ``}/#`;
config.backUrl = `${config.hostUI}${config.portUI ? `:${config.portUI}` : ``}`;
module.exports = config;

View File

@ -0,0 +1,386 @@
const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class Contact_tagsDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const contact_tags = await db.contact_tags.create(
{
id: data.id || undefined,
importHash: data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
await contact_tags.setContact(data.contact || null, {
transaction,
});
await contact_tags.setTag(data.tag || null, {
transaction,
});
await contact_tags.setWorkspace(data.workspace || null, {
transaction,
});
return contact_tags;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const contact_tagsData = data.map((item, index) => ({
id: item.id || undefined,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
// Bulk create items
const contact_tags = await db.contact_tags.bulkCreate(contact_tagsData, {
transaction,
});
// For each item created, replace relation files
return contact_tags;
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess;
const contact_tags = await db.contact_tags.findByPk(
id,
{},
{ transaction },
);
const updatePayload = {};
updatePayload.updatedById = currentUser.id;
await contact_tags.update(updatePayload, { transaction });
if (data.contact !== undefined) {
await contact_tags.setContact(
data.contact,
{ transaction },
);
}
if (data.tag !== undefined) {
await contact_tags.setTag(
data.tag,
{ transaction },
);
}
if (data.workspace !== undefined) {
await contact_tags.setWorkspace(
data.workspace,
{ transaction },
);
}
return contact_tags;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const contact_tags = await db.contact_tags.findAll({
where: {
id: {
[Op.in]: ids,
},
},
transaction,
});
await db.sequelize.transaction(async (transaction) => {
for (const record of contact_tags) {
await record.update({ deletedBy: currentUser.id }, { transaction });
}
for (const record of contact_tags) {
await record.destroy({ transaction });
}
});
return contact_tags;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const contact_tags = await db.contact_tags.findByPk(id, options);
await contact_tags.update(
{
deletedBy: currentUser.id,
},
{
transaction,
},
);
await contact_tags.destroy({
transaction,
});
return contact_tags;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const contact_tags = await db.contact_tags.findOne(
{ where },
{ transaction },
);
if (!contact_tags) {
return contact_tags;
}
const output = contact_tags.get({ plain: true });
output.contact = await contact_tags.getContact({
transaction,
});
output.tag = await contact_tags.getTag({
transaction,
});
output.workspace = await contact_tags.getWorkspace({
transaction,
});
return output;
}
static async findAll(filter, globalAccess, options) {
const limit = filter.limit || 0;
let offset = 0;
let where = {};
const currentPage = +filter.page;
const user = (options && options.currentUser) || null;
const userWorkspace = (user && user.workspace?.id) || null;
if (userWorkspace) {
if (options?.currentUser?.workspaceId) {
where.workspaceId = options.currentUser.workspaceId;
}
}
offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [
{
model: db.contacts,
as: 'contact',
where: filter.contact
? {
[Op.or]: [
{
id: {
[Op.in]: filter.contact
.split('|')
.map((term) => Utils.uuid(term)),
},
},
{
primary_email: {
[Op.or]: filter.contact
.split('|')
.map((term) => ({ [Op.iLike]: `%${term}%` })),
},
},
],
}
: {},
},
{
model: db.tags,
as: 'tag',
where: filter.tag
? {
[Op.or]: [
{
id: {
[Op.in]: filter.tag
.split('|')
.map((term) => Utils.uuid(term)),
},
},
{
name: {
[Op.or]: filter.tag
.split('|')
.map((term) => ({ [Op.iLike]: `%${term}%` })),
},
},
],
}
: {},
},
{
model: db.workspace,
as: 'workspace',
},
];
if (filter) {
if (filter.id) {
where = {
...where,
['id']: Utils.uuid(filter.id),
};
}
if (filter.active !== undefined) {
where = {
...where,
active: filter.active === true || filter.active === 'true',
};
}
if (filter.workspace) {
const listItems = filter.workspace.split('|').map((item) => {
return Utils.uuid(item);
});
where = {
...where,
workspaceId: { [Op.or]: listItems },
};
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.lte]: end,
},
};
}
}
}
if (globalAccess) {
delete where.workspaceId;
}
const queryOptions = {
where,
include,
distinct: true,
order:
filter.field && filter.sort
? [[filter.field, filter.sort]]
: [['createdAt', 'desc']],
transaction: options?.transaction,
logging: console.log,
};
if (!options?.countOnly) {
queryOptions.limit = limit ? Number(limit) : undefined;
queryOptions.offset = offset ? Number(offset) : undefined;
}
try {
const { rows, count } = await db.contact_tags.findAndCountAll(
queryOptions,
);
return {
rows: options?.countOnly ? [] : rows,
count: count,
};
} catch (error) {
console.error('Error executing query:', error);
throw error;
}
}
static async findAllAutocomplete(
query,
limit,
offset,
globalAccess,
organizationId,
) {
let where = {};
if (!globalAccess && organizationId) {
where.organizationId = organizationId;
}
if (query) {
where = {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike('contact_tags', 'contact', query),
],
};
}
const records = await db.contact_tags.findAll({
attributes: ['id', 'contact'],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['contact', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.contact,
}));
}
};

View File

@ -0,0 +1,423 @@
const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class ContactsDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const contacts = await db.contacts.create(
{
id: data.id || undefined,
primary_email: data.primary_email || null,
category: data.category || null,
status: data.status || null,
importHash: data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
await contacts.setCounterparty(data.counterparty || null, {
transaction,
});
await contacts.setEnriched_profile(data.enriched_profile || null, {
transaction,
});
await contacts.setWorkspace(data.workspace || null, {
transaction,
});
return contacts;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const contactsData = data.map((item, index) => ({
id: item.id || undefined,
primary_email: item.primary_email || null,
category: item.category || null,
status: item.status || null,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
// Bulk create items
const contacts = await db.contacts.bulkCreate(contactsData, {
transaction,
});
// For each item created, replace relation files
return contacts;
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess;
const contacts = await db.contacts.findByPk(id, {}, { transaction });
const updatePayload = {};
if (data.primary_email !== undefined)
updatePayload.primary_email = data.primary_email;
if (data.category !== undefined) updatePayload.category = data.category;
if (data.status !== undefined) updatePayload.status = data.status;
updatePayload.updatedById = currentUser.id;
await contacts.update(updatePayload, { transaction });
if (data.counterparty !== undefined) {
await contacts.setCounterparty(
data.counterparty,
{ transaction },
);
}
if (data.enriched_profile !== undefined) {
await contacts.setEnriched_profile(
data.enriched_profile,
{ transaction },
);
}
if (data.workspace !== undefined) {
await contacts.setWorkspace(
data.workspace,
{ transaction },
);
}
return contacts;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const contacts = await db.contacts.findAll({
where: {
id: {
[Op.in]: ids,
},
},
transaction,
});
await db.sequelize.transaction(async (transaction) => {
for (const record of contacts) {
await record.update({ deletedBy: currentUser.id }, { transaction });
}
for (const record of contacts) {
await record.destroy({ transaction });
}
});
return contacts;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const contacts = await db.contacts.findByPk(id, options);
await contacts.update(
{
deletedBy: currentUser.id,
},
{
transaction,
},
);
await contacts.destroy({
transaction,
});
return contacts;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const contacts = await db.contacts.findOne({ where }, { transaction });
if (!contacts) {
return contacts;
}
const output = contacts.get({ plain: true });
output.contact_tags_contact = await contacts.getContact_tags_contact({
transaction,
});
output.crm_exports_contact = await contacts.getCrm_exports_contact({
transaction,
});
output.counterparty = await contacts.getCounterparty({
transaction,
});
output.enriched_profile = await contacts.getEnriched_profile({
transaction,
});
output.workspace = await contacts.getWorkspace({
transaction,
});
return output;
}
static async findAll(filter, globalAccess, options) {
const limit = filter.limit || 0;
let offset = 0;
let where = {};
const currentPage = +filter.page;
const user = (options && options.currentUser) || null;
const userWorkspace = (user && user.workspace?.id) || null;
if (userWorkspace) {
if (options?.currentUser?.workspaceId) {
where.workspaceId = options.currentUser.workspaceId;
}
}
offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [
{
model: db.counterparties,
as: 'counterparty',
where: filter.counterparty
? {
[Op.or]: [
{
id: {
[Op.in]: filter.counterparty
.split('|')
.map((term) => Utils.uuid(term)),
},
},
{
platform_handle: {
[Op.or]: filter.counterparty
.split('|')
.map((term) => ({ [Op.iLike]: `%${term}%` })),
},
},
],
}
: {},
},
{
model: db.enriched_profiles,
as: 'enriched_profile',
where: filter.enriched_profile
? {
[Op.or]: [
{
id: {
[Op.in]: filter.enriched_profile
.split('|')
.map((term) => Utils.uuid(term)),
},
},
{
full_name: {
[Op.or]: filter.enriched_profile
.split('|')
.map((term) => ({ [Op.iLike]: `%${term}%` })),
},
},
],
}
: {},
},
{
model: db.workspace,
as: 'workspace',
},
];
if (filter) {
if (filter.id) {
where = {
...where,
['id']: Utils.uuid(filter.id),
};
}
if (filter.primary_email) {
where = {
...where,
[Op.and]: Utils.ilike(
'contacts',
'primary_email',
filter.primary_email,
),
};
}
if (filter.active !== undefined) {
where = {
...where,
active: filter.active === true || filter.active === 'true',
};
}
if (filter.category) {
where = {
...where,
category: filter.category,
};
}
if (filter.status) {
where = {
...where,
status: filter.status,
};
}
if (filter.workspace) {
const listItems = filter.workspace.split('|').map((item) => {
return Utils.uuid(item);
});
where = {
...where,
workspaceId: { [Op.or]: listItems },
};
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.lte]: end,
},
};
}
}
}
if (globalAccess) {
delete where.workspaceId;
}
const queryOptions = {
where,
include,
distinct: true,
order:
filter.field && filter.sort
? [[filter.field, filter.sort]]
: [['createdAt', 'desc']],
transaction: options?.transaction,
logging: console.log,
};
if (!options?.countOnly) {
queryOptions.limit = limit ? Number(limit) : undefined;
queryOptions.offset = offset ? Number(offset) : undefined;
}
try {
const { rows, count } = await db.contacts.findAndCountAll(queryOptions);
return {
rows: options?.countOnly ? [] : rows,
count: count,
};
} catch (error) {
console.error('Error executing query:', error);
throw error;
}
}
static async findAllAutocomplete(
query,
limit,
offset,
globalAccess,
organizationId,
) {
let where = {};
if (!globalAccess && organizationId) {
where.organizationId = organizationId;
}
if (query) {
where = {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike('contacts', 'primary_email', query),
],
};
}
const records = await db.contacts.findAll({
attributes: ['id', 'primary_email'],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['primary_email', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.primary_email,
}));
}
};

View File

@ -0,0 +1,440 @@
const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class ConversationsDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const conversations = await db.conversations.create(
{
id: data.id || undefined,
platform_conversation_id: data.platform_conversation_id || null,
participants_json: data.participants_json || null,
last_message_at: data.last_message_at || null,
last_synced_at: data.last_synced_at || null,
importHash: data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
await conversations.setSource_account(data.source_account || null, {
transaction,
});
await conversations.setWorkspace(data.workspace || null, {
transaction,
});
return conversations;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const conversationsData = data.map((item, index) => ({
id: item.id || undefined,
platform_conversation_id: item.platform_conversation_id || null,
participants_json: item.participants_json || null,
last_message_at: item.last_message_at || null,
last_synced_at: item.last_synced_at || null,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
// Bulk create items
const conversations = await db.conversations.bulkCreate(conversationsData, {
transaction,
});
// For each item created, replace relation files
return conversations;
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess;
const conversations = await db.conversations.findByPk(
id,
{},
{ transaction },
);
const updatePayload = {};
if (data.platform_conversation_id !== undefined)
updatePayload.platform_conversation_id = data.platform_conversation_id;
if (data.participants_json !== undefined)
updatePayload.participants_json = data.participants_json;
if (data.last_message_at !== undefined)
updatePayload.last_message_at = data.last_message_at;
if (data.last_synced_at !== undefined)
updatePayload.last_synced_at = data.last_synced_at;
updatePayload.updatedById = currentUser.id;
await conversations.update(updatePayload, { transaction });
if (data.source_account !== undefined) {
await conversations.setSource_account(
data.source_account,
{ transaction },
);
}
if (data.workspace !== undefined) {
await conversations.setWorkspace(
data.workspace,
{ transaction },
);
}
return conversations;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const conversations = await db.conversations.findAll({
where: {
id: {
[Op.in]: ids,
},
},
transaction,
});
await db.sequelize.transaction(async (transaction) => {
for (const record of conversations) {
await record.update({ deletedBy: currentUser.id }, { transaction });
}
for (const record of conversations) {
await record.destroy({ transaction });
}
});
return conversations;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const conversations = await db.conversations.findByPk(id, options);
await conversations.update(
{
deletedBy: currentUser.id,
},
{
transaction,
},
);
await conversations.destroy({
transaction,
});
return conversations;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const conversations = await db.conversations.findOne(
{ where },
{ transaction },
);
if (!conversations) {
return conversations;
}
const output = conversations.get({ plain: true });
output.messages_conversation = await conversations.getMessages_conversation(
{
transaction,
},
);
output.source_account = await conversations.getSource_account({
transaction,
});
output.workspace = await conversations.getWorkspace({
transaction,
});
return output;
}
static async findAll(filter, globalAccess, options) {
const limit = filter.limit || 0;
let offset = 0;
let where = {};
const currentPage = +filter.page;
const user = (options && options.currentUser) || null;
const userWorkspace = (user && user.workspace?.id) || null;
if (userWorkspace) {
if (options?.currentUser?.workspaceId) {
where.workspaceId = options.currentUser.workspaceId;
}
}
offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [
{
model: db.source_accounts,
as: 'source_account',
where: filter.source_account
? {
[Op.or]: [
{
id: {
[Op.in]: filter.source_account
.split('|')
.map((term) => Utils.uuid(term)),
},
},
{
handle: {
[Op.or]: filter.source_account
.split('|')
.map((term) => ({ [Op.iLike]: `%${term}%` })),
},
},
],
}
: {},
},
{
model: db.workspace,
as: 'workspace',
},
];
if (filter) {
if (filter.id) {
where = {
...where,
['id']: Utils.uuid(filter.id),
};
}
if (filter.platform_conversation_id) {
where = {
...where,
[Op.and]: Utils.ilike(
'conversations',
'platform_conversation_id',
filter.platform_conversation_id,
),
};
}
if (filter.participants_json) {
where = {
...where,
[Op.and]: Utils.ilike(
'conversations',
'participants_json',
filter.participants_json,
),
};
}
if (filter.last_message_atRange) {
const [start, end] = filter.last_message_atRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
last_message_at: {
...where.last_message_at,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
last_message_at: {
...where.last_message_at,
[Op.lte]: end,
},
};
}
}
if (filter.last_synced_atRange) {
const [start, end] = filter.last_synced_atRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
last_synced_at: {
...where.last_synced_at,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
last_synced_at: {
...where.last_synced_at,
[Op.lte]: end,
},
};
}
}
if (filter.active !== undefined) {
where = {
...where,
active: filter.active === true || filter.active === 'true',
};
}
if (filter.workspace) {
const listItems = filter.workspace.split('|').map((item) => {
return Utils.uuid(item);
});
where = {
...where,
workspaceId: { [Op.or]: listItems },
};
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.lte]: end,
},
};
}
}
}
if (globalAccess) {
delete where.workspaceId;
}
const queryOptions = {
where,
include,
distinct: true,
order:
filter.field && filter.sort
? [[filter.field, filter.sort]]
: [['createdAt', 'desc']],
transaction: options?.transaction,
logging: console.log,
};
if (!options?.countOnly) {
queryOptions.limit = limit ? Number(limit) : undefined;
queryOptions.offset = offset ? Number(offset) : undefined;
}
try {
const { rows, count } = await db.conversations.findAndCountAll(
queryOptions,
);
return {
rows: options?.countOnly ? [] : rows,
count: count,
};
} catch (error) {
console.error('Error executing query:', error);
throw error;
}
}
static async findAllAutocomplete(
query,
limit,
offset,
globalAccess,
organizationId,
) {
let where = {};
if (!globalAccess && organizationId) {
where.organizationId = organizationId;
}
if (query) {
where = {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike('conversations', 'platform_conversation_id', query),
],
};
}
const records = await db.conversations.findAll({
attributes: ['id', 'platform_conversation_id'],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['platform_conversation_id', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.platform_conversation_id,
}));
}
};

View File

@ -0,0 +1,425 @@
const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class CounterpartiesDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const counterparties = await db.counterparties.create(
{
id: data.id || undefined,
platform_handle: data.platform_handle || null,
platform_user_id: data.platform_user_id || null,
profile_url: data.profile_url || null,
bio: data.bio || null,
avatar_url: data.avatar_url || null,
importHash: data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
await counterparties.setEnriched_profile(data.enriched_profile || null, {
transaction,
});
await counterparties.setWorkspace(data.workspace || null, {
transaction,
});
return counterparties;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const counterpartiesData = data.map((item, index) => ({
id: item.id || undefined,
platform_handle: item.platform_handle || null,
platform_user_id: item.platform_user_id || null,
profile_url: item.profile_url || null,
bio: item.bio || null,
avatar_url: item.avatar_url || null,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
// Bulk create items
const counterparties = await db.counterparties.bulkCreate(
counterpartiesData,
{ transaction },
);
// For each item created, replace relation files
return counterparties;
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess;
const counterparties = await db.counterparties.findByPk(
id,
{},
{ transaction },
);
const updatePayload = {};
if (data.platform_handle !== undefined)
updatePayload.platform_handle = data.platform_handle;
if (data.platform_user_id !== undefined)
updatePayload.platform_user_id = data.platform_user_id;
if (data.profile_url !== undefined)
updatePayload.profile_url = data.profile_url;
if (data.bio !== undefined) updatePayload.bio = data.bio;
if (data.avatar_url !== undefined)
updatePayload.avatar_url = data.avatar_url;
updatePayload.updatedById = currentUser.id;
await counterparties.update(updatePayload, { transaction });
if (data.enriched_profile !== undefined) {
await counterparties.setEnriched_profile(
data.enriched_profile,
{ transaction },
);
}
if (data.workspace !== undefined) {
await counterparties.setWorkspace(
data.workspace,
{ transaction },
);
}
return counterparties;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const counterparties = await db.counterparties.findAll({
where: {
id: {
[Op.in]: ids,
},
},
transaction,
});
await db.sequelize.transaction(async (transaction) => {
for (const record of counterparties) {
await record.update({ deletedBy: currentUser.id }, { transaction });
}
for (const record of counterparties) {
await record.destroy({ transaction });
}
});
return counterparties;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const counterparties = await db.counterparties.findByPk(id, options);
await counterparties.update(
{
deletedBy: currentUser.id,
},
{
transaction,
},
);
await counterparties.destroy({
transaction,
});
return counterparties;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const counterparties = await db.counterparties.findOne(
{ where },
{ transaction },
);
if (!counterparties) {
return counterparties;
}
const output = counterparties.get({ plain: true });
output.contacts_counterparty =
await counterparties.getContacts_counterparty({
transaction,
});
output.enriched_profile = await counterparties.getEnriched_profile({
transaction,
});
output.workspace = await counterparties.getWorkspace({
transaction,
});
return output;
}
static async findAll(filter, globalAccess, options) {
const limit = filter.limit || 0;
let offset = 0;
let where = {};
const currentPage = +filter.page;
const user = (options && options.currentUser) || null;
const userWorkspace = (user && user.workspace?.id) || null;
if (userWorkspace) {
if (options?.currentUser?.workspaceId) {
where.workspaceId = options.currentUser.workspaceId;
}
}
offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [
{
model: db.enriched_profiles,
as: 'enriched_profile',
where: filter.enriched_profile
? {
[Op.or]: [
{
id: {
[Op.in]: filter.enriched_profile
.split('|')
.map((term) => Utils.uuid(term)),
},
},
{
full_name: {
[Op.or]: filter.enriched_profile
.split('|')
.map((term) => ({ [Op.iLike]: `%${term}%` })),
},
},
],
}
: {},
},
{
model: db.workspace,
as: 'workspace',
},
];
if (filter) {
if (filter.id) {
where = {
...where,
['id']: Utils.uuid(filter.id),
};
}
if (filter.platform_handle) {
where = {
...where,
[Op.and]: Utils.ilike(
'counterparties',
'platform_handle',
filter.platform_handle,
),
};
}
if (filter.platform_user_id) {
where = {
...where,
[Op.and]: Utils.ilike(
'counterparties',
'platform_user_id',
filter.platform_user_id,
),
};
}
if (filter.profile_url) {
where = {
...where,
[Op.and]: Utils.ilike(
'counterparties',
'profile_url',
filter.profile_url,
),
};
}
if (filter.bio) {
where = {
...where,
[Op.and]: Utils.ilike('counterparties', 'bio', filter.bio),
};
}
if (filter.avatar_url) {
where = {
...where,
[Op.and]: Utils.ilike(
'counterparties',
'avatar_url',
filter.avatar_url,
),
};
}
if (filter.active !== undefined) {
where = {
...where,
active: filter.active === true || filter.active === 'true',
};
}
if (filter.workspace) {
const listItems = filter.workspace.split('|').map((item) => {
return Utils.uuid(item);
});
where = {
...where,
workspaceId: { [Op.or]: listItems },
};
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.lte]: end,
},
};
}
}
}
if (globalAccess) {
delete where.workspaceId;
}
const queryOptions = {
where,
include,
distinct: true,
order:
filter.field && filter.sort
? [[filter.field, filter.sort]]
: [['createdAt', 'desc']],
transaction: options?.transaction,
logging: console.log,
};
if (!options?.countOnly) {
queryOptions.limit = limit ? Number(limit) : undefined;
queryOptions.offset = offset ? Number(offset) : undefined;
}
try {
const { rows, count } = await db.counterparties.findAndCountAll(
queryOptions,
);
return {
rows: options?.countOnly ? [] : rows,
count: count,
};
} catch (error) {
console.error('Error executing query:', error);
throw error;
}
}
static async findAllAutocomplete(
query,
limit,
offset,
globalAccess,
organizationId,
) {
let where = {};
if (!globalAccess && organizationId) {
where.organizationId = organizationId;
}
if (query) {
where = {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike('counterparties', 'platform_handle', query),
],
};
}
const records = await db.counterparties.findAll({
attributes: ['id', 'platform_handle'],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['platform_handle', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.platform_handle,
}));
}
};

View File

@ -0,0 +1,440 @@
const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class Crm_exportsDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const crm_exports = await db.crm_exports.create(
{
id: data.id || undefined,
crm_system: data.crm_system || null,
external_id: data.external_id || null,
payload_json: data.payload_json || null,
status: data.status || null,
last_exported_at: data.last_exported_at || null,
error_message: data.error_message || null,
importHash: data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
await crm_exports.setContact(data.contact || null, {
transaction,
});
await crm_exports.setWorkspace(data.workspace || null, {
transaction,
});
return crm_exports;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const crm_exportsData = data.map((item, index) => ({
id: item.id || undefined,
crm_system: item.crm_system || null,
external_id: item.external_id || null,
payload_json: item.payload_json || null,
status: item.status || null,
last_exported_at: item.last_exported_at || null,
error_message: item.error_message || null,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
// Bulk create items
const crm_exports = await db.crm_exports.bulkCreate(crm_exportsData, {
transaction,
});
// For each item created, replace relation files
return crm_exports;
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess;
const crm_exports = await db.crm_exports.findByPk(id, {}, { transaction });
const updatePayload = {};
if (data.crm_system !== undefined)
updatePayload.crm_system = data.crm_system;
if (data.external_id !== undefined)
updatePayload.external_id = data.external_id;
if (data.payload_json !== undefined)
updatePayload.payload_json = data.payload_json;
if (data.status !== undefined) updatePayload.status = data.status;
if (data.last_exported_at !== undefined)
updatePayload.last_exported_at = data.last_exported_at;
if (data.error_message !== undefined)
updatePayload.error_message = data.error_message;
updatePayload.updatedById = currentUser.id;
await crm_exports.update(updatePayload, { transaction });
if (data.contact !== undefined) {
await crm_exports.setContact(
data.contact,
{ transaction },
);
}
if (data.workspace !== undefined) {
await crm_exports.setWorkspace(
data.workspace,
{ transaction },
);
}
return crm_exports;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const crm_exports = await db.crm_exports.findAll({
where: {
id: {
[Op.in]: ids,
},
},
transaction,
});
await db.sequelize.transaction(async (transaction) => {
for (const record of crm_exports) {
await record.update({ deletedBy: currentUser.id }, { transaction });
}
for (const record of crm_exports) {
await record.destroy({ transaction });
}
});
return crm_exports;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const crm_exports = await db.crm_exports.findByPk(id, options);
await crm_exports.update(
{
deletedBy: currentUser.id,
},
{
transaction,
},
);
await crm_exports.destroy({
transaction,
});
return crm_exports;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const crm_exports = await db.crm_exports.findOne(
{ where },
{ transaction },
);
if (!crm_exports) {
return crm_exports;
}
const output = crm_exports.get({ plain: true });
output.contact = await crm_exports.getContact({
transaction,
});
output.workspace = await crm_exports.getWorkspace({
transaction,
});
return output;
}
static async findAll(filter, globalAccess, options) {
const limit = filter.limit || 0;
let offset = 0;
let where = {};
const currentPage = +filter.page;
const user = (options && options.currentUser) || null;
const userWorkspace = (user && user.workspace?.id) || null;
if (userWorkspace) {
if (options?.currentUser?.workspaceId) {
where.workspaceId = options.currentUser.workspaceId;
}
}
offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [
{
model: db.contacts,
as: 'contact',
where: filter.contact
? {
[Op.or]: [
{
id: {
[Op.in]: filter.contact
.split('|')
.map((term) => Utils.uuid(term)),
},
},
{
primary_email: {
[Op.or]: filter.contact
.split('|')
.map((term) => ({ [Op.iLike]: `%${term}%` })),
},
},
],
}
: {},
},
{
model: db.workspace,
as: 'workspace',
},
];
if (filter) {
if (filter.id) {
where = {
...where,
['id']: Utils.uuid(filter.id),
};
}
if (filter.crm_system) {
where = {
...where,
[Op.and]: Utils.ilike('crm_exports', 'crm_system', filter.crm_system),
};
}
if (filter.external_id) {
where = {
...where,
[Op.and]: Utils.ilike(
'crm_exports',
'external_id',
filter.external_id,
),
};
}
if (filter.payload_json) {
where = {
...where,
[Op.and]: Utils.ilike(
'crm_exports',
'payload_json',
filter.payload_json,
),
};
}
if (filter.error_message) {
where = {
...where,
[Op.and]: Utils.ilike(
'crm_exports',
'error_message',
filter.error_message,
),
};
}
if (filter.last_exported_atRange) {
const [start, end] = filter.last_exported_atRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
last_exported_at: {
...where.last_exported_at,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
last_exported_at: {
...where.last_exported_at,
[Op.lte]: end,
},
};
}
}
if (filter.active !== undefined) {
where = {
...where,
active: filter.active === true || filter.active === 'true',
};
}
if (filter.status) {
where = {
...where,
status: filter.status,
};
}
if (filter.workspace) {
const listItems = filter.workspace.split('|').map((item) => {
return Utils.uuid(item);
});
where = {
...where,
workspaceId: { [Op.or]: listItems },
};
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.lte]: end,
},
};
}
}
}
if (globalAccess) {
delete where.workspaceId;
}
const queryOptions = {
where,
include,
distinct: true,
order:
filter.field && filter.sort
? [[filter.field, filter.sort]]
: [['createdAt', 'desc']],
transaction: options?.transaction,
logging: console.log,
};
if (!options?.countOnly) {
queryOptions.limit = limit ? Number(limit) : undefined;
queryOptions.offset = offset ? Number(offset) : undefined;
}
try {
const { rows, count } = await db.crm_exports.findAndCountAll(
queryOptions,
);
return {
rows: options?.countOnly ? [] : rows,
count: count,
};
} catch (error) {
console.error('Error executing query:', error);
throw error;
}
}
static async findAllAutocomplete(
query,
limit,
offset,
globalAccess,
organizationId,
) {
let where = {};
if (!globalAccess && organizationId) {
where.organizationId = organizationId;
}
if (query) {
where = {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike('crm_exports', 'crm_system', query),
],
};
}
const records = await db.crm_exports.findAll({
attributes: ['id', 'crm_system'],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['crm_system', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.crm_system,
}));
}
};

View File

@ -0,0 +1,498 @@
const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class Enriched_profilesDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const enriched_profiles = await db.enriched_profiles.create(
{
id: data.id || undefined,
full_name: data.full_name || null,
company: data.company || null,
role: data.role || null,
website: data.website || null,
emails_json: data.emails_json || null,
linkedin_url: data.linkedin_url || null,
twitter_url: data.twitter_url || null,
location: data.location || null,
confidence_score: data.confidence_score || null,
evidence_json: data.evidence_json || null,
last_enriched_at: data.last_enriched_at || null,
importHash: data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
await enriched_profiles.setWorkspace(data.workspace || null, {
transaction,
});
return enriched_profiles;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const enriched_profilesData = data.map((item, index) => ({
id: item.id || undefined,
full_name: item.full_name || null,
company: item.company || null,
role: item.role || null,
website: item.website || null,
emails_json: item.emails_json || null,
linkedin_url: item.linkedin_url || null,
twitter_url: item.twitter_url || null,
location: item.location || null,
confidence_score: item.confidence_score || null,
evidence_json: item.evidence_json || null,
last_enriched_at: item.last_enriched_at || null,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
// Bulk create items
const enriched_profiles = await db.enriched_profiles.bulkCreate(
enriched_profilesData,
{ transaction },
);
// For each item created, replace relation files
return enriched_profiles;
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess;
const enriched_profiles = await db.enriched_profiles.findByPk(
id,
{},
{ transaction },
);
const updatePayload = {};
if (data.full_name !== undefined) updatePayload.full_name = data.full_name;
if (data.company !== undefined) updatePayload.company = data.company;
if (data.role !== undefined) updatePayload.role = data.role;
if (data.website !== undefined) updatePayload.website = data.website;
if (data.emails_json !== undefined)
updatePayload.emails_json = data.emails_json;
if (data.linkedin_url !== undefined)
updatePayload.linkedin_url = data.linkedin_url;
if (data.twitter_url !== undefined)
updatePayload.twitter_url = data.twitter_url;
if (data.location !== undefined) updatePayload.location = data.location;
if (data.confidence_score !== undefined)
updatePayload.confidence_score = data.confidence_score;
if (data.evidence_json !== undefined)
updatePayload.evidence_json = data.evidence_json;
if (data.last_enriched_at !== undefined)
updatePayload.last_enriched_at = data.last_enriched_at;
updatePayload.updatedById = currentUser.id;
await enriched_profiles.update(updatePayload, { transaction });
if (data.workspace !== undefined) {
await enriched_profiles.setWorkspace(
data.workspace,
{ transaction },
);
}
return enriched_profiles;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const enriched_profiles = await db.enriched_profiles.findAll({
where: {
id: {
[Op.in]: ids,
},
},
transaction,
});
await db.sequelize.transaction(async (transaction) => {
for (const record of enriched_profiles) {
await record.update({ deletedBy: currentUser.id }, { transaction });
}
for (const record of enriched_profiles) {
await record.destroy({ transaction });
}
});
return enriched_profiles;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const enriched_profiles = await db.enriched_profiles.findByPk(id, options);
await enriched_profiles.update(
{
deletedBy: currentUser.id,
},
{
transaction,
},
);
await enriched_profiles.destroy({
transaction,
});
return enriched_profiles;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const enriched_profiles = await db.enriched_profiles.findOne(
{ where },
{ transaction },
);
if (!enriched_profiles) {
return enriched_profiles;
}
const output = enriched_profiles.get({ plain: true });
output.contacts_enriched_profile =
await enriched_profiles.getContacts_enriched_profile({
transaction,
});
output.counterparties_enriched_profile =
await enriched_profiles.getCounterparties_enriched_profile({
transaction,
});
output.workspace = await enriched_profiles.getWorkspace({
transaction,
});
return output;
}
static async findAll(filter, globalAccess, options) {
const limit = filter.limit || 0;
let offset = 0;
let where = {};
const currentPage = +filter.page;
const user = (options && options.currentUser) || null;
const userWorkspace = (user && user.workspace?.id) || null;
if (userWorkspace) {
if (options?.currentUser?.workspaceId) {
where.workspaceId = options.currentUser.workspaceId;
}
}
offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [
{
model: db.workspace,
as: 'workspace',
},
];
if (filter) {
if (filter.id) {
where = {
...where,
['id']: Utils.uuid(filter.id),
};
}
if (filter.full_name) {
where = {
...where,
[Op.and]: Utils.ilike(
'enriched_profiles',
'full_name',
filter.full_name,
),
};
}
if (filter.company) {
where = {
...where,
[Op.and]: Utils.ilike('enriched_profiles', 'company', filter.company),
};
}
if (filter.role) {
where = {
...where,
[Op.and]: Utils.ilike('enriched_profiles', 'role', filter.role),
};
}
if (filter.website) {
where = {
...where,
[Op.and]: Utils.ilike('enriched_profiles', 'website', filter.website),
};
}
if (filter.emails_json) {
where = {
...where,
[Op.and]: Utils.ilike(
'enriched_profiles',
'emails_json',
filter.emails_json,
),
};
}
if (filter.linkedin_url) {
where = {
...where,
[Op.and]: Utils.ilike(
'enriched_profiles',
'linkedin_url',
filter.linkedin_url,
),
};
}
if (filter.twitter_url) {
where = {
...where,
[Op.and]: Utils.ilike(
'enriched_profiles',
'twitter_url',
filter.twitter_url,
),
};
}
if (filter.location) {
where = {
...where,
[Op.and]: Utils.ilike(
'enriched_profiles',
'location',
filter.location,
),
};
}
if (filter.evidence_json) {
where = {
...where,
[Op.and]: Utils.ilike(
'enriched_profiles',
'evidence_json',
filter.evidence_json,
),
};
}
if (filter.confidence_scoreRange) {
const [start, end] = filter.confidence_scoreRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
confidence_score: {
...where.confidence_score,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
confidence_score: {
...where.confidence_score,
[Op.lte]: end,
},
};
}
}
if (filter.last_enriched_atRange) {
const [start, end] = filter.last_enriched_atRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
last_enriched_at: {
...where.last_enriched_at,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
last_enriched_at: {
...where.last_enriched_at,
[Op.lte]: end,
},
};
}
}
if (filter.active !== undefined) {
where = {
...where,
active: filter.active === true || filter.active === 'true',
};
}
if (filter.workspace) {
const listItems = filter.workspace.split('|').map((item) => {
return Utils.uuid(item);
});
where = {
...where,
workspaceId: { [Op.or]: listItems },
};
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.lte]: end,
},
};
}
}
}
if (globalAccess) {
delete where.workspaceId;
}
const queryOptions = {
where,
include,
distinct: true,
order:
filter.field && filter.sort
? [[filter.field, filter.sort]]
: [['createdAt', 'desc']],
transaction: options?.transaction,
logging: console.log,
};
if (!options?.countOnly) {
queryOptions.limit = limit ? Number(limit) : undefined;
queryOptions.offset = offset ? Number(offset) : undefined;
}
try {
const { rows, count } = await db.enriched_profiles.findAndCountAll(
queryOptions,
);
return {
rows: options?.countOnly ? [] : rows,
count: count,
};
} catch (error) {
console.error('Error executing query:', error);
throw error;
}
}
static async findAllAutocomplete(
query,
limit,
offset,
globalAccess,
organizationId,
) {
let where = {};
if (!globalAccess && organizationId) {
where.organizationId = organizationId;
}
if (query) {
where = {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike('enriched_profiles', 'full_name', query),
],
};
}
const records = await db.enriched_profiles.findAll({
attributes: ['id', 'full_name'],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['full_name', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.full_name,
}));
}
};

View File

@ -0,0 +1,73 @@
const db = require('../models');
const assert = require('assert');
const services = require('../../services/file');
module.exports = class FileDBApi {
static async replaceRelationFiles(relation, rawFiles, options) {
assert(relation.belongsTo, 'belongsTo is required');
assert(relation.belongsToColumn, 'belongsToColumn is required');
assert(relation.belongsToId, 'belongsToId is required');
let files = [];
if (Array.isArray(rawFiles)) {
files = rawFiles;
} else {
files = rawFiles ? [rawFiles] : [];
}
await this._removeLegacyFiles(relation, files, options);
await this._addFiles(relation, files, options);
}
static async _addFiles(relation, files, options) {
const transaction = (options && options.transaction) || undefined;
const currentUser = (options && options.currentUser) || { id: null };
const inexistentFiles = files.filter((file) => !!file.new);
for (const file of inexistentFiles) {
await db.file.create(
{
belongsTo: relation.belongsTo,
belongsToColumn: relation.belongsToColumn,
belongsToId: relation.belongsToId,
name: file.name,
sizeInBytes: file.sizeInBytes,
privateUrl: file.privateUrl,
publicUrl: file.publicUrl,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{
transaction,
},
);
}
}
static async _removeLegacyFiles(relation, files, options) {
const transaction = (options && options.transaction) || undefined;
const filesToDelete = await db.file.findAll({
where: {
belongsTo: relation.belongsTo,
belongsToId: relation.belongsToId,
belongsToColumn: relation.belongsToColumn,
id: {
[db.Sequelize.Op.notIn]: files
.filter((file) => !file.new)
.map((file) => file.id),
},
},
transaction,
});
for (let file of filesToDelete) {
await services.deleteGCloud(file.privateUrl);
await file.destroy({
transaction,
});
}
}
};

View File

@ -0,0 +1,454 @@
const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class MessagesDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const messages = await db.messages.create(
{
id: data.id || undefined,
platform_message_id: data.platform_message_id || null,
sender_handle: data.sender_handle || null,
sender_platform_id: data.sender_platform_id || null,
text: data.text || null,
attachments_json: data.attachments_json || null,
sent_at: data.sent_at || null,
raw_payload_json: data.raw_payload_json || null,
importHash: data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
await messages.setConversation(data.conversation || null, {
transaction,
});
await messages.setWorkspace(data.workspace || null, {
transaction,
});
return messages;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const messagesData = data.map((item, index) => ({
id: item.id || undefined,
platform_message_id: item.platform_message_id || null,
sender_handle: item.sender_handle || null,
sender_platform_id: item.sender_platform_id || null,
text: item.text || null,
attachments_json: item.attachments_json || null,
sent_at: item.sent_at || null,
raw_payload_json: item.raw_payload_json || null,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
// Bulk create items
const messages = await db.messages.bulkCreate(messagesData, {
transaction,
});
// For each item created, replace relation files
return messages;
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess;
const messages = await db.messages.findByPk(id, {}, { transaction });
const updatePayload = {};
if (data.platform_message_id !== undefined)
updatePayload.platform_message_id = data.platform_message_id;
if (data.sender_handle !== undefined)
updatePayload.sender_handle = data.sender_handle;
if (data.sender_platform_id !== undefined)
updatePayload.sender_platform_id = data.sender_platform_id;
if (data.text !== undefined) updatePayload.text = data.text;
if (data.attachments_json !== undefined)
updatePayload.attachments_json = data.attachments_json;
if (data.sent_at !== undefined) updatePayload.sent_at = data.sent_at;
if (data.raw_payload_json !== undefined)
updatePayload.raw_payload_json = data.raw_payload_json;
updatePayload.updatedById = currentUser.id;
await messages.update(updatePayload, { transaction });
if (data.conversation !== undefined) {
await messages.setConversation(
data.conversation,
{ transaction },
);
}
if (data.workspace !== undefined) {
await messages.setWorkspace(
data.workspace,
{ transaction },
);
}
return messages;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const messages = await db.messages.findAll({
where: {
id: {
[Op.in]: ids,
},
},
transaction,
});
await db.sequelize.transaction(async (transaction) => {
for (const record of messages) {
await record.update({ deletedBy: currentUser.id }, { transaction });
}
for (const record of messages) {
await record.destroy({ transaction });
}
});
return messages;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const messages = await db.messages.findByPk(id, options);
await messages.update(
{
deletedBy: currentUser.id,
},
{
transaction,
},
);
await messages.destroy({
transaction,
});
return messages;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const messages = await db.messages.findOne({ where }, { transaction });
if (!messages) {
return messages;
}
const output = messages.get({ plain: true });
output.conversation = await messages.getConversation({
transaction,
});
output.workspace = await messages.getWorkspace({
transaction,
});
return output;
}
static async findAll(filter, globalAccess, options) {
const limit = filter.limit || 0;
let offset = 0;
let where = {};
const currentPage = +filter.page;
const user = (options && options.currentUser) || null;
const userWorkspace = (user && user.workspace?.id) || null;
if (userWorkspace) {
if (options?.currentUser?.workspaceId) {
where.workspaceId = options.currentUser.workspaceId;
}
}
offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [
{
model: db.conversations,
as: 'conversation',
where: filter.conversation
? {
[Op.or]: [
{
id: {
[Op.in]: filter.conversation
.split('|')
.map((term) => Utils.uuid(term)),
},
},
{
platform_conversation_id: {
[Op.or]: filter.conversation
.split('|')
.map((term) => ({ [Op.iLike]: `%${term}%` })),
},
},
],
}
: {},
},
{
model: db.workspace,
as: 'workspace',
},
];
if (filter) {
if (filter.id) {
where = {
...where,
['id']: Utils.uuid(filter.id),
};
}
if (filter.platform_message_id) {
where = {
...where,
[Op.and]: Utils.ilike(
'messages',
'platform_message_id',
filter.platform_message_id,
),
};
}
if (filter.sender_handle) {
where = {
...where,
[Op.and]: Utils.ilike(
'messages',
'sender_handle',
filter.sender_handle,
),
};
}
if (filter.sender_platform_id) {
where = {
...where,
[Op.and]: Utils.ilike(
'messages',
'sender_platform_id',
filter.sender_platform_id,
),
};
}
if (filter.text) {
where = {
...where,
[Op.and]: Utils.ilike('messages', 'text', filter.text),
};
}
if (filter.attachments_json) {
where = {
...where,
[Op.and]: Utils.ilike(
'messages',
'attachments_json',
filter.attachments_json,
),
};
}
if (filter.raw_payload_json) {
where = {
...where,
[Op.and]: Utils.ilike(
'messages',
'raw_payload_json',
filter.raw_payload_json,
),
};
}
if (filter.sent_atRange) {
const [start, end] = filter.sent_atRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
sent_at: {
...where.sent_at,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
sent_at: {
...where.sent_at,
[Op.lte]: end,
},
};
}
}
if (filter.active !== undefined) {
where = {
...where,
active: filter.active === true || filter.active === 'true',
};
}
if (filter.workspace) {
const listItems = filter.workspace.split('|').map((item) => {
return Utils.uuid(item);
});
where = {
...where,
workspaceId: { [Op.or]: listItems },
};
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.lte]: end,
},
};
}
}
}
if (globalAccess) {
delete where.workspaceId;
}
const queryOptions = {
where,
include,
distinct: true,
order:
filter.field && filter.sort
? [[filter.field, filter.sort]]
: [['createdAt', 'desc']],
transaction: options?.transaction,
logging: console.log,
};
if (!options?.countOnly) {
queryOptions.limit = limit ? Number(limit) : undefined;
queryOptions.offset = offset ? Number(offset) : undefined;
}
try {
const { rows, count } = await db.messages.findAndCountAll(queryOptions);
return {
rows: options?.countOnly ? [] : rows,
count: count,
};
} catch (error) {
console.error('Error executing query:', error);
throw error;
}
}
static async findAllAutocomplete(
query,
limit,
offset,
globalAccess,
organizationId,
) {
let where = {};
if (!globalAccess && organizationId) {
where.organizationId = organizationId;
}
if (query) {
where = {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike('messages', 'text', query),
],
};
}
const records = await db.messages.findAll({
attributes: ['id', 'text'],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['text', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.text,
}));
}
};

View File

@ -0,0 +1,257 @@
const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class PermissionsDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const permissions = await db.permissions.create(
{
id: data.id || undefined,
name: data.name || null,
importHash: data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
return permissions;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const permissionsData = data.map((item, index) => ({
id: item.id || undefined,
name: item.name || null,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
// Bulk create items
const permissions = await db.permissions.bulkCreate(permissionsData, {
transaction,
});
// For each item created, replace relation files
return permissions;
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess;
const permissions = await db.permissions.findByPk(id, {}, { transaction });
const updatePayload = {};
if (data.name !== undefined) updatePayload.name = data.name;
updatePayload.updatedById = currentUser.id;
await permissions.update(updatePayload, { transaction });
return permissions;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const permissions = await db.permissions.findAll({
where: {
id: {
[Op.in]: ids,
},
},
transaction,
});
await db.sequelize.transaction(async (transaction) => {
for (const record of permissions) {
await record.update({ deletedBy: currentUser.id }, { transaction });
}
for (const record of permissions) {
await record.destroy({ transaction });
}
});
return permissions;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const permissions = await db.permissions.findByPk(id, options);
await permissions.update(
{
deletedBy: currentUser.id,
},
{
transaction,
},
);
await permissions.destroy({
transaction,
});
return permissions;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const permissions = await db.permissions.findOne(
{ where },
{ transaction },
);
if (!permissions) {
return permissions;
}
const output = permissions.get({ plain: true });
return output;
}
static async findAll(filter, options) {
const limit = filter.limit || 0;
let offset = 0;
let where = {};
const currentPage = +filter.page;
const user = (options && options.currentUser) || null;
const userWorkspace = (user && user.workspace?.id) || null;
offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [];
if (filter) {
if (filter.id) {
where = {
...where,
['id']: Utils.uuid(filter.id),
};
}
if (filter.name) {
where = {
...where,
[Op.and]: Utils.ilike('permissions', 'name', filter.name),
};
}
if (filter.active !== undefined) {
where = {
...where,
active: filter.active === true || filter.active === 'true',
};
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.lte]: end,
},
};
}
}
}
const queryOptions = {
where,
include,
distinct: true,
order:
filter.field && filter.sort
? [[filter.field, filter.sort]]
: [['createdAt', 'desc']],
transaction: options?.transaction,
logging: console.log,
};
if (!options?.countOnly) {
queryOptions.limit = limit ? Number(limit) : undefined;
queryOptions.offset = offset ? Number(offset) : undefined;
}
try {
const { rows, count } = await db.permissions.findAndCountAll(
queryOptions,
);
return {
rows: options?.countOnly ? [] : rows,
count: count,
};
} catch (error) {
console.error('Error executing query:', error);
throw error;
}
}
static async findAllAutocomplete(query, limit, offset) {
let where = {};
if (query) {
where = {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike('permissions', 'name', query),
],
};
}
const records = await db.permissions.findAll({
attributes: ['id', 'name'],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['name', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.name,
}));
}
};

344
backend/src/db/api/roles.js Normal file
View File

@ -0,0 +1,344 @@
const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils');
const config = require('../../config');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class RolesDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const roles = await db.roles.create(
{
id: data.id || undefined,
name: data.name || null,
role_customization: data.role_customization || null,
globalAccess: data.globalAccess || false,
importHash: data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
await roles.setPermissions(data.permissions || [], {
transaction,
});
return roles;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const rolesData = data.map((item, index) => ({
id: item.id || undefined,
name: item.name || null,
role_customization: item.role_customization || null,
globalAccess: item.globalAccess || false,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
// Bulk create items
const roles = await db.roles.bulkCreate(rolesData, { transaction });
// For each item created, replace relation files
return roles;
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess;
const roles = await db.roles.findByPk(id, {}, { transaction });
const updatePayload = {};
if (data.name !== undefined) updatePayload.name = data.name;
if (data.role_customization !== undefined)
updatePayload.role_customization = data.role_customization;
if (data.globalAccess !== undefined)
updatePayload.globalAccess = data.globalAccess;
updatePayload.updatedById = currentUser.id;
await roles.update(updatePayload, { transaction });
if (data.permissions !== undefined) {
await roles.setPermissions(data.permissions, { transaction });
}
return roles;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const roles = await db.roles.findAll({
where: {
id: {
[Op.in]: ids,
},
},
transaction,
});
await db.sequelize.transaction(async (transaction) => {
for (const record of roles) {
await record.update({ deletedBy: currentUser.id }, { transaction });
}
for (const record of roles) {
await record.destroy({ transaction });
}
});
return roles;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const roles = await db.roles.findByPk(id, options);
await roles.update(
{
deletedBy: currentUser.id,
},
{
transaction,
},
);
await roles.destroy({
transaction,
});
return roles;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const roles = await db.roles.findOne({ where }, { transaction });
if (!roles) {
return roles;
}
const output = roles.get({ plain: true });
output.users_app_role = await roles.getUsers_app_role({
transaction,
});
output.permissions = await roles.getPermissions({
transaction,
});
return output;
}
static async findAll(filter, globalAccess, options) {
const limit = filter.limit || 0;
let offset = 0;
let where = {};
const currentPage = +filter.page;
const user = (options && options.currentUser) || null;
const userWorkspace = (user && user.workspace?.id) || null;
offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [
{
model: db.permissions,
as: 'permissions',
required: false,
},
];
if (filter) {
if (filter.id) {
where = {
...where,
['id']: Utils.uuid(filter.id),
};
}
if (filter.name) {
where = {
...where,
[Op.and]: Utils.ilike('roles', 'name', filter.name),
};
}
if (filter.role_customization) {
where = {
...where,
[Op.and]: Utils.ilike(
'roles',
'role_customization',
filter.role_customization,
),
};
}
if (filter.active !== undefined) {
where = {
...where,
active: filter.active === true || filter.active === 'true',
};
}
if (filter.globalAccess) {
where = {
...where,
globalAccess: filter.globalAccess,
};
}
if (filter.permissions) {
const searchTerms = filter.permissions.split('|');
include = [
{
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}%`,
})),
},
},
],
}
: undefined,
},
...include,
];
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.lte]: end,
},
};
}
}
}
if (!globalAccess) {
where = { name: { [Op.ne]: config.roles.super_admin } };
}
const queryOptions = {
where,
include,
distinct: true,
order:
filter.field && filter.sort
? [[filter.field, filter.sort]]
: [['createdAt', 'desc']],
transaction: options?.transaction,
logging: console.log,
};
if (!options?.countOnly) {
queryOptions.limit = limit ? Number(limit) : undefined;
queryOptions.offset = offset ? Number(offset) : undefined;
}
try {
const { rows, count } = await db.roles.findAndCountAll(queryOptions);
return {
rows: options?.countOnly ? [] : rows,
count: count,
};
} catch (error) {
console.error('Error executing query:', error);
throw error;
}
}
static async findAllAutocomplete(query, limit, offset, globalAccess) {
let where = {};
if (!globalAccess) {
where = { name: { [Op.ne]: config.roles.super_admin } };
}
if (query) {
where = {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike('roles', 'name', query),
],
};
}
const records = await db.roles.findAll({
attributes: ['id', 'name'],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['name', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.name,
}));
}
};

View File

@ -0,0 +1,402 @@
const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class Source_accountsDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const source_accounts = await db.source_accounts.create(
{
id: data.id || undefined,
platform: data.platform || null,
handle: data.handle || null,
oauth_access_token: data.oauth_access_token || null,
oauth_refresh_token: data.oauth_refresh_token || null,
token_expires_at: data.token_expires_at || null,
status: data.status || null,
importHash: data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
await source_accounts.setWorkspace(data.workspace || null, {
transaction,
});
return source_accounts;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const source_accountsData = data.map((item, index) => ({
id: item.id || undefined,
platform: item.platform || null,
handle: item.handle || null,
oauth_access_token: item.oauth_access_token || null,
oauth_refresh_token: item.oauth_refresh_token || null,
token_expires_at: item.token_expires_at || null,
status: item.status || null,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
// Bulk create items
const source_accounts = await db.source_accounts.bulkCreate(
source_accountsData,
{ transaction },
);
// For each item created, replace relation files
return source_accounts;
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess;
const source_accounts = await db.source_accounts.findByPk(
id,
{},
{ transaction },
);
const updatePayload = {};
if (data.platform !== undefined) updatePayload.platform = data.platform;
if (data.handle !== undefined) updatePayload.handle = data.handle;
if (data.oauth_access_token !== undefined)
updatePayload.oauth_access_token = data.oauth_access_token;
if (data.oauth_refresh_token !== undefined)
updatePayload.oauth_refresh_token = data.oauth_refresh_token;
if (data.token_expires_at !== undefined)
updatePayload.token_expires_at = data.token_expires_at;
if (data.status !== undefined) updatePayload.status = data.status;
updatePayload.updatedById = currentUser.id;
await source_accounts.update(updatePayload, { transaction });
if (data.workspace !== undefined) {
await source_accounts.setWorkspace(
data.workspace,
{ transaction },
);
}
return source_accounts;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const source_accounts = await db.source_accounts.findAll({
where: {
id: {
[Op.in]: ids,
},
},
transaction,
});
await db.sequelize.transaction(async (transaction) => {
for (const record of source_accounts) {
await record.update({ deletedBy: currentUser.id }, { transaction });
}
for (const record of source_accounts) {
await record.destroy({ transaction });
}
});
return source_accounts;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const source_accounts = await db.source_accounts.findByPk(id, options);
await source_accounts.update(
{
deletedBy: currentUser.id,
},
{
transaction,
},
);
await source_accounts.destroy({
transaction,
});
return source_accounts;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const source_accounts = await db.source_accounts.findOne(
{ where },
{ transaction },
);
if (!source_accounts) {
return source_accounts;
}
const output = source_accounts.get({ plain: true });
output.conversations_source_account =
await source_accounts.getConversations_source_account({
transaction,
});
output.workspace = await source_accounts.getWorkspace({
transaction,
});
return output;
}
static async findAll(filter, globalAccess, options) {
const limit = filter.limit || 0;
let offset = 0;
let where = {};
const currentPage = +filter.page;
const user = (options && options.currentUser) || null;
const userWorkspace = (user && user.workspace?.id) || null;
if (userWorkspace) {
if (options?.currentUser?.workspaceId) {
where.workspaceId = options.currentUser.workspaceId;
}
}
offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [
{
model: db.workspace,
as: 'workspace',
},
];
if (filter) {
if (filter.id) {
where = {
...where,
['id']: Utils.uuid(filter.id),
};
}
if (filter.platform) {
where = {
...where,
[Op.and]: Utils.ilike('source_accounts', 'platform', filter.platform),
};
}
if (filter.handle) {
where = {
...where,
[Op.and]: Utils.ilike('source_accounts', 'handle', filter.handle),
};
}
if (filter.oauth_access_token) {
where = {
...where,
[Op.and]: Utils.ilike(
'source_accounts',
'oauth_access_token',
filter.oauth_access_token,
),
};
}
if (filter.oauth_refresh_token) {
where = {
...where,
[Op.and]: Utils.ilike(
'source_accounts',
'oauth_refresh_token',
filter.oauth_refresh_token,
),
};
}
if (filter.status) {
where = {
...where,
[Op.and]: Utils.ilike('source_accounts', 'status', filter.status),
};
}
if (filter.token_expires_atRange) {
const [start, end] = filter.token_expires_atRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
token_expires_at: {
...where.token_expires_at,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
token_expires_at: {
...where.token_expires_at,
[Op.lte]: end,
},
};
}
}
if (filter.active !== undefined) {
where = {
...where,
active: filter.active === true || filter.active === 'true',
};
}
if (filter.workspace) {
const listItems = filter.workspace.split('|').map((item) => {
return Utils.uuid(item);
});
where = {
...where,
workspaceId: { [Op.or]: listItems },
};
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.lte]: end,
},
};
}
}
}
if (globalAccess) {
delete where.workspaceId;
}
const queryOptions = {
where,
include,
distinct: true,
order:
filter.field && filter.sort
? [[filter.field, filter.sort]]
: [['createdAt', 'desc']],
transaction: options?.transaction,
logging: console.log,
};
if (!options?.countOnly) {
queryOptions.limit = limit ? Number(limit) : undefined;
queryOptions.offset = offset ? Number(offset) : undefined;
}
try {
const { rows, count } = await db.source_accounts.findAndCountAll(
queryOptions,
);
return {
rows: options?.countOnly ? [] : rows,
count: count,
};
} catch (error) {
console.error('Error executing query:', error);
throw error;
}
}
static async findAllAutocomplete(
query,
limit,
offset,
globalAccess,
organizationId,
) {
let where = {};
if (!globalAccess && organizationId) {
where.organizationId = organizationId;
}
if (query) {
where = {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike('source_accounts', 'handle', query),
],
};
}
const records = await db.source_accounts.findAll({
attributes: ['id', 'handle'],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['handle', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.handle,
}));
}
};

318
backend/src/db/api/tags.js Normal file
View File

@ -0,0 +1,318 @@
const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class TagsDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const tags = await db.tags.create(
{
id: data.id || undefined,
name: data.name || null,
description: data.description || null,
importHash: data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
await tags.setWorkspace(data.workspace || null, {
transaction,
});
return tags;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const tagsData = data.map((item, index) => ({
id: item.id || undefined,
name: item.name || null,
description: item.description || null,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
// Bulk create items
const tags = await db.tags.bulkCreate(tagsData, { transaction });
// For each item created, replace relation files
return tags;
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess;
const tags = await db.tags.findByPk(id, {}, { transaction });
const updatePayload = {};
if (data.name !== undefined) updatePayload.name = data.name;
if (data.description !== undefined)
updatePayload.description = data.description;
updatePayload.updatedById = currentUser.id;
await tags.update(updatePayload, { transaction });
if (data.workspace !== undefined) {
await tags.setWorkspace(
data.workspace,
{ transaction },
);
}
return tags;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const tags = await db.tags.findAll({
where: {
id: {
[Op.in]: ids,
},
},
transaction,
});
await db.sequelize.transaction(async (transaction) => {
for (const record of tags) {
await record.update({ deletedBy: currentUser.id }, { transaction });
}
for (const record of tags) {
await record.destroy({ transaction });
}
});
return tags;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const tags = await db.tags.findByPk(id, options);
await tags.update(
{
deletedBy: currentUser.id,
},
{
transaction,
},
);
await tags.destroy({
transaction,
});
return tags;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const tags = await db.tags.findOne({ where }, { transaction });
if (!tags) {
return tags;
}
const output = tags.get({ plain: true });
output.contact_tags_tag = await tags.getContact_tags_tag({
transaction,
});
output.workspace = await tags.getWorkspace({
transaction,
});
return output;
}
static async findAll(filter, globalAccess, options) {
const limit = filter.limit || 0;
let offset = 0;
let where = {};
const currentPage = +filter.page;
const user = (options && options.currentUser) || null;
const userWorkspace = (user && user.workspace?.id) || null;
if (userWorkspace) {
if (options?.currentUser?.workspaceId) {
where.workspaceId = options.currentUser.workspaceId;
}
}
offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [
{
model: db.workspace,
as: 'workspace',
},
];
if (filter) {
if (filter.id) {
where = {
...where,
['id']: Utils.uuid(filter.id),
};
}
if (filter.name) {
where = {
...where,
[Op.and]: Utils.ilike('tags', 'name', filter.name),
};
}
if (filter.description) {
where = {
...where,
[Op.and]: Utils.ilike('tags', 'description', filter.description),
};
}
if (filter.active !== undefined) {
where = {
...where,
active: filter.active === true || filter.active === 'true',
};
}
if (filter.workspace) {
const listItems = filter.workspace.split('|').map((item) => {
return Utils.uuid(item);
});
where = {
...where,
workspaceId: { [Op.or]: listItems },
};
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.lte]: end,
},
};
}
}
}
if (globalAccess) {
delete where.workspaceId;
}
const queryOptions = {
where,
include,
distinct: true,
order:
filter.field && filter.sort
? [[filter.field, filter.sort]]
: [['createdAt', 'desc']],
transaction: options?.transaction,
logging: console.log,
};
if (!options?.countOnly) {
queryOptions.limit = limit ? Number(limit) : undefined;
queryOptions.offset = offset ? Number(offset) : undefined;
}
try {
const { rows, count } = await db.tags.findAndCountAll(queryOptions);
return {
rows: options?.countOnly ? [] : rows,
count: count,
};
} catch (error) {
console.error('Error executing query:', error);
throw error;
}
}
static async findAllAutocomplete(
query,
limit,
offset,
globalAccess,
organizationId,
) {
let where = {};
if (!globalAccess && organizationId) {
where.organizationId = organizationId;
}
if (query) {
where = {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike('tags', 'name', query),
],
};
}
const records = await db.tags.findAll({
attributes: ['id', 'name'],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['name', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.name,
}));
}
};

View File

@ -0,0 +1,419 @@
const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class Task_runsDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const task_runs = await db.task_runs.create(
{
id: data.id || undefined,
task_type: data.task_type || null,
started_at: data.started_at || null,
finished_at: data.finished_at || null,
status: data.status || null,
stats_json: data.stats_json || null,
error_message: data.error_message || null,
importHash: data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
await task_runs.setWorkspace(data.workspace || null, {
transaction,
});
return task_runs;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const task_runsData = data.map((item, index) => ({
id: item.id || undefined,
task_type: item.task_type || null,
started_at: item.started_at || null,
finished_at: item.finished_at || null,
status: item.status || null,
stats_json: item.stats_json || null,
error_message: item.error_message || null,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
// Bulk create items
const task_runs = await db.task_runs.bulkCreate(task_runsData, {
transaction,
});
// For each item created, replace relation files
return task_runs;
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess;
const task_runs = await db.task_runs.findByPk(id, {}, { transaction });
const updatePayload = {};
if (data.task_type !== undefined) updatePayload.task_type = data.task_type;
if (data.started_at !== undefined)
updatePayload.started_at = data.started_at;
if (data.finished_at !== undefined)
updatePayload.finished_at = data.finished_at;
if (data.status !== undefined) updatePayload.status = data.status;
if (data.stats_json !== undefined)
updatePayload.stats_json = data.stats_json;
if (data.error_message !== undefined)
updatePayload.error_message = data.error_message;
updatePayload.updatedById = currentUser.id;
await task_runs.update(updatePayload, { transaction });
if (data.workspace !== undefined) {
await task_runs.setWorkspace(
data.workspace,
{ transaction },
);
}
return task_runs;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const task_runs = await db.task_runs.findAll({
where: {
id: {
[Op.in]: ids,
},
},
transaction,
});
await db.sequelize.transaction(async (transaction) => {
for (const record of task_runs) {
await record.update({ deletedBy: currentUser.id }, { transaction });
}
for (const record of task_runs) {
await record.destroy({ transaction });
}
});
return task_runs;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const task_runs = await db.task_runs.findByPk(id, options);
await task_runs.update(
{
deletedBy: currentUser.id,
},
{
transaction,
},
);
await task_runs.destroy({
transaction,
});
return task_runs;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const task_runs = await db.task_runs.findOne({ where }, { transaction });
if (!task_runs) {
return task_runs;
}
const output = task_runs.get({ plain: true });
output.workspace = await task_runs.getWorkspace({
transaction,
});
return output;
}
static async findAll(filter, globalAccess, options) {
const limit = filter.limit || 0;
let offset = 0;
let where = {};
const currentPage = +filter.page;
const user = (options && options.currentUser) || null;
const userWorkspace = (user && user.workspace?.id) || null;
if (userWorkspace) {
if (options?.currentUser?.workspaceId) {
where.workspaceId = options.currentUser.workspaceId;
}
}
offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [
{
model: db.workspace,
as: 'workspace',
},
];
if (filter) {
if (filter.id) {
where = {
...where,
['id']: Utils.uuid(filter.id),
};
}
if (filter.task_type) {
where = {
...where,
[Op.and]: Utils.ilike('task_runs', 'task_type', filter.task_type),
};
}
if (filter.status) {
where = {
...where,
[Op.and]: Utils.ilike('task_runs', 'status', filter.status),
};
}
if (filter.stats_json) {
where = {
...where,
[Op.and]: Utils.ilike('task_runs', 'stats_json', filter.stats_json),
};
}
if (filter.error_message) {
where = {
...where,
[Op.and]: Utils.ilike(
'task_runs',
'error_message',
filter.error_message,
),
};
}
if (filter.calendarStart && filter.calendarEnd) {
where = {
...where,
[Op.or]: [
{
started_at: {
[Op.between]: [filter.calendarStart, filter.calendarEnd],
},
},
{
finished_at: {
[Op.between]: [filter.calendarStart, filter.calendarEnd],
},
},
],
};
}
if (filter.started_atRange) {
const [start, end] = filter.started_atRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
started_at: {
...where.started_at,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
started_at: {
...where.started_at,
[Op.lte]: end,
},
};
}
}
if (filter.finished_atRange) {
const [start, end] = filter.finished_atRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
finished_at: {
...where.finished_at,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
finished_at: {
...where.finished_at,
[Op.lte]: end,
},
};
}
}
if (filter.active !== undefined) {
where = {
...where,
active: filter.active === true || filter.active === 'true',
};
}
if (filter.workspace) {
const listItems = filter.workspace.split('|').map((item) => {
return Utils.uuid(item);
});
where = {
...where,
workspaceId: { [Op.or]: listItems },
};
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.lte]: end,
},
};
}
}
}
if (globalAccess) {
delete where.workspaceId;
}
const queryOptions = {
where,
include,
distinct: true,
order:
filter.field && filter.sort
? [[filter.field, filter.sort]]
: [['createdAt', 'desc']],
transaction: options?.transaction,
logging: console.log,
};
if (!options?.countOnly) {
queryOptions.limit = limit ? Number(limit) : undefined;
queryOptions.offset = offset ? Number(offset) : undefined;
}
try {
const { rows, count } = await db.task_runs.findAndCountAll(queryOptions);
return {
rows: options?.countOnly ? [] : rows,
count: count,
};
} catch (error) {
console.error('Error executing query:', error);
throw error;
}
}
static async findAllAutocomplete(
query,
limit,
offset,
globalAccess,
organizationId,
) {
let where = {};
if (!globalAccess && organizationId) {
where.organizationId = organizationId;
}
if (query) {
where = {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike('task_runs', 'task_type', query),
],
};
}
const records = await db.task_runs.findAll({
attributes: ['id', 'task_type'],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['task_type', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.task_type,
}));
}
};

804
backend/src/db/api/users.js Normal file
View File

@ -0,0 +1,804 @@
const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils');
const bcrypt = require('bcrypt');
const config = require('../../config');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class UsersDBApi {
static async create(data, globalAccess, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const users = await db.users.create(
{
id: data.data.id || undefined,
firstName: data.data.firstName || null,
lastName: data.data.lastName || null,
phoneNumber: data.data.phoneNumber || null,
email: data.data.email || null,
disabled: data.data.disabled || false,
password: data.data.password || null,
emailVerified: data.data.emailVerified || true,
emailVerificationToken: data.data.emailVerificationToken || null,
emailVerificationTokenExpiresAt:
data.data.emailVerificationTokenExpiresAt || null,
passwordResetToken: data.data.passwordResetToken || null,
passwordResetTokenExpiresAt:
data.data.passwordResetTokenExpiresAt || null,
provider: data.data.provider || null,
importHash: data.data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
if (!data.data.app_role) {
const role = await db.roles.findOne({
where: { name: 'User' },
});
if (role) {
await users.setApp_role(role, {
transaction,
});
}
} else {
await users.setApp_role(data.data.app_role || null, {
transaction,
});
}
await users.setWorkspace(data.data.workspace || null, {
transaction,
});
await users.setCustom_permissions(data.data.custom_permissions || [], {
transaction,
});
await FileDBApi.replaceRelationFiles(
{
belongsTo: db.users.getTableName(),
belongsToColumn: 'avatar',
belongsToId: users.id,
},
data.data.avatar,
options,
);
return users;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const usersData = data.map((item, index) => ({
id: item.id || undefined,
firstName: item.firstName || null,
lastName: item.lastName || null,
phoneNumber: item.phoneNumber || null,
email: item.email || null,
disabled: item.disabled || false,
password: item.password || null,
emailVerified: item.emailVerified || false,
emailVerificationToken: item.emailVerificationToken || null,
emailVerificationTokenExpiresAt:
item.emailVerificationTokenExpiresAt || null,
passwordResetToken: item.passwordResetToken || null,
passwordResetTokenExpiresAt: item.passwordResetTokenExpiresAt || null,
provider: item.provider || null,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
// Bulk create items
const users = await db.users.bulkCreate(usersData, { transaction });
// For each item created, replace relation files
for (let i = 0; i < users.length; i++) {
await FileDBApi.replaceRelationFiles(
{
belongsTo: db.users.getTableName(),
belongsToColumn: 'avatar',
belongsToId: users[i].id,
},
data[i].avatar,
options,
);
}
return users;
}
static async update(id, data, globalAccess, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const users = await db.users.findByPk(id, {}, { transaction });
if (!data?.app_role) {
data.app_role = users?.app_role?.id;
}
if (!data?.custom_permissions) {
data.custom_permissions = users?.custom_permissions?.map(
(item) => item.id,
);
}
if (data.password) {
data.password = bcrypt.hashSync(data.password, config.bcrypt.saltRounds);
} else {
data.password = users.password;
}
const updatePayload = {};
if (data.firstName !== undefined) updatePayload.firstName = data.firstName;
if (data.lastName !== undefined) updatePayload.lastName = data.lastName;
if (data.phoneNumber !== undefined)
updatePayload.phoneNumber = data.phoneNumber;
if (data.email !== undefined) updatePayload.email = data.email;
if (data.disabled !== undefined) updatePayload.disabled = data.disabled;
if (data.password !== undefined) updatePayload.password = data.password;
if (data.emailVerified !== undefined)
updatePayload.emailVerified = data.emailVerified;
else updatePayload.emailVerified = true;
if (data.emailVerificationToken !== undefined)
updatePayload.emailVerificationToken = data.emailVerificationToken;
if (data.emailVerificationTokenExpiresAt !== undefined)
updatePayload.emailVerificationTokenExpiresAt =
data.emailVerificationTokenExpiresAt;
if (data.passwordResetToken !== undefined)
updatePayload.passwordResetToken = data.passwordResetToken;
if (data.passwordResetTokenExpiresAt !== undefined)
updatePayload.passwordResetTokenExpiresAt =
data.passwordResetTokenExpiresAt;
if (data.provider !== undefined) updatePayload.provider = data.provider;
updatePayload.updatedById = currentUser.id;
await users.update(updatePayload, { transaction });
if (data.app_role !== undefined) {
await users.setApp_role(
data.app_role,
{ transaction },
);
}
if (data.workspace !== undefined) {
await users.setWorkspace(
data.workspace,
{ transaction },
);
}
if (data.custom_permissions !== undefined) {
await users.setCustom_permissions(data.custom_permissions, {
transaction,
});
}
await FileDBApi.replaceRelationFiles(
{
belongsTo: db.users.getTableName(),
belongsToColumn: 'avatar',
belongsToId: users.id,
},
data.avatar,
options,
);
return users;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const users = await db.users.findAll({
where: {
id: {
[Op.in]: ids,
},
},
transaction,
});
await db.sequelize.transaction(async (transaction) => {
for (const record of users) {
await record.update({ deletedBy: currentUser.id }, { transaction });
}
for (const record of users) {
await record.destroy({ transaction });
}
});
return users;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const users = await db.users.findByPk(id, options);
await users.update(
{
deletedBy: currentUser.id,
},
{
transaction,
},
);
await users.destroy({
transaction,
});
return users;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const users = await db.users.findOne({ where }, { transaction });
if (!users) {
return users;
}
const output = users.get({ plain: true });
output.workspace_members_user = await users.getWorkspace_members_user({
transaction,
});
output.avatar = await users.getAvatar({
transaction,
});
output.app_role = await users.getApp_role({
transaction,
});
if (output.app_role) {
output.app_role_permissions = await output.app_role.getPermissions({
transaction,
});
}
output.custom_permissions = await users.getCustom_permissions({
transaction,
});
output.workspace = await users.getWorkspace({
transaction,
});
return output;
}
static async findAll(filter, globalAccess, options) {
const limit = filter.limit || 0;
let offset = 0;
let where = {};
const currentPage = +filter.page;
const user = (options && options.currentUser) || null;
const userWorkspace = (user && user.workspace?.id) || null;
if (userWorkspace) {
if (options?.currentUser?.workspaceId) {
where.workspaceId = options.currentUser.workspaceId;
}
}
offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [
{
model: db.roles,
as: 'app_role',
where: filter.app_role
? {
[Op.or]: [
{
id: {
[Op.in]: filter.app_role
.split('|')
.map((term) => Utils.uuid(term)),
},
},
{
name: {
[Op.or]: filter.app_role
.split('|')
.map((term) => ({ [Op.iLike]: `%${term}%` })),
},
},
],
}
: {},
},
{
model: db.workspace,
as: 'workspace',
},
{
model: db.permissions,
as: 'custom_permissions',
required: false,
},
{
model: db.file,
as: 'avatar',
},
];
if (filter) {
if (filter.id) {
where = {
...where,
['id']: Utils.uuid(filter.id),
};
}
if (filter.firstName) {
where = {
...where,
[Op.and]: Utils.ilike('users', 'firstName', filter.firstName),
};
}
if (filter.lastName) {
where = {
...where,
[Op.and]: Utils.ilike('users', 'lastName', filter.lastName),
};
}
if (filter.phoneNumber) {
where = {
...where,
[Op.and]: Utils.ilike('users', 'phoneNumber', filter.phoneNumber),
};
}
if (filter.email) {
where = {
...where,
[Op.and]: Utils.ilike('users', 'email', filter.email),
};
}
if (filter.password) {
where = {
...where,
[Op.and]: Utils.ilike('users', 'password', filter.password),
};
}
if (filter.emailVerificationToken) {
where = {
...where,
[Op.and]: Utils.ilike(
'users',
'emailVerificationToken',
filter.emailVerificationToken,
),
};
}
if (filter.passwordResetToken) {
where = {
...where,
[Op.and]: Utils.ilike(
'users',
'passwordResetToken',
filter.passwordResetToken,
),
};
}
if (filter.provider) {
where = {
...where,
[Op.and]: Utils.ilike('users', 'provider', filter.provider),
};
}
if (filter.emailVerificationTokenExpiresAtRange) {
const [start, end] = filter.emailVerificationTokenExpiresAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
emailVerificationTokenExpiresAt: {
...where.emailVerificationTokenExpiresAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
emailVerificationTokenExpiresAt: {
...where.emailVerificationTokenExpiresAt,
[Op.lte]: end,
},
};
}
}
if (filter.passwordResetTokenExpiresAtRange) {
const [start, end] = filter.passwordResetTokenExpiresAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
passwordResetTokenExpiresAt: {
...where.passwordResetTokenExpiresAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
passwordResetTokenExpiresAt: {
...where.passwordResetTokenExpiresAt,
[Op.lte]: end,
},
};
}
}
if (filter.active !== undefined) {
where = {
...where,
active: filter.active === true || filter.active === 'true',
};
}
if (filter.disabled) {
where = {
...where,
disabled: filter.disabled,
};
}
if (filter.emailVerified) {
where = {
...where,
emailVerified: filter.emailVerified,
};
}
if (filter.workspace) {
const listItems = filter.workspace.split('|').map((item) => {
return Utils.uuid(item);
});
where = {
...where,
workspaceId: { [Op.or]: listItems },
};
}
if (filter.custom_permissions) {
const searchTerms = filter.custom_permissions.split('|');
include = [
{
model: db.permissions,
as: 'custom_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}%`,
})),
},
},
],
}
: undefined,
},
...include,
];
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.lte]: end,
},
};
}
}
}
if (globalAccess) {
delete where.workspaceId;
}
const queryOptions = {
where,
include,
distinct: true,
order:
filter.field && filter.sort
? [[filter.field, filter.sort]]
: [['createdAt', 'desc']],
transaction: options?.transaction,
logging: console.log,
};
if (!options?.countOnly) {
queryOptions.limit = limit ? Number(limit) : undefined;
queryOptions.offset = offset ? Number(offset) : undefined;
}
try {
const { rows, count } = await db.users.findAndCountAll(queryOptions);
return {
rows: options?.countOnly ? [] : rows,
count: count,
};
} catch (error) {
console.error('Error executing query:', error);
throw error;
}
}
static async findAllAutocomplete(
query,
limit,
offset,
globalAccess,
organizationId,
) {
let where = {};
if (!globalAccess && organizationId) {
where.organizationId = organizationId;
}
if (query) {
where = {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike('users', 'firstName', query),
],
};
}
const records = await db.users.findAll({
attributes: ['id', 'firstName'],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['firstName', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.firstName,
}));
}
static async createFromAuth(data, options) {
const transaction = (options && options.transaction) || undefined;
const users = await db.users.create(
{
email: data.email,
firstName: data.firstName,
authenticationUid: data.authenticationUid,
password: data.password,
organizationId: data.organizationId,
},
{ transaction },
);
const app_role = await db.roles.findOne({
where: { name: config.roles?.user || 'User' },
});
if (app_role?.id) {
await users.setApp_role(app_role?.id || null, {
transaction,
});
}
await users.update(
{
authenticationUid: users.id,
},
{ transaction },
);
delete users.password;
return users;
}
static async updatePassword(id, password, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const users = await db.users.findByPk(id, {
transaction,
});
await users.update(
{
password,
authenticationUid: id,
updatedById: currentUser.id,
},
{ transaction },
);
return users;
}
static async generateEmailVerificationToken(email, options) {
return this._generateToken(
['emailVerificationToken', 'emailVerificationTokenExpiresAt'],
email,
options,
);
}
static async generatePasswordResetToken(email, options) {
return this._generateToken(
['passwordResetToken', 'passwordResetTokenExpiresAt'],
email,
options,
);
}
static async findByPasswordResetToken(token, options) {
const transaction = (options && options.transaction) || undefined;
return db.users.findOne(
{
where: {
passwordResetToken: token,
passwordResetTokenExpiresAt: {
[db.Sequelize.Op.gt]: Date.now(),
},
},
},
{ transaction },
);
}
static async findByEmailVerificationToken(token, options) {
const transaction = (options && options.transaction) || undefined;
return db.users.findOne(
{
where: {
emailVerificationToken: token,
emailVerificationTokenExpiresAt: {
[db.Sequelize.Op.gt]: Date.now(),
},
},
},
{ transaction },
);
}
static async markEmailVerified(id, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const users = await db.users.findByPk(id, {
transaction,
});
await users.update(
{
emailVerified: true,
updatedById: currentUser.id,
},
{ transaction },
);
return true;
}
static async _generateToken(keyNames, email, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const users = await db.users.findOne(
{
where: { email: email.toLowerCase() },
},
{
transaction,
},
);
const token = crypto.randomBytes(20).toString('hex');
const tokenExpiresAt = Date.now() + 360000;
if (users) {
await users.update(
{
[keyNames[0]]: token,
[keyNames[1]]: tokenExpiresAt,
updatedById: currentUser.id,
},
{ transaction },
);
}
return token;
}
};

View File

@ -0,0 +1,326 @@
const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class WorkspaceDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const workspace = await db.workspace.create(
{
id: data.id || undefined,
name: data.name || null,
importHash: data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
return workspace;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const workspaceData = data.map((item, index) => ({
id: item.id || undefined,
name: item.name || null,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
// Bulk create items
const workspace = await db.workspace.bulkCreate(workspaceData, {
transaction,
});
// For each item created, replace relation files
return workspace;
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess;
const workspace = await db.workspace.findByPk(id, {}, { transaction });
const updatePayload = {};
if (data.name !== undefined) updatePayload.name = data.name;
updatePayload.updatedById = currentUser.id;
await workspace.update(updatePayload, { transaction });
return workspace;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const workspace = await db.workspace.findAll({
where: {
id: {
[Op.in]: ids,
},
},
transaction,
});
await db.sequelize.transaction(async (transaction) => {
for (const record of workspace) {
await record.update({ deletedBy: currentUser.id }, { transaction });
}
for (const record of workspace) {
await record.destroy({ transaction });
}
});
return workspace;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const workspace = await db.workspace.findByPk(id, options);
await workspace.update(
{
deletedBy: currentUser.id,
},
{
transaction,
},
);
await workspace.destroy({
transaction,
});
return workspace;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const workspace = await db.workspace.findOne({ where }, { transaction });
if (!workspace) {
return workspace;
}
const output = workspace.get({ plain: true });
output.users_workspace = await workspace.getUsers_workspace({
transaction,
});
output.contact_tags_workspace = await workspace.getContact_tags_workspace({
transaction,
});
output.contacts_workspace = await workspace.getContacts_workspace({
transaction,
});
output.conversations_workspace = await workspace.getConversations_workspace(
{
transaction,
},
);
output.counterparties_workspace =
await workspace.getCounterparties_workspace({
transaction,
});
output.crm_exports_workspace = await workspace.getCrm_exports_workspace({
transaction,
});
output.enriched_profiles_workspace =
await workspace.getEnriched_profiles_workspace({
transaction,
});
output.messages_workspace = await workspace.getMessages_workspace({
transaction,
});
output.source_accounts_workspace =
await workspace.getSource_accounts_workspace({
transaction,
});
output.tags_workspace = await workspace.getTags_workspace({
transaction,
});
output.task_runs_workspace = await workspace.getTask_runs_workspace({
transaction,
});
output.workspace_members_workspace =
await workspace.getWorkspace_members_workspace({
transaction,
});
return output;
}
static async findAll(filter, globalAccess, options) {
const limit = filter.limit || 0;
let offset = 0;
let where = {};
const currentPage = +filter.page;
const user = (options && options.currentUser) || null;
const userWorkspace = (user && user.workspace?.id) || null;
if (userWorkspace) {
if (options?.currentUser?.workspaceId) {
where.workspaceId = options.currentUser.workspaceId;
}
}
offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [];
if (filter) {
if (filter.id) {
where = {
...where,
['id']: Utils.uuid(filter.id),
};
}
if (filter.name) {
where = {
...where,
[Op.and]: Utils.ilike('workspace', 'name', filter.name),
};
}
if (filter.active !== undefined) {
where = {
...where,
active: filter.active === true || filter.active === 'true',
};
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.lte]: end,
},
};
}
}
}
if (globalAccess) {
delete where.workspaceId;
}
const queryOptions = {
where,
include,
distinct: true,
order:
filter.field && filter.sort
? [[filter.field, filter.sort]]
: [['createdAt', 'desc']],
transaction: options?.transaction,
logging: console.log,
};
if (!options?.countOnly) {
queryOptions.limit = limit ? Number(limit) : undefined;
queryOptions.offset = offset ? Number(offset) : undefined;
}
try {
const { rows, count } = await db.workspace.findAndCountAll(queryOptions);
return {
rows: options?.countOnly ? [] : rows,
count: count,
};
} catch (error) {
console.error('Error executing query:', error);
throw error;
}
}
static async findAllAutocomplete(
query,
limit,
offset,
globalAccess,
organizationId,
) {
let where = {};
if (!globalAccess && organizationId) {
where.organizationId = organizationId;
}
if (query) {
where = {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike('workspace', 'name', query),
],
};
}
const records = await db.workspace.findAll({
attributes: ['id', 'name'],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['name', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.name,
}));
}
};

View File

@ -0,0 +1,413 @@
const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class Workspace_membersDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const workspace_members = await db.workspace_members.create(
{
id: data.id || undefined,
role: data.role || null,
invited_at: data.invited_at || null,
joined_at: data.joined_at || null,
importHash: data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
await workspace_members.setWorkspace(data.workspace || null, {
transaction,
});
await workspace_members.setUser(data.user || null, {
transaction,
});
return workspace_members;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const workspace_membersData = data.map((item, index) => ({
id: item.id || undefined,
role: item.role || null,
invited_at: item.invited_at || null,
joined_at: item.joined_at || null,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
// Bulk create items
const workspace_members = await db.workspace_members.bulkCreate(
workspace_membersData,
{ transaction },
);
// For each item created, replace relation files
return workspace_members;
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess;
const workspace_members = await db.workspace_members.findByPk(
id,
{},
{ transaction },
);
const updatePayload = {};
if (data.role !== undefined) updatePayload.role = data.role;
if (data.invited_at !== undefined)
updatePayload.invited_at = data.invited_at;
if (data.joined_at !== undefined) updatePayload.joined_at = data.joined_at;
updatePayload.updatedById = currentUser.id;
await workspace_members.update(updatePayload, { transaction });
if (data.workspace !== undefined) {
await workspace_members.setWorkspace(
data.workspace,
{ transaction },
);
}
if (data.user !== undefined) {
await workspace_members.setUser(
data.user,
{ transaction },
);
}
return workspace_members;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const workspace_members = await db.workspace_members.findAll({
where: {
id: {
[Op.in]: ids,
},
},
transaction,
});
await db.sequelize.transaction(async (transaction) => {
for (const record of workspace_members) {
await record.update({ deletedBy: currentUser.id }, { transaction });
}
for (const record of workspace_members) {
await record.destroy({ transaction });
}
});
return workspace_members;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const workspace_members = await db.workspace_members.findByPk(id, options);
await workspace_members.update(
{
deletedBy: currentUser.id,
},
{
transaction,
},
);
await workspace_members.destroy({
transaction,
});
return workspace_members;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const workspace_members = await db.workspace_members.findOne(
{ where },
{ transaction },
);
if (!workspace_members) {
return workspace_members;
}
const output = workspace_members.get({ plain: true });
output.workspace = await workspace_members.getWorkspace({
transaction,
});
output.user = await workspace_members.getUser({
transaction,
});
return output;
}
static async findAll(filter, globalAccess, options) {
const limit = filter.limit || 0;
let offset = 0;
let where = {};
const currentPage = +filter.page;
const user = (options && options.currentUser) || null;
const userWorkspace = (user && user.workspace?.id) || null;
if (userWorkspace) {
if (options?.currentUser?.workspaceId) {
where.workspaceId = options.currentUser.workspaceId;
}
}
offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [
{
model: db.workspace,
as: 'workspace',
},
{
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}%` })),
},
},
],
}
: {},
},
];
if (filter) {
if (filter.id) {
where = {
...where,
['id']: Utils.uuid(filter.id),
};
}
if (filter.invited_atRange) {
const [start, end] = filter.invited_atRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
invited_at: {
...where.invited_at,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
invited_at: {
...where.invited_at,
[Op.lte]: end,
},
};
}
}
if (filter.joined_atRange) {
const [start, end] = filter.joined_atRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
joined_at: {
...where.joined_at,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
joined_at: {
...where.joined_at,
[Op.lte]: end,
},
};
}
}
if (filter.active !== undefined) {
where = {
...where,
active: filter.active === true || filter.active === 'true',
};
}
if (filter.role) {
where = {
...where,
role: filter.role,
};
}
if (filter.workspace) {
const listItems = filter.workspace.split('|').map((item) => {
return Utils.uuid(item);
});
where = {
...where,
workspaceId: { [Op.or]: listItems },
};
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.lte]: end,
},
};
}
}
}
if (globalAccess) {
delete where.workspaceId;
}
const queryOptions = {
where,
include,
distinct: true,
order:
filter.field && filter.sort
? [[filter.field, filter.sort]]
: [['createdAt', 'desc']],
transaction: options?.transaction,
logging: console.log,
};
if (!options?.countOnly) {
queryOptions.limit = limit ? Number(limit) : undefined;
queryOptions.offset = offset ? Number(offset) : undefined;
}
try {
const { rows, count } = await db.workspace_members.findAndCountAll(
queryOptions,
);
return {
rows: options?.countOnly ? [] : rows,
count: count,
};
} catch (error) {
console.error('Error executing query:', error);
throw error;
}
}
static async findAllAutocomplete(
query,
limit,
offset,
globalAccess,
organizationId,
) {
let where = {};
if (!globalAccess && organizationId) {
where.organizationId = organizationId;
}
if (query) {
where = {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike('workspace_members', 'role', query),
],
};
}
const records = await db.workspace_members.findAll({
attributes: ['id', 'role'],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['role', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.role,
}));
}
};

View File

@ -0,0 +1,31 @@
module.exports = {
production: {
dialect: 'postgres',
username: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
host: process.env.DB_HOST,
port: process.env.DB_PORT,
logging: console.log,
seederStorage: 'sequelize',
},
development: {
username: 'postgres',
dialect: 'postgres',
password: '',
database: 'db_i_want_a_web_app_that_imports_twitter_dms_extract',
host: process.env.DB_HOST || 'localhost',
logging: console.log,
seederStorage: 'sequelize',
},
dev_stage: {
dialect: 'postgres',
username: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
host: process.env.DB_HOST,
port: process.env.DB_PORT,
logging: console.log,
seederStorage: 'sequelize',
},
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,69 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function (sequelize, DataTypes) {
const contact_tags = sequelize.define(
'contact_tags',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
contact_tags.associate = (db) => {
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
//end loop
db.contact_tags.belongsTo(db.contacts, {
as: 'contact',
foreignKey: {
name: 'contactId',
},
constraints: false,
});
db.contact_tags.belongsTo(db.tags, {
as: 'tag',
foreignKey: {
name: 'tagId',
},
constraints: false,
});
db.contact_tags.belongsTo(db.workspace, {
as: 'workspace',
foreignKey: {
name: 'workspaceId',
},
constraints: false,
});
db.contact_tags.belongsTo(db.users, {
as: 'createdBy',
});
db.contact_tags.belongsTo(db.users, {
as: 'updatedBy',
});
};
return contact_tags;
};

View File

@ -0,0 +1,115 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function (sequelize, DataTypes) {
const contacts = sequelize.define(
'contacts',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
primary_email: {
type: DataTypes.TEXT,
},
category: {
type: DataTypes.ENUM,
values: [
'investor',
'customer',
'prospect',
'press',
'partner',
'vip',
'other',
],
},
status: {
type: DataTypes.ENUM,
values: ['new', 'approved', 'synced'],
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
contacts.associate = (db) => {
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
db.contacts.hasMany(db.contact_tags, {
as: 'contact_tags_contact',
foreignKey: {
name: 'contactId',
},
constraints: false,
});
db.contacts.hasMany(db.crm_exports, {
as: 'crm_exports_contact',
foreignKey: {
name: 'contactId',
},
constraints: false,
});
//end loop
db.contacts.belongsTo(db.counterparties, {
as: 'counterparty',
foreignKey: {
name: 'counterpartyId',
},
constraints: false,
});
db.contacts.belongsTo(db.enriched_profiles, {
as: 'enriched_profile',
foreignKey: {
name: 'enriched_profileId',
},
constraints: false,
});
db.contacts.belongsTo(db.workspace, {
as: 'workspace',
foreignKey: {
name: 'workspaceId',
},
constraints: false,
});
db.contacts.belongsTo(db.users, {
as: 'createdBy',
});
db.contacts.belongsTo(db.users, {
as: 'updatedBy',
});
};
return contacts;
};

View File

@ -0,0 +1,85 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function (sequelize, DataTypes) {
const conversations = sequelize.define(
'conversations',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
platform_conversation_id: {
type: DataTypes.TEXT,
},
participants_json: {
type: DataTypes.TEXT,
},
last_message_at: {
type: DataTypes.DATE,
},
last_synced_at: {
type: DataTypes.DATE,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
conversations.associate = (db) => {
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
db.conversations.hasMany(db.messages, {
as: 'messages_conversation',
foreignKey: {
name: 'conversationId',
},
constraints: false,
});
//end loop
db.conversations.belongsTo(db.source_accounts, {
as: 'source_account',
foreignKey: {
name: 'source_accountId',
},
constraints: false,
});
db.conversations.belongsTo(db.workspace, {
as: 'workspace',
foreignKey: {
name: 'workspaceId',
},
constraints: false,
});
db.conversations.belongsTo(db.users, {
as: 'createdBy',
});
db.conversations.belongsTo(db.users, {
as: 'updatedBy',
});
};
return conversations;
};

View File

@ -0,0 +1,89 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function (sequelize, DataTypes) {
const counterparties = sequelize.define(
'counterparties',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
platform_handle: {
type: DataTypes.TEXT,
},
platform_user_id: {
type: DataTypes.TEXT,
},
profile_url: {
type: DataTypes.TEXT,
},
bio: {
type: DataTypes.TEXT,
},
avatar_url: {
type: DataTypes.TEXT,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
counterparties.associate = (db) => {
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
db.counterparties.hasMany(db.contacts, {
as: 'contacts_counterparty',
foreignKey: {
name: 'counterpartyId',
},
constraints: false,
});
//end loop
db.counterparties.belongsTo(db.enriched_profiles, {
as: 'enriched_profile',
foreignKey: {
name: 'enriched_profileId',
},
constraints: false,
});
db.counterparties.belongsTo(db.workspace, {
as: 'workspace',
foreignKey: {
name: 'workspaceId',
},
constraints: false,
});
db.counterparties.belongsTo(db.users, {
as: 'createdBy',
});
db.counterparties.belongsTo(db.users, {
as: 'updatedBy',
});
};
return counterparties;
};

View File

@ -0,0 +1,87 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function (sequelize, DataTypes) {
const crm_exports = sequelize.define(
'crm_exports',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
crm_system: {
type: DataTypes.TEXT,
},
external_id: {
type: DataTypes.TEXT,
},
payload_json: {
type: DataTypes.TEXT,
},
status: {
type: DataTypes.ENUM,
values: ['queued', 'success', 'failed'],
},
last_exported_at: {
type: DataTypes.DATE,
},
error_message: {
type: DataTypes.TEXT,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
crm_exports.associate = (db) => {
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
//end loop
db.crm_exports.belongsTo(db.contacts, {
as: 'contact',
foreignKey: {
name: 'contactId',
},
constraints: false,
});
db.crm_exports.belongsTo(db.workspace, {
as: 'workspace',
foreignKey: {
name: 'workspaceId',
},
constraints: false,
});
db.crm_exports.belongsTo(db.users, {
as: 'createdBy',
});
db.crm_exports.belongsTo(db.users, {
as: 'updatedBy',
});
};
return crm_exports;
};

View File

@ -0,0 +1,113 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function (sequelize, DataTypes) {
const enriched_profiles = sequelize.define(
'enriched_profiles',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
full_name: {
type: DataTypes.TEXT,
},
company: {
type: DataTypes.TEXT,
},
role: {
type: DataTypes.TEXT,
},
website: {
type: DataTypes.TEXT,
},
emails_json: {
type: DataTypes.TEXT,
},
linkedin_url: {
type: DataTypes.TEXT,
},
twitter_url: {
type: DataTypes.TEXT,
},
location: {
type: DataTypes.TEXT,
},
confidence_score: {
type: DataTypes.DECIMAL,
},
evidence_json: {
type: DataTypes.TEXT,
},
last_enriched_at: {
type: DataTypes.DATE,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
enriched_profiles.associate = (db) => {
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
db.enriched_profiles.hasMany(db.contacts, {
as: 'contacts_enriched_profile',
foreignKey: {
name: 'enriched_profileId',
},
constraints: false,
});
db.enriched_profiles.hasMany(db.counterparties, {
as: 'counterparties_enriched_profile',
foreignKey: {
name: 'enriched_profileId',
},
constraints: false,
});
//end loop
db.enriched_profiles.belongsTo(db.workspace, {
as: 'workspace',
foreignKey: {
name: 'workspaceId',
},
constraints: false,
});
db.enriched_profiles.belongsTo(db.users, {
as: 'createdBy',
});
db.enriched_profiles.belongsTo(db.users, {
as: 'updatedBy',
});
};
return enriched_profiles;
};

View File

@ -0,0 +1,53 @@
module.exports = function (sequelize, DataTypes) {
const file = sequelize.define(
'file',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
belongsTo: DataTypes.STRING(255),
belongsToId: DataTypes.UUID,
belongsToColumn: DataTypes.STRING(255),
name: {
type: DataTypes.STRING(2083),
allowNull: false,
validate: {
notEmpty: true,
},
},
sizeInBytes: {
type: DataTypes.INTEGER,
allowNull: true,
},
privateUrl: {
type: DataTypes.STRING(2083),
allowNull: true,
},
publicUrl: {
type: DataTypes.STRING(2083),
allowNull: false,
validate: {
notEmpty: true,
},
},
},
{
timestamps: true,
paranoid: true,
},
);
file.associate = (db) => {
db.file.belongsTo(db.users, {
as: 'createdBy',
});
db.file.belongsTo(db.users, {
as: 'updatedBy',
});
};
return file;
};

View File

@ -0,0 +1,47 @@
'use strict';
const fs = require('fs');
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 db = {};
let sequelize;
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,
);
}
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,
);
db[model.name] = model;
});
Object.keys(db).forEach((modelName) => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
module.exports = db;

View File

@ -0,0 +1,89 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function (sequelize, DataTypes) {
const messages = sequelize.define(
'messages',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
platform_message_id: {
type: DataTypes.TEXT,
},
sender_handle: {
type: DataTypes.TEXT,
},
sender_platform_id: {
type: DataTypes.TEXT,
},
text: {
type: DataTypes.TEXT,
},
attachments_json: {
type: DataTypes.TEXT,
},
sent_at: {
type: DataTypes.DATE,
},
raw_payload_json: {
type: DataTypes.TEXT,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
messages.associate = (db) => {
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
//end loop
db.messages.belongsTo(db.conversations, {
as: 'conversation',
foreignKey: {
name: 'conversationId',
},
constraints: false,
});
db.messages.belongsTo(db.workspace, {
as: 'workspace',
foreignKey: {
name: 'workspaceId',
},
constraints: false,
});
db.messages.belongsTo(db.users, {
as: 'createdBy',
});
db.messages.belongsTo(db.users, {
as: 'updatedBy',
});
};
return messages;
};

View File

@ -0,0 +1,49 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function (sequelize, DataTypes) {
const permissions = sequelize.define(
'permissions',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.TEXT,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
permissions.associate = (db) => {
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
//end loop
db.permissions.belongsTo(db.users, {
as: 'createdBy',
});
db.permissions.belongsTo(db.users, {
as: 'updatedBy',
});
};
return permissions;
};

View File

@ -0,0 +1,86 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function (sequelize, DataTypes) {
const roles = sequelize.define(
'roles',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.TEXT,
},
role_customization: {
type: DataTypes.TEXT,
},
globalAccess: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
roles.associate = (db) => {
db.roles.belongsToMany(db.permissions, {
as: 'permissions',
foreignKey: {
name: 'roles_permissionsId',
},
constraints: false,
through: 'rolesPermissionsPermissions',
});
db.roles.belongsToMany(db.permissions, {
as: 'permissions_filter',
foreignKey: {
name: 'roles_permissionsId',
},
constraints: false,
through: 'rolesPermissionsPermissions',
});
/// 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',
},
constraints: false,
});
//end loop
db.roles.belongsTo(db.users, {
as: 'createdBy',
});
db.roles.belongsTo(db.users, {
as: 'updatedBy',
});
};
return roles;
};

View File

@ -0,0 +1,85 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function (sequelize, DataTypes) {
const source_accounts = sequelize.define(
'source_accounts',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
platform: {
type: DataTypes.TEXT,
},
handle: {
type: DataTypes.TEXT,
},
oauth_access_token: {
type: DataTypes.TEXT,
},
oauth_refresh_token: {
type: DataTypes.TEXT,
},
token_expires_at: {
type: DataTypes.DATE,
},
status: {
type: DataTypes.TEXT,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
source_accounts.associate = (db) => {
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
db.source_accounts.hasMany(db.conversations, {
as: 'conversations_source_account',
foreignKey: {
name: 'source_accountId',
},
constraints: false,
});
//end loop
db.source_accounts.belongsTo(db.workspace, {
as: 'workspace',
foreignKey: {
name: 'workspaceId',
},
constraints: false,
});
db.source_accounts.belongsTo(db.users, {
as: 'createdBy',
});
db.source_accounts.belongsTo(db.users, {
as: 'updatedBy',
});
};
return source_accounts;
};

View File

@ -0,0 +1,69 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function (sequelize, DataTypes) {
const tags = sequelize.define(
'tags',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.TEXT,
},
description: {
type: DataTypes.TEXT,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
tags.associate = (db) => {
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
db.tags.hasMany(db.contact_tags, {
as: 'contact_tags_tag',
foreignKey: {
name: 'tagId',
},
constraints: false,
});
//end loop
db.tags.belongsTo(db.workspace, {
as: 'workspace',
foreignKey: {
name: 'workspaceId',
},
constraints: false,
});
db.tags.belongsTo(db.users, {
as: 'createdBy',
});
db.tags.belongsTo(db.users, {
as: 'updatedBy',
});
};
return tags;
};

View File

@ -0,0 +1,77 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function (sequelize, DataTypes) {
const task_runs = sequelize.define(
'task_runs',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
task_type: {
type: DataTypes.TEXT,
},
started_at: {
type: DataTypes.DATE,
},
finished_at: {
type: DataTypes.DATE,
},
status: {
type: DataTypes.TEXT,
},
stats_json: {
type: DataTypes.TEXT,
},
error_message: {
type: DataTypes.TEXT,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
task_runs.associate = (db) => {
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
//end loop
db.task_runs.belongsTo(db.workspace, {
as: 'workspace',
foreignKey: {
name: 'workspaceId',
},
constraints: false,
});
db.task_runs.belongsTo(db.users, {
as: 'createdBy',
});
db.task_runs.belongsTo(db.users, {
as: 'updatedBy',
});
};
return task_runs;
};

View File

@ -0,0 +1,187 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function (sequelize, DataTypes) {
const users = sequelize.define(
'users',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
firstName: {
type: DataTypes.TEXT,
},
lastName: {
type: DataTypes.TEXT,
},
phoneNumber: {
type: DataTypes.TEXT,
},
email: {
type: DataTypes.TEXT,
},
disabled: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
},
password: {
type: DataTypes.TEXT,
},
emailVerified: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
},
emailVerificationToken: {
type: DataTypes.TEXT,
},
emailVerificationTokenExpiresAt: {
type: DataTypes.DATE,
},
passwordResetToken: {
type: DataTypes.TEXT,
},
passwordResetTokenExpiresAt: {
type: DataTypes.DATE,
},
provider: {
type: DataTypes.TEXT,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
users.associate = (db) => {
db.users.belongsToMany(db.permissions, {
as: 'custom_permissions',
foreignKey: {
name: 'users_custom_permissionsId',
},
constraints: false,
through: 'usersCustom_permissionsPermissions',
});
db.users.belongsToMany(db.permissions, {
as: 'custom_permissions_filter',
foreignKey: {
name: 'users_custom_permissionsId',
},
constraints: false,
through: 'usersCustom_permissionsPermissions',
});
/// 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.workspace_members, {
as: 'workspace_members_user',
foreignKey: {
name: 'userId',
},
constraints: false,
});
//end loop
db.users.belongsTo(db.roles, {
as: 'app_role',
foreignKey: {
name: 'app_roleId',
},
constraints: false,
});
db.users.belongsTo(db.workspace, {
as: 'workspace',
foreignKey: {
name: 'workspaceId',
},
constraints: false,
});
db.users.hasMany(db.file, {
as: 'avatar',
foreignKey: 'belongsToId',
constraints: false,
scope: {
belongsTo: db.users.getTableName(),
belongsToColumn: 'avatar',
},
});
db.users.belongsTo(db.users, {
as: 'createdBy',
});
db.users.belongsTo(db.users, {
as: 'updatedBy',
});
};
users.beforeCreate((users, options) => {
users = trimStringFields(users);
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');
const hashedPassword = bcrypt.hashSync(
password,
config.bcrypt.saltRounds,
);
users.password = hashedPassword;
}
}
});
users.beforeUpdate((users, options) => {
users = trimStringFields(users);
});
return users;
};
function trimStringFields(users) {
users.email = users.email.trim();
users.firstName = users.firstName ? users.firstName.trim() : null;
users.lastName = users.lastName ? users.lastName.trim() : null;
return users;
}

View File

@ -0,0 +1,145 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function (sequelize, DataTypes) {
const workspace = sequelize.define(
'workspace',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.TEXT,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
workspace.associate = (db) => {
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
db.workspace.hasMany(db.users, {
as: 'users_workspace',
foreignKey: {
name: 'workspaceId',
},
constraints: false,
});
db.workspace.hasMany(db.contact_tags, {
as: 'contact_tags_workspace',
foreignKey: {
name: 'workspaceId',
},
constraints: false,
});
db.workspace.hasMany(db.contacts, {
as: 'contacts_workspace',
foreignKey: {
name: 'workspaceId',
},
constraints: false,
});
db.workspace.hasMany(db.conversations, {
as: 'conversations_workspace',
foreignKey: {
name: 'workspaceId',
},
constraints: false,
});
db.workspace.hasMany(db.counterparties, {
as: 'counterparties_workspace',
foreignKey: {
name: 'workspaceId',
},
constraints: false,
});
db.workspace.hasMany(db.crm_exports, {
as: 'crm_exports_workspace',
foreignKey: {
name: 'workspaceId',
},
constraints: false,
});
db.workspace.hasMany(db.enriched_profiles, {
as: 'enriched_profiles_workspace',
foreignKey: {
name: 'workspaceId',
},
constraints: false,
});
db.workspace.hasMany(db.messages, {
as: 'messages_workspace',
foreignKey: {
name: 'workspaceId',
},
constraints: false,
});
db.workspace.hasMany(db.source_accounts, {
as: 'source_accounts_workspace',
foreignKey: {
name: 'workspaceId',
},
constraints: false,
});
db.workspace.hasMany(db.tags, {
as: 'tags_workspace',
foreignKey: {
name: 'workspaceId',
},
constraints: false,
});
db.workspace.hasMany(db.task_runs, {
as: 'task_runs_workspace',
foreignKey: {
name: 'workspaceId',
},
constraints: false,
});
db.workspace.hasMany(db.workspace_members, {
as: 'workspace_members_workspace',
foreignKey: {
name: 'workspaceId',
},
constraints: false,
});
//end loop
db.workspace.belongsTo(db.users, {
as: 'createdBy',
});
db.workspace.belongsTo(db.users, {
as: 'updatedBy',
});
};
return workspace;
};

View File

@ -0,0 +1,75 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function (sequelize, DataTypes) {
const workspace_members = sequelize.define(
'workspace_members',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
role: {
type: DataTypes.ENUM,
values: ['owner', 'admin', 'staff', 'analyst', 'read_only'],
},
invited_at: {
type: DataTypes.DATE,
},
joined_at: {
type: DataTypes.DATE,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
workspace_members.associate = (db) => {
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
//end loop
db.workspace_members.belongsTo(db.workspace, {
as: 'workspace',
foreignKey: {
name: 'workspaceId',
},
constraints: false,
});
db.workspace_members.belongsTo(db.users, {
as: 'user',
foreignKey: {
name: 'userId',
},
constraints: false,
});
db.workspace_members.belongsTo(db.users, {
as: 'createdBy',
});
db.workspace_members.belongsTo(db.users, {
as: 'updatedBy',
});
};
return workspace_members;
};

16
backend/src/db/reset.js Normal file
View File

@ -0,0 +1,16 @@
const db = require('./models');
const { execSync } = require('child_process');
console.log('Resetting Database');
db.sequelize
.sync({ force: true })
.then(() => {
execSync('sequelize db:seed:all');
console.log('OK');
process.exit();
})
.catch((error) => {
console.error(error);
process.exit(1);
});

View File

@ -0,0 +1,84 @@
'use strict';
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',
'ab4cf9bf-4eef-4107-b73d-9d0274cf69bc',
];
module.exports = {
up: async (queryInterface, Sequelize) => {
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(),
},
{
id: ids[3],
firstName: 'Super Admin',
email: 'super_admin@flatlogic.com',
emailVerified: true,
provider: config.providers.LOCAL,
password: admin_hash,
createdAt: new Date(),
updatedAt: new Date(),
},
]);
} catch (error) {
console.error('Error during bulkInsert:', error);
throw error;
}
},
down: async (queryInterface, Sequelize) => {
try {
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

24
backend/src/db/utils.js Normal file
View File

@ -0,0 +1,24 @@
const validator = require('validator');
const { v4: uuid } = require('uuid');
const Sequelize = require('./models').Sequelize;
module.exports = class Utils {
static uuid(value) {
let id = value;
if (!validator.isUUID(id)) {
id = uuid();
}
return id;
}
static ilike(model, column, value) {
return Sequelize.where(
Sequelize.fn('lower', Sequelize.col(`${model}.${column}`)),
{
[Sequelize.Op.like]: `%${value}%`.toLowerCase(),
},
);
}
};

23
backend/src/helpers.js Normal file
View File

@ -0,0 +1,23 @@
const jwt = require('jsonwebtoken');
const config = require('./config');
module.exports = class Helpers {
static wrapAsync(fn) {
return function (req, res, next) {
fn(req, res, next).catch(next);
};
}
static commonErrorHandler(error, req, res, next) {
if ([400, 403, 404].includes(error.code)) {
return res.status(error.code).send(error.message);
}
console.error(error);
return res.status(500).send(error.message);
}
static jwtSign(data) {
return jwt.sign(data, config.secret_key, { expiresIn: '6h' });
}
};

239
backend/src/index.js Normal file
View File

@ -0,0 +1,239 @@
const express = require('express');
const cors = require('cors');
const app = express();
const passport = require('passport');
const path = require('path');
const fs = require('fs');
const bodyParser = require('body-parser');
const db = require('./db/models');
const config = require('./config');
const swaggerUI = require('swagger-ui-express');
const swaggerJsDoc = require('swagger-jsdoc');
const authRoutes = require('./routes/auth');
const fileRoutes = require('./routes/file');
const searchRoutes = require('./routes/search');
const pexelsRoutes = require('./routes/pexels');
const organizationForAuthRoutes = require('./routes/organizationLogin');
const openaiRoutes = require('./routes/openai');
const usersRoutes = require('./routes/users');
const contact_tagsRoutes = require('./routes/contact_tags');
const contactsRoutes = require('./routes/contacts');
const conversationsRoutes = require('./routes/conversations');
const counterpartiesRoutes = require('./routes/counterparties');
const crm_exportsRoutes = require('./routes/crm_exports');
const enriched_profilesRoutes = require('./routes/enriched_profiles');
const messagesRoutes = require('./routes/messages');
const source_accountsRoutes = require('./routes/source_accounts');
const tagsRoutes = require('./routes/tags');
const task_runsRoutes = require('./routes/task_runs');
const workspace_membersRoutes = require('./routes/workspace_members');
const rolesRoutes = require('./routes/roles');
const permissionsRoutes = require('./routes/permissions');
const workspaceRoutes = require('./routes/workspace');
const getBaseUrl = (url) => {
if (!url) return '';
return url.endsWith('/api') ? url.slice(0, -4) : url;
};
const options = {
definition: {
openapi: '3.0.0',
info: {
version: '1.0.0',
title: 'I want a web app that imports Twitter DMs extract',
description:
'I want a web app that imports Twitter DMs extract 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',
},
],
components: {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
},
},
responses: {
UnauthorizedError: {
description: 'Access token is missing or invalid',
},
},
},
security: [
{
bearerAuth: [],
},
],
},
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(cors({ origin: true }));
require('./auth/auth');
app.use(bodyParser.json());
app.use('/api/auth', authRoutes);
app.use('/api/file', fileRoutes);
app.use('/api/pexels', pexelsRoutes);
app.enable('trust proxy');
app.use(
'/api/users',
passport.authenticate('jwt', { session: false }),
usersRoutes,
);
app.use(
'/api/contact_tags',
passport.authenticate('jwt', { session: false }),
contact_tagsRoutes,
);
app.use(
'/api/contacts',
passport.authenticate('jwt', { session: false }),
contactsRoutes,
);
app.use(
'/api/conversations',
passport.authenticate('jwt', { session: false }),
conversationsRoutes,
);
app.use(
'/api/counterparties',
passport.authenticate('jwt', { session: false }),
counterpartiesRoutes,
);
app.use(
'/api/crm_exports',
passport.authenticate('jwt', { session: false }),
crm_exportsRoutes,
);
app.use(
'/api/enriched_profiles',
passport.authenticate('jwt', { session: false }),
enriched_profilesRoutes,
);
app.use(
'/api/messages',
passport.authenticate('jwt', { session: false }),
messagesRoutes,
);
app.use(
'/api/source_accounts',
passport.authenticate('jwt', { session: false }),
source_accountsRoutes,
);
app.use(
'/api/tags',
passport.authenticate('jwt', { session: false }),
tagsRoutes,
);
app.use(
'/api/task_runs',
passport.authenticate('jwt', { session: false }),
task_runsRoutes,
);
app.use(
'/api/workspace_members',
passport.authenticate('jwt', { session: false }),
workspace_membersRoutes,
);
app.use(
'/api/roles',
passport.authenticate('jwt', { session: false }),
rolesRoutes,
);
app.use(
'/api/permissions',
passport.authenticate('jwt', { session: false }),
permissionsRoutes,
);
app.use(
'/api/workspace',
passport.authenticate('jwt', { session: false }),
workspaceRoutes,
);
app.use(
'/api/openai',
passport.authenticate('jwt', { session: false }),
openaiRoutes,
);
app.use(
'/api/search',
passport.authenticate('jwt', { session: false }),
searchRoutes,
);
app.use('/api/org-for-auth', organizationForAuthRoutes);
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'));
});
}
const PORT = process.env.NODE_ENV === 'dev_stage' ? 3000 : 8080;
db.sequelize.sync().then(function () {
app.listen(PORT, () => {
console.log(`Listening on port ${PORT}`);
});
});
module.exports = app;

View File

@ -0,0 +1,176 @@
const ValidationError = require('../services/notifications/errors/validation');
const RolesDBApi = require('../db/api/roles');
// Cache for the 'Public' role object
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' });
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);
});
/**
* Middleware creator to check if the current user (or Public role) has a specific permission.
* @param {string} permission - The name of the required permission.
* @return {import("express").RequestHandler} Express middleware function.
*/
function checkPermissions(permission) {
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
}
// 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.',
),
);
}
} 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.',
),
);
}
// 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
}
};
}
const METHOD_MAP = {
POST: 'CREATE',
GET: 'READ',
PUT: 'UPDATE',
PATCH: 'UPDATE',
DELETE: 'DELETE',
};
/**
* Middleware creator to check standard CRUD permissions based on HTTP method and entity name.
* @param {string} name - The name of the entity.
* @return {import("express").RequestHandler} Express middleware function.
*/
function checkCrudPermissions(name) {
return (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
checkPermissions(permissionName)(req, res, next);
};
}
module.exports = {
checkPermissions,
checkCrudPermissions,
};

View File

@ -0,0 +1,11 @@
const util = require('util');
const Multer = require('multer');
const maxSize = 10 * 1024 * 1024;
let processFile = Multer({
storage: Multer.memoryStorage(),
limits: { fileSize: maxSize },
}).single('file');
let processFileMiddleware = util.promisify(processFile);
module.exports = processFileMiddleware;

270
backend/src/routes/auth.js Normal file
View File

@ -0,0 +1,270 @@
const express = require('express');
const passport = require('passport');
const config = require('../config');
const AuthService = require('../services/auth');
const ForbiddenError = require('../services/notifications/errors/forbidden');
const EmailSender = require('../services/email');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
/**
* @swagger
* components:
* schemas:
* Auth:
* type: object
* required:
* - email
* - password
* properties:
* email:
* type: string
* default: admin@flatlogic.com
* description: User email
* password:
* type: string
* default: password
* description: User password
*/
/**
* @swagger
* tags:
* name: Auth
* description: Authorization operations
*/
/**
* @swagger
* /api/auth/signin/local:
* post:
* tags: [Auth]
* summary: Logs user into the system
* description: Logs user into the system
* requestBody:
* description: Set valid user email and password
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Auth"
* responses:
* 200:
* description: Successful login
* 400:
* description: Invalid username/password supplied
* x-codegen-request-body-name: body
*/
router.post(
'/signin/local',
wrapAsync(async (req, res) => {
const payload = await AuthService.signin(
req.body.email,
req.body.password,
req,
);
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/auth/me:
* get:
* security:
* - bearerAuth: []
* tags: [Auth]
* summary: Get current authorized user info
* description: Get current authorized user info
* responses:
* 200:
* description: Successful retrieval of current authorized user data
* 400:
* description: Invalid username/password supplied
* 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();
}
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-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();
}
await AuthService.sendEmailAddressVerificationEmail(req.currentUser.email);
const payload = true;
res.status(200).send(payload);
}),
);
router.post(
'/send-password-reset-email',
wrapAsync(async (req, res) => {
const link = new URL(req.headers.referer);
await AuthService.sendPasswordResetEmail(
req.body.email,
'register',
link.host,
);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/auth/signup:
* post:
* tags: [Auth]
* summary: Register new user into the system
* description: Register new user into the system
* requestBody:
* description: Set valid user email and password
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Auth"
* responses:
* 200:
* description: New user successfully signed up
* 400:
* description: Invalid username/password supplied
* 500:
* description: Some server error
* x-codegen-request-body-name: body
*/
router.post(
'/signup',
wrapAsync(async (req, res) => {
const link = new URL(req.headers.referer);
const payload = await AuthService.signup(
req.body.email,
req.body.password,
req.body.organizationId,
req,
link.host,
);
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();
}
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.get('/email-configured', (req, res) => {
const payload = EmailSender.isConfigured;
res.status(200).send(payload);
});
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,
})(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);
}
module.exports = router;

View File

View File

@ -0,0 +1,452 @@
const express = require('express');
const Contact_tagsService = require('../services/contact_tags');
const Contact_tagsDBApi = require('../db/api/contact_tags');
const wrapAsync = require('../helpers').wrapAsync;
const config = require('../config');
const router = express.Router();
const { parse } = require('json2csv');
const { checkCrudPermissions } = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('contact_tags'));
/**
* @swagger
* components:
* schemas:
* Contact_tags:
* type: object
* properties:
*/
/**
* @swagger
* tags:
* name: Contact_tags
* description: The Contact_tags managing API
*/
/**
* @swagger
* /api/contact_tags:
* post:
* security:
* - bearerAuth: []
* tags: [Contact_tags]
* 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/Contact_tags"
* responses:
* 200:
* description: The item was successfully added
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Contact_tags"
* 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 Contact_tagsService.create(
req.body.data,
req.currentUser,
true,
link.host,
);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/budgets/bulk-import:
* post:
* security:
* - bearerAuth: []
* tags: [Contact_tags]
* summary: Bulk import items
* description: Bulk import items
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated items
* type: array
* items:
* $ref: "#/components/schemas/Contact_tags"
* responses:
* 200:
* description: The items were successfully imported
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Contact_tags"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*
*/
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 Contact_tagsService.bulkImport(req, res, true, link.host);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/contact_tags/{id}:
* put:
* security:
* - bearerAuth: []
* tags: [Contact_tags]
* 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/Contact_tags"
* required:
* - id
* responses:
* 200:
* description: The item data was successfully updated
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Contact_tags"
* 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 Contact_tagsService.update(
req.body.data,
req.body.id,
req.currentUser,
);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/contact_tags/{id}:
* delete:
* security:
* - bearerAuth: []
* tags: [Contact_tags]
* 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/Contact_tags"
* 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 Contact_tagsService.remove(req.params.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/contact_tags/deleteByIds:
* post:
* security:
* - bearerAuth: []
* tags: [Contact_tags]
* 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/Contact_tags"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Items not found
* 500:
* description: Some server error
*/
router.post(
'/deleteByIds',
wrapAsync(async (req, res) => {
await Contact_tagsService.deleteByIds(req.body.data, req.currentUser);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/contact_tags:
* get:
* security:
* - bearerAuth: []
* tags: [Contact_tags]
* summary: Get all contact_tags
* description: Get all contact_tags
* responses:
* 200:
* description: Contact_tags list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Contact_tags"
* 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 globalAccess = req.currentUser.app_role.globalAccess;
const currentUser = req.currentUser;
const payload = await Contact_tagsDBApi.findAll(req.query, globalAccess, {
currentUser,
});
if (filetype && filetype === 'csv') {
const fields = ['id'];
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);
}
}),
);
/**
* @swagger
* /api/contact_tags/count:
* get:
* security:
* - bearerAuth: []
* tags: [Contact_tags]
* summary: Count all contact_tags
* description: Count all contact_tags
* responses:
* 200:
* description: Contact_tags count successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Contact_tags"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get(
'/count',
wrapAsync(async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const currentUser = req.currentUser;
const payload = await Contact_tagsDBApi.findAll(req.query, globalAccess, {
countOnly: true,
currentUser,
});
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/contact_tags/autocomplete:
* get:
* security:
* - bearerAuth: []
* tags: [Contact_tags]
* summary: Find all contact_tags that match search criteria
* description: Find all contact_tags that match search criteria
* responses:
* 200:
* description: Contact_tags list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Contact_tags"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/autocomplete', async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organization?.id;
const payload = await Contact_tagsDBApi.findAllAutocomplete(
req.query.query,
req.query.limit,
req.query.offset,
globalAccess,
organizationId,
);
res.status(200).send(payload);
});
/**
* @swagger
* /api/contact_tags/{id}:
* get:
* security:
* - bearerAuth: []
* tags: [Contact_tags]
* 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/Contact_tags"
* 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 Contact_tagsDBApi.findBy({ id: req.params.id });
res.status(200).send(payload);
}),
);
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,454 @@
const express = require('express');
const ContactsService = require('../services/contacts');
const ContactsDBApi = require('../db/api/contacts');
const wrapAsync = require('../helpers').wrapAsync;
const config = require('../config');
const router = express.Router();
const { parse } = require('json2csv');
const { checkCrudPermissions } = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('contacts'));
/**
* @swagger
* components:
* schemas:
* Contacts:
* type: object
* properties:
* primary_email:
* type: string
* default: primary_email
*
*
*/
/**
* @swagger
* tags:
* name: Contacts
* description: The Contacts managing API
*/
/**
* @swagger
* /api/contacts:
* post:
* security:
* - bearerAuth: []
* tags: [Contacts]
* 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/Contacts"
* responses:
* 200:
* description: The item was successfully added
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Contacts"
* 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 ContactsService.create(
req.body.data,
req.currentUser,
true,
link.host,
);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/budgets/bulk-import:
* post:
* security:
* - bearerAuth: []
* tags: [Contacts]
* summary: Bulk import items
* description: Bulk import items
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated items
* type: array
* items:
* $ref: "#/components/schemas/Contacts"
* responses:
* 200:
* description: The items were successfully imported
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Contacts"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*
*/
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 ContactsService.bulkImport(req, res, true, link.host);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/contacts/{id}:
* put:
* security:
* - bearerAuth: []
* tags: [Contacts]
* 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/Contacts"
* required:
* - id
* responses:
* 200:
* description: The item data was successfully updated
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Contacts"
* 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 ContactsService.update(req.body.data, req.body.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/contacts/{id}:
* delete:
* security:
* - bearerAuth: []
* tags: [Contacts]
* 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/Contacts"
* 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 ContactsService.remove(req.params.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/contacts/deleteByIds:
* post:
* security:
* - bearerAuth: []
* tags: [Contacts]
* 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/Contacts"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Items not found
* 500:
* description: Some server error
*/
router.post(
'/deleteByIds',
wrapAsync(async (req, res) => {
await ContactsService.deleteByIds(req.body.data, req.currentUser);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/contacts:
* get:
* security:
* - bearerAuth: []
* tags: [Contacts]
* summary: Get all contacts
* description: Get all contacts
* responses:
* 200:
* description: Contacts list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Contacts"
* 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 globalAccess = req.currentUser.app_role.globalAccess;
const currentUser = req.currentUser;
const payload = await ContactsDBApi.findAll(req.query, globalAccess, {
currentUser,
});
if (filetype && filetype === 'csv') {
const fields = ['id', 'primary_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);
}
}),
);
/**
* @swagger
* /api/contacts/count:
* get:
* security:
* - bearerAuth: []
* tags: [Contacts]
* summary: Count all contacts
* description: Count all contacts
* responses:
* 200:
* description: Contacts count successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Contacts"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get(
'/count',
wrapAsync(async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const currentUser = req.currentUser;
const payload = await ContactsDBApi.findAll(req.query, globalAccess, {
countOnly: true,
currentUser,
});
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/contacts/autocomplete:
* get:
* security:
* - bearerAuth: []
* tags: [Contacts]
* summary: Find all contacts that match search criteria
* description: Find all contacts that match search criteria
* responses:
* 200:
* description: Contacts list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Contacts"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/autocomplete', async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organization?.id;
const payload = await ContactsDBApi.findAllAutocomplete(
req.query.query,
req.query.limit,
req.query.offset,
globalAccess,
organizationId,
);
res.status(200).send(payload);
});
/**
* @swagger
* /api/contacts/{id}:
* get:
* security:
* - bearerAuth: []
* tags: [Contacts]
* 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/Contacts"
* 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 ContactsDBApi.findBy({ id: req.params.id });
res.status(200).send(payload);
}),
);
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,466 @@
const express = require('express');
const ConversationsService = require('../services/conversations');
const ConversationsDBApi = require('../db/api/conversations');
const wrapAsync = require('../helpers').wrapAsync;
const config = require('../config');
const router = express.Router();
const { parse } = require('json2csv');
const { checkCrudPermissions } = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('conversations'));
/**
* @swagger
* components:
* schemas:
* Conversations:
* type: object
* properties:
* platform_conversation_id:
* type: string
* default: platform_conversation_id
* participants_json:
* type: string
* default: participants_json
*/
/**
* @swagger
* tags:
* name: Conversations
* description: The Conversations managing API
*/
/**
* @swagger
* /api/conversations:
* post:
* security:
* - bearerAuth: []
* tags: [Conversations]
* 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/Conversations"
* responses:
* 200:
* description: The item was successfully added
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Conversations"
* 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 ConversationsService.create(
req.body.data,
req.currentUser,
true,
link.host,
);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/budgets/bulk-import:
* post:
* security:
* - bearerAuth: []
* tags: [Conversations]
* summary: Bulk import items
* description: Bulk import items
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated items
* type: array
* items:
* $ref: "#/components/schemas/Conversations"
* responses:
* 200:
* description: The items were successfully imported
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Conversations"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*
*/
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 ConversationsService.bulkImport(req, res, true, link.host);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/conversations/{id}:
* put:
* security:
* - bearerAuth: []
* tags: [Conversations]
* 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/Conversations"
* required:
* - id
* responses:
* 200:
* description: The item data was successfully updated
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Conversations"
* 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 ConversationsService.update(
req.body.data,
req.body.id,
req.currentUser,
);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/conversations/{id}:
* delete:
* security:
* - bearerAuth: []
* tags: [Conversations]
* 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/Conversations"
* 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 ConversationsService.remove(req.params.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/conversations/deleteByIds:
* post:
* security:
* - bearerAuth: []
* tags: [Conversations]
* 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/Conversations"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Items not found
* 500:
* description: Some server error
*/
router.post(
'/deleteByIds',
wrapAsync(async (req, res) => {
await ConversationsService.deleteByIds(req.body.data, req.currentUser);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/conversations:
* get:
* security:
* - bearerAuth: []
* tags: [Conversations]
* summary: Get all conversations
* description: Get all conversations
* responses:
* 200:
* description: Conversations list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Conversations"
* 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 globalAccess = req.currentUser.app_role.globalAccess;
const currentUser = req.currentUser;
const payload = await ConversationsDBApi.findAll(req.query, globalAccess, {
currentUser,
});
if (filetype && filetype === 'csv') {
const fields = [
'id',
'platform_conversation_id',
'participants_json',
'last_message_at',
'last_synced_at',
];
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);
}
}),
);
/**
* @swagger
* /api/conversations/count:
* get:
* security:
* - bearerAuth: []
* tags: [Conversations]
* summary: Count all conversations
* description: Count all conversations
* responses:
* 200:
* description: Conversations count successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Conversations"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get(
'/count',
wrapAsync(async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const currentUser = req.currentUser;
const payload = await ConversationsDBApi.findAll(req.query, globalAccess, {
countOnly: true,
currentUser,
});
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/conversations/autocomplete:
* get:
* security:
* - bearerAuth: []
* tags: [Conversations]
* summary: Find all conversations that match search criteria
* description: Find all conversations that match search criteria
* responses:
* 200:
* description: Conversations list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Conversations"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/autocomplete', async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organization?.id;
const payload = await ConversationsDBApi.findAllAutocomplete(
req.query.query,
req.query.limit,
req.query.offset,
globalAccess,
organizationId,
);
res.status(200).send(payload);
});
/**
* @swagger
* /api/conversations/{id}:
* get:
* security:
* - bearerAuth: []
* tags: [Conversations]
* 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/Conversations"
* 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 ConversationsDBApi.findBy({ id: req.params.id });
res.status(200).send(payload);
}),
);
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,475 @@
const express = require('express');
const CounterpartiesService = require('../services/counterparties');
const CounterpartiesDBApi = require('../db/api/counterparties');
const wrapAsync = require('../helpers').wrapAsync;
const config = require('../config');
const router = express.Router();
const { parse } = require('json2csv');
const { checkCrudPermissions } = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('counterparties'));
/**
* @swagger
* components:
* schemas:
* Counterparties:
* type: object
* properties:
* platform_handle:
* type: string
* default: platform_handle
* platform_user_id:
* type: string
* default: platform_user_id
* profile_url:
* type: string
* default: profile_url
* bio:
* type: string
* default: bio
* avatar_url:
* type: string
* default: avatar_url
*/
/**
* @swagger
* tags:
* name: Counterparties
* description: The Counterparties managing API
*/
/**
* @swagger
* /api/counterparties:
* post:
* security:
* - bearerAuth: []
* tags: [Counterparties]
* 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/Counterparties"
* responses:
* 200:
* description: The item was successfully added
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Counterparties"
* 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 CounterpartiesService.create(
req.body.data,
req.currentUser,
true,
link.host,
);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/budgets/bulk-import:
* post:
* security:
* - bearerAuth: []
* tags: [Counterparties]
* summary: Bulk import items
* description: Bulk import items
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated items
* type: array
* items:
* $ref: "#/components/schemas/Counterparties"
* responses:
* 200:
* description: The items were successfully imported
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Counterparties"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*
*/
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 CounterpartiesService.bulkImport(req, res, true, link.host);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/counterparties/{id}:
* put:
* security:
* - bearerAuth: []
* tags: [Counterparties]
* 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/Counterparties"
* required:
* - id
* responses:
* 200:
* description: The item data was successfully updated
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Counterparties"
* 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 CounterpartiesService.update(
req.body.data,
req.body.id,
req.currentUser,
);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/counterparties/{id}:
* delete:
* security:
* - bearerAuth: []
* tags: [Counterparties]
* 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/Counterparties"
* 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 CounterpartiesService.remove(req.params.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/counterparties/deleteByIds:
* post:
* security:
* - bearerAuth: []
* tags: [Counterparties]
* 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/Counterparties"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Items not found
* 500:
* description: Some server error
*/
router.post(
'/deleteByIds',
wrapAsync(async (req, res) => {
await CounterpartiesService.deleteByIds(req.body.data, req.currentUser);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/counterparties:
* get:
* security:
* - bearerAuth: []
* tags: [Counterparties]
* summary: Get all counterparties
* description: Get all counterparties
* responses:
* 200:
* description: Counterparties list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Counterparties"
* 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 globalAccess = req.currentUser.app_role.globalAccess;
const currentUser = req.currentUser;
const payload = await CounterpartiesDBApi.findAll(req.query, globalAccess, {
currentUser,
});
if (filetype && filetype === 'csv') {
const fields = [
'id',
'platform_handle',
'platform_user_id',
'profile_url',
'bio',
'avatar_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);
}
}),
);
/**
* @swagger
* /api/counterparties/count:
* get:
* security:
* - bearerAuth: []
* tags: [Counterparties]
* summary: Count all counterparties
* description: Count all counterparties
* responses:
* 200:
* description: Counterparties count successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Counterparties"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get(
'/count',
wrapAsync(async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const currentUser = req.currentUser;
const payload = await CounterpartiesDBApi.findAll(req.query, globalAccess, {
countOnly: true,
currentUser,
});
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/counterparties/autocomplete:
* get:
* security:
* - bearerAuth: []
* tags: [Counterparties]
* summary: Find all counterparties that match search criteria
* description: Find all counterparties that match search criteria
* responses:
* 200:
* description: Counterparties list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Counterparties"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/autocomplete', async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organization?.id;
const payload = await CounterpartiesDBApi.findAllAutocomplete(
req.query.query,
req.query.limit,
req.query.offset,
globalAccess,
organizationId,
);
res.status(200).send(payload);
});
/**
* @swagger
* /api/counterparties/{id}:
* get:
* security:
* - bearerAuth: []
* tags: [Counterparties]
* 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/Counterparties"
* 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 CounterpartiesDBApi.findBy({ id: req.params.id });
res.status(200).send(payload);
}),
);
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,474 @@
const express = require('express');
const Crm_exportsService = require('../services/crm_exports');
const Crm_exportsDBApi = require('../db/api/crm_exports');
const wrapAsync = require('../helpers').wrapAsync;
const config = require('../config');
const router = express.Router();
const { parse } = require('json2csv');
const { checkCrudPermissions } = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('crm_exports'));
/**
* @swagger
* components:
* schemas:
* Crm_exports:
* type: object
* properties:
* crm_system:
* type: string
* default: crm_system
* external_id:
* type: string
* default: external_id
* payload_json:
* type: string
* default: payload_json
* error_message:
* type: string
* default: error_message
*
*/
/**
* @swagger
* tags:
* name: Crm_exports
* description: The Crm_exports managing API
*/
/**
* @swagger
* /api/crm_exports:
* post:
* security:
* - bearerAuth: []
* tags: [Crm_exports]
* 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/Crm_exports"
* responses:
* 200:
* description: The item was successfully added
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Crm_exports"
* 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 Crm_exportsService.create(
req.body.data,
req.currentUser,
true,
link.host,
);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/budgets/bulk-import:
* post:
* security:
* - bearerAuth: []
* tags: [Crm_exports]
* summary: Bulk import items
* description: Bulk import items
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated items
* type: array
* items:
* $ref: "#/components/schemas/Crm_exports"
* responses:
* 200:
* description: The items were successfully imported
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Crm_exports"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*
*/
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 Crm_exportsService.bulkImport(req, res, true, link.host);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/crm_exports/{id}:
* put:
* security:
* - bearerAuth: []
* tags: [Crm_exports]
* 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/Crm_exports"
* required:
* - id
* responses:
* 200:
* description: The item data was successfully updated
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Crm_exports"
* 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 Crm_exportsService.update(
req.body.data,
req.body.id,
req.currentUser,
);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/crm_exports/{id}:
* delete:
* security:
* - bearerAuth: []
* tags: [Crm_exports]
* 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/Crm_exports"
* 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 Crm_exportsService.remove(req.params.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/crm_exports/deleteByIds:
* post:
* security:
* - bearerAuth: []
* tags: [Crm_exports]
* 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/Crm_exports"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Items not found
* 500:
* description: Some server error
*/
router.post(
'/deleteByIds',
wrapAsync(async (req, res) => {
await Crm_exportsService.deleteByIds(req.body.data, req.currentUser);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/crm_exports:
* get:
* security:
* - bearerAuth: []
* tags: [Crm_exports]
* summary: Get all crm_exports
* description: Get all crm_exports
* responses:
* 200:
* description: Crm_exports list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Crm_exports"
* 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 globalAccess = req.currentUser.app_role.globalAccess;
const currentUser = req.currentUser;
const payload = await Crm_exportsDBApi.findAll(req.query, globalAccess, {
currentUser,
});
if (filetype && filetype === 'csv') {
const fields = [
'id',
'crm_system',
'external_id',
'payload_json',
'error_message',
'last_exported_at',
];
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);
}
}),
);
/**
* @swagger
* /api/crm_exports/count:
* get:
* security:
* - bearerAuth: []
* tags: [Crm_exports]
* summary: Count all crm_exports
* description: Count all crm_exports
* responses:
* 200:
* description: Crm_exports count successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Crm_exports"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get(
'/count',
wrapAsync(async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const currentUser = req.currentUser;
const payload = await Crm_exportsDBApi.findAll(req.query, globalAccess, {
countOnly: true,
currentUser,
});
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/crm_exports/autocomplete:
* get:
* security:
* - bearerAuth: []
* tags: [Crm_exports]
* summary: Find all crm_exports that match search criteria
* description: Find all crm_exports that match search criteria
* responses:
* 200:
* description: Crm_exports list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Crm_exports"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/autocomplete', async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organization?.id;
const payload = await Crm_exportsDBApi.findAllAutocomplete(
req.query.query,
req.query.limit,
req.query.offset,
globalAccess,
organizationId,
);
res.status(200).send(payload);
});
/**
* @swagger
* /api/crm_exports/{id}:
* get:
* security:
* - bearerAuth: []
* tags: [Crm_exports]
* 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/Crm_exports"
* 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 Crm_exportsDBApi.findBy({ id: req.params.id });
res.status(200).send(payload);
}),
);
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,501 @@
const express = require('express');
const Enriched_profilesService = require('../services/enriched_profiles');
const Enriched_profilesDBApi = require('../db/api/enriched_profiles');
const wrapAsync = require('../helpers').wrapAsync;
const config = require('../config');
const router = express.Router();
const { parse } = require('json2csv');
const { checkCrudPermissions } = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('enriched_profiles'));
/**
* @swagger
* components:
* schemas:
* Enriched_profiles:
* type: object
* properties:
* full_name:
* type: string
* default: full_name
* company:
* type: string
* default: company
* role:
* type: string
* default: role
* website:
* type: string
* default: website
* emails_json:
* type: string
* default: emails_json
* linkedin_url:
* type: string
* default: linkedin_url
* twitter_url:
* type: string
* default: twitter_url
* location:
* type: string
* default: location
* evidence_json:
* type: string
* default: evidence_json
* confidence_score:
* type: integer
* format: int64
*/
/**
* @swagger
* tags:
* name: Enriched_profiles
* description: The Enriched_profiles managing API
*/
/**
* @swagger
* /api/enriched_profiles:
* post:
* security:
* - bearerAuth: []
* tags: [Enriched_profiles]
* 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/Enriched_profiles"
* responses:
* 200:
* description: The item was successfully added
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Enriched_profiles"
* 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 Enriched_profilesService.create(
req.body.data,
req.currentUser,
true,
link.host,
);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/budgets/bulk-import:
* post:
* security:
* - bearerAuth: []
* tags: [Enriched_profiles]
* summary: Bulk import items
* description: Bulk import items
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated items
* type: array
* items:
* $ref: "#/components/schemas/Enriched_profiles"
* responses:
* 200:
* description: The items were successfully imported
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Enriched_profiles"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*
*/
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 Enriched_profilesService.bulkImport(req, res, true, link.host);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/enriched_profiles/{id}:
* put:
* security:
* - bearerAuth: []
* tags: [Enriched_profiles]
* 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/Enriched_profiles"
* required:
* - id
* responses:
* 200:
* description: The item data was successfully updated
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Enriched_profiles"
* 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 Enriched_profilesService.update(
req.body.data,
req.body.id,
req.currentUser,
);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/enriched_profiles/{id}:
* delete:
* security:
* - bearerAuth: []
* tags: [Enriched_profiles]
* 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/Enriched_profiles"
* 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 Enriched_profilesService.remove(req.params.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/enriched_profiles/deleteByIds:
* post:
* security:
* - bearerAuth: []
* tags: [Enriched_profiles]
* 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/Enriched_profiles"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Items not found
* 500:
* description: Some server error
*/
router.post(
'/deleteByIds',
wrapAsync(async (req, res) => {
await Enriched_profilesService.deleteByIds(req.body.data, req.currentUser);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/enriched_profiles:
* get:
* security:
* - bearerAuth: []
* tags: [Enriched_profiles]
* summary: Get all enriched_profiles
* description: Get all enriched_profiles
* responses:
* 200:
* description: Enriched_profiles list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Enriched_profiles"
* 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 globalAccess = req.currentUser.app_role.globalAccess;
const currentUser = req.currentUser;
const payload = await Enriched_profilesDBApi.findAll(
req.query,
globalAccess,
{ currentUser },
);
if (filetype && filetype === 'csv') {
const fields = [
'id',
'full_name',
'company',
'role',
'website',
'emails_json',
'linkedin_url',
'twitter_url',
'location',
'evidence_json',
'confidence_score',
'last_enriched_at',
];
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);
}
}),
);
/**
* @swagger
* /api/enriched_profiles/count:
* get:
* security:
* - bearerAuth: []
* tags: [Enriched_profiles]
* summary: Count all enriched_profiles
* description: Count all enriched_profiles
* responses:
* 200:
* description: Enriched_profiles count successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Enriched_profiles"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get(
'/count',
wrapAsync(async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const currentUser = req.currentUser;
const payload = await Enriched_profilesDBApi.findAll(
req.query,
globalAccess,
{ countOnly: true, currentUser },
);
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/enriched_profiles/autocomplete:
* get:
* security:
* - bearerAuth: []
* tags: [Enriched_profiles]
* summary: Find all enriched_profiles that match search criteria
* description: Find all enriched_profiles that match search criteria
* responses:
* 200:
* description: Enriched_profiles list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Enriched_profiles"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/autocomplete', async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organization?.id;
const payload = await Enriched_profilesDBApi.findAllAutocomplete(
req.query.query,
req.query.limit,
req.query.offset,
globalAccess,
organizationId,
);
res.status(200).send(payload);
});
/**
* @swagger
* /api/enriched_profiles/{id}:
* get:
* security:
* - bearerAuth: []
* tags: [Enriched_profiles]
* 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/Enriched_profiles"
* 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 Enriched_profilesDBApi.findBy({ id: req.params.id });
res.status(200).send(payload);
}),
);
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,40 @@
const express = require('express');
const config = require('../config');
const path = require('path');
const passport = require('passport');
const services = require('../services/file');
const router = express.Router();
router.get('/download', (req, res) => {
if (
process.env.NODE_ENV == 'production' ||
process.env.NEXT_PUBLIC_BACK_API
) {
services.downloadGCloud(req, res);
} else {
services.downloadLocal(req, res);
}
});
router.post(
'/upload/:table/:field',
passport.authenticate('jwt', { session: false }),
(req, res) => {
const fileName = `${req.params.table}/${req.params.field}`;
if (
process.env.NODE_ENV == 'production' ||
process.env.NEXT_PUBLIC_BACK_API
) {
services.uploadGCloud(fileName, req, res);
} else {
services.uploadLocal(fileName, {
entity: null,
maxFileSize: 10 * 1024 * 1024,
folderIncludesAuthenticationUid: false,
})(req, res);
}
},
);
module.exports = router;

View File

@ -0,0 +1,477 @@
const express = require('express');
const MessagesService = require('../services/messages');
const MessagesDBApi = require('../db/api/messages');
const wrapAsync = require('../helpers').wrapAsync;
const config = require('../config');
const router = express.Router();
const { parse } = require('json2csv');
const { checkCrudPermissions } = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('messages'));
/**
* @swagger
* components:
* schemas:
* Messages:
* type: object
* properties:
* platform_message_id:
* type: string
* default: platform_message_id
* sender_handle:
* type: string
* default: sender_handle
* sender_platform_id:
* type: string
* default: sender_platform_id
* text:
* type: string
* default: text
* attachments_json:
* type: string
* default: attachments_json
* raw_payload_json:
* type: string
* default: raw_payload_json
*/
/**
* @swagger
* tags:
* name: Messages
* description: The Messages managing API
*/
/**
* @swagger
* /api/messages:
* post:
* security:
* - bearerAuth: []
* tags: [Messages]
* 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/Messages"
* responses:
* 200:
* description: The item was successfully added
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Messages"
* 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 MessagesService.create(
req.body.data,
req.currentUser,
true,
link.host,
);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/budgets/bulk-import:
* post:
* security:
* - bearerAuth: []
* tags: [Messages]
* summary: Bulk import items
* description: Bulk import items
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated items
* type: array
* items:
* $ref: "#/components/schemas/Messages"
* responses:
* 200:
* description: The items were successfully imported
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Messages"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*
*/
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 MessagesService.bulkImport(req, res, true, link.host);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/messages/{id}:
* put:
* security:
* - bearerAuth: []
* tags: [Messages]
* 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/Messages"
* required:
* - id
* responses:
* 200:
* description: The item data was successfully updated
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Messages"
* 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 MessagesService.update(req.body.data, req.body.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/messages/{id}:
* delete:
* security:
* - bearerAuth: []
* tags: [Messages]
* 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/Messages"
* 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 MessagesService.remove(req.params.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/messages/deleteByIds:
* post:
* security:
* - bearerAuth: []
* tags: [Messages]
* 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/Messages"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Items not found
* 500:
* description: Some server error
*/
router.post(
'/deleteByIds',
wrapAsync(async (req, res) => {
await MessagesService.deleteByIds(req.body.data, req.currentUser);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/messages:
* get:
* security:
* - bearerAuth: []
* tags: [Messages]
* summary: Get all messages
* description: Get all messages
* responses:
* 200:
* description: Messages list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Messages"
* 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 globalAccess = req.currentUser.app_role.globalAccess;
const currentUser = req.currentUser;
const payload = await MessagesDBApi.findAll(req.query, globalAccess, {
currentUser,
});
if (filetype && filetype === 'csv') {
const fields = [
'id',
'platform_message_id',
'sender_handle',
'sender_platform_id',
'text',
'attachments_json',
'raw_payload_json',
'sent_at',
];
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);
}
}),
);
/**
* @swagger
* /api/messages/count:
* get:
* security:
* - bearerAuth: []
* tags: [Messages]
* summary: Count all messages
* description: Count all messages
* responses:
* 200:
* description: Messages count successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Messages"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get(
'/count',
wrapAsync(async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const currentUser = req.currentUser;
const payload = await MessagesDBApi.findAll(req.query, globalAccess, {
countOnly: true,
currentUser,
});
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/messages/autocomplete:
* get:
* security:
* - bearerAuth: []
* tags: [Messages]
* summary: Find all messages that match search criteria
* description: Find all messages that match search criteria
* responses:
* 200:
* description: Messages list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Messages"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/autocomplete', async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organization?.id;
const payload = await MessagesDBApi.findAllAutocomplete(
req.query.query,
req.query.limit,
req.query.offset,
globalAccess,
organizationId,
);
res.status(200).send(payload);
});
/**
* @swagger
* /api/messages/{id}:
* get:
* security:
* - bearerAuth: []
* tags: [Messages]
* 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/Messages"
* 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 MessagesDBApi.findBy({ id: req.params.id });
res.status(200).send(payload);
}),
);
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,244 @@
const express = require('express');
const db = require('../db/models');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
const sjs = require('sequelize-json-schema');
const { getWidget, askGpt } = require('../services/openai');
const RolesService = require('../services/roles');
const RolesDBApi = require('../db/api/roles');
/**
* @swagger
* /api/roles/roles-info/{infoId}:
* delete:
* security:
* - bearerAuth: []
* tags: [Roles]
* summary: Remove role information by ID
* description: Remove specific role information by ID
* parameters:
* - in: path
* name: infoId
* description: ID of role information to remove
* required: true
* schema:
* type: string
* - in: query
* name: userId
* description: ID of the user
* required: true
* schema:
* type: string
* - in: query
* name: key
* description: Key of the role information to remove
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Role information successfully removed
* content:
* application/json:
* schema:
* type: object
* properties:
* user:
* type: string
* description: The user information
* 400:
* description: Invalid ID or key supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Role not found
* 500:
* description: Some server error
*/
router.delete(
'/roles-info/:infoId',
wrapAsync(async (req, res) => {
const role = await RolesService.removeRoleInfoById(
req.query.infoId,
req.query.roleId,
req.query.key,
req.currentUser,
);
res.status(200).send(role);
}),
);
/**
* @swagger
* /api/roles/role-info/{roleId}:
* get:
* security:
* - bearerAuth: []
* tags: [Roles]
* summary: Get role information by key
* description: Get specific role information by key
* parameters:
* - in: path
* name: roleId
* description: ID of role to get information for
* required: true
* schema:
* type: string
* - in: query
* name: key
* description: Key of the role information to retrieve
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Role information successfully received
* content:
* application/json:
* schema:
* type: object
* properties:
* info:
* type: string
* description: The role information
* 400:
* description: Invalid ID or key supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Role not found
* 500:
* description: Some server error
*/
router.get(
'/info-by-key',
wrapAsync(async (req, res) => {
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) {
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);
}
res.status(200).send(info);
}),
);
router.post(
'/create_widget',
wrapAsync(async (req, res) => {
const { description, userId, roleId } = req.body;
const currentUser = req.currentUser;
const schema = await sjs.getSequelizeSchema(db.sequelize, {});
const payload = {
description,
modelDefinition: schema.definitions,
};
const widgetId = await getWidget(payload, userId, roleId);
if (widgetId) {
await RolesService.addRoleInfo(
roleId,
userId,
'widgets',
widgetId,
currentUser,
);
return res.status(200).send(widgetId);
} else {
return res.status(400).send(widgetId);
}
}),
);
/**
* @swagger
* /api/openai/ask:
* post:
* security:
* - bearerAuth: []
* tags: [OpenAI]
* summary: Ask a question to ChatGPT
* description: Send a question to OpenAI's ChatGPT and get a response
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* question:
* type: string
* description: The question to ask ChatGPT
* apiKey:
* type: string
* description: OpenAI API key
* responses:
* 200:
* description: Question successfully answered
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* description: Whether the request was successful
* data:
* type: string
* description: The answer from ChatGPT
* 400:
* description: Invalid request
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 500:
* 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: 'Question and API key are required',
});
}
const response = await askGpt(prompt);
if (response.success) {
return res.status(200).send(response);
} else {
return res.status(500).send(response);
}
}),
);
module.exports = router;

View File

@ -0,0 +1,46 @@
const express = require('express');
const WorkspaceDBApi = require('../db/api/workspace');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
/**
* @swagger
* /api/organizations:
* get:
* security:
* - bearerAuth: []
* tags: [Organizations]
* summary: Get all organizations
* description: Get all organizations
* responses:
* 200:
* description: Organizations list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Organizations"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get(
'/',
wrapAsync(async (req, res) => {
const payload = await WorkspaceDBApi.findAll(req.query);
const simplifiedPayload = payload.rows.map((org) => ({
id: org.id,
name: org.name,
}));
res.status(200).send(simplifiedPayload);
}),
);
module.exports = router;

View File

@ -0,0 +1,442 @@
const express = require('express');
const PermissionsService = require('../services/permissions');
const PermissionsDBApi = require('../db/api/permissions');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
const { parse } = require('json2csv');
const { checkCrudPermissions } = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('permissions'));
/**
* @swagger
* components:
* schemas:
* Permissions:
* type: object
* properties:
* name:
* type: string
* default: name
*/
/**
* @swagger
* tags:
* name: Permissions
* description: The Permissions managing API
*/
/**
* @swagger
* /api/permissions:
* post:
* security:
* - bearerAuth: []
* tags: [Permissions]
* 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/Permissions"
* responses:
* 200:
* description: The item was successfully added
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Permissions"
* 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 PermissionsService.create(
req.body.data,
req.currentUser,
true,
link.host,
);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/budgets/bulk-import:
* post:
* security:
* - bearerAuth: []
* tags: [Permissions]
* summary: Bulk import items
* description: Bulk import items
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated items
* type: array
* items:
* $ref: "#/components/schemas/Permissions"
* responses:
* 200:
* description: The items were successfully imported
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Permissions"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*
*/
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 PermissionsService.bulkImport(req, res, true, link.host);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/permissions/{id}:
* put:
* security:
* - bearerAuth: []
* tags: [Permissions]
* 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/Permissions"
* required:
* - id
* responses:
* 200:
* description: The item data was successfully updated
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Permissions"
* 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 PermissionsService.update(
req.body.data,
req.body.id,
req.currentUser,
);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/permissions/{id}:
* delete:
* security:
* - bearerAuth: []
* tags: [Permissions]
* 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/Permissions"
* 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 PermissionsService.remove(req.params.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/permissions/deleteByIds:
* post:
* security:
* - bearerAuth: []
* tags: [Permissions]
* 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/Permissions"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Items not found
* 500:
* description: Some server error
*/
router.post(
'/deleteByIds',
wrapAsync(async (req, res) => {
await PermissionsService.deleteByIds(req.body.data, req.currentUser);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/permissions:
* get:
* security:
* - bearerAuth: []
* tags: [Permissions]
* summary: Get all permissions
* description: Get all permissions
* responses:
* 200:
* description: Permissions list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Permissions"
* 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 PermissionsDBApi.findAll(req.query, { currentUser });
if (filetype && filetype === 'csv') {
const fields = ['id', 'name'];
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);
}
}),
);
/**
* @swagger
* /api/permissions/count:
* get:
* security:
* - bearerAuth: []
* tags: [Permissions]
* summary: Count all permissions
* description: Count all permissions
* responses:
* 200:
* description: Permissions count successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Permissions"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get(
'/count',
wrapAsync(async (req, res) => {
const currentUser = req.currentUser;
const payload = await PermissionsDBApi.findAll(req.query, null, {
countOnly: true,
currentUser,
});
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/permissions/autocomplete:
* get:
* security:
* - bearerAuth: []
* tags: [Permissions]
* summary: Find all permissions that match search criteria
* description: Find all permissions that match search criteria
* responses:
* 200:
* description: Permissions list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Permissions"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/autocomplete', async (req, res) => {
const payload = await PermissionsDBApi.findAllAutocomplete(
req.query.query,
req.query.limit,
req.query.offset,
);
res.status(200).send(payload);
});
/**
* @swagger
* /api/permissions/{id}:
* get:
* security:
* - bearerAuth: []
* tags: [Permissions]
* 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/Permissions"
* 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 PermissionsDBApi.findBy({ id: req.params.id });
res.status(200).send(payload);
}),
);
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,106 @@
const express = require('express');
const router = express.Router();
const { pexelsKey, pexelsQuery } = require('../config');
const fetch = require('node-fetch');
const KEY = pexelsKey;
router.get('/image', async (req, res) => {
const headers = {
Authorization: `${KEY}`,
};
const query = pexelsQuery || 'nature';
const orientation = 'portrait';
const perPage = 1;
const url = `https://api.pexels.com/v1/search?query=${query}&orientation=${orientation}&per_page=${perPage}&page=1`;
try {
const response = await fetch(url, { headers });
const data = await response.json();
res.status(200).json(data.photos[0]);
} catch (error) {
res.status(200).json({ error: 'Failed to fetch image' });
}
});
router.get('/video', async (req, res) => {
const headers = {
Authorization: `${KEY}`,
};
const query = pexelsQuery || 'nature';
const orientation = 'portrait';
const perPage = 1;
const url = `https://api.pexels.com/videos/search?query=${query}&orientation=${orientation}&per_page=${perPage}&page=1`;
try {
const response = await fetch(url, { headers });
const data = await response.json();
res.status(200).json(data.videos[0]);
} catch (error) {
res.status(200).json({ error: 'Failed to fetch video' });
}
});
router.get('/multiple-images', async (req, res) => {
const headers = {
Authorization: `${KEY}`,
};
const queries = req.query.queries
? req.query.queries.split(',')
: ['home', 'apple', 'pizza', 'mountains', 'cat'];
const orientation = 'square';
const perPage = 1;
const fallbackImage = {
src: 'https://images.pexels.com/photos/8199252/pexels-photo-8199252.jpeg',
photographer: 'Yan Krukau',
photographer_url: 'https://www.pexels.com/@yankrukov',
};
const fetchFallbackImage = async () => {
try {
const response = await fetch('https://picsum.photos/600');
return {
src: response.url,
photographer: 'Random Picsum',
photographer_url: 'https://picsum.photos/',
};
} catch (error) {
return fallbackImage;
}
};
const fetchImage = async (query) => {
const url = `https://api.pexels.com/v1/search?query=${query}&orientation=${orientation}&per_page=${perPage}&page=1`;
const response = await fetch(url, { headers });
const data = await response.json();
return data.photos[0] || null;
};
const imagePromises = queries.map((query) => fetchImage(query));
const imagesResults = await Promise.allSettled(imagePromises);
const formattedImages = await Promise.all(
imagesResults.map(async (result) => {
if (result.status === 'fulfilled' && result.value) {
const image = result.value;
return {
src: image.src?.original || fallbackImage.src,
photographer: image.photographer || fallbackImage.photographer,
photographer_url:
image.photographer_url || fallbackImage.photographer_url,
};
} else {
const fallback = await fetchFallbackImage();
return {
src: fallback.src || '',
photographer: fallback.photographer || 'Unknown',
photographer_url: fallback.photographer_url || '',
};
}
}),
);
res.json(formattedImages);
});
module.exports = router;

444
backend/src/routes/roles.js Normal file
View File

@ -0,0 +1,444 @@
const express = require('express');
const RolesService = require('../services/roles');
const RolesDBApi = require('../db/api/roles');
const wrapAsync = require('../helpers').wrapAsync;
const config = require('../config');
const router = express.Router();
const { parse } = require('json2csv');
const { checkCrudPermissions } = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('roles'));
/**
* @swagger
* components:
* schemas:
* Roles:
* type: object
* properties:
* name:
* type: string
* default: name
*/
/**
* @swagger
* tags:
* name: Roles
* description: The Roles managing API
*/
/**
* @swagger
* /api/roles:
* post:
* security:
* - bearerAuth: []
* tags: [Roles]
* 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/Roles"
* responses:
* 200:
* description: The item was successfully added
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Roles"
* 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 RolesService.create(req.body.data, req.currentUser, true, link.host);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/budgets/bulk-import:
* post:
* security:
* - bearerAuth: []
* tags: [Roles]
* summary: Bulk import items
* description: Bulk import items
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated items
* type: array
* items:
* $ref: "#/components/schemas/Roles"
* responses:
* 200:
* description: The items were successfully imported
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Roles"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*
*/
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 RolesService.bulkImport(req, res, true, link.host);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/roles/{id}:
* put:
* security:
* - bearerAuth: []
* tags: [Roles]
* 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/Roles"
* required:
* - id
* responses:
* 200:
* description: The item data was successfully updated
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Roles"
* 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 RolesService.update(req.body.data, req.body.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/roles/{id}:
* delete:
* security:
* - bearerAuth: []
* tags: [Roles]
* 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/Roles"
* 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 RolesService.remove(req.params.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/roles/deleteByIds:
* post:
* security:
* - bearerAuth: []
* tags: [Roles]
* 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/Roles"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Items not found
* 500:
* description: Some server error
*/
router.post(
'/deleteByIds',
wrapAsync(async (req, res) => {
await RolesService.deleteByIds(req.body.data, req.currentUser);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/roles:
* get:
* security:
* - bearerAuth: []
* tags: [Roles]
* summary: Get all roles
* description: Get all roles
* responses:
* 200:
* description: Roles list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Roles"
* 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 globalAccess = req.currentUser.app_role.globalAccess;
const currentUser = req.currentUser;
const payload = await RolesDBApi.findAll(req.query, globalAccess, {
currentUser,
});
if (filetype && filetype === 'csv') {
const fields = ['id', 'name'];
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);
}
}),
);
/**
* @swagger
* /api/roles/count:
* get:
* security:
* - bearerAuth: []
* tags: [Roles]
* summary: Count all roles
* description: Count all roles
* responses:
* 200:
* description: Roles count successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Roles"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get(
'/count',
wrapAsync(async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const currentUser = req.currentUser;
const payload = await RolesDBApi.findAll(req.query, globalAccess, {
countOnly: true,
currentUser,
});
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/roles/autocomplete:
* get:
* security:
* - bearerAuth: []
* tags: [Roles]
* summary: Find all roles that match search criteria
* description: Find all roles that match search criteria
* responses:
* 200:
* description: Roles list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Roles"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/autocomplete', async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const payload = await RolesDBApi.findAllAutocomplete(
req.query.query,
req.query.limit,
req.query.offset,
globalAccess,
);
res.status(200).send(payload);
});
/**
* @swagger
* /api/roles/{id}:
* get:
* security:
* - bearerAuth: []
* tags: [Roles]
* 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/Roles"
* 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 RolesDBApi.findBy({ id: req.params.id });
res.status(200).send(payload);
}),
);
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,60 @@
const express = require('express');
const SearchService = require('../services/search');
const config = require('../config');
const router = express.Router();
const { checkCrudPermissions } = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('search'));
/**
* @swagger
* path:
* /api/search:
* post:
* summary: Search
* description: Search results across multiple tables
* requestBody:
* content:
* application/json:
* schema:
* type: object
* properties:
* searchQuery:
* type: string
* required:
* - searchQuery
* responses:
* 200:
* description: Successful request
* 400:
* description: Invalid request
* 500:
* description: Internal server error
*/
router.post('/', async (req, res) => {
const { searchQuery, organizationId } = req.body;
const globalAccess = req.currentUser.app_role.globalAccess;
if (!searchQuery) {
return res.status(400).json({ error: 'Please enter a search query' });
}
try {
const foundMatches = await SearchService.search(
searchQuery,
req.currentUser,
organizationId,
globalAccess,
);
res.json(foundMatches);
} catch (error) {
console.error('Internal Server Error', error);
res.status(500).json({ error: 'Internal Server Error' });
}
});
module.exports = router;

View File

@ -0,0 +1,480 @@
const express = require('express');
const Source_accountsService = require('../services/source_accounts');
const Source_accountsDBApi = require('../db/api/source_accounts');
const wrapAsync = require('../helpers').wrapAsync;
const config = require('../config');
const router = express.Router();
const { parse } = require('json2csv');
const { checkCrudPermissions } = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('source_accounts'));
/**
* @swagger
* components:
* schemas:
* Source_accounts:
* type: object
* properties:
* platform:
* type: string
* default: platform
* handle:
* type: string
* default: handle
* oauth_access_token:
* type: string
* default: oauth_access_token
* oauth_refresh_token:
* type: string
* default: oauth_refresh_token
* status:
* type: string
* default: status
*/
/**
* @swagger
* tags:
* name: Source_accounts
* description: The Source_accounts managing API
*/
/**
* @swagger
* /api/source_accounts:
* post:
* security:
* - bearerAuth: []
* tags: [Source_accounts]
* 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/Source_accounts"
* responses:
* 200:
* description: The item was successfully added
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Source_accounts"
* 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 Source_accountsService.create(
req.body.data,
req.currentUser,
true,
link.host,
);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/budgets/bulk-import:
* post:
* security:
* - bearerAuth: []
* tags: [Source_accounts]
* summary: Bulk import items
* description: Bulk import items
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated items
* type: array
* items:
* $ref: "#/components/schemas/Source_accounts"
* responses:
* 200:
* description: The items were successfully imported
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Source_accounts"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*
*/
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 Source_accountsService.bulkImport(req, res, true, link.host);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/source_accounts/{id}:
* put:
* security:
* - bearerAuth: []
* tags: [Source_accounts]
* 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/Source_accounts"
* required:
* - id
* responses:
* 200:
* description: The item data was successfully updated
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Source_accounts"
* 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 Source_accountsService.update(
req.body.data,
req.body.id,
req.currentUser,
);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/source_accounts/{id}:
* delete:
* security:
* - bearerAuth: []
* tags: [Source_accounts]
* 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/Source_accounts"
* 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 Source_accountsService.remove(req.params.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/source_accounts/deleteByIds:
* post:
* security:
* - bearerAuth: []
* tags: [Source_accounts]
* 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/Source_accounts"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Items not found
* 500:
* description: Some server error
*/
router.post(
'/deleteByIds',
wrapAsync(async (req, res) => {
await Source_accountsService.deleteByIds(req.body.data, req.currentUser);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/source_accounts:
* get:
* security:
* - bearerAuth: []
* tags: [Source_accounts]
* summary: Get all source_accounts
* description: Get all source_accounts
* responses:
* 200:
* description: Source_accounts list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Source_accounts"
* 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 globalAccess = req.currentUser.app_role.globalAccess;
const currentUser = req.currentUser;
const payload = await Source_accountsDBApi.findAll(
req.query,
globalAccess,
{ currentUser },
);
if (filetype && filetype === 'csv') {
const fields = [
'id',
'platform',
'handle',
'oauth_access_token',
'oauth_refresh_token',
'status',
'token_expires_at',
];
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);
}
}),
);
/**
* @swagger
* /api/source_accounts/count:
* get:
* security:
* - bearerAuth: []
* tags: [Source_accounts]
* summary: Count all source_accounts
* description: Count all source_accounts
* responses:
* 200:
* description: Source_accounts count successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Source_accounts"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get(
'/count',
wrapAsync(async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const currentUser = req.currentUser;
const payload = await Source_accountsDBApi.findAll(
req.query,
globalAccess,
{ countOnly: true, currentUser },
);
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/source_accounts/autocomplete:
* get:
* security:
* - bearerAuth: []
* tags: [Source_accounts]
* summary: Find all source_accounts that match search criteria
* description: Find all source_accounts that match search criteria
* responses:
* 200:
* description: Source_accounts list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Source_accounts"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/autocomplete', async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organization?.id;
const payload = await Source_accountsDBApi.findAllAutocomplete(
req.query.query,
req.query.limit,
req.query.offset,
globalAccess,
organizationId,
);
res.status(200).send(payload);
});
/**
* @swagger
* /api/source_accounts/{id}:
* get:
* security:
* - bearerAuth: []
* tags: [Source_accounts]
* 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/Source_accounts"
* 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 Source_accountsDBApi.findBy({ id: req.params.id });
res.status(200).send(payload);
}),
);
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

450
backend/src/routes/tags.js Normal file
View File

@ -0,0 +1,450 @@
const express = require('express');
const TagsService = require('../services/tags');
const TagsDBApi = require('../db/api/tags');
const wrapAsync = require('../helpers').wrapAsync;
const config = require('../config');
const router = express.Router();
const { parse } = require('json2csv');
const { checkCrudPermissions } = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('tags'));
/**
* @swagger
* components:
* schemas:
* Tags:
* type: object
* properties:
* name:
* type: string
* default: name
* description:
* type: string
* default: description
*/
/**
* @swagger
* tags:
* name: Tags
* description: The Tags managing API
*/
/**
* @swagger
* /api/tags:
* post:
* security:
* - bearerAuth: []
* tags: [Tags]
* 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/Tags"
* responses:
* 200:
* description: The item was successfully added
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Tags"
* 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 TagsService.create(req.body.data, req.currentUser, true, link.host);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/budgets/bulk-import:
* post:
* security:
* - bearerAuth: []
* tags: [Tags]
* summary: Bulk import items
* description: Bulk import items
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated items
* type: array
* items:
* $ref: "#/components/schemas/Tags"
* responses:
* 200:
* description: The items were successfully imported
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Tags"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*
*/
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 TagsService.bulkImport(req, res, true, link.host);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/tags/{id}:
* put:
* security:
* - bearerAuth: []
* tags: [Tags]
* 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/Tags"
* required:
* - id
* responses:
* 200:
* description: The item data was successfully updated
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Tags"
* 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 TagsService.update(req.body.data, req.body.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/tags/{id}:
* delete:
* security:
* - bearerAuth: []
* tags: [Tags]
* 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/Tags"
* 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 TagsService.remove(req.params.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/tags/deleteByIds:
* post:
* security:
* - bearerAuth: []
* tags: [Tags]
* 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/Tags"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Items not found
* 500:
* description: Some server error
*/
router.post(
'/deleteByIds',
wrapAsync(async (req, res) => {
await TagsService.deleteByIds(req.body.data, req.currentUser);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/tags:
* get:
* security:
* - bearerAuth: []
* tags: [Tags]
* summary: Get all tags
* description: Get all tags
* responses:
* 200:
* description: Tags list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Tags"
* 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 globalAccess = req.currentUser.app_role.globalAccess;
const currentUser = req.currentUser;
const payload = await TagsDBApi.findAll(req.query, globalAccess, {
currentUser,
});
if (filetype && filetype === 'csv') {
const fields = ['id', 'name', 'description'];
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);
}
}),
);
/**
* @swagger
* /api/tags/count:
* get:
* security:
* - bearerAuth: []
* tags: [Tags]
* summary: Count all tags
* description: Count all tags
* responses:
* 200:
* description: Tags count successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Tags"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get(
'/count',
wrapAsync(async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const currentUser = req.currentUser;
const payload = await TagsDBApi.findAll(req.query, globalAccess, {
countOnly: true,
currentUser,
});
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/tags/autocomplete:
* get:
* security:
* - bearerAuth: []
* tags: [Tags]
* summary: Find all tags that match search criteria
* description: Find all tags that match search criteria
* responses:
* 200:
* description: Tags list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Tags"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/autocomplete', async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organization?.id;
const payload = await TagsDBApi.findAllAutocomplete(
req.query.query,
req.query.limit,
req.query.offset,
globalAccess,
organizationId,
);
res.status(200).send(payload);
});
/**
* @swagger
* /api/tags/{id}:
* get:
* security:
* - bearerAuth: []
* tags: [Tags]
* 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/Tags"
* 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 TagsDBApi.findBy({ id: req.params.id });
res.status(200).send(payload);
}),
);
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,470 @@
const express = require('express');
const Task_runsService = require('../services/task_runs');
const Task_runsDBApi = require('../db/api/task_runs');
const wrapAsync = require('../helpers').wrapAsync;
const config = require('../config');
const router = express.Router();
const { parse } = require('json2csv');
const { checkCrudPermissions } = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('task_runs'));
/**
* @swagger
* components:
* schemas:
* Task_runs:
* type: object
* properties:
* task_type:
* type: string
* default: task_type
* status:
* type: string
* default: status
* stats_json:
* type: string
* default: stats_json
* error_message:
* type: string
* default: error_message
*/
/**
* @swagger
* tags:
* name: Task_runs
* description: The Task_runs managing API
*/
/**
* @swagger
* /api/task_runs:
* post:
* security:
* - bearerAuth: []
* tags: [Task_runs]
* 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/Task_runs"
* responses:
* 200:
* description: The item was successfully added
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Task_runs"
* 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 Task_runsService.create(
req.body.data,
req.currentUser,
true,
link.host,
);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/budgets/bulk-import:
* post:
* security:
* - bearerAuth: []
* tags: [Task_runs]
* summary: Bulk import items
* description: Bulk import items
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated items
* type: array
* items:
* $ref: "#/components/schemas/Task_runs"
* responses:
* 200:
* description: The items were successfully imported
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Task_runs"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*
*/
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 Task_runsService.bulkImport(req, res, true, link.host);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/task_runs/{id}:
* put:
* security:
* - bearerAuth: []
* tags: [Task_runs]
* 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/Task_runs"
* required:
* - id
* responses:
* 200:
* description: The item data was successfully updated
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Task_runs"
* 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 Task_runsService.update(req.body.data, req.body.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/task_runs/{id}:
* delete:
* security:
* - bearerAuth: []
* tags: [Task_runs]
* 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/Task_runs"
* 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 Task_runsService.remove(req.params.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/task_runs/deleteByIds:
* post:
* security:
* - bearerAuth: []
* tags: [Task_runs]
* 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/Task_runs"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Items not found
* 500:
* description: Some server error
*/
router.post(
'/deleteByIds',
wrapAsync(async (req, res) => {
await Task_runsService.deleteByIds(req.body.data, req.currentUser);
const payload = true;
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/task_runs:
* get:
* security:
* - bearerAuth: []
* tags: [Task_runs]
* summary: Get all task_runs
* description: Get all task_runs
* responses:
* 200:
* description: Task_runs list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Task_runs"
* 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 globalAccess = req.currentUser.app_role.globalAccess;
const currentUser = req.currentUser;
const payload = await Task_runsDBApi.findAll(req.query, globalAccess, {
currentUser,
});
if (filetype && filetype === 'csv') {
const fields = [
'id',
'task_type',
'status',
'stats_json',
'error_message',
'started_at',
'finished_at',
];
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);
}
}),
);
/**
* @swagger
* /api/task_runs/count:
* get:
* security:
* - bearerAuth: []
* tags: [Task_runs]
* summary: Count all task_runs
* description: Count all task_runs
* responses:
* 200:
* description: Task_runs count successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Task_runs"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get(
'/count',
wrapAsync(async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const currentUser = req.currentUser;
const payload = await Task_runsDBApi.findAll(req.query, globalAccess, {
countOnly: true,
currentUser,
});
res.status(200).send(payload);
}),
);
/**
* @swagger
* /api/task_runs/autocomplete:
* get:
* security:
* - bearerAuth: []
* tags: [Task_runs]
* summary: Find all task_runs that match search criteria
* description: Find all task_runs that match search criteria
* responses:
* 200:
* description: Task_runs list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Task_runs"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/autocomplete', async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organization?.id;
const payload = await Task_runsDBApi.findAllAutocomplete(
req.query.query,
req.query.limit,
req.query.offset,
globalAccess,
organizationId,
);
res.status(200).send(payload);
});
/**
* @swagger
* /api/task_runs/{id}:
* get:
* security:
* - bearerAuth: []
* tags: [Task_runs]
* 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/Task_runs"
* 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 Task_runsDBApi.findBy({ id: req.params.id });
res.status(200).send(payload);
}),
);
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

Some files were not shown because too many files have changed in this diff Show More