ajout app

This commit is contained in:
2024-04-17 20:22:30 +02:00
parent cc017cfc5e
commit f9d05a2fd3
8025 changed files with 729805 additions and 0 deletions

View File

@@ -0,0 +1,450 @@
/* eslint-disable react-hooks/rules-of-hooks */
import useSWR from 'swr'
import { apiFetcher } from '@/client/fetcher'
import { User } from '@/client/idp/user'
import { getConfig } from '@/config/config'
import { encodeQuery } from '@/helpers/query'
import { getAccessTokenOrRedirect } from '@/infra/token'
import { Group } from './group'
import { PermissionType } from './permission'
import { Download, Snapshot, Thumbnail } from './snapshot'
export enum FileType {
File = 'file',
Folder = 'folder',
}
export enum SortBy {
Name = 'name',
Kind = 'kind',
Size = 'size',
DateCreated = 'date_created',
DateModified = 'date_modified',
}
export enum SortOrder {
Asc = 'asc',
Desc = 'desc',
}
export enum SnapshotStatus {
New = 'new',
Processing = 'processing',
Ready = 'ready',
Error = 'error',
}
export type File = {
id: string
workspaceId: string
name: string
type: FileType
parentId: string
version: number
original?: Download
preview?: Download
thumbnail?: Thumbnail
status: SnapshotStatus
snapshots: Snapshot[]
permission: PermissionType
isShared: boolean
createTime: string
updateTime?: string
}
export type CreateFolderOptions = {
workspaceId: string
name: string
parentId: string
}
export type List = {
data: File[]
totalPages: number
totalElements: number
page: number
size: number
query?: Query
}
export type UserPermission = {
id: string
user: User
permission: string
}
export type GroupPermission = {
id: string
group: Group
permission: string
}
export type Query = {
text: string
type?: string
createTimeAfter?: number
createTimeBefore?: number
updateTimeAfter?: number
updateTimeBefore?: number
}
export type ListOptions = {
size?: number
page?: number
type?: FileType
sortBy?: SortBy
sortOrder?: SortOrder
query?: Query
}
export type MoveOptions = {
ids: string[]
}
export type CopyOptions = {
ids: string[]
}
export type BatchDeleteOptions = {
ids: string[]
}
export type BatchGetOptions = {
ids: string[]
}
export type RenameOptions = {
name: string
}
export type GrantUserPermissionOptions = {
ids: string[]
userId: string
permission: string
}
export type RevokeUserPermissionOptions = {
ids: string[]
userId: string
}
export type GrantGroupPermissionOptions = {
ids: string[]
groupId: string
permission: string
}
export type RevokeGroupPermissionOptions = {
ids: string[]
groupId: string
}
export type UploadOptions = {
workspaceId: string
parentId?: string
name?: string
file: Blob
request: XMLHttpRequest
onProgress?: (value: number) => void
}
export default class FileAPI {
static async upload({
workspaceId,
parentId,
name,
request,
file,
onProgress,
}: UploadOptions): Promise<File> {
const params = new URLSearchParams({ workspace_id: workspaceId })
if (parentId) {
params.append('parent_id', parentId)
}
if (name) {
params.append('name', name)
}
return this.doUpload(
`${getConfig().apiURL}/files?${params}`,
'POST',
request,
file,
onProgress,
)
}
static async patch(
id: string,
request: XMLHttpRequest,
file: Blob,
onProgress?: (value: number) => void,
): Promise<File> {
return this.doUpload(
`${getConfig().apiURL}/files/${id}`,
'PATCH',
request,
file,
onProgress,
)
}
private static async doUpload(
url: string,
method: string,
request: XMLHttpRequest,
file: Blob,
onProgress?: (value: number) => void,
) {
const formData = new FormData()
formData.append('file', file)
return new Promise<File>((resolve, reject) => {
request.open(method, url)
request.setRequestHeader(
'Authorization',
`Bearer ${getAccessTokenOrRedirect()}`,
)
request.onloadend = () => {
if (request.status <= 299) {
try {
resolve(JSON.parse(request.responseText))
} catch (e) {
reject(e)
}
} else {
try {
reject(JSON.parse(request.responseText))
} catch {
reject(request.responseText)
}
}
}
request.upload.onprogress = (e) => {
onProgress?.((e.loaded / e.total) * 100)
}
request.send(formData)
})
}
static async createFolder(options: CreateFolderOptions) {
return apiFetcher({
url: '/files/create_folder',
method: 'POST',
body: JSON.stringify(options),
}) as Promise<File>
}
static async list(id: string, options: ListOptions) {
return apiFetcher({
url: `/files/${id}/list?${this.paramsFromListOptions(options)}`,
method: 'GET',
}) as Promise<List>
}
static useList(
id: string | undefined,
options: ListOptions,
swrOptions?: any,
) {
const url = `/files/${id}/list?${this.paramsFromListOptions(options)}`
return useSWR<List | undefined>(
id ? url : null,
() => apiFetcher({ url, method: 'GET' }),
swrOptions,
)
}
static async getPath(id: string) {
return apiFetcher({
url: `/files/${id}/get_path`,
method: 'GET',
}) as Promise<File[]>
}
static useGetPath(id: string | null | undefined, swrOptions?: any) {
const url = `/files/${id}/get_path`
return useSWR<File[]>(
id ? url : null,
() => apiFetcher({ url, method: 'GET' }) as Promise<File[]>,
swrOptions,
)
}
static async getIds(id: string) {
return apiFetcher({
url: `/files/${id}/get_ids`,
method: 'GET',
}) as Promise<string[]>
}
static async rename(id: string, options: RenameOptions) {
return apiFetcher({
url: `/files/${id}/rename`,
method: 'POST',
body: JSON.stringify(options),
}) as Promise<File>
}
static async delete(id: string) {
return apiFetcher({
url: `/files/${id}`,
method: 'DELETE',
})
}
static async batchDelete(options: BatchDeleteOptions) {
return apiFetcher({
url: `/files/batch_delete`,
method: 'POST',
body: JSON.stringify(options),
})
}
static async move(id: string, options: MoveOptions) {
return apiFetcher({
url: `/files/${id}/move`,
method: 'POST',
body: JSON.stringify(options),
})
}
static async copy(id: string, options: CopyOptions) {
return apiFetcher({
url: `/files/${id}/copy`,
method: 'POST',
body: JSON.stringify(options),
})
}
static useGetById(id: string | null | undefined, swrOptions?: any) {
const url = `/files/${id}`
return useSWR(
id ? url : null,
() => apiFetcher({ url, method: 'GET' }) as Promise<File>,
swrOptions,
)
}
static async getById(id: string) {
return apiFetcher({
url: `/files/${id}`,
method: 'GET',
}) as Promise<File>
}
static async batchGet(options: BatchGetOptions) {
return apiFetcher({
url: `/files/batch_get`,
method: 'POST',
body: JSON.stringify(options),
}) as Promise<File[]>
}
static async getItemCount(id: string) {
return apiFetcher({
url: `/files/${id}/get_item_count`,
method: 'GET',
}) as Promise<number>
}
static useGetItemCount(id: string | null | undefined, swrOptions?: any) {
const url = `/files/${id}/get_item_count`
return useSWR<number>(
id ? url : null,
() => apiFetcher({ url, method: 'GET' }) as Promise<number>,
swrOptions,
)
}
static async grantUserPermission(options: GrantUserPermissionOptions) {
return apiFetcher({
url: `/files/grant_user_permission`,
method: 'POST',
body: JSON.stringify(options),
})
}
static async revokeUserPermission(options: RevokeUserPermissionOptions) {
return apiFetcher({
url: `/files/revoke_user_permission`,
method: 'POST',
body: JSON.stringify(options),
})
}
static async grantGroupPermission(options: GrantGroupPermissionOptions) {
return apiFetcher({
url: `/files/grant_group_permission`,
method: 'POST',
body: JSON.stringify(options),
})
}
static async revokeGroupPermission(options: RevokeGroupPermissionOptions) {
return apiFetcher({
url: `/files/revoke_group_permission`,
method: 'POST',
body: JSON.stringify(options),
})
}
static async getUserPermissions(id: string) {
return apiFetcher({
url: `/files/${id}/get_user_permissions`,
method: 'GET',
}) as Promise<UserPermission[]>
}
static useGetUserPermissions(
id: string | null | undefined,
swrOptions?: any,
) {
const url = `/files/${id}/get_user_permissions`
return useSWR<UserPermission[]>(
id ? url : null,
() => apiFetcher({ url, method: 'GET' }) as Promise<UserPermission[]>,
swrOptions,
)
}
static async getGroupPermissions(id: string) {
return apiFetcher<GroupPermission[]>({
url: `/files/${id}/get_group_permissions`,
method: 'GET',
})
}
static useGetGroupPermissions(
id: string | null | undefined,
swrOptions?: any,
) {
const url = `/files/${id}/get_group_permissions`
return useSWR<GroupPermission[]>(
id ? url : null,
() => apiFetcher({ url, method: 'GET' }) as Promise<GroupPermission[]>,
swrOptions,
)
}
static paramsFromListOptions(options?: ListOptions): URLSearchParams {
const params: any = {}
if (options?.page) {
params.page = options.page.toString()
}
if (options?.size) {
params.size = options.size.toString()
}
if (options?.sortBy) {
params.sort_by = options.sortBy.toString()
}
if (options?.sortOrder) {
params.sort_order = options.sortOrder.toString()
}
if (options?.type) {
params.type = options.type
}
if (options?.query) {
params.query = encodeQuery(JSON.stringify(options.query))
}
return new URLSearchParams(params)
}
}

