This commit is contained in:
Viktoria Polyakova
2026-01-25 08:57:38 +00:00
commit 4fb101c5db
7657 changed files with 497012 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.env

30
backend/.eslintrc.js.old Normal file
View File

@@ -0,0 +1,30 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir: __dirname,
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: ['plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
rules: {
'@typescript-eslint/consistent-type-imports': [
'warn',
{
prefer: 'type-imports',
fixStyle: 'inline-type-imports',
},
],
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'max-len': ['error', 120, 2],
},
};

45
backend/.gitignore vendored Normal file
View File

@@ -0,0 +1,45 @@
# compiled output
/dist
/node_modules
# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# OS
.DS_Store
# Tests
/coverage
/.nyc_output
# IDEs and editors
/.idea
.run
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode
# Http request env
artifacts/_environment-config/http-client.env.json
# Environment configuration
.env.local
# Yarn
.yarn/cache/
.yarn/unplugged/
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

145
backend/.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,145 @@
stages:
- prepare
- build
- deploy
default:
image: kroniak/ssh-client
interruptible: true
before_script:
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
- echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
- chmod 400 ~/.ssh/id_rsa
.prepare_script: &prepare_script
- tar -czf sources.tar.gz .
- scp -r sources.tar.gz $USER@$SERVER:$FOLDER
.build_script: &build_script
- ssh $USER@$SERVER "cd $FOLDER && ./build.sh"
.deploy_script: &deploy_script
- ssh $USER@$SERVER "cd $FOLDER && ./deploy.sh"
.staging_job:
rules:
- if: $CI_COMMIT_BRANCH == "develop"
variables:
SSH_PRIVATE_KEY: $STAGING_SSH_PRIVATE_KEY
USER: $STAGING_USER
SERVER: $STAGING_SERVER
FOLDER: /amwork/backend
environment:
name: staging
url: https://amwork.dev
.amwork_job:
rules:
- if: $CI_COMMIT_BRANCH == "main"
variables:
SSH_PRIVATE_KEY: $AMWORK_PROD_SSH_PRIVATE_KEY
USER: $AMWORK_PROD_USER
SERVER: $AMWORK_PROD_SERVER
FOLDER: /amwork/backend
environment:
name: production/amwork
url: https://amwork.com
.mywork_job:
rules:
- if: $CI_COMMIT_BRANCH == "main"
variables:
SSH_PRIVATE_KEY: $MYWORK_PROD_SSH_PRIVATE_KEY
USER: $MYWORK_PROD_USER
SERVER: $MYWORK_PROD_SERVER
FOLDER: /mywork/backend
environment:
name: production/mywork
url: https://mywork.app
# Staging
staging_prepare:
extends: .staging_job
stage: prepare
script: *prepare_script
staging_build:
extends: .staging_job
stage: build
variables:
GIT_STRATEGY: none
script: *build_script
staging_deploy:
extends: .staging_job
stage: deploy
variables:
GIT_STRATEGY: none
script: *deploy_script
# Amwork Production
amwork_prepare:
extends: .amwork_job
stage: prepare
script: *prepare_script
amwork_build:
extends: .amwork_job
stage: build
variables:
GIT_STRATEGY: none
script: *build_script
amwork_deploy:
extends: .amwork_job
stage: deploy
when: manual
variables:
GIT_STRATEGY: none
script: *deploy_script
# Mywork Production
mywork_prepare:
extends: .mywork_job
stage: prepare
variables:
FOLDER: /apps/mywork/backend
script: *prepare_script
mywork_build:
extends: .mywork_job
stage: build
variables:
GIT_STRATEGY: none
FOLDER: /apps/mywork/backend
script: *build_script
mywork_deploy:
extends: .mywork_job
stage: deploy
when: manual
variables:
GIT_STRATEGY: none
FOLDER: /apps/mywork/backend
script: *deploy_script
# Platforma500 Production
platforma500_prepare:
extends: .mywork_job
stage: prepare
needs: [mywork_prepare]
variables:
FOLDER: /apps/platforma500/backend
script: *prepare_script
platforma500_build:
extends: .mywork_job
stage: build
needs: [mywork_build]
variables:
GIT_STRATEGY: none
FOLDER: /apps/platforma500/backend
script: *build_script
platforma500_deploy:
extends: .mywork_job
stage: deploy
when: manual
variables:
GIT_STRATEGY: none
FOLDER: /apps/platforma500/backend
script: *deploy_script

4
backend/.prettierrc Normal file
View File

@@ -0,0 +1,4 @@
{
"singleQuote": true,
"printWidth": 120
}

948
backend/.yarn/releases/yarn-4.9.1.cjs vendored Normal file

File diff suppressed because one or more lines are too long

5
backend/.yarnrc.yml Normal file
View File

@@ -0,0 +1,5 @@
compressionLevel: mixed
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.9.1.cjs

26
backend/README.md Normal file
View File

