Restructure Web App into server/ + web/ (GsmNode parity)
Reorganize the Web App to match the GsmNode project layout: a Go backend-for-frontend in server/ that embeds the built SPA and reverse-proxies /api/* to the API Server, with the Vue 3 + Vite frontend moved into web/. - Move all frontend files into web/ (history preserved via renames) - Point vite build output at ../server/dist for Go embedding - Add server/ Go BFF (main.go, go.mod, .env.example, Run-WebApp.ps1) - Drop Docker/nginx deploy (Dockerfile, docker-compose.yml, nginx.conf.template, .dockerignore) in favor of the BFF, matching GsmNode - Update .claude/launch.json to run the dev server from web/ - Rewrite README.md for the new layout Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
ba3f227361
commit
75a2ccc226
@@ -0,0 +1,51 @@
|
||||
// Reactive auth state shared across the app. Token + user are persisted to
|
||||
// localStorage so a refresh keeps the session. The actual network calls live in
|
||||
// api.js (which reads the token from localStorage on each request).
|
||||
import { reactive, computed } from "vue";
|
||||
import { api, TOKEN_KEY, USER_KEY } from "./api";
|
||||
import { applyProfilePrefs } from "./prefs";
|
||||
|
||||
export const state = reactive({
|
||||
token: localStorage.getItem(TOKEN_KEY) || "",
|
||||
user: JSON.parse(localStorage.getItem(USER_KEY) || "null"),
|
||||
// Full settings-panel profile (bio, theme, locale, ...), fetched separately
|
||||
// from /api/me since the login response only carries id/email/name.
|
||||
profile: null,
|
||||
});
|
||||
|
||||
export const isAuthenticated = computed(() => !!state.token);
|
||||
|
||||
// Admin gate for the UI. Driven by the full profile (fetched from /api/me),
|
||||
// which always reflects the current role from the DB — so a promotion/demotion
|
||||
// takes effect on the next profile refresh without needing a re-login.
|
||||
export const isAdmin = computed(
|
||||
() => state.profile?.role === "admin" || state.user?.role === "admin"
|
||||
);
|
||||
|
||||
export async function login(email, password) {
|
||||
const res = await api.login(email, password);
|
||||
state.token = res.token;
|
||||
state.user = res.user;
|
||||
localStorage.setItem(TOKEN_KEY, res.token);
|
||||
localStorage.setItem(USER_KEY, JSON.stringify(res.user));
|
||||
await refreshProfile();
|
||||
return res;
|
||||
}
|
||||
|
||||
export function logout() {
|
||||
state.token = "";
|
||||
state.user = null;
|
||||
state.profile = null;
|
||||
localStorage.removeItem(TOKEN_KEY);
|
||||
localStorage.removeItem(USER_KEY);
|
||||
}
|
||||
|
||||
// Fetches the full profile (used for Settings + to apply appearance prefs).
|
||||
// Safe to call on app boot when a token already exists from a previous visit.
|
||||
export async function refreshProfile() {
|
||||
if (!state.token) return null;
|
||||
const profile = await api.getMe();
|
||||
state.profile = profile;
|
||||
applyProfilePrefs(profile);
|
||||
return profile;
|
||||
}
|
||||
Reference in New Issue
Block a user