View File

@@ -0,0 +1,164 @@
import useSWR from 'swr'
import { apiFetcher } from '@/client/fetcher'
import { Organization } from './organization'
export enum SortBy {
Name = 'name',
DateCreated = 'date_created',
DateModified = 'date_modified',
}
export enum SortOrder {
Asc = 'asc',
Desc = 'desc',
}
export type Group = {
id: string
name: string
organization: Organization
permission: string
createTime: string
updateTime?: string
}
export type List = {
data: Group[]
totalPages: number
totalElements: number
page: number
size: number
}
export type ListOptions = {
query?: string
organizationId?: string
size?: number
page?: number
sortBy?: SortBy
sortOrder?: SortOrder
}
export type CreateOptions = {
name: string
image?: string
organizationId: string
}
export type UpdateNameOptions = {
name: string
}
export type AddMemberOptions = {
userId: string
}
export type RemoveMemberOptions = {
userId: string
}
export default class GroupAPI {
static create(options: CreateOptions) {
return apiFetcher({
url: `/groups`,
method: 'POST',
body: JSON.stringify(options),
}) as Promise<Group>
}
static updateName(id: string, options: UpdateNameOptions) {
return apiFetcher({
url: `/groups/${id}/update_name`,
method: 'POST',
body: JSON.stringify(options),
}) as Promise<Group>
}
static async updateImage(id: string, file: any) {
const formData = new FormData()
formData.append('file', file)
return apiFetcher<Group>({
url: `/groups/${id}/update_image`,
method: 'POST',
body: formData,
})
}
static async getById(id: string) {
return apiFetcher({
url: `/groups/${id}`,
method: 'GET',
}) as Promise<Group>
}
static useGetById(id: string | null | undefined, swrOptions?: any) {
const url = `/groups/${id}`
return useSWR<Group>(
id ? url : null,
() => apiFetcher({ url, method: 'GET' }) as Promise<Group>,
swrOptions,
)
}
static async list(options?: ListOptions) {
return apiFetcher<List>({
url: `/groups?${this.paramsFromListOptions(options)}`,
method: 'GET',
})
}
static useList(options?: ListOptions, swrOptions?: any) {
const url = `/groups?${this.paramsFromListOptions(options)}`
return useSWR<List>(
url,
() => apiFetcher({ url, method: 'GET' }) as Promise<List>,
swrOptions,
)
}
static delete(id: string) {
return apiFetcher({
url: `/groups/${id}`,
method: 'DELETE',
})
}
static addMember(id: string, options: AddMemberOptions) {
return apiFetcher({
url: `/groups/${id}/add_member`,
method: 'POST',
body: JSON.stringify(options),
})
}
static removeMember(id: string, options: RemoveMemberOptions) {
return apiFetcher({
url: `/groups/${id}/remove_member`,
method: 'POST',
body: JSON.stringify(options),
})
}
static paramsFromListOptions(options?: ListOptions): URLSearchParams {
const params: any = {}
if (options?.query) {
params.query = encodeURIComponent(options.query.toString())
}
if (options?.organizationId) {
params.organization_id = options.organizationId.toString()
}
if (options?.page) {
params.page = options.page.toString()
}
if (options?.size) {
params.size = options.size.toString()
}
if (options?.sortBy) {
params.sort_by = options.sortBy.toString()
}
if (options?.sortOrder) {
params.sort_order = options.sortOrder.toString()
}
return new URLSearchParams(params)
}
}

