@commonpub/layer 0.8.4 → 0.8.6

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.
@@ -48,6 +48,14 @@ export function useTheme(): {
48
48
 
49
49
  if (import.meta.client) {
50
50
  applyThemeToElement(document.documentElement, newTheme);
51
+
52
+ // Persist to DB for cross-device sync (fire-and-forget, cookie is primary)
53
+ $fetch('/api/profile/theme', {
54
+ method: 'PUT',
55
+ body: { themeId: newTheme },
56
+ }).catch(() => {
57
+ // Not logged in or network error — cookie preference is sufficient
58
+ });
51
59
  }
52
60
  }
53
61
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commonpub/layer",
3
- "version": "0.8.4",
3
+ "version": "0.8.6",
4
4
  "type": "module",
5
5
  "main": "./nuxt.config.ts",
6
6
  "files": [
@@ -54,12 +54,12 @@
54
54
  "vue-router": "^4.3.0",
55
55
  "zod": "^4.3.6",
56
56
  "@commonpub/auth": "0.5.1",
57
+ "@commonpub/docs": "0.6.2",
57
58
  "@commonpub/config": "0.9.1",
58
- "@commonpub/protocol": "0.9.9",
59
- "@commonpub/ui": "0.8.5",
60
59
  "@commonpub/editor": "0.7.9",
61
- "@commonpub/docs": "0.6.2",
62
- "@commonpub/learning": "0.5.0"
60
+ "@commonpub/learning": "0.5.0",
61
+ "@commonpub/protocol": "0.9.9",
62
+ "@commonpub/ui": "0.8.5"
63
63
  },
