diff --git a/.changes/refactor-asset.md b/.changes/refactor-asset.md new file mode 100644 index 0000000..baa6edf --- /dev/null +++ b/.changes/refactor-asset.md @@ -0,0 +1,5 @@ +--- +"algohub": patch:feat +--- + +Full rewrite asset logic, use latest api and support get assets by api. diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index f1adf93..d49fbea 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -28,7 +28,7 @@ dependencies = [ [[package]] name = "algohub" -version = "0.1.1-alpha.3" +version = "0.1.1-alpha.4" dependencies = [ "reqwest", "serde", diff --git a/src/components/UniversalToolBar.vue b/src/components/UniversalToolBar.vue index 8877735..a20e8a9 100644 --- a/src/components/UniversalToolBar.vue +++ b/src/components/UniversalToolBar.vue @@ -128,8 +128,8 @@ const toString = (value: any) => { - + - - + { success: boolean; @@ -30,25 +36,14 @@ export const register = async (form: Register) => { } }; -interface Upload { - id: string; - token: string; - file: File; -} - -interface UploadResponse { - uri: string; - path: string; -} - -export const uploadContent = async (form: Upload) => { +export const uploadContent = async (form: CreateAsset) => { try { - const response = await axios.put("/account/content/upload", form, { + const response = await axios.put("/asset/upload", form, { headers: { "Content-Type": "multipart/form-data", }, }); - return response.data as Response; + return response.data as Response; } catch (error) { return handleAxiosError(AxiosError.from(error)); } @@ -69,12 +64,12 @@ export const updateProfile = async (form: ProfileForm) => { } }; -interface LoginForm { +interface Login { identity: string; password: string; } -export const login = async (form: LoginForm) => { +export const login = async (form: Login) => { try { const response = await axios.post("/account/login", form); return response.data as Response; @@ -92,7 +87,7 @@ export const fetchProfile = async (id: string) => { } }; -interface ProblemForm { +interface CreateProblem { id: string; token: string; @@ -114,7 +109,7 @@ interface ProblemResponse { id: string; } -export const createProblem = async (form: ProblemForm) => { +export const createProblem = async (form: CreateProblem) => { try { const response = await axios.post("/problem/create", form); return response.data as Response; diff --git a/src/scripts/store.ts b/src/scripts/store.ts index 2470dca..65b0536 100644 --- a/src/scripts/store.ts +++ b/src/scripts/store.ts @@ -1,7 +1,7 @@ import { defineStore } from "pinia"; import { computed, ref } from "vue"; -import { expandUrl } from "./utils"; -import { Credentials } from "./types"; +import { expandAssetUrl } from "./utils"; +import { Credentials, RecordId } from "./types"; const prefersDarkMode = () => { return ( @@ -88,7 +88,12 @@ export const useAccountStore = defineStore( } : undefined; }); - const avatarUrl = computed(() => expandUrl(account?.value?.avatar)); + const avatarUrl = computed( + () => account.value?.avatar && expandAssetUrl(account.value?.avatar) + ); + const recordId = computed(() => { + return { tb: "account", id: account.value?.id! }; + }); const mergeProfile = (profile: Partial) => { if (account.value) { @@ -100,7 +105,15 @@ export const useAccountStore = defineStore( account.value = {}; }; - return { account, auth, avatarUrl, isLoggedIn, mergeProfile, logout }; + return { + account, + auth, + avatarUrl, + recordId, + isLoggedIn, + mergeProfile, + logout, + }; }, { persist: true, diff --git a/src/scripts/types.ts b/src/scripts/types.ts index 75531ca..94d8ec9 100644 --- a/src/scripts/types.ts +++ b/src/scripts/types.ts @@ -35,6 +35,16 @@ export interface Profile { rating: number; } +export interface CreateAsset { + auth: Credentials; + owner: string; + file: File; +} + +export interface UserContent { + id: string; +} + export interface ProblemDetail { id: RecordId; title: string; diff --git a/src/scripts/utils.ts b/src/scripts/utils.ts index 7e23f17..79f088f 100644 --- a/src/scripts/utils.ts +++ b/src/scripts/utils.ts @@ -30,3 +30,7 @@ export const withoutHeadSlash = (url: string) => { export const expandUrl = (url?: string) => { return config.base + withoutHeadSlash(url ?? ""); }; + +export const expandAssetUrl = (url: string) => { + return config.base + withoutHeadSlash(`asset/${url}`); +}; diff --git a/src/views/account/[id].vue b/src/views/account/[id].vue index 45e0573..33f47ea 100644 --- a/src/views/account/[id].vue +++ b/src/views/account/[id].vue @@ -3,8 +3,8 @@ import * as api from "@/scripts/api"; import { useAccountStore, useThemeStore } from "@/scripts/store"; import { timeAgo } from "@/scripts/time"; import { ProblemDetail, type Profile } from "@/scripts/types"; -import { expandUrl } from "@/scripts/utils"; -import { useToast } from "primevue"; +import { expandAssetUrl } from "@/scripts/utils"; +import { Avatar, useToast } from "primevue"; import { onMounted, ref, watch } from "vue"; import { useRoute, useRouter } from "vue-router"; @@ -96,8 +96,12 @@ onMounted(async () => {
- + + +

{{ profile.nickname }}

{{ profile.username }} ยท {{ profile.sex ? @@ -112,15 +116,15 @@ onMounted(async () => { {{ profile.email }}
-
+
{{ profile.school }}
-
+
{{ profile.college }}
-
+
{{ profile.major }}
diff --git a/src/views/contest/create.vue b/src/views/contest/create.vue index b8ef750..02b333c 100644 --- a/src/views/contest/create.vue +++ b/src/views/contest/create.vue @@ -141,45 +141,6 @@ const onSelectedFiles = (event: FileUploadSelectEvent) => { }); }; -const uploadTestCases = async (callback: () => void) => { - normalizedFiles.value.forEach(async (fileTuple) => { - if (!fileTuple.input) { - return toast.add({ severity: 'error', summary: 'Error', detail: 'Input file not found for ' + fileTuple.output?.name, life: 3000 }); - } else if (!fileTuple.output) { - return toast.add({ severity: 'error', summary: 'Error', detail: 'Output file not found for ' + fileTuple.input?.name, life: 3000 }); - } - - const res = await api.uploadContent({ - id: accountStore.account.id!, - token: accountStore.account.token!, - file: fileTuple.input, - }) - if (!res.success) { - return toast.add({ severity: 'error', summary: 'Error', detail: res.message, life: 3000 }); - } else { - totalUploadedSize.value += parseInt(formatSize(fileTuple.input.size)); - } - - const outputRes = await api.uploadContent({ - id: accountStore.account.id!, - token: accountStore.account.token!, - file: fileTuple.output, - }) - if (!outputRes.success) { - return toast.add({ severity: 'error', summary: 'Error', detail: outputRes.message, life: 3000 }); - } else { - totalUploadedSize.value += parseInt(formatSize(fileTuple.output.size)); - } - - testCases.push({ - input: res.data!.path, - output: outputRes.data!.path, - }) - normalizedFiles.value.splice(normalizedFiles.value.indexOf(fileTuple), 1); - }); - callback(); -} - const normalizeFiles = (files: File[]) => { const normalizedFiles: { input?: File, output?: File }[] = []; diff --git a/src/views/dashboard.vue b/src/views/dashboard.vue index b40e0f9..dcfa74a 100644 --- a/src/views/dashboard.vue +++ b/src/views/dashboard.vue @@ -27,6 +27,7 @@ onMounted(async () => { if (!accountStore.isLoggedIn) return; const profile = await api.fetchProfile(accountStore.account.id!); if (!profile.success) { + accountStore.logout(); return toast.add({ severity: 'error', summary: 'Error', detail: profile.message, life: 3000 }); } accountStore.mergeProfile(profile.data!); diff --git a/src/views/problem/create.vue b/src/views/problem/create.vue index 6dc4f60..f7ab79b 100644 --- a/src/views/problem/create.vue +++ b/src/views/problem/create.vue @@ -145,8 +145,8 @@ const uploadTestCases = async (callback: () => void) => { } const res = await api.uploadContent({ - id: accountStore.account.id!, - token: accountStore.account.token!, + auth: accountStore.auth!, + owner: `account:${accountStore.account.id}`, file: fileTuple.input, }) if (!res.success) { @@ -156,8 +156,8 @@ const uploadTestCases = async (callback: () => void) => { } const outputRes = await api.uploadContent({ - id: accountStore.account.id!, - token: accountStore.account.token!, + auth: accountStore.auth!, + owner: `account:${accountStore.account.id}`, file: fileTuple.output, }) if (!outputRes.success) { @@ -167,8 +167,8 @@ const uploadTestCases = async (callback: () => void) => { } testCases.push({ - input: res.data!.path, - output: outputRes.data!.path, + input: res.data!.id, + output: outputRes.data!.id, }) normalizedFiles.value.splice(normalizedFiles.value.indexOf(fileTuple), 1); }); diff --git a/src/views/signup.vue b/src/views/signup.vue index 57627eb..1b028de 100644 --- a/src/views/signup.vue +++ b/src/views/signup.vue @@ -2,7 +2,7 @@ import { reactive, type Ref, ref } from "vue"; import { useToast } from 'primevue/usetoast'; import { useRouter } from "vue-router"; -import { useAccountStore, useThemeStore } from "../scripts/store"; +import { useAccountStore } from "../scripts/store"; import VuePictureCropper, { cropper } from 'vue-picture-cropper' import * as api from "@/scripts/api"; import { type FileUploadSelectEvent, useConfirm } from "primevue"; @@ -10,7 +10,6 @@ import { type FileUploadSelectEvent, useConfirm } from "primevue"; const toast = useToast(); const confirm = useConfirm(); const router = useRouter(); -const themeStore = useThemeStore(); const accountStore = useAccountStore(); const activeStep = ref("1"); @@ -68,7 +67,7 @@ const onRegister = async ({ valid, states }: { valid: boolean, states: RegisterF }) if (!res.success) { inProgress.value = false; - return toast.add({ severity: "error", summary: "Registration failed", detail: res.message, life: 3000 }); + return toast.add({ severity: "error", summary: "Registration failed", detail: res.message }); } accountStore.account = { username: states.username!.value, @@ -87,7 +86,9 @@ const onRegister = async ({ valid, states }: { valid: boolean, states: RegisterF toast.add({ severity: "error", summary: "Registration failed", detail: res.message, life: 3000 }); } +const uploading = ref(false); const selectAvatar = async (event: FileUploadSelectEvent) => { + uploading.value = true inProgress.value = true avatarString.value = '' @@ -122,23 +123,25 @@ const selectAvatar = async (event: FileUploadSelectEvent) => { }) if (cropped) { const res = await api.uploadContent({ - id: accountStore.account!.id!, - token: accountStore.account!.token!, + auth: accountStore.auth!, + owner: `account:${accountStore.account!.id}`, file: cropped, }) if (!res.success) { inProgress.value = false return toast.add({ severity: "error", summary: "Upload failed", detail: res.message }); } - accountStore.account!.avatar = res.data!.uri; - toast.add({ severity: "success", summary: "Avatar uploaded", detail: "Your new avatar has been saved." }); + accountStore.account!.avatar = res.data!.id; + toast.add({ severity: "success", summary: "Avatar uploaded", detail: "Your new avatar has been saved.", life: 3000 }); } else { toast.add({ severity: "error", summary: "Crop failed", detail: "Failed to initialize cropper." }); } + uploading.value = false inProgress.value = false }, reject: () => { avatarString.value = '' + uploading.value = false inProgress.value = false } }) @@ -190,7 +193,7 @@ const onUpdateProfile = async ({ valid, states }: { if (!res.success) { toast.add({ severity: "error", summary: "Update failed", detail: res.message }); } else { - toast.add({ severity: "success", summary: "Profile updated", detail: "Your profile has been updated." }); + toast.add({ severity: "success", summary: "Profile updated", detail: "Your profile has been updated.", life: 3000 }); accountStore.mergeProfile({ nickname: states.nickname!.value, signature: states.signature!.value, @@ -277,170 +280,169 @@ const onComplete = async ({ valid, states }: { valid: boolean, states: CompleteF inProgress.value = false; router.push('/') } + +const path = [ + { label: 'Home', link: '/' }, + { label: 'Sign Up' } +]