View File

@@ -0,0 +1,129 @@
/* eslint-disable react-hooks/rules-of-hooks */
import useSWR from 'swr'
import { apiFetcher } from '@/client/fetcher'
import { User } from '@/client/idp/user'
import { Organization } from './organization'
export enum SortBy {
Email = 'email',
DateCreated = 'date_created',
DateModified = 'date_modified',
}
export enum SortOrder {
Asc = 'asc',
Desc = 'desc',
}
export type InvitationStatus = 'pending' | 'accepted' | 'declined'
export type Invitation = {
id: string
owner: User
email: string[]
organization: Organization
status: InvitationStatus
createTime: string
updateTime?: string
}
export type List = {
data: Invitation[]
totalPages: number
totalElements: number
page: number
size: number
}
export type CreateOptions = {
organizationId: string
emails: string[]
}
export type ListOptions = {
organizationId?: string
size?: number
page?: number
sortBy?: SortBy
sortOrder?: SortOrder
}
export default class InvitationAPI {
static async create(options: CreateOptions) {
return apiFetcher({
url: `/invitations`,
method: 'POST',
body: JSON.stringify(options),
})
}
static useGetIncoming(options?: ListOptions, swrOptions?: any) {
const url = `/invitations/get_incoming?${this.paramsFromListOptions(
options,
)}`
return useSWR<List>(
url,
() => apiFetcher({ url, method: 'GET' }) as Promise<List>,
swrOptions,
)
}
static useGetOutgoing(options?: ListOptions, swrOptions?: any) {
const url = `/invitations/get_outgoing?${this.paramsFromListOptions(
options,
)}`
return useSWR<List>(
options?.organizationId ? url : null,
() => apiFetcher({ url, method: 'GET' }) as Promise<List>,
swrOptions,
)
}
static async delete(id: string) {
return apiFetcher({
url: `/invitations/${id}`,
method: 'DELETE',
})
}
static async resend(id: string) {
return apiFetcher({
url: `/invitations/${id}/resend`,
method: 'POST',
})
}
static async accept(id: string) {
return apiFetcher({
url: `/invitations/${id}/accept`,
method: 'POST',
})
}
static async decline(id: string) {
return apiFetcher({
url: `/invitations/${id}/decline`,
method: 'POST',
})
}
static paramsFromListOptions(options?: ListOptions): URLSearchParams {
const params: any = {}
if (options?.organizationId) {
params.organization_id = options.organizationId.toString()
}
if (options?.page) {
params.page = options.page.toString()
}
if (options?.size) {
params.size = options.size.toString()
}
if (options?.sortBy) {
params.sort_by = options.sortBy.toString()
}
if (options?.sortOrder) {
params.sort_order = options.sortOrder.toString()
}
return new URLSearchParams(params)
}
}

