feat: add Google Sign-In with JWT auth and Neon DB persistence

This commit is contained in:
user
2026-04-27 13:23:47 +09:00
parent 7f77c2aaf4
commit 3aa1d92c98
11 changed files with 393 additions and 19 deletions

View File

@@ -25,6 +25,9 @@ const els = {
status: document.querySelector("#status-line"),
error: document.querySelector("#error-line"),
title: document.querySelector("#session-title"),
gSignIn: document.querySelector(".g_id_signin"),
userInfo: document.querySelector("#user-info"),
logoutButton: document.querySelector("#logout-button"),
};
const readinessClassMap = {
@@ -359,11 +362,48 @@ function evidenceBlock(evidence = []) {
return `<section><h2>Evidence</h2><ul class="small-list">${evidence.map((item) => `<li>${escapeHTML(item.quote || item.id)}</li>`).join("")}</ul></section>`;
}
window.handleCredentialResponse = async (response) => {
try {
const res = await request("/api/v1/auth/google", {
method: "POST",
body: JSON.stringify({ id_token: response.credential }),
});
localStorage.setItem("tutor_token", res.token);
localStorage.setItem("tutor_user", JSON.stringify(res.user));
setStatus(`Signed in as ${res.user.email}`);
renderAuth();
} catch (err) {
showError(err.message);
}
};
function renderAuth() {
const user = JSON.parse(localStorage.getItem("tutor_user") || "null");
const token = localStorage.getItem("tutor_token");
if (user && token) {
els.gSignIn.style.display = "none";
els.userInfo.style.display = "block";
els.userInfo.textContent = user.email;
els.logoutButton.style.display = "inline-block";
} else {
els.gSignIn.style.display = "block";
els.userInfo.style.display = "none";
els.logoutButton.style.display = "none";
}
}
els.logoutButton.addEventListener("click", () => {
localStorage.removeItem("tutor_token");
localStorage.removeItem("tutor_user");
renderAuth();
setStatus("Signed out");
});
async function request(url, options = {}) {
const response = await fetch(url, {
headers: { "Content-Type": "application/json" },
...options,
});
const token = localStorage.getItem("tutor_token");
const headers = { "Content-Type": "application/json" };
if (token) headers["Authorization"] = `Bearer ${token}`;
const response = await fetch(url, { headers, ...options });
const body = await response.json();
if (!response.ok) {
throw new Error(body.error || `Request failed: ${response.status}`);
@@ -398,3 +438,5 @@ function escapeHTML(value) {
.replaceAll('"', "&quot;")
.replaceAll("'", "&#039;");
}
renderAuth();