From b8f522116fa29914dfff4ceca432bc2485f034a8 Mon Sep 17 00:00:00 2001 From: FrenchGithubUser Date: Sun, 14 Dec 2025 14:35:52 +0100 Subject: [PATCH] feat(frontend): user permissions --- frontend/src/components/MenuBar.vue | 5 +- .../components/artist/ArtistSlimHeader.vue | 2 +- .../components/community/GeneralComment.vue | 3 +- .../conversation/conversationMessages.vue | 1 + .../forum/EditForumThreadDialog.vue | 2 +- .../forum/ForumCategoryOverview.vue | 8 +- .../title_group/TitleGroupComments.vue | 1 + .../title_group/TitleGroupTable.vue | 4 +- frontend/src/components/user/UserSidebar.vue | 2 +- frontend/src/services/api-schema/api.ts | 138 +++++++----------- frontend/src/stores/user.ts | 4 +- frontend/src/views/TitleGroupTagsView.vue | 16 +- frontend/src/views/TitleGroupView.vue | 2 +- frontend/src/views/UserView.vue | 2 +- .../src/views/forum/ForumOverviewView.vue | 2 +- .../src/views/forum/ForumSubCategoryView.vue | 2 +- frontend/src/views/forum/ForumThreadView.vue | 3 +- .../src/views/staff_pm/StaffDashboardView.vue | 8 +- frontend/src/views/wiki/WikiView.vue | 4 +- 19 files changed, 97 insertions(+), 112 deletions(-) diff --git a/frontend/src/components/MenuBar.vue b/frontend/src/components/MenuBar.vue index 544225de..b769a2d7 100644 --- a/frontend/src/components/MenuBar.vue +++ b/frontend/src/components/MenuBar.vue @@ -11,6 +11,7 @@ import { ref } from 'vue' import { Button } from 'primevue' import { onMounted } from 'vue' import { useUserStore } from '@/stores/user' +import type { UserPermission } from '@/services/api-schema' const userStore = useUserStore() @@ -29,7 +30,9 @@ const menuItems = ref([ ]) onMounted(() => { - if (userStore.class === 'staff') { + // if the user can do one of those actions, they can access the staff dashboard + const permissionsToSeeStaffDashboard: UserPermission[] = ['create_css_sheet', 'edit_css_sheet', 'get_user_application', 'read_staff_pm'] + if (permissionsToSeeStaffDashboard.some((x: UserPermission) => userStore.permissions.includes(x))) { menuItems.value.push({ label: 'Staff Dashboard', route: '/staff-dashboard' }) } }) diff --git a/frontend/src/components/artist/ArtistSlimHeader.vue b/frontend/src/components/artist/ArtistSlimHeader.vue index eaddf438..62dd0bd8 100644 --- a/frontend/src/components/artist/ArtistSlimHeader.vue +++ b/frontend/src/components/artist/ArtistSlimHeader.vue @@ -4,7 +4,7 @@
diff --git a/frontend/src/components/community/GeneralComment.vue b/frontend/src/components/community/GeneralComment.vue index acd5bac8..23b8f81b 100644 --- a/frontend/src/components/community/GeneralComment.vue +++ b/frontend/src/components/community/GeneralComment.vue @@ -3,7 +3,7 @@
() const emit = defineEmits<{ diff --git a/frontend/src/components/conversation/conversationMessages.vue b/frontend/src/components/conversation/conversationMessages.vue index e504dac7..72bf73d7 100644 --- a/frontend/src/components/conversation/conversationMessages.vue +++ b/frontend/src/components/conversation/conversationMessages.vue @@ -5,6 +5,7 @@ :key="message.id" :comment="message" :class="`message ${userStore.id === message.created_by.id ? 'sent' : 'received'}`" + :hasEditPermission="false" />
diff --git a/frontend/src/components/forum/EditForumThreadDialog.vue b/frontend/src/components/forum/EditForumThreadDialog.vue index bb94f499..185bc18b 100644 --- a/frontend/src/components/forum/EditForumThreadDialog.vue +++ b/frontend/src/components/forum/EditForumThreadDialog.vue @@ -10,7 +10,7 @@ -
+
diff --git a/frontend/src/components/forum/ForumCategoryOverview.vue b/frontend/src/components/forum/ForumCategoryOverview.vue index ac34e639..2a81c71f 100644 --- a/frontend/src/components/forum/ForumCategoryOverview.vue +++ b/frontend/src/components/forum/ForumCategoryOverview.vue @@ -3,12 +3,16 @@
{{ forumCategory.name }}
- + diff --git a/frontend/src/components/title_group/TitleGroupComments.vue b/frontend/src/components/title_group/TitleGroupComments.vue index d1c031f9..376c0195 100644 --- a/frontend/src/components/title_group/TitleGroupComments.vue +++ b/frontend/src/components/title_group/TitleGroupComments.vue @@ -6,6 +6,7 @@ :comment="comment" @commentEdited="commentEdited($event, comment.id)" :editCommentMethod="(post: EditedTitleGroupComment) => editTitleGroupComment({ EditedTitleGroupComment: post, id: comment.id })" + :hasEditPermission="useUserStore().permissions.includes('edit_title_group_comment')" />
diff --git a/frontend/src/components/title_group/TitleGroupTable.vue b/frontend/src/components/title_group/TitleGroupTable.vue index 74077a80..af138dd0 100644 --- a/frontend/src/components/title_group/TitleGroupTable.vue +++ b/frontend/src/components/title_group/TitleGroupTable.vue @@ -49,11 +49,11 @@ {{ timeAgo(user.last_seen) }}
- {{ t('user.class') }}: {{ user.class }} + {{ t('user.class') }}: {{ user.class_name }}
{{ t('user.bonus_points') }}: {{ user.bonus_points }}
diff --git a/frontend/src/services/api-schema/api.ts b/frontend/src/services/api-schema/api.ts index 300c0470..9f48bb7a 100644 --- a/frontend/src/services/api-schema/api.ts +++ b/frontend/src/services/api-schema/api.ts @@ -1049,7 +1049,7 @@ export interface PublicUser { 'average_seeding_time': number; 'banned': boolean; 'bonus_points': number; - 'class': UserClass; + 'class_name': string; 'collages_started': number; 'created_at': string; 'description': string; @@ -1076,8 +1076,6 @@ export interface PublicUser { 'username': string; 'warned': boolean; } - - export interface RefreshToken { 'refresh_token': string; } @@ -1495,11 +1493,6 @@ export interface TorrentHierarchyLite { } -export interface TorrentMinimal { - 'created_at': string; - 'id': number; - 'info_hash'?: string | null; -} export interface TorrentReport { 'description': string; 'id': number; @@ -1669,7 +1662,8 @@ export interface User { 'average_seeding_time': number; 'banned': boolean; 'bonus_points': number; - 'class': UserClass; + 'class_locked': boolean; + 'class_name': string; 'collages_started': number; 'created_at': string; 'css_sheet_name': string; @@ -1686,6 +1680,7 @@ export interface User { 'leeching': number; 'passkey': string; 'password_hash': string; + 'permissions': Array; 'ratio': number; 'real_downloaded': number; 'real_uploaded': number; @@ -1703,8 +1698,6 @@ export interface User { 'username': string; 'warned': boolean; } - - export interface UserApplication { 'applied_from_ip': string; 'body': string; @@ -1727,16 +1720,6 @@ export const UserApplicationStatus = { export type UserApplicationStatus = typeof UserApplicationStatus[keyof typeof UserApplicationStatus]; - -export const UserClass = { - Newbie: 'newbie', - Staff: 'staff', - Tracker: 'tracker' -} as const; - -export type UserClass = typeof UserClass[keyof typeof UserClass]; - - export interface UserCreatedAffiliatedArtist { 'artist_id': number; 'nickname'?: string | null; @@ -1924,6 +1907,50 @@ export interface UserLiteAvatar { 'username': string; 'warned': boolean; } + +export const UserPermission = { + UploadTorrent: 'upload_torrent', + DownloadTorrent: 'download_torrent', + CreateTorrentRequest: 'create_torrent_request', + ImmuneActivityPruning: 'immune_activity_pruning', + EditTitleGroup: 'edit_title_group', + EditTitleGroupComment: 'edit_title_group_comment', + EditEditionGroup: 'edit_edition_group', + EditTorrent: 'edit_torrent', + EditArtist: 'edit_artist', + EditCollage: 'edit_collage', + EditSeries: 'edit_series', + EditTorrentRequest: 'edit_torrent_request', + EditForumPost: 'edit_forum_post', + EditForumThread: 'edit_forum_thread', + EditForumSubCategory: 'edit_forum_sub_category', + EditForumCategory: 'edit_forum_category', + CreateForumCategory: 'create_forum_category', + CreateForumSubCategory: 'create_forum_sub_category', + CreateForumThread: 'create_forum_thread', + CreateForumPost: 'create_forum_post', + SendPm: 'send_pm', + CreateCssSheet: 'create_css_sheet', + EditCssSheet: 'edit_css_sheet', + SetDefaultCssSheet: 'set_default_css_sheet', + ReadStaffPm: 'read_staff_pm', + ReplyStaffPm: 'reply_staff_pm', + ResolveStaffPm: 'resolve_staff_pm', + UnresolveStaffPm: 'unresolve_staff_pm', + DeleteTitleGroupTag: 'delete_title_group_tag', + EditTitleGroupTag: 'edit_title_group_tag', + DeleteTorrent: 'delete_torrent', + GetUserApplication: 'get_user_application', + UpdateUserApplication: 'update_user_application', + WarnUser: 'warn_user', + EditUser: 'edit_user', + CreateWikiArticle: 'create_wiki_article', + EditWikiArticle: 'edit_wiki_article' +} as const; + +export type UserPermission = typeof UserPermission[keyof typeof UserPermission]; + + export interface UserSettings { 'css_sheet_name': string; } @@ -9172,39 +9199,6 @@ export const TorrentApiAxiosParamCreator = function (configuration?: Configurati options: localVarRequestOptions, }; }, - /** - * - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - getRegisteredTorrents: async (options: RawAxiosRequestConfig = {}): Promise => { - const localVarPath = `/api/torrents/registered`; - // use dummy base URL string because the URL constructor only accepts absolute URLs. - const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); - let baseOptions; - if (configuration) { - baseOptions = configuration.baseOptions; - } - - const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; - const localVarHeaderParameter = {} as any; - const localVarQueryParameter = {} as any; - - // authentication http required - // http bearer authentication required - await setBearerAuthToObject(localVarHeaderParameter, configuration) - - - - setSearchParams(localVarUrlObj, localVarQueryParameter); - let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; - localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; - - return { - url: toPathString(localVarUrlObj), - options: localVarRequestOptions, - }; - }, /** * * @param {string} period @@ -9370,17 +9364,6 @@ export const TorrentApiFp = function(configuration?: Configuration) { const localVarOperationServerBasePath = operationServerMap['TorrentApi.editTorrent']?.[localVarOperationServerIndex]?.url; return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); }, - /** - * - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - async getRegisteredTorrents(options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getRegisteredTorrents(options); - const localVarOperationServerIndex = configuration?.serverIndex ?? 0; - const localVarOperationServerBasePath = operationServerMap['TorrentApi.getRegisteredTorrents']?.[localVarOperationServerIndex]?.url; - return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); - }, /** * * @param {string} period @@ -9479,14 +9462,6 @@ export const TorrentApiFactory = function (configuration?: Configuration, basePa editTorrent(editedTorrent: EditedTorrent, options?: RawAxiosRequestConfig): AxiosPromise { return localVarFp.editTorrent(editedTorrent, options).then((request) => request(axios, basePath)); }, - /** - * - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - getRegisteredTorrents(options?: RawAxiosRequestConfig): AxiosPromise> { - return localVarFp.getRegisteredTorrents(options).then((request) => request(axios, basePath)); - }, /** * * @param {string} period @@ -9582,15 +9557,6 @@ export class TorrentApi extends BaseAPI { return TorrentApiFp(this.configuration).editTorrent(editedTorrent, options).then((request) => request(this.axios, this.basePath)); } - /** - * - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - public getRegisteredTorrents(options?: RawAxiosRequestConfig) { - return TorrentApiFp(this.configuration).getRegisteredTorrents(options).then((request) => request(this.axios, this.basePath)); - } - /** * * @param {string} period @@ -9691,12 +9657,6 @@ export const editTorrent = async (editedTorrent: EditedTorrent, options?: RawAxi return response.data; }; - -export const getRegisteredTorrents = async (options?: RawAxiosRequestConfig): Promise> => { - const response = await torrentApi.getRegisteredTorrents(options); - return response.data; -}; - export interface GetTopTorrentRequest { /** */ 'period': string; @@ -10365,7 +10325,7 @@ export const UserApiAxiosParamCreator = function (configuration?: Configuration) warnUser: async (userCreatedUserWarning: UserCreatedUserWarning, options: RawAxiosRequestConfig = {}): Promise => { // verify required parameter 'userCreatedUserWarning' is not null or undefined assertParamExists('warnUser', 'userCreatedUserWarning', userCreatedUserWarning) - const localVarPath = `/api/users/warnings`; + const localVarPath = `/api/users/warn`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; diff --git a/frontend/src/stores/user.ts b/frontend/src/stores/user.ts index 832b1673..ee988b08 100644 --- a/frontend/src/stores/user.ts +++ b/frontend/src/stores/user.ts @@ -6,7 +6,9 @@ const initialState: User = { avatar: null, average_seeding_time: 0, bonus_points: 0, - class: 'newbie', + class_name: 'newbie', + class_locked: false, + permissions: [], collages_started: 0, created_at: '', description: '', diff --git a/frontend/src/views/TitleGroupTagsView.vue b/frontend/src/views/TitleGroupTagsView.vue index b6f41118..c04f3275 100644 --- a/frontend/src/views/TitleGroupTagsView.vue +++ b/frontend/src/views/TitleGroupTagsView.vue @@ -64,10 +64,20 @@ - + diff --git a/frontend/src/views/TitleGroupView.vue b/frontend/src/views/TitleGroupView.vue index 55f15d2b..fb1f1ad7 100644 --- a/frontend/src/views/TitleGroupView.vue +++ b/frontend/src/views/TitleGroupView.vue @@ -20,7 +20,7 @@
-