View File

@@ -0,0 +1,22 @@
/* eslint-disable react-hooks/rules-of-hooks */
import useSWR from 'swr'
import { apiFetcher } from '@/client/fetcher'
import { Invitation } from './invitation'
export type NotificationType = 'new_invitation'
export type Notification = {
type: NotificationType
body: Invitation | any
}
export default class NotificationAPI {
static useGetAll(swrOptions?: any) {
const url = `/notifications`
return useSWR<Notification[]>(
url,
() => apiFetcher({ url, method: 'GET' }) as Promise<Notification[]>,
swrOptions,
)
}
}

View File

@@ -0,0 +1,154 @@
/* eslint-disable react-hooks/rules-of-hooks */
import useSWR from 'swr'
import { apiFetcher } from '@/client/fetcher'
import { PermissionType } from './permission'
export enum SortBy {
Name = 'name',
DateCreated = 'date_created',
DateModified = 'date_modified',
}
export enum SortOrder {
Asc = 'asc',
Desc = 'desc',
}
export type Organization = {
id: string
name: string
permission: PermissionType
createTime: string
updateTime?: string
}
export type List = {
data: Organization[]
totalPages: number
totalElements: number
page: number
size: number
}
export type ListOptions = {
query?: string
size?: number
page?: number
sortBy?: SortBy
sortOrder?: SortOrder
}
export type CreateOptions = {
name: string
image?: string
}
export type UpdateNameOptions = {
name: string
}
export type RemoveMemberOptions = {
userId: string
}
export default class OrganizationAPI {
static async getById(id: string) {
return apiFetcher({
url: `/organizations/${id}`,
method: 'GET',
}) as Promise<Organization>
}
static useGetById(id: string | null | undefined, swrOptions?: any) {
const url = `/organizations/${id}`
return useSWR<Organization>(
id ? url : null,
() => apiFetcher({ url, method: 'GET' }) as Promise<Organization>,
swrOptions,
)
}
static async list(options?: ListOptions) {
return apiFetcher({
url: `/organizations?${this.paramsFromListOptions(options)}`,
method: 'GET',
}) as Promise<List>
}
static useList(options?: ListOptions, swrOptions?: any) {
const url = `/organizations?${this.paramsFromListOptions(options)}`
return useSWR<List>(
url,
() => apiFetcher({ url, method: 'GET' }) as Promise<List>,
swrOptions,
)
}
static async create(options: CreateOptions) {
return apiFetcher({
url: `/organizations`,
method: 'POST',
body: JSON.stringify(options),
}) as Promise<Organization>
}
static async updateName(id: string, options: UpdateNameOptions) {
return apiFetcher({
url: `/organizations/${id}/update_name`,
method: 'POST',
body: JSON.stringify(options),
}) as Promise<Organization>
}
static async updateImage(id: string, file: any) {
const formData = new FormData()
formData.append('file', file)
return apiFetcher({
url: `/organizations/${id}/update_image`,
method: 'POST',
body: formData,
}) as Promise<Organization>
}
static async delete(id: string) {
return apiFetcher({
url: `/organizations/${id}`,
method: 'DELETE',
})
}
static async leave(id: string) {
return apiFetcher({
url: `/organizations/${id}/leave`,
method: 'POST',
})
}
static async removeMember(id: string, options: RemoveMemberOptions) {
return apiFetcher({
url: `/organizations/${id}/remove_member`,
method: 'POST',
body: JSON.stringify(options),
})
}
static paramsFromListOptions(options?: ListOptions): URLSearchParams {
const params: any = {}
if (options?.query) {
params.query = encodeURIComponent(options.query.toString())
}
if (options?.page) {
params.page = options.page.toString()
}
if (options?.size) {
params.size = options.size.toString()
}
if (options?.sortBy) {
params.sort_by = options.sortBy.toString()
}
if (options?.sortOrder) {
params.sort_order = options.sortOrder.toString()
}
return new URLSearchParams(params)
}
}

