Compare commits
68 Commits
c5b2cb93f0
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 7f78691f91 | |||
| aef712ca1d | |||
| 77783549fa | |||
| be6e4a8d32 | |||
| af606ce9a7 | |||
| 7996617cf0 | |||
| 9e28dfd628 | |||
| c20e0ba684 | |||
| ced46e500d | |||
| c925d2e539 | |||
| 10035f3f25 | |||
| 19c396e9d1 | |||
| a13097bbd6 | |||
| 8aaac51b12 | |||
| b855d81817 | |||
| f66bc9fa8c | |||
| 6b222db890 | |||
| c82cc654f0 | |||
| c956bc0deb | |||
| 30d86c9775 | |||
| 4263d7118e | |||
| 0e8e0cba2e | |||
| 3a84740d9a | |||
| f35df6492c | |||
| 008bddb97a | |||
| cb77a55281 | |||
| 11445f0a07 | |||
| 8e23a06793 | |||
| c2a861041f | |||
| 1440b62373 | |||
| d93e4be6ef | |||
| 50d9fb2274 | |||
| 9ded147354 | |||
| 2768611a7c | |||
| b1a02d6b0d | |||
| 8b901e4d02 | |||
| 43b4f05894 | |||
| 7bef7d414a | |||
| 4cb20f6c0d | |||
| 95c947a587 | |||
| 8348692481 | |||
| 677fc5b384 | |||
| 8481a0e86f | |||
| b32d613a1b | |||
| b0668c7b2c | |||
| ff08da1330 | |||
| 29d647556c | |||
| a22d6ceb81 | |||
| eb6fbc3ce5 | |||
| 9565eda0fd | |||
| 8533682303 | |||
| e5c14bf5cc | |||
| e310411a4d | |||
| 0cd5a6a0cc | |||
| da71c20d6d | |||
| 263ede68b2 | |||
| d9283e3a90 | |||
| 5964a9b20d | |||
| 5715cbe9b1 | |||
| 2590a222b4 | |||
| 16a0c14149 | |||
| 9f61152d94 | |||
| a14bd490be | |||
| 47a5a2abb4 | |||
| 5763cb7d6c | |||
| 35d0d9f7cd | |||
| d4bd593f5b | |||
| 30dba5ae37 |
@@ -0,0 +1,75 @@
|
||||
NODE_ENV=production
|
||||
PORT=3500
|
||||
DB_NAME=dscDev
|
||||
DB_USER=postgres
|
||||
DB_PASS=1RWVdlUcZSf6PoFBQO8cprZk2DTkC89ttqeJN7aU2WQRDUEusK4z2FfNqNtV4I10
|
||||
DB_HOST=78.157.34.13
|
||||
DB_PORT=5432
|
||||
SMS_API_URL=https://api.sms.ir/v1
|
||||
SMS_API_KEY=QkkNxhyXZ6GtEf1soOTtikomO3mA4LaNQDH8mol8huDIwh00
|
||||
SMS_PATTERN_OTP=842088
|
||||
SMS_PATTERN_INVOICE=683883
|
||||
SMS_PATTERN_LOGIN=149141
|
||||
SMS_PATTERN_INVOICE_CREATED=829067
|
||||
SMS_PATTERN_ANNOUNCEMENT=752330
|
||||
SMS_PATTERN_WALLET_CHARGE=727726
|
||||
SMS_PATTERN_WALLET_DEDUCTION=360785
|
||||
SMS_PATTERN_TICKET_CREATED=853506
|
||||
SMS_PATTERN_TICKET_ANSWERED=179464
|
||||
SMS_PATTERN_TICKET_ASSIGNED_ADMIN=321927
|
||||
SMS_PATTERN_INVOICE_APPROVED=303807
|
||||
SMS_PATTERN_INVOICE_PAID=533817
|
||||
SMS_PATTERN_RECURRING_INVOICE_DRAFT=219287
|
||||
SMS_PATTERN_SUBSCRIPTION_CANCELLED=166142
|
||||
SMS_PATTERN_INVOICE_REMINDER=370266
|
||||
SMS_PATTERN_INVOICE_OVERDUE=160735
|
||||
SMS_PATTERN_PAYMENT_REMINDER=629772
|
||||
SMS_PATTERN_PAYMENT_CANCELLATION=197945
|
||||
SMS_PATTERN_BLOG_NEW_COMMENT=378881
|
||||
SMS_PATTERN_NEW_SERVICE_REVIEW=593446
|
||||
SMS_PATTERN_NEW_CUSTOMER=966214
|
||||
SMS_PATTERN_NEW_SUBSCRIPTION=757095
|
||||
SMS_PATTERN_NEW_TICKET_GLOBAL=536596
|
||||
SMS_PATTERN_NEW_CRITICISM=983724
|
||||
SMS_PATTERN_USER_PASSWORD=486237
|
||||
SMTP_HOST=smtps.danakcorp.com
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=no-reply-danakcorp.com
|
||||
SMTP_PASS=Mahyar.SR4@
|
||||
MAIL_FROM=no-reply <no-reply@danakcorp.com>
|
||||
EMAIL_SECRET=xDkdptbC2N7LoR2Aa4Q9U/aAGOi4DxG2748
|
||||
REDIS_URI=redis://localhost:6379/1
|
||||
CACHE_TTL=180000
|
||||
THROTTLE_TTL=300000
|
||||
THROTTLE_LIMIT=10
|
||||
JWT_SECRET_KEY=2ygxIB1PJhK/L2PToqR3+52gRmUIdPfwTguYlB7ZRCdRyvA/N36jjkjbYJLoMxEL\n6xGmk5CQmCmn9c0FsBlN1g
|
||||
JWT_ISSUER=console-danak
|
||||
ACCESS_TOKEN_EXPIRE=80000
|
||||
REFRESH_TOKEN_EXPIRE=6
|
||||
BUCKET_NAME=danak-project
|
||||
BUCKET_REGION=default
|
||||
BUCKET_URL=https://s3.ir-thr-at1.arvanstorage.ir
|
||||
BUCKET_ACCESS_KEY=4bd15218-60f1-4ce7-a7aa-3fd319b13372
|
||||
BUCKET_SECRET_KEY=de772e5bd62fb04fb3669e67e09ddb101db60c65d77eea66b215ce58275e6a76
|
||||
BUCKET_UPLOAD_URL=https://storage.danakcorp.com
|
||||
ZARINPAL_MERCHANT_ID=fc0b7503-bba5-40d1-9f9d-dae52ac02eef
|
||||
PARSIAN_LOGIN_ACCOUNT=uqXnB2q8rjkL6vN0xOh0
|
||||
SITE_URL=https://console.danakcorp.com
|
||||
API_URL=https://api.danakcorp.com
|
||||
CALLBACK_URL=https://api.danakcorp.com/payments/verify
|
||||
TELEGRAM_BOT_TOKEN=7839690719:AAGsyfwympR4rDLm2JCx43CQh52s1o865uY
|
||||
TELEGRAM_BOT_NAME=danak_logs_bot
|
||||
TELEGRAM_CHAT_ID=7801476800
|
||||
EXTERNAL_API_KEYS=uqXssnB2q8rs2soa9almm1jkL6vN0xOh0
|
||||
DMENU_BACKEND_URL=https://dmenu-api.danakcorp.com
|
||||
DMENU_USERNAME=danak@dsc.com
|
||||
DMENU_PASSWORD=DsCdAnAk?@ABC
|
||||
DKALA_BACKEND_URL=https://dkala-api.danakcorp.com
|
||||
DKALA_USERNAME=danak@dsc.com
|
||||
DKALA_PASSWORD=DsCdAnAk?@ABC
|
||||
DPAGE_BACKEND_URL=https://dpage-api.danakcorp.com
|
||||
DPAGE_USERNAME=danak@dsc.com
|
||||
DPAGE_PASSWORD=DsCdAnAk?@ABC
|
||||
DMAIL_BACKEND_URL=https://dmail-api.danakcorp.com
|
||||
DMAIL_USERNAME=danak@dsc.com
|
||||
DMAIL_PASSWORD=DsCdAnAk?@ABC
|
||||
+11
-7
@@ -2,10 +2,14 @@
|
||||
FROM node:22-alpine AS base
|
||||
|
||||
ENV TZ=Asia/Tehran
|
||||
# Use the public npm registry to avoid paid/unstable mirror outages.
|
||||
ENV NPM_CONFIG_REGISTRY=https://package-mirror.liara.ir/repository/npm
|
||||
|
||||
# Runflare build hosts cannot reach registry.npmjs.org; use their npm mirror.
|
||||
# RUN npm install -g pnpm@9.15.9 --registry=https://mirror-npm.runflare.com
|
||||
RUN npm install -g pnpm@9.15.9
|
||||
# Change Alpine repo
|
||||
RUN sed -i 's|https://dl-cdn.alpinelinux.org/alpine|https://mirror.de.velop.ir/alpine|g' /etc/apk/repositories
|
||||
|
||||
# Install pnpm globally (uses NPM_CONFIG_REGISTRY mirror above).
|
||||
RUN npm install -g pnpm@9.15.9 --registry=https://package-mirror.liara.ir/repository/npm
|
||||
|
||||
# Stage 2: Install all dependencies (including dev)
|
||||
FROM base AS deps
|
||||
@@ -15,9 +19,9 @@ COPY package.json pnpm-lock.yaml ./
|
||||
# @nestjs/cli and typescript are required for the build step.
|
||||
ENV NODE_ENV=development
|
||||
# RUN pnpm install --frozen-lockfile --loglevel info \
|
||||
# --registry=https://mirror-npm.runflare.com
|
||||
# --registry=https://package-mirror.liara.ir/repository/npm
|
||||
|
||||
RUN pnpm install --frozen-lockfile --loglevel info
|
||||
RUN pnpm install --frozen-lockfile --loglevel info --registry=https://package-mirror.liara.ir/repository/npm
|
||||
|
||||
|
||||
# Stage 3: Build
|
||||
@@ -32,8 +36,8 @@ FROM base AS prod-deps
|
||||
WORKDIR /temp-deps
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
# RUN pnpm install --prod --frozen-lockfile --loglevel info \
|
||||
# --registry=https://mirror-npm.runflare.com
|
||||
RUN pnpm install --prod --frozen-lockfile --loglevel info
|
||||
# --registry=https://package-mirror.liara.ir/repository/npm
|
||||
RUN pnpm install --prod --frozen-lockfile --loglevel info --registry=https://package-mirror.liara.ir/repository/npm
|
||||
|
||||
|
||||
# Stage 5: Runner
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class Taskmanager31783244669371 implements MigrationInterface {
|
||||
name = 'Taskmanager31783244669371'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "tm_workspaces" ADD CONSTRAINT "UQ_6496168de3e6c5f006d494ebac5" UNIQUE ("name")`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "tm_workspaces" DROP CONSTRAINT "UQ_6496168de3e6c5f006d494ebac5"`);
|
||||
}
|
||||
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class TaskmanagerAddingNewEnumValueForPermissionEnumType1783324039378 implements MigrationInterface {
|
||||
name = 'TaskmanagerAddingNewEnumValueForPermissionEnumType1783324039378'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TYPE "public"."permission_name_enum" RENAME TO "permission_name_enum_old"`);
|
||||
await queryRunner.query(`CREATE TYPE "public"."permission_name_enum" AS ENUM('services', 'customers', 'agents', 'developers', 'invoices', 'transactions', 'discounts', 'admins', 'tickets', 'criticisms', 'contacts_us', 'advertisements', 'announcements', 'blogs', 'learnings', 'logs', 'settings', 'bank_accounts', 'payments', 'manage_sso_clients', 'support_plan', 'dmenu', 'dkala', 'dpage', 'dmail', 'reseller', 'workspace', 'project')`);
|
||||
await queryRunner.query(`ALTER TABLE "permission" ALTER COLUMN "name" TYPE "public"."permission_name_enum" USING "name"::"text"::"public"."permission_name_enum"`);
|
||||
await queryRunner.query(`DROP TYPE "public"."permission_name_enum_old"`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TYPE "public"."permission_name_enum_old" AS ENUM('services', 'customers', 'agents', 'developers', 'invoices', 'transactions', 'discounts', 'admins', 'tickets', 'criticisms', 'contacts_us', 'advertisements', 'announcements', 'blogs', 'learnings', 'logs', 'settings', 'bank_accounts', 'payments', 'manage_sso_clients', 'support_plan', 'dmenu', 'dkala', 'dpage', 'dmail', 'reseller', 'workspace')`);
|
||||
await queryRunner.query(`ALTER TABLE "permission" ALTER COLUMN "name" TYPE "public"."permission_name_enum_old" USING "name"::"text"::"public"."permission_name_enum_old"`);
|
||||
await queryRunner.query(`DROP TYPE "public"."permission_name_enum"`);
|
||||
await queryRunner.query(`ALTER TYPE "public"."permission_name_enum_old" RENAME TO "permission_name_enum"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class TMAddingTaskPhasePermissionToPermissionEnumType1783335279945 implements MigrationInterface {
|
||||
name = 'TMAddingTaskPhasePermissionToPermissionEnumType1783335279945'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TYPE "public"."permission_name_enum" RENAME TO "permission_name_enum_old"`);
|
||||
await queryRunner.query(`CREATE TYPE "public"."permission_name_enum" AS ENUM('services', 'customers', 'agents', 'developers', 'invoices', 'transactions', 'discounts', 'admins', 'tickets', 'criticisms', 'contacts_us', 'advertisements', 'announcements', 'blogs', 'learnings', 'logs', 'settings', 'bank_accounts', 'payments', 'manage_sso_clients', 'support_plan', 'dmenu', 'dkala', 'dpage', 'dmail', 'reseller', 'workspace', 'project', 'task_phase')`);
|
||||
await queryRunner.query(`ALTER TABLE "permission" ALTER COLUMN "name" TYPE "public"."permission_name_enum" USING "name"::"text"::"public"."permission_name_enum"`);
|
||||
await queryRunner.query(`DROP TYPE "public"."permission_name_enum_old"`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TYPE "public"."permission_name_enum_old" AS ENUM('services', 'customers', 'agents', 'developers', 'invoices', 'transactions', 'discounts', 'admins', 'tickets', 'criticisms', 'contacts_us', 'advertisements', 'announcements', 'blogs', 'learnings', 'logs', 'settings', 'bank_accounts', 'payments', 'manage_sso_clients', 'support_plan', 'dmenu', 'dkala', 'dpage', 'dmail', 'reseller', 'workspace', 'project')`);
|
||||
await queryRunner.query(`ALTER TABLE "permission" ALTER COLUMN "name" TYPE "public"."permission_name_enum_old" USING "name"::"text"::"public"."permission_name_enum_old"`);
|
||||
await queryRunner.query(`DROP TYPE "public"."permission_name_enum"`);
|
||||
await queryRunner.query(`ALTER TYPE "public"."permission_name_enum_old" RENAME TO "permission_name_enum"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class TMaddingSomeNewPermissionsToTheEnum1783428145772 implements MigrationInterface {
|
||||
name = 'TMaddingSomeNewPermissionsToTheEnum1783428145772'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TYPE "public"."permission_name_enum" RENAME TO "permission_name_enum_old"`);
|
||||
await queryRunner.query(`CREATE TYPE "public"."permission_name_enum" AS ENUM('services', 'customers', 'agents', 'developers', 'invoices', 'transactions', 'discounts', 'admins', 'tickets', 'criticisms', 'contacts_us', 'advertisements', 'announcements', 'blogs', 'learnings', 'logs', 'settings', 'bank_accounts', 'payments', 'manage_sso_clients', 'support_plan', 'dmenu', 'dkala', 'dpage', 'dmail', 'reseller', 'workspace', 'view_all_workspaces', 'project', 'view_all_projects', 'task', 'task_phase', 'remark')`);
|
||||
await queryRunner.query(`ALTER TABLE "permission" ALTER COLUMN "name" TYPE "public"."permission_name_enum" USING "name"::"text"::"public"."permission_name_enum"`);
|
||||
await queryRunner.query(`DROP TYPE "public"."permission_name_enum_old"`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TYPE "public"."permission_name_enum_old" AS ENUM('services', 'customers', 'agents', 'developers', 'invoices', 'transactions', 'discounts', 'admins', 'tickets', 'criticisms', 'contacts_us', 'advertisements', 'announcements', 'blogs', 'learnings', 'logs', 'settings', 'bank_accounts', 'payments', 'manage_sso_clients', 'support_plan', 'dmenu', 'dkala', 'dpage', 'dmail', 'reseller', 'workspace', 'project', 'task_phase')`);
|
||||
await queryRunner.query(`ALTER TABLE "permission" ALTER COLUMN "name" TYPE "public"."permission_name_enum_old" USING "name"::"text"::"public"."permission_name_enum_old"`);
|
||||
await queryRunner.query(`DROP TYPE "public"."permission_name_enum"`);
|
||||
await queryRunner.query(`ALTER TYPE "public"."permission_name_enum_old" RENAME TO "permission_name_enum"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class TMChangingFewLastPermissions1783759715100 implements MigrationInterface {
|
||||
name = 'TMChangingFewLastPermissions1783759715100'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TYPE "public"."permission_name_enum" RENAME TO "permission_name_enum_old"`);
|
||||
await queryRunner.query(`CREATE TYPE "public"."permission_name_enum" AS ENUM('services', 'customers', 'agents', 'developers', 'invoices', 'transactions', 'discounts', 'admins', 'tickets', 'criticisms', 'contacts_us', 'advertisements', 'announcements', 'blogs', 'learnings', 'logs', 'settings', 'bank_accounts', 'payments', 'manage_sso_clients', 'support_plan', 'dmenu', 'dkala', 'dpage', 'dmail', 'reseller', 'workspace', 'view_all_workspaces', 'project', 'view_all_projects', 'task', 'task_phase')`);
|
||||
await queryRunner.query(`ALTER TABLE "permission" ALTER COLUMN "name" TYPE "public"."permission_name_enum" USING "name"::"text"::"public"."permission_name_enum"`);
|
||||
await queryRunner.query(`DROP TYPE "public"."permission_name_enum_old"`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TYPE "public"."permission_name_enum_old" AS ENUM('admins', 'advertisements', 'agents', 'announcements', 'bank_accounts', 'blogs', 'contacts_us', 'criticisms', 'customers', 'developers', 'discounts', 'dkala', 'dmail', 'dmenu', 'dpage', 'invoices', 'learnings', 'logs', 'manage_sso_clients', 'payments', 'project', 'remark', 'reseller', 'services', 'settings', 'support_plan', 'task', 'task_phase', 'tickets', 'transactions', 'view_all_projects', 'view_all_workspaces', 'workspace')`);
|
||||
await queryRunner.query(`ALTER TABLE "permission" ALTER COLUMN "name" TYPE "public"."permission_name_enum_old" USING "name"::"text"::"public"."permission_name_enum_old"`);
|
||||
await queryRunner.query(`DROP TYPE "public"."permission_name_enum"`);
|
||||
await queryRunner.query(`ALTER TYPE "public"."permission_name_enum_old" RENAME TO "permission_name_enum"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AddedPriorityToTaskEntity1784014433653 implements MigrationInterface {
|
||||
name = 'AddedPriorityToTaskEntity1784014433653'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "tm_tasks" ADD "priority" integer NOT NULL DEFAULT '1'`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "tm_tasks" DROP COLUMN "priority"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AddTaskTimeTable1784100000000 implements MigrationInterface {
|
||||
name = "AddTaskTimeTable1784100000000";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE "tm_times" (
|
||||
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
|
||||
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
"startedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
"endedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
"duration" integer NOT NULL,
|
||||
"adminId" uuid NOT NULL,
|
||||
"taskId" uuid NOT NULL,
|
||||
CONSTRAINT "PK_tm_times" PRIMARY KEY ("id")
|
||||
)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE "tm_times"
|
||||
ADD CONSTRAINT "FK_tm_times_admin"
|
||||
FOREIGN KEY ("adminId") REFERENCES "user"("id")
|
||||
ON DELETE RESTRICT ON UPDATE NO ACTION
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE "tm_times"
|
||||
ADD CONSTRAINT "FK_tm_times_task"
|
||||
FOREIGN KEY ("taskId") REFERENCES "tm_tasks"("id")
|
||||
ON DELETE CASCADE ON UPDATE NO ACTION
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "tm_times" DROP CONSTRAINT "FK_tm_times_task"`);
|
||||
await queryRunner.query(`ALTER TABLE "tm_times" DROP CONSTRAINT "FK_tm_times_admin"`);
|
||||
await queryRunner.query(`DROP TABLE "tm_times"`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class MakeTaskTimeEndedAtAndDurationNullable1784200000000
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = "MakeTaskTimeEndedAtAndDurationNullable1784200000000";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "tm_times" ALTER COLUMN "endedAt" DROP NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "tm_times" ALTER COLUMN "duration" DROP NOT NULL`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "tm_times" ALTER COLUMN "endedAt" SET NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "tm_times" ALTER COLUMN "duration" SET NOT NULL`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AddTitleToPermission1784465631499 implements MigrationInterface {
|
||||
name = "AddTitleToPermission1784465631499";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "permission" ADD "title" character varying(150)`);
|
||||
await queryRunner.query(`UPDATE "permission" SET "title" = "name"::text WHERE "title" IS NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "permission" ALTER COLUMN "title" SET NOT NULL`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "permission" DROP COLUMN "title"`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,300 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
type CrudResource = {
|
||||
old: string;
|
||||
group: string;
|
||||
titles: { create: string; read: string; update: string; delete: string };
|
||||
};
|
||||
|
||||
const CRUD_RESOURCES: CrudResource[] = [
|
||||
{
|
||||
old: "services",
|
||||
group: "services",
|
||||
titles: { create: "ایجاد سرویسها", read: "مشاهده سرویسها", update: "ویرایش سرویسها", delete: "حذف سرویسها" },
|
||||
},
|
||||
{
|
||||
old: "customers",
|
||||
group: "customers",
|
||||
titles: { create: "ایجاد مشتریان", read: "مشاهده مشتریان", update: "ویرایش مشتریان", delete: "حذف مشتریان" },
|
||||
},
|
||||
{
|
||||
old: "agents",
|
||||
group: "representatives",
|
||||
titles: { create: "ایجاد نمایندگان", read: "مشاهده نمایندگان", update: "ویرایش نمایندگان", delete: "حذف نمایندگان" },
|
||||
},
|
||||
{
|
||||
old: "developers",
|
||||
group: "users",
|
||||
titles: {
|
||||
create: "ایجاد توسعهدهندگان",
|
||||
read: "مشاهده توسعهدهندگان",
|
||||
update: "ویرایش توسعهدهندگان",
|
||||
delete: "حذف توسعهدهندگان",
|
||||
},
|
||||
},
|
||||
{
|
||||
old: "invoices",
|
||||
group: "financial",
|
||||
titles: { create: "ایجاد صورتحسابها", read: "مشاهده صورتحسابها", update: "ویرایش صورتحسابها", delete: "حذف صورتحسابها" },
|
||||
},
|
||||
{
|
||||
old: "transactions",
|
||||
group: "financial",
|
||||
titles: { create: "ایجاد تراکنشها", read: "مشاهده تراکنشها", update: "ویرایش تراکنشها", delete: "حذف تراکنشها" },
|
||||
},
|
||||
{
|
||||
old: "discounts",
|
||||
group: "financial",
|
||||
titles: { create: "ایجاد تخفیفها", read: "مشاهده تخفیفها", update: "ویرایش تخفیفها", delete: "حذف تخفیفها" },
|
||||
},
|
||||
{
|
||||
old: "admins",
|
||||
group: "users",
|
||||
titles: { create: "ایجاد ادمینها", read: "مشاهده ادمینها", update: "ویرایش ادمینها", delete: "حذف ادمینها" },
|
||||
},
|
||||
{
|
||||
old: "tickets",
|
||||
group: "support",
|
||||
titles: { create: "ایجاد تیکتها", read: "مشاهده تیکتها", update: "ویرایش تیکتها", delete: "حذف تیکتها" },
|
||||
},
|
||||
{
|
||||
old: "criticisms",
|
||||
group: "other",
|
||||
titles: { create: "ایجاد انتقادات", read: "مشاهده انتقادات", update: "ویرایش انتقادات", delete: "حذف انتقادات" },
|
||||
},
|
||||
{
|
||||
old: "contacts_us",
|
||||
group: "other",
|
||||
titles: { create: "ایجاد تماس با ما", read: "مشاهده تماس با ما", update: "ویرایش تماس با ما", delete: "حذف تماس با ما" },
|
||||
},
|
||||
{
|
||||
old: "advertisements",
|
||||
group: "content",
|
||||
titles: { create: "ایجاد تبلیغات", read: "مشاهده تبلیغات", update: "ویرایش تبلیغات", delete: "حذف تبلیغات" },
|
||||
},
|
||||
{
|
||||
old: "announcements",
|
||||
group: "content",
|
||||
titles: { create: "ایجاد اطلاعیهها", read: "مشاهده اطلاعیهها", update: "ویرایش اطلاعیهها", delete: "حذف اطلاعیهها" },
|
||||
},
|
||||
{
|
||||
old: "blogs",
|
||||
group: "content",
|
||||
titles: { create: "ایجاد بلاگها", read: "مشاهده بلاگها", update: "ویرایش بلاگها", delete: "حذف بلاگها" },
|
||||
},
|
||||
{
|
||||
old: "learnings",
|
||||
group: "content",
|
||||
titles: { create: "ایجاد آموزشها", read: "مشاهده آموزشها", update: "ویرایش آموزشها", delete: "حذف آموزشها" },
|
||||
},
|
||||
{
|
||||
old: "settings",
|
||||
group: "other",
|
||||
titles: { create: "ایجاد تنظیمات", read: "مشاهده تنظیمات", update: "ویرایش تنظیمات", delete: "حذف تنظیمات" },
|
||||
},
|
||||
{
|
||||
old: "bank_accounts",
|
||||
group: "financial",
|
||||
titles: {
|
||||
create: "ایجاد حسابهای بانکی",
|
||||
read: "مشاهده حسابهای بانکی",
|
||||
update: "ویرایش حسابهای بانکی",
|
||||
delete: "حذف حسابهای بانکی",
|
||||
},
|
||||
},
|
||||
{
|
||||
old: "payments",
|
||||
group: "financial",
|
||||
titles: { create: "ایجاد پرداختها", read: "مشاهده پرداختها", update: "ویرایش پرداختها", delete: "حذف پرداختها" },
|
||||
},
|
||||
{
|
||||
old: "support_plan",
|
||||
group: "support",
|
||||
titles: {
|
||||
create: "ایجاد پلن پشتیبانی",
|
||||
read: "مشاهده پلن پشتیبانی",
|
||||
update: "ویرایش پلن پشتیبانی",
|
||||
delete: "حذف پلن پشتیبانی",
|
||||
},
|
||||
},
|
||||
{
|
||||
old: "reseller",
|
||||
group: "representatives",
|
||||
titles: {
|
||||
create: "ایجاد نمایندگی فروش",
|
||||
read: "مشاهده نمایندگی فروش",
|
||||
update: "ویرایش نمایندگی فروش",
|
||||
delete: "حذف نمایندگی فروش",
|
||||
},
|
||||
},
|
||||
{
|
||||
old: "workspace",
|
||||
group: "task_manager",
|
||||
titles: { create: "ایجاد فضای کار", read: "مشاهده فضای کار", update: "ویرایش فضای کار", delete: "حذف فضای کار" },
|
||||
},
|
||||
{
|
||||
old: "project",
|
||||
group: "task_manager",
|
||||
titles: { create: "ایجاد پروژه", read: "مشاهده پروژه", update: "ویرایش پروژه", delete: "حذف پروژه" },
|
||||
},
|
||||
{
|
||||
old: "task",
|
||||
group: "task_manager",
|
||||
titles: { create: "ایجاد وظیفه", read: "مشاهده وظیفه", update: "ویرایش وظیفه", delete: "حذف وظیفه" },
|
||||
},
|
||||
{
|
||||
old: "task_phase",
|
||||
group: "task_manager",
|
||||
titles: { create: "ایجاد فاز وظیفه", read: "مشاهده فاز وظیفه", update: "ویرایش فاز وظیفه", delete: "حذف فاز وظیفه" },
|
||||
},
|
||||
];
|
||||
|
||||
const SINGLE_PERMISSIONS: { name: string; group: string; title: string }[] = [
|
||||
{ name: "logs", group: "other", title: "لاگها" },
|
||||
{ name: "manage_sso_clients", group: "other", title: "مدیریت کلاینتهای SSO" },
|
||||
{ name: "dmenu", group: "products", title: "دیمنو" },
|
||||
{ name: "dkala", group: "products", title: "دیکالا" },
|
||||
{ name: "dpage", group: "products", title: "دیپیج" },
|
||||
{ name: "dmail", group: "products", title: "دیمیل" },
|
||||
{ name: "view_all_workspaces", group: "task_manager", title: "مشاهده همه فضاهای کار" },
|
||||
{ name: "view_all_projects", group: "task_manager", title: "مشاهده همه پروژهها" },
|
||||
];
|
||||
|
||||
const NEW_ENUM_VALUES = [
|
||||
...CRUD_RESOURCES.flatMap((r) => [`${r.old}_create`, `${r.old}_read`, `${r.old}_update`, `${r.old}_delete`]),
|
||||
...SINGLE_PERMISSIONS.map((p) => p.name),
|
||||
];
|
||||
|
||||
const OLD_ENUM_VALUES = [
|
||||
"services",
|
||||
"customers",
|
||||
"agents",
|
||||
"developers",
|
||||
"invoices",
|
||||
"transactions",
|
||||
"discounts",
|
||||
"admins",
|
||||
"tickets",
|
||||
"criticisms",
|
||||
"contacts_us",
|
||||
"advertisements",
|
||||
"announcements",
|
||||
"blogs",
|
||||
"learnings",
|
||||
"logs",
|
||||
"settings",
|
||||
"bank_accounts",
|
||||
"payments",
|
||||
"manage_sso_clients",
|
||||
"support_plan",
|
||||
"dmenu",
|
||||
"dkala",
|
||||
"dpage",
|
||||
"dmail",
|
||||
"reseller",
|
||||
"workspace",
|
||||
"view_all_workspaces",
|
||||
"project",
|
||||
"view_all_projects",
|
||||
"task",
|
||||
"task_phase",
|
||||
];
|
||||
|
||||
export class RefactorPermissionsToCrudAndGroup1784500000000 implements MigrationInterface {
|
||||
name = "RefactorPermissionsToCrudAndGroup1784500000000";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TYPE "public"."permission_group_enum" AS ENUM('services', 'customers', 'users', 'financial', 'content', 'support', 'other', 'products', 'representatives', 'task_manager')`,
|
||||
);
|
||||
await queryRunner.query(`ALTER TABLE "permission" ADD "group" "public"."permission_group_enum"`);
|
||||
|
||||
await queryRunner.query(`ALTER TABLE "permission" ALTER COLUMN "name" TYPE text USING "name"::text`);
|
||||
await queryRunner.query(`DROP TYPE "public"."permission_name_enum"`);
|
||||
|
||||
for (const resource of CRUD_RESOURCES) {
|
||||
const oldPerm = await queryRunner.query(`SELECT id FROM "permission" WHERE "name" = $1`, [resource.old]);
|
||||
if (!oldPerm.length) continue;
|
||||
|
||||
const oldId = oldPerm[0].id as string;
|
||||
const actions = ["create", "read", "update", "delete"] as const;
|
||||
|
||||
for (const action of actions) {
|
||||
const newName = `${resource.old}_${action}`;
|
||||
const title = resource.titles[action];
|
||||
const inserted = await queryRunner.query(
|
||||
`INSERT INTO "permission" ("id", "name", "title", "group", "createdAt", "updatedAt")
|
||||
VALUES (uuid_generate_v4(), $1, $2, $3, NOW(), NOW())
|
||||
RETURNING id`,
|
||||
[newName, title, resource.group],
|
||||
);
|
||||
const newId = inserted[0].id as string;
|
||||
|
||||
await queryRunner.query(
|
||||
`INSERT INTO "role_permission_relation" ("roleId", "permissionId")
|
||||
SELECT "roleId", $1 FROM "role_permission_relation" WHERE "permissionId" = $2
|
||||
ON CONFLICT ("roleId", "permissionId") DO NOTHING`,
|
||||
[newId, oldId],
|
||||
);
|
||||
}
|
||||
|
||||
await queryRunner.query(`DELETE FROM "role_permission_relation" WHERE "permissionId" = $1`, [oldId]);
|
||||
await queryRunner.query(`DELETE FROM "permission" WHERE "id" = $1`, [oldId]);
|
||||
}
|
||||
|
||||
for (const perm of SINGLE_PERMISSIONS) {
|
||||
await queryRunner.query(`UPDATE "permission" SET "group" = $1, "title" = COALESCE(NULLIF("title", ''), $2) WHERE "name" = $3`, [
|
||||
perm.group,
|
||||
perm.title,
|
||||
perm.name,
|
||||
]);
|
||||
}
|
||||
|
||||
await queryRunner.query(`ALTER TABLE "permission" ALTER COLUMN "group" SET NOT NULL`);
|
||||
|
||||
const enumList = NEW_ENUM_VALUES.map((v) => `'${v}'`).join(", ");
|
||||
await queryRunner.query(`CREATE TYPE "public"."permission_name_enum" AS ENUM(${enumList})`);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "permission" ALTER COLUMN "name" TYPE "public"."permission_name_enum" USING "name"::"public"."permission_name_enum"`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "permission" ALTER COLUMN "name" TYPE text USING "name"::text`);
|
||||
await queryRunner.query(`DROP TYPE "public"."permission_name_enum"`);
|
||||
|
||||
for (const resource of CRUD_RESOURCES) {
|
||||
// const actions = ["create", "read", "update", "delete"];
|
||||
const crudRows = await queryRunner.query(`SELECT id, name FROM "permission" WHERE "name" LIKE $1`, [`${resource.old}_%`]);
|
||||
if (!crudRows.length) continue;
|
||||
|
||||
const inserted = await queryRunner.query(
|
||||
`INSERT INTO "permission" ("id", "name", "title", "group", "createdAt", "updatedAt")
|
||||
VALUES (uuid_generate_v4(), $1, $2, $3, NOW(), NOW())
|
||||
RETURNING id`,
|
||||
[resource.old, resource.old, resource.group],
|
||||
);
|
||||
const oldId = inserted[0].id as string;
|
||||
|
||||
for (const row of crudRows) {
|
||||
await queryRunner.query(
|
||||
`INSERT INTO "role_permission_relation" ("roleId", "permissionId")
|
||||
SELECT "roleId", $1 FROM "role_permission_relation" WHERE "permissionId" = $2
|
||||
ON CONFLICT ("roleId", "permissionId") DO NOTHING`,
|
||||
[oldId, row.id],
|
||||
);
|
||||
await queryRunner.query(`DELETE FROM "role_permission_relation" WHERE "permissionId" = $1`, [row.id]);
|
||||
await queryRunner.query(`DELETE FROM "permission" WHERE "id" = $1`, [row.id]);
|
||||
}
|
||||
}
|
||||
|
||||
const oldEnumList = OLD_ENUM_VALUES.map((v) => `'${v}'`).join(", ");
|
||||
await queryRunner.query(`CREATE TYPE "public"."permission_name_enum" AS ENUM(${oldEnumList})`);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "permission" ALTER COLUMN "name" TYPE "public"."permission_name_enum" USING "name"::"public"."permission_name_enum"`,
|
||||
);
|
||||
|
||||
await queryRunner.query(`ALTER TABLE "permission" DROP COLUMN "group"`);
|
||||
await queryRunner.query(`DROP TYPE "public"."permission_group_enum"`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AddTicketToTaskEntity1784600000000 implements MigrationInterface {
|
||||
name = "AddTicketToTaskEntity1784600000000";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "tm_tasks" ADD "ticketId" uuid`);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "tm_tasks" ADD CONSTRAINT "FK_tm_tasks_ticket" FOREIGN KEY ("ticketId") REFERENCES "ticket"("id") ON DELETE SET NULL ON UPDATE NO ACTION`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "tm_tasks" DROP CONSTRAINT "FK_tm_tasks_ticket"`);
|
||||
await queryRunner.query(`ALTER TABLE "tm_tasks" DROP COLUMN "ticketId"`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AddTaskCommentEntity1784700000000 implements MigrationInterface {
|
||||
name = "AddTaskCommentEntity1784700000000";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "tm_task_comments" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "content" text NOT NULL, "userId" uuid NOT NULL, "taskId" uuid NOT NULL, CONSTRAINT "PK_tm_task_comments" PRIMARY KEY ("id"))`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "tm_task_comments" ADD CONSTRAINT "FK_tm_task_comments_user" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE NO ACTION`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "tm_task_comments" ADD CONSTRAINT "FK_tm_task_comments_task" FOREIGN KEY ("taskId") REFERENCES "tm_tasks"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "tm_task_comments" DROP CONSTRAINT "FK_tm_task_comments_task"`);
|
||||
await queryRunner.query(`ALTER TABLE "tm_task_comments" DROP CONSTRAINT "FK_tm_task_comments_user"`);
|
||||
await queryRunner.query(`DROP TABLE "tm_task_comments"`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
const NEW_PERMISSIONS = [
|
||||
{ name: "ticket_categories_create", title: "ایجاد دستهبندی تیکت", source: "tickets_create" },
|
||||
{ name: "ticket_categories_read", title: "مشاهده دستهبندی تیکت", source: "tickets_read" },
|
||||
{ name: "ticket_categories_update", title: "ویرایش دستهبندی تیکت", source: "tickets_update" },
|
||||
{ name: "ticket_categories_delete", title: "حذف دستهبندی تیکت", source: "tickets_delete" },
|
||||
] as const;
|
||||
|
||||
const CURRENT_ENUM_VALUES = [
|
||||
"services_create",
|
||||
"services_read",
|
||||
"services_update",
|
||||
"services_delete",
|
||||
"customers_create",
|
||||
"customers_read",
|
||||
"customers_update",
|
||||
"customers_delete",
|
||||
"agents_create",
|
||||
"agents_read",
|
||||
"agents_update",
|
||||
"agents_delete",
|
||||
"developers_create",
|
||||
"developers_read",
|
||||
"developers_update",
|
||||
"developers_delete",
|
||||
"invoices_create",
|
||||
"invoices_read",
|
||||
"invoices_update",
|
||||
"invoices_delete",
|
||||
"transactions_create",
|
||||
"transactions_read",
|
||||
"transactions_update",
|
||||
"transactions_delete",
|
||||
"discounts_create",
|
||||
"discounts_read",
|
||||
"discounts_update",
|
||||
"discounts_delete",
|
||||
"admins_create",
|
||||
"admins_read",
|
||||
"admins_update",
|
||||
"admins_delete",
|
||||
"tickets_create",
|
||||
"tickets_read",
|
||||
"tickets_update",
|
||||
"tickets_delete",
|
||||
"criticisms_create",
|
||||
"criticisms_read",
|
||||
"criticisms_update",
|
||||
"criticisms_delete",
|
||||
"contacts_us_create",
|
||||
"contacts_us_read",
|
||||
"contacts_us_update",
|
||||
"contacts_us_delete",
|
||||
"advertisements_create",
|
||||
"advertisements_read",
|
||||
"advertisements_update",
|
||||
"advertisements_delete",
|
||||
"announcements_create",
|
||||
"announcements_read",
|
||||
"announcements_update",
|
||||
"announcements_delete",
|
||||
"blogs_create",
|
||||
"blogs_read",
|
||||
"blogs_update",
|
||||
"blogs_delete",
|
||||
"learnings_create",
|
||||
"learnings_read",
|
||||
"learnings_update",
|
||||
"learnings_delete",
|
||||
"settings_create",
|
||||
"settings_read",
|
||||
"settings_update",
|
||||
"settings_delete",
|
||||
"bank_accounts_create",
|
||||
"bank_accounts_read",
|
||||
"bank_accounts_update",
|
||||
"bank_accounts_delete",
|
||||
"payments_create",
|
||||
"payments_read",
|
||||
"payments_update",
|
||||
"payments_delete",
|
||||
"support_plan_create",
|
||||
"support_plan_read",
|
||||
"support_plan_update",
|
||||
"support_plan_delete",
|
||||
"reseller_create",
|
||||
"reseller_read",
|
||||
"reseller_update",
|
||||
"reseller_delete",
|
||||
"workspace_create",
|
||||
"workspace_read",
|
||||
"workspace_update",
|
||||
"workspace_delete",
|
||||
"project_create",
|
||||
"project_read",
|
||||
"project_update",
|
||||
"project_delete",
|
||||
"task_create",
|
||||
"task_read",
|
||||
"task_update",
|
||||
"task_delete",
|
||||
"task_phase_create",
|
||||
"task_phase_read",
|
||||
"task_phase_update",
|
||||
"task_phase_delete",
|
||||
"logs",
|
||||
"manage_sso_clients",
|
||||
"dmenu",
|
||||
"dkala",
|
||||
"dpage",
|
||||
"dmail",
|
||||
"view_all_workspaces",
|
||||
"view_all_projects",
|
||||
];
|
||||
|
||||
const UPDATED_ENUM_VALUES = [
|
||||
...CURRENT_ENUM_VALUES.slice(0, CURRENT_ENUM_VALUES.indexOf("tickets_delete") + 1),
|
||||
...NEW_PERMISSIONS.map((p) => p.name),
|
||||
...CURRENT_ENUM_VALUES.slice(CURRENT_ENUM_VALUES.indexOf("tickets_delete") + 1),
|
||||
];
|
||||
|
||||
export class AddTicketCategoriesPermissions1784800000000 implements MigrationInterface {
|
||||
name = "AddTicketCategoriesPermissions1784800000000";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "permission" ALTER COLUMN "name" TYPE text USING "name"::text`);
|
||||
await queryRunner.query(`DROP TYPE "public"."permission_name_enum"`);
|
||||
|
||||
const enumList = UPDATED_ENUM_VALUES.map((v) => `'${v}'`).join(", ");
|
||||
await queryRunner.query(`CREATE TYPE "public"."permission_name_enum" AS ENUM(${enumList})`);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "permission" ALTER COLUMN "name" TYPE "public"."permission_name_enum" USING "name"::"public"."permission_name_enum"`,
|
||||
);
|
||||
|
||||
for (const perm of NEW_PERMISSIONS) {
|
||||
const inserted = await queryRunner.query(
|
||||
`INSERT INTO "permission" ("id", "name", "title", "group", "createdAt", "updatedAt")
|
||||
VALUES (uuid_generate_v4(), $1, $2, 'support', NOW(), NOW())
|
||||
RETURNING id`,
|
||||
[perm.name, perm.title],
|
||||
);
|
||||
const newId = inserted[0].id as string;
|
||||
|
||||
const sourcePerm = await queryRunner.query(`SELECT id FROM "permission" WHERE "name" = $1`, [perm.source]);
|
||||
if (!sourcePerm.length) continue;
|
||||
|
||||
await queryRunner.query(
|
||||
`INSERT INTO "role_permission_relation" ("roleId", "permissionId")
|
||||
SELECT "roleId", $1 FROM "role_permission_relation" WHERE "permissionId" = $2
|
||||
ON CONFLICT ("roleId", "permissionId") DO NOTHING`,
|
||||
[newId, sourcePerm[0].id],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
for (const perm of NEW_PERMISSIONS) {
|
||||
const rows = await queryRunner.query(`SELECT id FROM "permission" WHERE "name" = $1`, [perm.name]);
|
||||
if (!rows.length) continue;
|
||||
|
||||
await queryRunner.query(`DELETE FROM "role_permission_relation" WHERE "permissionId" = $1`, [rows[0].id]);
|
||||
await queryRunner.query(`DELETE FROM "permission" WHERE "id" = $1`, [rows[0].id]);
|
||||
}
|
||||
|
||||
await queryRunner.query(`ALTER TABLE "permission" ALTER COLUMN "name" TYPE text USING "name"::text`);
|
||||
await queryRunner.query(`DROP TYPE "public"."permission_name_enum"`);
|
||||
|
||||
const enumList = CURRENT_ENUM_VALUES.map((v) => `'${v}'`).join(", ");
|
||||
await queryRunner.query(`CREATE TYPE "public"."permission_name_enum" AS ENUM(${enumList})`);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "permission" ALTER COLUMN "name" TYPE "public"."permission_name_enum" USING "name"::"public"."permission_name_enum"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AddAttachmentsToTaskComment1784900000000 implements MigrationInterface {
|
||||
name = "AddAttachmentsToTaskComment1784900000000";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "tm_task_comments" ADD "attachments" json NOT NULL DEFAULT '[]'`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "tm_task_comments" DROP COLUMN "attachments"`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AddTicketMasterEntity1785000000000 implements MigrationInterface {
|
||||
name = "AddTicketMasterEntity1785000000000";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "ticket_master" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "userId" uuid NOT NULL, CONSTRAINT "UQ_ticket_master_user" UNIQUE ("userId"), CONSTRAINT "PK_ticket_master" PRIMARY KEY ("id"))`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "ticket_master" ADD CONSTRAINT "FK_ticket_master_user" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "ticket_master" DROP CONSTRAINT "FK_ticket_master_user"`);
|
||||
await queryRunner.query(`DROP TABLE "ticket_master"`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
const NEW_PERMISSIONS = [
|
||||
{ name: "ticket_masters_create", title: "ایجاد مستر تیکت", source: "tickets_create" },
|
||||
{ name: "ticket_masters_read", title: "مشاهده مستر تیکت", source: "tickets_read" },
|
||||
{ name: "ticket_masters_update", title: "ویرایش مستر تیکت", source: "tickets_update" },
|
||||
{ name: "ticket_masters_delete", title: "حذف مستر تیکت", source: "tickets_delete" },
|
||||
] as const;
|
||||
|
||||
const CURRENT_ENUM_VALUES = [
|
||||
"services_create",
|
||||
"services_read",
|
||||
"services_update",
|
||||
"services_delete",
|
||||
"customers_create",
|
||||
"customers_read",
|
||||
"customers_update",
|
||||
"customers_delete",
|
||||
"agents_create",
|
||||
"agents_read",
|
||||
"agents_update",
|
||||
"agents_delete",
|
||||
"developers_create",
|
||||
"developers_read",
|
||||
"developers_update",
|
||||
"developers_delete",
|
||||
"invoices_create",
|
||||
"invoices_read",
|
||||
"invoices_update",
|
||||
"invoices_delete",
|
||||
"transactions_create",
|
||||
"transactions_read",
|
||||
"transactions_update",
|
||||
"transactions_delete",
|
||||
"discounts_create",
|
||||
"discounts_read",
|
||||
"discounts_update",
|
||||
"discounts_delete",
|
||||
"admins_create",
|
||||
"admins_read",
|
||||
"admins_update",
|
||||
"admins_delete",
|
||||
"tickets_create",
|
||||
"tickets_read",
|
||||
"tickets_update",
|
||||
"tickets_delete",
|
||||
"ticket_categories_create",
|
||||
"ticket_categories_read",
|
||||
"ticket_categories_update",
|
||||
"ticket_categories_delete",
|
||||
"criticisms_create",
|
||||
"criticisms_read",
|
||||
"criticisms_update",
|
||||
"criticisms_delete",
|
||||
"contacts_us_create",
|
||||
"contacts_us_read",
|
||||
"contacts_us_update",
|
||||
"contacts_us_delete",
|
||||
"advertisements_create",
|
||||
"advertisements_read",
|
||||
"advertisements_update",
|
||||
"advertisements_delete",
|
||||
"announcements_create",
|
||||
"announcements_read",
|
||||
"announcements_update",
|
||||
"announcements_delete",
|
||||
"blogs_create",
|
||||
"blogs_read",
|
||||
"blogs_update",
|
||||
"blogs_delete",
|
||||
"learnings_create",
|
||||
"learnings_read",
|
||||
"learnings_update",
|
||||
"learnings_delete",
|
||||
"settings_create",
|
||||
"settings_read",
|
||||
"settings_update",
|
||||
"settings_delete",
|
||||
"bank_accounts_create",
|
||||
"bank_accounts_read",
|
||||
"bank_accounts_update",
|
||||
"bank_accounts_delete",
|
||||
"payments_create",
|
||||
"payments_read",
|
||||
"payments_update",
|
||||
"payments_delete",
|
||||
"support_plan_create",
|
||||
"support_plan_read",
|
||||
"support_plan_update",
|
||||
"support_plan_delete",
|
||||
"reseller_create",
|
||||
"reseller_read",
|
||||
"reseller_update",
|
||||
"reseller_delete",
|
||||
"workspace_create",
|
||||
"workspace_read",
|
||||
"workspace_update",
|
||||
"workspace_delete",
|
||||
"project_create",
|
||||
"project_read",
|
||||
"project_update",
|
||||
"project_delete",
|
||||
"task_create",
|
||||
"task_read",
|
||||
"task_update",
|
||||
"task_delete",
|
||||
"task_phase_create",
|
||||
"task_phase_read",
|
||||
"task_phase_update",
|
||||
"task_phase_delete",
|
||||
"logs",
|
||||
"manage_sso_clients",
|
||||
"dmenu",
|
||||
"dkala",
|
||||
"dpage",
|
||||
"dmail",
|
||||
"view_all_workspaces",
|
||||
"view_all_projects",
|
||||
];
|
||||
|
||||
const UPDATED_ENUM_VALUES = [
|
||||
...CURRENT_ENUM_VALUES.slice(0, CURRENT_ENUM_VALUES.indexOf("ticket_categories_delete") + 1),
|
||||
...NEW_PERMISSIONS.map((p) => p.name),
|
||||
...CURRENT_ENUM_VALUES.slice(CURRENT_ENUM_VALUES.indexOf("ticket_categories_delete") + 1),
|
||||
];
|
||||
|
||||
export class AddTicketMastersPermissions1785100000000 implements MigrationInterface {
|
||||
name = "AddTicketMastersPermissions1785100000000";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "permission" ALTER COLUMN "name" TYPE text USING "name"::text`);
|
||||
await queryRunner.query(`DROP TYPE "public"."permission_name_enum"`);
|
||||
|
||||
const enumList = UPDATED_ENUM_VALUES.map((v) => `'${v}'`).join(", ");
|
||||
await queryRunner.query(`CREATE TYPE "public"."permission_name_enum" AS ENUM(${enumList})`);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "permission" ALTER COLUMN "name" TYPE "public"."permission_name_enum" USING "name"::"public"."permission_name_enum"`,
|
||||
);
|
||||
|
||||
for (const perm of NEW_PERMISSIONS) {
|
||||
const inserted = await queryRunner.query(
|
||||
`INSERT INTO "permission" ("id", "name", "title", "group", "createdAt", "updatedAt")
|
||||
VALUES (uuid_generate_v4(), $1, $2, 'support', NOW(), NOW())
|
||||
RETURNING id`,
|
||||
[perm.name, perm.title],
|
||||
);
|
||||
const newId = inserted[0].id as string;
|
||||
|
||||
const sourcePerm = await queryRunner.query(`SELECT id FROM "permission" WHERE "name" = $1`, [perm.source]);
|
||||
if (!sourcePerm.length) continue;
|
||||
|
||||
await queryRunner.query(
|
||||
`INSERT INTO "role_permission_relation" ("roleId", "permissionId")
|
||||
SELECT "roleId", $1 FROM "role_permission_relation" WHERE "permissionId" = $2
|
||||
ON CONFLICT ("roleId", "permissionId") DO NOTHING`,
|
||||
[newId, sourcePerm[0].id],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
for (const perm of NEW_PERMISSIONS) {
|
||||
const rows = await queryRunner.query(`SELECT id FROM "permission" WHERE "name" = $1`, [perm.name]);
|
||||
if (!rows.length) continue;
|
||||
|
||||
await queryRunner.query(`DELETE FROM "role_permission_relation" WHERE "permissionId" = $1`, [rows[0].id]);
|
||||
await queryRunner.query(`DELETE FROM "permission" WHERE "id" = $1`, [rows[0].id]);
|
||||
}
|
||||
|
||||
await queryRunner.query(`ALTER TABLE "permission" ALTER COLUMN "name" TYPE text USING "name"::text`);
|
||||
await queryRunner.query(`DROP TYPE "public"."permission_name_enum"`);
|
||||
|
||||
const enumList = CURRENT_ENUM_VALUES.map((v) => `'${v}'`).join(", ");
|
||||
await queryRunner.query(`CREATE TYPE "public"."permission_name_enum" AS ENUM(${enumList})`);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "permission" ALTER COLUMN "name" TYPE "public"."permission_name_enum" USING "name"::"public"."permission_name_enum"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
const NEW_PERMISSIONS = [
|
||||
{ name: "blog_categories_create", title: "ایجاد دستهبندی بلاگ", source: "blogs_create" },
|
||||
{ name: "blog_categories_read", title: "مشاهده دستهبندی بلاگ", source: "blogs_read" },
|
||||
{ name: "blog_categories_update", title: "ویرایش دستهبندی بلاگ", source: "blogs_update" },
|
||||
{ name: "blog_categories_delete", title: "حذف دستهبندی بلاگ", source: "blogs_delete" },
|
||||
{ name: "blog_comments_create", title: "ایجاد نظرات بلاگ", source: "blogs_create" },
|
||||
{ name: "blog_comments_read", title: "مشاهده نظرات بلاگ", source: "blogs_read" },
|
||||
{ name: "blog_comments_update", title: "ویرایش نظرات بلاگ", source: "blogs_update" },
|
||||
{ name: "blog_comments_delete", title: "حذف نظرات بلاگ", source: "blogs_delete" },
|
||||
{ name: "sliders_create", title: "ایجاد اسلایدر", source: "blogs_create" },
|
||||
{ name: "sliders_read", title: "مشاهده اسلایدر", source: "blogs_read" },
|
||||
{ name: "sliders_update", title: "ویرایش اسلایدر", source: "blogs_update" },
|
||||
{ name: "sliders_delete", title: "حذف اسلایدر", source: "blogs_delete" },
|
||||
] as const;
|
||||
|
||||
const CURRENT_ENUM_VALUES = [
|
||||
"services_create",
|
||||
"services_read",
|
||||
"services_update",
|
||||
"services_delete",
|
||||
"customers_create",
|
||||
"customers_read",
|
||||
"customers_update",
|
||||
"customers_delete",
|
||||
"agents_create",
|
||||
"agents_read",
|
||||
"agents_update",
|
||||
"agents_delete",
|
||||
"developers_create",
|
||||
"developers_read",
|
||||
"developers_update",
|
||||
"developers_delete",
|
||||
"invoices_create",
|
||||
"invoices_read",
|
||||
"invoices_update",
|
||||
"invoices_delete",
|
||||
"transactions_create",
|
||||
"transactions_read",
|
||||
"transactions_update",
|
||||
"transactions_delete",
|
||||
"discounts_create",
|
||||
"discounts_read",
|
||||
"discounts_update",
|
||||
"discounts_delete",
|
||||
"admins_create",
|
||||
"admins_read",
|
||||
"admins_update",
|
||||
"admins_delete",
|
||||
"tickets_create",
|
||||
"tickets_read",
|
||||
"tickets_update",
|
||||
"tickets_delete",
|
||||
"ticket_categories_create",
|
||||
"ticket_categories_read",
|
||||
"ticket_categories_update",
|
||||
"ticket_categories_delete",
|
||||
"ticket_masters_create",
|
||||
"ticket_masters_read",
|
||||
"ticket_masters_update",
|
||||
"ticket_masters_delete",
|
||||
"criticisms_create",
|
||||
"criticisms_read",
|
||||
"criticisms_update",
|
||||
"criticisms_delete",
|
||||
"contacts_us_create",
|
||||
"contacts_us_read",
|
||||
"contacts_us_update",
|
||||
"contacts_us_delete",
|
||||
"advertisements_create",
|
||||
"advertisements_read",
|
||||
"advertisements_update",
|
||||
"advertisements_delete",
|
||||
"announcements_create",
|
||||
"announcements_read",
|
||||
"announcements_update",
|
||||
"announcements_delete",
|
||||
"blogs_create",
|
||||
"blogs_read",
|
||||
"blogs_update",
|
||||
"blogs_delete",
|
||||
"learnings_create",
|
||||
"learnings_read",
|
||||
"learnings_update",
|
||||
"learnings_delete",
|
||||
"settings_create",
|
||||
"settings_read",
|
||||
"settings_update",
|
||||
"settings_delete",
|
||||
"bank_accounts_create",
|
||||
"bank_accounts_read",
|
||||
"bank_accounts_update",
|
||||
"bank_accounts_delete",
|
||||
"payments_create",
|
||||
"payments_read",
|
||||
"payments_update",
|
||||
"payments_delete",
|
||||
"support_plan_create",
|
||||
"support_plan_read",
|
||||
"support_plan_update",
|
||||
"support_plan_delete",
|
||||
"reseller_create",
|
||||
"reseller_read",
|
||||
"reseller_update",
|
||||
"reseller_delete",
|
||||
"workspace_create",
|
||||
"workspace_read",
|
||||
"workspace_update",
|
||||
"workspace_delete",
|
||||
"project_create",
|
||||
"project_read",
|
||||
"project_update",
|
||||
"project_delete",
|
||||
"task_create",
|
||||
"task_read",
|
||||
"task_update",
|
||||
"task_delete",
|
||||
"task_phase_create",
|
||||
"task_phase_read",
|
||||
"task_phase_update",
|
||||
"task_phase_delete",
|
||||
"logs",
|
||||
"manage_sso_clients",
|
||||
"dmenu",
|
||||
"dkala",
|
||||
"dpage",
|
||||
"dmail",
|
||||
"view_all_workspaces",
|
||||
"view_all_projects",
|
||||
];
|
||||
|
||||
const UPDATED_ENUM_VALUES = [
|
||||
...CURRENT_ENUM_VALUES.slice(0, CURRENT_ENUM_VALUES.indexOf("blogs_delete") + 1),
|
||||
...NEW_PERMISSIONS.map((p) => p.name),
|
||||
...CURRENT_ENUM_VALUES.slice(CURRENT_ENUM_VALUES.indexOf("blogs_delete") + 1),
|
||||
];
|
||||
|
||||
export class AddBlogCategoriesCommentsSlidersPermissions1785200000000 implements MigrationInterface {
|
||||
name = "AddBlogCategoriesCommentsSlidersPermissions1785200000000";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "permission" ALTER COLUMN "name" TYPE text USING "name"::text`);
|
||||
await queryRunner.query(`DROP TYPE "public"."permission_name_enum"`);
|
||||
|
||||
const enumList = UPDATED_ENUM_VALUES.map((v) => `'${v}'`).join(", ");
|
||||
await queryRunner.query(`CREATE TYPE "public"."permission_name_enum" AS ENUM(${enumList})`);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "permission" ALTER COLUMN "name" TYPE "public"."permission_name_enum" USING "name"::"public"."permission_name_enum"`,
|
||||
);
|
||||
|
||||
for (const perm of NEW_PERMISSIONS) {
|
||||
const inserted = await queryRunner.query(
|
||||
`INSERT INTO "permission" ("id", "name", "title", "group", "createdAt", "updatedAt")
|
||||
VALUES (uuid_generate_v4(), $1, $2, 'content', NOW(), NOW())
|
||||
RETURNING id`,
|
||||
[perm.name, perm.title],
|
||||
);
|
||||
const newId = inserted[0].id as string;
|
||||
|
||||
const sourcePerm = await queryRunner.query(`SELECT id FROM "permission" WHERE "name" = $1`, [perm.source]);
|
||||
if (!sourcePerm.length) continue;
|
||||
|
||||
await queryRunner.query(
|
||||
`INSERT INTO "role_permission_relation" ("roleId", "permissionId")
|
||||
SELECT "roleId", $1 FROM "role_permission_relation" WHERE "permissionId" = $2
|
||||
ON CONFLICT ("roleId", "permissionId") DO NOTHING`,
|
||||
[newId, sourcePerm[0].id],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
for (const perm of NEW_PERMISSIONS) {
|
||||
const rows = await queryRunner.query(`SELECT id FROM "permission" WHERE "name" = $1`, [perm.name]);
|
||||
if (!rows.length) continue;
|
||||
|
||||
await queryRunner.query(`DELETE FROM "role_permission_relation" WHERE "permissionId" = $1`, [rows[0].id]);
|
||||
await queryRunner.query(`DELETE FROM "permission" WHERE "id" = $1`, [rows[0].id]);
|
||||
}
|
||||
|
||||
await queryRunner.query(`ALTER TABLE "permission" ALTER COLUMN "name" TYPE text USING "name"::text`);
|
||||
await queryRunner.query(`DROP TYPE "public"."permission_name_enum"`);
|
||||
|
||||
const enumList = CURRENT_ENUM_VALUES.map((v) => `'${v}'`).join(", ");
|
||||
await queryRunner.query(`CREATE TYPE "public"."permission_name_enum" AS ENUM(${enumList})`);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "permission" ALTER COLUMN "name" TYPE "public"."permission_name_enum" USING "name"::"public"."permission_name_enum"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,9 @@ import { connectionSource } from "./connection";
|
||||
// import { seedCityAndProvince } from "./seeders/iran-city.seeder";
|
||||
// import { seedNotifSettings } from "./seeders/notif-setting.seeder";
|
||||
// import { seedPaymentGateways } from "./seeders/payment-gateway.seeder";
|
||||
// import { seedPermissionsAndRoles } from "./seeders/role.seeder";
|
||||
import { seedBlogCommentReplies } from "./seeders/blog-comment-reply.seeder";
|
||||
import { seedBlogComments } from "./seeders/blog-comment.seeder";
|
||||
import { seedPermissionsAndRoles } from "./seeders/role.seeder";
|
||||
// import { seedBlogCommentReplies } from "./seeders/blog-comment-reply.seeder";
|
||||
// import { seedBlogComments } from "./seeders/blog-comment.seeder";
|
||||
|
||||
const logger = new Logger("Seeder");
|
||||
|
||||
@@ -15,14 +15,14 @@ export const runSeeder = async () => {
|
||||
await connectionSource.initialize();
|
||||
logger.log("start seeding database");
|
||||
//
|
||||
// await seedPermissionsAndRoles(connectionSource, logger);
|
||||
await seedPermissionsAndRoles(connectionSource, logger);
|
||||
// await seedAdmin(connectionSource, logger);
|
||||
// await seedNotifSettings(connectionSource, logger);
|
||||
// await seedPaymentGateways(connectionSource, logger);
|
||||
// await seedCityAndProvince(connectionSource, logger);
|
||||
|
||||
await seedBlogComments(connectionSource, logger);
|
||||
await seedBlogCommentReplies(connectionSource, logger);
|
||||
// await seedBlogComments(connectionSource, logger);
|
||||
// await seedBlogCommentReplies(connectionSource, logger);
|
||||
|
||||
logger.log("seeding completed");
|
||||
process.exit(0);
|
||||
|
||||
@@ -1,13 +1,51 @@
|
||||
import { Logger } from "@nestjs/common";
|
||||
import { DataSource } from "typeorm";
|
||||
|
||||
import { Permission } from "../../src/modules/users/entities/permission.entity";
|
||||
import { Role } from "../../src/modules/users/entities/role.entity";
|
||||
// import { Permission } from "../../src/modules/users/entities/permission.entity";
|
||||
// import { Role } from "../../src/modules/users/entities/role.entity";
|
||||
import { PermissionEnum } from "../../src/modules/users/enums/permission.enum";
|
||||
import { RoleEnum } from "../../src/modules/users/enums/role.enum";
|
||||
|
||||
const permissionTitles: Record<PermissionEnum, string> = {
|
||||
[PermissionEnum.SERVICES]: "سرویسها",
|
||||
[PermissionEnum.CUSTOMERS]: "مشتریان",
|
||||
[PermissionEnum.AGENTS]: "نمایندگان",
|
||||
[PermissionEnum.DEVELOPERS]: "توسعهدهندگان",
|
||||
[PermissionEnum.INVOICES]: "صورتحسابها",
|
||||
[PermissionEnum.TRANSACTIONS]: "تراکنشها",
|
||||
[PermissionEnum.DISCOUNTS]: "تخفیفها",
|
||||
[PermissionEnum.ADMINS]: "ادمینها",
|
||||
[PermissionEnum.TICKETS]: "تیکتها",
|
||||
[PermissionEnum.CRITICISMS]: "انتقادات",
|
||||
[PermissionEnum.CONTACTS_US]: "تماس با ما",
|
||||
[PermissionEnum.ADVERTISEMENTS]: "تبلیغات",
|
||||
[PermissionEnum.ANNOUNCEMENTS]: "اطلاعیهها",
|
||||
[PermissionEnum.BLOGS]: "بلاگها",
|
||||
[PermissionEnum.LEARNINGS]: "آموزشها",
|
||||
[PermissionEnum.LOGS]: "لاگها",
|
||||
[PermissionEnum.SETTINGS]: "تنظیمات",
|
||||
[PermissionEnum.BANK_ACCOUNTS]: "حسابهای بانکی",
|
||||
[PermissionEnum.PAYMENTS]: "پرداختها",
|
||||
[PermissionEnum.MANAGE_SSO_CLIENTS]: "مدیریت کلاینتهای SSO",
|
||||
[PermissionEnum.SUPPORT_PLAN]: "پلن پشتیبانی",
|
||||
[PermissionEnum.DMENU]: "دیمنو",
|
||||
[PermissionEnum.DKALA]: "دیکالا",
|
||||
[PermissionEnum.DPAGE]: "دیپیج",
|
||||
[PermissionEnum.DMAIL]: "دیمیل",
|
||||
[PermissionEnum.RESELLER]: "نمایندگی فروش",
|
||||
[PermissionEnum.WORKSPACE]: "فضای کار",
|
||||
[PermissionEnum.VIEW_ALL_WORKSPACES]: "مشاهده همه فضاهای کار",
|
||||
[PermissionEnum.PROJECT]: "پروژه",
|
||||
[PermissionEnum.VIEW_ALL_PROJECTS]: "مشاهده همه پروژهها",
|
||||
[PermissionEnum.TASK]: "وظیفه",
|
||||
[PermissionEnum.TASK_PHASE]: "فاز وظیفه",
|
||||
};
|
||||
|
||||
// Define permissions
|
||||
const permissions = Object.values(PermissionEnum).map((perm) => ({ name: perm }));
|
||||
const permissions = Object.values(PermissionEnum).map((perm) => ({
|
||||
name: perm,
|
||||
title: permissionTitles[perm],
|
||||
}));
|
||||
|
||||
// Define roles with their default permissions
|
||||
const rolesWithPermissions: { name: RoleEnum; permissions: PermissionEnum[] }[] = [
|
||||
@@ -23,8 +61,8 @@ const rolesWithPermissions: { name: RoleEnum; permissions: PermissionEnum[] }[]
|
||||
|
||||
export const seedPermissionsAndRoles = async (dataSource: DataSource, logger: Logger) => {
|
||||
try {
|
||||
const permissionRepo = dataSource.getRepository(Permission);
|
||||
const roleRepo = dataSource.getRepository(Role);
|
||||
const permissionRepo = dataSource.getRepository("Permission");
|
||||
const roleRepo = dataSource.getRepository("Role");
|
||||
|
||||
// Seed Permissions
|
||||
logger.log("Seeding Permissions...");
|
||||
@@ -33,6 +71,9 @@ export const seedPermissionsAndRoles = async (dataSource: DataSource, logger: Lo
|
||||
if (!existingPerm) {
|
||||
const newPerm = permissionRepo.create(perm);
|
||||
await permissionRepo.save(newPerm);
|
||||
} else if (!existingPerm.title || existingPerm.title !== perm.title) {
|
||||
existingPerm.title = perm.title;
|
||||
await permissionRepo.save(existingPerm);
|
||||
}
|
||||
}
|
||||
logger.log("Permissions seeded successfully");
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
},
|
||||
{
|
||||
"path": "../danak-admin"
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
}
|
||||
@@ -1,11 +1,21 @@
|
||||
import { ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import { IsInt, IsISO8601, IsOptional, Max, Min } from "class-validator";
|
||||
import { IsEnum, IsInt, IsOptional, IsString, Max, Min } from "class-validator";
|
||||
|
||||
export enum CursorDirection {
|
||||
NEXT = "next",
|
||||
PREV = "prev",
|
||||
}
|
||||
|
||||
export class CursorPaginationDto {
|
||||
@IsOptional()
|
||||
@IsISO8601()
|
||||
@ApiPropertyOptional({ type: "string", description: "Cursor (createdAt of last seen item)", example: "2026-07-01T10:00:00.000Z" })
|
||||
@IsString()
|
||||
@ApiPropertyOptional({
|
||||
type: "string",
|
||||
description:
|
||||
"Base64‑encoded cursor containing the priority, createdAt (ISO) and id of the last seen item. Leave empty for the first page.",
|
||||
example: "MjAyNi0wNy0wMVQxMDowMDowMC4wMDBafDU=",
|
||||
})
|
||||
cursor?: string;
|
||||
|
||||
@IsOptional()
|
||||
@@ -15,4 +25,9 @@ export class CursorPaginationDto {
|
||||
@Type(() => Number)
|
||||
@ApiPropertyOptional({ type: "number", required: false, default: 10 })
|
||||
limit?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(CursorDirection)
|
||||
@ApiPropertyOptional({ enum: CursorDirection, default: CursorDirection.NEXT, description: "Direction to page in relative to 'cursor'." })
|
||||
direction?: CursorDirection;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { applyDecorators } from "@nestjs/common";
|
||||
import { ApiQuery } from "@nestjs/swagger";
|
||||
import { CursorDirection } from "../DTO/cursor-pagination.dto";
|
||||
|
||||
export function CursorPagination() {
|
||||
return applyDecorators(
|
||||
ApiQuery({
|
||||
name: "cursor",
|
||||
required: false,
|
||||
type: "string",
|
||||
description: "Base64‑encoded cursor of the last item (contains priority, createdAt and id). Leave empty for the first page.",
|
||||
example: "MjAyNi0wNy0wMVQxMDowMDowMC4wMDBafDU=",
|
||||
}),
|
||||
ApiQuery({
|
||||
name: "limit",
|
||||
required: false,
|
||||
type: "number",
|
||||
description: "Number of items per page (max 50)",
|
||||
example: 10,
|
||||
}),
|
||||
ApiQuery({
|
||||
name: "direction",
|
||||
required: false,
|
||||
enum: CursorDirection,
|
||||
description: "Direction to page in relative to 'cursor': 'next' (default) or 'prev'.",
|
||||
example: CursorDirection.NEXT,
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -330,6 +330,12 @@ export const enum TicketMessageEnum {
|
||||
INVALID_TICKET_LIMIT = "محدودیت تعداد تیکت در پلن پشتیبانی شما نامعتبر است",
|
||||
TICKET_LIMIT_EXCEEDED = "شما به محدودیت تعداد تیکت در پلن پشتیبانی خود رسیدهاید",
|
||||
OPENED = "تیکت با موفقیت باز شد",
|
||||
MASTER_USER_ID_REQUIRED = "شناسه کاربر مستر تیکت اجباری است",
|
||||
MASTER_USER_ID_SHOULD_BE_UUID = "شناسه کاربر مستر تیکت باید یک UUID معتبر باشد",
|
||||
MASTER_CREATED = "مستر تیکت با موفقیت ایجاد شد",
|
||||
MASTER_NOT_FOUND = "مستر تیکت یافت نشد",
|
||||
MASTER_EXIST = "این کاربر قبلاً به عنوان مستر تیکت ثبت شده است",
|
||||
MASTER_DELETED = "مستر تیکت با موفقیت حذف شد",
|
||||
}
|
||||
|
||||
export const enum WalletMessage {
|
||||
@@ -917,5 +923,53 @@ export const enum AccessLogMessage {
|
||||
}
|
||||
|
||||
export const enum WorkSpaceMessage {
|
||||
WORKSPACE_EXISTS = "فضای کار با این اسم وجود دارد!"
|
||||
WORKSPACE_EXISTS = "فضای کار با این اسم وجود دارد!",
|
||||
WORKSPACE_NOT_FOUND = "فضای کار مورد نظر شما وجود ندارد!",
|
||||
USERS_DONT_BELONG_TO_THIS_WORKSPACE = "کاربران تعیین شده برای این فضای کاری نیستند!",
|
||||
WORKSPACE_SAME_NAME_EXISTS = "فضای کاری با این اسم از قبل وجود دارد!",
|
||||
WORKSPACE_ID_PARAM_NOT_EMPTY = "پارامتر شناسه فضای کار نباید خالی باشد!",
|
||||
WORKSPACE_ID_UUID = "شناسه فضای کار باید uuid باشد",
|
||||
FORBIDDEN = "شما اجازه دسترسی به این فضای کار را ندارید و یا عضو آن نیستید!"
|
||||
}
|
||||
|
||||
export const enum ProjectMessage {
|
||||
PROJECT_EXISTS = "پروژه مورد نظر شما از قبل وجود دارد!",
|
||||
PROJECT_NOT_FOUND = "پروژه مورد نظر شما وجود ندارد!",
|
||||
PROJECT_CONTAINS_TASK_PHASES = "پروژه شامل فاز تسک است. لطفا ابتدا فاز تسک های این پروژه را حذف کنید.",
|
||||
NOT_MEMBER_OF_PROJECT = "شما عضو این پروژه نیستید!",
|
||||
}
|
||||
|
||||
export const enum TaskPhaseMessage {
|
||||
TASK_PHASE_NOT_FOUND = "فاز تسک مورد نظر پیدا نشد!",
|
||||
TASK_PHASE_NOT_SAME_PROJECT = "فاز تسک جدید با فاز تسک قبلی در یک پروژه نیستند!"
|
||||
}
|
||||
|
||||
export const enum TaskMessage {
|
||||
TASK_NOT_FOUND = "تسک مورد نظر پیدا نشد!",
|
||||
USERS_ASSIGNED_TO_TASK_NOT_PROJECT_MEMBER = "حداقل یکی از کاربران تعیین شده عضو پروژه نیستند!",
|
||||
USER_NOT_TASK_MEMBER = "شما عضو این تسک نیستید!",
|
||||
TASKS_NOT_IN_THE_SAME_TASK_PHASE = "تسک های مورد نظر داخل یک فاز تسک یکسان نیستند!"
|
||||
}
|
||||
|
||||
export const enum remarkMessage {
|
||||
REMARK_NOT_FOUND = "برچسب مورد نظر پیدا نشد!"
|
||||
}
|
||||
|
||||
export const enum checkListItemMessage {
|
||||
CHECKLISTITEM_NOT_FOUND = "آیتم چک لیست پیدا نشد!"
|
||||
}
|
||||
|
||||
export const enum attachmentMessage {
|
||||
ATTACHMENT_NOT_FOUND = "ضمیمه پیدا نشد!"
|
||||
}
|
||||
|
||||
export const enum timeMessage {
|
||||
TIME_NOT_FOUND = "زمان ثبت شده پیدا نشد!",
|
||||
INVALID_TIME_RANGE = "زمان پایان باید بعد از زمان شروع باشد!",
|
||||
TIME_ALREADY_STARTED = "برای این تسک یک زمان در حال اجرا وجود دارد!",
|
||||
NO_RUNNING_TIME = "زمان در حال اجرایی برای این تسک پیدا نشد!"
|
||||
}
|
||||
|
||||
export const enum taskCommentMessage {
|
||||
TASK_COMMENT_NOT_FOUND = "نظر مورد نظر پیدا نشد!"
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import { CursorPaginatedResult } from "../interfaces/cursor-paginated-result.interface";
|
||||
|
||||
export function buildCursorPageFormat<T extends { createdAt: Date }>(
|
||||
data: T[],
|
||||
limit: number,
|
||||
previousCursor?: Date,
|
||||
): CursorPaginatedResult<T> {
|
||||
const hasMore = data.length > limit;
|
||||
|
||||
if (hasMore) data.pop();
|
||||
|
||||
const nextCursor = hasMore ? data[data.length - 1].createdAt.toISOString() : null;
|
||||
|
||||
const prevCursor = previousCursor ? previousCursor.toISOString() : null;
|
||||
|
||||
return {
|
||||
data,
|
||||
meta: {
|
||||
nextCursor,
|
||||
prevCursor,
|
||||
hasMore,
|
||||
limit,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { IPageFormat } from "../interfaces/IPagination";
|
||||
|
||||
export function buildPageFormat(
|
||||
page: number,
|
||||
limit: number,
|
||||
totalItems: number,
|
||||
baseUrl: string,
|
||||
): IPageFormat {
|
||||
const totalPages = Math.ceil(totalItems / limit);
|
||||
|
||||
const prevPage =
|
||||
page > 1
|
||||
? `${baseUrl}?page=${page - 1}&limit=${limit}`
|
||||
: false;
|
||||
|
||||
const nextPage =
|
||||
page < totalPages
|
||||
? `${baseUrl}?page=${page + 1}&limit=${limit}`
|
||||
: false;
|
||||
|
||||
return {
|
||||
page,
|
||||
limit,
|
||||
totalItems,
|
||||
totalPages,
|
||||
prevPage,
|
||||
nextPage,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export interface ICursorPageFormat {
|
||||
limit: number;
|
||||
nextCursor: string | null;
|
||||
previousCursor: string | null;
|
||||
hasNextPage: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
export interface CursorPaginatedResult<T> {
|
||||
data: T[];
|
||||
meta: {
|
||||
nextCursor: string | null;
|
||||
prevCursor: string | null;
|
||||
hasMore: boolean;
|
||||
limit: number;
|
||||
};
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import { IPageFormat } from "./IPagination";
|
||||
|
||||
export interface PaginatedResult<T> {
|
||||
data: T[];
|
||||
meta: IPageFormat;
|
||||
}
|
||||
@@ -5,27 +5,33 @@ import { MailerAsyncOptions } from "@nestjs-modules/mailer/dist/interfaces/maile
|
||||
export function mailerConfig(): MailerAsyncOptions {
|
||||
return {
|
||||
inject: [ConfigService],
|
||||
useFactory: (configService: ConfigService) => ({
|
||||
transport: {
|
||||
host: configService.getOrThrow<string>("SMTP_HOST"),
|
||||
port: configService.getOrThrow<number>("SMTP_PORT"),
|
||||
secure: true,
|
||||
auth: {
|
||||
user: configService.getOrThrow<string>("SMTP_USER"),
|
||||
pass: configService.getOrThrow<string>("SMTP_PASS"),
|
||||
},
|
||||
},
|
||||
defaults: {
|
||||
from: configService.getOrThrow<string>("MAIL_FROM"),
|
||||
},
|
||||
useFactory: (configService: ConfigService) => {
|
||||
const port = Number(configService.getOrThrow<number>("SMTP_PORT"));
|
||||
// 465 = implicit TLS; 587 = STARTTLS (required by zone-mta behind Traefik)
|
||||
const secure = port === 465;
|
||||
|
||||
template: {
|
||||
dir: process.cwd() + "/src/modules/templates/email",
|
||||
adapter: new HandlebarsAdapter(),
|
||||
options: {
|
||||
strict: true,
|
||||
return {
|
||||
transport: {
|
||||
host: configService.getOrThrow<string>("SMTP_HOST"),
|
||||
port,
|
||||
secure,
|
||||
requireTLS: !secure,
|
||||
auth: {
|
||||
user: configService.getOrThrow<string>("SMTP_USER"),
|
||||
pass: configService.getOrThrow<string>("SMTP_PASS"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
defaults: {
|
||||
from: configService.getOrThrow<string>("MAIL_FROM"),
|
||||
},
|
||||
template: {
|
||||
dir: process.cwd() + "/src/modules/templates/email",
|
||||
adapter: new HandlebarsAdapter(),
|
||||
options: {
|
||||
strict: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { FastifyRequest } from "fastify";
|
||||
import { Observable, map } from "rxjs";
|
||||
|
||||
import { IPageFormat } from "../../common/interfaces/IPagination";
|
||||
import { ICursorPageFormat } from "../../common/interfaces/ICursor-pagination";
|
||||
|
||||
@Injectable()
|
||||
export class PaginationInterceptor implements NestInterceptor {
|
||||
@@ -10,7 +11,11 @@ export class PaginationInterceptor implements NestInterceptor {
|
||||
|
||||
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
|
||||
const request = context.switchToHttp().getRequest<FastifyRequest>();
|
||||
const query = request.query as { page: string; limit: string };
|
||||
const query = request.query as {
|
||||
page: string;
|
||||
limit: string;
|
||||
cursor?: string; // for cursor pagination
|
||||
};
|
||||
|
||||
const page = parseInt(query.page, 10) || 1;
|
||||
const limit = parseInt(query.limit, 10) || 10;
|
||||
@@ -20,17 +25,40 @@ export class PaginationInterceptor implements NestInterceptor {
|
||||
|
||||
return next.handle().pipe(
|
||||
map((data) => {
|
||||
// ---------- Offset (page‑based) pagination ----------
|
||||
if (data && (data.paginate || data.count)) {
|
||||
const { count, paginate, ...response } = data;
|
||||
this.logger.log(`paginate response from ${className}.${handlerName}`);
|
||||
this.logger.log(`offset paginate response from ${className}.${handlerName}`);
|
||||
const pager = this.formatPage(page, limit, count, request);
|
||||
|
||||
return {
|
||||
pager,
|
||||
...response,
|
||||
};
|
||||
return { pager, ...response };
|
||||
}
|
||||
|
||||
// ---------- Cursor‑based pagination ----------
|
||||
if (data && data.cursorPaginate === true) {
|
||||
const {
|
||||
data: items,
|
||||
nextCursor,
|
||||
previousCursor,
|
||||
hasNextPage,
|
||||
hasPreviousPage,
|
||||
cursorPaginate,
|
||||
...rest
|
||||
} = data;
|
||||
|
||||
this.logger.log(`cursor paginate response from ${className}.${handlerName}`);
|
||||
|
||||
const pager: ICursorPageFormat = {
|
||||
limit,
|
||||
nextCursor,
|
||||
previousCursor,
|
||||
hasNextPage,
|
||||
hasPreviousPage,
|
||||
};
|
||||
|
||||
return { items, pager, ...rest };
|
||||
}
|
||||
|
||||
// ---------- No pagination ----------
|
||||
return data;
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -19,41 +19,41 @@ export class AdsController {
|
||||
|
||||
@ApiOperation({ summary: "Create new ads" })
|
||||
@Post()
|
||||
@PermissionsDec(PermissionEnum.ADVERTISEMENTS)
|
||||
@PermissionsDec(PermissionEnum.ADVERTISEMENTS_CREATE)
|
||||
createAds(@Body() createDto: CreateAdsDto) {
|
||||
return this.adsService.createAds(createDto);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "Get all ads" })
|
||||
@PermissionsDec(PermissionEnum.ADVERTISEMENTS)
|
||||
@PermissionsDec(PermissionEnum.ADVERTISEMENTS_READ)
|
||||
@Get("list")
|
||||
getAdsList(@Query() queryDto: AdsSearchQueryDto) {
|
||||
return this.adsService.getAdsList(queryDto);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "Get ads by id" })
|
||||
@PermissionsDec(PermissionEnum.ADVERTISEMENTS)
|
||||
@PermissionsDec(PermissionEnum.ADVERTISEMENTS_READ)
|
||||
@Get(":id")
|
||||
getAdsById(@Param() paramDto: ParamDto) {
|
||||
return this.adsService.getAdsById(paramDto.id);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "Update ads by id" })
|
||||
@PermissionsDec(PermissionEnum.ADVERTISEMENTS)
|
||||
@PermissionsDec(PermissionEnum.ADVERTISEMENTS_UPDATE)
|
||||
@Patch(":id")
|
||||
updateAdsById(@Param() paramDto: ParamDto, @Body() updateDto: UpdateAdsDto) {
|
||||
return this.adsService.updateAdsById(paramDto.id, updateDto);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "Delete ads by id" })
|
||||
@PermissionsDec(PermissionEnum.ADVERTISEMENTS)
|
||||
@PermissionsDec(PermissionEnum.ADVERTISEMENTS_DELETE)
|
||||
@Delete(":id")
|
||||
deleteAdsById(@Param() paramDto: ParamDto) {
|
||||
return this.adsService.deleteAdsById(paramDto.id);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "Toggle ads status" })
|
||||
@PermissionsDec(PermissionEnum.ADVERTISEMENTS)
|
||||
@PermissionsDec(PermissionEnum.ADVERTISEMENTS_UPDATE)
|
||||
@Patch("toggle-status/:id")
|
||||
toggleAdsStatus(@Param() paramDto: ParamDto) {
|
||||
return this.adsService.toggleAdsStatus(paramDto.id);
|
||||
|
||||
@@ -16,14 +16,14 @@ import { PermissionEnum } from "../users/enums/permission.enum";
|
||||
export class AnnouncementController {
|
||||
constructor(private readonly announcementService: AnnouncementService) {}
|
||||
|
||||
@PermissionsDec(PermissionEnum.ANNOUNCEMENTS)
|
||||
@PermissionsDec(PermissionEnum.ANNOUNCEMENTS_CREATE)
|
||||
@ApiProperty({ description: "Create a new announcement (admin route)" })
|
||||
@Post()
|
||||
create(@Body() createAnnouncementDto: CreateAnnouncementDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.announcementService.createAnnouncement(createAnnouncementDto, userIpAndHeaders);
|
||||
}
|
||||
|
||||
@PermissionsDec(PermissionEnum.ANNOUNCEMENTS)
|
||||
@PermissionsDec(PermissionEnum.ANNOUNCEMENTS_UPDATE)
|
||||
@ApiProperty({ description: "Update an announcement (admin route)" })
|
||||
@Patch(":id")
|
||||
update(
|
||||
@@ -34,7 +34,7 @@ export class AnnouncementController {
|
||||
return this.announcementService.updateAnnouncement(paramDto.id, updateAnnouncementDto, userIpAndHeaders);
|
||||
}
|
||||
|
||||
@PermissionsDec(PermissionEnum.ANNOUNCEMENTS)
|
||||
@PermissionsDec(PermissionEnum.ANNOUNCEMENTS_DELETE)
|
||||
@ApiProperty({ description: "Delete an announcement (admin route)" })
|
||||
@Delete(":id")
|
||||
delete(@Param() paramDto: ParamDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
@@ -42,7 +42,7 @@ export class AnnouncementController {
|
||||
}
|
||||
|
||||
@ApiProperty({ description: "Get all announcements (admin route)" })
|
||||
@PermissionsDec(PermissionEnum.ANNOUNCEMENTS)
|
||||
@PermissionsDec(PermissionEnum.ANNOUNCEMENTS_READ)
|
||||
@Get()
|
||||
getAnnouncements(@Query() queryDto: SearchAnnouncementQueryDto) {
|
||||
return this.announcementService.getAnnouncements(queryDto);
|
||||
@@ -61,7 +61,7 @@ export class AnnouncementController {
|
||||
}
|
||||
|
||||
@ApiProperty({ description: "Get one announcements with id (admin route)" })
|
||||
@PermissionsDec(PermissionEnum.ANNOUNCEMENTS)
|
||||
@PermissionsDec(PermissionEnum.ANNOUNCEMENTS_READ)
|
||||
@Get(":id/admin")
|
||||
getAnnouncementByIdAdmin(@Param() paramDto: ParamDto) {
|
||||
return this.announcementService.getAnnouncementByIdAdmin(paramDto.id);
|
||||
|
||||
@@ -7,6 +7,7 @@ import { SSOClientService } from "./sso-client.service";
|
||||
import { TokensService } from "./tokens.service";
|
||||
import { AuthMessage } from "../../../common/enums/message.enum";
|
||||
import { User } from "../../users/entities/user.entity";
|
||||
import { getPermissionsFromRoles } from "../../users/utils/role-permissions.util";
|
||||
import { ITokenPayload } from "../interfaces/IToken-payload";
|
||||
|
||||
@Injectable()
|
||||
@@ -46,7 +47,7 @@ export class SSOService {
|
||||
const payload: ITokenPayload = {
|
||||
id: user.id,
|
||||
isAdmin: user.roles.some((r) => r.isAdmin),
|
||||
permissions: user.roles.flatMap((r) => (r?.permissions?.length ? r.permissions.map((p) => p.name) : [])),
|
||||
permissions: getPermissionsFromRoles(user.roles),
|
||||
audience: clientId,
|
||||
};
|
||||
|
||||
@@ -105,7 +106,7 @@ export class SSOService {
|
||||
const payload: ITokenPayload = {
|
||||
id: user.id,
|
||||
isAdmin: user.roles.some((r) => r.isAdmin),
|
||||
permissions: user.roles.flatMap((r) => (r?.permissions?.length ? r.permissions.map((p) => p.name) : [])),
|
||||
permissions: getPermissionsFromRoles(user.roles),
|
||||
audience: clientId,
|
||||
};
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import { DataSource, QueryRunner } from "typeorm";
|
||||
import { AuthMessage } from "../../../common/enums/message.enum";
|
||||
import { User } from "../../users/entities/user.entity";
|
||||
import { RefreshTokensRepository } from "../../users/repositories/refresh-token.repository";
|
||||
import { getPermissionsFromRoles } from "../../users/utils/role-permissions.util";
|
||||
import { ITokenPayload } from "../interfaces/IToken-payload";
|
||||
import { ResellerType } from "../DTO/verify-reseller-otp.dto";
|
||||
|
||||
@@ -28,7 +29,7 @@ export class TokensService {
|
||||
{
|
||||
id: user.id,
|
||||
isAdmin: user.roles.some((r) => r.isAdmin),
|
||||
permissions: user.roles.flatMap((r) => (r?.permissions?.length ? r.permissions.map((p) => p.name) : [])),
|
||||
permissions: getPermissionsFromRoles(user.roles),
|
||||
},
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
@@ -51,7 +51,7 @@ export class BlogsController {
|
||||
|
||||
@ApiOperation({ summary: "Get all blogs (admin route)" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.BLOGS)
|
||||
@PermissionsDec(PermissionEnum.BLOGS_READ)
|
||||
@Get("list")
|
||||
getBlogList(@Query() queryDto: BlogListSearchQueryDto) {
|
||||
return this.blogsService.getBlogList(queryDto);
|
||||
@@ -85,7 +85,7 @@ export class BlogsController {
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "toggle status of blogs (admin route)" })
|
||||
@PermissionsDec(PermissionEnum.BLOGS)
|
||||
@PermissionsDec(PermissionEnum.BLOGS_CREATE)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post(":id/toggle-status")
|
||||
toggleBlogStatus(@Param() paramDto: ParamDto) {
|
||||
@@ -103,7 +103,7 @@ export class BlogsController {
|
||||
|
||||
@ApiOperation({ summary: "Toggle pinned status of a blog (admin route)" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.BLOGS)
|
||||
@PermissionsDec(PermissionEnum.BLOGS_CREATE)
|
||||
@Post(":id/toggle-pinned")
|
||||
togglePinnedStatus(@Param() paramDto: ParamDto) {
|
||||
return this.blogsService.togglePinnedStatus(paramDto.id);
|
||||
@@ -112,7 +112,7 @@ export class BlogsController {
|
||||
@ApiOperation({ summary: "Create a new blog (admin route)" })
|
||||
@Post()
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.BLOGS)
|
||||
@PermissionsDec(PermissionEnum.BLOGS_CREATE)
|
||||
createBlog(@Body() createBlogDto: CreateBlogDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.blogsService.createBlog(createBlogDto, userIpAndHeaders);
|
||||
}
|
||||
@@ -120,7 +120,7 @@ export class BlogsController {
|
||||
@ApiOperation({ summary: "Update a blog (admin route)" })
|
||||
@Patch(":id")
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.BLOGS)
|
||||
@PermissionsDec(PermissionEnum.BLOGS_UPDATE)
|
||||
updateBlog(@Param() paramDto: ParamDto, @Body() updateBlogDto: UpdateBlogDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.blogsService.updateBlog(paramDto.id, updateBlogDto, userIpAndHeaders);
|
||||
}
|
||||
@@ -128,7 +128,7 @@ export class BlogsController {
|
||||
@ApiOperation({ summary: "Delete a blog (admin route) " })
|
||||
@Delete(":id")
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.BLOGS)
|
||||
@PermissionsDec(PermissionEnum.BLOGS_DELETE)
|
||||
deleteBlog(@Param() paramDto: ParamDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.blogsService.deleteBlog(paramDto.id, userIpAndHeaders);
|
||||
}
|
||||
@@ -149,7 +149,7 @@ export class BlogsController {
|
||||
|
||||
@ApiOperation({ summary: "Get all comments for a blog (admin route)" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.BLOGS)
|
||||
@PermissionsDec(PermissionEnum.BLOG_COMMENTS_READ)
|
||||
@Get(":id/comments")
|
||||
getBlogCommentsForAdmin(@Param() paramDto: ParamDto) {
|
||||
return this.blogCommentService.getBlogCommentsForAdmin(paramDto.id);
|
||||
@@ -157,7 +157,7 @@ export class BlogsController {
|
||||
|
||||
@ApiOperation({ summary: "Get all comments for a blog (admin route)" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.BLOGS)
|
||||
@PermissionsDec(PermissionEnum.BLOG_COMMENTS_READ)
|
||||
@Get("comments")
|
||||
getAllCommentForAdmin(@Query() queryDto: BlogCommentQueryDto) {
|
||||
return this.blogCommentService.getAllCommentForAdmin(queryDto);
|
||||
@@ -165,7 +165,7 @@ export class BlogsController {
|
||||
|
||||
@ApiOperation({ summary: "Update the status of a comment (admin route)" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.BLOGS)
|
||||
@PermissionsDec(PermissionEnum.BLOG_COMMENTS_UPDATE)
|
||||
@Patch("comments/:id/status")
|
||||
updateCommentStatus(@Param() paramDto: ParamDto, @Body() updateCommentStatusDto: UpdateCommentStatusDto) {
|
||||
return this.blogCommentService.updateCommentStatus(paramDto.id, updateCommentStatusDto);
|
||||
@@ -196,7 +196,7 @@ export class BlogsController {
|
||||
@ApiOperation({ summary: "Create a new category (admin route)" })
|
||||
@Post("categories")
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.BLOGS)
|
||||
@PermissionsDec(PermissionEnum.BLOG_CATEGORIES_CREATE)
|
||||
createCategory(@Body() createCategoryDto: CreateBlogCategoryDto) {
|
||||
return this.blogsService.createCategory(createCategoryDto);
|
||||
}
|
||||
@@ -204,7 +204,7 @@ export class BlogsController {
|
||||
@ApiOperation({ summary: "Update a category (admin route)" })
|
||||
@Patch("categories/:id")
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.BLOGS)
|
||||
@PermissionsDec(PermissionEnum.BLOG_CATEGORIES_UPDATE)
|
||||
updateCategory(@Param() paramDto: ParamDto, @Body() updateCategoryDto: UpdateBlogCategoryDto) {
|
||||
return this.blogsService.updateCategory(paramDto.id, updateCategoryDto);
|
||||
}
|
||||
@@ -212,13 +212,13 @@ export class BlogsController {
|
||||
@ApiOperation({ summary: "Delete a category (admin route)" })
|
||||
@Delete("categories/:id")
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.BLOGS)
|
||||
@PermissionsDec(PermissionEnum.BLOG_CATEGORIES_DELETE)
|
||||
deleteCategory(@Param() paramDto: ParamDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.blogsService.deleteCategory(paramDto.id, userIpAndHeaders);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "toggle status of categories (admin route)" })
|
||||
@PermissionsDec(PermissionEnum.BLOGS)
|
||||
@PermissionsDec(PermissionEnum.BLOG_CATEGORIES_UPDATE)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post("categories/toggle-status/:id")
|
||||
toggleCategoryStatus(@Param() paramDto: ParamDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
|
||||
@@ -25,7 +25,7 @@ export class ContactUsController {
|
||||
|
||||
//******************** */
|
||||
|
||||
@PermissionsDec(PermissionEnum.CONTACTS_US)
|
||||
@PermissionsDec(PermissionEnum.CONTACTS_US_READ)
|
||||
@AdminRoute()
|
||||
@ApiOperation({ summary: "Get all contact us (admin route)" })
|
||||
@Get()
|
||||
@@ -36,7 +36,7 @@ export class ContactUsController {
|
||||
//******************** */
|
||||
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.CONTACTS_US)
|
||||
@PermissionsDec(PermissionEnum.CONTACTS_US_READ)
|
||||
@ApiOperation({ summary: "Get one contact us with id (admin route)" })
|
||||
@Get(":id")
|
||||
getOneContactUs(@Param() paramDto: ParamDto) {
|
||||
|
||||
@@ -23,7 +23,7 @@ export class CriticismController {
|
||||
}
|
||||
|
||||
@AuthGuards()
|
||||
@PermissionsDec(PermissionEnum.CRITICISMS)
|
||||
@PermissionsDec(PermissionEnum.CRITICISMS_READ)
|
||||
@ApiOperation({ summary: "Get all Criticism ===> login as admin" })
|
||||
@Get()
|
||||
findAll(@Query() queryDto: SearchCriticismQueryDto) {
|
||||
@@ -31,7 +31,7 @@ export class CriticismController {
|
||||
}
|
||||
|
||||
@AuthGuards()
|
||||
@PermissionsDec(PermissionEnum.CRITICISMS)
|
||||
@PermissionsDec(PermissionEnum.CRITICISMS_READ)
|
||||
@ApiOperation({ summary: "Get one Criticism with id ===> login as admin" })
|
||||
@Get(":id")
|
||||
fineOne(@Param() paramDto: ParamDto) {
|
||||
@@ -39,7 +39,7 @@ export class CriticismController {
|
||||
}
|
||||
|
||||
@AuthGuards()
|
||||
@PermissionsDec(PermissionEnum.CRITICISMS)
|
||||
@PermissionsDec(PermissionEnum.CRITICISMS_DELETE)
|
||||
@ApiOperation({ summary: "Delete one Criticism with id ===> login as admin" })
|
||||
@Delete(":id")
|
||||
delete(@Param() paramDto: ParamDto) {
|
||||
|
||||
@@ -37,7 +37,7 @@ export class DanakServicesController {
|
||||
|
||||
//------------------------ service categories ------------------------
|
||||
@AuthGuards()
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@PermissionsDec(PermissionEnum.SERVICES_CREATE)
|
||||
@ApiOperation({ summary: "Create a new service category => admin route" })
|
||||
@Post("categories")
|
||||
createCategory(@Body() createDto: CreateDanakServiceCategoryDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
@@ -45,7 +45,7 @@ export class DanakServicesController {
|
||||
}
|
||||
|
||||
@AuthGuards()
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@PermissionsDec(PermissionEnum.SERVICES_READ)
|
||||
@ApiOperation({ summary: "Get all service categories => admin route" })
|
||||
@Get("categories")
|
||||
getCategories(@Query() queryDto: CategorySearchQueryDto) {
|
||||
@@ -53,7 +53,7 @@ export class DanakServicesController {
|
||||
}
|
||||
|
||||
@AuthGuards()
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@PermissionsDec(PermissionEnum.SERVICES_READ)
|
||||
@ApiOperation({ summary: "Get all service categories => admin route" })
|
||||
@Pagination()
|
||||
@Get("categories/list")
|
||||
@@ -69,7 +69,7 @@ export class DanakServicesController {
|
||||
}
|
||||
|
||||
@AuthGuards()
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@PermissionsDec(PermissionEnum.SERVICES_READ)
|
||||
@ApiOperation({ summary: "Get category by id => admin route" })
|
||||
@Get("categories/:id")
|
||||
getCategoryById(@Param() paramDto: ParamDto) {
|
||||
@@ -77,7 +77,7 @@ export class DanakServicesController {
|
||||
}
|
||||
|
||||
@AuthGuards()
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@PermissionsDec(PermissionEnum.SERVICES_UPDATE)
|
||||
@ApiOperation({ summary: "update category by id => admin route" })
|
||||
@Patch("categories/:id")
|
||||
updateCategory(
|
||||
@@ -89,7 +89,7 @@ export class DanakServicesController {
|
||||
}
|
||||
|
||||
@AuthGuards()
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@PermissionsDec(PermissionEnum.SERVICES_DELETE)
|
||||
@ApiOperation({ summary: "delete category by id => admin route" })
|
||||
@Delete("categories/:id")
|
||||
deleteCategoryById(@Param() paramDto: ParamDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
@@ -97,7 +97,7 @@ export class DanakServicesController {
|
||||
}
|
||||
|
||||
@AuthGuards()
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@PermissionsDec(PermissionEnum.SERVICES_CREATE)
|
||||
@ApiOperation({ summary: "toggle status of categories => admin route" })
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post("categories/toggle-status/:id")
|
||||
@@ -106,7 +106,7 @@ export class DanakServicesController {
|
||||
}
|
||||
//-------------------- service management --------------------------
|
||||
@AuthGuards()
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@PermissionsDec(PermissionEnum.SERVICES_CREATE)
|
||||
@ApiOperation({ summary: "create new danak services => admin route" })
|
||||
@Post()
|
||||
createService(@Body() createDto: CreateServiceDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
@@ -114,7 +114,7 @@ export class DanakServicesController {
|
||||
}
|
||||
|
||||
@AuthGuards()
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@PermissionsDec(PermissionEnum.SERVICES_READ)
|
||||
@ApiOperation({ summary: "get all danak services ==> admin route" })
|
||||
@Get()
|
||||
getServices(@Query() queryDto: DanakServicesSearchQueryDto) {
|
||||
@@ -136,7 +136,7 @@ export class DanakServicesController {
|
||||
}
|
||||
|
||||
@AuthGuards()
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@PermissionsDec(PermissionEnum.SERVICES_CREATE)
|
||||
@ApiOperation({ summary: "toggle status of danak service => admin route" })
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post("toggle-status/:id")
|
||||
@@ -145,7 +145,7 @@ export class DanakServicesController {
|
||||
}
|
||||
|
||||
@AuthGuards()
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@PermissionsDec(PermissionEnum.SERVICES_CREATE)
|
||||
@ApiOperation({ summary: "toggle status of danak service slider => admin route" })
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post("toggle-slider/:id")
|
||||
@@ -192,7 +192,7 @@ export class DanakServicesController {
|
||||
|
||||
@ApiOperation({ summary: "update danak service" })
|
||||
@AuthGuards()
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@PermissionsDec(PermissionEnum.SERVICES_UPDATE)
|
||||
@Patch(":id")
|
||||
updateDanakService(
|
||||
@Param() paramDto: ParamDto,
|
||||
@@ -204,7 +204,7 @@ export class DanakServicesController {
|
||||
|
||||
@ApiOperation({ summary: "delete danak service" })
|
||||
@AuthGuards()
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@PermissionsDec(PermissionEnum.SERVICES_DELETE)
|
||||
@Delete(":id")
|
||||
deleteServiceById(@Param() paramDto: ParamDto) {
|
||||
return this.danakServicesService.deleteServiceById(paramDto.id);
|
||||
@@ -212,7 +212,7 @@ export class DanakServicesController {
|
||||
|
||||
@ApiOperation({ summary: "get service users" })
|
||||
@AuthGuards()
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@PermissionsDec(PermissionEnum.SERVICES_READ)
|
||||
@Get(":id/users")
|
||||
getServiceUsers(@Param() paramDto: ParamDto) {
|
||||
return this.danakServicesService.getServiceUsers(paramDto.id);
|
||||
@@ -235,7 +235,7 @@ export class DanakServicesController {
|
||||
@ApiOperation({ summary: "get danak service reviews ==> admin route" })
|
||||
@AuthGuards()
|
||||
@Pagination()
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@PermissionsDec(PermissionEnum.SERVICES_READ)
|
||||
@Get("reviews")
|
||||
getDanakServiceReviews(@Query() queryDto: DanakServiceReviewQueryDto) {
|
||||
return this.danakServiceReviewService.getDanakServiceReviews(queryDto);
|
||||
@@ -243,7 +243,7 @@ export class DanakServicesController {
|
||||
|
||||
@ApiOperation({ summary: "update danak service review status ==> admin route" })
|
||||
@AuthGuards()
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@PermissionsDec(PermissionEnum.SERVICES_UPDATE)
|
||||
@Patch("reviews/:id/:status")
|
||||
updateDanakServiceReviewStatus(@Param() updateParamDto: UpdateReviewStatusDto) {
|
||||
return this.danakServiceReviewService.updateDanakServiceReviewStatus(updateParamDto);
|
||||
@@ -252,7 +252,7 @@ export class DanakServicesController {
|
||||
//------------------------ service guides ------------------------
|
||||
@ApiOperation({ summary: "Create a service guide => admin route" })
|
||||
@AuthGuards()
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@PermissionsDec(PermissionEnum.SERVICES_CREATE)
|
||||
@Post("guides")
|
||||
createServiceGuide(@Body() createDto: CreateServiceGuideDto) {
|
||||
return this.danakServicesService.createServiceGuide(createDto);
|
||||
@@ -266,7 +266,7 @@ export class DanakServicesController {
|
||||
|
||||
@ApiOperation({ summary: "Update service guide => admin route" })
|
||||
@AuthGuards()
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@PermissionsDec(PermissionEnum.SERVICES_UPDATE)
|
||||
@Patch("guides/:id")
|
||||
updateServiceGuide(@Param() paramDto: ParamDto, @Body() updateDto: UpdateServiceGuideDto) {
|
||||
return this.danakServicesService.updateServiceGuide(paramDto.id, updateDto);
|
||||
@@ -274,7 +274,7 @@ export class DanakServicesController {
|
||||
|
||||
@ApiOperation({ summary: "Delete service guide => admin route" })
|
||||
@AuthGuards()
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@PermissionsDec(PermissionEnum.SERVICES_DELETE)
|
||||
@Delete("guides/:id")
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
deleteServiceGuide(@Param() paramDto: ParamDto) {
|
||||
@@ -283,7 +283,7 @@ export class DanakServicesController {
|
||||
|
||||
@ApiOperation({ summary: "Get service guide by id => admin route" })
|
||||
@AuthGuards()
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@PermissionsDec(PermissionEnum.SERVICES_READ)
|
||||
@Get("guides/:id")
|
||||
getServiceGuideById(@Param() paramDto: ParamDto) {
|
||||
return this.danakServicesService.getServiceGuideById(paramDto.id);
|
||||
@@ -299,7 +299,7 @@ export class DanakServicesController {
|
||||
|
||||
@ApiOperation({ summary: "Get service feedbacks => admin route" })
|
||||
@AuthGuards()
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@PermissionsDec(PermissionEnum.SERVICES_READ)
|
||||
@Pagination()
|
||||
@Get("feedback")
|
||||
getServiceFeedbacks(@Query() queryDto: ServiceFeedbackQueryDto) {
|
||||
@@ -308,7 +308,7 @@ export class DanakServicesController {
|
||||
|
||||
@ApiOperation({ summary: "Get service feedback statistics => admin route" })
|
||||
@AuthGuards()
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@PermissionsDec(PermissionEnum.SERVICES_READ)
|
||||
@Get(":id/feedback")
|
||||
getServiceFeedbackStats(@Param() paramDto: ParamDto) {
|
||||
return this.danakServicesService.getServiceFeedback(paramDto.id);
|
||||
|
||||
@@ -19,7 +19,7 @@ export class DiscountsController {
|
||||
|
||||
@ApiOperation({ summary: "Create a new discount" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.DISCOUNTS)
|
||||
@PermissionsDec(PermissionEnum.DISCOUNTS_CREATE)
|
||||
@Post()
|
||||
@ApiResponse({ status: 201, description: "Discount created successfully" })
|
||||
create(@Body() createDiscountDto: CreateDiscountDto) {
|
||||
@@ -28,7 +28,7 @@ export class DiscountsController {
|
||||
|
||||
@ApiOperation({ summary: "Get all discounts for admin" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.DISCOUNTS)
|
||||
@PermissionsDec(PermissionEnum.DISCOUNTS_READ)
|
||||
@Get()
|
||||
@ApiResponse({ status: 200, description: "Discounts fetched successfully" })
|
||||
getDiscountsForAdmin(@Query() queryDto: SearchDiscountQueryDto) {
|
||||
@@ -37,7 +37,7 @@ export class DiscountsController {
|
||||
|
||||
@ApiOperation({ summary: "Get discount by id" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.DISCOUNTS)
|
||||
@PermissionsDec(PermissionEnum.DISCOUNTS_READ)
|
||||
@Get(":id")
|
||||
@ApiResponse({ status: 200, description: "Discount fetched successfully" })
|
||||
getDiscountById(@Param() paramDto: ParamDto) {
|
||||
@@ -46,7 +46,7 @@ export class DiscountsController {
|
||||
|
||||
@ApiOperation({ summary: "Toggle discount status" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.DISCOUNTS)
|
||||
@PermissionsDec(PermissionEnum.DISCOUNTS_CREATE)
|
||||
@Post(":id/toggle-status")
|
||||
toggleDiscountStatus(@Param() paramDto: ParamDto) {
|
||||
return this.discountsService.toggleDiscountStatus(paramDto.id);
|
||||
@@ -54,7 +54,7 @@ export class DiscountsController {
|
||||
|
||||
@ApiOperation({ summary: "Delete discount" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.DISCOUNTS)
|
||||
@PermissionsDec(PermissionEnum.DISCOUNTS_DELETE)
|
||||
@Delete(":id")
|
||||
deleteDiscount(@Param() paramDto: ParamDto) {
|
||||
return this.discountsService.softDeleteDiscount(paramDto.id);
|
||||
@@ -62,7 +62,7 @@ export class DiscountsController {
|
||||
|
||||
@ApiOperation({ summary: "Update discount" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.DISCOUNTS)
|
||||
@PermissionsDec(PermissionEnum.DISCOUNTS_UPDATE)
|
||||
@Patch(":id")
|
||||
updateDiscount(@Param() paramDto: ParamDto, @Body() updateDiscountDto: UpdateDiscountDto) {
|
||||
return this.discountsService.updateDiscount(paramDto.id, updateDiscountDto);
|
||||
|
||||
@@ -5,6 +5,8 @@ import { FindBusinessesDto } from './dto/find-businesses.dto';
|
||||
import { AuthGuards } from '../../common/decorators/auth-guard.decorator';
|
||||
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||
import { AdminRoute } from '../../common/decorators/admin.decorator';
|
||||
import { PermissionsDec } from '../../common/decorators/permission.decorator';
|
||||
import { PermissionEnum } from '../users/enums/permission.enum';
|
||||
|
||||
@Controller('dmail')
|
||||
@ApiTags('d-mail')
|
||||
@@ -15,6 +17,7 @@ export class DmailController {
|
||||
|
||||
@Get()
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.DMAIL)
|
||||
findAll(@Query() query: FindBusinessesDto) {
|
||||
return this.dmailService.findAllBusinesses(query)
|
||||
}
|
||||
@@ -27,6 +30,7 @@ export class DmailController {
|
||||
|
||||
@Patch(':id/active')
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.DMAIL)
|
||||
update(@Param('id') id: string, @Body() updateDmailDto: UpdateDmailAciveStatusDto) {
|
||||
return this.dmailService.updateDMailActiveStatus(id, updateDmailDto.isActive);
|
||||
}
|
||||
|
||||
@@ -444,6 +444,46 @@ export class RestaurantService {
|
||||
}
|
||||
}
|
||||
|
||||
async finishChargeRequest(invoiceId: string) {
|
||||
this.logger.log(`Finishing charge request for invoice ${invoiceId}`);
|
||||
try {
|
||||
const url = `${this.config.baseUrl}/super-admin/restaurants/charge-request/finish`;
|
||||
const { data } = await firstValueFrom(
|
||||
this.httpService
|
||||
.post(url, { invoiceId }, {
|
||||
headers: this.getHeaders(),
|
||||
})
|
||||
.pipe(
|
||||
catchError((err: AxiosError) => {
|
||||
this.logger.error(
|
||||
`Failed to finish charge request for invoice ${invoiceId}: ${err.message}`,
|
||||
err.stack,
|
||||
);
|
||||
return throwError(() => err);
|
||||
}),
|
||||
),
|
||||
);
|
||||
this.logger.log(`Successfully finished charge request for invoice ${invoiceId}`);
|
||||
return data;
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof AxiosError && error.response) {
|
||||
this.logger.error(
|
||||
`External API error finishing charge request for invoice ${invoiceId} (status ${error.response.status}):`,
|
||||
error.response.data,
|
||||
);
|
||||
const errorResponse = {
|
||||
...error.response.data,
|
||||
message: error.response.data.error?.message || error.message,
|
||||
};
|
||||
throw new HttpException(errorResponse, error.response.status);
|
||||
}
|
||||
this.logger.error(
|
||||
`Error finishing charge request for invoice ${invoiceId}: ${error instanceof Error ? error.message : "Unknown error"}`,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async directLogin(userSubscriptionId: string, userId: string) {
|
||||
try {
|
||||
// 1. Get userSubscription by id
|
||||
|
||||
@@ -21,28 +21,28 @@ export class InvoicesController {
|
||||
constructor(private readonly invoiceService: InvoicesService) {}
|
||||
|
||||
@ApiOperation({ summary: "create an invoice ==> admin route" })
|
||||
@PermissionsDec(PermissionEnum.INVOICES)
|
||||
@PermissionsDec(PermissionEnum.INVOICES_CREATE)
|
||||
@Post()
|
||||
createInvoice(@Body() createDto: CreateInvoiceDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.invoiceService.createInvoiceAdmin(createDto, userIpAndHeaders);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "update an invoice ==> admin route" })
|
||||
@PermissionsDec(PermissionEnum.INVOICES)
|
||||
@PermissionsDec(PermissionEnum.INVOICES_UPDATE)
|
||||
@Patch(":id")
|
||||
updateInvoice(@Param() paramDto: ParamDto, @Body() updateDto: UpdateInvoiceDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.invoiceService.updateInvoiceAdmin(paramDto.id, updateDto, userIpAndHeaders);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "delete an invoice ==> admin route" })
|
||||
@PermissionsDec(PermissionEnum.INVOICES)
|
||||
@PermissionsDec(PermissionEnum.INVOICES_DELETE)
|
||||
@Delete(":id")
|
||||
deleteInvoice(@Param() paramDto: ParamDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.invoiceService.deleteInvoiceAdmin(paramDto.id, userIpAndHeaders);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "get all invoices ==> admin route" })
|
||||
@PermissionsDec(PermissionEnum.INVOICES)
|
||||
@PermissionsDec(PermissionEnum.INVOICES_READ)
|
||||
@Get()
|
||||
getInvoices(@Query() queryDto: InvoicesSearchQueryDto) {
|
||||
return this.invoiceService.getInvoices(queryDto);
|
||||
|
||||
@@ -948,9 +948,13 @@ export class InvoicesService {
|
||||
await this.addNotifyForWalletDeduction(invoice, user, userWallet, WalletMessage.SUPPORT_PLAN_WALLET_TRANSFER);
|
||||
//
|
||||
} else if (invoice.isExternal) {
|
||||
if (invoice.items[0]?.name == 'purchase_design') {
|
||||
if (invoice.items[0]?.name == 'dmenu_wallet_charge') {
|
||||
this.restaurantService.finishChargeRequest(invoice.id);
|
||||
} else if (invoice.items[0]?.name == 'purchase_plan') {
|
||||
this.dpageService.finishPurchaseDesign(invoice.externalBusinessId!, invoice.id)
|
||||
} else if (invoice.items[0]?.name == 'purchase_new_catalogue') {
|
||||
}
|
||||
|
||||
else if (invoice.items[0]?.name == 'purchase_new_catalogue') {
|
||||
// call dpage api
|
||||
const businessId = invoice.externalBusinessId
|
||||
if (!businessId) {
|
||||
|
||||
@@ -18,7 +18,7 @@ export class LearningController {
|
||||
constructor(private readonly learningService: LearningService) {}
|
||||
|
||||
@ApiOperation({ summary: "Create learning video" })
|
||||
@PermissionsDec(PermissionEnum.LEARNINGS)
|
||||
@PermissionsDec(PermissionEnum.LEARNINGS_CREATE)
|
||||
@Post()
|
||||
createLearningVideo(@Body() createDto: CreateLearningDto) {
|
||||
return this.learningService.createLearningVideo(createDto);
|
||||
@@ -37,14 +37,14 @@ export class LearningController {
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "delete learning video by id" })
|
||||
@PermissionsDec(PermissionEnum.LEARNINGS)
|
||||
@PermissionsDec(PermissionEnum.LEARNINGS_DELETE)
|
||||
@Delete(":id")
|
||||
deleteLearningVideoById(@Param() paramDto: ParamDto) {
|
||||
return this.learningService.deleteLearningVideoById(paramDto.id);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "update learning video by id" })
|
||||
@PermissionsDec(PermissionEnum.LEARNINGS)
|
||||
@PermissionsDec(PermissionEnum.LEARNINGS_UPDATE)
|
||||
@Patch(":id")
|
||||
updateLearningVideoById(@Param() paramDto: ParamDto, @Body() updateDto: UpdateLearningDto) {
|
||||
return this.learningService.updateLearningVideo(paramDto.id, updateDto);
|
||||
@@ -53,7 +53,7 @@ export class LearningController {
|
||||
//----------------- category management ----------------------
|
||||
|
||||
@ApiOperation({ summary: "Create learning category" })
|
||||
@PermissionsDec(PermissionEnum.LEARNINGS)
|
||||
@PermissionsDec(PermissionEnum.LEARNINGS_CREATE)
|
||||
@Post("category")
|
||||
createCategory(@Body() createDto: CreateLearningCategoryDto) {
|
||||
return this.learningService.createLearningCategory(createDto);
|
||||
@@ -72,14 +72,14 @@ export class LearningController {
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "update learning category ==> admin route" })
|
||||
@PermissionsDec(PermissionEnum.LEARNINGS)
|
||||
@PermissionsDec(PermissionEnum.LEARNINGS_UPDATE)
|
||||
@Patch("category/:id")
|
||||
updateLearningCategory(@Param() paramDto: ParamDto, @Body() updateDto: UpdateLearningCategoryDto) {
|
||||
return this.learningService.updateLearningCategory(paramDto.id, updateDto);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "delete category by id" })
|
||||
@PermissionsDec(PermissionEnum.LEARNINGS)
|
||||
@PermissionsDec(PermissionEnum.LEARNINGS_DELETE)
|
||||
@Delete("category/:id")
|
||||
deleteCategoryById(@Param() paramDto: ParamDto) {
|
||||
return this.learningService.deleteCategoryById(paramDto.id);
|
||||
|
||||
@@ -45,7 +45,7 @@ export class PaymentsController {
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "get deposit request of users ==> admin route" })
|
||||
@PermissionsDec(PermissionEnum.PAYMENTS)
|
||||
@PermissionsDec(PermissionEnum.PAYMENTS_READ)
|
||||
@AuthGuards()
|
||||
@Pagination()
|
||||
@Get("deposit/transfer")
|
||||
@@ -54,7 +54,7 @@ export class PaymentsController {
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "get deposit gateway payment ==> admin route" })
|
||||
@PermissionsDec(PermissionEnum.PAYMENTS)
|
||||
@PermissionsDec(PermissionEnum.PAYMENTS_READ)
|
||||
@AuthGuards()
|
||||
@Pagination()
|
||||
@Get("deposit/gateway")
|
||||
@@ -63,7 +63,7 @@ export class PaymentsController {
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "approve deposit request ==> admin route" })
|
||||
@PermissionsDec(PermissionEnum.PAYMENTS)
|
||||
@PermissionsDec(PermissionEnum.PAYMENTS_UPDATE)
|
||||
@AuthGuards()
|
||||
@Post("deposit/transfer/approve/:id")
|
||||
approverDepositRequest(@Param() paramDto: ParamDto) {
|
||||
@@ -71,7 +71,7 @@ export class PaymentsController {
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "reject deposit request ==> admin route" })
|
||||
@PermissionsDec(PermissionEnum.PAYMENTS)
|
||||
@PermissionsDec(PermissionEnum.PAYMENTS_UPDATE)
|
||||
@AuthGuards()
|
||||
@Post("deposit/transfer/reject/:id")
|
||||
rejectDepositRequest(@Param() paramDto: ParamDto, @Body() rejectDto: RejectDepositRequestDto) {
|
||||
@@ -79,7 +79,7 @@ export class PaymentsController {
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "get transaction for admin" })
|
||||
@PermissionsDec(PermissionEnum.PAYMENTS)
|
||||
@PermissionsDec(PermissionEnum.PAYMENTS_READ)
|
||||
@AuthGuards()
|
||||
@Pagination()
|
||||
@Get("transactions")
|
||||
@@ -108,7 +108,7 @@ export class PaymentsController {
|
||||
///------------------- bank account -----------------------------
|
||||
|
||||
@AuthGuards()
|
||||
@PermissionsDec(PermissionEnum.PAYMENTS, PermissionEnum.BANK_ACCOUNTS)
|
||||
@PermissionsDec(PermissionEnum.PAYMENTS_CREATE, PermissionEnum.BANK_ACCOUNTS_CREATE)
|
||||
@ApiOperation({ summary: "add bank account for transfer and deposit ==> admin route" })
|
||||
@Post("bank-account")
|
||||
addBankAccount(@Body() createDto: CreateBankAccountDto) {
|
||||
@@ -124,7 +124,7 @@ export class PaymentsController {
|
||||
|
||||
@AuthGuards()
|
||||
@ApiOperation({ summary: "update bank account with its id" })
|
||||
@PermissionsDec(PermissionEnum.PAYMENTS, PermissionEnum.BANK_ACCOUNTS)
|
||||
@PermissionsDec(PermissionEnum.PAYMENTS_UPDATE, PermissionEnum.BANK_ACCOUNTS_UPDATE)
|
||||
@Patch("bank-account/:id")
|
||||
updateBankAccount(@Body() updateDto: UpdateBankAccountDto, @Param() paramDto: ParamDto) {
|
||||
return this.paymentsService.updateBankAccount(updateDto, paramDto.id);
|
||||
|
||||
@@ -34,7 +34,7 @@ export class ResellerController {
|
||||
//***************** DSC Endpoints **************** */
|
||||
@ApiOperation({ summary: "Create reseller ==> admin route" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.RESELLER)
|
||||
@PermissionsDec(PermissionEnum.RESELLER_CREATE)
|
||||
@Post()
|
||||
createReseller(@Body() dto: CreateResellerDto) {
|
||||
return this.resellerService.create(dto);
|
||||
@@ -42,7 +42,7 @@ export class ResellerController {
|
||||
|
||||
@ApiOperation({ summary: "Get resellers ==> admin route" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.RESELLER)
|
||||
@PermissionsDec(PermissionEnum.RESELLER_READ)
|
||||
@Get()
|
||||
getResellers() {
|
||||
return this.resellerService.findAll();
|
||||
@@ -52,7 +52,7 @@ export class ResellerController {
|
||||
|
||||
@ApiOperation({ summary: "Get withdraw requests ==> admin route" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.RESELLER)
|
||||
@PermissionsDec(PermissionEnum.RESELLER_READ)
|
||||
@Pagination()
|
||||
@Get("withdraw-request/admin")
|
||||
getRewardRequests(@Query() query: SearchWithdrawRequestQueryDto) {
|
||||
@@ -61,7 +61,7 @@ export class ResellerController {
|
||||
|
||||
@ApiOperation({ summary: "Confirm withdraw request ==> admin route" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.RESELLER)
|
||||
@PermissionsDec(PermissionEnum.RESELLER_UPDATE)
|
||||
@Patch("withdraw-request/:id")
|
||||
confirmWithdrawRequests(@Param() paramDto: ParamDto, @Body() dto: ConfirmwithdrawRequestDto) {
|
||||
return this.resellerService.confirmWithdrawRequest(paramDto.id, dto.transactionId);
|
||||
|
||||
@@ -18,7 +18,7 @@ export class SlidersController {
|
||||
|
||||
@ApiOperation({ summary: "create slider (admin route)" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.BLOGS)
|
||||
@PermissionsDec(PermissionEnum.SLIDERS_CREATE)
|
||||
@Post()
|
||||
create(@Body() createSliderDto: CreateSliderDto) {
|
||||
return this.slidersService.create(createSliderDto);
|
||||
@@ -26,7 +26,7 @@ export class SlidersController {
|
||||
|
||||
@ApiOperation({ summary: "Get all sliders (admin route)" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.BLOGS)
|
||||
@PermissionsDec(PermissionEnum.SLIDERS_READ)
|
||||
@Get()
|
||||
getSlidersList(@Query() queryDto: PaginationDto) {
|
||||
return this.slidersService.getSlidersList(queryDto);
|
||||
@@ -34,7 +34,7 @@ export class SlidersController {
|
||||
|
||||
@ApiOperation({ summary: "Get a slider by id (admin route)" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.BLOGS)
|
||||
@PermissionsDec(PermissionEnum.SLIDERS_READ)
|
||||
@Get(":id")
|
||||
getSliderById(@Param() paramDto: ParamDto) {
|
||||
return this.slidersService.getSliderById(paramDto.id);
|
||||
@@ -42,7 +42,7 @@ export class SlidersController {
|
||||
|
||||
@ApiOperation({ summary: "Update a slider (admin route)" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.BLOGS)
|
||||
@PermissionsDec(PermissionEnum.SLIDERS_UPDATE)
|
||||
@Patch(":id")
|
||||
update(@Param() paramDto: ParamDto, @Body() updateSliderDto: UpdateSliderDto) {
|
||||
return this.slidersService.update(paramDto.id, updateSliderDto);
|
||||
@@ -50,7 +50,7 @@ export class SlidersController {
|
||||
|
||||
@ApiOperation({ summary: "Delete a slider (admin route)" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.BLOGS)
|
||||
@PermissionsDec(PermissionEnum.SLIDERS_DELETE)
|
||||
@Delete(":id")
|
||||
remove(@Param() paramDto: ParamDto) {
|
||||
return this.slidersService.remove(paramDto.id);
|
||||
|
||||
@@ -31,7 +31,7 @@ export class SubscriptionsController {
|
||||
) {}
|
||||
|
||||
@ApiOperation({ summary: "Delete subscription =. admin route" })
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@PermissionsDec(PermissionEnum.SERVICES_DELETE)
|
||||
@AdminRoute()
|
||||
@Delete(':id')
|
||||
deleteSubscription(@Param('id') id :string) {
|
||||
@@ -39,14 +39,14 @@ export class SubscriptionsController {
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "Create a subscription plan" })
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@PermissionsDec(PermissionEnum.SERVICES_CREATE)
|
||||
@Post()
|
||||
createSubscription(@Body() createDto: AddSubscriptionsToServiceDto) {
|
||||
return this.subscriptionService.createSubscriptionsPlan(createDto);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "get all subscription plans" })
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@PermissionsDec(PermissionEnum.SERVICES_READ)
|
||||
@Get("service/:serviceId")
|
||||
getServiceSubscriptions(@Param() paramDto: ServiceIdParamDto, @Query() queryDto: ServiceSubsQueryDto) {
|
||||
return this.subscriptionService.getServiceSubscriptions(paramDto.serviceId, queryDto);
|
||||
@@ -65,7 +65,7 @@ export class SubscriptionsController {
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "toggle status of service subs" })
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@PermissionsDec(PermissionEnum.SERVICES_CREATE)
|
||||
@Post("toggle-status/:id")
|
||||
toggleStatusOfServiceSub(@Param() paramDto: ParamDto) {
|
||||
return this.subscriptionService.toggleSubStatus(paramDto.id);
|
||||
@@ -73,14 +73,14 @@ export class SubscriptionsController {
|
||||
|
||||
@ApiOperation({ summary: "get a subscription plan by id" })
|
||||
@Get(":id")
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@PermissionsDec(PermissionEnum.SERVICES_READ)
|
||||
getSubscription(@Param() paramDto: ParamDto) {
|
||||
return this.subscriptionService.getSubscriptionPlanById(paramDto.id);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "update a subscription plan by id" })
|
||||
@Patch(":id")
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@PermissionsDec(PermissionEnum.SERVICES_UPDATE)
|
||||
updateSubscription(@Param() paramDto: ParamDto, @Body() updateDto: UpdateSubscriptionPlanDto) {
|
||||
return this.subscriptionService.updateSubscriptionPlan(paramDto.id, updateDto);
|
||||
}
|
||||
@@ -144,7 +144,7 @@ export class SubscriptionsController {
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "get all user subscriptions with pagination and filters ==> admin route" })
|
||||
@PermissionsDec(PermissionEnum.PAYMENTS)
|
||||
@PermissionsDec(PermissionEnum.PAYMENTS_READ)
|
||||
@Get("admin/user-subscriptions/all")
|
||||
getAllUserSubscriptions(@Query() queryDto: AdminUserSubscriptionsQueryDto) {
|
||||
return this.subscriptionService.getAllUserSubscriptions(queryDto);
|
||||
|
||||
@@ -19,7 +19,7 @@ export class SupportPlansController {
|
||||
constructor(private readonly supportPlansService: SupportPlansService) {}
|
||||
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.SUPPORT_PLAN)
|
||||
@PermissionsDec(PermissionEnum.SUPPORT_PLAN_CREATE)
|
||||
@ApiOperation({ summary: "Create a new support plan (admin)" })
|
||||
@Post()
|
||||
createSupportPlan(@Body() createSupportPlanDto: CreateSupportPlanDto) {
|
||||
@@ -27,7 +27,7 @@ export class SupportPlansController {
|
||||
}
|
||||
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.SUPPORT_PLAN)
|
||||
@PermissionsDec(PermissionEnum.SUPPORT_PLAN_READ)
|
||||
@ApiOperation({ summary: "Get all support plans (admin)" })
|
||||
@Get("list")
|
||||
getSupportPlansList(@Query() queryDto: GetSupportPlanListQueryDto) {
|
||||
@@ -35,7 +35,7 @@ export class SupportPlansController {
|
||||
}
|
||||
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.SUPPORT_PLAN)
|
||||
@PermissionsDec(PermissionEnum.SUPPORT_PLAN_READ)
|
||||
@ApiOperation({ summary: "Get a support plan by id (admin)" })
|
||||
@Get(":id")
|
||||
getSupportPlanById(@Param() paramDto: ParamDto) {
|
||||
@@ -43,7 +43,7 @@ export class SupportPlansController {
|
||||
}
|
||||
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.SUPPORT_PLAN)
|
||||
@PermissionsDec(PermissionEnum.SUPPORT_PLAN_DELETE)
|
||||
@ApiOperation({ summary: "Delete a support plan by id (admin)" })
|
||||
@Delete(":id")
|
||||
deleteSupportPlanById(@Param() paramDto: ParamDto) {
|
||||
@@ -51,7 +51,7 @@ export class SupportPlansController {
|
||||
}
|
||||
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.SUPPORT_PLAN)
|
||||
@PermissionsDec(PermissionEnum.SUPPORT_PLAN_UPDATE)
|
||||
@ApiOperation({ summary: "Update a support plan by id (admin)" })
|
||||
@Patch(":id")
|
||||
updateSupportPlanById(@Param() paramDto: ParamDto, @Body() updateSupportPlanDto: UpdateSupportPlanDto) {
|
||||
@@ -71,7 +71,7 @@ export class SupportPlansController {
|
||||
}
|
||||
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.SUPPORT_PLAN)
|
||||
@PermissionsDec(PermissionEnum.SUPPORT_PLAN_READ)
|
||||
@ApiOperation({ summary: "Get support plan users (admin)" })
|
||||
@Get(":id/users")
|
||||
getSupportPlanUsers(@Param() paramDto: ParamDto, @Query() queryDto: SearchSupportPlanUsersQueryDto) {
|
||||
|
||||
@@ -1,33 +1,19 @@
|
||||
import { Controller, Get, Post, Patch, Delete, Param, Body, Query } from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery } from '@nestjs/swagger';
|
||||
import { Controller, Post, Patch, Delete, Param, Body } from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiParam } from '@nestjs/swagger';
|
||||
import { AttachmentService } from '../providers/attachment.service';
|
||||
import { CreateAttachmentDto } from '../dto/attachment/create-attachment.dto';
|
||||
import { UpdateAttachmentDto } from '../dto/attachment/update-attachment.dto';
|
||||
import { TMAttachment } from '../entities/attachment.entity';
|
||||
import { AuthGuards } from '../../../common/decorators/auth-guard.decorator';
|
||||
import { AdminRoute } from '../../../common/decorators/admin.decorator';
|
||||
|
||||
@AuthGuards()
|
||||
@AdminRoute()
|
||||
@ApiTags('Task Manager - Attachments')
|
||||
@Controller('task-manager/attachments')
|
||||
export class AttachmentController {
|
||||
constructor(private readonly attachmentService: AttachmentService) {}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: 'Get all attachments, optionally filtered by task' })
|
||||
@ApiQuery({ name: 'taskId', required: false, description: 'Filter attachments by task ID' })
|
||||
@ApiResponse({ status: 200, description: 'List of attachments', type: [TMAttachment] })
|
||||
findAll(@Query('taskId') taskId?: string): Promise<TMAttachment[]> {
|
||||
if (taskId) return this.attachmentService.findByTask(taskId);
|
||||
return this.attachmentService.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@ApiOperation({ summary: 'Get an attachment by ID' })
|
||||
@ApiParam({ name: 'id', description: 'Attachment ID' })
|
||||
@ApiResponse({ status: 200, description: 'Attachment found', type: TMAttachment })
|
||||
@ApiResponse({ status: 404, description: 'Attachment not found' })
|
||||
findOne(@Param('id') id: string): Promise<TMAttachment> {
|
||||
return this.attachmentService.findOne(id);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: 'Create a new attachment' })
|
||||
@ApiResponse({ status: 201, description: 'Attachment created', type: TMAttachment })
|
||||
|
||||
@@ -1,36 +1,20 @@
|
||||
import { Controller, Get, Post, Patch, Delete, Param, Body, Query } from "@nestjs/common";
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery } from "@nestjs/swagger";
|
||||
import { Controller, Post, Patch, Delete, Param, Body } from "@nestjs/common";
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiParam } from "@nestjs/swagger";
|
||||
import { CheckListItemService } from "../providers/check-list-item.service";
|
||||
import { CreateCheckListItemDto } from "../dto/check-list-item/create-check-list-item.dto";
|
||||
import { UpdateCheckListItemDto } from "../dto/check-list-item/update-check-list-item.dto";
|
||||
import { CursorPaginationDto } from "../../../common/DTO/cursor-pagination.dto";
|
||||
import { TMCheckListItem } from "../entities/check-list-item.entity";
|
||||
import { AuthGuards } from "../../../common/decorators/auth-guard.decorator";
|
||||
import { AdminRoute } from "../../../common/decorators/admin.decorator";
|
||||
import { ParamDto } from "../../../common/DTO/param.dto";
|
||||
|
||||
@AuthGuards()
|
||||
@AdminRoute()
|
||||
@ApiTags("Task Manager - Check List Items")
|
||||
@Controller("task-manager/check-list-items")
|
||||
export class CheckListItemController {
|
||||
constructor(private readonly checkListItemService: CheckListItemService) {}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: "Get all check list items, optionally filtered by task" })
|
||||
@ApiQuery({ name: "taskId", required: false, description: "Filter check list items by task ID" })
|
||||
@ApiQuery({ name: "cursor", required: false, description: "Cursor for pagination (createdAt ISO string)" })
|
||||
@ApiQuery({ name: "limit", required: false, description: "Number of records to return (max 50)" })
|
||||
@ApiResponse({ status: 200, description: "Cursor-paginated list of check list items" })
|
||||
findAll(@Query() pagination: CursorPaginationDto, @Query("taskId") taskId?: string) {
|
||||
if (taskId) return this.checkListItemService.findByTask(taskId, pagination);
|
||||
return this.checkListItemService.findAll(pagination);
|
||||
}
|
||||
|
||||
@Get(":id")
|
||||
@ApiOperation({ summary: "Get a check list item by ID" })
|
||||
@ApiParam({ name: "id", description: "CheckListItem ID" })
|
||||
@ApiResponse({ status: 200, description: "CheckListItem found", type: TMCheckListItem })
|
||||
@ApiResponse({ status: 404, description: "CheckListItem not found" })
|
||||
findOne(@Param("id") id: string): Promise<TMCheckListItem> {
|
||||
return this.checkListItemService.findOne(id);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: "Create a new check list item" })
|
||||
@ApiResponse({ status: 201, description: "CheckListItem created", type: TMCheckListItem })
|
||||
@@ -56,4 +40,13 @@ export class CheckListItemController {
|
||||
remove(@Param("id") id: string): Promise<TMCheckListItem> {
|
||||
return this.checkListItemService.remove(id);
|
||||
}
|
||||
|
||||
@Patch("change-status/:id")
|
||||
@ApiOperation({summary: "change status of a check list item."})
|
||||
@ApiParam({name: "id", description: "CheckListItem ID"})
|
||||
@ApiResponse({status: 200, description: "successfully changed the status!"})
|
||||
@ApiResponse({ status: 404, description: "CheckListItem not found" })
|
||||
changeStatus(@Param() paramDto: ParamDto) {
|
||||
return this.checkListItemService.toggleStatus(paramDto.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +1,37 @@
|
||||
import { Controller, Get, Post, Patch, Delete, Param, Body, Query, Headers } from "@nestjs/common";
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery } from "@nestjs/swagger";
|
||||
import { Controller, Get, Post, Patch, Delete, Param, Body, Query } from "@nestjs/common";
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiParam } from "@nestjs/swagger";
|
||||
import { ProjectService } from "../providers/project.service";
|
||||
import { CreateProjectDto } from "../dto/project/create-project.dto";
|
||||
import { UpdateProjectDto } from "../dto/project/update-project.dto";
|
||||
import { PaginationDto } from "../../../common/DTO/pagination.dto";
|
||||
import { TMProject } from "../entities/project.entity";
|
||||
import { AdminRoute } from "../../../common/decorators/admin.decorator";
|
||||
import { PermissionsDec } from "../../../common/decorators/permission.decorator";
|
||||
import { PermissionEnum } from "../../users/enums/permission.enum";
|
||||
import { UserDec } from "../../../common/decorators/user.decorator";
|
||||
import { AuthGuards } from "../../../common/decorators/auth-guard.decorator";
|
||||
import { ParamDto } from "../../../common/DTO/param.dto";
|
||||
import { ITokenPayload } from "../../auth/interfaces/IToken-payload";
|
||||
import { User } from "../../users/entities/user.entity";
|
||||
|
||||
@AuthGuards()
|
||||
@AdminRoute()
|
||||
@ApiTags("Task Manager - Projects")
|
||||
@Controller("task-manager/projects")
|
||||
export class ProjectController {
|
||||
constructor(private readonly projectService: ProjectService) {}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: "Get all projects, optionally filtered by workspace" })
|
||||
@ApiQuery({ name: "workspaceId", required: false, description: "Filter projects by workspace ID" })
|
||||
@ApiResponse({ status: 200, description: "Paginated list of projects" })
|
||||
findAll(
|
||||
@Query() pagination: PaginationDto,
|
||||
@Query("workspaceId") workspaceId: string,
|
||||
@Headers("host") host: string,
|
||||
@Headers("x-forwarded-proto") proto?: string,
|
||||
) {
|
||||
const protocol = proto ?? "http";
|
||||
const baseUrl = `${protocol}://${host}/task-manager/projects`;
|
||||
if (workspaceId) return this.projectService.findByWorkspace(workspaceId, pagination, baseUrl);
|
||||
return this.projectService.findAll(pagination, baseUrl);
|
||||
}
|
||||
|
||||
@Get(":id")
|
||||
@ApiOperation({ summary: "Get a project by ID" })
|
||||
@ApiParam({ name: "id", description: "Project ID" })
|
||||
@ApiResponse({ status: 200, description: "Project found", type: TMProject })
|
||||
@ApiResponse({ status: 404, description: "Project not found" })
|
||||
findOne(@Param("id") id: string): Promise<TMProject> {
|
||||
return this.projectService.findOne(id);
|
||||
@Get(':id')
|
||||
@ApiOperation({summary: 'Get one project by ID'})
|
||||
@ApiResponse({status: 200, description: 'received the project successfully!'})
|
||||
@ApiResponse({status: 404, description: 'Project Not Found!'})
|
||||
@ApiResponse({status: 400, description: 'Bad Request!'})
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.projectService.findOneOrFail(id);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@PermissionsDec(PermissionEnum.PROJECT_CREATE)
|
||||
@ApiOperation({ summary: "Create a new project" })
|
||||
@ApiResponse({ status: 201, description: "Project created", type: TMProject })
|
||||
@ApiResponse({ status: 400, description: "One or more users are not members of the workspace" })
|
||||
@@ -46,6 +41,7 @@ export class ProjectController {
|
||||
}
|
||||
|
||||
@Patch(":id")
|
||||
@PermissionsDec(PermissionEnum.PROJECT_UPDATE)
|
||||
@ApiOperation({ summary: "Update a project" })
|
||||
@ApiParam({ name: "id", description: "Project ID" })
|
||||
@ApiResponse({ status: 200, description: "Project updated", type: TMProject })
|
||||
@@ -56,6 +52,7 @@ export class ProjectController {
|
||||
}
|
||||
|
||||
@Delete(":id")
|
||||
@PermissionsDec(PermissionEnum.PROJECT_DELETE)
|
||||
@ApiOperation({ summary: "Delete a project" })
|
||||
@ApiParam({ name: "id", description: "Project ID" })
|
||||
@ApiResponse({ status: 200, description: "Project deleted", type: TMProject })
|
||||
@@ -63,4 +60,32 @@ export class ProjectController {
|
||||
remove(@Param("id") id: string): Promise<TMProject> {
|
||||
return this.projectService.remove(id);
|
||||
}
|
||||
|
||||
@Get("user/workspace/:id")
|
||||
@ApiOperation({ summary: "Get user projects in a specific workspace!" })
|
||||
@ApiResponse({ status: 200, description: "Got user projects successfully!" })
|
||||
@ApiResponse({ status: 400, description: "Bad Request!" })
|
||||
findUserProjectsByWorkspace(
|
||||
@UserDec() user: ITokenPayload,
|
||||
@Param() paramDto: ParamDto,
|
||||
@Query() pagination: PaginationDto,
|
||||
): Promise<{ projects: TMProject[]; count: number; paginate: true }> {
|
||||
return this.projectService.findUserProjectsByWorkspace(user.id, user.permissions, paramDto.id, pagination);
|
||||
}
|
||||
|
||||
@Get("detail/:id")
|
||||
@ApiOperation({ summary: "Get project detail" })
|
||||
@ApiResponse({ status: 200, description: "Got Project Detail Successfully!" })
|
||||
@ApiResponse({ status: 400, description: "Bad request!" })
|
||||
@ApiResponse({ status: 404, description: "The project was not found!" })
|
||||
getProjectDetail(@Param() paramDto: ParamDto, @UserDec() user: ITokenPayload): Promise<TMProject | null> {
|
||||
return this.projectService.getProjectDetail(paramDto.id, user.id, user.permissions);
|
||||
}
|
||||
|
||||
@Get(":id/users")
|
||||
@ApiOperation({ summary: "Get project users" })
|
||||
@ApiResponse({ status: 200, description: "Got Project Users Successfully!" })
|
||||
getProjectUsers(@Param() paramDto: ParamDto): Promise<User[]> {
|
||||
return this.projectService.getProjectUsers(paramDto.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,19 @@
|
||||
import { Controller, Get, Post, Patch, Delete, Param, Body, Query } from "@nestjs/common";
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery } from "@nestjs/swagger";
|
||||
import { Controller, Post, Patch, Delete, Param, Body } from "@nestjs/common";
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiParam } from "@nestjs/swagger";
|
||||
import { RemarkService } from "../providers/remark.service";
|
||||
import { CreateRemarkDto } from "../dto/remark/create-remark.dto";
|
||||
import { UpdateRemarkDto } from "../dto/remark/update-remark.dto";
|
||||
import { TMRemark } from "../entities/remark.entity";
|
||||
import { AuthGuards } from "../../../common/decorators/auth-guard.decorator";
|
||||
import { AdminRoute } from "../../../common/decorators/admin.decorator";
|
||||
|
||||
@AuthGuards()
|
||||
@AdminRoute()
|
||||
@ApiTags("Task Manager - Remarks")
|
||||
@Controller("task-manager/remarks")
|
||||
export class RemarkController {
|
||||
constructor(private readonly remarkService: RemarkService) {}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: "Get all remarks, optionally filtered by task" })
|
||||
@ApiQuery({ name: "taskId", required: false, description: "Filter remarks by task ID" })
|
||||
@ApiResponse({ status: 200, description: "List of remarks", type: [TMRemark] })
|
||||
findAll(@Query("taskId") taskId?: string): Promise<TMRemark[]> {
|
||||
if (taskId) return this.remarkService.findByTask(taskId);
|
||||
return this.remarkService.findAll();
|
||||
}
|
||||
|
||||
@Get(":id")
|
||||
@ApiOperation({ summary: "Get a remark by ID" })
|
||||
@ApiParam({ name: "id", description: "Remark ID" })
|
||||
@ApiResponse({ status: 200, description: "Remark found", type: TMRemark })
|
||||
@ApiResponse({ status: 404, description: "Remark not found" })
|
||||
findOne(@Param("id") id: string): Promise<TMRemark> {
|
||||
return this.remarkService.findOne(id);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: "Create a new remark" })
|
||||
@ApiResponse({ status: 201, description: "Remark created", type: TMRemark })
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Controller, Post, Patch, Delete, Param, Body, Get } from "@nestjs/common";
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiParam } from "@nestjs/swagger";
|
||||
import { TaskCommentService } from "../providers/task-comment.service";
|
||||
import { CreateTaskCommentDto } from "../dto/task-comment/create-task-comment.dto";
|
||||
import { UpdateTaskCommentDto } from "../dto/task-comment/update-task-comment.dto";
|
||||
import { TMTaskComment } from "../entities/task-comment.entity";
|
||||
import { AuthGuards } from "../../../common/decorators/auth-guard.decorator";
|
||||
import { AdminRoute } from "../../../common/decorators/admin.decorator";
|
||||
import { UserDec } from "../../../common/decorators/user.decorator";
|
||||
|
||||
@AuthGuards()
|
||||
@AdminRoute()
|
||||
@ApiTags("Task Manager - Comments")
|
||||
@Controller("task-manager/comments")
|
||||
export class TaskCommentController {
|
||||
constructor(private readonly taskCommentService: TaskCommentService) {}
|
||||
|
||||
@Get("task/:taskId")
|
||||
@ApiOperation({ summary: "Get all comments for a task" })
|
||||
@ApiParam({ name: "taskId", description: "Task ID" })
|
||||
getTaskComments(@Param("taskId") taskId: string): Promise<TMTaskComment[]> {
|
||||
return this.taskCommentService.getTaskComments(taskId);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: "Create a new comment" })
|
||||
@ApiResponse({ status: 201, description: "Comment created", type: TMTaskComment })
|
||||
@ApiResponse({ status: 404, description: "Task not found" })
|
||||
create(@UserDec("id") userId: string, @Body() body: CreateTaskCommentDto): Promise<TMTaskComment> {
|
||||
return this.taskCommentService.create(userId, body);
|
||||
}
|
||||
|
||||
@Patch(":id")
|
||||
@ApiOperation({ summary: "Update a comment" })
|
||||
@ApiParam({ name: "id", description: "Comment ID" })
|
||||
@ApiResponse({ status: 200, description: "Comment updated", type: TMTaskComment })
|
||||
@ApiResponse({ status: 404, description: "Comment or Task not found" })
|
||||
update(@Param("id") id: string, @Body() body: UpdateTaskCommentDto): Promise<TMTaskComment> {
|
||||
return this.taskCommentService.update(id, body);
|
||||
}
|
||||
|
||||
@Delete(":id")
|
||||
@ApiOperation({ summary: "Delete a comment" })
|
||||
@ApiParam({ name: "id", description: "Comment ID" })
|
||||
@ApiResponse({ status: 200, description: "Comment deleted" })
|
||||
@ApiResponse({ status: 404, description: "Comment not found" })
|
||||
remove(@Param("id") id: string): Promise<void> {
|
||||
return this.taskCommentService.remove(id);
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,23 @@
|
||||
import { Controller, Get, Post, Patch, Delete, Param, Body, Query } from "@nestjs/common";
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery } from "@nestjs/swagger";
|
||||
import { Controller, Post, Patch, Delete, Param, Body } from "@nestjs/common";
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiParam } from "@nestjs/swagger";
|
||||
import { TaskPhaseService } from "../providers/task-phase.service";
|
||||
import { CreateTaskPhaseDto } from "../dto/task-phase/create-task-phase.dto";
|
||||
import { UpdateTaskPhaseDto } from "../dto/task-phase/update-task-phase.dto";
|
||||
import { TMTaskPhase } from "../entities/task-phase.entity";
|
||||
import { AuthGuards } from "../../../common/decorators/auth-guard.decorator";
|
||||
import { AdminRoute } from "../../../common/decorators/admin.decorator";
|
||||
import { PermissionsDec } from "../../../common/decorators/permission.decorator";
|
||||
import { PermissionEnum } from "../../users/enums/permission.enum";
|
||||
|
||||
@AuthGuards()
|
||||
@AdminRoute()
|
||||
@ApiTags("Task Manager - Task Phases")
|
||||
@Controller("task-manager/task-phases")
|
||||
export class TaskPhaseController {
|
||||
constructor(private readonly taskPhaseService: TaskPhaseService) {}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: "Get all task phases, optionally filtered by project" })
|
||||
@ApiQuery({ name: "projectId", required: false, description: "Filter task phases by project ID" })
|
||||
@ApiResponse({ status: 200, description: "List of task phases", type: [TMTaskPhase] })
|
||||
findAll(@Query("projectId") projectId?: string): Promise<TMTaskPhase[]> {
|
||||
if (projectId) return this.taskPhaseService.findByProject(projectId);
|
||||
return this.taskPhaseService.findAll();
|
||||
}
|
||||
|
||||
@Get(":id")
|
||||
@ApiOperation({ summary: "Get a task phase by ID" })
|
||||
@ApiParam({ name: "id", description: "TaskPhase ID" })
|
||||
@ApiResponse({ status: 200, description: "TaskPhase found", type: TMTaskPhase })
|
||||
@ApiResponse({ status: 404, description: "TaskPhase not found" })
|
||||
findOne(@Param("id") id: string): Promise<TMTaskPhase> {
|
||||
return this.taskPhaseService.findOne(id);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@PermissionsDec(PermissionEnum.TASK_PHASE_CREATE)
|
||||
@ApiOperation({ summary: "Create a new task phase" })
|
||||
@ApiResponse({ status: 201, description: "TaskPhase created", type: TMTaskPhase })
|
||||
@ApiResponse({ status: 404, description: "Project not found" })
|
||||
@@ -37,6 +26,7 @@ export class TaskPhaseController {
|
||||
}
|
||||
|
||||
@Patch(":id")
|
||||
@PermissionsDec(PermissionEnum.TASK_PHASE_UPDATE)
|
||||
@ApiOperation({ summary: "Update a task phase" })
|
||||
@ApiParam({ name: "id", description: "TaskPhase ID" })
|
||||
@ApiResponse({ status: 200, description: "TaskPhase updated", type: TMTaskPhase })
|
||||
@@ -46,6 +36,7 @@ export class TaskPhaseController {
|
||||
}
|
||||
|
||||
@Delete(":id")
|
||||
@PermissionsDec(PermissionEnum.TASK_PHASE_DELETE)
|
||||
@ApiOperation({ summary: "Delete a task phase" })
|
||||
@ApiParam({ name: "id", description: "TaskPhase ID" })
|
||||
@ApiResponse({ status: 200, description: "TaskPhase deleted" })
|
||||
|
||||
@@ -1,67 +1,103 @@
|
||||
import { Controller, Get, Post, Patch, Delete, Param, Body, Query } from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery } from '@nestjs/swagger';
|
||||
import { TaskService } from '../providers/task.service';
|
||||
import { CreateTaskDto } from '../dto/task/create-task.dto';
|
||||
import { UpdateTaskDto } from '../dto/task/update-task.dto';
|
||||
import { ChangeTaskPhaseDto } from '../dto/task/change-task-phase.dto';
|
||||
import { TMTask } from '../entities/task.entity';
|
||||
import { Controller, Get, Post, Patch, Delete, Param, Body, Query } from "@nestjs/common";
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiParam } from "@nestjs/swagger";
|
||||
import { TaskService } from "../providers/task.service";
|
||||
import { CreateTaskDto } from "../dto/task/create-task.dto";
|
||||
import { UpdateTaskDto } from "../dto/task/update-task.dto";
|
||||
import { ChangeTaskPhaseDto } from "../dto/task/change-task-phase.dto";
|
||||
import { TMTask } from "../entities/task.entity";
|
||||
import { AuthGuards } from "../../../common/decorators/auth-guard.decorator";
|
||||
import { AdminRoute } from "../../../common/decorators/admin.decorator";
|
||||
import { UserDec } from "../../../common/decorators/user.decorator";
|
||||
import { ParamDto } from "../../../common/DTO/param.dto";
|
||||
import { PermissionsDec } from "../../../common/decorators/permission.decorator";
|
||||
import { PermissionEnum } from "../../users/enums/permission.enum";
|
||||
import { ITokenPayload } from "../../auth/interfaces/IToken-payload";
|
||||
import { CursorPagination } from "../../../common/decorators/cursor-pagination.decorator";
|
||||
import { CursorPaginationDto } from "../../../common/DTO/cursor-pagination.dto";
|
||||
|
||||
@ApiTags('Task Manager - Tasks')
|
||||
@Controller('task-manager/tasks')
|
||||
@AuthGuards()
|
||||
@AdminRoute()
|
||||
@ApiTags("Task Manager - Tasks")
|
||||
@Controller("task-manager/tasks")
|
||||
export class TaskController {
|
||||
constructor(private readonly taskService: TaskService) {}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: 'Get all tasks, optionally filtered by phase' })
|
||||
@ApiQuery({ name: 'taskPhaseId', required: false, description: 'Filter tasks by task phase ID' })
|
||||
@ApiResponse({ status: 200, description: 'List of tasks', type: [TMTask] })
|
||||
findAll(@Query('taskPhaseId') taskPhaseId?: string): Promise<TMTask[]> {
|
||||
if (taskPhaseId) return this.taskService.findByPhase(taskPhaseId);
|
||||
return this.taskService.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@ApiOperation({ summary: 'Get a task by ID' })
|
||||
@ApiParam({ name: 'id', description: 'Task ID' })
|
||||
@ApiResponse({ status: 200, description: 'Task found', type: TMTask })
|
||||
@ApiResponse({ status: 404, description: 'Task not found' })
|
||||
findOne(@Param('id') id: string): Promise<TMTask> {
|
||||
return this.taskService.findOne(id);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: 'Create a new task' })
|
||||
@ApiResponse({ status: 201, description: 'Task created', type: TMTask })
|
||||
@ApiResponse({ status: 404, description: 'TaskPhase not found' })
|
||||
@PermissionsDec(PermissionEnum.TASK_CREATE)
|
||||
@ApiOperation({ summary: "Create a new task" })
|
||||
@ApiResponse({ status: 201, description: "Task created", type: TMTask })
|
||||
@ApiResponse({ status: 404, description: "TaskPhase not found" })
|
||||
create(@Body() body: CreateTaskDto): Promise<TMTask> {
|
||||
return this.taskService.create(body);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@ApiOperation({ summary: 'Update a task' })
|
||||
@ApiParam({ name: 'id', description: 'Task ID' })
|
||||
@ApiResponse({ status: 200, description: 'Task updated', type: TMTask })
|
||||
@ApiResponse({ status: 404, description: 'Task or TaskPhase not found' })
|
||||
update(@Param('id') id: string, @Body() body: UpdateTaskDto): Promise<TMTask> {
|
||||
@Patch(":id")
|
||||
@PermissionsDec(PermissionEnum.TASK_UPDATE)
|
||||
@ApiOperation({ summary: "Update a task" })
|
||||
@ApiParam({ name: "id", description: "Task ID" })
|
||||
@ApiResponse({ status: 200, description: "Task updated", type: TMTask })
|
||||
@ApiResponse({ status: 404, description: "Task or TaskPhase not found" })
|
||||
update(@Param("id") id: string, @Body() body: UpdateTaskDto): Promise<TMTask> {
|
||||
return this.taskService.update(id, body);
|
||||
}
|
||||
|
||||
@Patch(':id/change-phase')
|
||||
@ApiOperation({ summary: 'Move a task to a different phase within the same project' })
|
||||
@ApiParam({ name: 'id', description: 'Task ID' })
|
||||
@ApiResponse({ status: 200, description: 'Task phase changed', type: TMTask })
|
||||
@ApiResponse({ status: 400, description: 'Target phase belongs to a different project' })
|
||||
@ApiResponse({ status: 404, description: 'Task or TaskPhase not found' })
|
||||
changePhase(@Param('id') id: string, @Body() body: ChangeTaskPhaseDto): Promise<TMTask> {
|
||||
return this.taskService.changePhase(id, body);
|
||||
@Patch(":id/change-phase")
|
||||
@ApiOperation({ summary: "Move a task to a different phase within the same project" })
|
||||
@ApiParam({ name: "id", description: "Task ID" })
|
||||
@ApiResponse({ status: 200, description: "Task phase changed", type: TMTask })
|
||||
@ApiResponse({ status: 400, description: "Target phase belongs to a different project" })
|
||||
@ApiResponse({ status: 404, description: "Task or TaskPhase not found" })
|
||||
changePhase(@Param("id") id: string,@UserDec() user: ITokenPayload, @Body() body: ChangeTaskPhaseDto): Promise<TMTask> {
|
||||
return this.taskService.changePhase(id, user.permissions, user.id, body);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@ApiOperation({ summary: 'Delete a task' })
|
||||
@ApiParam({ name: 'id', description: 'Task ID' })
|
||||
@ApiResponse({ status: 200, description: 'Task deleted' })
|
||||
@ApiResponse({ status: 404, description: 'Task not found' })
|
||||
remove(@Param('id') id: string): Promise<void> {
|
||||
@Delete(":id")
|
||||
@PermissionsDec(PermissionEnum.TASK_DELETE)
|
||||
@ApiOperation({ summary: "Delete a task" })
|
||||
@ApiParam({ name: "id", description: "Task ID" })
|
||||
@ApiResponse({ status: 200, description: "Task deleted" })
|
||||
@ApiResponse({ status: 404, description: "Task not found" })
|
||||
remove(@Param("id") id: string): Promise<void> {
|
||||
return this.taskService.remove(id);
|
||||
}
|
||||
|
||||
@Get("detail/:id")
|
||||
@ApiOperation({ summary: "Get Task Detail" })
|
||||
@ApiParam({ name: "id", description: "Task ID" })
|
||||
@ApiResponse({ status: 200, description: "Task detail received!" })
|
||||
@ApiResponse({ status: 404, description: "Task not found" })
|
||||
getTaskDetail(@UserDec() user: ITokenPayload, @Param() paramDto: ParamDto): Promise<TMTask | null> {
|
||||
return this.taskService.getTaskDetail(user.id, user.permissions, paramDto.id);
|
||||
}
|
||||
|
||||
@Get("task-phase/:id")
|
||||
@ApiOperation({ summary: "Get Tasks by task phase" })
|
||||
@ApiParam({ name: "id", description: "Task phase id" })
|
||||
@ApiResponse({ status: 200, description: "Got Tasks by task phase successfully!" })
|
||||
@ApiResponse({ status: 400, description: "Bad request from user." })
|
||||
@CursorPagination()
|
||||
findByTaskPhase(
|
||||
@Param("id") taskPhaseId: string,
|
||||
@Query() pagination: CursorPaginationDto,
|
||||
): Promise<{
|
||||
data: TMTask[];
|
||||
nextCursor: string | null;
|
||||
previousCursor: string | null;
|
||||
hasNextPage: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
cursorPaginate: true;
|
||||
}> {
|
||||
return this.taskService.findTasksByTaskPhase(taskPhaseId, pagination);
|
||||
}
|
||||
|
||||
@Patch("change-priority/:srcid/:desid")
|
||||
@ApiOperation({ summary: "changing the task priority" })
|
||||
@ApiParam({ name: "srcid", description: "ID of the source task" })
|
||||
@ApiParam({ name: "desid", description: "ID of the destination task" })
|
||||
@ApiResponse({ status: 200, description: "Changed the task priority successfully!" })
|
||||
@ApiResponse({ status: 400, description: "Bad Request from user." })
|
||||
@ApiResponse({ status: 404, description: "Task not found!" })
|
||||
changePriority(@Param("srcid") srcId: string, @Param("desid") desId: string,@UserDec() user: ITokenPayload): Promise<TMTask> {
|
||||
return this.taskService.changePriority(srcId, user.permissions, user.id, desId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { Controller, Post, Patch, Delete, Param, Body, Get } from "@nestjs/common";
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiParam } from "@nestjs/swagger";
|
||||
import { TimeService } from "../providers/time.service";
|
||||
import { CreateTimeDto } from "../dto/time/create-time.dto";
|
||||
import { UpdateTimeDto } from "../dto/time/update-time.dto";
|
||||
import { TMTime } from "../entities/time.entity";
|
||||
import { AuthGuards } from "../../../common/decorators/auth-guard.decorator";
|
||||
import { AdminRoute } from "../../../common/decorators/admin.decorator";
|
||||
import { UserDec } from "../../../common/decorators/user.decorator";
|
||||
|
||||
@AuthGuards()
|
||||
@AdminRoute()
|
||||
@ApiTags("Task Manager - Times")
|
||||
@Controller("task-manager/times")
|
||||
export class TimeController {
|
||||
constructor(private readonly timeService: TimeService) { }
|
||||
@Get("task/:taskId")
|
||||
@ApiOperation({ summary: "Get all time entries for a task" })
|
||||
@ApiParam({ name: "taskId", description: "Task ID" })
|
||||
getTaskTimes(@UserDec("id") adminId: string, @Param("taskId") taskId: string): Promise<TMTime[]> {
|
||||
return this.timeService.getTaskTimes(adminId, taskId);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: "Create a new time entry for a task" })
|
||||
@ApiResponse({ status: 201, description: "Time entry created", type: TMTime })
|
||||
@ApiResponse({ status: 404, description: "Task not found" })
|
||||
create(@UserDec("id") adminId: string, @Body() body: CreateTimeDto): Promise<TMTime> {
|
||||
return this.timeService.create(adminId, body);
|
||||
}
|
||||
|
||||
@Post("start/:taskId")
|
||||
@ApiOperation({ summary: "Start tracking time for a task" })
|
||||
@ApiParam({ name: "taskId", description: "Task ID" })
|
||||
@ApiResponse({ status: 201, description: "Time tracking started", type: TMTime })
|
||||
@ApiResponse({ status: 400, description: "A running time entry already exists" })
|
||||
@ApiResponse({ status: 404, description: "Task not found" })
|
||||
start(@UserDec("id") adminId: string, @Param("taskId") taskId: string): Promise<TMTime> {
|
||||
return this.timeService.start(adminId, taskId);
|
||||
}
|
||||
|
||||
@Post("end/:taskId")
|
||||
@ApiOperation({ summary: "Stop the current running time entry for a task" })
|
||||
@ApiParam({ name: "taskId", description: "Task ID" })
|
||||
@ApiResponse({ status: 200, description: "Time tracking stopped", type: TMTime })
|
||||
@ApiResponse({ status: 400, description: "No running time entry found" })
|
||||
@ApiResponse({ status: 404, description: "Task not found" })
|
||||
end(@UserDec("id") adminId: string, @Param("taskId") taskId: string): Promise<TMTime> {
|
||||
return this.timeService.end(adminId, taskId);
|
||||
}
|
||||
|
||||
@Patch(":id")
|
||||
@ApiOperation({ summary: "Update a time entry" })
|
||||
@ApiParam({ name: "id", description: "Time entry ID" })
|
||||
@ApiResponse({ status: 200, description: "Time entry updated", type: TMTime })
|
||||
@ApiResponse({ status: 404, description: "Time entry or Task not found" })
|
||||
update(@Param("id") id: string, @Body() body: UpdateTimeDto): Promise<TMTime> {
|
||||
return this.timeService.update(id, body);
|
||||
}
|
||||
|
||||
@Delete(":id")
|
||||
@ApiOperation({ summary: "Delete a time entry" })
|
||||
@ApiParam({ name: "id", description: "Time entry ID" })
|
||||
@ApiResponse({ status: 200, description: "Time entry deleted" })
|
||||
@ApiResponse({ status: 404, description: "Time entry not found" })
|
||||
remove(@Param("id") id: string): Promise<void> {
|
||||
return this.timeService.remove(id);
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,14 @@ import { WorkspaceService } from '../providers/workspace.service';
|
||||
import { CreateWorkspaceDto } from '../dto/workspace/create-workspace.dto';
|
||||
import { UpdateWorkspaceDto } from '../dto/workspace/update-workspace.dto';
|
||||
import { TMWorkspace } from '../entities/workspace.entity';
|
||||
// import { AuthGuards } from '../../../common/decorators/auth-guard.decorator';
|
||||
import { AdminRoute } from '../../../common/decorators/admin.decorator';
|
||||
import { PermissionsDec } from '../../../common/decorators/permission.decorator';
|
||||
import { PermissionEnum } from '../../users/enums/permission.enum';
|
||||
import { UserDec } from '../../../common/decorators/user.decorator';
|
||||
import { AuthGuards } from '../../../common/decorators/auth-guard.decorator';
|
||||
import { ITokenPayload } from '../../auth/interfaces/IToken-payload';
|
||||
import { ParamDto } from '../../../common/DTO/param.dto';
|
||||
import { User } from '../../users/entities/user.entity';
|
||||
|
||||
@AuthGuards()
|
||||
@AdminRoute()
|
||||
@@ -18,26 +20,26 @@ import { AuthGuards } from '../../../common/decorators/auth-guard.decorator';
|
||||
export class WorkspaceController {
|
||||
constructor(private readonly workspaceService: WorkspaceService) {}
|
||||
|
||||
@Get()
|
||||
@PermissionsDec(PermissionEnum.WORKSPACE)
|
||||
@ApiOperation({ summary: 'Get all workspaces' })
|
||||
@ApiResponse({ status: 200, description: 'List of workspaces', type: [TMWorkspace] })
|
||||
findAll(): Promise<TMWorkspace[]> {
|
||||
return this.workspaceService.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@PermissionsDec(PermissionEnum.WORKSPACE)
|
||||
@ApiOperation({ summary: 'Get a workspace by ID' })
|
||||
@ApiParam({ name: 'id', description: 'Workspace ID' })
|
||||
@ApiResponse({ status: 200, description: 'Workspace found', type: TMWorkspace })
|
||||
@ApiResponse({ status: 404, description: 'Workspace not found' })
|
||||
@PermissionsDec(PermissionEnum.WORKSPACE_READ)
|
||||
@ApiOperation({summary: "Get one workspace by ID"})
|
||||
@ApiResponse({status: 200, description: 'Received workspace', type: TMWorkspace})
|
||||
@ApiResponse({status: 404, description: 'Workspace Not Found!'})
|
||||
@ApiResponse({status: 400, description: 'Bad Request!'})
|
||||
findOne(@Param('id') id: string): Promise<TMWorkspace> {
|
||||
return this.workspaceService.findOneOrFail(id);
|
||||
}
|
||||
|
||||
@Get(':id/users')
|
||||
@ApiOperation({ summary: 'Get workspace users' })
|
||||
@ApiResponse({ status: 200, description: 'Got Workspace Users Successfully!' })
|
||||
@ApiResponse({ status: 404, description: 'Workspace Not Found!' })
|
||||
getWorkspaceUsers(@Param() paramDto: ParamDto): Promise<User[]> {
|
||||
return this.workspaceService.getWorkspaceUsers(paramDto.id);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@PermissionsDec(PermissionEnum.WORKSPACE)
|
||||
@PermissionsDec(PermissionEnum.WORKSPACE_CREATE)
|
||||
@ApiOperation({ summary: 'Create a new workspace' })
|
||||
@ApiResponse({ status: 201, description: 'Workspace created', type: TMWorkspace })
|
||||
create(@Body() body: CreateWorkspaceDto): Promise<TMWorkspace> {
|
||||
@@ -45,7 +47,7 @@ export class WorkspaceController {
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@PermissionsDec(PermissionEnum.WORKSPACE)
|
||||
@PermissionsDec(PermissionEnum.WORKSPACE_UPDATE)
|
||||
@ApiOperation({ summary: 'Update a workspace' })
|
||||
@ApiParam({ name: 'id', description: 'Workspace ID' })
|
||||
@ApiResponse({ status: 200, description: 'Workspace updated', type: TMWorkspace })
|
||||
@@ -55,7 +57,7 @@ export class WorkspaceController {
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@PermissionsDec(PermissionEnum.WORKSPACE)
|
||||
@PermissionsDec(PermissionEnum.WORKSPACE_DELETE)
|
||||
@ApiOperation({ summary: 'Delete a workspace' })
|
||||
@ApiParam({ name: 'id', description: 'Workspace ID' })
|
||||
@ApiResponse({ status: 200, description: 'Workspace deleted' })
|
||||
@@ -64,11 +66,11 @@ export class WorkspaceController {
|
||||
return this.workspaceService.remove(id);
|
||||
}
|
||||
|
||||
@Get('/byuser')
|
||||
@Get('byuser')
|
||||
@ApiOperation({summary: 'Get User WorkSpaces'})
|
||||
@ApiResponse({ status: 200, description: 'Got Workspaces successfully!' })
|
||||
@ApiResponse({ status: 404, description: 'Bad Request!' })
|
||||
findByUser(@UserDec('id') userId: string) {
|
||||
return this.workspaceService.findByUser(userId);
|
||||
findByUser(@UserDec() user: ITokenPayload) {
|
||||
return this.workspaceService.findByUser(user.id, user.permissions);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsString, IsNotEmpty, IsUUID, IsOptional, IsArray } from "class-validator";
|
||||
|
||||
export class CreateTaskCommentDto {
|
||||
@ApiProperty({ description: "Comment content", example: "Please review this task before closing." })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
content: string;
|
||||
|
||||
@ApiProperty({ description: "ID of the task this comment belongs to", example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" })
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
taskId: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: "Attachment file URLs or paths",
|
||||
type: [String],
|
||||
required: false,
|
||||
example: ["https://cdn.example.com/files/screenshot.png"],
|
||||
})
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
attachments?: string[];
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from "@nestjs/swagger";
|
||||
import { CreateTaskCommentDto } from "./create-task-comment.dto";
|
||||
|
||||
export class UpdateTaskCommentDto extends PartialType(CreateTaskCommentDto) {}
|
||||
@@ -34,6 +34,15 @@ export class CreateTaskDto {
|
||||
@IsNotEmpty()
|
||||
taskPhaseId: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: "ID of the related ticket",
|
||||
required: false,
|
||||
example: "c3d4e5f6-a7b8-9012-cdef-123456789012",
|
||||
})
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
ticketId?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: "IDs of users to assign to the task",
|
||||
required: false,
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsDateString, IsNotEmpty, IsUUID } from "class-validator";
|
||||
|
||||
export class CreateTimeDto {
|
||||
@ApiProperty({ description: "Start time of the entry", example: "2026-07-15T08:00:00.000Z" })
|
||||
@IsDateString()
|
||||
@IsNotEmpty()
|
||||
startedAt: string;
|
||||
|
||||
@ApiProperty({ description: "End time of the entry", example: "2026-07-15T10:30:00.000Z" })
|
||||
@IsDateString()
|
||||
@IsNotEmpty()
|
||||
endedAt: string;
|
||||
|
||||
@ApiProperty({ description: "ID of the task this time entry belongs to", example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" })
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
taskId: string;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from "@nestjs/swagger";
|
||||
import { CreateTimeDto } from "./create-time.dto";
|
||||
|
||||
export class UpdateTimeDto extends PartialType(CreateTimeDto) {}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Column, Entity, JoinColumn, ManyToOne } from "typeorm";
|
||||
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||
import { User } from "../../users/entities/user.entity";
|
||||
import { TMTask } from "./task.entity";
|
||||
|
||||
@Entity("tm_task_comments")
|
||||
export class TMTaskComment extends BaseEntity {
|
||||
@Column({ type: "text" })
|
||||
content: string;
|
||||
|
||||
@Column({ type: "json", default: [] })
|
||||
attachments: string[];
|
||||
|
||||
// --- Relations ---
|
||||
|
||||
@ManyToOne(() => User, {
|
||||
nullable: false,
|
||||
onDelete: "RESTRICT",
|
||||
})
|
||||
@JoinColumn({ name: "userId" })
|
||||
user: User;
|
||||
|
||||
@Column({ type: "uuid" })
|
||||
userId: string;
|
||||
|
||||
@ManyToOne(() => TMTask, (task) => task.comments, {
|
||||
nullable: false,
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
@JoinColumn({ name: "taskId" })
|
||||
task: TMTask;
|
||||
|
||||
@Column({ type: "uuid" })
|
||||
taskId: string;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,10 @@ import { TMTaskPhase } from "./task-phase.entity";
|
||||
import { TMRemark } from "./remark.entity";
|
||||
import { TMAttachment } from "./attachment.entity";
|
||||
import { TMCheckListItem } from "./check-list-item.entity";
|
||||
import { TMTime } from "./time.entity";
|
||||
import { TMTaskComment } from "./task-comment.entity";
|
||||
import { User } from "../../users/entities/user.entity";
|
||||
import { Ticket } from "../../tickets/entities/ticket.entity";
|
||||
|
||||
@Entity("tm_tasks")
|
||||
export class TMTask extends BaseEntity {
|
||||
@@ -23,6 +26,16 @@ export class TMTask extends BaseEntity {
|
||||
@Column({ type: "timestamptz", nullable: true })
|
||||
endDate: Date;
|
||||
|
||||
@Column({type: 'int', default: 1})
|
||||
priority: number;
|
||||
|
||||
@ManyToOne(() => Ticket, { nullable: true, onDelete: "SET NULL" })
|
||||
@JoinColumn({ name: "ticketId" })
|
||||
ticket: Ticket | null;
|
||||
|
||||
@Column({ type: "uuid", nullable: true })
|
||||
ticketId: string | null;
|
||||
|
||||
// --- Relations ---
|
||||
|
||||
@ManyToOne(() => TMTaskPhase, (taskPhase) => taskPhase.tasks, {
|
||||
@@ -50,6 +63,16 @@ export class TMTask extends BaseEntity {
|
||||
})
|
||||
checkListItems: TMCheckListItem[];
|
||||
|
||||
@OneToMany(() => TMTime, (time) => time.task, {
|
||||
cascade: ["insert", "update"],
|
||||
})
|
||||
times: TMTime[];
|
||||
|
||||
@OneToMany(() => TMTaskComment, (comment) => comment.task, {
|
||||
cascade: ["insert", "update"],
|
||||
})
|
||||
comments: TMTaskComment[];
|
||||
|
||||
@ManyToMany(() => User, (user) => user.tasks)
|
||||
@JoinTable({
|
||||
name: "tm_task_users",
|
||||
@@ -57,4 +80,10 @@ export class TMTask extends BaseEntity {
|
||||
inverseJoinColumn: { name: "userId", referencedColumnName: "id" },
|
||||
})
|
||||
users: User[];
|
||||
|
||||
// deriven(calculated) attributes. not database columns
|
||||
attachmentCount?: number;
|
||||
checkListItemCount?: number;
|
||||
completedCheckListItemCount?: number;
|
||||
time?: number;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Column, Entity, JoinColumn, ManyToOne } from "typeorm";
|
||||
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||
import { TMTask } from "./task.entity";
|
||||
import { User } from "../../users/entities/user.entity";
|
||||
|
||||
@Entity("tm_times")
|
||||
export class TMTime extends BaseEntity {
|
||||
@Column({ type: "timestamptz" })
|
||||
startedAt: Date;
|
||||
|
||||
@Column({ type: "timestamptz", nullable: true })
|
||||
endedAt: Date | null;
|
||||
|
||||
@Column({ type: "int", nullable: true })
|
||||
duration: number | null;
|
||||
|
||||
// --- Relations ---
|
||||
|
||||
@ManyToOne(() => User, {
|
||||
nullable: false,
|
||||
onDelete: "RESTRICT",
|
||||
})
|
||||
@JoinColumn({ name: "adminId" })
|
||||
admin: User;
|
||||
|
||||
@Column({ type: "uuid" })
|
||||
adminId: string;
|
||||
|
||||
@ManyToOne(() => TMTask, (task) => task.times, {
|
||||
nullable: false,
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
@JoinColumn({ name: "taskId" })
|
||||
task: TMTask;
|
||||
|
||||
@Column({ type: "uuid" })
|
||||
taskId: string;
|
||||
}
|
||||
@@ -1,45 +1,36 @@
|
||||
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { AttachmentRepository } from "../repositories/attachment.repository";
|
||||
import { TaskRepository } from "../repositories/task.repository";
|
||||
import { TMAttachment } from "../entities/attachment.entity";
|
||||
import { CreateAttachmentDto } from "../dto/attachment/create-attachment.dto";
|
||||
import { UpdateAttachmentDto } from "../dto/attachment/update-attachment.dto";
|
||||
import { attachmentMessage } from "../../../common/enums/message.enum";
|
||||
import { TaskService } from "./task.service";
|
||||
|
||||
@Injectable()
|
||||
export class AttachmentService {
|
||||
constructor(
|
||||
private readonly attachmentRepository: AttachmentRepository,
|
||||
private readonly taskRepository: TaskRepository,
|
||||
private readonly taskService: TaskService,
|
||||
) {}
|
||||
|
||||
findAll(): Promise<TMAttachment[]> {
|
||||
return this.attachmentRepository.findAll();
|
||||
}
|
||||
|
||||
findByTask(taskId: string): Promise<TMAttachment[]> {
|
||||
return this.attachmentRepository.findByTask(taskId);
|
||||
}
|
||||
|
||||
async findOne(id: string): Promise<TMAttachment> {
|
||||
const attachment = await this.attachmentRepository.findOneById(id);
|
||||
if (!attachment) throw new NotFoundException(`Attachment #${id} not found`);
|
||||
async findOneOrFail(id: string): Promise<TMAttachment> {
|
||||
const attachment = await this.attachmentRepository.findOne({ where: { id }, relations: { task: true } });
|
||||
if (!attachment) throw new NotFoundException(attachmentMessage.ATTACHMENT_NOT_FOUND);
|
||||
return attachment;
|
||||
}
|
||||
|
||||
async create(dto: CreateAttachmentDto): Promise<TMAttachment> {
|
||||
const task = await this.taskRepository.findOneById(dto.taskId);
|
||||
if (!task) throw new NotFoundException(`Task #${dto.taskId} not found`);
|
||||
await this.taskService.findOneOrFail(dto.taskId);
|
||||
|
||||
const attachment = this.attachmentRepository.create(dto);
|
||||
return this.attachmentRepository.save(attachment);
|
||||
}
|
||||
|
||||
async update(id: string, dto: UpdateAttachmentDto): Promise<TMAttachment> {
|
||||
const attachment = await this.findOne(id);
|
||||
const attachment = await this.findOneOrFail(id);
|
||||
|
||||
if (dto.taskId) {
|
||||
const task = await this.taskRepository.findOneById(dto.taskId);
|
||||
if (!task) throw new NotFoundException(`Task #${dto.taskId} not found`);
|
||||
await this.taskService.findOneOrFail(dto.taskId);
|
||||
}
|
||||
|
||||
Object.assign(attachment, dto);
|
||||
@@ -47,7 +38,7 @@ export class AttachmentService {
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<void> {
|
||||
await this.findOne(id);
|
||||
await this.findOneOrFail(id);
|
||||
await this.attachmentRepository.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,61 +1,36 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { CheckListItemRepository } from '../repositories/check-list-item.repository';
|
||||
import { TaskRepository } from '../repositories/task.repository';
|
||||
import { TMCheckListItem } from '../entities/check-list-item.entity';
|
||||
import { CreateCheckListItemDto } from '../dto/check-list-item/create-check-list-item.dto';
|
||||
import { UpdateCheckListItemDto } from '../dto/check-list-item/update-check-list-item.dto';
|
||||
import { CursorPaginationDto } from '../../../common/DTO/cursor-pagination.dto';
|
||||
import { CursorPaginatedResult } from '../../../common/interfaces/cursor-paginated-result.interface';
|
||||
import { buildCursorPageFormat } from '../../../common/helpers/cursor-paginated.helper';
|
||||
import { checkListItemMessage } from '../../../common/enums/message.enum';
|
||||
import { TaskService } from './task.service';
|
||||
|
||||
@Injectable()
|
||||
export class CheckListItemService {
|
||||
constructor(
|
||||
private readonly checkListItemRepository: CheckListItemRepository,
|
||||
private readonly taskRepository: TaskRepository,
|
||||
private readonly taskService: TaskService,
|
||||
) {}
|
||||
|
||||
async findAll(pagination: CursorPaginationDto): Promise<CursorPaginatedResult<TMCheckListItem>> {
|
||||
const limit = pagination.limit ?? 10;
|
||||
const cursorDate = pagination.cursor ? new Date(pagination.cursor) : undefined;
|
||||
|
||||
const [data] = await this.checkListItemRepository.findAll(limit, cursorDate);
|
||||
|
||||
return buildCursorPageFormat(data, limit, cursorDate);
|
||||
}
|
||||
|
||||
async findByTask(
|
||||
taskId: string,
|
||||
pagination: CursorPaginationDto,
|
||||
): Promise<CursorPaginatedResult<TMCheckListItem>> {
|
||||
const limit = pagination.limit ?? 10;
|
||||
const cursorDate = pagination.cursor ? new Date(pagination.cursor) : undefined;
|
||||
|
||||
const [data] = await this.checkListItemRepository.findByTask(taskId, limit, cursorDate);
|
||||
|
||||
return buildCursorPageFormat(data, limit, cursorDate);
|
||||
}
|
||||
|
||||
async findOne(id: string): Promise<TMCheckListItem> {
|
||||
async findOneOrFail(id: string): Promise<TMCheckListItem> {
|
||||
const checkListItem = await this.checkListItemRepository.findOneById(id);
|
||||
if (!checkListItem) throw new NotFoundException(`CheckListItem #${id} not found`);
|
||||
if (!checkListItem) throw new NotFoundException(checkListItemMessage.CHECKLISTITEM_NOT_FOUND);
|
||||
return checkListItem;
|
||||
}
|
||||
|
||||
async create(dto: CreateCheckListItemDto): Promise<TMCheckListItem> {
|
||||
const task = await this.taskRepository.findOneById(dto.taskId);
|
||||
if (!task) throw new NotFoundException(`Task #${dto.taskId} not found`);
|
||||
await this.taskService.findOneOrFail(dto.taskId);
|
||||
|
||||
const checkListItem = this.checkListItemRepository.create(dto);
|
||||
return this.checkListItemRepository.save(checkListItem);
|
||||
}
|
||||
|
||||
async update(id: string, dto: UpdateCheckListItemDto): Promise<TMCheckListItem> {
|
||||
const checkListItem = await this.findOne(id);
|
||||
const checkListItem = await this.findOneOrFail(id);
|
||||
|
||||
if (dto.taskId) {
|
||||
const task = await this.taskRepository.findOneById(dto.taskId);
|
||||
if (!task) throw new NotFoundException(`Task #${dto.taskId} not found`);
|
||||
await this.taskService.findOneOrFail(dto.taskId);
|
||||
}
|
||||
|
||||
Object.assign(checkListItem, dto);
|
||||
@@ -63,8 +38,17 @@ export class CheckListItemService {
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<TMCheckListItem> {
|
||||
const checkListItem = await this.findOne(id);
|
||||
const checkListItem = await this.findOneOrFail(id);
|
||||
await this.checkListItemRepository.delete(id);
|
||||
return checkListItem;
|
||||
}
|
||||
|
||||
async toggleStatus(id: string) : Promise<TMCheckListItem> {
|
||||
const checkListItem = await this.findOneOrFail(id);
|
||||
const isDone = checkListItem.isDone;
|
||||
checkListItem.isDone = !isDone;
|
||||
await this.checkListItemRepository.save(checkListItem);
|
||||
|
||||
return checkListItem;
|
||||
}
|
||||
}
|
||||
@@ -1,66 +1,53 @@
|
||||
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { In, Repository } from 'typeorm';
|
||||
import { ProjectRepository } from '../repositories/project.repository';
|
||||
import { WorkspaceRepository } from '../repositories/workspace.repository';
|
||||
import { TMProject } from '../entities/project.entity';
|
||||
import { CreateProjectDto } from '../dto/project/create-project.dto';
|
||||
import { UpdateProjectDto } from '../dto/project/update-project.dto';
|
||||
import { PaginationDto } from '../../../common/DTO/pagination.dto';
|
||||
import { PaginatedResult } from '../../../common/interfaces/paginated-result.interface';
|
||||
import { buildPageFormat } from '../../../common/helpers/pagination.helper';
|
||||
import { User } from '../../users/entities/user.entity';
|
||||
import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { InjectRepository } from "@nestjs/typeorm";
|
||||
import { In, Repository } from "typeorm";
|
||||
import { ProjectRepository } from "../repositories/project.repository";
|
||||
import { TMProject } from "../entities/project.entity";
|
||||
import { CreateProjectDto } from "../dto/project/create-project.dto";
|
||||
import { UpdateProjectDto } from "../dto/project/update-project.dto";
|
||||
import { PaginationDto } from "../../../common/DTO/pagination.dto";
|
||||
import { User } from "../../users/entities/user.entity";
|
||||
import { ProjectMessage, WorkSpaceMessage } from "../../../common/enums/message.enum";
|
||||
import { WorkspaceService } from "./workspace.service";
|
||||
import { PermissionEnum } from "../../users/enums/permission.enum";
|
||||
|
||||
@Injectable()
|
||||
export class ProjectService {
|
||||
constructor(
|
||||
private readonly projectRepository: ProjectRepository,
|
||||
private readonly workspaceRepository: WorkspaceRepository,
|
||||
private readonly workspaceService: WorkspaceService,
|
||||
@InjectRepository(User)
|
||||
private readonly userRepository: Repository<User>,
|
||||
) {}
|
||||
|
||||
async findAll(pagination: PaginationDto, baseUrl: string): Promise<PaginatedResult<TMProject>> {
|
||||
const page = pagination.page ?? 1;
|
||||
const limit = pagination.limit ?? 10;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [data, totalItems] = await this.projectRepository.findAll(skip, limit);
|
||||
|
||||
return {
|
||||
data,
|
||||
meta: buildPageFormat(page, limit, totalItems, baseUrl),
|
||||
};
|
||||
}
|
||||
|
||||
async findByWorkspace(
|
||||
workspaceId: string,
|
||||
pagination: PaginationDto,
|
||||
baseUrl: string,
|
||||
): Promise<PaginatedResult<TMProject>> {
|
||||
const page = pagination.page ?? 1;
|
||||
const limit = pagination.limit ?? 10;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [data, totalItems] = await this.projectRepository.findByWorkspace(workspaceId, skip, limit);
|
||||
|
||||
return {
|
||||
data,
|
||||
meta: buildPageFormat(page, limit, totalItems, baseUrl),
|
||||
};
|
||||
}
|
||||
|
||||
async findOne(id: string): Promise<TMProject> {
|
||||
const project = await this.projectRepository.findOneById(id);
|
||||
if (!project) throw new NotFoundException(`Project #${id} not found`);
|
||||
async findOneOrFail(id: string): Promise<TMProject> {
|
||||
const project = await this.projectRepository.findOne({
|
||||
where: { id },
|
||||
relations: {
|
||||
users: true,
|
||||
taskPhases: true
|
||||
},
|
||||
select: {
|
||||
users: {
|
||||
id: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
profilePic: true,
|
||||
},
|
||||
taskPhases: {
|
||||
id: true,
|
||||
name: true,
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!project) throw new NotFoundException(ProjectMessage.PROJECT_NOT_FOUND);
|
||||
return project;
|
||||
}
|
||||
|
||||
async create(dto: CreateProjectDto): Promise<TMProject> {
|
||||
const { userIds, startDate, ...rest } = dto;
|
||||
|
||||
const workspace = await this.workspaceRepository.findOneById(dto.workspaceId);
|
||||
if (!workspace) throw new NotFoundException(`Workspace #${dto.workspaceId} not found`);
|
||||
const workspace = await this.workspaceService.findOneOrFail(dto.workspaceId);
|
||||
|
||||
const project = this.projectRepository.create({
|
||||
...rest,
|
||||
@@ -71,9 +58,7 @@ export class ProjectService {
|
||||
const workspaceUserIds = workspace.users.map((u) => u.id);
|
||||
const invalidUsers = userIds.filter((id) => !workspaceUserIds.includes(id));
|
||||
if (invalidUsers.length) {
|
||||
throw new BadRequestException(
|
||||
`Users [${invalidUsers.join(', ')}] are not members of this workspace`,
|
||||
);
|
||||
throw new BadRequestException(WorkSpaceMessage.USERS_DONT_BELONG_TO_THIS_WORKSPACE);
|
||||
}
|
||||
project.users = await this.userRepository.findBy({ id: In(userIds) });
|
||||
}
|
||||
@@ -82,7 +67,8 @@ export class ProjectService {
|
||||
}
|
||||
|
||||
async update(id: string, dto: UpdateProjectDto): Promise<TMProject> {
|
||||
const project = await this.findOne(id);
|
||||
const project = await this.findOneOrFail(id);
|
||||
|
||||
const { userIds, startDate, ...rest } = dto;
|
||||
|
||||
Object.assign(project, {
|
||||
@@ -93,15 +79,12 @@ export class ProjectService {
|
||||
if (userIds) {
|
||||
if (userIds.length) {
|
||||
const workspaceId = dto.workspaceId ?? project.workspaceId;
|
||||
const workspace = await this.workspaceRepository.findOneById(workspaceId);
|
||||
if (!workspace) throw new NotFoundException(`Workspace #${workspaceId} not found`);
|
||||
const workspace = await this.workspaceService.findOneOrFail(workspaceId);
|
||||
|
||||
const workspaceUserIds = workspace.users.map((u) => u.id);
|
||||
const invalidUsers = userIds.filter((uid) => !workspaceUserIds.includes(uid));
|
||||
if (invalidUsers.length) {
|
||||
throw new BadRequestException(
|
||||
`Users [${invalidUsers.join(', ')}] are not members of this workspace`,
|
||||
);
|
||||
throw new BadRequestException(WorkSpaceMessage.USERS_DONT_BELONG_TO_THIS_WORKSPACE);
|
||||
}
|
||||
project.users = await this.userRepository.findBy({ id: In(userIds) });
|
||||
} else {
|
||||
@@ -113,8 +96,79 @@ export class ProjectService {
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<TMProject> {
|
||||
const project = await this.findOne(id);
|
||||
const project = await this.projectRepository.findOne({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
relations: {
|
||||
taskPhases: true,
|
||||
},
|
||||
select: {
|
||||
taskPhases: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!project) throw new NotFoundException(ProjectMessage.PROJECT_NOT_FOUND);
|
||||
|
||||
const taskPhases = project.taskPhases;
|
||||
if (taskPhases !== undefined && taskPhases.length) {
|
||||
throw new BadRequestException(ProjectMessage.PROJECT_CONTAINS_TASK_PHASES);
|
||||
}
|
||||
await this.projectRepository.delete(id);
|
||||
return project;
|
||||
}
|
||||
|
||||
async findUserProjectsByWorkspace(
|
||||
userId: string,
|
||||
permissions: PermissionEnum[],
|
||||
wsId: string,
|
||||
pagination: PaginationDto,
|
||||
): Promise<{ projects: TMProject[]; count: number; paginate: true }> {
|
||||
const workspace = await this.workspaceService.findOneOrFail(wsId);
|
||||
|
||||
const page = pagination.page ?? 1;
|
||||
const limit = pagination.limit ?? 10;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const isPermissionIncluded = permissions.includes(PermissionEnum.PROJECT_READ);
|
||||
const isUserMemeberOfWorkspace = workspace.users.some((user) => user.id === userId);
|
||||
|
||||
var res: [TMProject[], number];
|
||||
if (isUserMemeberOfWorkspace && !isPermissionIncluded) {
|
||||
res = await this.projectRepository.findUserProjects(userId, wsId, skip, limit);
|
||||
} else if (isPermissionIncluded) {
|
||||
res = await this.projectRepository.findAndCount({ where: { workspaceId: wsId } });
|
||||
} else {
|
||||
throw new ForbiddenException(WorkSpaceMessage.FORBIDDEN);
|
||||
}
|
||||
|
||||
const [projects, count] = res;
|
||||
|
||||
return { projects, count, paginate: true };
|
||||
}
|
||||
|
||||
async getProjectDetail(projectId: string, userId: string, permissions: PermissionEnum[]): Promise<TMProject | null> {
|
||||
const project = await this.projectRepository.findOne({
|
||||
where: { id: projectId },
|
||||
relations: {
|
||||
users: true,
|
||||
},
|
||||
});
|
||||
if (!project) throw new NotFoundException(ProjectMessage.PROJECT_NOT_FOUND);
|
||||
|
||||
const isUserMember = project.users.some((user) => user.id === userId);
|
||||
if (!isUserMember && !permissions.includes(PermissionEnum.PROJECT_READ))
|
||||
throw new ForbiddenException(ProjectMessage.NOT_MEMBER_OF_PROJECT);
|
||||
|
||||
const projectDetail = await this.projectRepository.getProjectDetail(projectId);
|
||||
|
||||
return projectDetail;
|
||||
}
|
||||
|
||||
async getProjectUsers(projectId: string): Promise<User[]> {
|
||||
const project = await this.findOneOrFail(projectId);
|
||||
return project.users;
|
||||
}
|
||||
}
|
||||
@@ -1,45 +1,36 @@
|
||||
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { RemarkRepository } from "../repositories/remark.repository";
|
||||
import { TaskRepository } from "../repositories/task.repository";
|
||||
import { TMRemark } from "../entities/remark.entity";
|
||||
import { CreateRemarkDto } from "../dto/remark/create-remark.dto";
|
||||
import { UpdateRemarkDto } from "../dto/remark/update-remark.dto";
|
||||
import { remarkMessage } from "../../../common/enums/message.enum";
|
||||
import { TaskService } from './task.service';
|
||||
|
||||
@Injectable()
|
||||
export class RemarkService {
|
||||
constructor(
|
||||
private readonly remarkRepository: RemarkRepository,
|
||||
private readonly taskRepository: TaskRepository,
|
||||
private readonly taskService: TaskService,
|
||||
) {}
|
||||
|
||||
findAll(): Promise<TMRemark[]> {
|
||||
return this.remarkRepository.findAll();
|
||||
}
|
||||
|
||||
findByTask(taskId: string): Promise<TMRemark[]> {
|
||||
return this.remarkRepository.findByTask(taskId);
|
||||
}
|
||||
|
||||
async findOne(id: string): Promise<TMRemark> {
|
||||
const remark = await this.remarkRepository.findOneById(id);
|
||||
if (!remark) throw new NotFoundException(`Remark #${id} not found`);
|
||||
async findOneOrFail(id: string): Promise<TMRemark> {
|
||||
const remark = await this.remarkRepository.findOne({ where: { id } });
|
||||
if (!remark) throw new NotFoundException(remarkMessage.REMARK_NOT_FOUND);
|
||||
return remark;
|
||||
}
|
||||
|
||||
async create(dto: CreateRemarkDto): Promise<TMRemark> {
|
||||
const task = await this.taskRepository.findOneById(dto.taskId);
|
||||
if (!task) throw new NotFoundException(`Task #${dto.taskId} not found`);
|
||||
await this.taskService.findOneOrFail(dto.taskId);
|
||||
|
||||
const remark = this.remarkRepository.create(dto);
|
||||
return this.remarkRepository.save(remark);
|
||||
}
|
||||
|
||||
async update(id: string, dto: UpdateRemarkDto): Promise<TMRemark> {
|
||||
const remark = await this.findOne(id);
|
||||
const remark = await this.findOneOrFail(id);
|
||||
|
||||
if (dto.taskId) {
|
||||
const task = await this.taskRepository.findOneById(dto.taskId);
|
||||
if (!task) throw new NotFoundException(`Task #${dto.taskId} not found`);
|
||||
await this.taskService.findOneOrFail(dto.taskId);
|
||||
}
|
||||
|
||||
Object.assign(remark, dto);
|
||||
@@ -47,7 +38,7 @@ export class RemarkService {
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<void> {
|
||||
await this.findOne(id);
|
||||
await this.findOneOrFail(id);
|
||||
await this.remarkRepository.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { TaskCommentRepository } from "../repositories/task-comment.repository";
|
||||
import { TMTaskComment } from "../entities/task-comment.entity";
|
||||
import { CreateTaskCommentDto } from "../dto/task-comment/create-task-comment.dto";
|
||||
import { UpdateTaskCommentDto } from "../dto/task-comment/update-task-comment.dto";
|
||||
import { taskCommentMessage } from "../../../common/enums/message.enum";
|
||||
import { TaskService } from "./task.service";
|
||||
|
||||
@Injectable()
|
||||
export class TaskCommentService {
|
||||
constructor(
|
||||
private readonly taskCommentRepository: TaskCommentRepository,
|
||||
private readonly taskService: TaskService,
|
||||
) {}
|
||||
|
||||
async findOneOrFail(id: string): Promise<TMTaskComment> {
|
||||
const comment = await this.taskCommentRepository.findOne({
|
||||
where: { id },
|
||||
relations: { user: true, task: true },
|
||||
});
|
||||
if (!comment) throw new NotFoundException(taskCommentMessage.TASK_COMMENT_NOT_FOUND);
|
||||
return comment;
|
||||
}
|
||||
|
||||
async getTaskComments(taskId: string): Promise<TMTaskComment[]> {
|
||||
await this.taskService.findOneOrFail(taskId);
|
||||
|
||||
return this.taskCommentRepository.find({
|
||||
where: { taskId },
|
||||
relations: { user: true },
|
||||
order: { createdAt: "DESC" },
|
||||
});
|
||||
}
|
||||
|
||||
async create(userId: string, dto: CreateTaskCommentDto): Promise<TMTaskComment> {
|
||||
await this.taskService.findOneOrFail(dto.taskId);
|
||||
|
||||
const comment = this.taskCommentRepository.create({
|
||||
...dto,
|
||||
attachments: dto.attachments ?? [],
|
||||
userId,
|
||||
});
|
||||
return this.taskCommentRepository.save(comment);
|
||||
}
|
||||
|
||||
async update(id: string, dto: UpdateTaskCommentDto): Promise<TMTaskComment> {
|
||||
const comment = await this.findOneOrFail(id);
|
||||
|
||||
if (dto.taskId) {
|
||||
await this.taskService.findOneOrFail(dto.taskId);
|
||||
}
|
||||
|
||||
Object.assign(comment, dto);
|
||||
return this.taskCommentRepository.save(comment);
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<void> {
|
||||
await this.findOneOrFail(id);
|
||||
await this.taskCommentRepository.delete(id);
|
||||
}
|
||||
}
|
||||
@@ -1,45 +1,35 @@
|
||||
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { TaskPhaseRepository } from "../repositories/task-phase.repository";
|
||||
import { ProjectRepository } from "../repositories/project.repository";
|
||||
import { TMTaskPhase } from "../entities/task-phase.entity";
|
||||
import { CreateTaskPhaseDto } from "../dto/task-phase/create-task-phase.dto";
|
||||
import { UpdateTaskPhaseDto } from "../dto/task-phase/update-task-phase.dto";
|
||||
import { TaskPhaseMessage } from "../../../common/enums/message.enum";
|
||||
import { ProjectService } from "./project.service";
|
||||
|
||||
@Injectable()
|
||||
export class TaskPhaseService {
|
||||
constructor(
|
||||
private readonly taskPhaseRepository: TaskPhaseRepository,
|
||||
private readonly projectRepository: ProjectRepository,
|
||||
private readonly projectService: ProjectService
|
||||
) {}
|
||||
|
||||
findAll(): Promise<TMTaskPhase[]> {
|
||||
return this.taskPhaseRepository.findAll();
|
||||
}
|
||||
|
||||
findByProject(projectId: string): Promise<TMTaskPhase[]> {
|
||||
return this.taskPhaseRepository.findByProject(projectId);
|
||||
}
|
||||
|
||||
async findOne(id: string): Promise<TMTaskPhase> {
|
||||
const taskPhase = await this.taskPhaseRepository.findOneById(id);
|
||||
if (!taskPhase) throw new NotFoundException(`TaskPhase #${id} not found`);
|
||||
async findOneOrFail(id: string): Promise<TMTaskPhase> {
|
||||
const taskPhase = await this.taskPhaseRepository.findOne({where: {id}});
|
||||
if (!taskPhase) throw new NotFoundException(TaskPhaseMessage.TASK_PHASE_NOT_FOUND);
|
||||
return taskPhase;
|
||||
}
|
||||
|
||||
async create(dto: CreateTaskPhaseDto): Promise<TMTaskPhase> {
|
||||
const project = await this.projectRepository.findOneById(dto.projectId);
|
||||
if (!project) throw new NotFoundException(`Project #${dto.projectId} not found`);
|
||||
|
||||
await this.projectService.findOneOrFail(dto.projectId);
|
||||
const taskPhase = this.taskPhaseRepository.create(dto);
|
||||
return this.taskPhaseRepository.save(taskPhase);
|
||||
}
|
||||
|
||||
async update(id: string, dto: UpdateTaskPhaseDto): Promise<TMTaskPhase> {
|
||||
const taskPhase = await this.findOne(id);
|
||||
const taskPhase = await this.findOneOrFail(id);
|
||||
|
||||
if (dto.projectId) {
|
||||
const project = await this.projectRepository.findOneById(dto.projectId);
|
||||
if (!project) throw new NotFoundException(`Project #${dto.projectId} not found`);
|
||||
await this.projectService.findOneOrFail(dto.projectId);
|
||||
}
|
||||
|
||||
Object.assign(taskPhase, dto);
|
||||
@@ -47,7 +37,7 @@ export class TaskPhaseService {
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<void> {
|
||||
await this.findOne(id);
|
||||
await this.findOneOrFail(id);
|
||||
await this.taskPhaseRepository.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { In, Repository } from 'typeorm';
|
||||
import { TaskRepository } from '../repositories/task.repository';
|
||||
import { TaskPhaseRepository } from '../repositories/task-phase.repository';
|
||||
import { TMTask } from '../entities/task.entity';
|
||||
import { CreateTaskDto } from '../dto/task/create-task.dto';
|
||||
import { UpdateTaskDto } from '../dto/task/update-task.dto';
|
||||
import { ChangeTaskPhaseDto } from '../dto/task/change-task-phase.dto';
|
||||
import { User } from '../../users/entities/user.entity';
|
||||
import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { InjectRepository } from "@nestjs/typeorm";
|
||||
import { In, Repository } from "typeorm";
|
||||
import { TaskRepository } from "../repositories/task.repository";
|
||||
import { TMTask } from "../entities/task.entity";
|
||||
import { CreateTaskDto } from "../dto/task/create-task.dto";
|
||||
import { UpdateTaskDto } from "../dto/task/update-task.dto";
|
||||
import { ChangeTaskPhaseDto } from "../dto/task/change-task-phase.dto";
|
||||
import { User } from "../../users/entities/user.entity";
|
||||
import { Ticket } from "../../tickets/entities/ticket.entity";
|
||||
import { TaskMessage, TaskPhaseMessage, TicketMessageEnum } from "../../../common/enums/message.enum";
|
||||
import { TaskPhaseRepository } from "../repositories/task-phase.repository";
|
||||
import { TMTaskPhase } from "../entities/task-phase.entity";
|
||||
import { PermissionEnum } from "../../users/enums/permission.enum";
|
||||
import { encodeCursor } from "../../utils/providers/cursor-pagination.utils";
|
||||
import { CursorDirection, CursorPaginationDto } from "../../../common/DTO/cursor-pagination.dto";
|
||||
|
||||
@Injectable()
|
||||
export class TaskService {
|
||||
@@ -16,30 +22,60 @@ export class TaskService {
|
||||
private readonly taskPhaseRepository: TaskPhaseRepository,
|
||||
@InjectRepository(User)
|
||||
private readonly userRepository: Repository<User>,
|
||||
@InjectRepository(Ticket)
|
||||
private readonly ticketRepository: Repository<Ticket>,
|
||||
) {}
|
||||
|
||||
findAll(): Promise<TMTask[]> {
|
||||
return this.taskRepository.findAll();
|
||||
}
|
||||
|
||||
findByPhase(taskPhaseId: string): Promise<TMTask[]> {
|
||||
return this.taskRepository.findByPhase(taskPhaseId);
|
||||
}
|
||||
|
||||
async findOne(id: string): Promise<TMTask> {
|
||||
const task = await this.taskRepository.findOneById(id);
|
||||
if (!task) throw new NotFoundException(`Task #${id} not found`);
|
||||
async findOneOrFail(id: string): Promise<TMTask> {
|
||||
const task = await this.taskRepository.findOne({
|
||||
where: { id },
|
||||
relations: {
|
||||
taskPhase: true,
|
||||
users: true,
|
||||
ticket: true,
|
||||
},
|
||||
});
|
||||
if (!task) throw new NotFoundException(TaskMessage.TASK_NOT_FOUND);
|
||||
return task;
|
||||
}
|
||||
|
||||
async create(dto: CreateTaskDto): Promise<TMTask> {
|
||||
const { userIds, startDate, endDate, ...rest } = dto;
|
||||
private async findTicketOrFail(ticketId: string): Promise<Ticket> {
|
||||
const ticket = await this.ticketRepository.findOne({ where: { id: ticketId } });
|
||||
if (!ticket) throw new NotFoundException(TicketMessageEnum.TICKET_NOT_FOUND);
|
||||
return ticket;
|
||||
}
|
||||
|
||||
const taskPhase = await this.taskPhaseRepository.findOneById(dto.taskPhaseId);
|
||||
if (!taskPhase) throw new NotFoundException(`TaskPhase #${dto.taskPhaseId} not found`);
|
||||
async findTaskPhaseOrFail(taskPhaseId: string): Promise<TMTaskPhase> {
|
||||
const taskPhase = await this.taskPhaseRepository.findOne({
|
||||
where: { id: taskPhaseId },
|
||||
relations: {
|
||||
project: {
|
||||
users: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!taskPhase) throw new NotFoundException(TaskPhaseMessage.TASK_PHASE_NOT_FOUND);
|
||||
|
||||
return taskPhase;
|
||||
}
|
||||
|
||||
async create(dto: CreateTaskDto): Promise<TMTask> {
|
||||
const { userIds, startDate, endDate, ticketId, ...rest } = dto;
|
||||
|
||||
const taskPhase = await this.findTaskPhaseOrFail(dto.taskPhaseId);
|
||||
|
||||
if (ticketId) {
|
||||
await this.findTicketOrFail(ticketId);
|
||||
}
|
||||
|
||||
if (userIds?.length) {
|
||||
const isUsersValid = userIds.every((userId) => taskPhase.project.users.some((user) => user.id === userId));
|
||||
if (!isUsersValid) throw new BadRequestException(TaskMessage.USERS_ASSIGNED_TO_TASK_NOT_PROJECT_MEMBER);
|
||||
}
|
||||
|
||||
const task = this.taskRepository.create({
|
||||
...rest,
|
||||
ticketId,
|
||||
startDate: startDate ? new Date(startDate) : undefined,
|
||||
endDate: endDate ? new Date(endDate) : undefined,
|
||||
});
|
||||
@@ -52,39 +88,47 @@ export class TaskService {
|
||||
}
|
||||
|
||||
async update(id: string, dto: UpdateTaskDto): Promise<TMTask> {
|
||||
const task = await this.findOne(id);
|
||||
const { userIds, startDate, endDate, ...rest } = dto;
|
||||
const task = await this.findOneOrFail(id);
|
||||
const { userIds, startDate, endDate, ticketId, ...rest } = dto;
|
||||
|
||||
if (ticketId) {
|
||||
await this.findTicketOrFail(ticketId);
|
||||
}
|
||||
|
||||
if (dto.taskPhaseId) {
|
||||
const taskPhase = await this.taskPhaseRepository.findOneById(dto.taskPhaseId);
|
||||
if (!taskPhase) throw new NotFoundException(`TaskPhase #${dto.taskPhaseId} not found`);
|
||||
const taskPhase = await this.findTaskPhaseOrFail(dto.taskPhaseId);
|
||||
|
||||
if (userIds?.length) {
|
||||
const isUsersValid = userIds.every((userId) => taskPhase.project.users.some((user) => user.id === userId));
|
||||
if (!isUsersValid) throw new BadRequestException(TaskMessage.USERS_ASSIGNED_TO_TASK_NOT_PROJECT_MEMBER);
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(task, {
|
||||
...rest,
|
||||
...(ticketId !== undefined && { ticketId }),
|
||||
startDate: startDate ? new Date(startDate) : task.startDate,
|
||||
endDate: endDate ? new Date(endDate) : task.endDate,
|
||||
});
|
||||
|
||||
if (userIds) {
|
||||
task.users = userIds.length
|
||||
? await this.userRepository.findBy({ id: In(userIds) })
|
||||
: [];
|
||||
task.users = userIds.length ? await this.userRepository.findBy({ id: In(userIds) }) : [];
|
||||
}
|
||||
|
||||
return this.taskRepository.save(task);
|
||||
}
|
||||
|
||||
async changePhase(id: string, dto: ChangeTaskPhaseDto): Promise<TMTask> {
|
||||
const task = await this.findOne(id);
|
||||
async changePhase(id: string,permissions: PermissionEnum[],userId: string, dto: ChangeTaskPhaseDto): Promise<TMTask> {
|
||||
const task = await this.findOneOrFail(id);
|
||||
|
||||
const newPhase = await this.taskPhaseRepository.findOneById(dto.taskPhaseId);
|
||||
if (!newPhase) throw new NotFoundException(`TaskPhase #${dto.taskPhaseId} not found`);
|
||||
const isTaskMember = task.users.some((user) => user.id === userId);
|
||||
const hasPermission = permissions.includes(PermissionEnum.TASK_UPDATE);
|
||||
if (!isTaskMember && !hasPermission) throw new ForbiddenException(TaskMessage.USER_NOT_TASK_MEMBER);
|
||||
|
||||
const newPhase = await this.findTaskPhaseOrFail(dto.taskPhaseId);
|
||||
|
||||
if (task.taskPhase.projectId !== newPhase.projectId) {
|
||||
throw new BadRequestException(
|
||||
`TaskPhase #${dto.taskPhaseId} does not belong to the same project as the current phase`,
|
||||
);
|
||||
throw new BadRequestException(TaskPhaseMessage.TASK_PHASE_NOT_SAME_PROJECT);
|
||||
}
|
||||
|
||||
task.taskPhaseId = dto.taskPhaseId;
|
||||
@@ -94,7 +138,79 @@ export class TaskService {
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<void> {
|
||||
await this.findOne(id);
|
||||
await this.findOneOrFail(id);
|
||||
await this.taskRepository.delete(id);
|
||||
}
|
||||
|
||||
async getTaskDetail(userId: string, permissions: PermissionEnum[], taskId: string): Promise<TMTask | null> {
|
||||
const task = await this.findOneOrFail(taskId);
|
||||
|
||||
const isTaskMember = task.users.some((user) => user.id === userId);
|
||||
const hasPermission = permissions.includes(PermissionEnum.TASK_READ);
|
||||
if (!isTaskMember && !hasPermission) throw new ForbiddenException(TaskMessage.USER_NOT_TASK_MEMBER);
|
||||
|
||||
const taskDetail = await this.taskRepository.getTaskDetail(taskId);
|
||||
return taskDetail;
|
||||
}
|
||||
|
||||
async findTasksByTaskPhase(
|
||||
taskPhaseId: string,
|
||||
pagination: CursorPaginationDto,
|
||||
): Promise<{
|
||||
data: TMTask[];
|
||||
nextCursor: string | null;
|
||||
previousCursor: string | null;
|
||||
hasNextPage: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
cursorPaginate: true;
|
||||
}> {
|
||||
await this.findTaskPhaseOrFail(taskPhaseId);
|
||||
|
||||
const isBackward = pagination.direction === CursorDirection.PREV;
|
||||
const { items, hasMore } = await this.taskRepository.findTasksByTaskPhase(taskPhaseId, pagination);
|
||||
|
||||
// paging backward always has more ahead (we came from there); paging forward relies on
|
||||
// whether a cursor was even supplied to know whether anything precedes this page
|
||||
const hasNextPage = items.length > 0 && (isBackward ? true : hasMore);
|
||||
const hasPreviousPage = items.length > 0 && (isBackward ? hasMore : !!pagination.cursor);
|
||||
|
||||
const nextCursor = hasNextPage ? encodeCursor(items[items.length - 1]) : null;
|
||||
const previousCursor = hasPreviousPage ? encodeCursor(items[0]) : null;
|
||||
|
||||
return { data: items, nextCursor, previousCursor, hasNextPage, hasPreviousPage, cursorPaginate: true };
|
||||
}
|
||||
|
||||
async changePriority(srcTaskId: string,permissions: PermissionEnum[],userId: string, destTaskId: string): Promise<TMTask> {
|
||||
const srcTask = await this.findOneOrFail(srcTaskId);
|
||||
const desTask = await this.findOneOrFail(destTaskId);
|
||||
|
||||
const isTaskMember = srcTask.users.some((user) => user.id === userId);
|
||||
const hasPermission = permissions.includes(PermissionEnum.TASK_UPDATE);
|
||||
if (!isTaskMember && !hasPermission) throw new ForbiddenException(TaskMessage.USER_NOT_TASK_MEMBER);
|
||||
|
||||
if (srcTask.taskPhaseId !== desTask.taskPhaseId)
|
||||
throw new BadRequestException(TaskMessage.TASKS_NOT_IN_THE_SAME_TASK_PHASE);
|
||||
|
||||
let movingUp: boolean = false;
|
||||
|
||||
if(srcTask.priority < desTask.priority) {
|
||||
movingUp = true;
|
||||
} else if(srcTask.priority < desTask.priority) {
|
||||
movingUp = false;
|
||||
} else {
|
||||
if(srcTask.createdAt > desTask.createdAt) {
|
||||
movingUp = true;
|
||||
}else {
|
||||
movingUp = false;
|
||||
}
|
||||
}
|
||||
|
||||
const priorityChange: number = movingUp ? 1 : -1;
|
||||
|
||||
srcTask.priority = desTask.priority + priorityChange;
|
||||
|
||||
await this.taskRepository.save(srcTask);
|
||||
|
||||
return srcTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { IsNull } from "typeorm";
|
||||
import { TimeRepository } from "../repositories/time.repository";
|
||||
import { TMTime } from "../entities/time.entity";
|
||||
import { CreateTimeDto } from "../dto/time/create-time.dto";
|
||||
import { UpdateTimeDto } from "../dto/time/update-time.dto";
|
||||
import { timeMessage } from "../../../common/enums/message.enum";
|
||||
import { TaskService } from "./task.service";
|
||||
|
||||
@Injectable()
|
||||
export class TimeService {
|
||||
constructor(
|
||||
private readonly timeRepository: TimeRepository,
|
||||
private readonly taskService: TaskService,
|
||||
) { }
|
||||
|
||||
private calculateDuration(startedAt: Date, endedAt: Date): number {
|
||||
if (endedAt.getTime() < startedAt.getTime()) {
|
||||
throw new BadRequestException(timeMessage.INVALID_TIME_RANGE);
|
||||
}
|
||||
|
||||
return Math.floor((endedAt.getTime() - startedAt.getTime()) / 1000);
|
||||
}
|
||||
|
||||
private async findRunningTime(adminId: string, taskId: string): Promise<TMTime | null> {
|
||||
return this.timeRepository.findOne({
|
||||
where: { adminId, taskId, endedAt: IsNull() },
|
||||
order: { startedAt: "DESC" },
|
||||
});
|
||||
}
|
||||
|
||||
async findOneOrFail(id: string): Promise<TMTime> {
|
||||
const time = await this.timeRepository.findOne({
|
||||
where: { id },
|
||||
relations: { admin: true, task: true },
|
||||
});
|
||||
if (!time) throw new NotFoundException(timeMessage.TIME_NOT_FOUND);
|
||||
return time;
|
||||
}
|
||||
|
||||
async start(adminId: string, taskId: string): Promise<TMTime> {
|
||||
await this.taskService.findOneOrFail(taskId);
|
||||
|
||||
const runningTime = await this.findRunningTime(adminId, taskId);
|
||||
if (runningTime) throw new BadRequestException(timeMessage.TIME_ALREADY_STARTED);
|
||||
|
||||
const time = this.timeRepository.create({
|
||||
taskId,
|
||||
adminId,
|
||||
startedAt: new Date(),
|
||||
endedAt: null,
|
||||
duration: null,
|
||||
});
|
||||
|
||||
return this.timeRepository.save(time);
|
||||
}
|
||||
|
||||
async getTaskTimes(adminId: string, taskId: string): Promise<TMTime[]> {
|
||||
return this.timeRepository.find({
|
||||
where: { adminId, taskId },
|
||||
order: { startedAt: "DESC" },
|
||||
});
|
||||
}
|
||||
|
||||
async end(adminId: string, taskId: string): Promise<TMTime> {
|
||||
await this.taskService.findOneOrFail(taskId);
|
||||
|
||||
const runningTime = await this.findRunningTime(adminId, taskId);
|
||||
if (!runningTime) throw new BadRequestException(timeMessage.NO_RUNNING_TIME);
|
||||
|
||||
const endedAt = new Date();
|
||||
runningTime.endedAt = endedAt;
|
||||
runningTime.duration = this.calculateDuration(runningTime.startedAt, endedAt);
|
||||
|
||||
return this.timeRepository.save(runningTime);
|
||||
}
|
||||
|
||||
async create(adminId: string, dto: CreateTimeDto): Promise<TMTime> {
|
||||
await this.taskService.findOneOrFail(dto.taskId);
|
||||
|
||||
const startedAt = new Date(dto.startedAt);
|
||||
const endedAt = new Date(dto.endedAt);
|
||||
|
||||
const time = this.timeRepository.create({
|
||||
taskId: dto.taskId,
|
||||
adminId,
|
||||
startedAt,
|
||||
endedAt,
|
||||
duration: this.calculateDuration(startedAt, endedAt),
|
||||
});
|
||||
|
||||
return this.timeRepository.save(time);
|
||||
}
|
||||
|
||||
async update(id: string, dto: UpdateTimeDto): Promise<TMTime> {
|
||||
const time = await this.findOneOrFail(id);
|
||||
|
||||
if (dto.taskId) {
|
||||
await this.taskService.findOneOrFail(dto.taskId);
|
||||
}
|
||||
|
||||
const startedAt = dto.startedAt ? new Date(dto.startedAt) : time.startedAt;
|
||||
const endedAt = dto.endedAt ? new Date(dto.endedAt) : time.endedAt;
|
||||
|
||||
Object.assign(time, {
|
||||
...dto,
|
||||
startedAt,
|
||||
endedAt,
|
||||
duration: endedAt ? this.calculateDuration(startedAt, endedAt) : null,
|
||||
});
|
||||
|
||||
return this.timeRepository.save(time);
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<void> {
|
||||
await this.findOneOrFail(id);
|
||||
await this.timeRepository.delete(id);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { InjectRepository } from "@nestjs/typeorm";
|
||||
import { In, Repository } from "typeorm";
|
||||
import { In, Not, Repository } from "typeorm";
|
||||
import { WorkspaceRepository } from "../repositories/workspace.repository";
|
||||
import { TMWorkspace } from "../entities/workspace.entity";
|
||||
import { CreateWorkspaceDto } from "../dto/workspace/create-workspace.dto";
|
||||
import { UpdateWorkspaceDto } from "../dto/workspace/update-workspace.dto";
|
||||
import { User } from "../../users/entities/user.entity";
|
||||
import { WorkSpaceMessage } from "../../../common/enums/message.enum";
|
||||
import { PermissionEnum } from "../../users/enums/permission.enum";
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceService {
|
||||
@@ -16,23 +17,17 @@ export class WorkspaceService {
|
||||
private readonly userRepository: Repository<User>,
|
||||
) {}
|
||||
|
||||
findAll(): Promise<TMWorkspace[]> {
|
||||
return this.workspaceRepository.find({
|
||||
relations: ["projects", "users"],
|
||||
});
|
||||
}
|
||||
|
||||
async findOneOrFail(id: string): Promise<TMWorkspace> {
|
||||
const workspace = await this.workspaceRepository.findOne({ where: { id } });
|
||||
if (!workspace) throw new NotFoundException(`Workspace #${id} not found`);
|
||||
const workspace = await this.workspaceRepository.findOneWorkSpace(id);
|
||||
if (!workspace) throw new NotFoundException(WorkSpaceMessage.WORKSPACE_NOT_FOUND);
|
||||
return workspace;
|
||||
}
|
||||
|
||||
async create(dto: CreateWorkspaceDto): Promise<TMWorkspace> {
|
||||
const { userIds, ...rest } = dto;
|
||||
|
||||
const existingWorkspace = await this.workspaceRepository.findOne({where: {name: rest.name}});
|
||||
if(existingWorkspace) {
|
||||
const existingWorkspace = await this.workspaceRepository.findOne({ where: { name: rest.name } });
|
||||
if (existingWorkspace) {
|
||||
throw new BadRequestException(WorkSpaceMessage.WORKSPACE_EXISTS);
|
||||
}
|
||||
|
||||
@@ -49,6 +44,11 @@ export class WorkspaceService {
|
||||
const workspace = await this.findOneOrFail(id);
|
||||
const { userIds, ...rest } = dto;
|
||||
|
||||
if (rest.name) {
|
||||
const existingWorkSpaceWithTheSameName = await this.workspaceRepository.findOne({ where: { name: rest.name, id: Not(id) } });
|
||||
if (existingWorkSpaceWithTheSameName) throw new BadRequestException(WorkSpaceMessage.WORKSPACE_SAME_NAME_EXISTS);
|
||||
}
|
||||
|
||||
Object.assign(workspace, rest);
|
||||
|
||||
if (userIds) {
|
||||
@@ -65,7 +65,15 @@ export class WorkspaceService {
|
||||
return workspace;
|
||||
}
|
||||
|
||||
async findByUser(userId: string): Promise<TMWorkspace[]> {
|
||||
async findByUser(userId: string, permissions: PermissionEnum[]): Promise<TMWorkspace[]> {
|
||||
if(permissions.includes(PermissionEnum.WORKSPACE_READ)) {
|
||||
return await this.workspaceRepository.find();
|
||||
}
|
||||
return await this.workspaceRepository.findByUser(userId);
|
||||
}
|
||||
|
||||
async getWorkspaceUsers(workspaceId: string): Promise<User[]> {
|
||||
const workspace = await this.findOneOrFail(workspaceId);
|
||||
return workspace.users;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,44 +4,10 @@ import { Repository } from 'typeorm';
|
||||
import { TMAttachment } from '../entities/attachment.entity';
|
||||
|
||||
@Injectable()
|
||||
export class AttachmentRepository {
|
||||
export class AttachmentRepository extends Repository<TMAttachment>{
|
||||
constructor(
|
||||
@InjectRepository(TMAttachment)
|
||||
private readonly repository: Repository<TMAttachment>,
|
||||
) {}
|
||||
|
||||
findAll() {
|
||||
return this.repository.find({
|
||||
relations: ['task'],
|
||||
});
|
||||
}
|
||||
|
||||
findByTask(taskId: string) {
|
||||
return this.repository.find({
|
||||
where: { taskId },
|
||||
});
|
||||
}
|
||||
|
||||
findOneById(id: string) {
|
||||
return this.repository.findOne({
|
||||
where: { id },
|
||||
relations: ['task'],
|
||||
});
|
||||
}
|
||||
|
||||
create(data: Partial<TMAttachment>) {
|
||||
return this.repository.create(data);
|
||||
}
|
||||
|
||||
save(attachment: TMAttachment) {
|
||||
return this.repository.save(attachment);
|
||||
}
|
||||
|
||||
update(id: string, data: Partial<TMAttachment>) {
|
||||
return this.repository.update(id, data);
|
||||
}
|
||||
|
||||
delete(id: string) {
|
||||
return this.repository.delete(id);
|
||||
@InjectRepository(TMAttachment) attachmentRepository: Repository<TMAttachment>,
|
||||
) {
|
||||
super(attachmentRepository.target, attachmentRepository.manager, attachmentRepository.queryRunner);
|
||||
}
|
||||
}
|
||||
@@ -1,62 +1,13 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository, MoreThan } from 'typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { TMCheckListItem } from '../entities/check-list-item.entity';
|
||||
|
||||
@Injectable()
|
||||
export class CheckListItemRepository {
|
||||
export class CheckListItemRepository extends Repository<TMCheckListItem>{
|
||||
constructor(
|
||||
@InjectRepository(TMCheckListItem)
|
||||
private readonly repository: Repository<TMCheckListItem>,
|
||||
) {}
|
||||
|
||||
findAll(limit: number, cursor?: Date) {
|
||||
return this.repository.findAndCount({
|
||||
where: cursor ? { createdAt: MoreThan(cursor) } : {},
|
||||
order: { createdAt: 'ASC' },
|
||||
take: limit + 1,
|
||||
relations: ['task'],
|
||||
});
|
||||
}
|
||||
|
||||
findByTask(taskId: string, limit: number, cursor?: Date) {
|
||||
return this.repository.findAndCount({
|
||||
where: cursor
|
||||
? { taskId, createdAt: MoreThan(cursor) }
|
||||
: { taskId },
|
||||
order: { createdAt: 'ASC' },
|
||||
take: limit + 1,
|
||||
relations: ['task'],
|
||||
});
|
||||
}
|
||||
|
||||
findOneById(id: string) {
|
||||
return this.repository.findOne({
|
||||
where: { id },
|
||||
relations: ['task'],
|
||||
});
|
||||
}
|
||||
|
||||
findOneByCreatedAt(createdAt: Date) {
|
||||
return this.repository.findOne({
|
||||
where: { createdAt },
|
||||
order: { createdAt: 'ASC' },
|
||||
});
|
||||
}
|
||||
|
||||
create(data: Partial<TMCheckListItem>) {
|
||||
return this.repository.create(data);
|
||||
}
|
||||
|
||||
save(checkListItem: TMCheckListItem) {
|
||||
return this.repository.save(checkListItem);
|
||||
}
|
||||
|
||||
update(id: string, data: Partial<TMCheckListItem>) {
|
||||
return this.repository.update(id, data);
|
||||
}
|
||||
|
||||
delete(id: string) {
|
||||
return this.repository.delete(id);
|
||||
@InjectRepository(TMCheckListItem) checkListItemRepository: Repository<TMCheckListItem>,
|
||||
) {
|
||||
super(checkListItemRepository.target, checkListItemRepository.manager, checkListItemRepository.queryRunner);
|
||||
}
|
||||
}
|
||||
@@ -1,52 +1,40 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { TMProject } from '../entities/project.entity';
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { InjectRepository } from "@nestjs/typeorm";
|
||||
import { Repository } from "typeorm";
|
||||
import { TMProject } from "../entities/project.entity";
|
||||
|
||||
@Injectable()
|
||||
export class ProjectRepository {
|
||||
constructor(
|
||||
@InjectRepository(TMProject)
|
||||
private readonly repository: Repository<TMProject>,
|
||||
) {}
|
||||
|
||||
findAll(skip: number, take: number) {
|
||||
return this.repository.findAndCount({
|
||||
relations: ['workspace', 'taskPhases', 'users'],
|
||||
skip,
|
||||
take,
|
||||
});
|
||||
export class ProjectRepository extends Repository<TMProject> {
|
||||
constructor(@InjectRepository(TMProject) projectRepository: Repository<TMProject>) {
|
||||
super(projectRepository.target, projectRepository.manager, projectRepository.queryRunner);
|
||||
}
|
||||
|
||||
findByWorkspace(workspaceId: string, skip: number, take: number) {
|
||||
return this.repository.findAndCount({
|
||||
where: { workspaceId },
|
||||
relations: ['taskPhases', 'users'],
|
||||
skip,
|
||||
take,
|
||||
});
|
||||
async findUserProjects(userId: string, wsId: string, skip?: number, take?: number): Promise<[TMProject[], number]> {
|
||||
return this.createQueryBuilder("project")
|
||||
.innerJoin("project.workspace", "workspace")
|
||||
.innerJoin("project.users", "user")
|
||||
.where("workspace.id = :wsId", { wsId })
|
||||
.andWhere("user.id = :userId", { userId })
|
||||
.skip(skip ?? 0)
|
||||
.take(take ?? 10)
|
||||
.getManyAndCount();
|
||||
}
|
||||
|
||||
findOneById(id: string) {
|
||||
return this.repository.findOne({
|
||||
where: { id },
|
||||
relations: ['workspace', 'taskPhases', 'users'],
|
||||
});
|
||||
}
|
||||
async getProjectDetail(projectId: string): Promise<TMProject | null> {
|
||||
return this.createQueryBuilder("project")
|
||||
.leftJoinAndSelect("project.taskPhases", "taskPhase")
|
||||
.where("project.id = :projectId", { projectId })
|
||||
|
||||
create(data: Partial<TMProject>) {
|
||||
return this.repository.create(data);
|
||||
}
|
||||
.select([
|
||||
"project.id",
|
||||
"project.name",
|
||||
"taskPhase.id",
|
||||
"taskPhase.name",
|
||||
"taskPhase.order",
|
||||
])
|
||||
|
||||
save(project: TMProject) {
|
||||
return this.repository.save(project);
|
||||
}
|
||||
.orderBy("taskPhase.order", "ASC")
|
||||
|
||||
update(id: string, data: Partial<TMProject>) {
|
||||
return this.repository.update(id, data);
|
||||
}
|
||||
|
||||
delete(id: string) {
|
||||
return this.repository.delete(id);
|
||||
.getOne();
|
||||
}
|
||||
}
|
||||
@@ -4,44 +4,10 @@ import { Repository } from "typeorm";
|
||||
import { TMRemark } from "../entities/remark.entity";
|
||||
|
||||
@Injectable()
|
||||
export class RemarkRepository {
|
||||
export class RemarkRepository extends Repository<TMRemark>{
|
||||
constructor(
|
||||
@InjectRepository(TMRemark)
|
||||
private readonly repository: Repository<TMRemark>,
|
||||
) {}
|
||||
|
||||
findAll() {
|
||||
return this.repository.find({
|
||||
relations: ["task"],
|
||||
});
|
||||
}
|
||||
|
||||
findByTask(taskId: string) {
|
||||
return this.repository.find({
|
||||
where: { taskId },
|
||||
});
|
||||
}
|
||||
|
||||
findOneById(id: string) {
|
||||
return this.repository.findOne({
|
||||
where: { id },
|
||||
relations: ["task"],
|
||||
});
|
||||
}
|
||||
|
||||
create(data: Partial<TMRemark>) {
|
||||
return this.repository.create(data);
|
||||
}
|
||||
|
||||
save(remark: TMRemark) {
|
||||
return this.repository.save(remark);
|
||||
}
|
||||
|
||||
update(id: string, data: Partial<TMRemark>) {
|
||||
return this.repository.update(id, data);
|
||||
}
|
||||
|
||||
delete(id: string) {
|
||||
return this.repository.delete(id);
|
||||
@InjectRepository(TMRemark) remarkRepository: Repository<TMRemark>,
|
||||
) {
|
||||
super(remarkRepository.target, remarkRepository.manager, remarkRepository.queryRunner);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { InjectRepository } from "@nestjs/typeorm";
|
||||
import { Repository } from "typeorm";
|
||||
import { TMTaskComment } from "../entities/task-comment.entity";
|
||||
|
||||
@Injectable()
|
||||
export class TaskCommentRepository extends Repository<TMTaskComment> {
|
||||
constructor(
|
||||
@InjectRepository(TMTaskComment) taskCommentRepository: Repository<TMTaskComment>,
|
||||
) {
|
||||
super(taskCommentRepository.target, taskCommentRepository.manager, taskCommentRepository.queryRunner);
|
||||
}
|
||||
}
|
||||
@@ -4,47 +4,10 @@ import { Repository } from "typeorm";
|
||||
import { TMTaskPhase } from "../entities/task-phase.entity";
|
||||
|
||||
@Injectable()
|
||||
export class TaskPhaseRepository {
|
||||
export class TaskPhaseRepository extends Repository<TMTaskPhase> {
|
||||
constructor(
|
||||
@InjectRepository(TMTaskPhase)
|
||||
private readonly repository: Repository<TMTaskPhase>,
|
||||
) {}
|
||||
|
||||
findAll() {
|
||||
return this.repository.find({
|
||||
relations: ["project", "tasks"],
|
||||
order: { order: "ASC" },
|
||||
});
|
||||
}
|
||||
|
||||
findByProject(projectId: string) {
|
||||
return this.repository.find({
|
||||
where: { projectId },
|
||||
relations: ["tasks"],
|
||||
order: { order: "ASC" },
|
||||
});
|
||||
}
|
||||
|
||||
findOneById(id: string) {
|
||||
return this.repository.findOne({
|
||||
where: { id },
|
||||
relations: ["project", "tasks"],
|
||||
});
|
||||
}
|
||||
|
||||
create(data: Partial<TMTaskPhase>) {
|
||||
return this.repository.create(data);
|
||||
}
|
||||
|
||||
save(taskPhase: TMTaskPhase) {
|
||||
return this.repository.save(taskPhase);
|
||||
}
|
||||
|
||||
update(id: string, data: Partial<TMTaskPhase>) {
|
||||
return this.repository.update(id, data);
|
||||
}
|
||||
|
||||
delete(id: string) {
|
||||
return this.repository.delete(id);
|
||||
@InjectRepository(TMTaskPhase) taskPhaseRepository: Repository<TMTaskPhase>,
|
||||
) {
|
||||
super(taskPhaseRepository.target, taskPhaseRepository.manager, taskPhaseRepository.queryRunner);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,47 +2,176 @@ import { Injectable } from "@nestjs/common";
|
||||
import { InjectRepository } from "@nestjs/typeorm";
|
||||
import { Repository } from "typeorm";
|
||||
import { TMTask } from "../entities/task.entity";
|
||||
import { TMTime } from "../entities/time.entity";
|
||||
import { CursorDirection, CursorPaginationDto } from "../../../common/DTO/cursor-pagination.dto";
|
||||
import { decodeCursor } from "../../utils/providers/cursor-pagination.utils";
|
||||
|
||||
@Injectable()
|
||||
export class TaskRepository {
|
||||
constructor(
|
||||
@InjectRepository(TMTask)
|
||||
private readonly repository: Repository<TMTask>,
|
||||
) {}
|
||||
|
||||
findAll() {
|
||||
return this.repository.find({
|
||||
relations: ["taskPhase", "remarks", "attachments", "checkListItems", "users"],
|
||||
});
|
||||
export class TaskRepository extends Repository<TMTask> {
|
||||
constructor(@InjectRepository(TMTask) taskRepository: Repository<TMTask>) {
|
||||
super(taskRepository.target, taskRepository.manager, taskRepository.queryRunner);
|
||||
}
|
||||
|
||||
findByPhase(taskPhaseId: string) {
|
||||
return this.repository.find({
|
||||
where: { taskPhaseId },
|
||||
relations: ["remarks", "attachments", "checkListItems", "users"],
|
||||
});
|
||||
async getTaskDetail(taskId: string): Promise<TMTask | null> {
|
||||
const task = await this.createQueryBuilder("task")
|
||||
.leftJoinAndSelect("task.remarks", "remark")
|
||||
.leftJoinAndSelect("task.attachments", "attachment")
|
||||
.leftJoinAndSelect("task.checkListItems", "checkListItem")
|
||||
.leftJoinAndSelect("task.users", "user")
|
||||
.leftJoinAndSelect("task.times", "time")
|
||||
.leftJoinAndSelect("task.ticket", "ticket")
|
||||
.leftJoinAndSelect("time.admin", "timeAdmin")
|
||||
.leftJoinAndSelect("task.comments", "comment")
|
||||
.leftJoinAndSelect("comment.user", "commentUser")
|
||||
|
||||
.loadRelationCountAndMap("task.checkListItemCount", "task.checkListItems")
|
||||
|
||||
.loadRelationCountAndMap("task.completedCheckListItemCount", "task.checkListItems", "completedCheckListItems", (qb) =>
|
||||
qb.where("completedCheckListItems.isDone = :isDone", { isDone: true }),
|
||||
)
|
||||
|
||||
.select([
|
||||
"task",
|
||||
|
||||
"remark.id",
|
||||
"remark.title",
|
||||
"remark.color",
|
||||
"checkListItem.id",
|
||||
"checkListItem.title",
|
||||
"checkListItem.isDone",
|
||||
"attachment.id",
|
||||
"attachment.title",
|
||||
"attachment.type",
|
||||
"attachment.file",
|
||||
|
||||
"user.id",
|
||||
"user.firstName",
|
||||
"user.lastName",
|
||||
"user.profilePic",
|
||||
|
||||
"ticket.id",
|
||||
"ticket.subject",
|
||||
"ticket.numericId",
|
||||
"ticket.status",
|
||||
|
||||
"time.id",
|
||||
"time.startedAt",
|
||||
"time.endedAt",
|
||||
"time.duration",
|
||||
"time.adminId",
|
||||
"timeAdmin.id",
|
||||
"timeAdmin.firstName",
|
||||
"timeAdmin.lastName",
|
||||
|
||||
"comment.id",
|
||||
"comment.content",
|
||||
"comment.attachments",
|
||||
"comment.createdAt",
|
||||
"commentUser.firstName",
|
||||
"commentUser.lastName",
|
||||
"commentUser.profilePic",
|
||||
])
|
||||
.where("task.id = :taskId", { taskId })
|
||||
|
||||
.orderBy("checkListItem.createdAt", "ASC")
|
||||
.addOrderBy("time.createdAt", "ASC")
|
||||
|
||||
.getOne();
|
||||
|
||||
if (task) {
|
||||
task.time = task.times?.reduce((sum, entry) => sum + (entry.duration ?? 0), 0) ?? 0;
|
||||
}
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
findOneById(id: string) {
|
||||
return this.repository.findOne({
|
||||
where: { id },
|
||||
relations: ["taskPhase", "remarks", "attachments", "checkListItems", "users"],
|
||||
});
|
||||
}
|
||||
async findTasksByTaskPhase(taskPhaseId: string, pagination: CursorPaginationDto): Promise<{ items: TMTask[]; hasMore: boolean }> {
|
||||
const limit = pagination.limit || 10;
|
||||
const isBackward = pagination.direction === CursorDirection.PREV;
|
||||
|
||||
create(data: Partial<TMTask>) {
|
||||
return this.repository.create(data);
|
||||
}
|
||||
const createdAtExpr = `date_trunc('milliseconds', task.createdAt)`;
|
||||
|
||||
save(task: TMTask) {
|
||||
return this.repository.save(task);
|
||||
}
|
||||
const seekQuery = this.createQueryBuilder("task")
|
||||
.select(["task.id", "task.priority", "task.createdAt"])
|
||||
.where("task.taskPhaseId = :taskPhaseId", { taskPhaseId })
|
||||
.limit(limit + 1); // fetch one extra to check whether there's a further page
|
||||
|
||||
update(id: string, data: Partial<TMTask>) {
|
||||
return this.repository.update(id, data);
|
||||
}
|
||||
if (isBackward) {
|
||||
seekQuery.orderBy("task.priority", "ASC").addOrderBy(createdAtExpr, "DESC").addOrderBy("task.id", "DESC");
|
||||
} else {
|
||||
seekQuery.orderBy("task.priority", "DESC").addOrderBy(createdAtExpr, "ASC").addOrderBy("task.id", "ASC");
|
||||
}
|
||||
|
||||
delete(id: string) {
|
||||
return this.repository.delete(id);
|
||||
if (pagination.cursor) {
|
||||
const { priority: cursorPriority, createdAt: cursorCreatedAt, id: cursorId } = decodeCursor(pagination.cursor);
|
||||
const priorityOp = isBackward ? ">" : "<";
|
||||
const seekOp = isBackward ? "<" : ">";
|
||||
|
||||
seekQuery.andWhere(
|
||||
`(task.priority ${priorityOp} :cursorPriority
|
||||
OR (task.priority = :cursorPriority AND ${createdAtExpr} ${seekOp} :cursorCreatedAt)
|
||||
OR (task.priority = :cursorPriority AND ${createdAtExpr} = :cursorCreatedAt AND task.id ${seekOp} :cursorId))`,
|
||||
{ cursorPriority, cursorCreatedAt, cursorId },
|
||||
);
|
||||
}
|
||||
|
||||
const seekRows = await seekQuery.getMany();
|
||||
const hasMore = seekRows.length > limit;
|
||||
if (hasMore) seekRows.pop();
|
||||
|
||||
// backward queries fetch in reverse order to seek from the cursor, so flip back to
|
||||
// the normal display order before returning
|
||||
const orderedRows = isBackward ? seekRows.reverse() : seekRows;
|
||||
const ids = orderedRows.map((row) => row.id);
|
||||
|
||||
if (!ids.length) return { items: [], hasMore };
|
||||
|
||||
// Detail query: hydrates exactly the ids picked above with the relations/counts the
|
||||
// response needs. IN (:...ids) doesn't preserve order, so re-order to match seekQuery.
|
||||
const detailRows = await this.createQueryBuilder("task")
|
||||
.leftJoinAndSelect("task.remarks", "remark")
|
||||
.leftJoinAndSelect("task.ticket", "ticket")
|
||||
.leftJoinAndSelect("task.users", "user")
|
||||
.loadRelationCountAndMap("task.attachmentCount", "task.attachments")
|
||||
.loadRelationCountAndMap("task.checkListItemCount", "task.checkListItems")
|
||||
.loadRelationCountAndMap("task.completedCheckListItemCount", "task.checkListItems", "completedCheckListItems", (qb) =>
|
||||
qb.where("completedCheckListItems.isDone = :isDone", { isDone: true }),
|
||||
)
|
||||
.select([
|
||||
"task",
|
||||
"remark.id",
|
||||
"remark.title",
|
||||
"remark.color",
|
||||
"user.id",
|
||||
"user.firstName",
|
||||
"user.lastName",
|
||||
"user.profilePic",
|
||||
"ticket.id",
|
||||
"ticket.subject",
|
||||
"ticket.numericId",
|
||||
"ticket.status",
|
||||
])
|
||||
.where("task.id IN (:...ids)", { ids })
|
||||
.getMany();
|
||||
|
||||
const timeSums = await this.manager
|
||||
.createQueryBuilder(TMTime, "time")
|
||||
.select("time.taskId", "taskId")
|
||||
.addSelect("COALESCE(SUM(time.duration), 0)", "totalTime")
|
||||
.where("time.taskId IN (:...ids)", { ids })
|
||||
.groupBy("time.taskId")
|
||||
.getRawMany<{ taskId: string; totalTime: string }>();
|
||||
|
||||
const timeByTaskId = new Map(timeSums.map((row) => [row.taskId, Number(row.totalTime)]));
|
||||
|
||||
const detailById = new Map(
|
||||
detailRows.map((task) => {
|
||||
task.time = timeByTaskId.get(task.id) ?? 0;
|
||||
return [task.id, task];
|
||||
}),
|
||||
);
|
||||
const items = ids.map((id) => detailById.get(id)).filter((task): task is TMTask => task !== undefined);
|
||||
|
||||
return { items, hasMore };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { InjectRepository } from "@nestjs/typeorm";
|
||||
import { Repository } from "typeorm";
|
||||
import { TMTime } from "../entities/time.entity";
|
||||
|
||||
@Injectable()
|
||||
export class TimeRepository extends Repository<TMTime> {
|
||||
constructor(@InjectRepository(TMTime) timeRepository: Repository<TMTime>) {
|
||||
super(timeRepository.target, timeRepository.manager, timeRepository.queryRunner);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { InjectRepository } from "@nestjs/typeorm";
|
||||
import { Repository } from "typeorm";
|
||||
import { TMWorkspace } from "../entities/workspace.entity";
|
||||
import { Repository } from 'typeorm';
|
||||
import { TMWorkspace } from '../entities/workspace.entity';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceRepository extends Repository<TMWorkspace> {
|
||||
@@ -9,10 +9,23 @@ export class WorkspaceRepository extends Repository<TMWorkspace> {
|
||||
super(workspaceRepository.target, workspaceRepository.manager, workspaceRepository.queryRunner);
|
||||
}
|
||||
|
||||
async findOneWorkSpace(workspaceId: string): Promise<TMWorkspace | null> {
|
||||
return this.createQueryBuilder("workspace")
|
||||
.leftJoinAndSelect('workspace.users', 'user')
|
||||
.select([
|
||||
'workspace',
|
||||
'user.id',
|
||||
'user.firstName',
|
||||
'user.lastName',
|
||||
'user.profilePic',
|
||||
]).where('workspace.id = :workspaceId', {workspaceId})
|
||||
.getOne();
|
||||
}
|
||||
|
||||
async findByUser(userId: string): Promise<TMWorkspace[]> {
|
||||
return this.createQueryBuilder("workspace")
|
||||
.innerJoin("workspace.users", "users")
|
||||
.where("user.id = :userId", { userId })
|
||||
.where("users.id = :userId", { userId })
|
||||
.getMany();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,13 @@ import { TMTask } from "./entities/task.entity";
|
||||
import { TMRemark } from "./entities/remark.entity";
|
||||
import { TMAttachment } from "./entities/attachment.entity";
|
||||
import { TMCheckListItem } from "./entities/check-list-item.entity";
|
||||
import { TMTime } from "./entities/time.entity";
|
||||
import { TMTaskComment } from "./entities/task-comment.entity";
|
||||
import { WorkspaceRepository } from "./repositories/workspace.repository";
|
||||
import { WorkspaceController } from "./controllers/workspace.controller";
|
||||
import { WorkspaceService } from "./providers/workspace.service";
|
||||
import { User } from "../users/entities/user.entity";
|
||||
import { Ticket } from "../tickets/entities/ticket.entity";
|
||||
import { ProjectController } from "./controllers/project.controller";
|
||||
import { ProjectRepository } from "./repositories/project.repository";
|
||||
import { ProjectService } from "./providers/project.service";
|
||||
@@ -30,10 +33,16 @@ import { RemarkService } from "./providers/remark.service";
|
||||
import { AttachmentController } from "./controllers/attachment.controller";
|
||||
import { AttachmentRepository } from "./repositories/attachment.repository";
|
||||
import { AttachmentService } from "./providers/attachment.service";
|
||||
import { TimeController } from "./controllers/time.controller";
|
||||
import { TimeRepository } from "./repositories/time.repository";
|
||||
import { TimeService } from "./providers/time.service";
|
||||
import { TaskCommentController } from "./controllers/task-comment.controller";
|
||||
import { TaskCommentRepository } from "./repositories/task-comment.repository";
|
||||
import { TaskCommentService } from "./providers/task-comment.service";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([TMWorkspace, TMProject, TMTaskPhase, TMTask, TMRemark, TMAttachment, TMCheckListItem, User]),
|
||||
TypeOrmModule.forFeature([TMWorkspace, TMProject, TMTaskPhase, TMTask, TMRemark, TMAttachment, TMCheckListItem, TMTime, TMTaskComment, User, Ticket]),
|
||||
],
|
||||
controllers: [
|
||||
WorkspaceController,
|
||||
@@ -42,7 +51,9 @@ import { AttachmentService } from "./providers/attachment.service";
|
||||
TaskController,
|
||||
CheckListItemController,
|
||||
RemarkController,
|
||||
AttachmentController
|
||||
AttachmentController,
|
||||
TimeController,
|
||||
TaskCommentController,
|
||||
],
|
||||
providers: [
|
||||
WorkspaceRepository,
|
||||
@@ -58,7 +69,11 @@ import { AttachmentService } from "./providers/attachment.service";
|
||||
RemarkRepository,
|
||||
RemarkService,
|
||||
AttachmentRepository,
|
||||
AttachmentService
|
||||
]
|
||||
AttachmentService,
|
||||
TimeRepository,
|
||||
TimeService,
|
||||
TaskCommentRepository,
|
||||
TaskCommentService,
|
||||
],
|
||||
})
|
||||
export class TaskManagerModule {}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsNotEmpty, IsUUID } from "class-validator";
|
||||
|
||||
import { TicketMessageEnum } from "../../../common/enums/message.enum";
|
||||
|
||||
export class CreateTicketMasterDto {
|
||||
@IsNotEmpty({ message: TicketMessageEnum.MASTER_USER_ID_REQUIRED })
|
||||
@IsUUID("4", { message: TicketMessageEnum.MASTER_USER_ID_SHOULD_BE_UUID })
|
||||
@ApiProperty({ description: "User ID of the ticket master", example: "123e4567-e89b-12d3-a456-426614174000" })
|
||||
userId: string;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsNotEmpty, IsUUID } from "class-validator";
|
||||
|
||||
import { TicketMessageEnum } from "../../../common/enums/message.enum";
|
||||
|
||||
export class UpdateTicketMasterDto {
|
||||
@IsNotEmpty({ message: TicketMessageEnum.MASTER_USER_ID_REQUIRED })
|
||||
@IsUUID("4", { message: TicketMessageEnum.MASTER_USER_ID_SHOULD_BE_UUID })
|
||||
@ApiProperty({ description: "User ID of the ticket master", example: "123e4567-e89b-12d3-a456-426614174000" })
|
||||
userId: string;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Entity, JoinColumn, ManyToOne, Unique } from "typeorm";
|
||||
|
||||
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||
import { User } from "../../users/entities/user.entity";
|
||||
|
||||
@Entity("ticket_master")
|
||||
@Unique(["user"])
|
||||
export class TicketMaster extends BaseEntity {
|
||||
@ManyToOne(() => User, { nullable: false, onDelete: "CASCADE" })
|
||||
@JoinColumn()
|
||||
user: User;
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import { User } from "../../users/entities/user.entity";
|
||||
import { AdminsService } from "../../users/providers/admins.service";
|
||||
import { UsersService } from "../../users/providers/users.service";
|
||||
import { CreateTicketCategoryDto } from "../DTO/create-ticket-category.dto";
|
||||
import { CreateTicketMasterDto } from "../DTO/create-ticket-master.dto";
|
||||
import { CreateTicketMessageDto } from "../DTO/create-ticket-message.dto";
|
||||
import { CreateTicketByAdminDto, CreateTicketDto } from "../DTO/create-ticket.dto";
|
||||
import { ReferTicketDto } from "../DTO/refer-ticket.dto";
|
||||
@@ -30,9 +31,11 @@ import { TicketMessageAttachment } from "../entities/ticket-message-attachment";
|
||||
import { TicketMessage } from "../entities/ticket-message.entity";
|
||||
import { Ticket } from "../entities/ticket.entity";
|
||||
import { TicketStatus } from "../enums/ticket-status.enum";
|
||||
import { TicketMasterRepository } from "../repositories/ticket-master.repository";
|
||||
import { TicketCategoryRepository } from "../repositories/tickets-category.repository";
|
||||
import { TicketMessagesRepository } from "../repositories/tickets-message.repository";
|
||||
import { TicketsRepository } from "../repositories/tickets.repository";
|
||||
import { PermissionEnum } from "../../users/enums/permission.enum";
|
||||
|
||||
@Injectable()
|
||||
export class TicketsService implements OnModuleInit {
|
||||
@@ -42,6 +45,7 @@ export class TicketsService implements OnModuleInit {
|
||||
@InjectQueue(TICKET.QUEUE_NAME) private readonly ticketQueue: Queue,
|
||||
private readonly ticketsRepository: TicketsRepository,
|
||||
private readonly ticketsCategoryRepository: TicketCategoryRepository,
|
||||
private readonly ticketMasterRepository: TicketMasterRepository,
|
||||
private readonly ticketMessagesRepository: TicketMessagesRepository,
|
||||
private readonly usersService: UsersService,
|
||||
private readonly adminsService: AdminsService,
|
||||
@@ -166,6 +170,79 @@ export class TicketsService implements OnModuleInit {
|
||||
};
|
||||
}
|
||||
|
||||
//******************************** */
|
||||
// ticket masters
|
||||
//******************************** */
|
||||
|
||||
async createTicketMaster(createDto: CreateTicketMasterDto, userIpAndHeaders: IUserIpAndHeaders) {
|
||||
const { user } = await this.usersService.findOneById(createDto.userId);
|
||||
|
||||
const existingMaster = await this.ticketMasterRepository.findByUserId(createDto.userId);
|
||||
if (existingMaster) throw new BadRequestException(TicketMessageEnum.MASTER_EXIST);
|
||||
|
||||
const ticketMaster = this.ticketMasterRepository.create({ user });
|
||||
await this.ticketMasterRepository.save(ticketMaster);
|
||||
|
||||
await this.accessLogService.logCreate("TicketMaster", UserType.ADMIN, {
|
||||
user: { id: userIpAndHeaders.userId },
|
||||
endpoint: "/tickets/masters",
|
||||
requestId: userIpAndHeaders.headers["x-request-id"],
|
||||
ipAddress: userIpAndHeaders.ip,
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
actionDescription: `Admin created ticket master for user: ${user.id}`,
|
||||
metadata: {
|
||||
ticketMasterId: ticketMaster.id,
|
||||
masterUserId: user.id,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
message: TicketMessageEnum.MASTER_CREATED,
|
||||
ticketMaster: await this.ticketMasterRepository.findByIdWithUser(ticketMaster.id),
|
||||
};
|
||||
}
|
||||
|
||||
//******************************** */
|
||||
|
||||
async getTicketMasters() {
|
||||
const ticketMasters = await this.ticketMasterRepository.findAllWithUser();
|
||||
return { ticketMasters };
|
||||
}
|
||||
|
||||
//******************************** */
|
||||
|
||||
async getTicketMaster(paramDto: ParamDto) {
|
||||
const ticketMaster = await this.ticketMasterRepository.findByIdWithUser(paramDto.id);
|
||||
if (!ticketMaster) throw new BadRequestException(TicketMessageEnum.MASTER_NOT_FOUND);
|
||||
return { ticketMaster };
|
||||
}
|
||||
|
||||
//******************************** */
|
||||
|
||||
async deleteTicketMaster(paramDto: ParamDto, userIpAndHeaders: IUserIpAndHeaders) {
|
||||
const ticketMaster = await this.ticketMasterRepository.findByIdWithUser(paramDto.id);
|
||||
if (!ticketMaster) throw new BadRequestException(TicketMessageEnum.MASTER_NOT_FOUND);
|
||||
|
||||
await this.ticketMasterRepository.delete(paramDto.id);
|
||||
|
||||
await this.accessLogService.logDelete("TicketMaster", paramDto.id, UserType.ADMIN, {
|
||||
user: { id: userIpAndHeaders.userId },
|
||||
endpoint: "/tickets/masters",
|
||||
requestId: userIpAndHeaders.headers["x-request-id"],
|
||||
ipAddress: userIpAndHeaders.ip,
|
||||
userAgent: userIpAndHeaders.headers["user-agent"],
|
||||
actionDescription: `Admin deleted ticket master: ${paramDto.id}`,
|
||||
metadata: {
|
||||
ticketMasterId: ticketMaster.id,
|
||||
masterUserId: ticketMaster.user.id,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
message: TicketMessageEnum.MASTER_DELETED,
|
||||
};
|
||||
}
|
||||
|
||||
//******************************** */
|
||||
|
||||
private async checkTicketLimit(userId: string, queryRunner: QueryRunner): Promise<void> {
|
||||
@@ -326,10 +403,10 @@ export class TicketsService implements OnModuleInit {
|
||||
return { tickets, count, paginate: true };
|
||||
}
|
||||
//******************************** */
|
||||
async getTicketForAdmin(queryDto: SearchTicketQueryDto, adminId: string) {
|
||||
const isSuperAdmin = await this.usersService.isSuperAdmin(adminId);
|
||||
async getTicketForAdmin(queryDto: SearchTicketQueryDto, adminPermissions: PermissionEnum[],adminId: string) {
|
||||
const canSeeAllTickets = adminPermissions.includes(PermissionEnum.TICKETS_READ);
|
||||
|
||||
const [tickets, count] = await this.ticketsRepository.findTicketsListForAdmin(queryDto, adminId, isSuperAdmin);
|
||||
const [tickets, count] = await this.ticketsRepository.findTicketsListForAdmin(queryDto, adminId, canSeeAllTickets);
|
||||
|
||||
return { tickets, count, paginate: true };
|
||||
}
|
||||
@@ -538,7 +615,7 @@ export class TicketsService implements OnModuleInit {
|
||||
|
||||
//***************************** */
|
||||
private async assignTicketAndNotify(ticket: Ticket, ticketCategory: TicketCategory, user: User, queryRunner: QueryRunner) {
|
||||
const assignedUser = await this.referralService.assignAdmin(ticketCategory.group);
|
||||
const assignedUser = (await this.assignFromTicketMasters()) ?? (await this.referralService.assignAdmin(ticketCategory.group));
|
||||
//
|
||||
ticket.assignedTo = assignedUser;
|
||||
await queryRunner.manager.save(Ticket, ticket);
|
||||
@@ -561,6 +638,22 @@ export class TicketsService implements OnModuleInit {
|
||||
});
|
||||
await this.notifySuperAdminsForTicket(queryRunner, ticket);
|
||||
}
|
||||
|
||||
//***************************** */
|
||||
|
||||
private async assignFromTicketMasters(): Promise<User | null> {
|
||||
const masters = await this.ticketMasterRepository.findAllWithUser();
|
||||
if (!masters.length) return null;
|
||||
|
||||
const mastersWithLoad = await Promise.all(
|
||||
masters.map(async (master) => ({
|
||||
user: master.user,
|
||||
assignedCount: await this.ticketsRepository.count({ where: { assignedTo: { id: master.user.id } } }),
|
||||
})),
|
||||
);
|
||||
|
||||
return mastersWithLoad.reduce((prev, curr) => (prev.assignedCount < curr.assignedCount ? prev : curr)).user;
|
||||
}
|
||||
//***************************** */
|
||||
|
||||
private async findTicket(queryRunner: QueryRunner, ticketId: string, userId: string, isAdmin: boolean): Promise<Ticket> {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { InjectRepository } from "@nestjs/typeorm";
|
||||
import { Repository } from "typeorm";
|
||||
|
||||
import { TicketMaster } from "../entities/ticket-master.entity";
|
||||
|
||||
@Injectable()
|
||||
export class TicketMasterRepository extends Repository<TicketMaster> {
|
||||
constructor(@InjectRepository(TicketMaster) ticketMasterRepository: Repository<TicketMaster>) {
|
||||
super(ticketMasterRepository.target, ticketMasterRepository.manager, ticketMasterRepository.queryRunner);
|
||||
}
|
||||
|
||||
findAllWithUser(): Promise<TicketMaster[]> {
|
||||
return this.find({
|
||||
relations: { user: true },
|
||||
order: { createdAt: "ASC" },
|
||||
});
|
||||
}
|
||||
|
||||
findByIdWithUser(id: string): Promise<TicketMaster | null> {
|
||||
return this.findOne({
|
||||
where: { id },
|
||||
relations: { user: true },
|
||||
});
|
||||
}
|
||||
|
||||
findByUserId(userId: string): Promise<TicketMaster | null> {
|
||||
return this.findOne({
|
||||
where: { user: { id: userId } },
|
||||
relations: { user: true },
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Body, Controller, Get, HttpCode, HttpStatus, Param, Patch, Post, Query } from "@nestjs/common";
|
||||
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Patch, Post, Query } from "@nestjs/common";
|
||||
import { ApiOperation, ApiTags } from "@nestjs/swagger";
|
||||
|
||||
import { CreateTicketCategoryDto } from "./DTO/create-ticket-category.dto";
|
||||
import { CreateTicketMasterDto } from "./DTO/create-ticket-master.dto";
|
||||
import { CreateTicketMessageDto } from "./DTO/create-ticket-message.dto";
|
||||
import { CreateTicketByAdminDto, CreateTicketDto } from "./DTO/create-ticket.dto";
|
||||
import { ReferTicketDto } from "./DTO/refer-ticket.dto";
|
||||
@@ -15,6 +16,8 @@ import { PermissionsDec } from "../../common/decorators/permission.decorator";
|
||||
import { IUserIpAndHeaders, UserDec, UserIpAndHeaders } from "../../common/decorators/user.decorator";
|
||||
import { ParamDto } from "../../common/DTO/param.dto";
|
||||
import { PermissionEnum } from "../users/enums/permission.enum";
|
||||
import { ITokenPayload } from "../auth/interfaces/IToken-payload";
|
||||
|
||||
@Controller("tickets")
|
||||
@ApiTags("Tickets")
|
||||
@AuthGuards()
|
||||
@@ -24,14 +27,14 @@ export class TicketsController {
|
||||
//----------------- ticket categories -------------------
|
||||
|
||||
@ApiOperation({ summary: "Create ticket category => admin route" })
|
||||
@PermissionsDec(PermissionEnum.TICKETS)
|
||||
@PermissionsDec(PermissionEnum.TICKET_CATEGORIES_CREATE)
|
||||
@Post("category")
|
||||
createTicketCategory(@Body() createDto: CreateTicketCategoryDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.ticketsService.createTicketCategory(createDto, userIpAndHeaders);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "Update ticket category => admin route" })
|
||||
@PermissionsDec(PermissionEnum.TICKETS)
|
||||
@PermissionsDec(PermissionEnum.TICKET_CATEGORIES_UPDATE)
|
||||
@Patch("category/:id")
|
||||
updateCategory(
|
||||
@Param() paramDto: ParamDto,
|
||||
@@ -49,7 +52,7 @@ export class TicketsController {
|
||||
|
||||
@ApiOperation({ summary: "Get ticket categories list => admin route" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.TICKETS)
|
||||
@PermissionsDec(PermissionEnum.TICKET_CATEGORIES_READ)
|
||||
@Get("categories-list")
|
||||
getCategoriesList(@Query() queryDto: SearchTicketCategoryDto) {
|
||||
return this.ticketsService.getCategoriesList(queryDto);
|
||||
@@ -58,12 +61,46 @@ export class TicketsController {
|
||||
@ApiOperation({ summary: "toggle status of categories => admin route" })
|
||||
@AdminRoute()
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@PermissionsDec(PermissionEnum.TICKETS)
|
||||
@PermissionsDec(PermissionEnum.TICKET_CATEGORIES_UPDATE)
|
||||
@Post("category/toggle-status/:id")
|
||||
toggleCategoryStatus(@Param() paramDto: ParamDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.ticketsService.toggleCategoryStatus(paramDto, userIpAndHeaders);
|
||||
}
|
||||
|
||||
//----------------- ticket masters -------------------
|
||||
|
||||
@ApiOperation({ summary: "Create ticket master => admin route" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.TICKET_MASTERS_CREATE)
|
||||
@Post("masters")
|
||||
createTicketMaster(@Body() createDto: CreateTicketMasterDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.ticketsService.createTicketMaster(createDto, userIpAndHeaders);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "Get all ticket masters => admin route" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.TICKET_MASTERS_READ)
|
||||
@Get("masters")
|
||||
getTicketMasters() {
|
||||
return this.ticketsService.getTicketMasters();
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "Get ticket master by id => admin route" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.TICKET_MASTERS_READ)
|
||||
@Get("masters/:id")
|
||||
getTicketMaster(@Param() paramDto: ParamDto) {
|
||||
return this.ticketsService.getTicketMaster(paramDto);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "Delete ticket master => admin route" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.TICKET_MASTERS_DELETE)
|
||||
@Delete("masters/:id")
|
||||
deleteTicketMaster(@Param() paramDto: ParamDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.ticketsService.deleteTicketMaster(paramDto, userIpAndHeaders);
|
||||
}
|
||||
|
||||
//----------------- ticket and ticket messages -------------------
|
||||
|
||||
@ApiOperation({ summary: "create ticket ==> user route" })
|
||||
@@ -74,7 +111,7 @@ export class TicketsController {
|
||||
|
||||
@ApiOperation({ summary: "create ticket by admin" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.TICKETS)
|
||||
@PermissionsDec(PermissionEnum.TICKETS_CREATE)
|
||||
@Post("admin")
|
||||
createTicketByAdmin(@Body() createDto: CreateTicketByAdminDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.ticketsService.createTicketByAdmin(createDto, userIpAndHeaders);
|
||||
@@ -88,10 +125,9 @@ export class TicketsController {
|
||||
|
||||
@ApiOperation({ summary: "Get all tickets ==> admin route" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.TICKETS)
|
||||
@Get("admin")
|
||||
getAllTickets(@Query() queryDto: SearchTicketQueryDto, @UserDec("id") adminId: string) {
|
||||
return this.ticketsService.getTicketForAdmin(queryDto, adminId);
|
||||
getAllTickets(@Query() queryDto: SearchTicketQueryDto, @UserDec() admin: ITokenPayload) {
|
||||
return this.ticketsService.getTicketForAdmin(queryDto, admin.permissions, admin.id);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "create ticket messages" })
|
||||
@@ -124,7 +160,7 @@ export class TicketsController {
|
||||
|
||||
@ApiOperation({ summary: "open ticket by admin" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.TICKETS)
|
||||
@PermissionsDec(PermissionEnum.TICKETS_UPDATE)
|
||||
@Post(":id/open")
|
||||
openTicketByAdmin(@Param() paramDto: ParamDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.ticketsService.openTicketByAdmin(paramDto.id, userIpAndHeaders);
|
||||
@@ -132,7 +168,7 @@ export class TicketsController {
|
||||
|
||||
@ApiOperation({ summary: "referer ticket ==> admin route" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.TICKETS)
|
||||
@PermissionsDec(PermissionEnum.TICKETS_UPDATE)
|
||||
@Post(":id/refer")
|
||||
referTicket(@Param() paramDto: ParamDto, @Body() referDto: ReferTicketDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
|
||||
return this.ticketsService.referTicket(paramDto.id, referDto, userIpAndHeaders);
|
||||
|
||||
@@ -4,10 +4,12 @@ import { TypeOrmModule } from "@nestjs/typeorm";
|
||||
|
||||
import { TICKET } from "./constant";
|
||||
import { TicketCategory } from "./entities/ticket-category.entity";
|
||||
import { TicketMaster } from "./entities/ticket-master.entity";
|
||||
import { TicketMessage } from "./entities/ticket-message.entity";
|
||||
import { Ticket } from "./entities/ticket.entity";
|
||||
import { ReferralStrategyFactory } from "./providers/referral-strategy.factory";
|
||||
import { TicketsService } from "./providers/tickets.service";
|
||||
import { TicketMasterRepository } from "./repositories/ticket-master.repository";
|
||||
import { TicketCategoryRepository } from "./repositories/tickets-category.repository";
|
||||
import { TicketMessagesRepository } from "./repositories/tickets-message.repository";
|
||||
import { TicketsRepository } from "./repositories/tickets.repository";
|
||||
@@ -27,7 +29,7 @@ import { LoggerModule } from "../logger/logger.module";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Ticket, TicketCategory, TicketMessageAttachment, TicketMessage]),
|
||||
TypeOrmModule.forFeature([Ticket, TicketCategory, TicketMaster, TicketMessageAttachment, TicketMessage]),
|
||||
AccessLogsModule,
|
||||
BullModule.registerQueue({ name: TICKET.QUEUE_NAME }),
|
||||
UsersModule,
|
||||
@@ -48,6 +50,7 @@ import { LoggerModule } from "../logger/logger.module";
|
||||
TicketsService,
|
||||
TicketsRepository,
|
||||
TicketCategoryRepository,
|
||||
TicketMasterRepository,
|
||||
TicketMessagesRepository,
|
||||
TicketMessageAttachmentsRepository,
|
||||
TicketProcessor,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import { IsEmail, IsMobilePhone, IsNotEmpty, IsOptional, IsString, IsUUID, IsUrl, Length, MinLength } from "class-validator";
|
||||
import { ArrayNotEmpty, IsArray, IsEmail, IsMobilePhone, IsNotEmpty, IsOptional, IsString, IsUUID, IsUrl, Length, MinLength } from "class-validator";
|
||||
|
||||
import { AdminMessage, AuthMessage } from "../../../common/enums/message.enum";
|
||||
|
||||
@@ -22,9 +22,11 @@ export class CreateAdminDto {
|
||||
phone: string;
|
||||
|
||||
@IsNotEmpty({ message: AdminMessage.ROLE_REQUIRED })
|
||||
@IsUUID("4", { message: AdminMessage.ROLE_ID_SHOULD_BE_UUID })
|
||||
@ApiProperty({ description: "Role id", example: "123e4567-e89b-12d3-a456-426614174000" })
|
||||
roleId: string;
|
||||
@IsArray({ message: AdminMessage.ROLE_REQUIRED })
|
||||
@ArrayNotEmpty({ message: AdminMessage.ROLE_REQUIRED })
|
||||
@IsUUID("4", { each: true, message: AdminMessage.ROLE_ID_SHOULD_BE_UUID })
|
||||
@ApiProperty({ description: "Role ids", example: ["123e4567-e89b-12d3-a456-426614174000"], type: [String] })
|
||||
roleIds: string[];
|
||||
|
||||
@IsNotEmpty({ message: AuthMessage.FIRST_NAME_NOT_EMPTY })
|
||||
@IsString({ message: AuthMessage.FIRST_NAME_NOT_EMPTY })
|
||||
|
||||
@@ -12,6 +12,6 @@ export class SearchAdminQueryDto extends PaginationDto {
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(PermissionEnum)
|
||||
@ApiPropertyOptional({ description: "permission", example: PermissionEnum.SERVICES })
|
||||
@ApiPropertyOptional({ description: "permission", example: PermissionEnum.SERVICES_READ })
|
||||
permission?: PermissionEnum;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
import { PermissionEnum } from "../enums/permission.enum";
|
||||
import { PermissionGroupEnum } from "../enums/permission-group.enum";
|
||||
|
||||
export type PermissionMeta = {
|
||||
title: string;
|
||||
group: PermissionGroupEnum;
|
||||
};
|
||||
|
||||
export const permissionMeta: Record<PermissionEnum, PermissionMeta> = {
|
||||
[PermissionEnum.SERVICES_CREATE]: { title: "ایجاد سرویسها", group: PermissionGroupEnum.SERVICES },
|
||||
[PermissionEnum.SERVICES_READ]: { title: "مشاهده سرویسها", group: PermissionGroupEnum.SERVICES },
|
||||
[PermissionEnum.SERVICES_UPDATE]: { title: "ویرایش سرویسها", group: PermissionGroupEnum.SERVICES },
|
||||
[PermissionEnum.SERVICES_DELETE]: { title: "حذف سرویسها", group: PermissionGroupEnum.SERVICES },
|
||||
|
||||
[PermissionEnum.CUSTOMERS_CREATE]: { title: "ایجاد مشتریان", group: PermissionGroupEnum.CUSTOMERS },
|
||||
[PermissionEnum.CUSTOMERS_READ]: { title: "مشاهده مشتریان", group: PermissionGroupEnum.CUSTOMERS },
|
||||
[PermissionEnum.CUSTOMERS_UPDATE]: { title: "ویرایش مشتریان", group: PermissionGroupEnum.CUSTOMERS },
|
||||
[PermissionEnum.CUSTOMERS_DELETE]: { title: "حذف مشتریان", group: PermissionGroupEnum.CUSTOMERS },
|
||||
|
||||
[PermissionEnum.AGENTS_CREATE]: { title: "ایجاد نمایندگان", group: PermissionGroupEnum.REPRESENTATIVES },
|
||||
[PermissionEnum.AGENTS_READ]: { title: "مشاهده نمایندگان", group: PermissionGroupEnum.REPRESENTATIVES },
|
||||
[PermissionEnum.AGENTS_UPDATE]: { title: "ویرایش نمایندگان", group: PermissionGroupEnum.REPRESENTATIVES },
|
||||
[PermissionEnum.AGENTS_DELETE]: { title: "حذف نمایندگان", group: PermissionGroupEnum.REPRESENTATIVES },
|
||||
|
||||
[PermissionEnum.DEVELOPERS_CREATE]: { title: "ایجاد توسعهدهندگان", group: PermissionGroupEnum.USERS },
|
||||
[PermissionEnum.DEVELOPERS_READ]: { title: "مشاهده توسعهدهندگان", group: PermissionGroupEnum.USERS },
|
||||
[PermissionEnum.DEVELOPERS_UPDATE]: { title: "ویرایش توسعهدهندگان", group: PermissionGroupEnum.USERS },
|
||||
[PermissionEnum.DEVELOPERS_DELETE]: { title: "حذف توسعهدهندگان", group: PermissionGroupEnum.USERS },
|
||||
|
||||
[PermissionEnum.INVOICES_CREATE]: { title: "ایجاد صورتحسابها", group: PermissionGroupEnum.FINANCIAL },
|
||||
[PermissionEnum.INVOICES_READ]: { title: "مشاهده صورتحسابها", group: PermissionGroupEnum.FINANCIAL },
|
||||
[PermissionEnum.INVOICES_UPDATE]: { title: "ویرایش صورتحسابها", group: PermissionGroupEnum.FINANCIAL },
|
||||
[PermissionEnum.INVOICES_DELETE]: { title: "حذف صورتحسابها", group: PermissionGroupEnum.FINANCIAL },
|
||||
|
||||
[PermissionEnum.TRANSACTIONS_CREATE]: { title: "ایجاد تراکنشها", group: PermissionGroupEnum.FINANCIAL },
|
||||
[PermissionEnum.TRANSACTIONS_READ]: { title: "مشاهده تراکنشها", group: PermissionGroupEnum.FINANCIAL },
|
||||
[PermissionEnum.TRANSACTIONS_UPDATE]: { title: "ویرایش تراکنشها", group: PermissionGroupEnum.FINANCIAL },
|
||||
[PermissionEnum.TRANSACTIONS_DELETE]: { title: "حذف تراکنشها", group: PermissionGroupEnum.FINANCIAL },
|
||||
|
||||
[PermissionEnum.DISCOUNTS_CREATE]: { title: "ایجاد تخفیفها", group: PermissionGroupEnum.FINANCIAL },
|
||||
[PermissionEnum.DISCOUNTS_READ]: { title: "مشاهده تخفیفها", group: PermissionGroupEnum.FINANCIAL },
|
||||
[PermissionEnum.DISCOUNTS_UPDATE]: { title: "ویرایش تخفیفها", group: PermissionGroupEnum.FINANCIAL },
|
||||
[PermissionEnum.DISCOUNTS_DELETE]: { title: "حذف تخفیفها", group: PermissionGroupEnum.FINANCIAL },
|
||||
|
||||
[PermissionEnum.ADMINS_CREATE]: { title: "ایجاد ادمینها", group: PermissionGroupEnum.USERS },
|
||||
[PermissionEnum.ADMINS_READ]: { title: "مشاهده ادمینها", group: PermissionGroupEnum.USERS },
|
||||
[PermissionEnum.ADMINS_UPDATE]: { title: "ویرایش ادمینها", group: PermissionGroupEnum.USERS },
|
||||
[PermissionEnum.ADMINS_DELETE]: { title: "حذف ادمینها", group: PermissionGroupEnum.USERS },
|
||||
|
||||
[PermissionEnum.TICKETS_CREATE]: { title: "ایجاد تیکتها", group: PermissionGroupEnum.SUPPORT },
|
||||
[PermissionEnum.TICKETS_READ]: { title: "مشاهده تیکتها", group: PermissionGroupEnum.SUPPORT },
|
||||
[PermissionEnum.TICKETS_UPDATE]: { title: "ویرایش تیکتها", group: PermissionGroupEnum.SUPPORT },
|
||||
[PermissionEnum.TICKETS_DELETE]: { title: "حذف تیکتها", group: PermissionGroupEnum.SUPPORT },
|
||||
|
||||
[PermissionEnum.TICKET_CATEGORIES_CREATE]: { title: "ایجاد دستهبندی تیکت", group: PermissionGroupEnum.SUPPORT },
|
||||
[PermissionEnum.TICKET_CATEGORIES_READ]: { title: "مشاهده دستهبندی تیکت", group: PermissionGroupEnum.SUPPORT },
|
||||
[PermissionEnum.TICKET_CATEGORIES_UPDATE]: { title: "ویرایش دستهبندی تیکت", group: PermissionGroupEnum.SUPPORT },
|
||||
[PermissionEnum.TICKET_CATEGORIES_DELETE]: { title: "حذف دستهبندی تیکت", group: PermissionGroupEnum.SUPPORT },
|
||||
|
||||
[PermissionEnum.TICKET_MASTERS_CREATE]: { title: "ایجاد مستر تیکت", group: PermissionGroupEnum.SUPPORT },
|
||||
[PermissionEnum.TICKET_MASTERS_READ]: { title: "مشاهده مستر تیکت", group: PermissionGroupEnum.SUPPORT },
|
||||
[PermissionEnum.TICKET_MASTERS_UPDATE]: { title: "ویرایش مستر تیکت", group: PermissionGroupEnum.SUPPORT },
|
||||
[PermissionEnum.TICKET_MASTERS_DELETE]: { title: "حذف مستر تیکت", group: PermissionGroupEnum.SUPPORT },
|
||||
|
||||
[PermissionEnum.CRITICISMS_CREATE]: { title: "ایجاد انتقادات", group: PermissionGroupEnum.OTHER },
|
||||
[PermissionEnum.CRITICISMS_READ]: { title: "مشاهده انتقادات", group: PermissionGroupEnum.OTHER },
|
||||
[PermissionEnum.CRITICISMS_UPDATE]: { title: "ویرایش انتقادات", group: PermissionGroupEnum.OTHER },
|
||||
[PermissionEnum.CRITICISMS_DELETE]: { title: "حذف انتقادات", group: PermissionGroupEnum.OTHER },
|
||||
|
||||
[PermissionEnum.CONTACTS_US_CREATE]: { title: "ایجاد تماس با ما", group: PermissionGroupEnum.OTHER },
|
||||
[PermissionEnum.CONTACTS_US_READ]: { title: "مشاهده تماس با ما", group: PermissionGroupEnum.OTHER },
|
||||
[PermissionEnum.CONTACTS_US_UPDATE]: { title: "ویرایش تماس با ما", group: PermissionGroupEnum.OTHER },
|
||||
[PermissionEnum.CONTACTS_US_DELETE]: { title: "حذف تماس با ما", group: PermissionGroupEnum.OTHER },
|
||||
|
||||
[PermissionEnum.ADVERTISEMENTS_CREATE]: { title: "ایجاد تبلیغات", group: PermissionGroupEnum.CONTENT },
|
||||
[PermissionEnum.ADVERTISEMENTS_READ]: { title: "مشاهده تبلیغات", group: PermissionGroupEnum.CONTENT },
|
||||
[PermissionEnum.ADVERTISEMENTS_UPDATE]: { title: "ویرایش تبلیغات", group: PermissionGroupEnum.CONTENT },
|
||||
[PermissionEnum.ADVERTISEMENTS_DELETE]: { title: "حذف تبلیغات", group: PermissionGroupEnum.CONTENT },
|
||||
|
||||
[PermissionEnum.ANNOUNCEMENTS_CREATE]: { title: "ایجاد اطلاعیهها", group: PermissionGroupEnum.CONTENT },
|
||||
[PermissionEnum.ANNOUNCEMENTS_READ]: { title: "مشاهده اطلاعیهها", group: PermissionGroupEnum.CONTENT },
|
||||
[PermissionEnum.ANNOUNCEMENTS_UPDATE]: { title: "ویرایش اطلاعیهها", group: PermissionGroupEnum.CONTENT },
|
||||
[PermissionEnum.ANNOUNCEMENTS_DELETE]: { title: "حذف اطلاعیهها", group: PermissionGroupEnum.CONTENT },
|
||||
|
||||
[PermissionEnum.BLOGS_CREATE]: { title: "ایجاد بلاگها", group: PermissionGroupEnum.CONTENT },
|
||||
[PermissionEnum.BLOGS_READ]: { title: "مشاهده بلاگها", group: PermissionGroupEnum.CONTENT },
|
||||
[PermissionEnum.BLOGS_UPDATE]: { title: "ویرایش بلاگها", group: PermissionGroupEnum.CONTENT },
|
||||
[PermissionEnum.BLOGS_DELETE]: { title: "حذف بلاگها", group: PermissionGroupEnum.CONTENT },
|
||||
|
||||
[PermissionEnum.BLOG_CATEGORIES_CREATE]: { title: "ایجاد دستهبندی بلاگ", group: PermissionGroupEnum.CONTENT },
|
||||
[PermissionEnum.BLOG_CATEGORIES_READ]: { title: "مشاهده دستهبندی بلاگ", group: PermissionGroupEnum.CONTENT },
|
||||
[PermissionEnum.BLOG_CATEGORIES_UPDATE]: { title: "ویرایش دستهبندی بلاگ", group: PermissionGroupEnum.CONTENT },
|
||||
[PermissionEnum.BLOG_CATEGORIES_DELETE]: { title: "حذف دستهبندی بلاگ", group: PermissionGroupEnum.CONTENT },
|
||||
|
||||
[PermissionEnum.BLOG_COMMENTS_CREATE]: { title: "ایجاد نظرات بلاگ", group: PermissionGroupEnum.CONTENT },
|
||||
[PermissionEnum.BLOG_COMMENTS_READ]: { title: "مشاهده نظرات بلاگ", group: PermissionGroupEnum.CONTENT },
|
||||
[PermissionEnum.BLOG_COMMENTS_UPDATE]: { title: "ویرایش نظرات بلاگ", group: PermissionGroupEnum.CONTENT },
|
||||
[PermissionEnum.BLOG_COMMENTS_DELETE]: { title: "حذف نظرات بلاگ", group: PermissionGroupEnum.CONTENT },
|
||||
|
||||
[PermissionEnum.SLIDERS_CREATE]: { title: "ایجاد اسلایدر", group: PermissionGroupEnum.CONTENT },
|
||||
[PermissionEnum.SLIDERS_READ]: { title: "مشاهده اسلایدر", group: PermissionGroupEnum.CONTENT },
|
||||
[PermissionEnum.SLIDERS_UPDATE]: { title: "ویرایش اسلایدر", group: PermissionGroupEnum.CONTENT },
|
||||
[PermissionEnum.SLIDERS_DELETE]: { title: "حذف اسلایدر", group: PermissionGroupEnum.CONTENT },
|
||||
|
||||
[PermissionEnum.LEARNINGS_CREATE]: { title: "ایجاد آموزشها", group: PermissionGroupEnum.CONTENT },
|
||||
[PermissionEnum.LEARNINGS_READ]: { title: "مشاهده آموزشها", group: PermissionGroupEnum.CONTENT },
|
||||
[PermissionEnum.LEARNINGS_UPDATE]: { title: "ویرایش آموزشها", group: PermissionGroupEnum.CONTENT },
|
||||
[PermissionEnum.LEARNINGS_DELETE]: { title: "حذف آموزشها", group: PermissionGroupEnum.CONTENT },
|
||||
|
||||
[PermissionEnum.SETTINGS_CREATE]: { title: "ایجاد تنظیمات", group: PermissionGroupEnum.OTHER },
|
||||
[PermissionEnum.SETTINGS_READ]: { title: "مشاهده تنظیمات", group: PermissionGroupEnum.OTHER },
|
||||
[PermissionEnum.SETTINGS_UPDATE]: { title: "ویرایش تنظیمات", group: PermissionGroupEnum.OTHER },
|
||||
[PermissionEnum.SETTINGS_DELETE]: { title: "حذف تنظیمات", group: PermissionGroupEnum.OTHER },
|
||||
|
||||
[PermissionEnum.BANK_ACCOUNTS_CREATE]: { title: "ایجاد حسابهای بانکی", group: PermissionGroupEnum.FINANCIAL },
|
||||
[PermissionEnum.BANK_ACCOUNTS_READ]: { title: "مشاهده حسابهای بانکی", group: PermissionGroupEnum.FINANCIAL },
|
||||
[PermissionEnum.BANK_ACCOUNTS_UPDATE]: { title: "ویرایش حسابهای بانکی", group: PermissionGroupEnum.FINANCIAL },
|
||||
[PermissionEnum.BANK_ACCOUNTS_DELETE]: { title: "حذف حسابهای بانکی", group: PermissionGroupEnum.FINANCIAL },
|
||||
|
||||
[PermissionEnum.PAYMENTS_CREATE]: { title: "ایجاد پرداختها", group: PermissionGroupEnum.FINANCIAL },
|
||||
[PermissionEnum.PAYMENTS_READ]: { title: "مشاهده پرداختها", group: PermissionGroupEnum.FINANCIAL },
|
||||
[PermissionEnum.PAYMENTS_UPDATE]: { title: "ویرایش پرداختها", group: PermissionGroupEnum.FINANCIAL },
|
||||
[PermissionEnum.PAYMENTS_DELETE]: { title: "حذف پرداختها", group: PermissionGroupEnum.FINANCIAL },
|
||||
|
||||
[PermissionEnum.SUPPORT_PLAN_CREATE]: { title: "ایجاد پلن پشتیبانی", group: PermissionGroupEnum.SUPPORT },
|
||||
[PermissionEnum.SUPPORT_PLAN_READ]: { title: "مشاهده پلن پشتیبانی", group: PermissionGroupEnum.SUPPORT },
|
||||
[PermissionEnum.SUPPORT_PLAN_UPDATE]: { title: "ویرایش پلن پشتیبانی", group: PermissionGroupEnum.SUPPORT },
|
||||
[PermissionEnum.SUPPORT_PLAN_DELETE]: { title: "حذف پلن پشتیبانی", group: PermissionGroupEnum.SUPPORT },
|
||||
|
||||
[PermissionEnum.RESELLER_CREATE]: { title: "ایجاد نمایندگی فروش", group: PermissionGroupEnum.REPRESENTATIVES },
|
||||
[PermissionEnum.RESELLER_READ]: { title: "مشاهده نمایندگی فروش", group: PermissionGroupEnum.REPRESENTATIVES },
|
||||
[PermissionEnum.RESELLER_UPDATE]: { title: "ویرایش نمایندگی فروش", group: PermissionGroupEnum.REPRESENTATIVES },
|
||||
[PermissionEnum.RESELLER_DELETE]: { title: "حذف نمایندگی فروش", group: PermissionGroupEnum.REPRESENTATIVES },
|
||||
|
||||
[PermissionEnum.WORKSPACE_CREATE]: { title: "ایجاد فضای کار", group: PermissionGroupEnum.TASK_MANAGER },
|
||||
[PermissionEnum.WORKSPACE_READ]: { title: "مشاهده فضای کار", group: PermissionGroupEnum.TASK_MANAGER },
|
||||
[PermissionEnum.WORKSPACE_UPDATE]: { title: "ویرایش فضای کار", group: PermissionGroupEnum.TASK_MANAGER },
|
||||
[PermissionEnum.WORKSPACE_DELETE]: { title: "حذف فضای کار", group: PermissionGroupEnum.TASK_MANAGER },
|
||||
|
||||
[PermissionEnum.PROJECT_CREATE]: { title: "ایجاد پروژه", group: PermissionGroupEnum.TASK_MANAGER },
|
||||
[PermissionEnum.PROJECT_READ]: { title: "مشاهده پروژه", group: PermissionGroupEnum.TASK_MANAGER },
|
||||
[PermissionEnum.PROJECT_UPDATE]: { title: "ویرایش پروژه", group: PermissionGroupEnum.TASK_MANAGER },
|
||||
[PermissionEnum.PROJECT_DELETE]: { title: "حذف پروژه", group: PermissionGroupEnum.TASK_MANAGER },
|
||||
|
||||
[PermissionEnum.TASK_CREATE]: { title: "ایجاد وظیفه", group: PermissionGroupEnum.TASK_MANAGER },
|
||||
[PermissionEnum.TASK_READ]: { title: "مشاهده وظیفه", group: PermissionGroupEnum.TASK_MANAGER },
|
||||
[PermissionEnum.TASK_UPDATE]: { title: "ویرایش وظیفه", group: PermissionGroupEnum.TASK_MANAGER },
|
||||
[PermissionEnum.TASK_DELETE]: { title: "حذف وظیفه", group: PermissionGroupEnum.TASK_MANAGER },
|
||||
|
||||
[PermissionEnum.TASK_PHASE_CREATE]: { title: "ایجاد فاز وظیفه", group: PermissionGroupEnum.TASK_MANAGER },
|
||||
[PermissionEnum.TASK_PHASE_READ]: { title: "مشاهده فاز وظیفه", group: PermissionGroupEnum.TASK_MANAGER },
|
||||
[PermissionEnum.TASK_PHASE_UPDATE]: { title: "ویرایش فاز وظیفه", group: PermissionGroupEnum.TASK_MANAGER },
|
||||
[PermissionEnum.TASK_PHASE_DELETE]: { title: "حذف فاز وظیفه", group: PermissionGroupEnum.TASK_MANAGER },
|
||||
|
||||
[PermissionEnum.LOGS]: { title: "لاگها", group: PermissionGroupEnum.OTHER },
|
||||
[PermissionEnum.MANAGE_SSO_CLIENTS]: { title: "مدیریت کلاینتهای SSO", group: PermissionGroupEnum.OTHER },
|
||||
[PermissionEnum.DMENU]: { title: "دیمنو", group: PermissionGroupEnum.PRODUCTS },
|
||||
[PermissionEnum.DKALA]: { title: "دیکالا", group: PermissionGroupEnum.PRODUCTS },
|
||||
[PermissionEnum.DPAGE]: { title: "دیپیج", group: PermissionGroupEnum.PRODUCTS },
|
||||
[PermissionEnum.DMAIL]: { title: "دیمیل", group: PermissionGroupEnum.PRODUCTS },
|
||||
// [PermissionEnum.VIEW_ALL_WORKSPACES]: { title: "مشاهده همه فضاهای کار", group: PermissionGroupEnum.TASK_MANAGER },
|
||||
// [PermissionEnum.VIEW_ALL_PROJECTS]: { title: "مشاهده همه پروژهها", group: PermissionGroupEnum.TASK_MANAGER },
|
||||
};
|
||||
@@ -3,12 +3,19 @@ import { Column, Entity, ManyToMany } from "typeorm";
|
||||
import { Role } from "./role.entity";
|
||||
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||
import { PermissionEnum } from "../enums/permission.enum";
|
||||
import { PermissionGroupEnum } from "../enums/permission-group.enum";
|
||||
|
||||
@Entity()
|
||||
export class Permission extends BaseEntity {
|
||||
@Column({ type: "enum", enum: PermissionEnum, nullable: false, unique: true })
|
||||
name: PermissionEnum;
|
||||
|
||||
@Column({ type: "varchar", length: 150, nullable: false })
|
||||
title: string;
|
||||
|
||||
@Column({ type: "enum", enum: PermissionGroupEnum, nullable: false })
|
||||
group: PermissionGroupEnum;
|
||||
|
||||
@ManyToMany(() => Role, (role) => role.permissions)
|
||||
roles: Role[];
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user