@@ -0,0 +1,26 @@
# Rifeberry backend
## Общее описание системы
* [Описание системы](https://olivergrand.atlassian.net/wiki/spaces/TEAM/pages/6291457)
* [Упрощенная ER диаграмма](https://olivergrand.atlassian.net/wiki/spaces/BACK/pages/6389854/ER).
## Настройка локального окружения для разработки
[Setup local environment](doc/setup-local.md).
## Deploy
Деплой: [Gitlab](https://gitlab.com/rifeberry1/nest-backend/-/pipelines).
## Links
* [Jira project board](https://olivergrand.atlassian.net/jira/software/projects/BACK/boards/1)
* [Confluence space](https://olivergrand.atlassian.net/wiki/spaces/BACK/pages)
## Именование веток и комитов
Ветки именуем следующим образом: название задачи и описание в настоящем времени,
например, `BACK-123-add-api-doc`
Комиты именуем название задачи + описание комита: `BACK-123: Add api documentation`

View File

@@ -0,0 +1,104 @@
{
"nestdev": {
"url": "http://test.amwork.loc",
"baseDomain": "amwork.loc",
"subdomain": "test",
"email": "test@test.com",
"password": "test",
"entityTypeId": 123,
"entityId": 123,
"teslaDealEntityId": 123,
"elonContactEntityId": 123,
"boardId": 123,
"stageId": 123,
"fieldGroupId": 123,
"activityTypeId": 123,
"taskId": 123,
"subtaskId": 123,
"taskBoardId": 123,
"taskStageId": 15022001,
"taskSettingsId": 123,
"taskCommentId": 123,
"activityId": 123,
"noteId": 123,
"userId": 123,
"budgetFieldId": 123,
"fileId": "11111111-1111-1111-1111-111111111111",
"mailboxId": 123,
"folderId": 123,
"messageId": 123,
"payloadId": 123,
"automationId": 123,
"taskAutomationId": 123,
"changeStageAutomationId": 123,
"notificationId": 123
},
"nestprod": {
"url": "https://test.amwork.com",
"baseDomain": "amwork.com",
"subdomain": "test",
"email": "test@test.com",
"password": "test",
"entityTypeId": 123,
"entityId": 123,
"teslaDealEntityId": 123,
"elonContactEntityId": 123,
"boardId": 123,
"stageId": 123,
"fieldGroupId": 123,
"activityTypeId": 123,
"taskId": 123,
"subtaskId": 123,
"taskBoardId": 123,
"taskStageId": 15022001,
"taskSettingsId": 123,
"taskCommentId": 123,
"activityId": 123,
"noteId": 123,
"userId": 123,
"budgetFieldId": 123,
"fileId": "11111111-1111-1111-1111-111111111111",
"mailboxId": 123,
"folderId": 123,
"messageId": 123,
"payloadId": 123,
"automationId": 123,
"taskAutomationId": 123,
"changeStageAutomationId": 123,
"notificationId": 123
},
"neststaging": {
"url": "https://test.amwork.com",
"baseDomain": "amwork.com",
"subdomain": "test",
"email": "test@test.com",
"password": "test",
"entityTypeId": 123,
"entityId": 123,
"teslaDealEntityId": 123,
"elonContactEntityId": 123,
"boardId": 123,
"stageId": 123,
"fieldGroupId": 123,
"activityTypeId": 123,
"taskId": 123,
"subtaskId": 123,
"taskBoardId": 123,
"taskStageId": 15022001,
"taskSettingsId": 123,
"taskCommentId": 123,
"activityId": 123,
"noteId": 123,
"userId": 123,
"budgetFieldId": 123,
"fileId": "11111111-1111-1111-1111-111111111111",
"mailboxId": 123,
"folderId": 123,
"messageId": 123,
"payloadId": 123,
"automationId": 123,
"taskAutomationId": 123,
"changeStageAutomationId": 123,
"notificationId": 123
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

View File

@@ -0,0 +1,5 @@
### Get account settings
GET {{url}}/api/account/settings
Content-Type: application/json
Authorization: Bearer {{token}}

View File

@@ -0,0 +1,92 @@
### Login by subdomain
POST {{url}}/api/auth/login
Content-Type: application/json
{
"email": "{{email}}",
"password": "{{password}}"
}
> {%
client.global.set("token", response.body.token);
client.global.set("userId", response.body.userId);
%}
### Login for site
POST {{url}}/api/auth/login-site
Content-Type: application/json
{
"email": "test@test.com",
"password": "test"
}
> {%
client.global.set("loginLink", response.body.loginLink);
client.global.set("subdomain", response.body.subdomain);
%}
### Login for extension
POST {{url}}/api/auth/login-ext
Content-Type: application/json
{
"email": "test@test.com",
"password": "test"
}
> {%
client.global.set("token", response.body.token);
client.global.set("userId", response.body.userId);
%}
### Decode login link
POST {{subdomain}}.{{baseDomain}}/api/auth/decode-login-link
Content-Type: application/json
{
"loginLink": "{{loginLink}}"
}
> {%
client.global.set("token", response.body.token);
client.global.set("userId", response.body.userId);
%}
### Refresh token
POST {{url}}/api/auth/refresh-token
Content-Type: application/json
Authorization: Bearer {{token}}
> {%
client.global.set("token", response.body.token);
client.global.set("userId", response.body.userId);
%}
### Create account
POST {{url}}/api/auth/accounts
Content-Type: application/json
{
"firstName": "John",
"lastName": "Doe",
"email": "test{{$randomInt}}@test.com",
"phone": "+79998887766",
"companyName": "test5",
"password": "test",
"ref": "{{partnerRef}}",
"promoCode": "some-promo-code",
"rmsCode": "demo"
}
> {%
client.global.set("loginLink", response.body.loginLink);
client.global.set("subdomain", response.body.subdomain);
%}

View File

@@ -0,0 +1,93 @@
### Create automation with activity action
POST {{url}}/api/automation/automations
Content-Type: application/json
Authorization: Bearer {{token}}
{
"trigger": {
"type": "move_or_create_entity",
"settings": null
},
"action": {
"type": "create_activity",
"delay": 300,
"settings": {
"responsibleUserType": "custom",
"responsibleUserId": {{userId}},
"activityTypeId": {{activityTypeId}},
"text": "some-text",
"deadlineType": "immediately",
"deadlineTime": null
}
},
"stageIds": [
{{stageId}}
],
"conditions": [
{
"type": "responsible_user",
"settings": {
"userIds": [{{userId}}]
}
},
{
"type": "field",
"settings": {
"fieldId": {{budgetFieldId}},
"fieldType": "value",
"payload": {
"from": 100,
"to": 200
}
}
}
]
}
### Update automation with activity action
PUT {{url}}/api/automation/automations/{{activityAutomationId}}
Content-Type: application/json
Authorization: Bearer {{token}}
{
"trigger": {
"type": "move_or_create_entity",
"settings": null
},
"action": {
"type": "create_activity",
"delay": 300,
"settings": {
"responsibleUserType": "custom",
"responsibleUserId": {{userId}},
"activityTypeId": {{activityTypeId}},
"text": "test",
"deadlineType": "immediately",
"deadlineTime": null
}
},
"stageIds": [
{{stageId}}
],
"conditions": [
{
"type": "responsible_user",
"settings": {
"userIds": [{{userId}}]
}
},
{
"type": "field",
"settings": {
"fieldId": {{budgetFieldId}},
"fieldType": "value",
"payload": {
"from": 100,
"to": 200
}
}
}
]
}

View File

@@ -0,0 +1,11 @@
### Get automations
GET {{url}}/api/automation/board/{{boardId}}/automations
Content-Type: application/json
Authorization: Bearer {{token}}
### Delete automation
DELETE {{url}}/api/automation/automations/51011020
Content-Type: application/json
Authorization: Bearer {{token}}

View File

@@ -0,0 +1,47 @@
### Create automation with task action
POST {{url}}/api/automation/automations
Content-Type: application/json
Authorization: Bearer {{token}}
{
"trigger": {
"type": "move_or_create_entity",
"settings": null
},
"action": {
"type": "change_stage",
"delay": null,
"settings": {
"stageId": {{stageId}}
}
},
"stageIds": [
{{stageId}}
],
"conditions": []
}
### Update automation with task action
PUT {{url}}/api/automation/automations/{{changeStageAutomationId}}
Content-Type: application/json
Authorization: Bearer {{token}}
{
"trigger": {
"type": "move_or_create_entity",
"settings": null
},
"action": {
"type": "change_stage",
"delay": 300,
"settings": {
"stageId": {{stageId}}
}
},
"stageIds": [
{{stageId}}
],
"conditions": []
}

View File

@@ -0,0 +1,63 @@
### Create automation with email action
POST {{url}}/api/automation/automations
Content-Type: application/json
Authorization: Bearer {{token}}
{
"trigger": {
"type": "move_or_create_entity",
"settings": null
},
"action": {
"type": "send_email",
"delay": null,
"settings": {
"subject": "email subject",
"content": "email body",
"signature": "some signature",
"sendAsHtml": false,
"mailboxId": 27023020,
"userId": {{userId}},
"fileIds": []
}
},
"stageIds": [
{{stageId}}
],
"conditions": [],
"isActive": true,
"applyTrigger": false
}
### Update automation with email action
PUT {{url}}/api/automation/automations/{{emailAutomationId}}
Content-Type: application/json
Authorization: Bearer {{token}}
{
"trigger": {
"type": "move_or_create_entity",
"settings": null
},
"action": {
"type": "send_email",
"delay": null,
"settings": {
"subject": "email subject",
"content": "email body",
"signature": "new signature",
"sendAsHtml": false,
"mailboxId": 27023020,
"userId": {{userId}},
"fileIds": []
}
},
"stageIds": [
{{stageId}}
],
"conditions": [],
"isActive": true,
"applyTrigger": false
}

View File

@@ -0,0 +1,57 @@
### Create automation with task action
POST {{url}}/api/automation/automations
Content-Type: application/json
Authorization: Bearer {{token}}
{
"trigger": {
"type": "move_or_create_entity",
"settings": null
},
"action": {
"type": "create_task",
"delay": 300,
"settings": {
"responsibleUserType": "custom",
"responsibleUserId": {{userId}},
"title": "some-title",
"text": "some-text",
"deadlineType": "immediately",
"deadlineTime": null
}
},
"stageIds": [
{{stageId}}
],
"conditions": []
}
### Update automation with task action
PUT {{url}}/api/automation/automations/{{taskAutomationId}}
Content-Type: application/json
Authorization: Bearer {{token}}
{
"trigger": {
"type": "move_entity",
"settings": null
},
"action": {
"type": "create_task",
"delay": 3000,
"settings": {
"responsibleUserType": "custom",
"responsibleUserId": {{userId}},
"title": "new-title",
"text": "new-text",
"deadlineType": "custom",
"deadlineTime": 3000
}
},
"stageIds": [
{{stageId}}
],
"conditions": []
}

View File

@@ -0,0 +1,35 @@
### Get boards
GET {{url}}/api/crm/boards
Content-Type: application/json
Authorization: Bearer {{token}}
### Create board
POST {{url}}/api/crm/boards
Content-Type: application/json
Authorization: Bearer {{token}}
{
"name": "Board name",
"sortOrder": 1,
"type": "entity_type",
"recordId": {{entityTypeId}}
}
### Update board
PUT {{url}}/api/crm/boards/{{boardId}}
Content-Type: application/json
Authorization: Bearer {{token}}
{
"name": "Board name",
"sortOrder": 1
}
### Delete board
DELETE {{url}}/api/crm/boards/321
Content-Type: application/json
Authorization: Bearer {{token}}

View File

@@ -0,0 +1,72 @@
### Create entity type
POST {{url}}/api/crm/constructor/create-entity-type
Content-Type: application/json
Authorization: Bearer {{token}}
{
"name": "Object",
"entityCategory": "universal",
"fieldGroups": [
{
"id": {{fieldGroupId}},
"name": "Details",
"sortOrder": 0,
"state": "created"
}
],
"fields": [
{
"id": 4201,
"name": "Field name",
"type": "text",
"sortOrder": 1,
"fieldGroupId": {{fieldGroupId}},
"state": "created"
},
{
"id": 4202,
"name": "Field name",
"type": "select",
"sortOrder": 1,
"fieldGroupId": {{fieldGroupId}},
"options": [
{
"id": 1,
"label": "Option 1",
"sortOrder": 0,
"state": "created"
},
{
"id": 2,
"label": "Option 2",
"sortOrder": 1,
"state": "created"
}
],
"state": "created"
}
],
"featureIds": [1, 2, 3],
"taskSettingsActiveFields": ["planned_time", "board_name", "start_date", "end_date", "description", "subtasks"],
"linkedEntityTypes": [
{
"sortOrder": 0,
"targetEntityTypeId": "{{entityTypeId}}"
}
],
"linkedEntityCategories": [{
"entityCategory": "contact",
"entityTypeName": "Partner contact",
"sectionName": "Partner contacts"
}, {
"entityCategory": "company",
"entityTypeName": "Partner company",
"sectionName": "Partner companies"
}],
"section": {
"name": "Objects",
"view": "board",
"icon": "crown"
}
}

View File

@@ -0,0 +1,10 @@
### Delete user and change responsible
DELETE {{url}}/api/crm/user/delete
Content-Type: application/json
Authorization: Bearer {{token}}
{
"currentUserId": 12022001,
"newUserId": 12022002
}

View File

@@ -0,0 +1,5 @@
### Create demo massive data
POST {{url}}/api/crm/demo-massive-data
Content-Type: application/json
Authorization: Bearer {{token}}

View File

@@ -0,0 +1,76 @@
### Get entity type
GET {{url}}/api/crm/entity-types/{{entityTypeId}}
Content-Type: application/json
Authorization: Bearer {{token}}
### Get entity types
GET {{url}}/api/crm/entity-types
Content-Type: application/json
Authorization: Bearer {{token}}
### Delete entity type
DELETE {{url}}/api/crm/entity-types/321
Content-Type: application/json
Authorization: Bearer {{token}}
### Update entity type
PUT {{url}}/api/crm/entity-types/{{entityTypeId}}
Content-Type: application/json
Authorization: Bearer {{token}}
{
"id": {{entityTypeId}},
"name": "Deal",
"cardView": "fields_and_notes",
"fieldGroups": [
{
"id": {{fieldGroupId}},
"name": "Details",
"sortOrder": 0,
"state": "created"
}
],
"fields": [
{
"id": 4201,
"name": "Field name",
"type": "text",
"sortOrder": 1,
"fieldGroupId": {{fieldGroupId}},
"state": "updated"
},
{
"id": 4202,
"name": "Field name",
"type": "select",
"sortOrder": 1,
"fieldGroupId": {{fieldGroupId}},
"options": [
{
"id": 1,
"label": "Option 1",
"sortOrder": 0,
"state": "created"
},
{
"id": 2,
"label": "Option 2",
"sortOrder": 1,
"state": "created"
}
],
"state": "created"
}
],
"featureIds": [1, 2, 3],
"taskSettingsActiveFields": ["planned_time", "board_name", "start_date", "end_date", "description", "subtasks"],
"linkedEntityTypes": [],
"section": {
"name": "Deals",
"view": "board",
"icon": "crown"
}
}

View File

@@ -0,0 +1,108 @@
### Get entity
GET {{url}}/api/crm/entities/{{entityId}}
Content-Type: application/json
Authorization: Bearer {{token}}
### Create entity
POST {{url}}/api/crm/entities
Content-Type: application/json
Authorization: Bearer {{token}}
{
"id": {{$randomInt}},
"name": "Tesla",
"responsibleUserId": {{userId}},
"entityTypeId": {{entityTypeId}},
"stageId": {{stageId}},
"fieldValues": [
{
"id": {{$randomInt}},
"fieldId": {{budgetFieldId}},
"fieldType": "number",
"payload": {
"value": 100000
},
"state": "created"
}
],
"entityLinks": [
{
"id": {{$randomInt}},
"sourceId": {{entityId}},
"targetId": {{elonContactEntityId}},
"sortOrder": 0,
"state": "created"
}
]
}
### Update entity
PUT {{url}}/api/crm/entities/{{entityId}}
Content-Type: application/json
Authorization: Bearer {{token}}
{
"id": {{entityId}},
"name": "Tesla",
"responsibleUserId": {{userId}},
"entityTypeId": {{entityTypeId}},
"stageId": {{stageId}},
"fieldValues": [
{
"fieldId": {{budgetFieldId}},
"fieldType": "number",
"payload": {
"value": 10000
},
"state": "updated"
}
],
"entityLinks": [
{
"id": {{$randomInt}},
"sourceId": {{entityId}},
"targetId": {{elonContactEntityId}},
"sortOrder": 0,
"state": "created"
}
]
}
### Update entity stage
PUT {{url}}/api/crm/entities/{{entityId}}/stage/{{stageId}}
Content-Type: application/json
Authorization: Bearer {{token}}
### Delete entity
DELETE {{url}}/api/crm/entities/321
Content-Type: application/json
Authorization: Bearer {{token}}
### Get entities for board
GET {{url}}/api/crm/entities/{{entityTypeId}}/{{boardId}}/cards
Content-Type: application/json
Authorization: Bearer {{token}}
### Get entities list items
GET {{url}}/api/crm/entities/{{entityTypeId}}/list-items
Content-Type: application/json
Authorization: Bearer {{token}}
### Search entities in name, all fields and linked entities
GET {{url}}/api/crm/entities/{{entityTypeId}}/search?value=book
Content-Type: application/json
Authorization: Bearer {{token}}
### Search entities by name or field value
GET {{url}}/api/crm/entities/{{entityTypeId}}/search/fields?name=book&field[42022001]=4&field[42022003]=com
Content-Type: application/json
Authorization: Bearer {{token}}

View File

@@ -0,0 +1,15 @@
### Create external link
POST {{url}}/api/extension/external-link
Content-Type: application/json
Authorization: Bearer {{token}}
{
"url": "https://salesforce.com",
"name": "Tesla",
"responsibleUserId": {{userId}},
"entityTypeId": {{entityTypeId}},
"stageId": {{stageId}},
"fieldValues": [],
"entityLinks": []
}

View File

@@ -0,0 +1,5 @@
### Get feed items for entity with paging
GET {{url}}/api/crm/feed-items?entityId={{entityId}}&offset=0&limit=10
Content-Type: application/json
Authorization: Bearer {{token}}

View File

@@ -0,0 +1,17 @@
### Send feedback
POST {{url}}/api/mailing/feedback
Content-Type: application/json
Authorization: Bearer {{token}}
{
"type": "trial_expired",
"payload": {
"name": "John Dow",
"phone": "+1234567890",
"email": "john.dow@company.com",
"userNumber": 10,
"subscribe": "yearly",
"plan": "basic"
}
}

View File

@@ -0,0 +1,18 @@
### Get fields settings
GET {{url}}/api/crm/entity-types/13022735/fields-settings
Content-Type: application/json
Authorization: Bearer {{token}}
### Update fields settings
PUT {{url}}/api/crm/entity-types/13022735/fields-settings
Content-Type: application/json
Authorization: Bearer {{token}}
{
"activeFieldCodes": [
"participants",
"description"
]
}

View File

@@ -0,0 +1,14 @@
### Save field value
POST {{url}}/api/crm/entities/{{entityId}}/field-values/{{budgetFieldId}}
Content-Type: application/json
Authorization: Bearer {{token}}
{
"fieldId": {{budgetFieldId}},
"fieldType": "value",
"payload": {
"value": 500
},
"state": "updated"
}

View File

@@ -0,0 +1,48 @@
### Update entity type fields and groups
PUT {{url}}/api/crm/entity-types/{{entityTypeId}}/fields-and-groups
Content-Type: application/json
Authorization: Bearer {{token}}
{
"fieldGroups": [
{
"id": {{fieldGroupId}},
"name": "Details",
"sortOrder": 0,
"state": "created"
}
],
"fields": [
{
"id": 4201,
"name": "Field name",
"type": "text",
"sortOrder": 1,
"fieldGroupId": {{fieldGroupId}},
"state": "updated"
},
{
"id": 4202,
"name": "Field name",
"type": "select",
"sortOrder": 1,
"fieldGroupId": {{fieldGroupId}},
"options": [
{
"id": 1,
"label": "Option 1",
"sortOrder": 0,
"state": "created"
},
{
"id": 2,
"label": "Option 2",
"sortOrder": 1,
"state": "created"
}
],
"state": "created"
}
]
}

View File

@@ -0,0 +1,5 @@
### Delete file
DELETE {{url}}/api/crm/file-link/1
Content-Type: application/json
Authorization: Bearer {{token}}

View File

@@ -0,0 +1,31 @@
### Send contact us form
POST {{url}}/api/forms/contact-us
Content-Type: application/json
Authorization: Bearer {{token}}
{
"name": "test 10",
"phone": "+7999888776633",
"email": "test@test.com",
"comment": "some comment"
}
### Send partner form
POST {{url}}/api/forms/partner
Content-Type: application/json
Authorization: Bearer {{token}}
{
"firstName": "John",
"lastName": "Doe",
"phone": "+7999888776633",
"email": "test@test.com",
"company": "Tesla",
"country": "USA",
"website": "tesla.com",
"employees": "10000",
"comment": "some comment"
}

View File

@@ -0,0 +1,17 @@
### Get next identity for sequence
GET {{url}}/api/crm/identity/board_id_seq
Content-Type: application/json
Authorization: Bearer {{token}}
### Get all identities pool
GET {{url}}/api/crm/identities/all
Content-Type: application/json
Authorization: Bearer {{token}}
### Get identities pool
GET {{url}}/api/crm/identities/feed_item_id_seq
Content-Type: application/json
Authorization: Bearer {{token}}

View File

@@ -0,0 +1,5 @@
### Get import template for entityType
GET {{url}}/api/crm/entity-types/{{entityTypeId}}/template
Content-Type: application/json
Authorization: Bearer {{token}}

View File

@@ -0,0 +1,30 @@
### Get mails for mailbox
GET {{url}}/api/mailing/mailboxes/{{mailboxId}}/messages?folderId={{folderId}}&offset=0&limit=50
Content-Type: application/json
Authorization: Bearer {{token}}
### Get mails for section
GET {{url}}/api/mailing/section/inbox/messages?mailboxId={{mailboxId}}&offset=0&limit=50
Content-Type: application/json
Authorization: Bearer {{token}}
### Get mail thread
GET {{url}}/api/mailing/mailboxes/{{mailboxId}}/threads/1234567890
Content-Type: application/json
Authorization: Bearer {{token}}
### Get mail message
GET {{url}}/api/mailing/mailboxes/{{mailboxId}}/messages/{{messageId}}
Content-Type: application/json
Authorization: Bearer {{token}}
### Get mail attachment
GET {{url}}'/api/mailing/mailboxes/{{mailboxId}}/messages/{{messageId}}/attachments/{{payloadId}}
Content-Type: application/json
Authorization: Bearer {{token}}

View File

@@ -0,0 +1,76 @@
### Create mailbox
POST {{url}}/api/mailing/settings/mailboxes
Content-Type: application/json
Authorization: Bearer {{token}}
{
"email": "test@company.com"
}
### Update mailbox
PUT {{url}}/api/mailing/settings/mailboxes/{{mailboxId}}
Content-Type: application/json
Authorization: Bearer {{token}}
{
"email": "test2@company.com",
"ownerId": {{userid}},
"accessibleUserIds": [{{userid}}],
"createContact": true,
"syncDays": 7
}
### Delete mailbox
DELETE {{url}}/api/mailing/settings/mailboxes/{{mailboxId}}?save=true
Content-Type: application/json
Authorization: Bearer {{token}}
### Get mailboxes for settings
GET {{url}}/api/mailing/settings/mailboxes
Content-Type: application/json
Authorization: Bearer {{token}}
### Get mailbox for settings
GET {{url}}/api/mailing/settings/mailboxes/{{mailboxId}}
Content-Type: application/json
Authorization: Bearer {{token}}
### Get mailbox manual settings
GET {{url}}/api/mailing/settings/mailboxes/{{mailboxId}}/manual
Content-Type: application/json
Authorization: Bearer {{token}}
### Update mailbox manual settings
POST {{url}}/api/mailing/settings/mailboxes/{{mailboxId}}/manual
Content-Type: application/json
Authorization: Bearer {{token}}
{
"password": "12345678",
"imapServer": "imap.company.com",
"imapPort": 993,
"imapSecure": true,
"smtpServer": "smtp.company.com",
"smtpPort": 465,
"smtpSecure": true
}
### Get Gmail connection string for redirect
GET {{url}}/api/mailing/settings/mailboxes/gmail/connect/{{mailboxId}}
Content-Type: application/json
Authorization: Bearer {{token}}
### Gmail callback endpoint
GET {{url}}/api/mailing/settings/mailboxes/gmail/callback?state={{mailboxId}}&code=1234567890
Content-Type: application/json
Authorization: Bearer {{token}}

View File

@@ -0,0 +1,5 @@
### Get mailboxes
GET {{url}}/api/mailing/mailboxes
Content-Type: application/json
Authorization: Bearer {{token}}

View File

@@ -0,0 +1,5 @@
### Run migration
POST {{url}}/api/crm/run-migration
Content-Type: application/json
Authorization: Bearer {{token}}

View File

@@ -0,0 +1,17 @@
### Get modules
GET {{url}}/api/crm/modules
Content-Type: application/json
Authorization: Bearer {{token}}
### Activate module
POST {{url}}/api/crm/modules/{{moduleId}}/activate
Content-Type: application/json
Authorization: Bearer {{token}}
### Deactivate module
POST {{url}}/api/crm/modules/{{moduleId}}/deactivate
Content-Type: application/json
Authorization: Bearer {{token}}

View File

@@ -0,0 +1,27 @@
### Create note
POST {{url}}/api/crm/notes
Content-Type: application/json
Authorization: Bearer {{token}}
{
"entityId": {{entityId}},
"text": "Hello world!!!"
}
### Update note
PUT {{url}}/api/crm/notes/{{noteId}}
Content-Type: application/json
Authorization: Bearer {{token}}
{
"entityId": {{entityId}},
"text": "Updated note"
}
### Delete note
DELETE {{url}}/api/crm/notes/321
Content-Type: application/json
Authorization: Bearer {{token}}

View File

@@ -0,0 +1,23 @@
### Get notifications
GET {{url}}/api/notifications
Content-Type: application/json
Authorization: Bearer {{token}}
### Get unseen count
GET {{url}}/api/notifications/unseen-count
Content-Type: application/json
Authorization: Bearer {{token}}
### Mark seen all
PUT {{url}}/api/notifications/seen
Content-Type: application/json
Authorization: Bearer {{token}}
### Mark seen notification
PUT {{url}}/api/notifications/{{notificationId}}/seen
Content-Type: application/json
Authorization: Bearer {{token}}

View File

@@ -0,0 +1,11 @@
### Get partner summary
GET {{url}}/api/partners/{{partnerId}}/summary
Content-Type: application/json
Authorization: Bearer {{token}}
### Get partner leads
GET {{url}}/api/partners/{{partnerId}}/leads
Content-Type: application/json
Authorization: Bearer {{token}}

View File

@@ -0,0 +1,5 @@
### Get order statuses
GET {{url}}/api/products/order-statuses
Content-Type: application/json
Authorization: Bearer {{token}}

View File

@@ -0,0 +1,92 @@
### Get entity order
GET {{url}}/api/products/orders/entity/{{entityId}}
Content-Type: application/json
Authorization: Bearer {{token}}
### Get entity order products count
GET {{url}}/api/products/orders/entity/{{entityId}}/products-count
Content-Type: application/json
Authorization: Bearer {{token}}
### Create order
POST {{url}}/api/products/orders
Content-Type: application/json
Authorization: Bearer {{token}}
{
"entityId": {{entityId}},
"currency": "USD",
"taxIncluded": true,
"statusId": {{orderStatusId}},
"warehouseId": {{warehouseId}},
"items": [
{
"productId": {{productId}},
"quantity": 5,
"unitPrice": 100,
"tax": 10,
"discount": 0,
"sortOrder": 0,
"reservations": [
{
"warehouseId": {{warehouseId}},
"quantity": 5
},
{
"warehouseId": {{secondWarehouseId}},
"quantity": 10
}
]
}
]
}
### Update order
PUT {{url}}/api/products/orders/{{orderId}}
Content-Type: application/json
Authorization: Bearer {{token}}
{
"entityId": {{entityId}},
"currency": "USD",
"taxIncluded": true,
"statusId": {{orderStatusId}},
"warehouseId": {{secondWarehouseId}},
"items": [
{
"id": 14,
"productId": {{productId}},
"quantity": 10,
"unitPrice": 200,
"tax": 10,
"discount": 30,
"sortOrder": 2,
"reservations": [
{
"warehouseId": {{warehouseId}},
"quantity": 5
}
]
},
{
"id": -1,
"productId": {{productId}},
"quantity": 20,
"unitPrice": 100,
"tax": 20,
"discount": 30,
"sortOrder": 3,
"reservations": []
}
]
}
### Change order status
PUT {{url}}/api/products/orders/{{orderId}}/status/{{orderStatusId}}
Content-Type: application/json
Authorization: Bearer {{token}}

View File

@@ -0,0 +1,32 @@
### Get categories
GET {{url}}/api/products/categories
Content-Type: application/json
Authorization: Bearer {{token}}
### Create category
POST {{url}}/api/products/categories
Content-Type: application/json
Authorization: Bearer {{token}}
{
"name": "Category 1",
"parentId": null
}
### Update category
PUT {{url}}/api/products/categories/{{productCategoryId}}
Content-Type: application/json
Authorization: Bearer {{token}}
{
"name": "Category new name"
}
### Delete category
DELETE {{url}}/api/products/categories/123
Content-Type: application/json
Authorization: Bearer {{token}}

View File

@@ -0,0 +1,29 @@
### Create product price
POST {{url}}/api/products/{{productId}}/prices
Content-Type: application/json
Authorization: Bearer {{token}}
{
"name": null,
"unitPrice": 100,
"currency": "EUR"
}
### Update product price
PUT {{url}}/api/products/{{productId}}/prices/{{productPriceId}}
Content-Type: application/json
Authorization: Bearer {{token}}
{
"name": "Updated price",
"unitPrice": 200,
"currency": "USD"
}
### Delete product price
DELETE {{url}}/api/products/{{productId}}/prices/67
Content-Type: application/json
Authorization: Bearer {{token}}

View File

@@ -0,0 +1,86 @@
### Get products
GET {{url}}/api/products?categoryId={{productCategoryId}}&search=some&offset=0&limit=10
Content-Type: application/json
Authorization: Bearer {{token}}
### Get products by ids
GET {{url}}/api/products?ids=1,2,3
Content-Type: application/json
Authorization: Bearer {{token}}
### Get product
GET {{url}}/api/products/{{productId}}
Content-Type: application/json
Authorization: Bearer {{token}}
### Create product
POST {{url}}/api/products
Content-Type: application/json
Authorization: Bearer {{token}}
{
"name": "Some product",
"type": "service",
"description": "Some description",
"sku": "some-product",
"unit": "some-unit",
"tax": 10,
"categoryId": {{productCategoryId}},
"prices": [
{
"name": "Price in USD",
"unitPrice": 100,
"currency": "USD"
},
{
"name": "Price in KZT",
"unitPrice": 200,
"currency": "KZT"
}
],
"photoFileIds": ["c7d48430-a2b7-4ea5-96a2-58bc40d90574"],
"stocks": [
{
"warehouseId": {{warehouseId}},
"stockQuantity": 100
}
]
}
### Update product
PUT {{url}}/api/products/{{productId}}
Content-Type: application/json
Authorization: Bearer {{token}}
{
"name": "New product name",
"description": "New description",
"sku": "new-product",
"unit": "new-unit",
"tax": 20,
"categoryId": {{productCategoryId}}
}
### Delete product
DELETE {{url}}/api/products/123
Content-Type: application/json
Authorization: Bearer {{token}}
### Upload product photos
POST {{url}}/api/products/{{productId}}/photos
Authorization: Bearer {{token}}
Content-Type: multipart/form-data; boundary="abcd"
--abcd
Content-Disposition: form-data; name="test"; filename="test.png"
Content-Type: image/png
< ../_resources/test.png
--abcd--

View File

@@ -0,0 +1,5 @@
### Get shipment statuses
GET {{url}}/api/products/shipment-statuses
Content-Type: application/json
Authorization: Bearer {{token}}

View File

@@ -0,0 +1,17 @@
### Get shipments
GET {{url}}/api/products/shipments?offset=0&limit=10
Content-Type: application/json
Authorization: Bearer {{token}}
### Get shipment by id
GET {{url}}/api/products/shipments/{{shipmentId}}
Content-Type: application/json
Authorization: Bearer {{token}}
### Change shipment status
PUT {{url}}/api/products/shipments/{{shipmentId}}/status/{{shipmentStatusId}}
Content-Type: application/json
Authorization: Bearer {{token}}

View File

@@ -0,0 +1,18 @@
### Update product stocks
PUT {{url}}/api/products/{{productId}}/stocks
Content-Type: application/json
Authorization: Bearer {{token}}
{
"stocks": [
{
"warehouseId": {{warehouseId}},
"stockQuantity": 100
},
{
"warehouseId": {{secondWarehouseId}},
"stockQuantity": 200
}
]
}

View File

@@ -0,0 +1,31 @@
### Get warehouses
GET {{url}}/api/products/warehouses
Content-Type: application/json
Authorization: Bearer {{token}}
### Create warehouse
POST {{url}}/api/products/warehouses
Content-Type: application/json
Authorization: Bearer {{token}}
{
"name": "Some warehouse"
}
### Update warehouse
PUT {{url}}/api/products/warehouses/{{warehouseId}}
Content-Type: application/json
Authorization: Bearer {{token}}
{
"name": "New warehouse name"
}
### Delete warehouse
DELETE {{url}}/api/products/warehouses/123
Content-Type: application/json
Authorization: Bearer {{token}}

View File

@@ -0,0 +1,5 @@
### Send event to rabbit
POST {{url}}/api/rabbit
Content-Type: application/json
Authorization: Bearer {{token}}

View File

@@ -0,0 +1,10 @@
### Get industries
GET {{url}}/api/crm/rms/industries
Content-Type: application/json
### Delete demo data
DELETE {{url}}/api/crm/rms/demo-data
Content-Type: application/json
Authorization: Bearer {{token}}

View File

@@ -0,0 +1,47 @@
### Get all stages or by boardId
GET {{url}}/api/crm/stages
Content-Type: application/json
Authorization: Bearer {{token}}
### Save stages batch
POST {{url}}/api/crm/stages/batch
Content-Type: application/json
Authorization: Bearer {{token}}
{
"stages": [
{
"id": "172",
"name": "Stage 1",
"color": "#fff",
"code": null,
"isSystem": false,
"sortOrder": 1,
"boardId": {{boardId}},
"state": "unchanged"
},
{
"id": "298",
"name": "Stage 2",
"color": "#fff",
"code": null,
"isSystem": false,
"sortOrder": 2,
"boardId": {{boardId}},
"state": "updated"
},
{
"id": "{{$randomInt}}",
"name": "Stage 4",
"color": "#fff",
"code": null,
"isSystem": false,
"sortOrder": 1,
"boardId": {{boardId}},
"state": "added"
}
]
}

View File

@@ -0,0 +1,12 @@
### Delete file link by id
DELETE {{url}}/api/crm/file-links/123
Content-Type: application/json
Authorization: Bearer {{token}}
### Delete file links by ids
DELETE {{url}}/api/crm/file-links?ids=72,73
Content-Type: application/json
Authorization: Bearer {{token}}

View File

@@ -0,0 +1,24 @@
### Upload file
POST {{url}}/api/storage/upload
Authorization: Bearer {{token}}
Content-Type: multipart/form-data; boundary="abcd"
--abcd
Content-Disposition: form-data; name="test"; filename="test.png"
Content-Type: image/png
< ../_resources/test.png
--abcd--
### Delete file
DELETE {{url}}/api/storage/file/{{fileId}}
Content-Type: application/json
Authorization: Bearer {{token}}
### Get file
GET {{url}}/api/storage/file/{{fileId}}
Content-Type: application/json
Authorization: Bearer {{token}}

View File

@@ -0,0 +1,27 @@
### Create subtask
POST {{url}}/api/crm/tasks/{{taskId}}/subtasks
Content-Type: application/json
Authorization: Bearer {{token}}
{
"text": "new subtask",
"resolved": false
}
### Update subtask
PUT {{url}}/api/crm/tasks/{{taskId}}/subtasks/{{subtaskId}}
Content-Type: application/json
Authorization: Bearer {{token}}
{
"text": "updated subtask",
"resolved": false
}
### Delete subtask
DELETE {{url}}/api/crm/tasks/{{taskId}}/subtasks/46022010
Content-Type: application/json
Authorization: Bearer {{token}}

View File

@@ -0,0 +1,11 @@
### Like task comment
POST {{url}}/api/crm/tasks/{{taskId}}/comments/{{taskCommentId}}/like
Content-Type: application/json
Authorization: Bearer {{token}}
### Unlike task comment
POST {{url}}/api/crm/tasks/{{taskId}}/comments/{{taskCommentId}}/unlike
Content-Type: application/json
Authorization: Bearer {{token}}

View File

@@ -0,0 +1,33 @@
### Get task comments
GET {{url}}/api/crm/tasks/{{taskId}}/comments?offset=0&limit=10
Content-Type: application/json
Authorization: Bearer {{token}}
### Create task comment
POST {{url}}/api/crm/tasks/{{taskId}}/comments
Content-Type: application/json
Authorization: Bearer {{token}}
{
"text": "Hello world!!!",
"fileIds": []
}
### Update task comment
PUT {{url}}/api/crm/tasks/{{taskId}}/comments/{{taskCommentId}}
Content-Type: application/json
Authorization: Bearer {{token}}
{
"text": "New text"
}
### Delete task comment
DELETE {{url}}/api/crm/tasks/{{taskId}}/comments/47022018
Content-Type: application/json
Authorization: Bearer {{token}}

View File

@@ -0,0 +1,39 @@
### Get task settings
GET {{url}}/api/crm/task-settings
Content-Type: application/json
Authorization: Bearer {{token}}
### Create task settings
POST {{url}}/api/crm/task-settings
Content-Type: application/json
Authorization: Bearer {{token}}
{
"type": "task_board",
"recordId": 123,
"activeFields": [
"planned_time",
"board_name",
"start_date",
"end_date"
]
}
### Update task settings
PUT {{url}}/api/crm/task-settings/{{taskSettingsId}}
Content-Type: application/json
Authorization: Bearer {{token}}
{
"activeFields": [
"planned_time",
"board_name",
"start_date",
"end_date",
"description",
"subtasks"
]
}

View File

@@ -0,0 +1,21 @@
### Get task types
GET {{url}}/api/crm/task-types
Content-Type: application/json
Authorization: Bearer {{token}}
### Create task type
POST {{url}}/api/crm/task-types
Content-Type: application/json
Authorization: Bearer {{token}}
{
"name": "Call"
}
### Delete task type
DELETE {{url}}/api/crm/task-types/321
Content-Type: application/json
Authorization: Bearer {{token}}

134
backend/artifacts/task.http Normal file
View File

@@ -0,0 +1,134 @@
### Get task by id
GET {{url}}/api/crm/tasks/{{taskId}}
Content-Type: application/json
Authorization: Bearer {{token}}
### Create task
POST {{url}}/api/crm/tasks
Content-Type: application/json
Authorization: Bearer {{token}}
{
"responsibleUserId": {{userId}},
"startDate": "2022-11-21T17:37:03",
"endDate": "2022-11-22T17:37:03",
"text": "Task with start and end time",
"isResolved": false,
"result": null,
"entityId": {{entityId}},
"title": "Task 1",
"plannedTime": 3600,
"stageId": {{taskStageId}},
"settingsId": {{taskSettingsId}},
"subtasks": [
{"text": "subtask 1", "resolved": true},
{"text": "subtask 2", "resolved": false}
]
}
### Update task
PUT {{url}}/api/crm/tasks/{{taskId}}
Content-Type: application/json
Authorization: Bearer {{token}}
{
"responsibleUserId": {{userId}},
"startDate": "2022-11-23T17:37:03",
"endDate": "2022-11-24T17:37:03",
"text": "Updated Task 1 with start and end time",
"isResolved": true,
"result": "Result",
"entityId": {{entityId}},
"title": "Updated Task 1"
}
### Delete task
DELETE {{url}}/api/crm/tasks/321
Content-Type: application/json
Authorization: Bearer {{token}}
### Create activity
POST {{url}}/api/crm/activities
Content-Type: application/json
Authorization: Bearer {{token}}
{
"responsibleUserId": {{userId}},
"startDate": "2022-11-21T17:37:03",
"endDate": "2022-11-22T17:37:03",
"text": "Activity with start and end time",
"isResolved": false,
"result": null,
"entityId": {{entityId}},
"activityTypeId": {{activityTypeId}}
}
### Update activity
PUT {{url}}/api/crm/activities/{{activityId}}
Content-Type: application/json
Authorization: Bearer {{token}}
{
"responsibleUserId": {{userId}},
"startDate": "2022-11-23T17:37:03",
"endDate": "2022-11-24T17:37:03",
"text": "Updated Activity 1 with start and end time",
"isResolved": true,
"result": "Result",
"entityId": {{entityId}},
"activityTypeId": {{activityTypeId}}
}
### Delete activity
DELETE {{url}}/api/crm/activities/321
Content-Type: application/json
Authorization: Bearer {{token}}
### Get activities
GET {{url}}/api/crm/activities/cards?responsibleUserId={{userId}}
Content-Type: application/json
Authorization: Bearer {{token}}
### Get tasks by time
POST {{url}}/api/crm/tasks/by_time
Content-Type: application/json
Authorization: Bearer {{token}}
### Get time board meta
POST {{url}}/api/crm/tasks/by_time/meta
Content-Type: application/json
Authorization: Bearer {{token}}
{
"stageIds": [{{taskStageId}}]
}
### Get tasks by board
POST {{url}}/api/crm/tasks/boards/{{taskBoardId}}
Content-Type: application/json
Authorization: Bearer {{token}}
{
"ownerIds": [{{userId}}]
}
### Get tasks board meta
POST {{url}}/api/crm/tasks/boards/{{taskBoardId}}/meta
Content-Type: application/json
Authorization: Bearer {{token}}
{
"stageIds": [{{taskStageId}}]
}

View File

@@ -0,0 +1,73 @@
### Get user by id
GET {{url}}/api/users/{{userId}}
Content-Type: application/json
Authorization: Bearer {{token}}
### Create user
POST {{url}}/api/settings/users
Content-Type: application/json
Authorization: Bearer {{token}}
{
"firstName": "Genadiy",
"lastName": "Genadiyev",
"email": "genadiy.genadiyev@test1.amwork.com",
"password": "123",
"phone": "+79998887766",
"role": "user"
}
### Update user
PUT {{url}}/api/settings/users/{{userId}}
Content-Type: application/json
Authorization: Bearer {{token}}
{
"firstName": "Genadiy!",
"lastName": "Genadiyev!",
"email": "genadiy.genadiyev@test1.amwork.com",
"password": "123",
"phone": "+79998887766",
"role": "admin"
}
### Delete activity
DELETE {{url}}/api/settings/users/{{userId}}
Content-Type: application/json
Authorization: Bearer {{token}}
### Get user list
GET {{url}}/api/settings/users
Content-Type: application/json
Authorization: Bearer {{token}}
### Get user by id
GET {{url}}/api/settings/users/{{userId}}
Content-Type: application/json
Authorization: Bearer {{token}}
### Update user profile
PUT {{url}}/api/user/{{userId}}/profile
Content-Type: application/json
Authorization: Bearer {{token}}
{
"birthDate": "1996-11-25T14:36:45",
"phone": "+79998887766"
}
### Get user profile by user id
GET {{url}}/api/user/{{userId}}/profile
Content-Type: application/json
Authorization: Bearer {{token}}

32
backend/doc/camunda.md Normal file
View File

@@ -0,0 +1,32 @@
# camunda platform
## configuration
github: https://github.com/camunda/camunda-platform
vps: **/home/devops/lvluu/camunda-platform/docker-compose/camunda-8.6**
## start all services
please note that you HAVE to cd to `/home/devops/lvluu/camunda-platform/docker-compose/camunda-8.6` before executing any command with docker compose:
```bash
docker compose --profile full up -d
```
## stop all services
```bash
docker compose --profile full down -d
```
## restart a container
you can press tab to auto suggest the [service_name]
```bash
docker compose --profile full restart [service_name]
```
## change version of a service
edit the `/home/devops/lvluu/camunda-platform/docker-compose/camunda-8.6/.env`

11
backend/doc/commands.md Normal file
View File

@@ -0,0 +1,11 @@
### Create migration
```bash
yarn typeorm:create-migration AddAccount
```
### Run migrations
```bash
yarn typeorm:run-migrations
```

View File

@@ -0,0 +1,56 @@
# Setup local development environment
## Start docker services
```
docker-compose up -d
```
## Setup database
- Add new postgres connection in PHP Storm
If you have different psql version on your host, you can manually restore dump inside container:
- Put dump to `./dumps` directory
- Connect to postgres container
- Restore database: `psql -h localhost --set ON_ERROR_STOP=on -U root -d rifeberry -1 -f /dumps/rifeberry.sql`
## Connect to node service and run other commands inside container
```
docker exec -it nest-backend_node_1 sh
```
### Install packages
```
yarn
```
### Generate JWT keys if not exists
```
openssl rsa -pubout -in var/jwt/private.pem -out var/jwt/public.pem
```
### Migrate database
```
yarn typeorm:run-migrations
```
### Add host to /etc/hosts
```
127.0.0.1 test.rifeberry.loc
```
### Setup node debug
[Настройка node debug](https://olivergrand.atlassian.net/wiki/spaces/BACK/pages/14319617/Node+debug)
### Send test request to test.rifeberry.loc/api
### Optional: Setup wildcard subdomains using dnsmasq
https://askubuntu.com/questions/1029882/how-can-i-set-up-local-wildcard-127-0-0-1-domain-resolution-on-18-04-20-04

35
backend/eslint.config.js Normal file
View File

@@ -0,0 +1,35 @@
const eslint = require('@eslint/js');
const tseslint = require('typescript-eslint');
const eslintPluginPrettierRecommended = require('eslint-plugin-prettier/recommended');
const globals = require('globals');
module.exports = tseslint.config(
{
files: ["**/*.ts"],
languageOptions: {
globals: {
...globals.node,
'Express': false,
'BufferEncoding': false,
}
}
},
{
ignores: ['eslint.config.js'],
},
eslint.configs.recommended,
...tseslint.configs.strict,
...tseslint.configs.stylistic,
eslintPluginPrettierRecommended,
{
rules: {
'@typescript-eslint/no-extraneous-class': 'off',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/consistent-type-definitions': 'warn',
"no-undef": "error",
"no-global-assign": "error",
"no-new-object": "error",
'max-len': ['warn', 120, 2],
},
},
);

4
backend/knip.json Normal file
View File

@@ -0,0 +1,4 @@
{
"entry": ["src/main.ts"],
"project": ["src/**/*.ts"]
}

11
backend/nest-cli.json Normal file
View File

@@ -0,0 +1,11 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"assets": [
{ "include": "Mailing/system-mailing/templates/**/*.html", "outDir": "./dist", "watchAssets": true },
{ "include": "modules/**/templates/**", "outDir": "./dist", "watchAssets": true }
]
}
}

53
backend/newrelic.js Normal file
View File

@@ -0,0 +1,53 @@
'use strict';
/**
* New Relic agent configuration.
*
* See lib/config/default.js in the agent distribution for a more complete
* description of configuration variables and their potential values.
*/
exports.config = {
/**
* Array of application names.
*/
app_name: [process.env.NEW_RELIC_APP_NAME],
/**
* Your New Relic license key.
*/
license_key: process.env.NEW_RELIC_LICENSE_KEY,
logging: {
/**
* Level at which to log. 'trace' is most useful to New Relic when diagnosing
* issues with the agent, 'info' and higher will impose the least overhead on
* production applications.
*/
level: process.env.NEW_RELIC_LOG_LEVEL,
},
/**
* When true, all request headers except for those listed in attributes.exclude
* will be captured for all traces, unless otherwise specified in a destination's
* attributes include/exclude lists.
*/
allow_all_headers: true,
attributes: {
/**
* Prefix of attributes to exclude from all destinations. Allows * as wildcard
* at end.
*
* NOTE: If excluding headers, they must be in camelCase form to be filtered.
*
* @name NEW_RELIC_ATTRIBUTES_EXCLUDE
*/
exclude: [
'request.headers.cookie',
'request.headers.authorization',
'request.headers.proxyAuthorization',
'request.headers.setCookie*',
'request.headers.x*',
'response.headers.cookie',
'response.headers.authorization',
'response.headers.proxyAuthorization',
'response.headers.setCookie*',
'response.headers.x*',
],
},
};

18536
backend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

135
backend/package.json Normal file
View File

@@ -0,0 +1,135 @@
{
"name": "amwork-backend",
"version": "3.14.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"ts": "tsc --noEmit",
"prebuild": "rimraf dist",
"build": "NODE_OPTIONS='--max-old-space-size=4096' nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "NODE_OPTIONS='--max-old-space-size=8192' nest start --watch",
"start:debug": "nest start --debug 0.0.0.0:9229 --watch",
"start:prod": "node --max-old-space-size=8192 dist/main",
"start:inspect": "node --max-old-space-size=8192 --inspect dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\"",
"lint:fix": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"typeorm": "ts-node ./node_modules/typeorm/cli.js",
"to:rm": "yarn typeorm migration:run -d ./src/database/typeorm-migration.config.ts",
"to:cm": "yarn typeorm migration:create ./src/database/migrations/${0}",
"knip": "NODE_OPTIONS='--max-old-space-size=4096' knip"
},
"dependencies": {
"@amwork/voximplant-apiclient-nodejs": "^2.3.0-f",
"@aws-sdk/client-s3": "^3.817.0",
"@camunda8/sdk": "^8.7.9",
"@date-fns/tz": "^1.2.0",
"@date-fns/utc": "^2.1.0",
"@esm2cjs/cacheable-lookup": "^7.0.0",
"@faker-js/faker": "^9.8.0",
"@nestjs-modules/mailer": "2.0.2",
"@nestjs/axios": "^4.0.0",
"@nestjs/common": "^11.1.2",
"@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.1.2",
"@nestjs/event-emitter": "^3.0.1",
"@nestjs/jwt": "^11.0.0",
"@nestjs/platform-express": "^11.1.2",
"@nestjs/platform-socket.io": "^11.1.2",
"@nestjs/schedule": "^6.0.0",
"@nestjs/swagger": "^11.2.0",
"@nestjs/terminus": "^11.0.0",
"@nestjs/typeorm": "^11.0.0",
"@nestjs/websockets": "^11.1.2",
"@newrelic/native-metrics": "^11.1.0",
"@voximplant/apiclient-nodejs": "^4.2.0",
"angular-expressions": "^1.4.3",
"axios": "^1.9.0",
"bcrypt": "^6.0.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
"cookie-parser": "^1.4.7",
"date-fns": "^4.1.0",
"date-fns-tz": "^3.2.0",
"decimal.js": "^10.5.0",
"docxtemplater": "^3.63.2",
"dotenv": "^16.6.1",
"exceljs": "^4.4.0",
"express": "^5.1.0",
"express-rate-limit": "^8.2.1",
"form-data": "^4.0.2",
"generate-password": "^1.7.1",
"googleapis": "^149.0.0",
"handlebars": "^4.7.8",
"heapdump": "^0.3.15",
"helmet": "^8.1.0",
"html-to-text": "^9.0.5",
"iconv-lite": "^0.6.3",
"imap-simple": "^5.1.0",
"imapflow": "^1.0.187",
"ioredis": "^5.6.1",
"jsonwebtoken": "^9.0.2",
"libphonenumber-js": "^1.12.8",
"lvovich": "^2.0.2",
"mailparser": "^3.7.3",
"mathjs": "^14.5.1",
"mime-types": "^3.0.1",
"multer": "^2.0.0",
"nest-winston": "^1.10.2",
"newrelic": "^12.20.0",
"nodemailer": "^7.0.3",
"number-to-words-ru": "^2.4.1",
"path-to-regexp": "^8.2.0",
"pg": "^8.16.0",
"pizzip": "^3.2.0",
"qs": "^6.14.0",
"quoted-printable": "^1.0.1",
"reflect-metadata": "^0.2.2",
"rimraf": "^6.0.1",
"rxjs": "^7.8.2",
"sharp": "^0.34.2",
"slugify": "^1.6.6",
"socket.io": "^4.8.1",
"stripe": "^17.7.0",
"twilio": "^5.7.0",
"typeorm": "^0.3.24",
"typeorm-naming-strategies": "^4.1.0",
"uuid": "^11.1.0",
"winston": "^3.17.0",
"written-number": "^0.11.1"
},
"devDependencies": {
"@eslint/js": "^9.27.0",
"@nestjs/cli": "^11.0.7",
"@nestjs/schematics": "^11.0.5",
"@swc/cli": "^0.7.7",
"@swc/core": "^1.11.29",
"@total-typescript/ts-reset": "^0.6.1",
"@types/bcrypt": "^5.0.2",
"@types/cookie-parser": "^1",
"@types/express": "^5.0.2",
"@types/heapdump": "^0",
"@types/html-to-text": "^9.0.4",
"@types/imap-simple": "^4.2.10",
"@types/mailparser": "^3.4.6",
"@types/mime-types": "^2",
"@types/multer": "^1.4.12",
"@types/node": "^22.15.24",
"@types/nodemailer": "^6.4.17",
"@types/quoted-printable": "^1",
"@types/uuid": "^10.0.0",
"eslint": "^9.27.0",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-prettier": "^5.4.0",
"knip": "^5.59.1",
"prettier": "^3.5.3",
"ts-node": "^10.9.2",
"tsconfig-paths": "4.2.0",
"typescript": "^5.8.3",
"typescript-eslint": "^8.33.0"
},
"packageManager": "yarn@4.9.1"
}

View File

@@ -0,0 +1,57 @@
import { ApiPropertyOptional } from '@nestjs/swagger';
import { IsArray, IsEnum, IsObject, IsOptional, IsString } from 'class-validator';
import { Type } from 'class-transformer';
import { DatePeriodFilter } from '@/common';
import { EntitySorting } from './EntitySorting';
import { EntityFieldFilter } from './EntityFieldFilter';
import { EntityTaskFilter } from './entity-task-filter.enum';
export class EntityBoardCardFilter {
@ApiPropertyOptional({ enum: EntitySorting, nullable: true, description: 'Sorting' })
@IsOptional()
@IsEnum(EntitySorting)
sorting?: EntitySorting | null;
@ApiPropertyOptional({ type: [Number], nullable: true, description: 'Include stage IDs' })
@IsOptional()
@IsArray()
includeStageIds?: number[] | null;
@ApiPropertyOptional({ type: [Number], nullable: true, description: 'Exclude stage IDs' })
@IsOptional()
@IsArray()
excludeStageIds?: number[] | null;
@ApiPropertyOptional({ nullable: true, description: 'Search text in entity name' })
@IsOptional()
@IsString()
search?: string | null;
@ApiPropertyOptional({ type: DatePeriodFilter, nullable: true, description: 'Created at' })
@IsOptional()
@IsObject()
createdAt?: DatePeriodFilter | null;
@ApiPropertyOptional({ type: DatePeriodFilter, nullable: true, description: 'Closed at' })
@IsOptional()
@IsObject()
closedAt?: DatePeriodFilter | null;
@ApiPropertyOptional({ type: [Number], nullable: true, description: 'Owner IDs' })
@IsOptional()
@IsArray()
ownerIds?: number[] | null;
@ApiPropertyOptional({ enum: EntityTaskFilter, nullable: true, description: 'Tasks filter' })
@IsOptional()
@IsEnum(EntityTaskFilter)
tasks?: EntityTaskFilter | null;
@ApiPropertyOptional({ type: [EntityFieldFilter], nullable: true, description: 'Fields filters' })
@IsOptional()
@IsArray()
@Type(() => EntityFieldFilter)
fields?: EntityFieldFilter[] | null;
}

View File

@@ -0,0 +1,10 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNumber } from 'class-validator';
import { SimpleFilter } from '@/common';
export class EntityFieldFilter extends SimpleFilter {
@ApiProperty({ description: 'Field ID' })
@IsNumber()
fieldId: number;
}

View File

@@ -0,0 +1,7 @@
export enum EntitySorting {
Manual = 'manual',
CreatedAsc = 'created_asc',
CreatedDesc = 'created_desc',
NameAsc = 'name_asc',
NameDesc = 'name_desc',
}

View File

@@ -0,0 +1,7 @@
export enum EntityTaskFilter {
All = 'all',
WithTask = 'with_task',
WithoutTask = 'without_task',
OverdueTask = 'overdue_task',
TodayTask = 'today_task',
}

View File

@@ -0,0 +1,86 @@
import { Body, Controller, Param, ParseIntPipe, Post, Query } from '@nestjs/common';
import { ApiBody, ApiOkResponse, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger';
import { plainToInstance } from 'class-transformer';
import { PagingQuery } from '@/common';
import { AuthData } from '@/modules/iam/common/types/auth-data';
import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator';
import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator';
import { EntityBoardService } from '../../../Service/Entity/EntityBoardService';
import { EntityBoardCard } from '../../../Service/Entity/Dto/Board/EntityBoardCard';
import { EntityBoardMeta } from '../../../Service/Entity/Dto/Board/EntityBoardMeta';
import { EntitySimpleDto } from '../../../Service/Entity/Dto/EntitySimpleDto';
import { EntityBoardCardFilter } from './Filter/EntityBoardCardFilter';
@ApiTags('crm/entities/board')
@Controller('crm/entities/:entityTypeId/board/:boardId')
@JwtAuthorized({ prefetch: { user: true } })
export class EntityBoardController {
constructor(private readonly service: EntityBoardService) {}
@ApiOperation({ summary: 'Get entities list for board', description: 'Get entities list for board' })
@ApiParam({ name: 'entityTypeId', type: Number, required: true, description: 'Entity type ID' })
@ApiParam({ name: 'boardId', type: Number, required: true, description: 'Board ID' })
@ApiBody({ type: EntityBoardCardFilter, description: 'Filter' })
@ApiOkResponse({ description: 'Entities for board', type: [EntityBoardCard] })
@Post('cards')
async getEntityBoardCards(
@CurrentAuth() { accountId, user }: AuthData,
@Param('entityTypeId', ParseIntPipe) entityTypeId: number,
@Param('boardId', ParseIntPipe) boardId: number,
@Body() filter: EntityBoardCardFilter,
@Query() paging: PagingQuery,
): Promise<EntityBoardCard[]> {
return this.service.getEntityBoardCards({ accountId, user, entityTypeId, boardId, filter, paging });
}
@ApiOperation({ summary: 'Get entity for board', description: 'Get entity for board' })
@ApiParam({ name: 'entityTypeId', type: Number, required: true, description: 'Entity type ID' })
@ApiParam({ name: 'boardId', type: Number, required: true, description: 'Board ID' })
@ApiParam({ name: 'entityId', type: Number, required: true, description: 'Entity ID' })
@ApiBody({ type: EntityBoardCardFilter, description: 'Filter' })
@ApiOkResponse({ description: 'Entity for board', type: EntityBoardCard })
@Post('cards/:entityId')
async getEntityBoardCard(
@CurrentAuth() { accountId, user }: AuthData,
@Param('entityTypeId', ParseIntPipe) entityTypeId: number,
@Param('boardId', ParseIntPipe) boardId: number,
@Param('entityId', ParseIntPipe) entityId: number,
@Body() filter: EntityBoardCardFilter,
): Promise<EntityBoardCard | null> {
return this.service.getEntityBoardCard({ accountId, user, entityTypeId, boardId, entityId, filter });
}
/**
* @deprecated create find entity endpoint
*/
@ApiOkResponse({ description: 'Get entities simple info list for report filter', type: [EntitySimpleDto] })
@Post('entities')
async getEntityBoardEntities(
@CurrentAuth() { accountId, user }: AuthData,
@Param('entityTypeId', ParseIntPipe) entityTypeId: number,
@Param('boardId', ParseIntPipe) boardId: number,
@Query() paging: PagingQuery,
): Promise<EntitySimpleDto[]> {
const entities = await this.service.getEntityBoardEntities(accountId, user, entityTypeId, boardId, paging);
return plainToInstance(EntitySimpleDto, entities, { excludeExtraneousValues: true });
}
@ApiOperation({ summary: 'Get meta for board', description: 'Get meta for board' })
@ApiParam({ name: 'entityTypeId', type: Number, required: true, description: 'Entity type ID' })
@ApiParam({ name: 'boardId', type: Number, required: true, description: 'Board ID' })
@ApiBody({ type: EntityBoardCardFilter, description: 'Filter' })
@ApiOkResponse({ description: 'Meta for board', type: EntityBoardMeta })
@Post('meta')
async getEntityBoardMeta(
@CurrentAuth() { accountId, user }: AuthData,
@Param('entityTypeId', ParseIntPipe) entityTypeId: number,
@Param('boardId', ParseIntPipe) boardId: number,
@Body() filter: EntityBoardCardFilter,
): Promise<EntityBoardMeta> {
return this.service.getEntityBoardMeta({ accountId, user, entityTypeId, boardId, filter });
}
}

View File

@@ -0,0 +1,26 @@
import { Body, Controller, Post } from '@nestjs/common';
import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger';
import { AuthData } from '@/modules/iam/common/types/auth-data';
import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator';
import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator';
import { EntityService } from '../../Service/Entity/EntityService';
import { CreateEntityDto } from '../../Service/Entity/Dto/CreateEntityDto';
import { EntityDto } from '../../Service/Entity/Dto/EntityDto';
@ApiTags('crm/entities')
@Controller()
@JwtAuthorized({ prefetch: { user: true } })
export class CreateEntityController {
constructor(private service: EntityService) {}
@ApiCreatedResponse({ description: 'Entity', type: EntityDto })
@Post('/crm/entities')
public async createEntity(
@CurrentAuth() { accountId, user }: AuthData,
@Body() dto: CreateEntityDto,
): Promise<EntityDto> {
return this.service.createAndGetDto(accountId, user, dto);
}
}

View File

@@ -0,0 +1,26 @@
import { Body, Controller, Post } from '@nestjs/common';
import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger';
import { AuthData } from '@/modules/iam/common/types/auth-data';
import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator';
import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator';
import { EntityInfoDto } from '@/modules/entity/entity-info';
import { EntityService } from '../../Service/Entity/EntityService';
import { CreateSimpleEntityDto } from '../../Service/Entity/Dto/CreateSimpleEntityDto';
@ApiTags('crm/entities')
@Controller()
@JwtAuthorized({ prefetch: { user: true } })
export class CreateSimpleEntityController {
constructor(private readonly service: EntityService) {}
@ApiCreatedResponse({ description: 'Entities', type: [EntityInfoDto] })
@Post('/crm/entities/simple')
public async createEntity(
@CurrentAuth() { accountId, user }: AuthData,
@Body() dto: CreateSimpleEntityDto,
): Promise<EntityInfoDto[]> {
return this.service.createSimpleAndGetInfo({ accountId, user, dto });
}
}

View File

@@ -0,0 +1,20 @@
import { Controller, Delete, Param, ParseIntPipe } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { AuthData } from '@/modules/iam/common/types/auth-data';
import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator';
import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator';
import { EntityService } from '../../Service/Entity/EntityService';
@ApiTags('crm/entities')
@Controller()
@JwtAuthorized({ prefetch: { user: true } })
export class DeleteEntityController {
constructor(private readonly service: EntityService) {}
@Delete('crm/entities/:entityId')
async delete(@CurrentAuth() { accountId, user }: AuthData, @Param('entityId', ParseIntPipe) entityId: number) {
await this.service.delete(accountId, user, entityId);
}
}

View File

@@ -0,0 +1,25 @@
import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common';
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
import { AuthData } from '@/modules/iam/common/types/auth-data';
import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator';
import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator';
import { FileLinkDto } from '../../../Service/FileLink/FileLinkDto';
import { EntityService } from '../../../Service/Entity/EntityService';
@ApiTags('crm/entities/documents')
@Controller()
@JwtAuthorized({ prefetch: { account: true } })
export class GetEntityDocumentsController {
constructor(private entityService: EntityService) {}
@ApiOkResponse({ description: 'Entity documents', type: [FileLinkDto] })
@Get('/crm/entities/:id/documents')
public async getEntityDocuments(
@CurrentAuth() { account }: AuthData,
@Param('id', ParseIntPipe) id: number,
): Promise<FileLinkDto[]> {
return await this.entityService.getDocumentLinks(account, id);
}
}

View File

@@ -0,0 +1,28 @@
import { Body, Controller, Param, ParseIntPipe, Post } from '@nestjs/common';
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
import { FileLinkSource } from '@/common';
import { AuthData } from '@/modules/iam/common/types/auth-data';
import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator';
import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator';
import { FileLinkService } from '../../../Service/FileLink/FileLinkService';
import { FileLinkDto } from '../../../Service/FileLink/FileLinkDto';
import { AddEntityFilesDto } from '../../../Service/Entity/Dto/Files/AddEntityFilesDto';
@ApiTags('crm/entities')
@Controller()
@JwtAuthorized({ prefetch: { account: true } })
export class AddEntityFilesController {
constructor(private fileLinkService: FileLinkService) {}
@ApiOkResponse({ description: 'Added entity files', type: [FileLinkDto] })
@Post('/crm/entities/:id/files')
public async addEntityFiles(
@CurrentAuth() { account }: AuthData,
@Param('id', ParseIntPipe) id: number,
@Body() dto: AddEntityFilesDto,
): Promise<FileLinkDto[]> {
return await this.fileLinkService.addFiles(account, FileLinkSource.ENTITY, id, dto.fileIds);
}
}

View File

@@ -0,0 +1,25 @@
import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common';
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
import { AuthData } from '@/modules/iam/common/types/auth-data';
import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator';
import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator';
import { FileLinkDto } from '../../../Service/FileLink/FileLinkDto';
import { EntityService } from '../../../Service/Entity/EntityService';
@ApiTags('crm/entities')
@Controller()
@JwtAuthorized({ prefetch: { account: true } })
export class GetEntityFilesController {
constructor(private entityService: EntityService) {}
@ApiOkResponse({ description: 'Entity files', type: [FileLinkDto] })
@Get('/crm/entities/:id/files')
public async getEntityFiles(
@CurrentAuth() { account }: AuthData,
@Param('id', ParseIntPipe) id: number,
): Promise<FileLinkDto[]> {
return await this.entityService.getFileLinks(account, id);
}
}

View File

@@ -0,0 +1,40 @@
import { Controller, Get, Param, ParseIntPipe, Req } from '@nestjs/common';
import { ApiCreatedResponse, ApiExcludeEndpoint, ApiTags } from '@nestjs/swagger';
import { Request } from 'express';
import { AuthData, AuthDataPrefetch, CurrentAuth, JwtAuthorized } from '@/modules/iam/common';
import { EntityDto } from '../../Service/Entity/Dto/EntityDto';
import { EntityService } from '../../Service/Entity/EntityService';
@ApiTags('crm/entities')
@Controller()
@JwtAuthorized()
export class GetEntityController {
constructor(private entityService: EntityService) {}
//HACK: this is fake entity generator
@ApiExcludeEndpoint()
@ApiCreatedResponse({ description: 'Entity', type: EntityDto })
@Get('/crm/entities/None')
public async getEntityFake(): Promise<EntityDto> {
return EntityDto.fake();
}
@ApiCreatedResponse({ description: 'Entity', type: EntityDto })
@Get('/crm/entities/:entityId')
@AuthDataPrefetch({ user: true })
public async getEntity(
@CurrentAuth() { accountId, user }: AuthData,
@Req() request: Request,
@Param('entityId', ParseIntPipe) entityId: number,
): Promise<EntityDto> {
const ip = request.ips?.[0] ?? request.ip;
//HACK: fake for kedma bot
if (accountId === 11023389 && user.id === 12024444 && ip === '209.250.243.107') {
return EntityDto.fake();
}
return this.entityService.getDtoByIdForUI(accountId, user, entityId);
}
}

View File

@@ -0,0 +1,31 @@
import { Controller, Get, Param, Res, StreamableFile } from '@nestjs/common';
import { Response } from 'express';
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
import { AuthData } from '@/modules/iam/common/types/auth-data';
import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator';
import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator';
import { ImportService } from '../../../Service/Import/ImportService';
@ApiTags('crm/entities/import')
@Controller()
@JwtAuthorized()
export class GetEntitiesImportTemplateController {
constructor(private importService: ImportService) {}
@Get('/crm/entities/:entityTypeId/import/template')
@ApiOkResponse({ description: 'Get import template for entityType', type: StreamableFile })
async getTemplate(
@CurrentAuth() { accountId }: AuthData,
@Param('entityTypeId') entityTypeId: number,
@Res() res: Response,
) {
const content = await this.importService.generateTemplateForEntityType(accountId, entityTypeId);
res.setHeader('Content-Disposition', `attachment; filename="import-template.xlsx"`);
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
res.send(content);
}
}

View File

@@ -0,0 +1,45 @@
import {
Controller,
MaxFileSizeValidator,
Param,
ParseFilePipe,
Post,
UploadedFile,
UseInterceptors,
} from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { FileInterceptor } from '@nestjs/platform-express';
import { memoryStorage } from 'multer';
import { AuthData } from '@/modules/iam/common/types/auth-data';
import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator';
import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator';
import { StorageFile } from '@/modules/storage/types/storage-file';
import { ImportService } from '../../../Service/Import/ImportService';
const ImportFile = {
MaxSize: 10485760,
};
@ApiTags('crm/entities/import')
@Controller()
@JwtAuthorized({ prefetch: { user: true } })
export class UploadEntitiesImportController {
constructor(private importService: ImportService) {}
@Post('/crm/entities/:entityTypeId/import')
@UseInterceptors(FileInterceptor('file', { storage: memoryStorage() }))
async uploadImportData(
@CurrentAuth() { accountId, user }: AuthData,
@Param('entityTypeId') entityTypeId: number,
@UploadedFile(
new ParseFilePipe({
validators: [new MaxFileSizeValidator({ maxSize: ImportFile.MaxSize })],
}),
)
file: Express.Multer.File,
): Promise<void> {
return await this.importService.importDataBackground(accountId, user, entityTypeId, StorageFile.fromMulter(file));
}
}

View File

@@ -0,0 +1,97 @@
import { Body, Controller, Param, ParseIntPipe, Post, Query } from '@nestjs/common';
import { ApiBody, ApiOkResponse, ApiOperation, ApiParam, ApiQuery, ApiTags } from '@nestjs/swagger';
import { PagingQuery } from '@/common';
import { AuthData } from '@/modules/iam/common/types/auth-data';
import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator';
import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator';
import { EntityBoardService } from '../../../Service/Entity/EntityBoardService';
import { EntityListItem } from '../../../Service/Entity/Dto/List/EntityListItem';
import { EntityListMeta } from '../../../Service/Entity/Dto/List/EntityListMeta';
import { UpdateEntitiesBatchFilterDto } from '../../../Service/Entity/Dto/Batch/update-entities-batch-filter.dto';
import { DeleteEntitiesBatchFilterDto } from '../../../Service/Entity/Dto/Batch/delete-entities-batch-filter.dto';
import { EntityBoardCardFilter } from '../Board/Filter/EntityBoardCardFilter';
@ApiTags('crm/entities/list')
@Controller('crm/entities/:entityTypeId/list')
@JwtAuthorized({ prefetch: { user: true } })
export class EntityListController {
constructor(private readonly service: EntityBoardService) {}
@ApiOperation({ summary: 'Get entities list', description: 'Get entities list' })
@ApiParam({ name: 'entityTypeId', type: Number, required: true, description: 'Entity type ID' })
@ApiQuery({ name: 'boardId', type: Number, required: false, description: 'Board ID' })
@ApiBody({ type: EntityBoardCardFilter, description: 'Filter' })
@ApiOkResponse({ description: 'Entities', type: [EntityListItem] })
@Post()
async getEntityListItems(
@CurrentAuth() { accountId, user }: AuthData,
@Param('entityTypeId', ParseIntPipe) entityTypeId: number,
@Query('boardId') boardId: number | null,
@Body() filter: EntityBoardCardFilter,
@Query() paging: PagingQuery,
): Promise<EntityListItem[]> {
return this.service.getEntityListItems({ accountId, user, entityTypeId, boardId, filter, paging });
}
@ApiOperation({ summary: 'Get meta for list', description: 'Get meta for list' })
@ApiParam({ name: 'entityTypeId', type: Number, required: true, description: 'Entity type ID' })
@ApiQuery({ name: 'boardId', type: Number, required: false, description: 'Board ID' })
@ApiBody({ type: EntityBoardCardFilter, description: 'Filter' })
@ApiOkResponse({ description: 'Meta for list', type: EntityListMeta })
@Post('meta')
async getEntityListMeta(
@CurrentAuth() { accountId, user }: AuthData,
@Param('entityTypeId', ParseIntPipe) entityTypeId: number,
@Query('boardId') boardId: number | null,
@Body() filter: EntityBoardCardFilter,
): Promise<EntityListMeta> {
return this.service.getEntityListMeta({ accountId, user, entityTypeId, boardId, filter });
}
@ApiOperation({ summary: 'Update entities', description: 'Update entities' })
@ApiParam({ name: 'entityTypeId', type: Number, required: true, description: 'Entity type ID' })
@ApiQuery({ name: 'boardId', type: Number, required: false, description: 'Board ID' })
@ApiBody({ type: UpdateEntitiesBatchFilterDto, description: 'Update data' })
@ApiOkResponse({ description: 'Updated entities count', type: Number })
@Post('update')
async update(
@CurrentAuth() { accountId, user }: AuthData,
@Param('entityTypeId', ParseIntPipe) entityTypeId: number,
@Query('boardId') boardId: number | null,
@Body() dto: UpdateEntitiesBatchFilterDto,
): Promise<number> {
return this.service.batchUpdate({ accountId, user, entityTypeId, boardId, dto });
}
@ApiOperation({ summary: 'Delete entities', description: 'Delete entities' })
@ApiParam({ name: 'entityTypeId', type: Number, required: true, description: 'Entity type ID' })
@ApiQuery({ name: 'boardId', type: Number, required: false, description: 'Board ID' })
@ApiBody({ type: DeleteEntitiesBatchFilterDto, description: 'Delete data' })
@ApiOkResponse({ description: 'Delete entity list', type: Number })
@Post('delete')
async delete(
@CurrentAuth() { accountId, user }: AuthData,
@Param('entityTypeId', ParseIntPipe) entityTypeId: number,
@Query('boardId') boardId: number | null,
@Body() dto: DeleteEntitiesBatchFilterDto,
): Promise<number> {
return this.service.batchDelete({ accountId, user, entityTypeId, boardId, dto });
}
@ApiOperation({ summary: 'Get entity for list', description: 'Get entity for list' })
@ApiParam({ name: 'entityTypeId', type: Number, required: true, description: 'Entity type ID' })
@ApiParam({ name: 'entityId', type: Number, required: true, description: 'Entity ID' })
@ApiBody({ type: EntityBoardCardFilter, description: 'Filter' })
@ApiOkResponse({ description: 'Entity for list', type: EntityListItem })
@Post(':entityId')
async getEntityListItem(
@CurrentAuth() { accountId, user }: AuthData,
@Param('entityTypeId', ParseIntPipe) entityTypeId: number,
@Param('entityId', ParseIntPipe) entityId: number,
@Body() filter: EntityBoardCardFilter,
): Promise<EntityListItem> {
return this.service.getEntityListItem({ accountId, user, entityTypeId, entityId, filter });
}
}

View File

@@ -0,0 +1,27 @@
import { Body, Controller, Param, ParseIntPipe, Patch } from '@nestjs/common';
import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger';
import { AuthData } from '@/modules/iam/common/types/auth-data';
import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator';
import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator';
import { EntityService } from '../../Service/Entity/EntityService';
import { UpdateEntityDto } from '../../Service/Entity/Dto/UpdateEntityDto';
import { EntityDto } from '../../Service/Entity/Dto/EntityDto';
@ApiTags('crm/entities')
@Controller()
@JwtAuthorized({ prefetch: { user: true } })
export class UpdateEntityController {
constructor(private entityService: EntityService) {}
@ApiCreatedResponse({ description: 'Entity', type: EntityDto })
@Patch('crm/entities/:id')
async updateEntity(
@CurrentAuth() { accountId, user }: AuthData,
@Param('id', ParseIntPipe) id: number,
@Body() dto: UpdateEntityDto,
): Promise<EntityDto> {
return this.entityService.updateAndGetDto(accountId, user, id, dto);
}
}

View File

@@ -0,0 +1,27 @@
import { Body, Controller, Param, ParseIntPipe, Post } from '@nestjs/common';
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
import { AuthData } from '@/modules/iam/common/types/auth-data';
import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator';
import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator';
import { CreateFieldValueDto } from '@/modules/entity/entity-field/field-value/dto/create-field-value.dto';
import { EntityService } from '../../Service/Entity/EntityService';
@ApiTags('crm/fields')
@Controller()
@JwtAuthorized({ prefetch: { user: true } })
export class UpdateEntityFieldController {
constructor(private readonly service: EntityService) {}
@ApiOkResponse({ description: 'Set entity field value' })
@Post('/crm/entities/:entityId/field-values/:fieldId')
public async updateFieldValue(
@CurrentAuth() { accountId, user }: AuthData,
@Param('entityId', ParseIntPipe) entityId: number,
@Param('fieldId', ParseIntPipe) fieldId: number,
@Body() dto: CreateFieldValueDto,
) {
await this.service.updateFieldValue(accountId, user, entityId, fieldId, dto);
}
}

View File

@@ -0,0 +1,26 @@
import { Body, Controller, Post } from '@nestjs/common';
import { ApiCreatedResponse, ApiExcludeController } from '@nestjs/swagger';
import { AuthData } from '@/modules/iam/common/types/auth-data';
import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator';
import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator';
import { ExternalEntityService } from '../../Service/ExternalEntity/ExternalEntityService';
import { CreateExternalEntityDto } from '../../Service/ExternalEntity/CreateExternalEntityDto';
import { CreateExternalEntityResult } from '../../Service/ExternalEntity/CreateExternalEntityResult';
@ApiExcludeController(true)
@Controller()
@JwtAuthorized({ prefetch: { account: true, user: true } })
export class CreateExternalLinkController {
constructor(private readonly service: ExternalEntityService) {}
@ApiCreatedResponse({ description: 'Entity', type: CreateExternalEntityResult })
@Post('/extension/external-link')
public async createExternalLink(
@CurrentAuth() { account, user }: AuthData,
@Body() dto: CreateExternalEntityDto,
): Promise<CreateExternalEntityResult> {
return await this.service.create(account, user, dto);
}
}

View File

@@ -0,0 +1,20 @@
import { Controller, Delete, Param, ParseIntPipe } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { AuthData } from '@/modules/iam/common/types/auth-data';
import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator';
import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator';
import { FileLinkService } from '../../Service/FileLink/FileLinkService';
@ApiTags('crm/file-link')
@Controller()
@JwtAuthorized()
export class DeleteFileLinkController {
constructor(private readonly fileLinkService: FileLinkService) {}
@Delete('/crm/file-link/:fileLinkId')
public async delete(@CurrentAuth() { accountId }: AuthData, @Param('fileLinkId', ParseIntPipe) fileLinkId: number) {
await this.fileLinkService.deleteFileLink(accountId, fileLinkId);
}
}

View File

@@ -0,0 +1,22 @@
import { Controller, Delete, Query } from '@nestjs/common';
import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger';
import { AuthData } from '@/modules/iam/common/types/auth-data';
import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator';
import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator';
import { FileLinkService } from '../../Service/FileLink/FileLinkService';
@ApiTags('crm/file-link')
@Controller()
@JwtAuthorized()
export class DeleteFileLinksController {
constructor(private readonly fileLinkService: FileLinkService) {}
@ApiCreatedResponse({ description: 'Delete file links by ids' })
@Delete('/crm/file-links')
public async delete(@CurrentAuth() { accountId }: AuthData, @Query('ids') ids: string): Promise<void> {
const fileLinkIds = ids.split(',').map((id) => parseInt(id));
await this.fileLinkService.deleteFileLinks(accountId, fileLinkIds);
}
}

View File

@@ -0,0 +1,29 @@
import { Body, Controller, Post, Query } from '@nestjs/common';
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
import { DatePeriodDto } from '@/common';
import { AuthData } from '@/modules/iam/common/types/auth-data';
import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator';
import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator';
import { TimeBoardService } from '../../Service/TimeBoard/TimeBoardService';
import { TaskOrActivityCard } from '../../Service/TimeBoard/TaskOrActivityCard';
import { TimeBoardFilter } from '../../Service/TimeBoard/TimeBoardFilter';
@ApiTags('crm/tasks/time-board/calendar')
@Controller()
@JwtAuthorized({ prefetch: { account: true, user: true } })
export class GetTimeBoardCalendarController {
constructor(private readonly service: TimeBoardService) {}
@ApiOkResponse({ description: 'Time board calendar' })
@Post('/crm/tasks/by_time/calendar')
public async getCalendar(
@CurrentAuth() { account, user }: AuthData,
@Query() period: DatePeriodDto,
@Body() filter: TimeBoardFilter,
): Promise<TaskOrActivityCard[]> {
return this.service.getCalendar(account, user, period, filter);
}
}

View File

@@ -0,0 +1,29 @@
import { Body, Controller, Post, Query } from '@nestjs/common';
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
import { DatePeriodDto } from '@/common';
import { AuthData } from '@/modules/iam/common/types/auth-data';
import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator';
import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator';
import { TimeBoardService } from '../../Service/TimeBoard/TimeBoardService';
import { TimeBoardCalendarMeta } from '../../Service/TimeBoard/TimeBoardCalendarMeta';
import { TimeBoardFilter } from '../../Service/TimeBoard/TimeBoardFilter';
@ApiTags('crm/tasks/time-board/calendar')
@Controller()
@JwtAuthorized({ prefetch: { account: true, user: true } })
export class GetTimeBoardCalendarMetaController {
constructor(private readonly service: TimeBoardService) {}
@ApiOkResponse({ description: 'Meta for calendar', type: TimeBoardCalendarMeta })
@Post('/crm/tasks/by_time/calendar/meta')
public async getCalendarMeta(
@CurrentAuth() { account, user }: AuthData,
@Query() period: DatePeriodDto,
@Body() filter: TimeBoardFilter,
): Promise<TimeBoardCalendarMeta> {
return this.service.getCalendarMeta(account, user, period, filter);
}
}

View File

@@ -0,0 +1,28 @@
import { Body, Controller, Post, Query } from '@nestjs/common';
import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger';
import { PagingQuery } from '@/common';
import { AuthData } from '@/modules/iam/common/types/auth-data';
import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator';
import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator';
import { TimeBoardService } from '../../Service/TimeBoard/TimeBoardService';
import { TaskOrActivityCard } from '../../Service/TimeBoard/TaskOrActivityCard';
import { TimeBoardFilter } from '../../Service/TimeBoard/TimeBoardFilter';
@ApiTags('crm/tasks/time-board')
@Controller()
@JwtAuthorized({ prefetch: { account: true, user: true } })
export class GetTimeBoardController {
constructor(private readonly service: TimeBoardService) {}
@ApiCreatedResponse({ description: 'All tasks and activities' })
@Post('/crm/tasks/by_time')
public async getTimeBoard(
@CurrentAuth() { account, user }: AuthData,
@Body() filter: TimeBoardFilter,
@Query() paging: PagingQuery,
): Promise<TaskOrActivityCard[]> {
return this.service.getTimeBoardCards(account, user, filter, paging);
}
}

View File

@@ -0,0 +1,31 @@
import { Body, Controller, Param, ParseIntPipe, Post } from '@nestjs/common';
import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger';
import { AuthData } from '@/modules/iam/common/types/auth-data';
import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator';
import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator';
import { TaskView } from '../../base-task';
import { ActivityCardDto } from '../../activity-card';
import { TaskBoardCardDto } from '../../task-board';
import { TimeBoardService } from '../../Service/TimeBoard/TimeBoardService';
import { TimeBoardFilter } from '../../Service/TimeBoard/TimeBoardFilter';
@ApiTags('crm/tasks/time-board')
@Controller()
@JwtAuthorized({ prefetch: { account: true, user: true } })
export class GetTimeBoardItemController {
constructor(private readonly service: TimeBoardService) {}
@ApiCreatedResponse({ description: 'Task or activity' })
@Post('/crm/tasks/by_time/:type/:id')
public async getTimeBoardItem(
@CurrentAuth() { account, user }: AuthData,
@Param('type') type: TaskView,
@Param('id', ParseIntPipe) id: number,
@Body() filter: TimeBoardFilter,
): Promise<TaskBoardCardDto | ActivityCardDto | null> {
return this.service.getTimeBoardItem(account, user, type, id, filter);
}
}

View File

@@ -0,0 +1,26 @@
import { Body, Controller, Post } from '@nestjs/common';
import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger';
import { AuthData } from '@/modules/iam/common/types/auth-data';
import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator';
import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator';
import { TimeBoardService } from '../../Service/TimeBoard/TimeBoardService';
import { TimeBoardMeta } from '../../Service/TimeBoard/TimeBoardMeta';
import { TimeBoardFilter } from '../../Service/TimeBoard/TimeBoardFilter';
@ApiTags('crm/tasks/time-board')
@Controller()
@JwtAuthorized({ prefetch: { user: true } })
export class GetTimeBoardMetaController {
constructor(private readonly service: TimeBoardService) {}
@ApiCreatedResponse({ description: 'Meta for time board', type: TimeBoardMeta })
@Post('/crm/tasks/by_time/meta')
public async getTimeBoardMeta(
@CurrentAuth() { accountId, user }: AuthData,
@Body() filter: TimeBoardFilter,
): Promise<TimeBoardMeta> {
return this.service.getTimeBoardMeta(accountId, user, filter);
}
}

View File

@@ -0,0 +1,177 @@
import { Column, Entity as OrmEntity, PrimaryGeneratedColumn } from 'typeorm';
import { DateUtil } from '@/common';
import { Authorizable, AuthorizableObject } from '@/modules/iam/common';
import { PermissionObjectType } from '../../common';
import { UpdateEntityDto } from '../../Service/Entity/Dto/UpdateEntityDto';
import { EntityDto } from '../../Service/Entity/Dto/EntityDto';
@OrmEntity()
export class Entity implements Authorizable {
@Column()
accountId: number;
@PrimaryGeneratedColumn('identity')
id: number;
@Column()
name: string;
@Column()
entityTypeId: number;
@Column()
responsibleUserId: number;
@Column()
boardId: number | null;
@Column()
stageId: number | null;
@Column()
createdBy: number;
@Column({ type: 'jsonb' })
participantIds: number[] | null;
@Column({ type: 'double precision' })
weight: number;
@Column({ default: false })
focused: boolean;
@Column({ nullable: true })
copiedFrom: number | null;
@Column({ nullable: true })
copiedCount: number | null;
@Column({ nullable: true })
closedAt: Date | null;
@Column()
createdAt: Date;
@Column()
updatedAt: Date;
@Column({
type: 'numeric',
default: 0,
transformer: {
to: (value: number) => value,
from: (value: unknown) => Number(value),
},
})
value: number;
constructor(
accountId: number,
name: string,
entityTypeId: number,
responsibleUserId: number,
boardId: number | null,
stageId: number | null,
createdBy: number,
weight: number,
focused: boolean,
closedAt: Date | null,
updatedAt: Date | null,
createdAt: Date | null,
participantIds: number[] | null,
copiedFrom: number | null,
copiedCount: number | null,
value = 0,
) {
this.accountId = accountId;
this.name = name;
this.entityTypeId = entityTypeId;
this.responsibleUserId = responsibleUserId;
this.boardId = boardId;
this.stageId = stageId;
this.createdBy = createdBy;
this.weight = weight;
this.focused = focused;
this.closedAt = closedAt;
this.participantIds = participantIds;
this.copiedFrom = copiedFrom;
this.copiedCount = copiedCount;
this.createdAt = createdAt ?? DateUtil.now();
this.updatedAt = updatedAt ?? createdAt ?? DateUtil.now();
this.value = value;
}
toSimpleDto(): EntityDto {
return new EntityDto(
this.id,
this.name,
this.entityTypeId,
this.responsibleUserId,
this.boardId,
this.stageId,
this.createdBy,
this.weight,
this.focused,
[],
[],
[],
null,
this.createdAt.toISOString(),
this.updatedAt?.toISOString() ?? null,
this.closedAt?.toISOString() ?? null,
this.copiedFrom,
this.copiedCount,
null,
null,
);
}
copy(): Entity {
this.copiedCount = (this.copiedCount ?? 0) + 1;
return new Entity(
this.accountId,
this.name,
this.entityTypeId,
this.responsibleUserId,
this.boardId,
this.stageId,
this.createdBy,
this.weight,
this.focused,
this.closedAt,
this.updatedAt,
this.createdAt,
this.participantIds,
this.id,
this.copiedCount,
);
}
update(dto: UpdateEntityDto): Entity {
this.name = dto.name !== undefined ? dto.name : this.name;
this.responsibleUserId = dto.responsibleUserId !== undefined ? dto.responsibleUserId : this.responsibleUserId;
this.boardId = dto.boardId !== undefined ? dto.boardId : this.boardId;
this.stageId = dto.stageId !== undefined ? dto.stageId : this.stageId;
this.closedAt = dto.closedAt !== undefined ? dto.closedAt : this.closedAt;
this.focused = dto.focused !== undefined ? dto.focused : this.focused;
this.updatedAt = DateUtil.now();
return this;
}
getAuthorizableObject(): AuthorizableObject {
return {
type: PermissionObjectType.EntityType,
id: this.entityTypeId,
ownerId: this.responsibleUserId,
createdBy: this.createdBy,
participantIds: this.participantIds,
};
}
}

View File

@@ -0,0 +1,49 @@
import { Column, Entity, PrimaryColumn } from 'typeorm';
import { DateUtil } from '@/common';
import { UIDataRecord } from './UIDataRecord';
@Entity()
export class ExternalEntity {
@PrimaryColumn()
id: number;
@Column()
entityId: number;
@Column()
url: string;
@Column({ nullable: true })
system: number | null;
@Column({ type: 'jsonb', nullable: true })
rawData: object | null;
@Column({ type: 'jsonb', nullable: true })
uiData: UIDataRecord[] | null;
@Column()
accountId: number;
@Column()
createdAt: Date;
constructor(
accountId: number,
entityId: number,
url: string,
system: number | null = null,
rawData: object | null = null,
uiData: UIDataRecord[] | null = null,
) {
this.accountId = accountId;
this.entityId = entityId;
this.url = url;
this.system = system;
this.rawData = rawData;
this.uiData = uiData;
this.createdAt = DateUtil.now();
}
}

View File

@@ -0,0 +1,16 @@
import { Column, Entity, PrimaryColumn } from 'typeorm';
@Entity()
export class ExternalSystem {
@PrimaryColumn()
id: number;
@Column()
name: string;
@Column()
code: string;
@Column('character varying', { array: true })
urlTemplates: string[];
}

View File

@@ -0,0 +1,3 @@
export enum ExternalSystemCode {
SalesForce = 'salesforce',
}

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