View File

@@ -0,0 +1,52 @@
export const VIEWER_PERMISSION = 'viewer'
export const EDITOR_PERMISSION = 'editor'
export const OWNER_PERMISSION = 'owner'
export type PermissionType = 'viewer' | 'editor' | 'owner'
export function geViewerPermission(permission: string): boolean {
return (
getPermissionWeight(permission) >= getPermissionWeight(VIEWER_PERMISSION)
)
}
export function geEditorPermission(permission: string) {
return (
getPermissionWeight(permission) >= getPermissionWeight(EDITOR_PERMISSION)
)
}
export function geOwnerPermission(permission: string) {
return (
getPermissionWeight(permission) >= getPermissionWeight(OWNER_PERMISSION)
)
}
export function ltViewerPermission(permission: string): boolean {
return (
getPermissionWeight(permission) < getPermissionWeight(VIEWER_PERMISSION)
)
}
export function ltEditorPermission(permission: string) {
return (
getPermissionWeight(permission) < getPermissionWeight(EDITOR_PERMISSION)
)
}
export function ltOwnerPermission(permission: string) {
return getPermissionWeight(permission) < getPermissionWeight(OWNER_PERMISSION)
}
export function getPermissionWeight(permission: string) {
switch (permission) {
case VIEWER_PERMISSION:
return 1
case EDITOR_PERMISSION:
return 2
case OWNER_PERMISSION:
return 3
default:
return 0
}
}

View File

@@ -0,0 +1,24 @@
export type Snapshot = {
version: number
original: Download
preview?: Download
text?: Download
thumbnail?: Thumbnail
}
export type Download = {
extension: string
size: number
image?: ImageProps
}
export type ImageProps = {
width: number
height: number
}
export type Thumbnail = {
base64: string
width: number
height: number
}

View File

@@ -0,0 +1,44 @@
/* eslint-disable react-hooks/rules-of-hooks */
import useSWR from 'swr'
import { apiFetcher } from '@/client/fetcher'
export type StorageUsage = {
bytes: number
maxBytes: number
percentage: number
}
export default class StorageAPI {
static useGetAccountUsage(swrOptions?: any) {
const url = `/storage/get_account_usage`
return useSWR<StorageUsage>(
url,
() => apiFetcher({ url, method: 'GET' }) as Promise<StorageUsage>,
swrOptions,
)
}
static useGetWorkspaceUsage(id: string | null | undefined, swrOptions?: any) {
const url = id
? `/storage/get_workspace_usage?${new URLSearchParams({
id,
})}`
: null
return useSWR<StorageUsage>(
url,
() => apiFetcher({ url: url!, method: 'GET' }) as Promise<StorageUsage>,
swrOptions,
)
}
static useGetFileUsage(id: string | null | undefined, swrOptions?: any) {
const url = `/storage/get_file_usage?${new URLSearchParams({
id: id!,
})}`
return useSWR<StorageUsage>(
id ? url : null,
() => apiFetcher({ url, method: 'GET' }) as Promise<StorageUsage>,
swrOptions,
)
}
}

