feat: add tenant and user management with migrations, handlers, and tests

- Implemented tenant and user management features including:
  - Tenant creation and management with associated migrations.
  - User creation and management with associated migrations.
  - Tenant membership management with associated migrations.
  - Platform user roles management with associated migrations.
  - Quota management with associated migrations.
  - Article and template management with associated migrations.
- Added HTTP handlers for templates and workspaces.
- Created tests for protected and public routes.
- Introduced a script to check tenant scope in SQL queries.
- Documented task plan for backend completion and frontend foundation.
This commit is contained in:
2026-04-01 00:58:42 +08:00
commit de30497f59
210 changed files with 23733 additions and 0 deletions
+80
View File
@@ -0,0 +1,80 @@
import { defineStore } from "pinia";
import { computed, ref } from "vue";
import type { LoginRequest, UserInfo } from "@geo/shared-types";
import { authApi } from "@/lib/api";
import {
clearStoredSession,
readStoredSession,
setStoredTokens,
setStoredUser,
subscribeStoredSession,
type SessionSnapshot,
} from "@/lib/session";
export const useAuthStore = defineStore("auth", () => {
const accessToken = ref<string | null>(null);
const refreshToken = ref<string | null>(null);
const user = ref<UserInfo | null>(null);
const initialized = ref(false);
let unsubscribe: (() => void) | null = null;
function sync(snapshot: SessionSnapshot = readStoredSession()): void {
accessToken.value = snapshot.accessToken;
refreshToken.value = snapshot.refreshToken;
user.value = snapshot.user;
}
async function bootstrap(): Promise<void> {
if (!unsubscribe) {
unsubscribe = subscribeStoredSession(sync);
}
sync();
if (accessToken.value) {
try {
const currentUser = await authApi.me();
setStoredUser(currentUser);
} catch {
clearStoredSession();
}
}
initialized.value = true;
}
async function login(payload: LoginRequest): Promise<UserInfo> {
const response = await authApi.login(payload);
setStoredTokens({
accessToken: response.access_token,
refreshToken: response.refresh_token,
});
setStoredUser(response.user);
return response.user;
}
async function logout(): Promise<void> {
try {
if (accessToken.value) {
await authApi.logout();
}
} finally {
clearStoredSession();
}
}
const isAuthenticated = computed(() => Boolean(accessToken.value && user.value));
return {
accessToken,
refreshToken,
user,
initialized,
isAuthenticated,
bootstrap,
login,
logout,
};
});
+4
View File
@@ -0,0 +1,4 @@
import { createPinia } from "pinia";
export const pinia = createPinia();