From 1070d4370d37529702d7499baeaf145ba4cd9e62 Mon Sep 17 00:00:00 2001 From: Roman Dyakov Date: Mon, 8 Apr 2024 18:17:29 +0300 Subject: [PATCH] Working frontend with api association --- frontend/.env | 2 +- frontend/src/App.vue | 32 ++++++- frontend/src/api/BaseApi.ts | 38 ++++++-- frontend/src/api/index.ts | 3 + frontend/src/api/touch/touch.ts | 17 ++++ frontend/src/api/user/AuthApi.ts | 125 +++++++++++++++++++++++++++ frontend/src/api/user/UserdataApi.ts | 64 ++++++++++++++ frontend/src/assets/logo.svg | 16 +++- frontend/src/pages/MainPage.vue | 71 +++++++++++++-- frontend/src/store/index.ts | 4 +- frontend/src/store/profileStore.ts | 44 ++++++++++ 11 files changed, 396 insertions(+), 20 deletions(-) create mode 100644 frontend/src/api/index.ts create mode 100644 frontend/src/api/touch/touch.ts create mode 100644 frontend/src/api/user/AuthApi.ts create mode 100644 frontend/src/api/user/UserdataApi.ts create mode 100644 frontend/src/store/profileStore.ts diff --git a/frontend/.env b/frontend/.env index 7bcd427..7ba5bf3 100644 --- a/frontend/.env +++ b/frontend/.env @@ -1 +1 @@ -VITE_API_URL=/ +VITE_AUTH_API_BASE_URL=https://api.profcomff.com diff --git a/frontend/src/App.vue b/frontend/src/App.vue index b1b843f..1306098 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,9 +1,22 @@ - + diff --git a/frontend/src/api/BaseApi.ts b/frontend/src/api/BaseApi.ts index bad6447..2f124bf 100644 --- a/frontend/src/api/BaseApi.ts +++ b/frontend/src/api/BaseApi.ts @@ -1,18 +1,21 @@ import axios, { AxiosResponse } from 'axios'; import queryString from 'query-string'; +import { useProfileStore } from '../store'; -export interface DefaultResponse { - status: string; - message: string; -} - +const profileStore = useProfileStore(); type Path = `/${string}` | ''; export class BaseApi { url: string; - constructor(path: Path) { - this.url = import.meta.env.VITE_API_URL + path; + constructor(path: Path, base: string = document.location.origin) { + this.url = base + path; + } + + private ensureToken(): string | null { + if (!profileStore.token) profileStore.fromUrl(); + if (profileStore.token) return profileStore.token; + return null; } protected async get( @@ -20,6 +23,10 @@ export class BaseApi { params?: Partial, headers: Record = {}, ): Promise> { + if (!headers.Authorization) { + const token = this.ensureToken(); + if (token) headers.Authorization = token; + } return axios.get(`${this.url}${path}`, { params, headers, @@ -35,6 +42,10 @@ export class BaseApi { params?: Params, headers: Record = {}, ): Promise> { + if (!headers.Authorization) { + const token = this.ensureToken(); + if (token) headers.Authorization = token; + } return axios.post, Body>(`${this.url}${path}`, body, { headers, params }); } @@ -43,6 +54,10 @@ export class BaseApi { params?: Params, headers: Record = {}, ): Promise> { + if (!headers.Authorization) { + const token = this.ensureToken(); + if (token) headers.Authorization = token; + } return axios.delete(`${this.url}${path}`, { params, headers }); } @@ -51,6 +66,10 @@ export class BaseApi { body?: Body, headers: Record = {}, ): Promise> { + if (!headers.Authorization) { + const token = this.ensureToken(); + if (token) headers.Authorization = token; + } return axios.patch, Body>(`${this.url}${path}`, body, { headers }); } @@ -58,7 +77,12 @@ export class BaseApi { path: Path, body?: Body, params?: Params, + headers: Record = {}, ): Promise> { + if (!headers.Authorization) { + const token = this.ensureToken(); + if (token) headers.Authorization = token; + } return axios.put(`${this.url}${path}`, body, { params }); } } diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts new file mode 100644 index 0000000..4905dfb --- /dev/null +++ b/frontend/src/api/index.ts @@ -0,0 +1,3 @@ +export { authMeApi, authUserApi, authGroupApi } from './user/AuthApi'; +export { userdataUserApi } from './user/UserdataApi.ts'; +export { touchMeApi } from './touch/touch'; diff --git a/frontend/src/api/touch/touch.ts b/frontend/src/api/touch/touch.ts new file mode 100644 index 0000000..8b42ded --- /dev/null +++ b/frontend/src/api/touch/touch.ts @@ -0,0 +1,17 @@ +import { BaseApi } from '../BaseApi'; + +export interface TouchResponse { + id: string; + count?: string; +} + +class TouchMeApi extends BaseApi { + constructor() { + super('', import.meta.env.VITE_API_BASE_URL ?? document.location.origin); + } + public async touch() { + return this.post('/touch'); + } +} + +export const touchMeApi = new TouchMeApi(); diff --git a/frontend/src/api/user/AuthApi.ts b/frontend/src/api/user/AuthApi.ts new file mode 100644 index 0000000..7f23169 --- /dev/null +++ b/frontend/src/api/user/AuthApi.ts @@ -0,0 +1,125 @@ +import { BaseApi } from '../BaseApi'; + +export interface User { + id: string; + email?: string; +} + +export interface DefaultResponse { + status: string; + message: string; + ru?: string; +} + +export interface Scope { + id: number; + name: string; +} + +export interface Group { + id: number; + name: string; + parent_id: number | null; +} + +interface CreateGroupBody { + name: string; + parent_id: number; + scopes: number[]; +} + +export enum UserInfo { + Groups = 'groups', + IndirectGroups = 'indirect_groups', + SessionScopes = 'session_scopes', + UserScopes = 'user_scopes', + AuthMethods = 'auth_methods', +} + +export enum GroupInfo { + Children = 'child', + Scopes = 'scopes', + IndirectScopes = 'indirect_scopes', + Users = 'users', +} + +type UserResponse = User & { + [UserInfo.Groups]: UserInfo.Groups extends Info ? number[] : never; + [UserInfo.IndirectGroups]: UserInfo.IndirectGroups extends Info ? number[] : never; + [UserInfo.SessionScopes]: UserInfo.SessionScopes extends Info ? string[] : never; + [UserInfo.UserScopes]: UserInfo.UserScopes extends Info ? number[] : never; + [UserInfo.AuthMethods]: UserInfo.AuthMethods extends Info ? string[] : never; +}; + +type GetGroupResponse = Group & { + [GroupInfo.Scopes]: GroupInfo.Scopes extends Info ? Scope[] : never; + [GroupInfo.IndirectScopes]: GroupInfo.IndirectScopes extends Info ? Scope[] : never; + [GroupInfo.Children]: GroupInfo.Children extends Info ? Group[] : never; + [GroupInfo.Users]: never; +}; + +export class AuthBaseApi extends BaseApi { + constructor(path = '') { + super(`/auth${path}`, import.meta.env.VITE_AUTH_API_BASE_URL); + } +} + +class AuthMeApi extends AuthBaseApi { + constructor() { + super(''); + } + public async logout() { + return this.post('/logout'); + } + public async getMe(info?: Info[]) { + return this.get, { info?: Info[] }>('/me', { info }); + } +} + +class AuthUserApi extends AuthBaseApi { + constructor() { + super('/user'); + } + + public async getUser(id: number, info: Info[]) { + return this.get, { info: Info[] }>(`/${id}`, { info }); + } + + public async deleteUser(id: number) { + return this.delete(`/${id}`); + } + + public async getUsers(info: Info[]) { + return this.get<{ items: UserResponse[] }, { info: Info[] }>('', { info }); + } +} + +class AuthGroupApi extends AuthBaseApi { + constructor() { + super('/group'); + } + + public async getGroup(id: number, info?: Info[]) { + return this.get, { info?: Info[] }>(`/${id}`, { info }); + } + + public async deleteGroup(id: number) { + return this.delete(`/${id}`, undefined); + } + + public async patchGroup(id: number, body: Partial) { + return this.patch>(`/${id}`, body); + } + + public async getGroups(info: Info[]) { + return this.get<{ items: GetGroupResponse[] }, { info: Info[] }>('', { info }); + } + + public async createGroup(body: CreateGroupBody) { + return this.post('', body); + } +} + +export const authMeApi = new AuthMeApi(); +export const authUserApi = new AuthUserApi(); +export const authGroupApi = new AuthGroupApi(); diff --git a/frontend/src/api/user/UserdataApi.ts b/frontend/src/api/user/UserdataApi.ts new file mode 100644 index 0000000..f5ebaae --- /dev/null +++ b/frontend/src/api/user/UserdataApi.ts @@ -0,0 +1,64 @@ +import { BaseApi } from '../BaseApi'; + +export enum UserdataParamResponseType { + All = 'all', + Last = 'last', + MostTrusted = 'most_trusted', +} + +export interface UserdataRawItem { + category: string; + param: string; + value: string; +} + +export interface UserdataExtendedValue { + name: string; + is_required?: boolean; + changeable?: boolean; + type?: UserdataParamResponseType; +} + +export interface UserdataItem { + category: string; + param: string; + value: UserdataExtendedValue; +} + +export interface UserdataRaw { + items: UserdataRawItem[]; +} + +export class UserdataBaseApi extends BaseApi { + constructor(path = '') { + super(`/userdata${path}`, import.meta.env.VITE_AUTH_API_BASE_URL); + } +} + +export interface UserdataUpdateUserItem { + category: string; + param: string; + value: string | null; +} + +export interface UserdataUpdateUser { + items: UserdataUpdateUserItem[]; + source: string; +} + +type Json = Record>; + +class UserdataUserApi extends UserdataBaseApi { + constructor() { + super('/user'); + } + + public async getById(id: number) { + return this.get(`/${id}`); + } + + public async patchById(id: number, items: UserdataUpdateUserItem[], source: string = 'user') { + return this.post(`/${id}`, { items, source }); + } +} +export const userdataUserApi = new UserdataUserApi(); diff --git a/frontend/src/assets/logo.svg b/frontend/src/assets/logo.svg index 819c849..3e2b38b 100644 --- a/frontend/src/assets/logo.svg +++ b/frontend/src/assets/logo.svg @@ -1 +1,15 @@ - \ No newline at end of file + + + + + + + + + + + + + + + diff --git a/frontend/src/pages/MainPage.vue b/frontend/src/pages/MainPage.vue index 712f855..bb874fa 100644 --- a/frontend/src/pages/MainPage.vue +++ b/frontend/src/pages/MainPage.vue @@ -1,8 +1,69 @@ - + diff --git a/frontend/src/store/index.ts b/frontend/src/store/index.ts index 4c0b302..21feb3f 100644 --- a/frontend/src/store/index.ts +++ b/frontend/src/store/index.ts @@ -1,3 +1 @@ -import { defineStore } from 'pinia'; - -export const useStore = defineStore('store', () => {}); +export { useProfileStore } from './profileStore'; diff --git a/frontend/src/store/profileStore.ts b/frontend/src/store/profileStore.ts new file mode 100644 index 0000000..28d0c8a --- /dev/null +++ b/frontend/src/store/profileStore.ts @@ -0,0 +1,44 @@ +import { defineStore } from 'pinia'; +import { ref } from 'vue'; + +export const useProfileStore = defineStore('profile', () => { + const id = ref(null); + const email = ref(null); + const token = ref(null); + const groups = ref(null); + const indirectGroups = ref(null); + const userScopes = ref(null); + const sessionScopes = ref(null); + + const full_name = ref(null); + + const fromUrl = () => { + const url = new URL(document.location.toString()); + const currToken = url.searchParams.get('token'); + const currId = url.searchParams.get('user_id'); + const currScopes = url.searchParams.get('scopes'); + if (currToken) { + token.value = currToken; + } + if (currId) { + id.value = +currId; + } + if (currScopes) { + sessionScopes.value = currScopes.split(','); + } + }; + + return { + id, + email, + token, + groups, + indirectGroups, + userScopes, + sessionScopes, + + full_name, + + fromUrl, + }; +});