View File

@@ -0,0 +1,86 @@
import useSWR from 'swr'
import { apiFetcher } from '@/client/fetcher'
export enum SortBy {
Email = 'email',
FullName = 'full_name',
}
export enum SortOrder {
Asc = 'asc',
Desc = 'desc',
}
export type User = {
id: string
username: string
email: string
fullName: string
picture?: string
}
export type List = {
data: User[]
totalPages: number
totalElements: number
page: number
size: number
}
export type ListOptions = {
query?: string
organizationId?: string
groupId?: string
nonGroupMembersOnly?: boolean
size?: number
page?: number
sortBy?: SortBy
sortOrder?: SortOrder
}
export default class UserAPI {
static async list(options?: ListOptions) {
return apiFetcher({
url: `/users?${this.paramsFromListOptions(options)}`,
method: 'GET',
}) as Promise<List>
}
static useList(options?: ListOptions, swrOptions?: any) {
const url = `/users?${this.paramsFromListOptions(options)}`
return useSWR<List>(
url,
() => apiFetcher({ url, method: 'GET' }) as Promise<List>,
swrOptions,
)
}
static paramsFromListOptions(options?: ListOptions): URLSearchParams {
const params: any = {}
if (options?.query) {
params.query = encodeURIComponent(options.query.toString())
}
if (options?.organizationId) {
params.organization_id = options.organizationId.toString()
}
if (options?.groupId) {
params.group_id = options.groupId.toString()
}
if (options?.nonGroupMembersOnly) {
params.non_group_members_only = options.nonGroupMembersOnly.toString()
}
if (options?.page) {
params.page = options.page.toString()
}
if (options?.size) {
params.size = options.size.toString()
}
if (options?.sortBy) {
params.sort_by = options.sortBy.toString()
}
if (options?.sortOrder) {
params.sort_order = options.sortOrder.toString()
}
return new URLSearchParams(params)
}
}

View File

@@ -0,0 +1,156 @@
/* eslint-disable react-hooks/rules-of-hooks */
import useSWR from 'swr'
import { apiFetcher } from '@/client/fetcher'
import { Organization } from './organization'
import { PermissionType } from './permission'
export enum SortBy {
Name = 'name',
DateCreated = 'date_created',
DateModified = 'date_modified',
}
export enum SortOrder {
Asc = 'asc',
Desc = 'desc',
}
export type Workspace = {
id: string
name: string
permission: PermissionType
storageCapacity: number
rootId: string
organization: Organization
createTime: string
updateTime?: string
}
export type List = {
data: Workspace[]
totalPages: number
totalElements: number
page: number
size: number
}
export interface CreateOptions {
name: string
image?: string
organizationId: string
storageCapacity: number
}
export type ListOptions = {
query?: string
size?: number
page?: number
sortBy?: SortBy
sortOrder?: SortOrder
}
export interface UpdateNameOptions {
name: string
}
export interface StorageCapacityOptions {
storageCapacity: number
}
export default class WorkspaceAPI {
static async getById(id: string) {
return apiFetcher({
url: `/workspaces/${id}`,
method: 'GET',
}) as Promise<Workspace>
}
static useGetById(id: string | null | undefined, swrOptions?: any) {
const url = `/workspaces/${id}`
return useSWR<Workspace>(
id ? url : null,
() => apiFetcher({ url, method: 'GET' }) as Promise<Workspace>,
swrOptions,
)
}
static useList(options?: ListOptions, swrOptions?: any) {
const url = `/workspaces?${this.paramsFromListOptions(options)}`
return useSWR<List>(
url,
() => apiFetcher({ url, method: 'GET' }) as Promise<List>,
swrOptions,
)
}
static async list(options?: ListOptions) {
return apiFetcher({
url: `/workspaces?${this.paramsFromListOptions(options)}`,
method: 'GET',
}) as Promise<List>
}
static async create(options: CreateOptions) {
return apiFetcher({
url: '/workspaces',
method: 'POST',
body: JSON.stringify(options),
}) as Promise<Workspace>
}
static async updateName(id: string, options: UpdateNameOptions) {
return apiFetcher({
url: `/workspaces/${id}/update_name`,
method: 'POST',
body: JSON.stringify(options),
}) as Promise<Workspace>
}
static async updateStorageCapacity(
id: string,
options: StorageCapacityOptions,
) {
return apiFetcher({
url: `/workspaces/${id}/update_storage_capacity`,
method: 'POST',
body: JSON.stringify(options),
}) as Promise<Workspace>
}
static async updateImage(id: string, file: any) {
const formData = new FormData()
formData.append('file', file)
return apiFetcher({
url: `/workspaces/${id}/update_image`,
method: 'POST',
body: formData,
}) as Promise<Workspace>
}
static async delete(id: string) {
return apiFetcher({
url: `/workspaces/${id}`,
method: 'DELETE',
})
}
static paramsFromListOptions(options?: ListOptions): URLSearchParams {
const params: any = {}
if (options?.query) {
params.query = encodeURIComponent(options.query.toString())
}
if (options?.page) {
params.page = options.page.toString()
}
if (options?.size) {
params.size = options.size.toString()
}
if (options?.sortBy) {
params.sort_by = options.sortBy.toString()
}
if (options?.sortOrder) {
params.sort_order = options.sortOrder.toString()
}
return new URLSearchParams(params)
}
}

