fix: api client, auth workflow
This commit is contained in:
parent
aef6c2b541
commit
ec41110e0b
@ -1,4 +1,4 @@
|
||||
import { client, type ResponseError, handle_error } from "@/api/client";
|
||||
import { api_client, type ResponseError, handle_error } from "@/client";
|
||||
|
||||
export interface UserCredentials {
|
||||
name: string,
|
||||
@ -7,17 +7,16 @@ export interface UserCredentials {
|
||||
}
|
||||
|
||||
export async function signup(body: UserCredentials): Promise<null | ResponseError> {
|
||||
return await client.post("/auth/signup", JSON.stringify(body))
|
||||
return await api_client.post("/auth/signup", JSON.stringify(body))
|
||||
.catch(handle_error);
|
||||
}
|
||||
|
||||
export async function signin(body: UserCredentials): Promise<null | ResponseError> {
|
||||
return await client.post("/auth/signin", JSON.stringify(body))
|
||||
return await api_client.post("/auth/signin", JSON.stringify(body))
|
||||
.catch(handle_error);
|
||||
}
|
||||
|
||||
|
||||
export async function signout(): Promise<null | ResponseError> {
|
||||
return await client.post("/auth/signout")
|
||||
return await api_client.get("/auth/signout")
|
||||
.catch(handle_error);
|
||||
}
|
||||
|
@ -1,2 +1,3 @@
|
||||
export * as auth from "@/api/auth";
|
||||
export * as user from "@/api/user";
|
||||
export * as repository from "@/api/repository";
|
||||
|
@ -0,0 +1,23 @@
|
||||
import { api_client, type ResponseError, handle_error } from "@/client";
|
||||
|
||||
export interface RepositoryInfo {
|
||||
id: number,
|
||||
capacity: number,
|
||||
used?: number
|
||||
}
|
||||
|
||||
export async function info(): Promise<RepositoryInfo | ResponseError> {
|
||||
return await api_client.get("/repository")
|
||||
.then(async response => { return Promise.resolve<RepositoryInfo>(response.data); })
|
||||
.catch(handle_error);
|
||||
}
|
||||
|
||||
export async function create(): Promise<null | ResponseError> {
|
||||
return await api_client.post("/repository")
|
||||
.catch(handle_error);
|
||||
}
|
||||
|
||||
export async function remove(): Promise<null | ResponseError> {
|
||||
return await api_client.delete("/repository")
|
||||
.catch(handle_error);
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { client, upload_client, resources_client, type ResponseError, handle_error } from "@/api/client";
|
||||
import { api_client, type ResponseError, handle_error } from "@/client";
|
||||
|
||||
export interface UserCredentials {
|
||||
name: string,
|
||||
@ -6,77 +6,30 @@ export interface UserCredentials {
|
||||
email?: string
|
||||
}
|
||||
|
||||
export async function signup(body: UserCredentials): Promise<null | ResponseError> {
|
||||
return await client.post("/auth/signup", JSON.stringify(body))
|
||||
.catch(handle_error);
|
||||
export interface UserInfo {
|
||||
id: string,
|
||||
name: string,
|
||||
lower_name: string,
|
||||
full_name?: string,
|
||||
email?: string,
|
||||
is_email_private: boolean,
|
||||
must_change_password: boolean,
|
||||
login_type: string,
|
||||
created: number,
|
||||
updated: number,
|
||||
last_login?: number,
|
||||
is_active: boolean,
|
||||
is_admin: boolean,
|
||||
avatar?: string
|
||||
}
|
||||
|
||||
export async function signin(body: UserCredentials): Promise<null | ResponseError> {
|
||||
return await client.post("/auth/signin", JSON.stringify(body))
|
||||
.catch(handle_error);
|
||||
}
|
||||
|
||||
export type Image = string | ArrayBuffer;
|
||||
|
||||
export async function register(body: NewUser): Promise<User | ResponseError> {
|
||||
return await client.post("/user/register", JSON.stringify(body))
|
||||
.then(async response => { return Promise.resolve<User>(response.data); })
|
||||
export async function info(): Promise<UserInfo | ResponseError> {
|
||||
return await api_client.get("/user")
|
||||
.then(async response => { return Promise.resolve<UserInfo>(response.data); })
|
||||
.catch(handle_error);
|
||||
}
|
||||
|
||||
export async function login(body: LoginUser): Promise<User | ResponseError> {
|
||||
return await client.post("/user/login", JSON.stringify(body))
|
||||
.then(async response => { return Promise.resolve<User>(response.data); })
|
||||
.catch(handle_error);
|
||||
}
|
||||
|
||||
export async function remove(body: RemoveUser): Promise<null | ResponseError> {
|
||||
return await client.post("/user/remove", JSON.stringify(body))
|
||||
.then(async () => { return Promise.resolve(null); })
|
||||
.catch(handle_error);
|
||||
}
|
||||
|
||||
export async function logout(): Promise<null | ResponseError> {
|
||||
return await client.get("/user/logout")
|
||||
.then(async () => { return Promise.resolve(null); })
|
||||
.catch(handle_error);
|
||||
}
|
||||
|
||||
export async function current(): Promise<User | ResponseError> {
|
||||
return await client.get("/user/current")
|
||||
.then(async response => { return Promise.resolve<User>(response.data); })
|
||||
.catch(handle_error);
|
||||
}
|
||||
|
||||
export async function avatar(file: FormData, progress?: any): Promise<null | ResponseError> {
|
||||
return await upload_client.post("/user/avatar", file, {
|
||||
onUploadProgress: progress ?? null,
|
||||
//headers: { "Accept-Encoding": "gzip" }
|
||||
})
|
||||
.then(async () => { return Promise.resolve(null); })
|
||||
.catch(handle_error);
|
||||
}
|
||||
|
||||
export async function get_avatar(avatar: string): Promise<Image | null | ResponseError> {
|
||||
return await resources_client.get("/avatars/".concat(avatar))
|
||||
.then(async response => {
|
||||
return new Promise<Image | null>((resolve, reject) => {
|
||||
let reader = new FileReader();
|
||||
|
||||
reader.onload = () => {
|
||||
resolve(reader.result);
|
||||
};
|
||||
reader.onerror = (e) => {
|
||||
reject(e);
|
||||
};
|
||||
reader.readAsDataURL(response.data);
|
||||
})
|
||||
})
|
||||
.catch(handle_error);
|
||||
}
|
||||
|
||||
export async function profile(login: string): Promise<User | ResponseError> {
|
||||
return await client.get("/user/".concat(login))
|
||||
.then(async response => { return Promise.resolve<User>(response.data); })
|
||||
.catch(handle_error);
|
||||
}
|
||||
|
@ -22,11 +22,15 @@
|
||||
}
|
||||
|
||||
.button {
|
||||
@apply pt-2 pb-2 pl-5 pr-5 rounded bg-ctp-mantle hover:bg-ctp-base text-ctp-blue;
|
||||
@apply pt-2 pb-2 pl-5 pr-5 rounded bg-ctp-mantle hover:bg-ctp-base text-ctp-blue cursor-pointer;
|
||||
}
|
||||
|
||||
.link-button {
|
||||
@apply button text-ctp-green;
|
||||
@apply button text-ctp-green cursor-pointer;
|
||||
}
|
||||
|
||||
.hline {
|
||||
@apply border-t border-ctp-overlay0 ml-0 mr-0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
|
@ -30,7 +30,7 @@ export function handle_error(error: AxiosError): Promise<ResponseError> {
|
||||
|
||||
const debug = import.meta.hot;
|
||||
|
||||
export const client: AxiosInstance = axios.create({
|
||||
export const api_client: AxiosInstance = axios.create({
|
||||
baseURL: debug ? "http://localhost:54601/api" : "/api",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
@ -38,17 +38,8 @@ export const client: AxiosInstance = axios.create({
|
||||
withCredentials: true,
|
||||
});
|
||||
|
||||
export const upload_client: AxiosInstance = axios.create({
|
||||
baseURL: debug ? "http://localhost:54601/api" : "/api",
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data"
|
||||
},
|
||||
withCredentials: true,
|
||||
});
|
||||
|
||||
export const resources_client: AxiosInstance = axios.create({
|
||||
baseURL: debug ? "http://localhost:54601/resources" : "/resources",
|
||||
responseType: "blob"
|
||||
});
|
||||
|
||||
export default client;
|
@ -1 +1,2 @@
|
||||
export * as api from "@/api";
|
||||
export * as resources from "@/resources";
|
||||
|
@ -0,0 +1,23 @@
|
||||
import { resources_client, type ResponseError, handle_error } from "@/client";
|
||||
|
||||
|
||||
export type Image = string | ArrayBuffer;
|
||||
|
||||
export async function avatars(avatar_id: string, format?: string): Promise<Image | null | ResponseError> {
|
||||
return await resources_client.get("/avatars/".concat(avatar_id), { params: { format: format ? format : "png" }})
|
||||
.then(async response => {
|
||||
return new Promise<Image | null>((resolve, reject) => {
|
||||
let reader = new FileReader();
|
||||
|
||||
reader.onload = () => {
|
||||
resolve(reader.result);
|
||||
};
|
||||
reader.onerror = (e) => {
|
||||
reject(e);
|
||||
};
|
||||
reader.readAsDataURL(response.data);
|
||||
})
|
||||
|
||||
})
|
||||
.catch(handle_error);
|
||||
}
|
@ -1,18 +1,16 @@
|
||||
import { createRouter, createWebHistory } from "vue-router";
|
||||
|
||||
import { user } from "@/api";
|
||||
import { useUserStore } from "@/stores";
|
||||
|
||||
import { api, resources } from "@";
|
||||
|
||||
async function check_authorized(): Promise<boolean> {
|
||||
const userStore = useUserStore();
|
||||
|
||||
// TODO: add timer
|
||||
return await user.current()
|
||||
.then(async user => { userStore.current = user; })
|
||||
return await api.user.info()
|
||||
.then(async user_info => { userStore.info = user_info; })
|
||||
.then(async () => {
|
||||
if (userStore.current.avatar?.length) {
|
||||
await user.get_avatar(userStore.current.avatar)
|
||||
if (!userStore.avatar && userStore.info.avatar) {
|
||||
await resources.avatars(userStore.info.avatar)
|
||||
.then(async avatar => { userStore.avatar = avatar; })
|
||||
}
|
||||
})
|
||||
@ -69,8 +67,8 @@ const router = createRouter({
|
||||
]
|
||||
},
|
||||
{
|
||||
path: "/:user", name: "profile", beforeEnter: [bypass_auth],
|
||||
component: () => import("@/views/user/Profile.vue")
|
||||
path: "/:user/repository", name: "repository", beforeEnter: [required_auth],
|
||||
component: () => import("@/views/Repository.vue")
|
||||
},
|
||||
{
|
||||
path: "/admin/settings", name: "settings", beforeEnter: [required_auth, required_admin],
|
||||
|
@ -2,17 +2,18 @@ import { defineStore } from "pinia";
|
||||
import { ref, type Ref } from "vue";
|
||||
|
||||
import { user } from "@/api";
|
||||
import { resources } from "@";
|
||||
|
||||
export const useUserStore = defineStore("user", () => {
|
||||
const current: Ref<user.User | null> = ref(null);
|
||||
const avatar: Ref<Blob | null> = ref(null);
|
||||
const info: Ref<user.UserInfo | null> = ref(null);
|
||||
const avatar: Ref<resources.Image | null> = ref(null);
|
||||
|
||||
function clear() {
|
||||
current.value = null;
|
||||
info.value = null;
|
||||
avatar.value = null;
|
||||
}
|
||||
|
||||
return { current, avatar, clear };
|
||||
return { info, avatar, clear };
|
||||
});
|
||||
|
||||
export const useMiscStore = defineStore("misc", () => {
|
||||
|
@ -5,14 +5,15 @@ import DropdownMenu from "@/components/DropdownMenu.vue";
|
||||
import { ref, onMounted } from "vue";
|
||||
|
||||
import router from "@/router";
|
||||
import { user } from "@/api";
|
||||
import { user, auth } from "@/api";
|
||||
import { resources } from "@";
|
||||
import { useUserStore } from "@/stores";
|
||||
|
||||
const userStore = useUserStore();
|
||||
const error = ref(null);
|
||||
|
||||
async function signout() {
|
||||
await user.logout()
|
||||
await auth.signout()
|
||||
.then(async () => {
|
||||
userStore.clear();
|
||||
router.push({ path: "/" });
|
||||
@ -28,36 +29,36 @@ async function signout() {
|
||||
<template #left>
|
||||
<RouterLink class="link-button" to="/">Home</RouterLink>
|
||||
</template>
|
||||
<template #right>
|
||||
<DropdownMenu v-if="userStore.current">
|
||||
<template #right v-if="userStore.info">
|
||||
<RouterLink v-if="userStore.info.is_admin" :to="{ name: 'settings' }" class="link-button">Settings
|
||||
</RouterLink>
|
||||
<DropdownMenu>
|
||||
<template #button>
|
||||
<div class="pl-3 pr-3 flex gap-2 items-center rounded hover:bg-zinc-600 cursor-pointer">
|
||||
<div class="max-w-8" v-if="userStore.avatar"><img :src="userStore.avatar"></div>
|
||||
<span class="flex min-w-9 min-h-9 items-center">{{userStore.current.login }}</span>
|
||||
<div class="link-button flex">
|
||||
<span>{{ userStore.info.name }}</span>
|
||||
<div class="max-w-6 ml-4" v-if="userStore.avatar">
|
||||
<img :src="userStore.avatar">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<div
|
||||
class="absolute z-20 flex flex-col left-auto right-0 mt-4 bg-zinc-700 border rounded border-zinc-500 mr-3">
|
||||
<RouterLink :to="{ name: 'profile', params: { user: userStore.current.login } }"
|
||||
class="flex min-w-7 pl-5 pr-5 pt-1 pb-1 hover:bg-zinc-600">
|
||||
Profile</RouterLink>
|
||||
<RouterLink :to="{ name: 'prefs' }"
|
||||
class="flex min-w-7 pl-5 pr-5 pt-1 pb-1 hover:bg-zinc-600">
|
||||
class="absolute z-20 flex flex-col left-auto right-0 mt-4 mr-3 bg-ctp-mantle border rounded border-ctp-overlay0">
|
||||
<RouterLink :to="{ name: 'repository', params: { user: userStore.info.lower_name } }"
|
||||
class="button">
|
||||
Repository</RouterLink>
|
||||
<RouterLink :to="{ name: 'prefs' }" class="button">
|
||||
Preferencies</RouterLink>
|
||||
<div class="border-t border-zinc-500 ml-0 mr-0"></div>
|
||||
<RouterLink v-if="userStore.current.is_admin" :to="{ name: 'settings' }"
|
||||
class="flex min-w-7 pl-5 pr-5 pt-1 pb-1 hover:bg-zinc-600">
|
||||
Settings</RouterLink>
|
||||
<div class="border-t border-zinc-500 ml-0 mr-0"></div>
|
||||
<div @click="signout"
|
||||
class="flex min-w-7 pl-5 pr-5 pt-1 pb-1 hover:bg-zinc-600 cursor-pointer">
|
||||
|
||||
<div class="hline"></div>
|
||||
<div @click="signout" class="button">
|
||||
Sign Out</div>
|
||||
</div>
|
||||
</template>
|
||||
</DropdownMenu>
|
||||
|
||||
<RouterLink v-if="!userStore.current" class="link-button" to="/user/login">Sign In</RouterLink>
|
||||
</template>
|
||||
<template #right v-else>
|
||||
<RouterLink class="link-button" to="/auth/signin">Sign In</RouterLink>
|
||||
</template>
|
||||
</NavBar>
|
||||
|
||||
@ -68,11 +69,10 @@ async function signout() {
|
||||
|
||||
<div class="relative overflow-hidden h-full ">
|
||||
</div>
|
||||
<footer
|
||||
class="flex justify-between pb-2 pt-2 pl-5 pr-5 bg-ctp-mantle">
|
||||
<footer class="flex justify-between pb-2 pt-2 pl-5 pr-5 bg-ctp-mantle">
|
||||
<a href="/">Made with glove by Elnafo, 2024</a>
|
||||
<div>
|
||||
|
||||
|
||||
</div>
|
||||
<a href="/api/docs">API</a>
|
||||
</footer>
|
||||
|
@ -0,0 +1,81 @@
|
||||
<script setup lang="ts">
|
||||
import Base from "@/views/Base.vue";
|
||||
import Error from "@/components/Error.vue";
|
||||
|
||||
import { ref, onMounted, watch } from "vue";
|
||||
import { onBeforeRouteUpdate, useRoute } from "vue-router"
|
||||
|
||||
import { repository } from "@/api";
|
||||
import { useUserStore } from "@/stores";
|
||||
import router from "@/router";
|
||||
|
||||
const route = useRoute();
|
||||
const userStore = useUserStore();
|
||||
const error = ref<string>(null);
|
||||
|
||||
const repository_info = ref(null);
|
||||
const is_created = ref(null);
|
||||
|
||||
onMounted(async () => {
|
||||
await repository.info()
|
||||
.then(async _repository_info => {
|
||||
is_created.value = true;
|
||||
repository_info.value = _repository_info;
|
||||
})
|
||||
.catch(err => {
|
||||
is_created.value = false;
|
||||
});
|
||||
});
|
||||
|
||||
async function create_repository() {
|
||||
await repository.create()
|
||||
.then(async () => {
|
||||
await repository.info()
|
||||
.then(async _repository_info => {
|
||||
repository_info.value = _repository_info;
|
||||
})
|
||||
.catch(err => {
|
||||
error.value = err;
|
||||
});
|
||||
|
||||
is_created.value = true;
|
||||
})
|
||||
.catch(err => {
|
||||
error.value = err;
|
||||
})
|
||||
}
|
||||
|
||||
function round_size(size: number, mesure: string) {
|
||||
if (mesure == "GB") {
|
||||
return size / 8 / 1024 / 1024;
|
||||
} else if (mesure == "MB") {
|
||||
return size / 8 / 1024;
|
||||
}
|
||||
}
|
||||
|
||||
function size_procent() {
|
||||
return Math.round(repository_info.value.used / repository_info.value.capacity) * 100;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Base>
|
||||
<Error v-if="error">{{ error }}</Error>
|
||||
<section v-if="is_created">
|
||||
<div class="flex items-center">
|
||||
<div class="w-full rounded-full h-2.5 bg-ctp-surface0">
|
||||
<div class="bg-ctp-lavender h-2.5 rounded-full" :style="{ width: size_procent() + '%' }"></div>
|
||||
</div>
|
||||
<span class="min-w-32 text-center">{{ round_size(repository_info.used, "GB") }} / {{
|
||||
round_size(repository_info.capacity, "GB") }} GB</span>
|
||||
</div>
|
||||
</section>
|
||||
<section v-else>
|
||||
<p>It looks like you don't have a repository yet...</p>
|
||||
<div class="flex justify-center mt-8">
|
||||
<button @click="create_repository" class="button">+ Create repository</button>
|
||||
</div>
|
||||
</section>
|
||||
</Base>
|
||||
</template>
|
@ -40,7 +40,7 @@ async function signin() {
|
||||
|
||||
await api.auth.signin(body)
|
||||
.then(async () => {
|
||||
//userStore.current = user;
|
||||
//userStore.info = user_info;
|
||||
router.push({ path: "/" });
|
||||
})
|
||||
.catch(err => { error.value = err.message; });
|
||||
@ -62,7 +62,7 @@ async function signin() {
|
||||
</div>
|
||||
<div class="mb-5 flex justify-between items-center">
|
||||
<button @click="signin" class="button">Sign In</button>
|
||||
<button @click="$router.push('/user/register')" class="button">Sign Up</button>
|
||||
<button @click="$router.push('/auth/signup')" class="button">Sign Up</button>
|
||||
</div>
|
||||
</form>
|
||||
<Error v-if="error">{{ error }}</Error>
|
||||
|
@ -18,7 +18,7 @@ async function signup() {
|
||||
return;
|
||||
}
|
||||
await api.auth.signup({ name: login.value, password: password.value, email: email.value })
|
||||
.then(async user => { router.push({ path: "/user/login" }); })
|
||||
.then(async user => { router.push({ path: "/auth/signin" }); })
|
||||
.catch(err => { error.value = err.message; });
|
||||
};
|
||||
</script>
|
||||
|
Loading…
Reference in New Issue
Block a user