diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 568811143..8b590cb6b 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -50,7 +50,7 @@ jobs: run: pnpm run build:scripts - name: Build Projects - run: pnpm run build --filter=vitnode-backend --filter=vitnode-frontend --filter=vitnode-backend-email-resend --filter=vitnode-backend-email-smtp + run: pnpm run build --filter=vitnode-backend --filter=vitnode-frontend --filter=vitnode-backend-email-resend --filter=vitnode-backend-email-smtp --filter=vitnode-backend-ai-google --filter=vitnode-backend-ai-open-ai - name: Run script to bump version run: pnpm run release @@ -74,21 +74,21 @@ jobs: - name: Publish canary if: github.event.inputs.release == 'canary' - run: pnpm publish --access public --filter vitnode-backend --filter vitnode-backend-email-resend --filter vitnode-backend-email-smtp --filter vitnode-frontend --filter create-vitnode-app --filter eslint-config-typescript-vitnode --tag canary --no-git-checks + run: pnpm publish --access public --filter vitnode-backend --filter vitnode-backend-email-resend --filter vitnode-backend-email-smtp --filter vitnode-backend-ai-google --filter vitnode-backend-ai-open-ai --filter vitnode-frontend --filter create-vitnode-app --filter eslint-config-typescript-vitnode --tag canary --no-git-checks env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_CONFIG_PROVENANCE: true - name: Publish release candidate if: github.event.inputs.release == 'release-candidate' - run: pnpm publish --access public --filter vitnode-backend --filter vitnode-backend-email-resend --filter vitnode-backend-email-smtp --filter vitnode-frontend --filter create-vitnode-app --filter eslint-config-typescript-vitnode --tag rc --no-git-checks + run: pnpm publish --access public --filter vitnode-backend --filter vitnode-backend-email-resend --filter vitnode-backend-email-smtp --filter vitnode-backend-ai-google --filter vitnode-backend-ai-open-ai --filter vitnode-frontend --filter create-vitnode-app --filter eslint-config-typescript-vitnode --tag rc --no-git-checks env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_CONFIG_PROVENANCE: true - name: Publish stable if: github.event.inputs.release == 'stable' - run: pnpm publish --access public --filter vitnode-backend --filter vitnode-backend-email-resend --filter vitnode-backend-email-smtp --filter vitnode-frontend --filter create-vitnode-app --filter eslint-config-typescript-vitnode --no-git-checks + run: pnpm publish --access public --filter vitnode-backend --filter vitnode-backend-email-resend --filter vitnode-backend-email-smtp --filter vitnode-backend-ai-google --filter vitnode-backend-ai-open-ai --filter vitnode-frontend --filter create-vitnode-app --filter eslint-config-typescript-vitnode --no-git-checks env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_CONFIG_PROVENANCE: true diff --git a/apps/backend/schema.gql b/apps/backend/schema.gql index 7a84637ce..482cf9a91 100644 --- a/apps/backend/schema.gql +++ b/apps/backend/schema.gql @@ -244,6 +244,8 @@ type Mutation { admin__core_plugins__nav__create(code: String!, icon: String, keywords: [String!]!, parent_code: String, plugin_code: String!): ShowAdminNavPluginsObj! admin__core_plugins__nav__delete(code: String!, parent_code: String, plugin_code: String!): String! admin__core_plugins__nav__edit(code: String!, icon: String, keywords: [String!]!, parent_code: String, plugin_code: String!, previous_code: String!): ShowAdminNavPluginsObj! + admin__core_plugins__permissions_admin__create_edit(id: String!, old_id: String, parent_id: String, plugin_code: String!): ShowAdminPermissionsAdminPluginsObj! + admin__core_plugins__permissions_admin__delete(id: String!, parent_id: String, plugin_code: String!): String! admin__core_plugins__upload(code: String, file: Upload!): String! admin__core_security__captcha__edit(secret_key: String!, site_key: String!, type: CaptchaTypeEnum!): ShowAdminCaptchaSecurityObj! admin__core_staff_administrators__create(group_id: Int, unrestricted: Boolean!, user_id: Int): ShowAdminStaffAdministrators! @@ -298,6 +300,7 @@ type Query { admin__core_members__show(cursor: Int, first: Int, groups: [Int!], id: Float, last: Int, search: String, sortBy: ShowAdminMembersSortByArgs): ShowAdminMembersObj! admin__core_members__stats_sign_up: [SignUpStatsAdminMembers!]! admin__core_plugins__nav__show(plugin_code: String!): [ShowAdminNavPluginsObj!]! + admin__core_plugins__permissions_admin__show(plugin_code: String!): [ShowAdminPermissionsAdminPluginsObj!]! admin__core_plugins__show(code: String, cursor: Int, first: Int, last: Int, search: String, sortBy: ShowAdminPluginsSortByArgs): ShowAdminPluginsObj! admin__core_security__captcha__show: ShowAdminCaptchaSecurityObj! admin__core_staff_administrators__show(cursor: Int, first: Int, last: Int, sortBy: ShowAdminStaffAdministratorsSortByArgs): ShowAdminStaffAdministratorsObj! @@ -458,6 +461,15 @@ type ShowAdminNavPluginsObj { keywords: [String!]! } +type ShowAdminPermissionsAdminPlugins { + id: String! +} + +type ShowAdminPermissionsAdminPluginsObj { + children: [ShowAdminPermissionsAdminPlugins!]! + id: String! +} + type ShowAdminPlugins { allow_default: Boolean! author: String! diff --git a/apps/backend/src/plugins/welcome/config.json b/apps/backend/src/plugins/welcome/config.json index edfcb7c02..65535e446 100644 --- a/apps/backend/src/plugins/welcome/config.json +++ b/apps/backend/src/plugins/welcome/config.json @@ -8,5 +8,6 @@ "author_url": "https://vitnode.com/", "support_url": "https://github.com/VitNode/vitnode/issues", "allow_default": true, - "nav": [] + "nav": [], + "permissions_admin": [] } \ No newline at end of file diff --git a/apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/plugins/[code]/dev/layout.tsx b/apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/plugins/[code]/dev/layout.tsx index 513bdc9cb..3dc0a86cb 100644 --- a/apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/plugins/[code]/dev/layout.tsx +++ b/apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/plugins/[code]/dev/layout.tsx @@ -1,4 +1,3 @@ -// ! DO NOT TOUCH THIS FILE!!! IT IS GENERATED BY VITNODE-CLI import { DevPluginAdminLayout, generateMetadataDevPluginAdminLayout, diff --git a/apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/plugins/[code]/dev/permissions-admin/page.tsx b/apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/plugins/[code]/dev/permissions-admin/page.tsx new file mode 100644 index 000000000..5d71108c5 --- /dev/null +++ b/apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/plugins/[code]/dev/permissions-admin/page.tsx @@ -0,0 +1,7 @@ +import { PermissionsAdminDevPluginAdminView } from 'vitnode-frontend/views/admin/views/core/plugins/views/dev/permissions-admin/permissions-admin'; + +export default function Page( + props: React.ComponentProps, +) { + return ; +} diff --git a/apps/frontend/src/graphql/types.ts b/apps/frontend/src/graphql/types.ts index 4f5a43096..68412aaa2 100644 --- a/apps/frontend/src/graphql/types.ts +++ b/apps/frontend/src/graphql/types.ts @@ -277,6 +277,8 @@ export type Mutation = { admin__core_plugins__nav__create: ShowAdminNavPluginsObj; admin__core_plugins__nav__delete: Scalars['String']['output']; admin__core_plugins__nav__edit: ShowAdminNavPluginsObj; + admin__core_plugins__permissions_admin__create_edit: ShowAdminPermissionsAdminPluginsObj; + admin__core_plugins__permissions_admin__delete: Scalars['String']['output']; admin__core_plugins__upload: Scalars['String']['output']; admin__core_security__captcha__edit: ShowAdminCaptchaSecurityObj; admin__core_staff_administrators__create: ShowAdminStaffAdministrators; @@ -501,6 +503,21 @@ export type MutationAdmin__Core_Plugins__Nav__EditArgs = { }; +export type MutationAdmin__Core_Plugins__Permissions_Admin__Create_EditArgs = { + id: Scalars['String']['input']; + old_id?: InputMaybe; + parent_id?: InputMaybe; + plugin_code: Scalars['String']['input']; +}; + + +export type MutationAdmin__Core_Plugins__Permissions_Admin__DeleteArgs = { + id: Scalars['String']['input']; + parent_id?: InputMaybe; + plugin_code: Scalars['String']['input']; +}; + + export type MutationAdmin__Core_Plugins__UploadArgs = { code?: InputMaybe; file: Scalars['Upload']['input']; @@ -668,6 +685,7 @@ export type Query = { admin__core_members__show: ShowAdminMembersObj; admin__core_members__stats_sign_up: Array; admin__core_plugins__nav__show: Array; + admin__core_plugins__permissions_admin__show: Array; admin__core_plugins__show: ShowAdminPluginsObj; admin__core_security__captcha__show: ShowAdminCaptchaSecurityObj; admin__core_staff_administrators__show: ShowAdminStaffAdministratorsObj; @@ -732,6 +750,11 @@ export type QueryAdmin__Core_Plugins__Nav__ShowArgs = { }; +export type QueryAdmin__Core_Plugins__Permissions_Admin__ShowArgs = { + plugin_code: Scalars['String']['input']; +}; + + export type QueryAdmin__Core_Plugins__ShowArgs = { code?: InputMaybe; cursor?: InputMaybe; @@ -966,6 +989,17 @@ export type ShowAdminNavPluginsObj = { keywords: Array; }; +export type ShowAdminPermissionsAdminPlugins = { + __typename?: 'ShowAdminPermissionsAdminPlugins'; + id: Scalars['String']['output']; +}; + +export type ShowAdminPermissionsAdminPluginsObj = { + __typename?: 'ShowAdminPermissionsAdminPluginsObj'; + children: Array; + id: Scalars['String']['output']; +}; + export type ShowAdminPlugins = { __typename?: 'ShowAdminPlugins'; allow_default: Scalars['Boolean']['output']; diff --git a/apps/frontend/src/plugins/admin/langs/en.json b/apps/frontend/src/plugins/admin/langs/en.json index 77ecfef07..49cfd3436 100644 --- a/apps/frontend/src/plugins/admin/langs/en.json +++ b/apps/frontend/src/plugins/admin/langs/en.json @@ -303,6 +303,30 @@ "desc": "This action will delete navigation.", "success": "Navigation has been deleted." } + }, + "permissions-admin": { + "title": "Permissions in AdminCP", + "create_edit": { + "id": { + "label": "ID", + "desc": "Unique ID for this permission.", + "exists": "Permission with this ID already exists." + }, + "parent": { + "label": "Parent Permission", + "null": "No parent permission" + }, + "create_success": "Permission has been created." + }, + "delete": { + "desc": "This action will delete the permission . Remember to delete all conditions associated with this permission.", + "submit": "Yes, delete permission", + "success": "Permission has been deleted.", + "children_warn": { + "title": "This permission has children", + "desc": "If you delete this permission, all children will be deleted too." + } + } } }, "create": { @@ -577,18 +601,9 @@ }, "staff": { "title": "Staff", - "unrestricted": "Unrestricted", - "restricted": "Restricted", "user": "User", "group": "Group", "already_exists": "This permission is already on the list.", - "table": { - "administrator": "Administrator", - "moderator": "Moderator", - "type": "Type", - "updated": "Updated", - "permissions": "Permissions" - }, "create_edit": { "type": { "title": "Type", @@ -600,8 +615,19 @@ "desc": "Unrestricted administrators have full access to the admin control panel (AdminCP)." } }, + "shared": { + "type": "Type", + "updated": "Updated", + "permissions": "Permissions", + "unrestricted": "Unrestricted", + "restricted": "Restricted", + "user": "User", + "group": "Group" + }, "moderators": { "title": "Moderators", + "desc": "Manage access to the moderator panel (ModCP).", + "moderator": "Moderator", "add": { "title": "Add Moderator", "success": "Moderator has been added" @@ -614,6 +640,8 @@ }, "administrators": { "title": "Administrators", + "desc": "Manage access to the administrator panel (AdminCP).", + "administrator": "Administrator", "add": { "title": "Add Administrator", "success": "Administrator has been added" diff --git a/apps/frontend/src/plugins/welcome/langs/en.json b/apps/frontend/src/plugins/welcome/langs/en.json index f603476d6..9eb2fe948 100644 --- a/apps/frontend/src/plugins/welcome/langs/en.json +++ b/apps/frontend/src/plugins/welcome/langs/en.json @@ -9,7 +9,9 @@ }, "admin_welcome": { "nav": { - "title": "Welcome" + "title": "Welcome", + "test": "Test", + "test_testchild": "Test Child" } } } diff --git a/bump-version.mjs b/bump-version.mjs index 8b7e2c1ed..dfb22a9f0 100644 --- a/bump-version.mjs +++ b/bump-version.mjs @@ -25,6 +25,8 @@ const packages = [ 'eslint-config-typescript-vitnode', 'backend-email-resend', 'backend-email-smtp', + 'backend-ai-open-ai', + 'backend-ai-google', ]; const getPackageJson = () => { diff --git a/packages/backend-ai-google/.npmignore b/packages/backend-ai-google/.npmignore index 48be7e516..1edadfea8 100644 --- a/packages/backend-ai-google/.npmignore +++ b/packages/backend-ai-google/.npmignore @@ -1,6 +1,6 @@ /src /.turbo /node_modules -/.eslintrc.json +/eslint.config.mjs /tsconfig.json /.swcrc \ No newline at end of file diff --git a/packages/backend-ai-open-ai/.npmignore b/packages/backend-ai-open-ai/.npmignore index 48be7e516..1edadfea8 100644 --- a/packages/backend-ai-open-ai/.npmignore +++ b/packages/backend-ai-open-ai/.npmignore @@ -1,6 +1,6 @@ /src /.turbo /node_modules -/.eslintrc.json +/eslint.config.mjs /tsconfig.json /.swcrc \ No newline at end of file diff --git a/packages/backend-email-resend/.npmignore b/packages/backend-email-resend/.npmignore index 48be7e516..1edadfea8 100644 --- a/packages/backend-email-resend/.npmignore +++ b/packages/backend-email-resend/.npmignore @@ -1,6 +1,6 @@ /src /.turbo /node_modules -/.eslintrc.json +/eslint.config.mjs /tsconfig.json /.swcrc \ No newline at end of file diff --git a/packages/backend/.npmignore b/packages/backend/.npmignore index 05feea030..a55813766 100644 --- a/packages/backend/.npmignore +++ b/packages/backend/.npmignore @@ -2,7 +2,7 @@ !/src/database /.turbo /node_modules -/.eslintrc.json +/eslint.config.mjs /tsconfig.json /tsup.config.ts /.swcrc diff --git a/packages/backend/src/core/admin/plugins/nav/edit/edit.service.ts b/packages/backend/src/core/admin/plugins/nav/edit/edit.service.ts index 5a4206afc..bd063d8f3 100644 --- a/packages/backend/src/core/admin/plugins/nav/edit/edit.service.ts +++ b/packages/backend/src/core/admin/plugins/nav/edit/edit.service.ts @@ -60,6 +60,7 @@ export class EditAdminNavPluginsService { code: currentCode, icon: icon ?? null, keywords, + children: config.nav[navIndex]?.children, }; } diff --git a/packages/backend/src/core/admin/plugins/permissions-admin/create-edit/create-edit.dto.ts b/packages/backend/src/core/admin/plugins/permissions-admin/create-edit/create-edit.dto.ts new file mode 100644 index 000000000..7f368e7b9 --- /dev/null +++ b/packages/backend/src/core/admin/plugins/permissions-admin/create-edit/create-edit.dto.ts @@ -0,0 +1,16 @@ +import { ArgsType, Field } from '@nestjs/graphql'; + +@ArgsType() +export class CreateEditAdminPermissionsAdminPluginsArgs { + @Field(() => String) + id: string; + + @Field(() => String, { nullable: true }) + old_id?: string; + + @Field(() => String, { nullable: true }) + parent_id?: string; + + @Field(() => String) + plugin_code: string; +} diff --git a/packages/backend/src/core/admin/plugins/permissions-admin/create-edit/create-edit.resolver.ts b/packages/backend/src/core/admin/plugins/permissions-admin/create-edit/create-edit.resolver.ts new file mode 100644 index 000000000..38867f2d6 --- /dev/null +++ b/packages/backend/src/core/admin/plugins/permissions-admin/create-edit/create-edit.resolver.ts @@ -0,0 +1,23 @@ +import { AdminAuthGuards, OnlyForDevelopment } from '@/utils'; +import { UseGuards } from '@nestjs/common'; +import { Args, Mutation, Resolver } from '@nestjs/graphql'; + +import { ShowAdminPermissionsAdminPluginsObj } from '../show/show.dto'; +import { CreateEditAdminPermissionsAdminPluginsArgs } from './create-edit.dto'; +import { CreateEditAdminPermissionsAdminPluginsService } from './create-edit.service'; + +@Resolver() +export class CreateEditAdminPermissionsAdminPluginsResolver { + constructor( + private readonly service: CreateEditAdminPermissionsAdminPluginsService, + ) {} + + @Mutation(() => ShowAdminPermissionsAdminPluginsObj) + @UseGuards(AdminAuthGuards) + @UseGuards(OnlyForDevelopment) + async admin__core_plugins__permissions_admin__create_edit( + @Args() args: CreateEditAdminPermissionsAdminPluginsArgs, + ): Promise { + return await this.service.createEdit(args); + } +} diff --git a/packages/backend/src/core/admin/plugins/permissions-admin/create-edit/create-edit.service.ts b/packages/backend/src/core/admin/plugins/permissions-admin/create-edit/create-edit.service.ts new file mode 100644 index 000000000..a92c7b287 --- /dev/null +++ b/packages/backend/src/core/admin/plugins/permissions-admin/create-edit/create-edit.service.ts @@ -0,0 +1,172 @@ +import { + ABSOLUTE_PATHS_BACKEND, + ConfigPlugin, + CustomError, + InternalServerError, + NotFoundError, +} from '@/index'; +import { Injectable } from '@nestjs/common'; +import { existsSync } from 'fs'; +import { readFile, writeFile } from 'fs/promises'; + +import { ShowAdminPermissionsAdminPluginsObj } from '../show/show.dto'; +import { CreateEditAdminPermissionsAdminPluginsArgs } from './create-edit.dto'; + +@Injectable() +export class CreateEditAdminPermissionsAdminPluginsService { + async createEdit({ + id, + old_id, + plugin_code, + parent_id, + }: CreateEditAdminPermissionsAdminPluginsArgs): Promise { + const pathConfig = ABSOLUTE_PATHS_BACKEND.plugin({ + code: plugin_code, + }).config; + if (!existsSync(pathConfig)) { + throw new NotFoundError('Plugin'); + } + + const config: ConfigPlugin = JSON.parse(await readFile(pathConfig, 'utf8')); + + const parent = config.permissions_admin?.find( + permission => permission.id === parent_id, + ); + + if (!parent && parent_id) { + throw new NotFoundError('Parent permission for the plugin'); + } + + // Check if the id already exists + if (old_id !== id) { + const existsPermission = parent + ? parent.children.find(child => child.id === id) + : config.permissions_admin?.find(permission => permission.id === id); + + if (existsPermission) { + throw new CustomError({ + message: 'Permission already exists', + code: 'PERMISSION_ALREADY_EXISTS', + }); + } + } + + // Edit + if (old_id) { + const oldPermission = parent + ? parent.children.find(child => child.id === old_id) + : config.permissions_admin?.find( + permission => permission.id === old_id, + ); + + if (!oldPermission) { + throw new NotFoundError('Permission with the old id for the plugin'); + } + + let newConfig: ConfigPlugin; + + if (parent) { + newConfig = { + ...config, + permissions_admin: config.permissions_admin?.map(permission => { + if (permission.id === parent.id) { + return { + ...permission, + children: permission.children.map(child => { + if (child.id === old_id) { + return { + id, + }; + } + + return child; + }), + }; + } + + return permission; + }), + }; + } else { + newConfig = { + ...config, + permissions_admin: config.permissions_admin?.map(permission => { + if (permission.id === old_id) { + return { + id, + children: permission.children, + }; + } + + return permission; + }), + }; + } + + await writeFile(pathConfig, JSON.stringify(newConfig, null, 2)); + + const returnValue = parent + ? newConfig.permissions_admin?.find( + permission => permission.id === parent.id, + ) + : newConfig.permissions_admin?.find(permission => permission.id === id); + + if (!returnValue) { + throw new InternalServerError(); + } + + return { + id, + children: [], + }; + } + + let newConfig: ConfigPlugin; + if (parent) { + newConfig = { + ...config, + permissions_admin: (config.permissions_admin ?? []).map(permission => { + if (permission.id === parent.id) { + return { + ...permission, + children: [ + ...permission.children, + { + id, + children: [], + }, + ], + }; + } + + return permission; + }), + }; + } else { + newConfig = { + ...config, + permissions_admin: [ + ...(config.permissions_admin ?? []), + { + id, + children: [], + }, + ], + }; + } + + await writeFile(pathConfig, JSON.stringify(newConfig, null, 2)); + + const returnValue = parent + ? newConfig.permissions_admin?.find( + permission => permission.id === parent.id, + ) + : newConfig.permissions_admin?.find(permission => permission.id === id); + + if (!returnValue) { + throw new InternalServerError(); + } + + return returnValue; + } +} diff --git a/packages/backend/src/core/admin/plugins/permissions-admin/delete/delete.dto.ts b/packages/backend/src/core/admin/plugins/permissions-admin/delete/delete.dto.ts new file mode 100644 index 000000000..9af9676f2 --- /dev/null +++ b/packages/backend/src/core/admin/plugins/permissions-admin/delete/delete.dto.ts @@ -0,0 +1,13 @@ +import { ArgsType, Field } from '@nestjs/graphql'; + +@ArgsType() +export class DeleteAdminPermissionsAdminPluginsArgs { + @Field(() => String) + id: string; + + @Field(() => String, { nullable: true }) + parent_id?: string; + + @Field(() => String) + plugin_code: string; +} diff --git a/packages/backend/src/core/admin/plugins/permissions-admin/delete/delete.resolver.ts b/packages/backend/src/core/admin/plugins/permissions-admin/delete/delete.resolver.ts new file mode 100644 index 000000000..de04afcff --- /dev/null +++ b/packages/backend/src/core/admin/plugins/permissions-admin/delete/delete.resolver.ts @@ -0,0 +1,22 @@ +import { AdminAuthGuards, OnlyForDevelopment } from '@/utils'; +import { UseGuards } from '@nestjs/common'; +import { Args, Mutation, Resolver } from '@nestjs/graphql'; + +import { DeleteAdminPermissionsAdminPluginsArgs } from './delete.dto'; +import { DeleteAdminPermissionsAdminPluginsService } from './delete.service'; + +@Resolver() +export class DeleteAdminPermissionsAdminPluginsResolver { + constructor( + private readonly service: DeleteAdminPermissionsAdminPluginsService, + ) {} + + @Mutation(() => String) + @UseGuards(AdminAuthGuards) + @UseGuards(OnlyForDevelopment) + async admin__core_plugins__permissions_admin__delete( + @Args() args: DeleteAdminPermissionsAdminPluginsArgs, + ): Promise { + return await this.service.delete(args); + } +} diff --git a/packages/backend/src/core/admin/plugins/permissions-admin/delete/delete.service.ts b/packages/backend/src/core/admin/plugins/permissions-admin/delete/delete.service.ts new file mode 100644 index 000000000..0a79fa058 --- /dev/null +++ b/packages/backend/src/core/admin/plugins/permissions-admin/delete/delete.service.ts @@ -0,0 +1,60 @@ +import { ABSOLUTE_PATHS_BACKEND, ConfigPlugin, NotFoundError } from '@/index'; +import { Injectable } from '@nestjs/common'; +import { existsSync } from 'fs'; +import { readFile, writeFile } from 'fs/promises'; + +import { DeleteAdminPermissionsAdminPluginsArgs } from './delete.dto'; + +@Injectable() +export class DeleteAdminPermissionsAdminPluginsService { + async delete({ + id, + plugin_code, + parent_id, + }: DeleteAdminPermissionsAdminPluginsArgs): Promise { + const pathConfig = ABSOLUTE_PATHS_BACKEND.plugin({ + code: plugin_code, + }).config; + if (!existsSync(pathConfig)) { + throw new NotFoundError('Plugin'); + } + + const config: ConfigPlugin = JSON.parse(await readFile(pathConfig, 'utf8')); + + const parent = config.permissions_admin?.find( + permission => permission.id === parent_id, + ); + + if (!parent && parent_id) { + throw new NotFoundError('Parent permission for the plugin'); + } + + const existsPermission = parent + ? parent.children.find(child => child.id === id) + : config.permissions_admin?.find(permission => permission.id === id); + + if (!existsPermission) { + throw new NotFoundError('Permission'); + } + + if (parent) { + config.permissions_admin = config.permissions_admin?.map(permission => { + if (permission.id === parent_id) { + permission.children = permission.children.filter( + child => child.id !== id, + ); + } + + return permission; + }); + } else { + config.permissions_admin = config.permissions_admin?.filter( + permission => permission.id !== id, + ); + } + + await writeFile(pathConfig, JSON.stringify(config, null, 2)); + + return 'Permission deleted'; + } +} diff --git a/packages/backend/src/core/admin/plugins/permissions-admin/permissions-admin.module.ts b/packages/backend/src/core/admin/plugins/permissions-admin/permissions-admin.module.ts new file mode 100644 index 000000000..fb2fbdb14 --- /dev/null +++ b/packages/backend/src/core/admin/plugins/permissions-admin/permissions-admin.module.ts @@ -0,0 +1,20 @@ +import { Module } from '@nestjs/common'; + +import { CreateEditAdminPermissionsAdminPluginsResolver } from './create-edit/create-edit.resolver'; +import { CreateEditAdminPermissionsAdminPluginsService } from './create-edit/create-edit.service'; +import { DeleteAdminPermissionsAdminPluginsResolver } from './delete/delete.resolver'; +import { DeleteAdminPermissionsAdminPluginsService } from './delete/delete.service'; +import { ShowAdminPermissionsAdminPluginsResolver } from './show/show.resolver'; +import { ShowAdminPermissionsAdminPluginsService } from './show/show.service'; + +@Module({ + providers: [ + ShowAdminPermissionsAdminPluginsResolver, + ShowAdminPermissionsAdminPluginsService, + CreateEditAdminPermissionsAdminPluginsService, + CreateEditAdminPermissionsAdminPluginsResolver, + DeleteAdminPermissionsAdminPluginsResolver, + DeleteAdminPermissionsAdminPluginsService, + ], +}) +export class AdminPermissionsAdminPluginsModule {} diff --git a/packages/backend/src/core/admin/plugins/permissions-admin/show/show.dto.ts b/packages/backend/src/core/admin/plugins/permissions-admin/show/show.dto.ts new file mode 100644 index 000000000..c0d1e313b --- /dev/null +++ b/packages/backend/src/core/admin/plugins/permissions-admin/show/show.dto.ts @@ -0,0 +1,13 @@ +import { Field, ObjectType } from '@nestjs/graphql'; + +@ObjectType() +export class ShowAdminPermissionsAdminPlugins { + @Field(() => String) + id: string; +} + +@ObjectType() +export class ShowAdminPermissionsAdminPluginsObj extends ShowAdminPermissionsAdminPlugins { + @Field(() => [ShowAdminPermissionsAdminPlugins]) + children: ShowAdminPermissionsAdminPlugins[]; +} diff --git a/packages/backend/src/core/admin/plugins/permissions-admin/show/show.resolver.ts b/packages/backend/src/core/admin/plugins/permissions-admin/show/show.resolver.ts new file mode 100644 index 000000000..eef9b10d0 --- /dev/null +++ b/packages/backend/src/core/admin/plugins/permissions-admin/show/show.resolver.ts @@ -0,0 +1,22 @@ +import { AdminAuthGuards, OnlyForDevelopment } from '@/utils'; +import { UseGuards } from '@nestjs/common'; +import { Args, Query, Resolver } from '@nestjs/graphql'; + +import { ShowAdminPermissionsAdminPluginsObj } from './show.dto'; +import { ShowAdminPermissionsAdminPluginsService } from './show.service'; + +@Resolver() +export class ShowAdminPermissionsAdminPluginsResolver { + constructor( + private readonly service: ShowAdminPermissionsAdminPluginsService, + ) {} + + @Query(() => [ShowAdminPermissionsAdminPluginsObj]) + @UseGuards(AdminAuthGuards) + @UseGuards(OnlyForDevelopment) + async admin__core_plugins__permissions_admin__show( + @Args('plugin_code', { type: () => String }) plugin_code: string, + ): Promise { + return await this.service.show({ plugin_code }); + } +} diff --git a/packages/backend/src/core/admin/plugins/permissions-admin/show/show.service.ts b/packages/backend/src/core/admin/plugins/permissions-admin/show/show.service.ts new file mode 100644 index 000000000..38e491d6c --- /dev/null +++ b/packages/backend/src/core/admin/plugins/permissions-admin/show/show.service.ts @@ -0,0 +1,26 @@ +import { ABSOLUTE_PATHS_BACKEND, ConfigPlugin, NotFoundError } from '@/index'; +import { Injectable } from '@nestjs/common'; +import { existsSync } from 'fs'; +import { readFile } from 'fs/promises'; + +import { ShowAdminPermissionsAdminPluginsObj } from './show.dto'; + +@Injectable() +export class ShowAdminPermissionsAdminPluginsService { + async show({ + plugin_code, + }: { + plugin_code: string; + }): Promise { + const pathConfig = ABSOLUTE_PATHS_BACKEND.plugin({ + code: plugin_code, + }).config; + if (!existsSync(pathConfig)) { + throw new NotFoundError('Plugin'); + } + + const config: ConfigPlugin = JSON.parse(await readFile(pathConfig, 'utf8')); + + return config.permissions_admin ?? []; + } +} diff --git a/packages/backend/src/core/admin/plugins/plugins.module.ts b/packages/backend/src/core/admin/plugins/plugins.module.ts index fea568b39..7360984fe 100644 --- a/packages/backend/src/core/admin/plugins/plugins.module.ts +++ b/packages/backend/src/core/admin/plugins/plugins.module.ts @@ -11,6 +11,7 @@ import { EditAdminPluginsService } from './edit/edit.service'; import { ChangeFilesAdminPluginsService } from './helpers/files/change/change.service'; import { CreateFilesAdminPluginsService } from './helpers/files/create/create-files.service'; import { AdminNavPluginsModule } from './nav/nav-plugins.module'; +import { AdminPermissionsAdminPluginsModule } from './permissions-admin/permissions-admin.module'; import { ShowAdminPluginsResolver } from './show/show.resolver'; import { ShowAdminPluginsService } from './show/show.service'; import { UploadAdminPluginsResolver } from './upload/upload.resolver'; @@ -33,6 +34,6 @@ import { UploadAdminPluginsService } from './upload/upload.service'; EditAdminPluginsResolver, EditAdminPluginsService, ], - imports: [AdminNavPluginsModule], + imports: [AdminNavPluginsModule, AdminPermissionsAdminPluginsModule], }) export class AdminPluginsModule {} diff --git a/packages/backend/src/core/admin/staff/administrators/show/show.dto.ts b/packages/backend/src/core/admin/staff/administrators/show/show.dto.ts index b589bcb46..eddbb77a2 100644 --- a/packages/backend/src/core/admin/staff/administrators/show/show.dto.ts +++ b/packages/backend/src/core/admin/staff/administrators/show/show.dto.ts @@ -62,7 +62,7 @@ export const UserOrGroupCoreStaffUnion = createUnionType({ return User; } - if (Array.isArray(value.name)) { + if (Array.isArray(value.group_name)) { return StaffGroupUser; } diff --git a/packages/backend/src/core/admin/staff/administrators/show/show.service.ts b/packages/backend/src/core/admin/staff/administrators/show/show.service.ts index c2f7e91b8..6a818e1fa 100644 --- a/packages/backend/src/core/admin/staff/administrators/show/show.service.ts +++ b/packages/backend/src/core/admin/staff/administrators/show/show.service.ts @@ -1,4 +1,6 @@ +import { StringLanguageHelper } from '@/core/helpers/string_language/helpers.service'; import { core_admin_permissions } from '@/database/schema/admins'; +import { core_groups } from '@/database/schema/groups'; import { NotFoundError } from '@/errors'; import { inputPaginationCursor, outputPagination } from '@/functions'; import { SortDirectionEnum } from '@/utils'; @@ -14,7 +16,10 @@ import { @Injectable() export class ShowAdminStaffAdministratorsService { - constructor(private readonly databaseService: InternalDatabaseService) {} + constructor( + private readonly databaseService: InternalDatabaseService, + private readonly stringLanguageHelper: StringLanguageHelper, + ) {} async show({ cursor, @@ -77,11 +82,18 @@ export class ShowAdminStaffAdministratorsService { throw new NotFoundError('Group'); } + const group_name = await this.stringLanguageHelper.get({ + database: core_groups, + item_ids: [edge.group.id], + plugin_code: 'core', + variables: ['name'], + }); + return { ...edge, user_or_group: { ...edge.group, - group_name: [], + group_name, }, }; }), diff --git a/packages/backend/src/core/admin/staff/moderators/show/show.service.ts b/packages/backend/src/core/admin/staff/moderators/show/show.service.ts index 0b86a75fd..16d0a573a 100644 --- a/packages/backend/src/core/admin/staff/moderators/show/show.service.ts +++ b/packages/backend/src/core/admin/staff/moderators/show/show.service.ts @@ -1,3 +1,5 @@ +import { StringLanguageHelper } from '@/core/helpers/string_language/helpers.service'; +import { core_groups } from '@/database/schema/groups'; import { core_moderators_permissions } from '@/database/schema/moderators'; import { NotFoundError } from '@/errors'; import { inputPaginationCursor, outputPagination } from '@/functions'; @@ -8,13 +10,17 @@ import { Injectable } from '@nestjs/common'; import { count } from 'drizzle-orm'; import { + ShowAdminStaffModerators, ShowAdminStaffModeratorsArgs, ShowAdminStaffModeratorsObj, } from './show.dto'; @Injectable() export class ShowAdminStaffModeratorsService { - constructor(private readonly databaseService: InternalDatabaseService) {} + constructor( + private readonly databaseService: InternalDatabaseService, + private readonly stringLanguageHelper: StringLanguageHelper, + ) {} async show({ cursor, @@ -50,42 +56,60 @@ export class ShowAdminStaffModeratorsService { }, }, }, + columns: { + id: true, + user_id: true, + updated: true, + group_id: true, + created: true, + protected: true, + unrestricted: true, + }, }); const totalCount = await this.databaseService.db .select({ count: count() }) .from(core_moderators_permissions); - return outputPagination({ - edges: await Promise.all( - edges.map(async edge => { - if (edge.user_id) { - const user = await getUser({ - id: edge.user_id, - db: this.databaseService.db, - }); - - return { - ...edge, - user_or_group: { - ...user, - }, - }; - } - - if (!edge.group) { - throw new NotFoundError('Group'); - } + const processedEdges: ShowAdminStaffModerators[] = await Promise.all( + edges.map(async edge => { + if (edge.user_id) { + const user = await getUser({ + id: edge.user_id, + db: this.databaseService.db, + }); return { ...edge, user_or_group: { - ...edge.group, - group_name: [], + ...user, }, }; - }), - ), + } + + if (!edge.group) { + throw new NotFoundError('Group'); + } + + const group_name = await this.stringLanguageHelper.get({ + database: core_groups, + item_ids: [edge.group.id], + plugin_code: 'core', + variables: ['name'], + }); + + return { + ...edge, + user_or_group: { + ...edge.group, + group_name, + }, + }; + }), + ); + + return outputPagination({ + edges: processedEdges, totalCount, first, cursor, diff --git a/packages/backend/src/providers/plugins.type.ts b/packages/backend/src/providers/plugins.type.ts index 6cff510a5..d2e93c42b 100644 --- a/packages/backend/src/providers/plugins.type.ts +++ b/packages/backend/src/providers/plugins.type.ts @@ -6,6 +6,10 @@ interface NavPluginInfoJSONType { keywords: string[]; } +interface PermissionsAdminPluginInfoJSONType { + id: string; +} + export interface NavPluginInfoJSONTypeWithChildren extends NavPluginInfoJSONType { children?: NavPluginInfoJSONType[]; @@ -15,6 +19,9 @@ export interface NavPluginInfoJSONTypeWithChildren export interface PluginInfoJSONType extends CreateAdminPluginsArgs { allow_default: boolean; nav: NavPluginInfoJSONTypeWithChildren[]; + permissions_admin?: ({ + children: PermissionsAdminPluginInfoJSONType[]; + } & PermissionsAdminPluginInfoJSONType)[]; version: string; version_code: number; } diff --git a/packages/create-vitnode-app/.npmignore b/packages/create-vitnode-app/.npmignore index 925fca6fa..4cd0f61f3 100644 --- a/packages/create-vitnode-app/.npmignore +++ b/packages/create-vitnode-app/.npmignore @@ -8,4 +8,4 @@ /index.ts /tsconfig.json /tsup.config.ts -/.eslintrc.json \ No newline at end of file +/eslint.config.mjs \ No newline at end of file diff --git a/packages/frontend/.npmignore b/packages/frontend/.npmignore index f79182cea..edfb23c5d 100644 --- a/packages/frontend/.npmignore +++ b/packages/frontend/.npmignore @@ -3,7 +3,7 @@ !/src/global.css /scripts /node_modules -/.eslintrc.json +/eslint.config.mjs /codegen.ts /next-env.d.ts /tsconfig.json diff --git a/packages/frontend/src/components/drag&drop/sortable-list/item.tsx b/packages/frontend/src/components/drag&drop/sortable-list/item.tsx index c9c2cecdd..c4448ecce 100644 --- a/packages/frontend/src/components/drag&drop/sortable-list/item.tsx +++ b/packages/frontend/src/components/drag&drop/sortable-list/item.tsx @@ -16,6 +16,7 @@ export function SortableTreeItem>({ id, onCollapse, children, + isDragEnd = true, ...props }: { childCount?: number; @@ -25,6 +26,7 @@ export function SortableTreeItem>({ depth: number; id: number | string; indentationWidth: number; + isDragEnd?: boolean; onCollapse?: () => void; } & Omit, 'id' | 'style'>) { const { @@ -71,16 +73,18 @@ export function SortableTreeItem>({ } as React.CSSProperties } > - + {isDragEnd && ( + + )} {onCollapse && ( - - - - {t('delete')} - - + + + + + diff --git a/packages/frontend/src/views/admin/views/core/plugins/views/dev/nav/item/actions/edit.tsx b/packages/frontend/src/views/admin/views/core/plugins/views/dev/nav/item/actions/edit.tsx index edc59d59b..bdfa683d5 100644 --- a/packages/frontend/src/views/admin/views/core/plugins/views/dev/nav/item/actions/edit.tsx +++ b/packages/frontend/src/views/admin/views/core/plugins/views/dev/nav/item/actions/edit.tsx @@ -7,12 +7,7 @@ import { DialogTrigger, } from '@/components/ui/dialog'; import { Loader } from '@/components/ui/loader'; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from '@/components/ui/tooltip'; +import { TooltipWrapper } from '@/components/ui/tooltip'; import { Pencil } from 'lucide-react'; import { useTranslations } from 'next-intl'; import React from 'react'; @@ -30,19 +25,13 @@ export const EditActionTableNavDevPluginAdmin = ( return ( - - - - - - - - - {t('edit.title')} - - + + + + + diff --git a/packages/frontend/src/views/admin/views/core/plugins/views/dev/nav/item/item.tsx b/packages/frontend/src/views/admin/views/core/plugins/views/dev/nav/item/item.tsx index ff670b1c1..7717ffc9a 100644 --- a/packages/frontend/src/views/admin/views/core/plugins/views/dev/nav/item/item.tsx +++ b/packages/frontend/src/views/admin/views/core/plugins/views/dev/nav/item/item.tsx @@ -1,5 +1,6 @@ import { Admin__Core_Plugins__Nav__ShowQuery } from '@/graphql/queries/admin/plugins/dev/nav/admin__core_plugins__nav__show.generated'; import { ShowAdminNavPluginsObj } from '@/graphql/types'; +import { TextAndIconsAsideAdmin } from '@/views/admin/layout/admin-layout'; import { useParams } from 'next/navigation'; import { useTranslations } from 'next-intl'; @@ -8,29 +9,28 @@ import { ActionsTableNavDevPluginAdmin } from './actions/actions'; export const ItemContentNavDevPluginAdmin = ({ data, parentId, - icons, + textsAndIcons, dataFromSSR, }: { data: ShowAdminNavPluginsObj; dataFromSSR: Admin__Core_Plugins__Nav__ShowQuery['admin__core_plugins__nav__show']; - icons: { icon: React.ReactNode; id: string }[]; parentId?: string; + textsAndIcons: TextAndIconsAsideAdmin[]; }) => { const { code: pluginCode } = useParams(); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - const t = useTranslations(`admin_${pluginCode}.nav`); const tAdmin = useTranslations('admin.core.plugins.dev.nav'); const langKey = parentId ? `${parentId}_${data.code}` : data.code; + const textAndIcon = textsAndIcons.find(item => item.id === langKey); + + if (!textAndIcon) return null; + return ( <>
- {icons.find(icon => icon.id === data.code)?.icon} - {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */} - {/* @ts-expect-error */} - {t(langKey)} + {textAndIcon.icon} + {textAndIcon.text}

{tAdmin.rich('lang_key', { @@ -46,22 +46,24 @@ export const ItemContentNavDevPluginAdmin = ({ ), })}

-

- {tAdmin.rich('keywords', { - keywords: () => ( - - {data.keywords.join(', ')} - - ), - })} -

+ {data.keywords.length > 0 && ( +

+ {tAdmin.rich('keywords', { + keywords: () => ( + + {data.keywords.join(', ')} + + ), + })} +

+ )}
); diff --git a/packages/frontend/src/views/admin/views/core/plugins/views/dev/nav/nav.tsx b/packages/frontend/src/views/admin/views/core/plugins/views/dev/nav/nav.tsx index 65fbf756c..0b8b04383 100644 --- a/packages/frontend/src/views/admin/views/core/plugins/views/dev/nav/nav.tsx +++ b/packages/frontend/src/views/admin/views/core/plugins/views/dev/nav/nav.tsx @@ -1,4 +1,3 @@ -import { flattenTree } from '@/components/drag&drop/sortable-list/flat'; import { Icon } from '@/components/icon/icon'; import { HeaderContent } from '@/components/ui/header-content'; import { fetcher } from '@/graphql/fetcher'; @@ -7,7 +6,7 @@ import { Admin__Core_Plugins__Nav__ShowQuery, Admin__Core_Plugins__Nav__ShowQueryVariables, } from '@/graphql/queries/admin/plugins/dev/nav/admin__core_plugins__nav__show.generated'; -import { ShowAdminNavPluginsObj } from '@/graphql/types'; +import { TextAndIconsAsideAdmin } from '@/views/admin/layout/admin-layout'; import { getTranslations } from 'next-intl/server'; import { CreateNavDevPluginAdmin } from './actions/create/create'; @@ -27,52 +26,69 @@ const getData = async ( return data; }; -interface NavItem extends Omit { - children: NavItem[]; - id: string; -} - export const NavDevPluginAdminView = async ({ params, }: { params: Promise<{ code: string }>; }) => { const { code } = await params; - const [data, t] = await Promise.all([ + const [data, t, tGlobal] = await Promise.all([ getData({ pluginCode: code }), getTranslations('admin.core.plugins.dev.nav'), + getTranslations(), ]); - const flattenData = flattenTree( - data.admin__core_plugins__nav__show.map(nav => ({ - id: nav.code, - ...nav, - children: (nav.children?.map(child => ({ - id: `${nav.code}_${child.code}`, - ...child, - children: [], - })) ?? []) as NavItem[], - })), - ); + // Flat map to remove children + const nav: { + code: string; + icon?: string; + parent_icon?: string; + parent_nav_code?: string; + plugin: string; + }[] = data.admin__core_plugins__nav__show.flatMap(nav => { + const children = nav.children ?? []; + const mappedChildren = children.map(child => ({ + parent_nav_code: nav.children ? nav.code : undefined, + ...child, + parent_icon: nav.icon, + plugin: code, + })); + + return [{ ...nav, plugin: code }, ...mappedChildren]; + }); + + const textsAndIcons: TextAndIconsAsideAdmin[] = nav.map(item => { + const id = item.parent_nav_code + ? `${item.parent_nav_code}_${item.code}` + : item.code; - const icons: { - icon: React.ReactNode; - id: string; - }[] = flattenData.map(item => ({ - icon: item.icon ? : null, - id: item.id.toString(), - })); + return { + id, + parent_text: item.parent_nav_code + ? // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + tGlobal(`admin_${item.plugin}.nav.${item.parent_nav_code}`) + : undefined, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + text: tGlobal(`admin_${item.plugin}.nav.${id}`), + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + plugin: tGlobal(`admin_${item.plugin}.nav.title`), + icon: item.icon ? : null, + }; + }); return ( <> - + ); }; diff --git a/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/actions.tsx b/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/actions.tsx new file mode 100644 index 000000000..7f1d18f5d --- /dev/null +++ b/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/actions.tsx @@ -0,0 +1,47 @@ +'use client'; + +import { Button } from '@/components/ui/button'; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/components/ui/dialog'; +import { Loader } from '@/components/ui/loader'; +import { Plus } from 'lucide-react'; +import { useTranslations } from 'next-intl'; +import React from 'react'; + +const CreateEditContent = React.lazy(async () => + import('./create-edit/create-edit').then(module => ({ + default: module.CreateEditPermissionsAdminDevPluginAdmin, + })), +); + +export const ActionsPermissionsAdminDevPluginAdminView = ( + props: React.ComponentProps, +) => { + const t = useTranslations('core.global'); + + return ( + + + + + + + + {t('create')} + + + }> + + + + + ); +}; diff --git a/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/content.tsx b/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/content.tsx new file mode 100644 index 000000000..46537fbb2 --- /dev/null +++ b/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/content.tsx @@ -0,0 +1,36 @@ +'use client'; + +import { DragAndDropSortableList } from '@/components/drag&drop/sortable-list/list'; +import { Admin__Core_Plugins__Permissions_Admin__ShowQuery } from '@/graphql/queries/admin/plugins/dev/permissions-admin/admin__core_plugins__permissions_admin__show.generated'; + +import { ItemPermissionsAdminDevPluginAdmin } from './item/item'; + +export const ContentPermissionsAdminDevPluginAdminView = ( + dataFromSSR: Admin__Core_Plugins__Permissions_Admin__ShowQuery, +) => { + const data = dataFromSSR.admin__core_plugins__permissions_admin__show.map( + item => ({ + ...item, + children: item.children.map(child => ({ + ...child, + children: [], + })), + }), + ); + + return ( + { + return ( + + ); + }} + data={data} + maxDepth={1} + /> + ); +}; diff --git a/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/create-edit/create-edit.tsx b/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/create-edit/create-edit.tsx new file mode 100644 index 000000000..b368f1fb6 --- /dev/null +++ b/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/create-edit/create-edit.tsx @@ -0,0 +1,54 @@ +import { AutoForm } from '@/components/form/auto-form'; +import { + AutoFormInput, + AutoFormInputProps, +} from '@/components/form/fields/input'; +import { AutoFormSelect } from '@/components/form/fields/select'; +import { Admin__Core_Plugins__Permissions_Admin__ShowQuery } from '@/graphql/queries/admin/plugins/dev/permissions-admin/admin__core_plugins__permissions_admin__show.generated'; +import { useTranslations } from 'next-intl'; + +import { useCreateEditPermissionAdminPluginAdmin } from './hooks/use-create-edit-permission-admin-plugin-admin'; + +export const CreateEditPermissionsAdminDevPluginAdmin = ({ + dataFromSSR, + data, + parentId, +}: { + data?: Admin__Core_Plugins__Permissions_Admin__ShowQuery['admin__core_plugins__permissions_admin__show'][0]; + dataFromSSR: Admin__Core_Plugins__Permissions_Admin__ShowQuery; + parentId?: string; +}) => { + const t = useTranslations( + 'admin.core.plugins.dev.permissions-admin.create_edit', + ); + const { formSchema, onSubmit } = useCreateEditPermissionAdminPluginAdmin({ + dataFromSSR, + data, + parentId, + }); + + return ( + + ); +}; diff --git a/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/create-edit/hooks/mutation-api.ts b/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/create-edit/hooks/mutation-api.ts new file mode 100644 index 000000000..e032c5b19 --- /dev/null +++ b/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/create-edit/hooks/mutation-api.ts @@ -0,0 +1,29 @@ +'use server'; + +import { fetcher } from '@/graphql/fetcher'; +import { + Admin__Core_Plugins__Permissions_Admin__Create_Edit, + Admin__Core_Plugins__Permissions_Admin__Create_EditMutation, + Admin__Core_Plugins__Permissions_Admin__Create_EditMutationVariables, +} from '@/graphql/mutations/admin/plugins/dev/permissions-admin/admin__core_plugins__permissions_admin__create_edit.generated'; +import { revalidatePath } from 'next/cache'; + +export const mutationApi = async ( + variables: Admin__Core_Plugins__Permissions_Admin__Create_EditMutationVariables, +) => { + try { + await fetcher< + Admin__Core_Plugins__Permissions_Admin__Create_EditMutation, + Admin__Core_Plugins__Permissions_Admin__Create_EditMutationVariables + >({ + query: Admin__Core_Plugins__Permissions_Admin__Create_Edit, + variables, + }); + + revalidatePath('/', 'layout'); + } catch (error) { + const e = error as Error; + + return { error: e.message }; + } +}; diff --git a/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/create-edit/hooks/use-create-edit-permission-admin-plugin-admin.ts b/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/create-edit/hooks/use-create-edit-permission-admin-plugin-admin.ts new file mode 100644 index 000000000..7dbb7c45f --- /dev/null +++ b/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/create-edit/hooks/use-create-edit-permission-admin-plugin-admin.ts @@ -0,0 +1,82 @@ +import { useDialog } from '@/components/ui/dialog'; +import { Admin__Core_Plugins__Permissions_Admin__ShowQuery } from '@/graphql/queries/admin/plugins/dev/permissions-admin/admin__core_plugins__permissions_admin__show.generated'; +import { useParams } from 'next/navigation'; +import { useTranslations } from 'next-intl'; +import { UseFormReturn } from 'react-hook-form'; +import { toast } from 'sonner'; +import * as z from 'zod'; + +import { mutationApi } from './mutation-api'; + +export const useCreateEditPermissionAdminPluginAdmin = ({ + dataFromSSR, + data, + parentId, +}: { + data?: Admin__Core_Plugins__Permissions_Admin__ShowQuery['admin__core_plugins__permissions_admin__show'][0]; + dataFromSSR: Admin__Core_Plugins__Permissions_Admin__ShowQuery; + parentId: string | undefined; +}) => { + const t = useTranslations( + 'admin.core.plugins.dev.permissions-admin.create_edit', + ); + const tCore = useTranslations('core.global.errors'); + const formSchema = z.object({ + id: z + .string() + .min(3) + .max(50) + .default(data?.id ?? ''), + parent_id: z + .enum([ + 'null', + ...dataFromSSR.admin__core_plugins__permissions_admin__show.map( + item => item.id, + ), + ]) + .default(parentId ?? 'null'), + }); + const { code } = useParams(); + const { setOpen } = useDialog(); + + const onSubmit = async ( + values: z.infer, + form: UseFormReturn>, + ) => { + if (!code) return; + let error = ''; + + const mutation = await mutationApi({ + ...values, + parentId: values.parent_id === 'null' ? undefined : values.parent_id, + pluginCode: Array.isArray(code) ? code[0] : code, + oldId: data?.id, + }); + + if (mutation?.error) { + error = mutation.error; + } + + if (error) { + if (error === 'PERMISSION_ALREADY_EXISTS') { + form.setError('id', { + type: 'manual', + message: t('id.exists'), + }); + + return; + } + + toast.error(tCore('title'), { + description: tCore('internal_server_error'), + }); + + return; + } + + setOpen?.(false); + toast.success(t('create_success')); + }; + + return { formSchema, onSubmit }; +}; diff --git a/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/item/actions/actions.tsx b/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/item/actions/actions.tsx new file mode 100644 index 000000000..550005a30 --- /dev/null +++ b/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/item/actions/actions.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +import { DeleteActionItemPermissionsAdminDevPluginAdmin } from './delete/delete'; +import { EditActionItemPermissionsAdminDevPluginAdmin } from './edit'; + +export const ActionsItemPermissionsAdminDevPluginAdmin = ( + props: React.ComponentProps< + typeof EditActionItemPermissionsAdminDevPluginAdmin + >, +) => { + return ( +
+ + +
+ ); +}; diff --git a/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/item/actions/delete/content.tsx b/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/item/actions/delete/content.tsx new file mode 100644 index 000000000..6c2bc7dfd --- /dev/null +++ b/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/item/actions/delete/content.tsx @@ -0,0 +1,55 @@ +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; +import { + AlertDialogCancel, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from '@/components/ui/alert-dialog'; +import { Button } from '@/components/ui/button'; +import { Admin__Core_Plugins__Permissions_Admin__ShowQuery } from '@/graphql/queries/admin/plugins/dev/permissions-admin/admin__core_plugins__permissions_admin__show.generated'; +import { useTranslations } from 'next-intl'; + +import { useDeletePermissionAdminPluginAdmin } from './hooks/use-delete-permission-admin-plugin-admin'; +import { SubmitDeleteActionItemPermissionsAdminDevPluginAdmin } from './submit'; + +export const ContentDeleteActionItemPermissionsAdminDevPluginAdmin = ({ + id, + parentId, + children, +}: { + parentId: string | undefined; +} & Admin__Core_Plugins__Permissions_Admin__ShowQuery['admin__core_plugins__permissions_admin__show'][0]) => { + const t = useTranslations('admin.core.plugins.dev.permissions-admin.delete'); + const tCore = useTranslations('core.global'); + const { onSubmit } = useDeletePermissionAdminPluginAdmin({ id, parentId }); + + return ( +
+ + {tCore('are_you_sure')} + + {t.rich('desc', { + id: () => {id}, + })} + + + {children.length > 0 && ( + + {t('children_warn.title')} + {t('children_warn.desc')} + + )} + + + + + + + + +
+ ); +}; diff --git a/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/item/actions/delete/delete.tsx b/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/item/actions/delete/delete.tsx new file mode 100644 index 000000000..aaf09ec0f --- /dev/null +++ b/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/item/actions/delete/delete.tsx @@ -0,0 +1,40 @@ +import { + AlertDialog, + AlertDialogContent, + AlertDialogTrigger, +} from '@/components/ui/alert-dialog'; +import { Button } from '@/components/ui/button'; +import { TooltipWrapper } from '@/components/ui/tooltip'; +import { Trash2 } from 'lucide-react'; +import { useTranslations } from 'next-intl'; +import React from 'react'; + +import { ContentDeleteActionItemPermissionsAdminDevPluginAdmin } from './content'; + +export const DeleteActionItemPermissionsAdminDevPluginAdmin = ( + props: React.ComponentProps< + typeof ContentDeleteActionItemPermissionsAdminDevPluginAdmin + >, +) => { + const t = useTranslations('core.global'); + + return ( + + + + + + + + + + + + ); +}; diff --git a/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/item/actions/delete/hooks/mutation-api.ts b/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/item/actions/delete/hooks/mutation-api.ts new file mode 100644 index 000000000..cbe7b8ef6 --- /dev/null +++ b/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/item/actions/delete/hooks/mutation-api.ts @@ -0,0 +1,29 @@ +'use server'; + +import { fetcher } from '@/graphql/fetcher'; +import { + Admin__Core_Plugins__Permissions_Admin__Delete, + Admin__Core_Plugins__Permissions_Admin__DeleteMutation, + Admin__Core_Plugins__Permissions_Admin__DeleteMutationVariables, +} from '@/graphql/mutations/admin/plugins/dev/permissions-admin/admin__core_plugins__permissions_admin__delete.generated'; +import { revalidatePath } from 'next/cache'; + +export const mutationApi = async ( + variables: Admin__Core_Plugins__Permissions_Admin__DeleteMutationVariables, +) => { + try { + await fetcher< + Admin__Core_Plugins__Permissions_Admin__DeleteMutation, + Admin__Core_Plugins__Permissions_Admin__DeleteMutationVariables + >({ + query: Admin__Core_Plugins__Permissions_Admin__Delete, + variables, + }); + + revalidatePath('/', 'layout'); + } catch (error) { + const e = error as Error; + + return { error: e.message }; + } +}; diff --git a/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/item/actions/delete/hooks/use-delete-permission-admin-plugin-admin.ts b/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/item/actions/delete/hooks/use-delete-permission-admin-plugin-admin.ts new file mode 100644 index 000000000..7759296d5 --- /dev/null +++ b/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/item/actions/delete/hooks/use-delete-permission-admin-plugin-admin.ts @@ -0,0 +1,47 @@ +import { useAlertDialog } from '@/components/ui/alert-dialog'; +import { Admin__Core_Plugins__Permissions_Admin__ShowQuery } from '@/graphql/queries/admin/plugins/dev/permissions-admin/admin__core_plugins__permissions_admin__show.generated'; +import { useParams } from 'next/navigation'; +import { useTranslations } from 'next-intl'; +import { toast } from 'sonner'; + +import { mutationApi } from './mutation-api'; + +export const useDeletePermissionAdminPluginAdmin = ({ + id, + parentId, +}: { + parentId: string | undefined; +} & Pick< + Admin__Core_Plugins__Permissions_Admin__ShowQuery['admin__core_plugins__permissions_admin__show'][0], + 'id' +>) => { + const t = useTranslations('admin.core.plugins.dev.permissions-admin.delete'); + const tCore = useTranslations('core.global.errors'); + const { setOpen } = useAlertDialog(); + const { code: pluginCode } = useParams(); + + const onSubmit = async () => { + if (!pluginCode) return; + + const mutation = await mutationApi({ + pluginCode: Array.isArray(pluginCode) ? pluginCode[0] : pluginCode, + id, + parentId, + }); + + if (mutation?.error) { + toast.error(tCore('title'), { + description: tCore('internal_server_error'), + }); + + return; + } + + setOpen(false); + toast.success(t('success'), { + description: id, + }); + }; + + return { onSubmit }; +}; diff --git a/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/item/actions/delete/submit.tsx b/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/item/actions/delete/submit.tsx new file mode 100644 index 000000000..b101c8dc3 --- /dev/null +++ b/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/item/actions/delete/submit.tsx @@ -0,0 +1,14 @@ +import { Button } from '@/components/ui/button'; +import { useTranslations } from 'next-intl'; +import { useFormStatus } from 'react-dom'; + +export const SubmitDeleteActionItemPermissionsAdminDevPluginAdmin = () => { + const t = useTranslations('admin.core.plugins.dev.permissions-admin.delete'); + const { pending } = useFormStatus(); + + return ( + + ); +}; diff --git a/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/item/actions/edit.tsx b/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/item/actions/edit.tsx new file mode 100644 index 000000000..cd42d0b3d --- /dev/null +++ b/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/item/actions/edit.tsx @@ -0,0 +1,50 @@ +import { Button } from '@/components/ui/button'; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/components/ui/dialog'; +import { Loader } from '@/components/ui/loader'; +import { TooltipWrapper } from '@/components/ui/tooltip'; +import { Admin__Core_Plugins__Permissions_Admin__ShowQuery } from '@/graphql/queries/admin/plugins/dev/permissions-admin/admin__core_plugins__permissions_admin__show.generated'; +import { Pencil } from 'lucide-react'; +import { useTranslations } from 'next-intl'; +import React from 'react'; + +const Content = React.lazy(async () => + import('../../create-edit/create-edit').then(module => ({ + default: module.CreateEditPermissionsAdminDevPluginAdmin, + })), +); + +export const EditActionItemPermissionsAdminDevPluginAdmin = (props: { + data: Admin__Core_Plugins__Permissions_Admin__ShowQuery['admin__core_plugins__permissions_admin__show'][0]; + dataFromSSR: Admin__Core_Plugins__Permissions_Admin__ShowQuery; + parentId: string | undefined; +}) => { + const t = useTranslations('core.global'); + + return ( + + + + + + + + + + {t('edit')} + + + }> + + + + + ); +}; diff --git a/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/item/item.tsx b/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/item/item.tsx new file mode 100644 index 000000000..1c7a476ed --- /dev/null +++ b/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/item/item.tsx @@ -0,0 +1,24 @@ +import { Admin__Core_Plugins__Permissions_Admin__ShowQuery } from '@/graphql/queries/admin/plugins/dev/permissions-admin/admin__core_plugins__permissions_admin__show.generated'; + +import { ActionsItemPermissionsAdminDevPluginAdmin } from './actions/actions'; + +export const ItemPermissionsAdminDevPluginAdmin = ({ + id, + parentId, + dataFromSSR, + children, +}: { + dataFromSSR: Admin__Core_Plugins__Permissions_Admin__ShowQuery; + parentId: string | undefined; +} & Admin__Core_Plugins__Permissions_Admin__ShowQuery['admin__core_plugins__permissions_admin__show'][0]) => { + return ( +
+ {id} + +
+ ); +}; diff --git a/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/permissions-admin.tsx b/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/permissions-admin.tsx new file mode 100644 index 000000000..1b45eb4b4 --- /dev/null +++ b/packages/frontend/src/views/admin/views/core/plugins/views/dev/permissions-admin/permissions-admin.tsx @@ -0,0 +1,48 @@ +import { HeaderContent } from '@/components/ui/header-content'; +import { fetcher } from '@/graphql/fetcher'; +import { + Admin__Core_Plugins__Permissions_Admin__Show, + Admin__Core_Plugins__Permissions_Admin__ShowQuery, + Admin__Core_Plugins__Permissions_Admin__ShowQueryVariables, +} from '@/graphql/queries/admin/plugins/dev/permissions-admin/admin__core_plugins__permissions_admin__show.generated'; +import { getTranslations } from 'next-intl/server'; + +import { ActionsPermissionsAdminDevPluginAdminView } from './actions'; +import { ContentPermissionsAdminDevPluginAdminView } from './content'; + +const getData = async ( + variables: Admin__Core_Plugins__Permissions_Admin__ShowQueryVariables, +) => { + const data = await fetcher< + Admin__Core_Plugins__Permissions_Admin__ShowQuery, + Admin__Core_Plugins__Permissions_Admin__ShowQueryVariables + >({ + query: Admin__Core_Plugins__Permissions_Admin__Show, + variables, + cache: 'force-cache', + }); + + return data; +}; + +export const PermissionsAdminDevPluginAdminView = async ({ + params, +}: { + params: Promise<{ code: string }>; +}) => { + const { code } = await params; + const [t, data] = await Promise.all([ + getTranslations('admin.core.plugins.dev.permissions-admin'), + getData({ pluginCode: code }), + ]); + + return ( + <> + + + + + + + ); +}; diff --git a/packages/frontend/src/views/admin/views/members/staff/administrators/administrators-view.tsx b/packages/frontend/src/views/admin/views/members/staff/administrators/administrators-view.tsx index 65ebb224b..91467bb4d 100644 --- a/packages/frontend/src/views/admin/views/members/staff/administrators/administrators-view.tsx +++ b/packages/frontend/src/views/admin/views/members/staff/administrators/administrators-view.tsx @@ -1,3 +1,4 @@ +import { TranslationsProvider } from '@/components/translations-provider'; import { HeaderContent } from '@/components/ui/header-content'; import { fetcher } from '@/graphql/fetcher'; import { @@ -56,12 +57,17 @@ export const AdministratorsStaffAdminView = async ({ ]); return ( - <> + - + ); }; diff --git a/packages/frontend/src/views/admin/views/members/staff/administrators/table/table.tsx b/packages/frontend/src/views/admin/views/members/staff/administrators/table/table.tsx index 193fa78de..2877252da 100644 --- a/packages/frontend/src/views/admin/views/members/staff/administrators/table/table.tsx +++ b/packages/frontend/src/views/admin/views/members/staff/administrators/table/table.tsx @@ -15,14 +15,15 @@ import { ActionsTableAdministratorsStaffAdmin } from './actions/actions'; export const TableAdministratorsStaffAdmin = ({ admin__core_staff_administrators__show: { edges, pageInfo }, }: Admin__Core_Staff_Administrators__ShowQuery) => { - const t = useTranslations('admin.members.staff'); + const t = useTranslations('admin.members.staff.administrators'); + const tShared = useTranslations('admin.members.staff.shared'); return ( { if (row.user_or_group.__typename === 'User') { return ; @@ -40,18 +41,20 @@ export const TableAdministratorsStaffAdmin = ({ }, { id: 'type', - title: t('table.type'), + title: tShared('type'), cell: ({ row }) => { return ( - {t(row.user_or_group.__typename === 'User' ? 'user' : 'group')} + {tShared( + row.user_or_group.__typename === 'User' ? 'user' : 'group', + )} ); }, }, { id: 'updated', - title: t('table.updated'), + title: tShared('updated'), sortable: true, cell: ({ row }) => { return ; @@ -59,7 +62,7 @@ export const TableAdministratorsStaffAdmin = ({ }, { id: 'permissions', - title: t('table.permissions'), + title: tShared('permissions'), cell: ({ row }) => { const unrestricted = row.unrestricted; @@ -69,7 +72,7 @@ export const TableAdministratorsStaffAdmin = ({ variant={unrestricted ? 'default' : 'secondary'} > {unrestricted ? : } - {t(unrestricted ? 'unrestricted' : 'restricted')} + {tShared(unrestricted ? 'unrestricted' : 'restricted')} ); }, diff --git a/packages/frontend/src/views/admin/views/members/staff/moderators/moderators-view.tsx b/packages/frontend/src/views/admin/views/members/staff/moderators/moderators-view.tsx index f077112b2..ffae63d23 100644 --- a/packages/frontend/src/views/admin/views/members/staff/moderators/moderators-view.tsx +++ b/packages/frontend/src/views/admin/views/members/staff/moderators/moderators-view.tsx @@ -1,3 +1,4 @@ +import { TranslationsProvider } from '@/components/translations-provider'; import { HeaderContent } from '@/components/ui/header-content'; import { fetcher } from '@/graphql/fetcher'; import { @@ -56,12 +57,17 @@ export const ModeratorsStaffAdminView = async ({ ]); return ( - <> - + + - + ); }; diff --git a/packages/frontend/src/views/admin/views/members/staff/moderators/table/table.tsx b/packages/frontend/src/views/admin/views/members/staff/moderators/table/table.tsx index 9d19259ba..dd1c553bb 100644 --- a/packages/frontend/src/views/admin/views/members/staff/moderators/table/table.tsx +++ b/packages/frontend/src/views/admin/views/members/staff/moderators/table/table.tsx @@ -15,14 +15,15 @@ import { ActionsTableModeratorsStaffAdmin } from './actions/actions'; export const TableModeratorsStaffAdmin = ({ admin__core_staff_moderators__show: { edges, pageInfo }, }: Admin__Core_Staff_Moderators__ShowQuery) => { - const t = useTranslations('admin.members.staff'); + const t = useTranslations('admin.members.staff.moderators'); + const tShared = useTranslations('admin.members.staff.shared'); return ( { if (row.user_or_group.__typename === 'User') { return ; @@ -40,18 +41,20 @@ export const TableModeratorsStaffAdmin = ({ }, { id: 'type', - title: t('table.type'), + title: tShared('type'), cell: ({ row }) => { return ( - {t(row.user_or_group.__typename === 'User' ? 'user' : 'group')} + {tShared( + row.user_or_group.__typename === 'User' ? 'user' : 'group', + )} ); }, }, { id: 'updated', - title: t('table.updated'), + title: tShared('updated'), sortable: true, cell: ({ row }) => { return ; @@ -59,7 +62,7 @@ export const TableModeratorsStaffAdmin = ({ }, { id: 'permissions', - title: t('table.permissions'), + title: tShared('permissions'), cell: ({ row }) => { const unrestricted = row.unrestricted; @@ -69,7 +72,7 @@ export const TableModeratorsStaffAdmin = ({ variant={unrestricted ? 'default' : 'secondary'} > {unrestricted ? : } - {t(unrestricted ? 'unrestricted' : 'restricted')} + {tShared(unrestricted ? 'unrestricted' : 'restricted')} ); },