View File

@@ -0,0 +1,15 @@
export type ErrorResponse = {
code: string
status: number
message: string
userMessage: string
moreInfo: string
}
export function errorToString(value: any): string {
if (value.code && value.message && value.userMessage && value.moreInfo) {
const error = value as ErrorResponse
return error.userMessage
}
return value.toString()
}

View File

@@ -0,0 +1,94 @@
import { getConfig } from '@/config/config'
import { getAccessToken, getAccessTokenOrRedirect } from '@/infra/token'
import store from '@/store/configure-store'
import { errorOccurred } from '@/store/ui/error'
import { errorToString } from './error'
export type FetcherOptions = {
url: string
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS' | 'HEAD'
body?: BodyInit | null
contentType?: string
redirect?: boolean
authenticate?: boolean
}
export function apiFetcher<T>(options: FetcherOptions) {
return fetcher<T>({ ...options, url: `${getConfig().apiURL}${options.url}` })
}
export function idpFetcher<T>(options: FetcherOptions) {
return fetcher<T>({
...options,
url: `${getConfig().idpURL}${options.url}`,
})
}
export async function fetcher<T>({
url,
method,
body,
contentType,
redirect,
authenticate = true,
}: FetcherOptions): Promise<T | undefined> {
const headers: HeadersInit = {}
if (!contentType) {
headers['Content-Type'] = 'application/json'
}
if (authenticate) {
headers['Authorization'] = `Bearer ${
redirect ? getAccessTokenOrRedirect() : getAccessToken()
}`
}
const response = await baseFetcher(
url,
{
method,
body,
headers,
credentials: authenticate ? 'include' : undefined,
},
redirect,
)
try {
if (response) {
return (await response.json()) as T
}
} catch {
// Ignored
}
}
export async function baseFetcher(
url: string,
init?: RequestInit,
redirect = true,
) {
try {
const response = await fetch(url, init)
return handleResponse(response, redirect)
} catch (error) {
const message = 'Unexpected error occurred.'
store.dispatch(errorOccurred(message))
throw new Error(message)
}
}
async function handleResponse(response: Response, redirect = true) {
if (response.status <= 299) {
return response
} else {
if (response.status === 401 && redirect) {
window.location.href = '/sign-in'
}
let message
try {
message = errorToString(await response.json())
} catch {
message = 'Oops! something went wrong.'
}
store.dispatch(errorOccurred(message))
throw new Error(message)
}
}

View File

