inicontent 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/pull_request.yml +18 -0
- package/CONTRIBUTING.md +67 -0
- package/LICENSE +21 -0
- package/README.md +142 -0
- package/app/app.vue +106 -0
- package/app/assets/main.css +974 -0
- package/app/components/Api/EndpointCard.vue +169 -0
- package/app/components/Api/SchemaDocsTable.vue +208 -0
- package/app/components/Asset/Card.vue +1363 -0
- package/app/components/Asset/Grid.vue +295 -0
- package/app/components/Asset/Icon.vue +38 -0
- package/app/components/Asset/Preview.vue +486 -0
- package/app/components/Asset/Scanner.vue +700 -0
- package/app/components/Asset/Thumb.vue +708 -0
- package/app/components/Auth/Reset.vue +99 -0
- package/app/components/Auth/index.vue +346 -0
- package/app/components/ChatInterface.vue +2326 -0
- package/app/components/ChatPagePreview.vue +34 -0
- package/app/components/Column/Array.vue +29 -0
- package/app/components/Column/Asset.vue +21 -0
- package/app/components/Column/Boolean.vue +12 -0
- package/app/components/Column/Color.vue +9 -0
- package/app/components/Column/Date.vue +57 -0
- package/app/components/Column/Edit.vue +215 -0
- package/app/components/Column/Email.vue +9 -0
- package/app/components/Column/Html.vue +27 -0
- package/app/components/Column/Id.vue +20 -0
- package/app/components/Column/Locale.vue +32 -0
- package/app/components/Column/Object.vue +18 -0
- package/app/components/Column/Password.vue +7 -0
- package/app/components/Column/Role.vue +15 -0
- package/app/components/Column/S.vue +14 -0
- package/app/components/Column/Table/Single.vue +49 -0
- package/app/components/Column/Table/index.vue +42 -0
- package/app/components/Column/Tags.vue +31 -0
- package/app/components/Column/Text.vue +9 -0
- package/app/components/Column/Url.vue +9 -0
- package/app/components/Column/index.vue +41 -0
- package/app/components/Dashboard/Editor.vue +221 -0
- package/app/components/Dashboard/Grid.vue +270 -0
- package/app/components/Dashboard/View.vue +131 -0
- package/app/components/Dashboard/Widget/BarChart.vue +73 -0
- package/app/components/Dashboard/Widget/Counter.vue +51 -0
- package/app/components/Dashboard/Widget/LineChart.vue +76 -0
- package/app/components/Dashboard/Widget/PieChart.vue +75 -0
- package/app/components/Dashboard/Widget/Table.vue +94 -0
- package/app/components/Dashboard/WidgetEditor.vue +274 -0
- package/app/components/Data/Array.vue +102 -0
- package/app/components/Data/Html.vue +9 -0
- package/app/components/Data/Icon.vue +9 -0
- package/app/components/Data/Object.vue +10 -0
- package/app/components/Data/S.vue +16 -0
- package/app/components/Data/Table.vue +53 -0
- package/app/components/Data/Value.vue +34 -0
- package/app/components/Data/index.vue +14 -0
- package/app/components/Field/Array.vue +324 -0
- package/app/components/Field/Asset/Actions.vue +97 -0
- package/app/components/Field/Asset/index.vue +639 -0
- package/app/components/Field/Boolean.vue +26 -0
- package/app/components/Field/Checkbox.vue +48 -0
- package/app/components/Field/Color.vue +27 -0
- package/app/components/Field/Date.vue +30 -0
- package/app/components/Field/Email.vue +51 -0
- package/app/components/Field/EmailTemplate.vue +420 -0
- package/app/components/Field/Html/ColorPicker.vue +127 -0
- package/app/components/Field/Html/Editor.vue +544 -0
- package/app/components/Field/Html/index.vue +25 -0
- package/app/components/Field/Icon.vue +114 -0
- package/app/components/Field/JSON.vue +113 -0
- package/app/components/Field/Mention.vue +36 -0
- package/app/components/Field/Number.vue +27 -0
- package/app/components/Field/Object.vue +92 -0
- package/app/components/Field/Password.vue +55 -0
- package/app/components/Field/Radio.vue +47 -0
- package/app/components/Field/S.vue +44 -0
- package/app/components/Field/Select.vue +49 -0
- package/app/components/Field/Table.vue +348 -0
- package/app/components/Field/Tags.vue +113 -0
- package/app/components/Field/Text.vue +31 -0
- package/app/components/Field/Textarea.vue +33 -0
- package/app/components/Field/Url.vue +40 -0
- package/app/components/Field/Wrapper.vue +128 -0
- package/app/components/Field/index.vue +68 -0
- package/app/components/FloatingChatbot.vue +73 -0
- package/app/components/Form/Card.vue +151 -0
- package/app/components/Form/Drawer.vue +195 -0
- package/app/components/Form/index.vue +621 -0
- package/app/components/Header.vue +328 -0
- package/app/components/Offline/ConflictModal.vue +200 -0
- package/app/components/Offline/SyncStatus.vue +249 -0
- package/app/components/PlatformFeatureLanding.vue +65 -0
- package/app/components/Table/Backups.vue +638 -0
- package/app/components/Table/Flows.vue +814 -0
- package/app/components/Table/Grid.vue +296 -0
- package/app/components/Table/Icon.vue +36 -0
- package/app/components/Table/Logs.vue +181 -0
- package/app/components/Table/Schedules.vue +722 -0
- package/app/components/Table/Search/Button.vue +256 -0
- package/app/components/Table/Search/Items.vue +286 -0
- package/app/components/Table/Search/index.vue +87 -0
- package/app/components/Table/Settings/Schema.vue +801 -0
- package/app/components/Table/Settings/index.vue +631 -0
- package/app/components/Table/TranslateDrawer.vue +526 -0
- package/app/components/Table/Views/Kanban.vue +289 -0
- package/app/components/Table/Views/Table.vue +1273 -0
- package/app/components/Table/index.vue +890 -0
- package/app/composables/Translation/define.ts +41 -0
- package/app/composables/Translation/fetch.ts +107 -0
- package/app/composables/Translation/languages.ts +1 -0
- package/app/composables/Translation/loadCoreTranslations.ts +38 -0
- package/app/composables/Translation/translate.ts +250 -0
- package/app/composables/databaseCookies.ts +79 -0
- package/app/composables/fieldValidator.ts +72 -0
- package/app/composables/fieldsList.ts +301 -0
- package/app/composables/formatDatabase.ts +101 -0
- package/app/composables/index.ts +444 -0
- package/app/composables/openDrawer.ts +118 -0
- package/app/composables/optimizeFile.ts +287 -0
- package/app/composables/renderLabel.ts +88 -0
- package/app/composables/schemaClipboard.ts +95 -0
- package/app/composables/search.ts +353 -0
- package/app/composables/setThemeConfig.ts +48 -0
- package/app/composables/translationValue.ts +169 -0
- package/app/composables/useAssetPreview.ts +84 -0
- package/app/composables/useAssetUploader.ts +56 -0
- package/app/composables/useDashboardData.ts +310 -0
- package/app/composables/useDocumentThumbnail.ts +351 -0
- package/app/composables/useEcharts.ts +33 -0
- package/app/composables/useLocalDatabaseConfig.ts +51 -0
- package/app/composables/useOcr.ts +300 -0
- package/app/composables/useOfflineFetch.ts +194 -0
- package/app/composables/useOfflineItem.ts +107 -0
- package/app/composables/useOfflineQueue.ts +302 -0
- package/app/composables/useOfflineSync.ts +235 -0
- package/app/composables/usePasskeyAuth.ts +156 -0
- package/app/composables/usePdfCompressor.ts +180 -0
- package/app/composables/usePlatformRedirect.ts +72 -0
- package/app/composables/useRealtimeSync.ts +233 -0
- package/app/composables/useReferencedItems.ts +180 -0
- package/app/composables/useScanner.ts +664 -0
- package/app/composables/useSubscription.ts +650 -0
- package/app/composables/useThumbnailCache.ts +136 -0
- package/app/composables/useVideoCompressor.ts +194 -0
- package/app/error.vue +112 -0
- package/app/layouts/api.vue +188 -0
- package/app/layouts/dashboard.vue +91 -0
- package/app/layouts/default.vue +228 -0
- package/app/layouts/table.vue +471 -0
- package/app/locales/ar.ts +996 -0
- package/app/locales/en.ts +655 -0
- package/app/locales/es.ts +996 -0
- package/app/locales/fr.ts +997 -0
- package/app/middleware/dashboard.ts +33 -0
- package/app/middleware/database.ts +83 -0
- package/app/middleware/global.ts +1 -0
- package/app/middleware/sid-ingest.global.ts +29 -0
- package/app/middleware/table.ts +54 -0
- package/app/middleware/user.ts +26 -0
- package/app/pages/[[database]]/admin/api/auth/index.vue +306 -0
- package/app/pages/[[database]]/admin/api/index.vue +18 -0
- package/app/pages/[[database]]/admin/api/tables/[table]/index.vue +497 -0
- package/app/pages/[[database]]/admin/api/tables/index.vue +87 -0
- package/app/pages/[[database]]/admin/billing/index.vue +16 -0
- package/app/pages/[[database]]/admin/dashboards/[id]/edit.vue +66 -0
- package/app/pages/[[database]]/admin/dashboards/[id]/index.vue +117 -0
- package/app/pages/[[database]]/admin/dashboards/index.vue +20 -0
- package/app/pages/[[database]]/admin/index.vue +28 -0
- package/app/pages/[[database]]/admin/settings.vue +526 -0
- package/app/pages/[[database]]/admin/tables/[table]/[id]/edit.vue +143 -0
- package/app/pages/[[database]]/admin/tables/[table]/[id]/index.vue +146 -0
- package/app/pages/[[database]]/admin/tables/[table]/flows.vue +10 -0
- package/app/pages/[[database]]/admin/tables/[table]/index.vue +10 -0
- package/app/pages/[[database]]/admin/tables/[table]/new.vue +12 -0
- package/app/pages/[[database]]/admin/tables/[table]/schedules.vue +10 -0
- package/app/pages/[[database]]/admin/tables/[table]/settings.vue +30 -0
- package/app/pages/[[database]]/admin/tables/assets/[...path].vue +24 -0
- package/app/pages/[[database]]/admin/tables/assets/flows.vue +10 -0
- package/app/pages/[[database]]/admin/tables/backups/index.vue +10 -0
- package/app/pages/[[database]]/admin/tables/blocks/index.vue +16 -0
- package/app/pages/[[database]]/admin/tables/index.vue +20 -0
- package/app/pages/[[database]]/admin/tables/pages/index.vue +16 -0
- package/app/pages/[[database]]/admin/tables/templates/index.vue +16 -0
- package/app/pages/[[database]]/api/index.vue +120 -0
- package/app/pages/[[database]]/auth/index.vue +12 -0
- package/app/pages/[[database]]/auth/reset.vue +10 -0
- package/app/pages/[[database]]/index.vue +10 -0
- package/app/plugins/offline-warmup.client.ts +53 -0
- package/app/plugins/template-globals.ts +9 -0
- package/app/spa-loading-template.html +93 -0
- package/biome.json +53 -0
- package/index.d.ts +380 -0
- package/modules/naiveui.ts +41 -0
- package/nuxt.config.ts +269 -0
- package/package.json +56 -0
- package/pnpm-workspace.yaml +5 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/pwa-192x192.png +0 -0
- package/public/pwa-512x512.png +0 -0
- package/public/pwa-maskable-192x192.png +0 -0
- package/public/pwa-maskable-512x512.png +0 -0
- package/public/vendor/opencv.js +48 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export default defineNuxtRouteMiddleware(async (to, from) => {
|
|
2
|
+
const user = useState<User>("user");
|
|
3
|
+
const database = useState<Database>("database");
|
|
4
|
+
const redirectTo = useScopedCookie<string | null>("redirectTo", database.value?.slug);
|
|
5
|
+
const isSafeRedirect = (value?: string) =>
|
|
6
|
+
!!value &&
|
|
7
|
+
value.startsWith("/") &&
|
|
8
|
+
!value.startsWith("//") &&
|
|
9
|
+
!value.includes("/auth");
|
|
10
|
+
const queryRedirectTo =
|
|
11
|
+
typeof to.query.redirectTo === "string" ? to.query.redirectTo : undefined;
|
|
12
|
+
if (user.value) {
|
|
13
|
+
if (["auth", "auth-reset", "database-auth", "database-auth-reset"].includes(to.name?.toString() ?? "")) {
|
|
14
|
+
const target = isSafeRedirect(queryRedirectTo)
|
|
15
|
+
? queryRedirectTo
|
|
16
|
+
: isSafeRedirect(redirectTo.value ?? undefined)
|
|
17
|
+
? redirectTo.value
|
|
18
|
+
: undefined;
|
|
19
|
+
return navigateTo(
|
|
20
|
+
target
|
|
21
|
+
? target
|
|
22
|
+
: to.params.database
|
|
23
|
+
? `/${database.value.slug}/admin`
|
|
24
|
+
: "/admin",
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
} else if (!["auth", "auth-reset", "database-auth", "database-auth-reset"].includes(to.name?.toString() ?? "")) {
|
|
28
|
+
redirectTo.value = from.fullPath;
|
|
29
|
+
return navigateTo(
|
|
30
|
+
to.params.database ? `/${database.value.slug}/auth` : "/auth",
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import {
|
|
2
|
+
loadDatabaseConfigLocally,
|
|
3
|
+
saveDatabaseConfigLocally,
|
|
4
|
+
} from "~/composables/useLocalDatabaseConfig";
|
|
5
|
+
import { isJunkResponse, isNetworkError } from "~/composables/useOfflineFetch";
|
|
6
|
+
|
|
7
|
+
export default defineNuxtRouteMiddleware(async (to) => {
|
|
8
|
+
const database = useState<Database>("database");
|
|
9
|
+
const config = useRuntimeConfig();
|
|
10
|
+
const currentDatabaseSlug = String(
|
|
11
|
+
config.public.database === "inicontent"
|
|
12
|
+
? to.params.database || config.public.database
|
|
13
|
+
: config.public.database || to.params.database,
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
const sessionID = useScopedCookie<string>("sid", currentDatabaseSlug);
|
|
17
|
+
const query: Record<string, string> = {};
|
|
18
|
+
|
|
19
|
+
if (sessionID.value) query[`${currentDatabaseSlug}_sid`] = sessionID.value;
|
|
20
|
+
|
|
21
|
+
if (!database.value) {
|
|
22
|
+
try {
|
|
23
|
+
database.value = (
|
|
24
|
+
await $fetch<apiResponse<Database>>(
|
|
25
|
+
`${config.public.apiBase}inicontent/databases/${currentDatabaseSlug}`,
|
|
26
|
+
{
|
|
27
|
+
credentials: "include",
|
|
28
|
+
query,
|
|
29
|
+
},
|
|
30
|
+
)
|
|
31
|
+
).result;
|
|
32
|
+
// Persist a local copy of the database config so the app can still
|
|
33
|
+
// boot offline from local storage even when the service worker's
|
|
34
|
+
// config cache is missing or evicted.
|
|
35
|
+
saveDatabaseConfigLocally(currentDatabaseSlug, database.value);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
// Offline (or a hung network). Prefer the locally-persisted copy of
|
|
38
|
+
// the database config — with it the app boots normally with the real
|
|
39
|
+
// metadata. In production the service worker also serves this request
|
|
40
|
+
// from its config cache, so this local copy is a second, independent
|
|
41
|
+
// fallback that survives cache eviction.
|
|
42
|
+
//
|
|
43
|
+
// Never THROW from here: an error thrown during the *initial*
|
|
44
|
+
// navigation rejects nuxt's `app:created` hook, which skips
|
|
45
|
+
// `vueApp.mount()` entirely in entry.js — the app never renders
|
|
46
|
+
// and the user just sees a blank page (NUXT_E1005). Instead,
|
|
47
|
+
// register the error via `showError()` so `payload.error` is set
|
|
48
|
+
// *before* mount (NuxtRoot then swaps in error.vue), and return
|
|
49
|
+
// `true` so the navigation itself completes and mounting proceeds.
|
|
50
|
+
const localConfig = loadDatabaseConfigLocally(currentDatabaseSlug);
|
|
51
|
+
if (localConfig) {
|
|
52
|
+
database.value = localConfig;
|
|
53
|
+
} else {
|
|
54
|
+
showError(
|
|
55
|
+
isNetworkError(error) || isJunkResponse(error)
|
|
56
|
+
? createError({
|
|
57
|
+
statusCode: 503,
|
|
58
|
+
statusMessage: "offline",
|
|
59
|
+
})
|
|
60
|
+
: (error as Error),
|
|
61
|
+
);
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!database.value) {
|
|
68
|
+
showError(createError({ statusCode: 404, statusMessage: "database" }));
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
formatDatabase();
|
|
73
|
+
syncCookiesFromDatabase(database.value.slug);
|
|
74
|
+
|
|
75
|
+
useState<ThemeConfig>("ThemeConfig", () => ({
|
|
76
|
+
primaryColor: "#FF9800",
|
|
77
|
+
primaryColorHover: "#F7A42A",
|
|
78
|
+
primaryColorPressed: "#E19421",
|
|
79
|
+
primaryColorSuppl: "#CB7900",
|
|
80
|
+
}));
|
|
81
|
+
|
|
82
|
+
setThemeConfig();
|
|
83
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default defineNuxtRouteMiddleware(() => {})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Global middleware that restores sessions passed across deployments as URL
|
|
2
|
+
// query params. When the tenant app redirects to the platform app (pages,
|
|
3
|
+
// blocks, templates, billing), cookies cannot travel cross-origin, so the
|
|
4
|
+
// redirect URL carries `?{db}_sid=...&inicontent_sid=...` (see
|
|
5
|
+
// usePlatformRedirect). This middleware writes them back into the scoped
|
|
6
|
+
// cookies so the target boots already signed in. It runs before the route
|
|
7
|
+
// level `database` / `user` middlewares and is a no-op when the params are
|
|
8
|
+
// absent, so it is safe on every deployment of this app.
|
|
9
|
+
export default defineNuxtRouteMiddleware((to) => {
|
|
10
|
+
const config = useRuntimeConfig();
|
|
11
|
+
const databaseSlug = String(
|
|
12
|
+
config.public.database === "inicontent"
|
|
13
|
+
? to.params.database || config.public.database
|
|
14
|
+
: config.public.database || to.params.database,
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
const ingest = (key: string) => {
|
|
18
|
+
const value = to.query[key];
|
|
19
|
+
if (typeof value !== "string" || !value.trim()) return;
|
|
20
|
+
const cookie = useCookie<string | null>(key, { sameSite: true });
|
|
21
|
+
// Never overwrite an existing (fresher) local session.
|
|
22
|
+
if (!cookie.value) cookie.value = value;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
ingest(`${databaseSlug}_sid`);
|
|
26
|
+
// On the platform database the database sid key IS the platform key, so
|
|
27
|
+
// only the platform key is ingested there.
|
|
28
|
+
if (databaseSlug !== "inicontent") ingest("inicontent_sid");
|
|
29
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export default defineNuxtRouteMiddleware(async (to) => {
|
|
2
|
+
const database = useState<Database>("database")
|
|
3
|
+
const table = useState<Table>("table")
|
|
4
|
+
let currentTableInPath: string | undefined = decodeURIComponent(
|
|
5
|
+
(to.params.table as string) ||
|
|
6
|
+
to.path.split("/admin/tables/")[1]?.split("/")[0] ||
|
|
7
|
+
"",
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
if (currentTableInPath === "undefined") currentTableInPath = undefined
|
|
11
|
+
|
|
12
|
+
if (!currentTableInPath && to.path.replace(/\/$/, "").includes("/auth"))
|
|
13
|
+
currentTableInPath = "users"
|
|
14
|
+
|
|
15
|
+
// The backups page is a database-level admin surface. New databases ship
|
|
16
|
+
// with a "backups" table in their meta, but legacy databases created before
|
|
17
|
+
// the feature only get a lazy table — so never 404 on its dedicated pages.
|
|
18
|
+
if (currentTableInPath === "backups") {
|
|
19
|
+
table.value =
|
|
20
|
+
database.value.tables?.find(({ slug }) => slug === "backups") ??
|
|
21
|
+
({ slug: "backups", label: "backups" } as Table)
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (
|
|
26
|
+
!database.value.tables ||
|
|
27
|
+
!currentTableInPath ||
|
|
28
|
+
!database.value.tables?.find(({ slug }) => slug === currentTableInPath)
|
|
29
|
+
) {
|
|
30
|
+
// The 404 error page renders inside the table layout. Drop any stale
|
|
31
|
+
// table state (e.g. the previously visited table) so the sidebar
|
|
32
|
+
// doesn't keep a now-mismatched table highlighted.
|
|
33
|
+
clearNuxtState("table")
|
|
34
|
+
// The redirect intent is dead (the table no longer exists) — drop it
|
|
35
|
+
// so a later login doesn't bounce back into this same 404 loop.
|
|
36
|
+
useScopedCookie<string | null>("redirectTo", database.value?.slug).value =
|
|
37
|
+
undefined
|
|
38
|
+
// Do NOT `throw` here: an error thrown from a route middleware during
|
|
39
|
+
// *client-side* navigation is swallowed by the router (nothing renders
|
|
40
|
+
// the error page), so navigating here from /auth after a redirectTo
|
|
41
|
+
// would abort silently and leave the user stuck on the auth page.
|
|
42
|
+
// Register the error with `showError()` (NuxtRoot swaps in error.vue)
|
|
43
|
+
// and `return true` so the navigation itself completes.
|
|
44
|
+
showError(createError({
|
|
45
|
+
statusCode: 404,
|
|
46
|
+
statusMessage: "table",
|
|
47
|
+
}))
|
|
48
|
+
return true
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
table.value = database.value.tables.find(
|
|
52
|
+
({ slug }) => slug === currentTableInPath,
|
|
53
|
+
) as Table
|
|
54
|
+
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export default defineNuxtRouteMiddleware(async () => {
|
|
2
|
+
const user = useState<User>("user");
|
|
3
|
+
const database = useState<Database>("database");
|
|
4
|
+
const config = useRuntimeConfig();
|
|
5
|
+
|
|
6
|
+
const sessionID = useScopedCookie<string|undefined>("sid", database.value?.slug);
|
|
7
|
+
|
|
8
|
+
if (!user.value) {
|
|
9
|
+
const platformSessionID = useScopedCookie<string|undefined>("sid", "inicontent");
|
|
10
|
+
|
|
11
|
+
if (!sessionID.value && !platformSessionID.value) return;
|
|
12
|
+
|
|
13
|
+
user.value = (
|
|
14
|
+
await $fetch<apiResponse<User>>(
|
|
15
|
+
`${config.public.apiBase}${database.value.slug}/auth/current`,
|
|
16
|
+
{
|
|
17
|
+
credentials: "include",
|
|
18
|
+
query: sessionID.value ? { [`${database.value.slug}_sid`]: sessionID.value } : { "inicontent_sid": platformSessionID.value },
|
|
19
|
+
},
|
|
20
|
+
)
|
|
21
|
+
).result;
|
|
22
|
+
if (!sessionID.value && user.value?.sessionID)
|
|
23
|
+
sessionID.value = user.value.sessionID;
|
|
24
|
+
if (!user.value) sessionID.value = undefined;
|
|
25
|
+
}
|
|
26
|
+
});
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<main class="api-auth-doc-page">
|
|
3
|
+
<NCard :bordered="false">
|
|
4
|
+
<template #header>
|
|
5
|
+
<NFlex align="center" :size="8">
|
|
6
|
+
<Icon name="tabler:key" size="18" />
|
|
7
|
+
<NH2 style="margin: 0">{{ t('apiDocs.auth.title') }}</NH2>
|
|
8
|
+
</NFlex>
|
|
9
|
+
</template>
|
|
10
|
+
<NH5>{{ t('apiDocs.auth.description', { param: sessionParamName }) }}</NH5>
|
|
11
|
+
</NCard>
|
|
12
|
+
|
|
13
|
+
<section class="api-auth-doc-page__endpoints">
|
|
14
|
+
<ApiEndpointCard
|
|
15
|
+
:endpoint="signinEndpoint"
|
|
16
|
+
method="PUT"
|
|
17
|
+
:description="t('apiDocs.auth.signinDescription')"
|
|
18
|
+
:request-example="signinRequest"
|
|
19
|
+
:response-example="signinResponse"
|
|
20
|
+
:params="signinParams"
|
|
21
|
+
/>
|
|
22
|
+
|
|
23
|
+
<ApiEndpointCard
|
|
24
|
+
:endpoint="signupEndpoint"
|
|
25
|
+
method="POST"
|
|
26
|
+
:description="t('apiDocs.auth.signupDescription')"
|
|
27
|
+
:request-example="signupRequest"
|
|
28
|
+
:response-example="signupResponse"
|
|
29
|
+
/>
|
|
30
|
+
|
|
31
|
+
<ApiEndpointCard
|
|
32
|
+
:endpoint="currentEndpoint"
|
|
33
|
+
method="GET"
|
|
34
|
+
:description="t('apiDocs.auth.currentDescription', { param: sessionParamName })"
|
|
35
|
+
:response-example="currentResponse"
|
|
36
|
+
:params="currentParams"
|
|
37
|
+
/>
|
|
38
|
+
|
|
39
|
+
<ApiEndpointCard
|
|
40
|
+
:endpoint="signoutEndpoint"
|
|
41
|
+
method="GET"
|
|
42
|
+
:description="t('apiDocs.auth.signoutDescription')"
|
|
43
|
+
:response-example="signoutResponse"
|
|
44
|
+
/>
|
|
45
|
+
|
|
46
|
+
<ApiEndpointCard
|
|
47
|
+
:endpoint="resetRequestEndpoint"
|
|
48
|
+
method="POST"
|
|
49
|
+
:description="t('apiDocs.auth.resetRequestDescription')"
|
|
50
|
+
:request-example="resetRequestRequest"
|
|
51
|
+
:response-example="resetRequestResponse"
|
|
52
|
+
/>
|
|
53
|
+
|
|
54
|
+
<ApiEndpointCard
|
|
55
|
+
:endpoint="resetCompleteEndpoint"
|
|
56
|
+
method="PUT"
|
|
57
|
+
:description="t('apiDocs.auth.resetCompleteDescription')"
|
|
58
|
+
:request-example="resetCompleteRequest"
|
|
59
|
+
:response-example="resetCompleteResponse"
|
|
60
|
+
/>
|
|
61
|
+
</section>
|
|
62
|
+
|
|
63
|
+
<section v-if="usersTable?.schema?.length" class="api-auth-doc-page__schema">
|
|
64
|
+
<NCard :bordered="false">
|
|
65
|
+
<template #header>
|
|
66
|
+
<NFlex align="center" :size="8">
|
|
67
|
+
<Icon name="tabler:users" size="18" />
|
|
68
|
+
<NH3 style="margin: 0">{{ t('apiDocs.auth.signupSchemaTitle') }}</NH3>
|
|
69
|
+
</NFlex>
|
|
70
|
+
</template>
|
|
71
|
+
<ApiSchemaDocsTable :schema="usersTable.schema" size="large" />
|
|
72
|
+
</NCard>
|
|
73
|
+
</section>
|
|
74
|
+
|
|
75
|
+
<NAlert type="info" :show-icon="false">
|
|
76
|
+
{{ t('apiDocs.auth.cookieHint', { param: sessionParamName }) }}
|
|
77
|
+
</NAlert>
|
|
78
|
+
</main>
|
|
79
|
+
</template>
|
|
80
|
+
|
|
81
|
+
<script setup lang="ts">
|
|
82
|
+
import { Icon } from "#components";
|
|
83
|
+
|
|
84
|
+
definePageMeta({
|
|
85
|
+
layout: "dashboard",
|
|
86
|
+
middleware: ["database", "user", "dashboard", "global"],
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const route = useRoute();
|
|
90
|
+
const database = useState<Database>("database");
|
|
91
|
+
const config = useRuntimeConfig();
|
|
92
|
+
|
|
93
|
+
const sessionParamName = computed(() => {
|
|
94
|
+
const slug =
|
|
95
|
+
(route.params.database as string | undefined) ||
|
|
96
|
+
database.value?.slug ||
|
|
97
|
+
"inicontent";
|
|
98
|
+
return `${slug}_sid`;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const dbSlug = computed(
|
|
102
|
+
() =>
|
|
103
|
+
(route.params.database as string | undefined) ||
|
|
104
|
+
database.value?.slug ||
|
|
105
|
+
"inicontent",
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const apiBase = computed(() => config.public.apiBase);
|
|
109
|
+
|
|
110
|
+
const usersTable = computed(() =>
|
|
111
|
+
database.value?.tables?.find((table) => table.slug === "users"),
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const signinEndpoint = computed(() => `/${dbSlug.value}/auth/signin`);
|
|
115
|
+
const signupEndpoint = computed(() => `/${dbSlug.value}/auth/signup`);
|
|
116
|
+
const currentEndpoint = computed(() => `/${dbSlug.value}/auth/current`);
|
|
117
|
+
const signoutEndpoint = computed(() => `/${dbSlug.value}/auth/signout`);
|
|
118
|
+
const resetRequestEndpoint = computed(() => `/${dbSlug.value}/auth/reset`);
|
|
119
|
+
const resetCompleteEndpoint = computed(() => `/${dbSlug.value}/auth/reset`);
|
|
120
|
+
|
|
121
|
+
const signinRequest = `{
|
|
122
|
+
"username": "demo",
|
|
123
|
+
"password": "mypassword123"
|
|
124
|
+
}`;
|
|
125
|
+
|
|
126
|
+
const signinResponse = `{
|
|
127
|
+
"result": {
|
|
128
|
+
"id": "a1b2c3d4",
|
|
129
|
+
"username": "demo",
|
|
130
|
+
"email": "demo@example.com",
|
|
131
|
+
"role": "editor",
|
|
132
|
+
"createdBy": "...",
|
|
133
|
+
"sessionID": "e5f6g7h8i9j0"
|
|
134
|
+
},
|
|
135
|
+
"message": "Signed in successfully",
|
|
136
|
+
"options": {},
|
|
137
|
+
"code": 200
|
|
138
|
+
}`;
|
|
139
|
+
|
|
140
|
+
const signinParams = computed(() => [
|
|
141
|
+
{
|
|
142
|
+
name: "locale",
|
|
143
|
+
type: "string",
|
|
144
|
+
required: false,
|
|
145
|
+
description: t("apiDocs.localeParam"),
|
|
146
|
+
},
|
|
147
|
+
]);
|
|
148
|
+
|
|
149
|
+
const signupRequest = `{
|
|
150
|
+
"username": "newuser",
|
|
151
|
+
"password": "securepass123",
|
|
152
|
+
"email": "newuser@example.com",
|
|
153
|
+
"role": "editor"
|
|
154
|
+
}`;
|
|
155
|
+
|
|
156
|
+
const signupResponse = `{
|
|
157
|
+
"result": {
|
|
158
|
+
"id": "b2c3d4e5",
|
|
159
|
+
"username": "newuser",
|
|
160
|
+
"email": "newuser@example.com",
|
|
161
|
+
"role": "editor",
|
|
162
|
+
"createdBy": "..."
|
|
163
|
+
},
|
|
164
|
+
"message": "User created successfully",
|
|
165
|
+
"options": {},
|
|
166
|
+
"code": 200
|
|
167
|
+
}`;
|
|
168
|
+
|
|
169
|
+
const currentResponse = `{
|
|
170
|
+
"result": {
|
|
171
|
+
"id": "a1b2c3d4",
|
|
172
|
+
"username": "demo",
|
|
173
|
+
"email": "demo@example.com",
|
|
174
|
+
"role": "editor",
|
|
175
|
+
"createdBy": "..."
|
|
176
|
+
},
|
|
177
|
+
"message": "Session is valid",
|
|
178
|
+
"options": {},
|
|
179
|
+
"code": 200
|
|
180
|
+
}`;
|
|
181
|
+
|
|
182
|
+
const currentParams = computed(() => [
|
|
183
|
+
{
|
|
184
|
+
name: sessionParamName.value,
|
|
185
|
+
type: "string",
|
|
186
|
+
required: true,
|
|
187
|
+
description: "Session ID",
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
name: "isSignedIn",
|
|
191
|
+
type: "boolean",
|
|
192
|
+
required: false,
|
|
193
|
+
description: t("apiDocs.isSignedInParam"),
|
|
194
|
+
},
|
|
195
|
+
]);
|
|
196
|
+
|
|
197
|
+
const signoutResponse = `{
|
|
198
|
+
"result": true,
|
|
199
|
+
"message": "Signed out successfully",
|
|
200
|
+
"options": {},
|
|
201
|
+
"code": 200
|
|
202
|
+
}`;
|
|
203
|
+
|
|
204
|
+
const resetRequestRequest = `{
|
|
205
|
+
"email": "demo@example.com"
|
|
206
|
+
}`;
|
|
207
|
+
|
|
208
|
+
const resetRequestResponse = `{
|
|
209
|
+
"result": true,
|
|
210
|
+
"message": "Password reset email sent",
|
|
211
|
+
"options": {},
|
|
212
|
+
"code": 200
|
|
213
|
+
}`;
|
|
214
|
+
|
|
215
|
+
const resetCompleteRequest = `{
|
|
216
|
+
"token": "abc123def456",
|
|
217
|
+
"password": "newpassword123"
|
|
218
|
+
}`;
|
|
219
|
+
|
|
220
|
+
const resetCompleteResponse = `{
|
|
221
|
+
"result": true,
|
|
222
|
+
"message": "Password has been reset successfully",
|
|
223
|
+
"options": {},
|
|
224
|
+
"code": 200
|
|
225
|
+
}`;
|
|
226
|
+
|
|
227
|
+
const jsonLd = computed(() => ({
|
|
228
|
+
"@context": "https://schema.org",
|
|
229
|
+
"@type": "WebAPI",
|
|
230
|
+
name: `${dbSlug.value} Authentication API`,
|
|
231
|
+
description: "Authentication endpoints for user sessions",
|
|
232
|
+
baseUrl: `${apiBase.value}${dbSlug.value}/auth`,
|
|
233
|
+
endpoints: [
|
|
234
|
+
{
|
|
235
|
+
"@type": "EntryPoint",
|
|
236
|
+
name: "Sign in",
|
|
237
|
+
httpMethod: "PUT",
|
|
238
|
+
urlTemplate: `${apiBase.value}${dbSlug.value}/auth/signin`,
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
"@type": "EntryPoint",
|
|
242
|
+
name: "Sign up",
|
|
243
|
+
httpMethod: "POST",
|
|
244
|
+
urlTemplate: `${apiBase.value}${dbSlug.value}/auth/signup`,
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
"@type": "EntryPoint",
|
|
248
|
+
name: "Current user",
|
|
249
|
+
httpMethod: "GET",
|
|
250
|
+
urlTemplate: `${apiBase.value}${dbSlug.value}/auth/current`,
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
"@type": "EntryPoint",
|
|
254
|
+
name: "Sign out",
|
|
255
|
+
httpMethod: "GET",
|
|
256
|
+
urlTemplate: `${apiBase.value}${dbSlug.value}/auth/signout`,
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
"@type": "EntryPoint",
|
|
260
|
+
name: "Request password reset",
|
|
261
|
+
httpMethod: "POST",
|
|
262
|
+
urlTemplate: `${apiBase.value}${dbSlug.value}/auth/reset`,
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
"@type": "EntryPoint",
|
|
266
|
+
name: "Complete password reset",
|
|
267
|
+
httpMethod: "PUT",
|
|
268
|
+
urlTemplate: `${apiBase.value}${dbSlug.value}/auth/reset`,
|
|
269
|
+
},
|
|
270
|
+
],
|
|
271
|
+
}));
|
|
272
|
+
|
|
273
|
+
useHead(() => ({
|
|
274
|
+
title: `${t("auth")} | ${t("apiDocumentation")}`,
|
|
275
|
+
meta: [
|
|
276
|
+
{
|
|
277
|
+
name: "description",
|
|
278
|
+
content: `API authentication endpoints for ${dbSlug.value}. Sign in, sign up, manage sessions, and reset passwords.`,
|
|
279
|
+
},
|
|
280
|
+
],
|
|
281
|
+
script: [
|
|
282
|
+
{
|
|
283
|
+
type: "application/ld+json",
|
|
284
|
+
innerHTML: JSON.stringify(jsonLd.value),
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
}));
|
|
288
|
+
</script>
|
|
289
|
+
|
|
290
|
+
<style scoped>
|
|
291
|
+
.api-auth-doc-page {
|
|
292
|
+
display: flex;
|
|
293
|
+
flex-direction: column;
|
|
294
|
+
gap: 24px;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.api-auth-doc-page__endpoints {
|
|
298
|
+
display: flex;
|
|
299
|
+
flex-direction: column;
|
|
300
|
+
gap: 16px;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.api-auth-doc-page__schema {
|
|
304
|
+
margin-top: 8px;
|
|
305
|
+
}
|
|
306
|
+
</style>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<NFlex justify="center" align="center" style="min-height: 50vh">
|
|
3
|
+
<NSpin size="large" />
|
|
4
|
+
</NFlex>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
definePageMeta({
|
|
9
|
+
layout: "dashboard",
|
|
10
|
+
middleware: ["database", "user", "dashboard", "global"],
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const route = useRoute();
|
|
14
|
+
|
|
15
|
+
await navigateTo(
|
|
16
|
+
`${route.params.database ? `/${route.params.database}` : ""}/admin/api/tables`,
|
|
17
|
+
);
|
|
18
|
+
</script>
|