31 Commits

Author SHA1 Message Date
morteza 7f78691f91 update national code
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-26 14:29:25 +03:30
morteza aef712ca1d workspace users
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-26 10:06:36 +03:30
morteza 77783549fa new perms and fix email
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-26 09:31:08 +03:30
morteza be6e4a8d32 ticket master 2026-07-26 08:44:56 +03:30
morteza af606ce9a7 task manager
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-25 15:48:33 +03:30
morteza 7996617cf0 fix superadmin bug
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-25 10:34:06 +03:30
morteza 9e28dfd628 new perms for ticket category
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-23 12:06:34 +03:30
morteza c20e0ba684 sort customers
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-22 16:17:55 +03:30
morteza ced46e500d sort customers
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-22 15:43:08 +03:30
morteza c925d2e539 multi role
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-22 12:19:37 +03:30
morteza 10035f3f25 update users
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-22 11:24:46 +03:30
morteza 19c396e9d1 populate user
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-21 14:32:54 +03:30
morteza a13097bbd6 task : populate
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-20 19:34:14 +03:30
morteza 8aaac51b12 add : ticket comment
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-20 12:07:20 +03:30
morteza b855d81817 add : ticket to task 2026-07-20 11:54:39 +03:30
morteza f66bc9fa8c fix permission bug
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-20 11:13:35 +03:30
morteza 6b222db890 refactor permissions 2026-07-20 10:16:45 +03:30
morteza c82cc654f0 add title to permission
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-19 16:37:45 +03:30
morteza c956bc0deb Merge branch 'rafi/taskmanager'
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-15 12:11:03 +03:30
morteza 0e8e0cba2e update mirrors
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-14 15:12:19 +03:30
morteza 3a84740d9a Merge branch 'rafi/taskmanager'
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-14 15:08:15 +03:30
morteza cb77a55281 update docker
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-12 15:20:49 +03:30
morteza 11445f0a07 Merge branch 'rafi/taskmanager'
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-12 14:53:27 +03:30
morteza c2a861041f Merge branch 'rafi/taskmanager'
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-11 12:44:17 +03:30
morteza 50d9fb2274 Merge branch 'rafi/taskmanager'
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-11 09:43:51 +03:30
morteza 2768611a7c change mirror
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-08 15:59:21 +03:30
morteza b1a02d6b0d mirror
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-08 15:54:41 +03:30
morteza 8b901e4d02 mirror
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-08 15:45:32 +03:30
morteza 43b4f05894 mirror
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-08 15:42:10 +03:30
morteza 7bef7d414a Merge branch 'rafi/taskmanager'
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-08 11:49:27 +03:30
morteza 30dba5ae37 add charge dmenu wallet
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-04 15:16:51 +03:30
73 changed files with 2320 additions and 346 deletions
+1 -1
View File
@@ -33,7 +33,7 @@ SMS_PATTERN_NEW_TICKET_GLOBAL=536596
SMS_PATTERN_NEW_CRITICISM=983724
SMS_PATTERN_USER_PASSWORD=486237
SMTP_HOST=smtps.danakcorp.com
SMTP_PORT=465
SMTP_PORT=587
SMTP_USER=no-reply-danakcorp.com
SMTP_PASS=Mahyar.SR4@
MAIL_FROM=no-reply <no-reply@danakcorp.com>
+11 -7
View File
@@ -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,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"`,
);
}
}
+44 -3
View File
@@ -6,8 +6,46 @@ import { DataSource } from "typeorm";
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");
+11
View File
@@ -0,0 +1,11 @@
{
"folders": [
{
"path": "."
},
{
"path": "../danak-admin"
}
],
"settings": {}
}
+10
View File
@@ -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 {
@@ -963,3 +969,7 @@ export const enum timeMessage {
TIME_ALREADY_STARTED = "برای این تسک یک زمان در حال اجرا وجود دارد!",
NO_RUNNING_TIME = "زمان در حال اجرایی برای این تسک پیدا نشد!"
}
export const enum taskCommentMessage {
TASK_COMMENT_NOT_FOUND = "نظر مورد نظر پیدا نشد!"
}
+26 -20
View File
@@ -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,
},
},
};
},
};
}
+6 -6
View File
@@ -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);
+3 -2
View File
@@ -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,
};
+2 -1
View File
@@ -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,
);
+13 -13
View File
@@ -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);
+4
View File
@@ -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
+4 -4
View File
@@ -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) {
+6 -6
View File
@@ -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);
+7 -7
View File
@@ -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);
+4 -4
View File
@@ -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);
+5 -5
View File
@@ -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) {
@@ -12,6 +12,7 @@ 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()
@@ -30,7 +31,7 @@ export class ProjectController {
}
@Post()
@PermissionsDec(PermissionEnum.PROJECT)
@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" })
@@ -40,7 +41,7 @@ export class ProjectController {
}
@Patch(":id")
@PermissionsDec(PermissionEnum.PROJECT)
@PermissionsDec(PermissionEnum.PROJECT_UPDATE)
@ApiOperation({ summary: "Update a project" })
@ApiParam({ name: "id", description: "Project ID" })
@ApiResponse({ status: 200, description: "Project updated", type: TMProject })
@@ -51,7 +52,7 @@ export class ProjectController {
}
@Delete(":id")
@PermissionsDec(PermissionEnum.PROJECT)
@PermissionsDec(PermissionEnum.PROJECT_DELETE)
@ApiOperation({ summary: "Delete a project" })
@ApiParam({ name: "id", description: "Project ID" })
@ApiResponse({ status: 200, description: "Project deleted", type: TMProject })
@@ -80,4 +81,11 @@ export class ProjectController {
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);
}
}
@@ -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);
}
}
@@ -11,13 +11,13 @@ import { PermissionEnum } from "../../users/enums/permission.enum";
@AuthGuards()
@AdminRoute()
@PermissionsDec(PermissionEnum.TASK_PHASE)
@ApiTags("Task Manager - Task Phases")
@Controller("task-manager/task-phases")
export class TaskPhaseController {
constructor(private readonly taskPhaseService: TaskPhaseService) {}
@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" })
@@ -26,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 })
@@ -35,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" })
@@ -23,7 +23,7 @@ export class TaskController {
constructor(private readonly taskService: TaskService) {}
@Post()
@PermissionsDec(PermissionEnum.TASK)
@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" })
@@ -32,7 +32,7 @@ export class TaskController {
}
@Patch(":id")
@PermissionsDec(PermissionEnum.TASK)
@PermissionsDec(PermissionEnum.TASK_UPDATE)
@ApiOperation({ summary: "Update a task" })
@ApiParam({ name: "id", description: "Task ID" })
@ApiResponse({ status: 200, description: "Task updated", type: TMTask })
@@ -42,18 +42,17 @@ export class TaskController {
}
@Patch(":id/change-phase")
@PermissionsDec(PermissionEnum.TASK)
@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);
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")
@PermissionsDec(PermissionEnum.TASK)
@PermissionsDec(PermissionEnum.TASK_DELETE)
@ApiOperation({ summary: "Delete a task" })
@ApiParam({ name: "id", description: "Task ID" })
@ApiResponse({ status: 200, description: "Task deleted" })
@@ -98,7 +97,7 @@ export class TaskController {
@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): Promise<TMTask> {
return this.taskService.changePriority(srcId, desId);
changePriority(@Param("srcid") srcId: string, @Param("desid") desId: string,@UserDec() user: ITokenPayload): Promise<TMTask> {
return this.taskService.changePriority(srcId, user.permissions, user.id, desId);
}
}
@@ -10,6 +10,8 @@ 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()
@@ -19,7 +21,7 @@ export class WorkspaceController {
constructor(private readonly workspaceService: WorkspaceService) {}
@Get(':id')
@PermissionsDec(PermissionEnum.WORKSPACE)
@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!'})
@@ -28,8 +30,16 @@ export class WorkspaceController {
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> {
@@ -37,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 })
@@ -47,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' })
@@ -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,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;
}
@@ -5,7 +5,9 @@ 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 {
@@ -27,6 +29,13 @@ export class TMTask extends BaseEntity {
@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, {
@@ -59,6 +68,11 @@ export class TMTask extends BaseEntity {
})
times: TMTime[];
@OneToMany(() => TMTaskComment, (comment) => comment.task, {
cascade: ["insert", "update"],
})
comments: TMTaskComment[];
@ManyToMany(() => User, (user) => user.tasks)
@JoinTable({
name: "tm_task_users",
@@ -31,7 +31,8 @@ export class ProjectService {
users: {
id: true,
firstName: true,
lastName: true
lastName: true,
profilePic: true,
},
taskPhases: {
id: true,
@@ -131,7 +132,7 @@ export class ProjectService {
const limit = pagination.limit ?? 10;
const skip = (page - 1) * limit;
const isPermissionIncluded = permissions.includes(PermissionEnum.VIEW_ALL_PROJECTS);
const isPermissionIncluded = permissions.includes(PermissionEnum.PROJECT_READ);
const isUserMemeberOfWorkspace = workspace.users.some((user) => user.id === userId);
var res: [TMProject[], number];
@@ -158,11 +159,16 @@ export class ProjectService {
if (!project) throw new NotFoundException(ProjectMessage.PROJECT_NOT_FOUND);
const isUserMember = project.users.some((user) => user.id === userId);
if (!isUserMember && !permissions.includes(PermissionEnum.VIEW_ALL_PROJECTS))
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;
}
}
@@ -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);
}
}
@@ -7,7 +7,8 @@ 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 { TaskMessage, TaskPhaseMessage } from "../../../common/enums/message.enum";
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";
@@ -21,6 +22,8 @@ export class TaskService {
private readonly taskPhaseRepository: TaskPhaseRepository,
@InjectRepository(User)
private readonly userRepository: Repository<User>,
@InjectRepository(Ticket)
private readonly ticketRepository: Repository<Ticket>,
) {}
async findOneOrFail(id: string): Promise<TMTask> {
@@ -29,12 +32,19 @@ export class TaskService {
relations: {
taskPhase: true,
users: true,
ticket: true,
},
});
if (!task) throw new NotFoundException(TaskMessage.TASK_NOT_FOUND);
return task;
}
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;
}
async findTaskPhaseOrFail(taskPhaseId: string): Promise<TMTaskPhase> {
const taskPhase = await this.taskPhaseRepository.findOne({
where: { id: taskPhaseId },
@@ -50,10 +60,14 @@ export class TaskService {
}
async create(dto: CreateTaskDto): Promise<TMTask> {
const { userIds, startDate, endDate, ...rest } = dto;
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);
@@ -61,6 +75,7 @@ export class TaskService {
const task = this.taskRepository.create({
...rest,
ticketId,
startDate: startDate ? new Date(startDate) : undefined,
endDate: endDate ? new Date(endDate) : undefined,
});
@@ -74,7 +89,11 @@ export class TaskService {
async update(id: string, dto: UpdateTaskDto): Promise<TMTask> {
const task = await this.findOneOrFail(id);
const { userIds, startDate, endDate, ...rest } = dto;
const { userIds, startDate, endDate, ticketId, ...rest } = dto;
if (ticketId) {
await this.findTicketOrFail(ticketId);
}
if (dto.taskPhaseId) {
const taskPhase = await this.findTaskPhaseOrFail(dto.taskPhaseId);
@@ -87,6 +106,7 @@ export class TaskService {
Object.assign(task, {
...rest,
...(ticketId !== undefined && { ticketId }),
startDate: startDate ? new Date(startDate) : task.startDate,
endDate: endDate ? new Date(endDate) : task.endDate,
});
@@ -98,9 +118,13 @@ export class TaskService {
return this.taskRepository.save(task);
}
async changePhase(id: string, dto: ChangeTaskPhaseDto): Promise<TMTask> {
async changePhase(id: string,permissions: PermissionEnum[],userId: string, dto: ChangeTaskPhaseDto): Promise<TMTask> {
const task = await this.findOneOrFail(id);
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) {
@@ -122,7 +146,7 @@ export class TaskService {
const task = await this.findOneOrFail(taskId);
const isTaskMember = task.users.some((user) => user.id === userId);
const hasPermission = permissions.includes(PermissionEnum.TASK);
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);
@@ -156,10 +180,14 @@ export class TaskService {
return { data: items, nextCursor, previousCursor, hasNextPage, hasPreviousPage, cursorPaginate: true };
}
async changePriority(srcTaskId: string, destTaskId: string): Promise<TMTask> {
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);
@@ -66,9 +66,14 @@ export class WorkspaceService {
}
async findByUser(userId: string, permissions: PermissionEnum[]): Promise<TMWorkspace[]> {
if(permissions.includes(PermissionEnum.VIEW_ALL_WORKSPACES)) {
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;
}
}
@@ -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);
}
}
@@ -19,7 +19,10 @@ export class TaskRepository extends Repository<TMTask> {
.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")
@@ -44,6 +47,12 @@ export class TaskRepository extends Repository<TMTask> {
"user.id",
"user.firstName",
"user.lastName",
"user.profilePic",
"ticket.id",
"ticket.subject",
"ticket.numericId",
"ticket.status",
"time.id",
"time.startedAt",
@@ -53,6 +62,14 @@ export class TaskRepository extends Repository<TMTask> {
"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 })
@@ -113,12 +130,27 @@ export class TaskRepository extends Repository<TMTask> {
// 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"])
.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();
@@ -16,7 +16,8 @@ export class WorkspaceRepository extends Repository<TMWorkspace> {
'workspace',
'user.id',
'user.firstName',
'user.lastName'
'user.lastName',
'user.profilePic',
]).where('workspace.id = :workspaceId', {workspaceId})
.getOne();
}
@@ -9,10 +9,12 @@ 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";
@@ -34,10 +36,13 @@ 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, TMTime, User]),
TypeOrmModule.forFeature([TMWorkspace, TMProject, TMTaskPhase, TMTask, TMRemark, TMAttachment, TMCheckListItem, TMTime, TMTaskComment, User, Ticket]),
],
controllers: [
WorkspaceController,
@@ -48,6 +53,7 @@ import { TimeService } from "./providers/time.service";
RemarkController,
AttachmentController,
TimeController,
TaskCommentController,
],
providers: [
WorkspaceRepository,
@@ -66,6 +72,8 @@ import { TimeService } from "./providers/time.service";
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 },
});
}
}
+47 -11
View File
@@ -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 -1
View File
@@ -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,
+6 -4
View File
@@ -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[];
}
@@ -0,0 +1,12 @@
export enum PermissionGroupEnum {
SERVICES = "services",
CUSTOMERS = "customers",
USERS = "users",
FINANCIAL = "financial",
CONTENT = "content",
SUPPORT = "support",
OTHER = "other",
PRODUCTS = "products",
REPRESENTATIVES = "representatives",
TASK_MANAGER = "task_manager",
}
+178 -27
View File
@@ -1,34 +1,185 @@
export enum PermissionEnum {
SERVICES = "services",
CUSTOMERS = "customers",
AGENTS = "agents",
DEVELOPERS = "developers",
INVOICES = "invoices",
TRANSACTIONS = "transactions",
DISCOUNTS = "discounts",
ADMINS = "admins",
TICKETS = "tickets",
CRITICISMS = "criticisms",
CONTACTS_US = "contacts_us",
ADVERTISEMENTS = "advertisements",
ANNOUNCEMENTS = "announcements",
BLOGS = "blogs",
LEARNINGS = "learnings",
// services
SERVICES_CREATE = "services_create",
SERVICES_READ = "services_read",
SERVICES_UPDATE = "services_update",
SERVICES_DELETE = "services_delete",
// customers
CUSTOMERS_CREATE = "customers_create",
CUSTOMERS_READ = "customers_read",
CUSTOMERS_UPDATE = "customers_update",
CUSTOMERS_DELETE = "customers_delete",
// agents
AGENTS_CREATE = "agents_create",
AGENTS_READ = "agents_read",
AGENTS_UPDATE = "agents_update",
AGENTS_DELETE = "agents_delete",
// developers
DEVELOPERS_CREATE = "developers_create",
DEVELOPERS_READ = "developers_read",
DEVELOPERS_UPDATE = "developers_update",
DEVELOPERS_DELETE = "developers_delete",
// invoices
INVOICES_CREATE = "invoices_create",
INVOICES_READ = "invoices_read",
INVOICES_UPDATE = "invoices_update",
INVOICES_DELETE = "invoices_delete",
// transactions
TRANSACTIONS_CREATE = "transactions_create",
TRANSACTIONS_READ = "transactions_read",
TRANSACTIONS_UPDATE = "transactions_update",
TRANSACTIONS_DELETE = "transactions_delete",
// discounts
DISCOUNTS_CREATE = "discounts_create",
DISCOUNTS_READ = "discounts_read",
DISCOUNTS_UPDATE = "discounts_update",
DISCOUNTS_DELETE = "discounts_delete",
// admins
ADMINS_CREATE = "admins_create",
ADMINS_READ = "admins_read",
ADMINS_UPDATE = "admins_update",
ADMINS_DELETE = "admins_delete",
// tickets
TICKETS_CREATE = "tickets_create",
TICKETS_READ = "tickets_read",
TICKETS_UPDATE = "tickets_update",
TICKETS_DELETE = "tickets_delete",
// ticket_categories
TICKET_CATEGORIES_CREATE = "ticket_categories_create",
TICKET_CATEGORIES_READ = "ticket_categories_read",
TICKET_CATEGORIES_UPDATE = "ticket_categories_update",
TICKET_CATEGORIES_DELETE = "ticket_categories_delete",
// ticket_masters
TICKET_MASTERS_CREATE = "ticket_masters_create",
TICKET_MASTERS_READ = "ticket_masters_read",
TICKET_MASTERS_UPDATE = "ticket_masters_update",
TICKET_MASTERS_DELETE = "ticket_masters_delete",
// criticisms
CRITICISMS_CREATE = "criticisms_create",
CRITICISMS_READ = "criticisms_read",
CRITICISMS_UPDATE = "criticisms_update",
CRITICISMS_DELETE = "criticisms_delete",
// contacts_us
CONTACTS_US_CREATE = "contacts_us_create",
CONTACTS_US_READ = "contacts_us_read",
CONTACTS_US_UPDATE = "contacts_us_update",
CONTACTS_US_DELETE = "contacts_us_delete",
// advertisements
ADVERTISEMENTS_CREATE = "advertisements_create",
ADVERTISEMENTS_READ = "advertisements_read",
ADVERTISEMENTS_UPDATE = "advertisements_update",
ADVERTISEMENTS_DELETE = "advertisements_delete",
// announcements
ANNOUNCEMENTS_CREATE = "announcements_create",
ANNOUNCEMENTS_READ = "announcements_read",
ANNOUNCEMENTS_UPDATE = "announcements_update",
ANNOUNCEMENTS_DELETE = "announcements_delete",
// blogs
BLOGS_CREATE = "blogs_create",
BLOGS_READ = "blogs_read",
BLOGS_UPDATE = "blogs_update",
BLOGS_DELETE = "blogs_delete",
// blog_categories
BLOG_CATEGORIES_CREATE = "blog_categories_create",
BLOG_CATEGORIES_READ = "blog_categories_read",
BLOG_CATEGORIES_UPDATE = "blog_categories_update",
BLOG_CATEGORIES_DELETE = "blog_categories_delete",
// blog_comments
BLOG_COMMENTS_CREATE = "blog_comments_create",
BLOG_COMMENTS_READ = "blog_comments_read",
BLOG_COMMENTS_UPDATE = "blog_comments_update",
BLOG_COMMENTS_DELETE = "blog_comments_delete",
// sliders
SLIDERS_CREATE = "sliders_create",
SLIDERS_READ = "sliders_read",
SLIDERS_UPDATE = "sliders_update",
SLIDERS_DELETE = "sliders_delete",
// learnings
LEARNINGS_CREATE = "learnings_create",
LEARNINGS_READ = "learnings_read",
LEARNINGS_UPDATE = "learnings_update",
LEARNINGS_DELETE = "learnings_delete",
// settings
SETTINGS_CREATE = "settings_create",
SETTINGS_READ = "settings_read",
SETTINGS_UPDATE = "settings_update",
SETTINGS_DELETE = "settings_delete",
// bank_accounts
BANK_ACCOUNTS_CREATE = "bank_accounts_create",
BANK_ACCOUNTS_READ = "bank_accounts_read",
BANK_ACCOUNTS_UPDATE = "bank_accounts_update",
BANK_ACCOUNTS_DELETE = "bank_accounts_delete",
// payments
PAYMENTS_CREATE = "payments_create",
PAYMENTS_READ = "payments_read",
PAYMENTS_UPDATE = "payments_update",
PAYMENTS_DELETE = "payments_delete",
// support_plan
SUPPORT_PLAN_CREATE = "support_plan_create",
SUPPORT_PLAN_READ = "support_plan_read",
SUPPORT_PLAN_UPDATE = "support_plan_update",
SUPPORT_PLAN_DELETE = "support_plan_delete",
// reseller
RESELLER_CREATE = "reseller_create",
RESELLER_READ = "reseller_read",
RESELLER_UPDATE = "reseller_update",
RESELLER_DELETE = "reseller_delete",
// workspace
WORKSPACE_CREATE = "workspace_create",
WORKSPACE_READ = "workspace_read",
WORKSPACE_UPDATE = "workspace_update",
WORKSPACE_DELETE = "workspace_delete",
// project
PROJECT_CREATE = "project_create",
PROJECT_READ = "project_read",
PROJECT_UPDATE = "project_update",
PROJECT_DELETE = "project_delete",
// task
TASK_CREATE = "task_create",
TASK_READ = "task_read",
TASK_UPDATE = "task_update",
TASK_DELETE = "task_delete",
// task_phase
TASK_PHASE_CREATE = "task_phase_create",
TASK_PHASE_READ = "task_phase_read",
TASK_PHASE_UPDATE = "task_phase_update",
TASK_PHASE_DELETE = "task_phase_delete",
// single permissions (no CRUD split)
LOGS = "logs",
SETTINGS = "settings",
BANK_ACCOUNTS = "bank_accounts",
PAYMENTS = "payments",
MANAGE_SSO_CLIENTS = "manage_sso_clients",
SUPPORT_PLAN = "support_plan",
DMENU = "dmenu",
DKALA = "dkala",
DPAGE = "dpage",
DMAIL = 'dmail',
RESELLER="reseller",
WORKSPACE="workspace", // create, update, remove, read-one(for update purposes)
VIEW_ALL_WORKSPACES="view_all_workspaces",
PROJECT="project", // create, update, remove
VIEW_ALL_PROJECTS="view_all_projects",
TASK="task", // read, create, update, remove
TASK_PHASE="task_phase",
DMAIL = "dmail",
// VIEW_ALL_WORKSPACES = "view_all_workspaces",
// VIEW_ALL_PROJECTS = "view_all_projects",
}
+27 -20
View File
@@ -18,6 +18,7 @@ import { RoleEnum } from "../enums/role.enum";
import { PermissionsRepository } from "../repositories/permissions.repository";
import { RoleRepository } from "../repositories/roles.repository";
import { UserRepository } from "../repositories/users.repository";
import { getPermissionEntitiesFromRoles } from "../utils/role-permissions.util";
@Injectable()
export class AdminsService {
@@ -43,22 +44,22 @@ export class AdminsService {
const existPhone = await queryRunner.manager.findOneBy(this.userRepository.target, { phone: createDto.phone });
if (existPhone) throw new BadRequestException(UserMessage.PHONE_EXIST);
const role = await queryRunner.manager.findOne(this.rolesRepository.target, {
where: { id: createDto.roleId },
const roles = await queryRunner.manager.find(this.rolesRepository.target, {
where: { id: In(createDto.roleIds), isAdmin: true },
relations: { permissions: true },
});
if (!role) throw new BadRequestException(AdsMessage.ROLE_NOT_FOUND);
if (roles.length !== createDto.roleIds.length) throw new BadRequestException(AdsMessage.ROLE_NOT_FOUND);
if (createDto.password !== createDto.repeatPassword) throw new BadRequestException(AdminMessage.PASSWORD_NOT_MATCH);
const hashedPassword = await this.passwordService.hashPassword(createDto.password);
const userName = slugify(`${createDto.firstName} ${Date.now().toString().slice(-5)}`, { lower: true, trim: true });
await queryRunner.manager.save(this.rolesRepository.target, role);
const { roleIds: _roleIds, repeatPassword: _repeatPassword, ...adminData } = createDto;
const adminUser = queryRunner.manager.create(this.userRepository.target, {
...createDto,
roles: [role],
...adminData,
roles,
password: hashedPassword,
userName,
});
@@ -75,7 +76,7 @@ export class AdminsService {
userAgent: userIpAndHeaders.headers["user-agent"],
actionDescription: `Admin created new admin: ${createDto.email}`,
metadata: {
roleId: createDto.roleId,
roleIds: createDto.roleIds,
createdBy: "admin",
email: createDto.email,
phone: createDto.phone,
@@ -118,16 +119,14 @@ export class AdminsService {
if (existPhone) throw new BadRequestException(UserMessage.PHONE_EXIST);
}
if (updateDto.roleId) {
const role = await queryRunner.manager.findOne(this.rolesRepository.target, {
where: { id: updateDto.roleId },
if (updateDto.roleIds?.length) {
const roles = await queryRunner.manager.find(this.rolesRepository.target, {
where: { id: In(updateDto.roleIds), isAdmin: true },
relations: { permissions: true },
});
if (!role) throw new BadRequestException(AdsMessage.ROLE_NOT_FOUND);
if (roles.length !== updateDto.roleIds.length) throw new BadRequestException(AdsMessage.ROLE_NOT_FOUND);
await queryRunner.manager.save(this.rolesRepository.target, role);
admin.roles = [role];
admin.roles = roles;
await queryRunner.manager.save(this.userRepository.target, admin);
}
@@ -138,7 +137,8 @@ export class AdminsService {
admin.password = await this.passwordService.hashPassword(updateDto.password);
}
await queryRunner.manager.save(this.userRepository.target, { ...admin, ...updateDto, password: admin.password });
const { roleIds: _roleIds, repeatPassword: _repeatPassword, ...updateData } = updateDto;
await queryRunner.manager.save(this.userRepository.target, { ...admin, ...updateData, password: admin.password });
await queryRunner.commitTransaction();
await this.accessLogService.logUpdate("User", admin.id, UserType.ADMIN, {
@@ -150,7 +150,7 @@ export class AdminsService {
userType: UserType.ADMIN,
actionDescription: `Admin updated admin: ${admin.email}`,
metadata: {
roleId: updateDto.roleId,
roleIds: updateDto.roleIds,
},
oldValues: JSON.stringify({ ...admin }),
newValues: JSON.stringify({ ...updateDto }),
@@ -169,7 +169,7 @@ export class AdminsService {
/************************************************************ */
async deleteAdmin(id: string, userIpAndHeaders: IUserIpAndHeaders) {
const admin = await this.userRepository.findOne({ where: { id, roles: { isAdmin: true } } });
const admin = await this.userRepository.findOne({ where: { id, roles: { isAdmin: true } }, relations: { roles: true } });
if (!admin) throw new BadRequestException(AdminMessage.ADMIN_NOT_FOUND);
await this.userRepository.softDelete(id);
@@ -182,7 +182,7 @@ export class AdminsService {
userAgent: userIpAndHeaders.headers["user-agent"],
actionDescription: `Admin deleted admin: ${admin.email}`,
metadata: {
roleId: admin.roles[0].id,
roleIds: admin.roles.map((role) => role.id),
email: admin.email,
phone: admin.phone,
},
@@ -277,7 +277,11 @@ export class AdminsService {
paginate: true,
};
}
/************************************************************ */
async getAllAdmins() {
return this.userRepository.find({ where: { roles: { isAdmin: true } } });
}
/************************************************************ */
async getAdminById(id: string) {
@@ -288,7 +292,10 @@ export class AdminsService {
/************************************************************ */
async getPermissions() {
const permissions = await this.permissionsRepository.find({ select: { id: true, name: true } });
const permissions = await this.permissionsRepository.find({
select: { id: true, name: true, title: true, group: true },
order: { group: "ASC", name: "ASC" },
});
return {
permissions,
};
@@ -300,7 +307,7 @@ export class AdminsService {
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
return {
permissions: user.roles[0].permissions,
permissions: getPermissionEntitiesFromRoles(user.roles),
};
}
/************************************************************ */
+102 -59
View File
@@ -2,7 +2,7 @@ import { BadRequestException, Injectable, Logger } from "@nestjs/common";
import { DataSource, Not } from "typeorm";
import { IUserIpAndHeaders } from "../../../common/decorators/user.decorator";
import { CommonMessage, UserMessage } from "../../../common/enums/message.enum";
import { AuthMessage, CommonMessage, FinancialMessage, UserMessage } from "../../../common/enums/message.enum";
import { UserType } from "../../access-logs/enums/user-type.enum";
import { AccessLogService } from "../../access-logs/providers/access-log.service";
import { Address } from "../../address/entities/address.entity";
@@ -135,6 +135,7 @@ export class CustomersService {
password,
phone,
birthDate,
nationalCode,
};
// const addressUpdateData = {
@@ -169,7 +170,7 @@ export class CustomersService {
if (nationalCode) {
const existingNationalCode = await queryRunner.manager.findOne(this.realUserRepository.target, {
where: { nationalCode: nationalCode, id: Not(user.id) },
where: { nationalCode: nationalCode, user: { id: Not(user.id) } },
});
if (existingNationalCode) throw new BadRequestException(UserMessage.NATIONAL_CODE_EXIST);
}
@@ -180,67 +181,109 @@ export class CustomersService {
await queryRunner.manager.update(this.userRepository.target, { id: customerId }, { password: hashPassword });
}
const legalUser = await queryRunner.manager.findOne(this.legalUserRepository.target, {
where: { user: { id: customerId } },
relations: { address: true },
});
if (!legalUser) throw new BadRequestException(UserMessage.LEGAL_DATA_NOT_FOUND);
if (economicCode) {
const existingEconomicCode = await queryRunner.manager.findOne(this.legalUserRepository.target, {
where: { economicCode, id: Not(legalUser.id) },
if (nationalCode) {
const realUser = await queryRunner.manager.findOne(this.realUserRepository.target, {
where: { user: { id: customerId } },
});
if (existingEconomicCode) throw new BadRequestException(UserMessage.ECONOMIC_CODE_EXIST);
}
if (registrationCode) {
const existingRegistrationCode = await queryRunner.manager.findOne(this.legalUserRepository.target, {
where: { registrationCode, id: Not(legalUser.id) },
});
if (existingRegistrationCode) throw new BadRequestException(UserMessage.REGISTRATION_CODE_EXIST);
}
if (nationalIdentity) {
const existingNationalIdentity = await queryRunner.manager.findOne(this.legalUserRepository.target, {
where: { nationalIdentity, id: Not(legalUser.id) },
});
if (existingNationalIdentity) throw new BadRequestException(UserMessage.NATIONAL_IDENTITY_EXIST);
}
if (registrationName) {
const existingRegistrationName = await queryRunner.manager.findOne(this.legalUserRepository.target, {
where: { registrationName, id: Not(legalUser.id) },
});
if (existingRegistrationName) throw new BadRequestException(UserMessage.REGISTRATION_NAME_EXIST);
}
await queryRunner.manager.update(this.legalUserRepository.target, { id: legalUser.id }, legalUserUpdateData);
// Update address if needed
if (cityId || address || postalCode) {
// Get the address entity
const addressEntity = legalUser.address;
if (!addressEntity) throw new BadRequestException(UserMessage.ADDRESS_NOT_FOUND);
// Prepare address updates
const addressUpdates: Record<string, unknown> = {};
if (cityId) {
const { city } = await this.addressService.getCity(+cityId);
addressUpdates.city = city;
if (realUser) {
await queryRunner.manager.update(this.realUserRepository.target, { id: realUser.id }, { nationalCode });
}
}
if (address) {
addressUpdates.fullAddress = address;
const hasLegalUpdateData = Boolean(
registrationName || economicCode || nationalIdentity || registrationCode || postalCode || address || cityId,
);
if (hasLegalUpdateData) {
const legalUser = await queryRunner.manager.findOne(this.legalUserRepository.target, {
where: { user: { id: customerId } },
relations: { address: true },
});
if (!legalUser) {
if (user.financialType === FinancialType.REAL) {
throw new BadRequestException(UserMessage.FINANCIAL_TYPE_IS_REAL);
}
const legalPhone = phone ?? user.phone;
if (!economicCode) throw new BadRequestException(FinancialMessage.ECONOMIC_CODE_REQUIRED);
if (!registrationCode) throw new BadRequestException(FinancialMessage.REGISTRATION_CODE_REQUIRED);
if (!registrationName) throw new BadRequestException(FinancialMessage.REGISTRATION_NAME_REQUIRED);
if (!nationalIdentity) throw new BadRequestException(FinancialMessage.NATIONAL_ID_REQUIRED);
if (!legalPhone) throw new BadRequestException(AuthMessage.PHONE_NOT_EMPTY);
if (!address) throw new BadRequestException(FinancialMessage.ADDRESS_REQUIRED);
if (!postalCode) throw new BadRequestException(FinancialMessage.POSTAL_CODE_REQUIRED);
if (!cityId) throw new BadRequestException(FinancialMessage.CITY_ID_REQUIRED);
await this.createLegalUserData(
{
economicCode,
registrationCode,
registrationName,
nationalIdentity,
phone: legalPhone,
address,
postalCode,
cityId: +cityId,
},
customerId,
queryRunner,
);
} else {
if (economicCode) {
const existingEconomicCode = await queryRunner.manager.findOne(this.legalUserRepository.target, {
where: { economicCode, id: Not(legalUser.id) },
});
if (existingEconomicCode) throw new BadRequestException(UserMessage.ECONOMIC_CODE_EXIST);
}
if (registrationCode) {
const existingRegistrationCode = await queryRunner.manager.findOne(this.legalUserRepository.target, {
where: { registrationCode, id: Not(legalUser.id) },
});
if (existingRegistrationCode) throw new BadRequestException(UserMessage.REGISTRATION_CODE_EXIST);
}
if (nationalIdentity) {
const existingNationalIdentity = await queryRunner.manager.findOne(this.legalUserRepository.target, {
where: { nationalIdentity, id: Not(legalUser.id) },
});
if (existingNationalIdentity) throw new BadRequestException(UserMessage.NATIONAL_IDENTITY_EXIST);
}
if (registrationName) {
const existingRegistrationName = await queryRunner.manager.findOne(this.legalUserRepository.target, {
where: { registrationName, id: Not(legalUser.id) },
});
if (existingRegistrationName) throw new BadRequestException(UserMessage.REGISTRATION_NAME_EXIST);
}
await queryRunner.manager.update(this.legalUserRepository.target, { id: legalUser.id }, legalUserUpdateData);
if (cityId || address || postalCode) {
const addressEntity = legalUser.address;
if (!addressEntity) throw new BadRequestException(UserMessage.ADDRESS_NOT_FOUND);
const addressUpdates: Record<string, unknown> = {};
if (cityId) {
const { city } = await this.addressService.getCity(+cityId);
addressUpdates.city = city;
}
if (address) {
addressUpdates.fullAddress = address;
}
if (postalCode) {
addressUpdates.postalCode = postalCode;
}
await queryRunner.manager.update(Address, { id: addressEntity.id }, addressUpdates);
}
}
if (postalCode) {
addressUpdates.postalCode = postalCode;
}
await queryRunner.manager.update(Address, { id: addressEntity.id }, addressUpdates);
}
await queryRunner.commitTransaction();
@@ -359,6 +359,7 @@ export class UsersService {
async isSuperAdmin(adminId: string) {
const user = await this.userRepository.findOne({ where: { id: adminId }, relations: { roles: true } });
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
console.log("user.roles", user.roles.map((role) => role.name));
return user.roles.some((role) => role.name === RoleEnum.SUPER_ADMIN);
}
@@ -119,10 +119,21 @@ export class UserRepository extends Repository<User> {
.loadRelationCountAndMap("user.ticketsCount", "user.tickets");
if (queryDto.q) {
queryBuilder
.orWhere("user.firstName ILIKE :search", { search: `%${queryDto.q}%` })
.orWhere("user.lastName ILIKE :search", { search: `%${queryDto.q}%` })
.orWhere("user.userName ILIKE :search", { search: `%${queryDto.q}%` });
queryBuilder.andWhere(
`(
user.firstName ILIKE :search
OR user.lastName ILIKE :search
OR CONCAT(user.firstName, ' ', user.lastName) ILIKE :search
OR user.userName ILIKE :search
OR user.phone ILIKE :search
OR legalUser.registrationName ILIKE :search
OR legalUser.phone ILIKE :search
OR realUser.firstName ILIKE :search
OR realUser.lastName ILIKE :search
OR realUser.phone ILIKE :search
)`,
{ search: `%${queryDto.q}%` },
);
}
return queryBuilder.getMany();
@@ -166,13 +177,28 @@ export class UserRepository extends Repository<User> {
.loadRelationCountAndMap("user.ticketsCount", "user.tickets");
if (queryDto.q) {
queryBuilder
.orWhere("user.firstName ILIKE :search", { search: `%${queryDto.q}%` })
.orWhere("user.lastName ILIKE :search", { search: `%${queryDto.q}%` })
.orWhere("user.userName ILIKE :search", { search: `%${queryDto.q}%` });
queryBuilder.andWhere(
`(
user.firstName ILIKE :search
OR user.lastName ILIKE :search
OR CONCAT(user.firstName, ' ', user.lastName) ILIKE :search
OR user.userName ILIKE :search
OR user.phone ILIKE :search
OR legalUser.registrationName ILIKE :search
OR legalUser.phone ILIKE :search
OR realUser.firstName ILIKE :search
OR realUser.lastName ILIKE :search
OR realUser.phone ILIKE :search
)`,
{ search: `%${queryDto.q}%` },
);
}
return await queryBuilder.skip(skip).take(limit).getManyAndCount();
return await queryBuilder
.orderBy("user.createdAt", "DESC")
.skip(skip)
.take(limit)
.getManyAndCount();
}
//************* */
@@ -188,7 +214,7 @@ export class UserRepository extends Repository<User> {
.leftJoinAndSelect("role.permissions", "permissions")
.andWhere("user.deletedAt IS NULL");
// .where("permissions.name IN (:...adminPermissions)", {
// adminPermissions: [PermissionEnum.ADMINS],
// adminPermissions: [PermissionEnum.ADMINS_READ],
// });
// .addSelect(["permissions.id", "permissions.name"]);
+24 -16
View File
@@ -113,21 +113,21 @@ export class UsersController {
}
@ApiOperation({ summary: "Create user group ==> admin route" })
@PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS)
@PermissionsDec(PermissionEnum.ADMINS_CREATE, PermissionEnum.CUSTOMERS_CREATE)
@Post("user-group")
createUserGroup(@Body() createDto: CreateUserGroupDto) {
return this.usersService.createUserGroup(createDto);
}
@ApiOperation({ summary: "get all user group ==> admin route" })
@PermissionsDec(PermissionEnum.CUSTOMERS)
@PermissionsDec(PermissionEnum.CUSTOMERS_READ)
@Get("user-group")
UserGroups() {
return this.usersService.getUserGroups();
}
@ApiOperation({ summary: "get all users ==> admin route" })
@PermissionsDec(PermissionEnum.CUSTOMERS)
@PermissionsDec(PermissionEnum.CUSTOMERS_READ)
@Get()
getUsers() {
return this.usersService.findAllUsers();
@@ -136,12 +136,14 @@ export class UsersController {
@ApiOperation({ summary: "Create customer" })
@HttpCode(HttpStatus.CREATED)
@PermissionsDec(PermissionEnum.CUSTOMERS_CREATE)
@Post("customers")
createCustomer(@Body() createDto: CreateCustomerDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
return this.customersService.createCustomer(createDto, userIpAndHeaders);
}
@ApiOperation({ summary: "Update customer" })
@PermissionsDec(PermissionEnum.CUSTOMERS_UPDATE)
@Patch("customers/:id")
updateCustomer(
@Param() paramDto: ParamDto,
@@ -152,14 +154,14 @@ export class UsersController {
}
@ApiOperation({ summary: "get customers ==> admin route" })
@PermissionsDec(PermissionEnum.CUSTOMERS)
@PermissionsDec(PermissionEnum.CUSTOMERS_READ)
@Get("customers")
getCustomers(@Query() queryDto: SearchCustomersDto) {
return this.customersService.getCustomers(queryDto);
}
@ApiOperation({ summary: "get customers by id ==> admin route" })
@PermissionsDec(PermissionEnum.CUSTOMERS)
@PermissionsDec(PermissionEnum.CUSTOMERS_READ)
@Get("customers/:id")
getCustomerById(@Param() paramDto: ParamDto) {
return this.customersService.getCustomerById(paramDto.id);
@@ -169,35 +171,41 @@ export class UsersController {
@ApiOperation({ summary: "get all admins ==> admin route" })
@AdminRoute()
// @PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS)
@Get("admins")
getAdmins(@Query() queryDto: SearchAdminQueryDto) {
return this.adminsService.getAdmins(queryDto);
}
@ApiOperation({ summary: "get all admins without pagination==> admin route" })
@AdminRoute()
@Get("admins-list/all")
getAllAdmins() {
return this.adminsService.getAllAdmins();
}
@ApiOperation({ summary: "get admin by id ==> admin route" })
@PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS)
@PermissionsDec(PermissionEnum.ADMINS_READ, PermissionEnum.CUSTOMERS_READ)
@Get("admins/:id")
getAdminById(@Param() paramDto: ParamDto) {
return this.adminsService.getAdminById(paramDto.id);
}
@ApiOperation({ summary: "update admin ==> admin route" })
@PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS)
@PermissionsDec(PermissionEnum.ADMINS_UPDATE, PermissionEnum.CUSTOMERS_UPDATE)
@Patch("admins/:id")
updateAdmin(@Param() paramDto: ParamDto, @Body() updateDto: UpdateAdminDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
return this.adminsService.updateAdmin(paramDto.id, updateDto, userIpAndHeaders);
}
@ApiOperation({ summary: "delete admin ==> admin route" })
@PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS)
@PermissionsDec(PermissionEnum.ADMINS_DELETE, PermissionEnum.CUSTOMERS_DELETE)
@Delete("admins/:id")
deleteAdmin(@Param() paramDto: ParamDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
return this.adminsService.deleteAdmin(paramDto.id, userIpAndHeaders);
}
@ApiOperation({ summary: "create admin ==> admin route" })
@PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS)
@PermissionsDec(PermissionEnum.ADMINS_CREATE, PermissionEnum.CUSTOMERS_CREATE)
@Post("admins")
createAdmin(@Body() createDto: CreateAdminDto, @UserIpAndHeaders() userIpAndHeaders: IUserIpAndHeaders) {
return this.adminsService.createAdmin(createDto, userIpAndHeaders);
@@ -210,42 +218,42 @@ export class UsersController {
}
@ApiOperation({ summary: "get all permissions ==> admin route" })
@PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS)
@PermissionsDec(PermissionEnum.ADMINS_READ, PermissionEnum.CUSTOMERS_READ)
@Get("permissions")
getPermissions() {
return this.adminsService.getPermissions();
}
@ApiOperation({ summary: "get all roles ==> admin route" })
@PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS)
@PermissionsDec(PermissionEnum.ADMINS_READ, PermissionEnum.CUSTOMERS_READ)
@Get("roles")
getRoles(@Query() queryDto: SearchRolesQueryDto) {
return this.adminsService.getRoles(queryDto);
}
@ApiOperation({ summary: "get role by id ==> admin route" })
@PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS)
@PermissionsDec(PermissionEnum.ADMINS_READ, PermissionEnum.CUSTOMERS_READ)
@Get("roles/:id")
getRoleById(@Param() paramDto: ParamDto) {
return this.adminsService.getRoleById(paramDto.id);
}
@ApiOperation({ summary: "create role ==> admin route" })
@PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS)
@PermissionsDec(PermissionEnum.ADMINS_CREATE, PermissionEnum.CUSTOMERS_CREATE)
@Post("roles")
createRole(@Body() createDto: CreateRoleDto) {
return this.adminsService.createRole(createDto);
}
@ApiOperation({ summary: "update role ==> admin route" })
@PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS)
@PermissionsDec(PermissionEnum.ADMINS_UPDATE, PermissionEnum.CUSTOMERS_UPDATE)
@Patch("roles/:id")
updateRole(@Param() paramDto: ParamDto, @Body() updateDto: UpdateRoleDto) {
return this.adminsService.updateRole(paramDto.id, updateDto);
}
@ApiOperation({ summary: "delete role ==> admin route" })
@PermissionsDec(PermissionEnum.ADMINS, PermissionEnum.CUSTOMERS)
@PermissionsDec(PermissionEnum.ADMINS_DELETE, PermissionEnum.CUSTOMERS_DELETE)
@Delete("roles/:id")
deleteRole(@Param() paramDto: ParamDto) {
return this.adminsService.deleteRole(paramDto.id);
@@ -0,0 +1,19 @@
import { PermissionEnum } from "../enums/permission.enum";
import { Role } from "../entities/role.entity";
import { Permission } from "../entities/permission.entity";
export function getPermissionsFromRoles(roles: Role[]): PermissionEnum[] {
return [...new Set(roles.flatMap((role) => (role?.permissions?.length ? role.permissions.map((permission) => permission.name) : [])))];
}
export function getPermissionEntitiesFromRoles(roles: Role[]): Permission[] {
const permissionsById = new Map<string, Permission>();
for (const role of roles) {
for (const permission of role?.permissions ?? []) {
permissionsById.set(permission.id, permission);
}
}
return [...permissionsById.values()];
}
-22
View File
@@ -1,22 +0,0 @@
import { INestApplication } from "@nestjs/common";
import { Test, TestingModule } from "@nestjs/testing";
import request from "supertest";
import { AppModule } from "./../src/app.module";
describe("AppController (e2e)", () => {
let app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it("/ (GET)", () => {
return request(app.getHttpServer()).get("/").expect(200).expect("Hello World!");
});
});
-9
View File
@@ -1,9 +0,0 @@
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
}