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
+146
View File
@@ -0,0 +1,146 @@
import { createRouter, createWebHistory } from "vue-router";
import { pinia } from "@/stores/pinia";
import { useAuthStore } from "@/stores/auth";
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: "/login",
name: "login",
component: () => import("@/views/LoginView.vue"),
meta: {
titleKey: "route.login.title",
descriptionKey: "route.login.description",
},
},
{
path: "/",
component: () => import("@/layouts/AppShell.vue"),
meta: {
requiresAuth: true,
},
children: [
{
path: "",
redirect: "/workspace",
},
{
path: "workspace",
name: "workspace",
component: () => import("@/views/WorkspaceView.vue"),
meta: {
titleKey: "route.workspace.title",
descriptionKey: "route.workspace.description",
navKey: "/workspace",
},
},
{
path: "articles/templates",
name: "articles-templates",
component: () => import("@/views/TemplatesView.vue"),
meta: {
titleKey: "route.templates.title",
descriptionKey: "route.templates.description",
navKey: "/articles/templates",
},
},
{
path: "articles/wizard",
name: "article-wizard",
component: () => import("@/views/TemplateWizardView.vue"),
meta: {
titleKey: "route.wizard.title",
descriptionKey: "route.wizard.description",
navKey: "/articles/templates",
},
},
{
path: "articles/custom",
name: "articles-custom",
component: () => import("@/views/FeatureStubView.vue"),
meta: {
titleKey: "route.custom.title",
descriptionKey: "route.custom.description",
navKey: "/articles/custom",
},
},
{
path: "articles/optimize",
name: "articles-optimize",
component: () => import("@/views/FeatureStubView.vue"),
meta: {
titleKey: "route.optimize.title",
descriptionKey: "route.optimize.description",
navKey: "/articles/optimize",
},
},
{
path: "media",
name: "media",
component: () => import("@/views/FeatureStubView.vue"),
meta: {
titleKey: "route.media.title",
descriptionKey: "route.media.description",
navKey: "/media",
},
},
{
path: "brands",
name: "brands",
component: () => import("@/views/BrandsView.vue"),
meta: {
titleKey: "route.brands.title",
descriptionKey: "route.brands.description",
navKey: "/brands",
},
},
{
path: "tracking",
name: "tracking",
component: () => import("@/views/FeatureStubView.vue"),
meta: {
titleKey: "route.tracking.title",
descriptionKey: "route.tracking.description",
navKey: "/tracking",
},
},
{
path: "knowledge",
name: "knowledge",
component: () => import("@/views/FeatureStubView.vue"),
meta: {
titleKey: "route.knowledge.title",
descriptionKey: "route.knowledge.description",
navKey: "/knowledge",
},
},
],
},
],
});
router.beforeEach(async (to) => {
const authStore = useAuthStore(pinia);
if (!authStore.initialized) {
await authStore.bootstrap();
}
const requiresAuth = to.matched.some((record) => record.meta.requiresAuth);
if (requiresAuth && !authStore.isAuthenticated) {
return {
name: "login",
query: { redirect: to.fullPath },
};
}
if (to.name === "login" && authStore.isAuthenticated) {
return { name: "workspace" };
}
return true;
});
export { router };
+14
View File
@@ -0,0 +1,14 @@
import "vue-router";
declare module "vue-router" {
interface RouteMeta {
title?: string;
description?: string;
titleKey?: string;
descriptionKey?: string;
requiresAuth?: boolean;
navKey?: string;
}
}
export {};