64
64
  "devDependencies": {
65
65
  "@testing-library/jest-dom": "^6.9.1",
@@ -46,11 +46,15 @@ async function addTrusted(): Promise<void> {
46
46
  }
47
47
 
48
48
  async function removeTrusted(domain: string): Promise<void> {
49
- await $fetch('/api/admin/federation/trusted-instances', {
50
- method: 'DELETE',
51
- body: { domain },
52
- });
53
- await refreshTrusted();
49
+ try {
50
+ await $fetch('/api/admin/federation/trusted-instances', {
51
+ method: 'DELETE',
52
+ body: { domain },
53
+ });
54
+ await refreshTrusted();
55
+ } catch {
56
+ alert('Failed to remove trusted instance');
57
+ }
54
58
  }
55
59
 
56
60
  // Mirror creation
@@ -79,16 +83,24 @@ async function createMirror(): Promise<void> {
79
83
  }
80
84
 
81
85
  async function toggleMirror(id: string, currentStatus: string): Promise<void> {
82
- await $fetch(`/api/admin/federation/mirrors/${id}`, {
83
- method: 'PUT',
84
- body: { action: currentStatus === 'active' ? 'pause' : 'resume' },
85
- });
86
- await refreshMirrors();
86
+ try {
87
+ await $fetch(`/api/admin/federation/mirrors/${id}`, {
88
+ method: 'PUT',
89
+ body: { action: currentStatus === 'active' ? 'pause' : 'resume' },
90
+ });
91
+ await refreshMirrors();
92
+ } catch {
93
+ alert('Failed to update mirror');
94
+ }
87
95
  }
88
96
 
89
97
  async function deleteMirror(id: string): Promise<void> {
90
- await $fetch(`/api/admin/federation/mirrors/${id}`, { method: 'DELETE' });
91
- await refreshMirrors();
98
+ try {
99
+ await $fetch(`/api/admin/federation/mirrors/${id}`, { method: 'DELETE' });
100
+ await refreshMirrors();
101
+ } catch {
102
+ alert('Failed to delete mirror');
103
+ }
92
104
  }
93
105
 
94
106
  // Backfill
@@ -2,7 +2,7 @@
2
2
  const route = useRoute();
3
3
  const slug = route.params.slug as string;
4
4
 
5
- const { data: product } = useLazyFetch(`/api/products/${slug}`) as { data: Ref<Record<string, any> | null> };
5
+ const { data: product, pending } = useLazyFetch(`/api/products/${slug}`) as { data: Ref<Record<string, any> | null>; pending: Ref<boolean> };
6
6
  const { data: projectsUsing } = useLazyFetch(`/api/products/${slug}/content`) as { data: Ref<any[] | null> };
7
7
 
8
8
  useSeoMeta({
@@ -12,7 +12,10 @@ useSeoMeta({
12
12
  </script>
13
13
 
14
14
  <template>
15
- <div v-if="product" class="product-detail">
15
+ <div v-if="pending" style="padding: 32px; text-align: center; color: var(--text-faint);">
16
+ <i class="fa-solid fa-circle-notch fa-spin"></i> Loading product...
17
+ </div>
18
+ <div v-else-if="product" class="product-detail">
16
19
  <NuxtLink to="/products" class="cpub-back-link"><i class="fa-solid fa-arrow-left"></i> Products</NuxtLink>
17
20
 
18
21
  <div class="product-layout">
@@ -36,8 +36,28 @@ export default defineEventHandler(async (event) => {
36
36
  }
37
37
 
38
38
  const redirectUri = `https://${config.instance.domain}/api/auth/federated/callback`;
39
- const effectiveClientId = clientId ?? `cpub_${config.instance.domain}`;
40
- const effectiveClientSecret = clientSecret ?? '';
39
+ let effectiveClientId = clientId ?? `cpub_${config.instance.domain}`;
40
+ let effectiveClientSecret = clientSecret ?? '';
41
+
42
+ // Auto-register as a dynamic client if no explicit credentials provided
43
+ if (!clientId) {
44
+ try {
45
+ const regUrl = endpoints.tokenEndpoint.replace('/token', '/register');
46
+ const regResult = await $fetch<{ client_id: string; client_secret: string }>(regUrl, {
47
+ method: 'POST',
48
+ body: {
49
+ client_name: config.instance.name || config.instance.domain,
50
+ redirect_uris: [redirectUri],
51
+ client_uri: `https://${config.instance.domain}`,
52
+ instance_domain: config.instance.domain,
53
+ },
54
+ });
55
+ effectiveClientId = regResult.client_id;
56
+ effectiveClientSecret = regResult.client_secret;
57
+ } catch {
58
+ // Registration failed — proceed with default client ID (may fail at authorize step)
59
+ }
60
+ }
41
61
 
42
62
  // Store state for the callback handler
43
63
  const stateToken = await storeOAuthState(db, {
@@ -1,4 +1,5 @@
1
- import { resolveIdentityToEmail } from '@commonpub/server';
1
+ import { eq, and, isNull } from 'drizzle-orm';
2
+ import { users } from '@commonpub/schema';
2
3
  import { z } from 'zod';
3
4
 
4
5
  const signInSchema = z.object({
@@ -14,11 +15,21 @@ const signInSchema = z.object({
14
15
  export default defineEventHandler(async (event) => {
15
16
  const body = await parseBody(event, signInSchema);
16
17
 
17
- let email: string;
18
- try {
19
- email = await resolveIdentityToEmail(useDB(), body.identity);
20
- } catch {
21
- throw createError({ statusCode: 401, statusMessage: 'Invalid credentials' });
18
+ let email = body.identity;
19
+
20
+ // If identity doesn't look like an email, resolve username → email
21
+ if (!body.identity.includes('@')) {
22
+ const db = useDB();
23
+ const [user] = await db
24
+ .select({ email: users.email })
25
+ .from(users)
26
+ .where(and(eq(users.username, body.identity), isNull(users.deletedAt)))
27
+ .limit(1);
28
+
29
+ if (!user) {
30
+ throw createError({ statusCode: 401, statusMessage: 'Invalid credentials' });
31
+ }
32
+ email = user.email;
22
33
  }
23
34
 
24
35
  // Proxy to Better Auth's email sign-in (internal server-side call)
@@ -0,0 +1,23 @@
1
+ import { setUserTheme } from '@commonpub/server';
2
+ import { z } from 'zod';
3
+
4
+ const themeSchema = z.object({
5
+ themeId: z.string().min(1).max(32),
6
+ });
7
+
8
+ export default defineEventHandler(async (event): Promise<{ success: boolean }> => {
9
+ const db = useDB();
10
+ const user = requireAuth(event);
11
+ const { themeId } = await parseBody(event, themeSchema);
12
+
13
+ try {
14
+ await setUserTheme(db, user.id, themeId);
15
+ } catch (err) {
16
+ throw createError({
17
+ statusCode: 400,
18
+ statusMessage: err instanceof Error ? err.message : 'Invalid theme',
19
+ });
20
+ }
21
+
22
+ return { success: true };
23
+ });