First Commit(なのに未完成)

This commit is contained in:
2026-01-01 12:47:24 +09:00
commit d3697c164a
31 changed files with 2379 additions and 0 deletions
+189
View File
@@ -0,0 +1,189 @@
<template>
<div
:class='[
"w-full", "h-full", "pt-10",
"flex", "justify-center",
]'
>
<div :class='[
"w-130", "h-fit", "flex", "flex-col", "p-6",
"bg-white", "text-black",
"dark:bg-neutral-600", "dark:text-white",
"shadow-sm", "rounded-2xl",
]'>
<h1 class="font-bold">サインイン</h1>
<SmallText v-html="
`uwuzu ${sprtVerTxt}に対応しています。<br />
ただし、最新版でない場合一部の機能が制限される可能性があります。`
"/>
<hr class="my-3" />
<form class="flex flex-col gap-2" @submit="onSubmit">
<div class="flex flex-col">
<label for="origin">オリジン(必須)</label>
<InputText
type="url"
placeholder="https://uwuzu.net"
id="origin"
v-model="origin"
:disabled="isSubmitting"
/>
<SmallText>
サインインするサーバーのオリジンを入力してください。
</SmallText>
</div>
<hr class="w-[90%] m-auto" />
<details>
<summary class="select-none cursor-pointer">その他のオプション</summary>
<div class="flex flex-col mt-1">
<label for="token">APIトークン(任意)</label>
<InputText
type="password"
placeholder="ABCD123456789"
id="token"
v-model="token"
:disabled="isSubmitting"
/>
<div class="text-yellow-200">
通常は必要ありません。
</div>
<SmallText>
サインインするアカウントのAPIトークンを入力してください。権限の確認は行いません。<br />
以下の権限が必要です。
</SmallText>
<ul>
<li class="list-disc list-inside" v-for="scope in requiredScopes">
{{ scope }}
</li>
</ul>
</div>
</details>
<Button
type="submit"
class="m-auto"
:disabled="isSubmitting"
>
<span v-if="isSubmitting">サインイン中...</span>
<span v-else>サインイン</span>
</Button>
</form>
</div>
</div>
</template>
<script lang="ts" setup>
import { useHead } from "@vueuse/head";
import { ref } from "vue";
import { z } from "zod/v3";
import { v4 as UUID } from "uuid";
import { useRouter } from "vue-router";
import Database from "@/lib/db";
import { isSignin } from "@/lib/account";
import useAPI from "@/lib/api";
import { isVersionAvailable } from "@/lib/version";
import SmallText from "@/components/SmallText.vue";
import InputText from "@/components/InputText.vue";
import Button from "@/components/Button.vue";
useHead({
title: "サインイン | Clean Follow uwuzu",
});
if (await isSignin(new Database())) {
useRouter().replace("/");
}
const emit = defineEmits(["failed"]);
const sprtVer = __CONFIG.uwuzu.supportedVersion;
const sprtVerTxt = `v${sprtVer.min}${
sprtVer.min !== sprtVer.max
? `からv${sprtVer.max}`
: ''
}`;
const requiredScopes = __CONFIG.uwuzu.requiredScopes;
const origin = ref<string>("");
const token = ref<string>("");
const isSubmitting = ref<boolean>(false);
const onSubmit = async (e: Event) => {
e.preventDefault();
isSubmitting.value = true;
const result = z.object({
origin: z.string({
invalid_type_error: "オリジンが文字列ではありません",
})
.min(1, "オリジンが入力されていません")
.refine(value => {
if (value.length === 0)
return true;
try {
const url = new URL(value);
return url.origin === value;
} catch {
return false;
}
}, "オリジンが有効ではありません"),
token: z.string({
invalid_type_error: "APIトークンが文字列ではありません",
})
.min(1, "APIトークンが入力されていません")
.length(64, "APIトークンは64文字です")
.or(z.literal("")),
}).safeParse({
origin: origin.value,
token: token.value,
});
if (!result.success) {
emit("failed", result.error.errors.map(err => err.message).join("\n"));
return;
}
const serverinfo = await useAPI(result.data.origin, "/serverinfo-api", {
method: "POST",
cache: "no-store",
});
if (serverinfo.software.name !== "uwuzu") {
throw new Error("サーバーがuwuzuではありません。");
}
if (!isVersionAvailable({
current: serverinfo.software.version,
min: sprtVer.min,
max: sprtVer.max,
})) {
throw "サーバーのバージョンが対象外です。";
}
if (result.data.token.length === 64) {
const callback = new URL("/signin/callback", window.location.origin);
callback.searchParams.set("type", "token");
callback.searchParams.set("origin", result.data.origin);
callback.searchParams.set("session", result.data.token);
window.location.href = callback.toString();
return;
}
const session = UUID();
const callback = new URL("/signin/callback", window.location.origin);
callback.searchParams.set("type", "auth");
callback.searchParams.set("origin", result.data.origin);
callback.searchParams.set("session", session);
const authURL = new URL("/api/auth", result.data.origin);
authURL.searchParams.set("session", session);
authURL.searchParams.set("scope", requiredScopes.join(","));
authURL.searchParams.set("callback", callback.toString());
authURL.searchParams.set("client", "Clean Follow uwuzu");
authURL.searchParams.set("about", "uwuzu向けのフォロー整理アプリです。");
authURL.searchParams.set("icon", new URL("/cfu.svg", window.location.origin).toString());
window.location.href = authURL.toString();
return;
};
</script>