@@ -0,0 +1,58 @@
import { idpFetcher } from '@/client/fetcher'
import { User } from './user'
export type CreateOptions = {
email: string
password: string
fullName: string
picture?: string
}
export type SendResetPasswordEmailOptions = {
email: string
}
export type ResetPasswordOptions = {
token: string
newPassword: string
}
export type ConfirmEmailOptions = {
token: string
}
export default class AccountAPI {
static async create(options: CreateOptions) {
return idpFetcher({
url: `/accounts`,
method: 'POST',
body: JSON.stringify(options),
redirect: false,
authenticate: false,
}) as Promise<User>
}
static async sendResetPasswordEmail(options: SendResetPasswordEmailOptions) {
return idpFetcher({
url: `/accounts/send_reset_password_email`,
method: 'POST',
body: JSON.stringify(options),
})
}
static async resetPassword(options: ResetPasswordOptions) {
return idpFetcher({
url: `/accounts/reset_password`,
method: 'POST',
body: JSON.stringify(options),
})
}
static async confirmEmail(options: ConfirmEmailOptions) {
return idpFetcher({
url: `/accounts/confirm_email`,
method: 'POST',
body: JSON.stringify(options),
})
}
}

View File

@@ -0,0 +1,66 @@
import { baseFetcher } from '@/client/fetcher'
import { getConfig } from '@/config/config'
export type GrantType = 'password' | 'refresh_token'
export type Token = {
access_token: string
expires_in: number
token_type: string
refresh_token: string
}
export type ExchangeOptions = {
grant_type: GrantType
username?: string
password?: string
refresh_token?: string
locale?: string
}
export default class TokenAPI {
static async exchange(options: ExchangeOptions) {
const formBody = []
formBody.push(`grant_type=${options.grant_type}`)
if (options.grant_type === 'password') {
if (options.username && options.password) {
formBody.push(`username=${encodeURIComponent(options.username)}`)
formBody.push(`password=${encodeURIComponent(options.password)}`)
} else {
throw new Error('Username or password missing!')
}
}
if (options.grant_type === 'refresh_token') {
if (options.refresh_token) {
formBody.push(
`refresh_token=${encodeURIComponent(options.refresh_token)}`,
)
} else {
throw new Error('Refresh token missing!')
}
}
if (options.locale) {
formBody.push(`&locale=${encodeURIComponent(options.locale)}`)
}
const response = await baseFetcher(
`${getConfig().idpURL}/token`,
{
method: 'POST',
body: formBody.join('&'),
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
},
false,
)
try {
if (response) {
return (await response.json()) as Token
} else {
throw new Error('No response')
}
} catch {
throw new Error('Failed to parse token')
}
}
}

View File

@@ -0,0 +1,104 @@
/* eslint-disable react-hooks/rules-of-hooks */
import useSWR from 'swr'
import { idpFetcher } from '@/client/fetcher'
export type User = {
id: string
username: string
email: string
fullName: string
picture?: string
pendingEmail?: string
}
export type UpdateFullNameOptions = {
fullName: string
}
export type UpdateEmailRequestOptions = {
email: string
}
export type UpdateEmailConfirmationOptions = {
token: string
}
export type UpdatePasswordOptions = {
currentPassword: string
newPassword: string
}
export type DeleteOptions = {
password: string
}
export default class UserAPI {
static useGet(swrOptions?: any) {
const url = `/user`
return useSWR<User>(
url,
() => idpFetcher({ url, method: 'GET' }) as Promise<User>,
swrOptions,
)
}
static async updateFullName(options: UpdateFullNameOptions) {
return idpFetcher({
url: `/user/update_full_name`,
method: 'POST',
body: JSON.stringify(options),
}) as Promise<User>
}
static async updateEmailRequest(options: UpdateEmailRequestOptions) {
return idpFetcher({
url: `/user/update_email_request`,
method: 'POST',
body: JSON.stringify(options),
}) as Promise<User>
}
static async updateEmailConfirmation(
options: UpdateEmailConfirmationOptions,
) {
return idpFetcher({
url: `/user/update_email_confirmation`,
method: 'POST',
body: JSON.stringify(options),
}) as Promise<User>
}
static async updatePassword(options: UpdatePasswordOptions) {
return idpFetcher({
url: `/user/update_password`,
method: 'POST',
body: JSON.stringify(options),
}) as Promise<User>
}
static async delete(options: DeleteOptions) {
return idpFetcher({
url: `/user`,
method: 'DELETE',
body: JSON.stringify(options),
})
}
static async updatePicture(file: File) {
const body = new FormData()
body.append('file', file)
return idpFetcher({
url: `/user/update_picture`,
method: 'POST',
body,
contentType: 'multipart/form-data',
}) as Promise<User>
}
static async deletePicture() {
return idpFetcher({
url: `/user/delete_picture`,
method: 'POST',
}) as Promise<User>
}
}

View File

@@ -0,0 +1,7 @@
export const REFRESH_INTERVAL = 5000
export function swrConfig() {
return {
refreshInterval: REFRESH_INTERVAL,
}
}