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:
tajniak81
2026-07-13 11:34:02 +02:00
co-authored by Claude Opus 4.8
parent ba3f227361
commit 75a2ccc226
47 changed files with 230 additions and 161 deletions
+51
View File
@@ -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;
}