@commonpub/layer 0.3.37 → 0.3.38

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.
@@ -183,8 +183,9 @@ const userUsername = computed(() => user.value?.username ?? '');
183
183
  <h4 class="cpub-footer-col-title">Platform</h4>
184
184
  <NuxtLink to="/about" class="cpub-footer-link">About</NuxtLink>
185
185
  <NuxtLink v-if="docs" to="/docs" class="cpub-footer-link">Docs</NuxtLink>
186
+ <NuxtLink to="/privacy" class="cpub-footer-link">Privacy Policy</NuxtLink>
187
+ <NuxtLink to="/terms" class="cpub-footer-link">Terms of Service</NuxtLink>
186
188
  <a href="/feed.xml" class="cpub-footer-link">RSS Feed</a>
187
- <a href="/sitemap.xml" class="cpub-footer-link">Sitemap</a>
188
189
  </nav>
189
190
  </div>
190
191
  <div class="cpub-footer-bottom">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commonpub/layer",
3
- "version": "0.3.37",
3
+ "version": "0.3.38",
4
4
  "type": "module",
5
5
  "main": "./nuxt.config.ts",
6
6
  "files": [
@@ -51,12 +51,12 @@
51
51
  "zod": "^4.3.6",
52
52
  "@commonpub/auth": "0.5.0",
53
53
  "@commonpub/config": "0.7.1",
54
+ "@commonpub/docs": "0.5.2",
54
55
  "@commonpub/editor": "0.5.0",
55
56
  "@commonpub/learning": "0.5.0",
56
- "@commonpub/docs": "0.5.2",
57
57
  "@commonpub/protocol": "0.9.5",
58
+ "@commonpub/server": "2.21.0",
58
59
  "@commonpub/schema": "0.8.13",
59
- "@commonpub/server": "2.20.1",
60
60
  "@commonpub/ui": "0.7.1"
61
61
  },
62
62
  "devDependencies": {
@@ -101,6 +101,13 @@ async function handleSubmit(): Promise<void> {
101
101
  />
102
102
  </div>
103
103
 
104
+ <p class="register-legal">
105
+ By creating an account, you agree to our
106
+ <NuxtLink to="/terms">Terms of Service</NuxtLink>
107
+ and acknowledge our
108
+ <NuxtLink to="/privacy">Privacy Policy</NuxtLink>.
109
+ </p>
110
+
104
111
  <button type="submit" class="submit-btn" :disabled="loading">
105
112
  {{ loading ? 'Creating...' : 'Create account' }}
106
113
  </button>
@@ -206,6 +213,21 @@ async function handleSubmit(): Promise<void> {
206
213
  cursor: not-allowed;
207
214
  }
208
215
 
216
+ .register-legal {
217
+ font-size: 11px;
218
+ color: var(--text-faint);
219
+ line-height: 1.5;
220
+ }
221
+
222
+ .register-legal a {
223
+ color: var(--accent);
224
+ text-decoration: none;
225
+ }
226
+
227
+ .register-legal a:hover {
228
+ text-decoration: underline;
229
+ }
230
+
209
231
  .register-footer {
210
232
  text-align: center;
211
233
  font-size: 12px;
@@ -0,0 +1,206 @@
1
+ <script setup lang="ts">
2
+ useSeoMeta({
3
+ title: `Privacy Policy — ${useSiteName()}`,
4
+ description: 'How we collect, use, and protect your personal data.',
5
+ });
6
+
7
+ const siteName = useSiteName();
8
+ const { federation: federationEnabled } = useFeatures();
9
+ </script>
10
+
11
+ <template>
12
+ <div class="cpub-legal">
13
+ <div class="cpub-legal-header">
14
+ <h1 class="cpub-legal-title">Privacy Policy</h1>
15
+ <p class="cpub-legal-updated">Last updated: April 2026</p>
16
+ </div>
17
+
18
+ <div class="cpub-legal-body">
19
+ <section class="cpub-legal-section">
20
+ <h2>1. Who We Are</h2>
21
+ <p>
22
+ This {{ siteName }} instance is operated by its administrator (the "data controller").
23
+ {{ siteName }} is powered by <a href="https://commonpub.io" target="_blank" rel="noopener">CommonPub</a>,
24
+ an open-source, self-hosted platform. Each instance is independently operated and responsible for its own data processing.
25
+ </p>
26
+ </section>
27
+
28
+ <section class="cpub-legal-section">
29
+ <h2>2. What Data We Collect</h2>
30
+ <p>When you create an account, we collect:</p>
31
+ <ul>
32
+ <li><strong>Account data:</strong> email address, username, password (stored as a secure hash)</li>
33
+ <li><strong>Profile data:</strong> display name, bio, headline, location, website, avatar, banner image, social links, skills, pronouns, timezone (all optional)</li>
34
+ <li><strong>Content:</strong> projects, articles, blog posts, comments, and other content you create</li>
35
+ <li><strong>Activity data:</strong> likes, follows, bookmarks, hub memberships, learning path enrollments</li>
36
+ <li><strong>Messages:</strong> direct messages you send to other users on this instance</li>
37
+ </ul>
38
+ <p>We also automatically collect:</p>
39
+ <ul>
40
+ <li><strong>Session data:</strong> IP address and browser user agent when you log in, stored for the duration of your session (up to 7 days)</li>
41
+ <li><strong>Theme preference:</strong> your light/dark mode choice, stored in your browser's local storage</li>
42
+ </ul>
43
+ </section>
44
+
45
+ <section class="cpub-legal-section">
46
+ <h2>3. How We Use Your Data</h2>
47
+ <ul>
48
+ <li><strong>Providing the service:</strong> displaying your profile, publishing your content, delivering notifications</li>
49
+ <li><strong>Authentication:</strong> verifying your identity when you log in</li>
50
+ <li><strong>Security:</strong> protecting against unauthorized access, abuse, and spam</li>
51
+ <li><strong>Email notifications:</strong> sending notification digests and alerts you've opted into (configurable in settings)</li>
52
+ </ul>
53
+ </section>
54
+
55
+ <section class="cpub-legal-section">
56
+ <h2>4. Legal Basis for Processing</h2>
57
+ <p>We process your data under the following legal bases (GDPR Article 6):</p>
58
+ <ul>
59
+ <li><strong>Contract performance (Art. 6(1)(b)):</strong> processing necessary to provide you with the service you signed up for</li>
60
+ <li><strong>Legitimate interest (Art. 6(1)(f)):</strong> session security, rate limiting, and preventing abuse</li>
61
+ </ul>
62
+ </section>
63
+
64
+ <section class="cpub-legal-section">
65
+ <h2>5. Cookies and Local Storage</h2>
66
+ <p>We use only <strong>strictly necessary</strong> technologies:</p>
67
+ <ul>
68
+ <li><strong>Session cookie</strong> (<code>better-auth.session_token</code>): an authentication cookie that identifies your login session. It is httpOnly (not accessible to JavaScript), secure (HTTPS-only in production), and expires after 7 days. This cookie is strictly necessary for the service to function and does not require consent.</li>
69
+ <li><strong>Theme preference</strong> (<code>cpub-theme</code> in localStorage): stores your light/dark mode choice in your browser. This is a UI preference, not used for tracking.</li>
70
+ </ul>
71
+ <p>We do not use any analytics, advertising, or tracking cookies. No cookie consent banner is required because we only use strictly necessary cookies.</p>
72
+ </section>
73
+
74
+ <section v-if="federationEnabled" class="cpub-legal-section">
75
+ <h2>6. Federation and ActivityPub</h2>
76
+ <p>This instance participates in the <a href="https://activitypub.rocks" target="_blank" rel="noopener">ActivityPub</a> federation protocol. When you publish content or interact publicly, the following data may be shared with remote instances:</p>
77
+ <ul>
78
+ <li>Your username, display name, avatar, and bio</li>
79
+ <li>Your published content (projects, articles, blog posts)</li>
80
+ <li>Your public interactions (likes, follows, comments on federated content)</li>
81
+ </ul>
82
+ <p>Your email address, location, social links, timezone, and other private profile fields are <strong>never</strong> shared via federation.</p>
83
+ <p><strong>Important:</strong> Once your data is federated to remote instances, this instance cannot guarantee its deletion on those servers. Remote instances operate independently and may retain cached copies of your public data even after you delete your account here.</p>
84
+ </section>
85
+
86
+ <section class="cpub-legal-section">
87
+ <h2>{{ federationEnabled ? '7' : '6' }}. Third-Party Services</h2>
88
+ <p>We load icon fonts from <strong>Font Awesome</strong> via the Cloudflare CDN (<code>cdnjs.cloudflare.com</code>). This means your browser makes requests to Cloudflare's servers, which are subject to <a href="https://www.cloudflare.com/privacypolicy/" target="_blank" rel="noopener">Cloudflare's privacy policy</a>.</p>
89
+ <p>We do not use any analytics services, advertising networks, or tracking technologies.</p>
90
+ </section>
91
+
92
+ <section class="cpub-legal-section">
93
+ <h2>{{ federationEnabled ? '8' : '7' }}. Data Retention</h2>
94
+ <ul>
95
+ <li><strong>Account data:</strong> retained until you delete your account</li>
96
+ <li><strong>Session data:</strong> automatically expires after 7 days of inactivity</li>
97
+ <li><strong>Content:</strong> retained until you delete it or delete your account</li>
98
+ <li><strong>Audit logs:</strong> retained per the instance operator's policy</li>
99
+ </ul>
100
+ </section>
101
+
102
+ <section class="cpub-legal-section">
103
+ <h2>{{ federationEnabled ? '9' : '8' }}. Your Rights</h2>
104
+ <p>Under the GDPR and similar data protection laws, you have the right to:</p>
105
+ <ul>
106
+ <li><strong>Access:</strong> view the data we hold about you (via your profile and settings)</li>
107
+ <li><strong>Rectification:</strong> update or correct your data (via your profile settings)</li>
108
+ <li><strong>Erasure:</strong> delete your account and all associated data (via account settings)</li>
109
+ <li><strong>Portability:</strong> download your data in a machine-readable format (via account settings)</li>
110
+ <li><strong>Restriction and objection:</strong> contact the instance administrator</li>
111
+ </ul>
112
+ <p>To exercise these rights, visit your <NuxtLink to="/settings/account">account settings</NuxtLink> or contact the instance administrator.</p>
113
+ </section>
114
+
115
+ <section class="cpub-legal-section">
116
+ <h2>{{ federationEnabled ? '10' : '9' }}. Contact</h2>
117
+ <p>For privacy-related inquiries, contact the administrator of this {{ siteName }} instance.</p>
118
+ </section>
119
+ </div>
120
+ </div>
121
+ </template>
122
+
123
+ <style scoped>
124
+ .cpub-legal {
125
+ max-width: 740px;
126
+ margin: 0 auto;
127
+ padding: 48px 24px 80px;
128
+ }
129
+
130
+ .cpub-legal-header {
131
+ margin-bottom: 40px;
132
+ }
133
+
134
+ .cpub-legal-title {
135
+ font-size: 28px;
136
+ font-weight: 700;
137
+ margin-bottom: 8px;
138
+ }
139
+
140
+ .cpub-legal-updated {
141
+ font-size: 12px;
142
+ color: var(--text-faint);
143
+ font-family: var(--font-mono);
144
+ }
145
+
146
+ .cpub-legal-body {
147
+ display: flex;
148
+ flex-direction: column;
149
+ gap: 32px;
150
+ }
151
+
152
+ .cpub-legal-section h2 {
153
+ font-size: 16px;
154
+ font-weight: 600;
155
+ margin-bottom: 12px;
156
+ }
157
+
158
+ .cpub-legal-section p {
159
+ font-size: 14px;
160
+ line-height: 1.7;
161
+ color: var(--text-dim);
162
+ margin-bottom: 8px;
163
+ }
164
+
165
+ .cpub-legal-section ul {
166
+ padding-left: 20px;
167
+ margin: 8px 0;
168
+ }
169
+
170
+ .cpub-legal-section li {
171
+ font-size: 14px;
172
+ line-height: 1.7;
173
+ color: var(--text-dim);
174
+ margin-bottom: 4px;
175
+ }
176
+
177
+ .cpub-legal-section strong {
178
+ color: var(--text);
179
+ }
180
+
181
+ .cpub-legal-section code {
182
+ font-family: var(--font-mono);
183
+ font-size: 12px;
184
+ padding: 1px 5px;
185
+ background: var(--surface2);
186
+ border: var(--border-width-default) solid var(--border);
187
+ }
188
+
189
+ .cpub-legal-section a {
190
+ color: var(--accent);
191
+ text-decoration: none;
192
+ }
193
+
194
+ .cpub-legal-section a:hover {
195
+ text-decoration: underline;
196
+ }
197
+
198
+ @media (max-width: 640px) {
199
+ .cpub-legal {
200
+ padding: 24px 16px 60px;
201
+ }
202
+ .cpub-legal-title {
203
+ font-size: 22px;
204
+ }
205
+ }
206
+ </style>
@@ -86,6 +86,14 @@ async function handleDeleteAccount(): Promise<void> {
86
86
  </button>
87
87
  </form>
88
88
 
89
+ <div class="cpub-form-group">
90
+ <label class="cpub-form-label">Your Data</label>
91
+ <p class="cpub-form-hint cpub-mb-2">Download a copy of all your data in JSON format.</p>
92
+ <a href="/api/auth/export-data" download class="cpub-btn cpub-btn-sm">
93
+ <i class="fa-solid fa-download"></i> Download My Data
94
+ </a>
95
+ </div>
96
+
89
97
  <hr class="cpub-danger-divider" />
90
98
 
91
99
  <div>
@@ -126,6 +134,7 @@ async function handleDeleteAccount(): Promise<void> {
126
134
 
127
135
  <style scoped>
128
136
  .cpub-mt-2 { margin-top: var(--space-2); }
137
+ .cpub-mb-2 { margin-bottom: var(--space-2); }
129
138
 
130
139
  .cpub-danger-divider {
131
140
  border: none;
@@ -0,0 +1,168 @@
1
+ <script setup lang="ts">
2
+ useSeoMeta({
3
+ title: `Terms of Service — ${useSiteName()}`,
4
+ description: 'Terms and conditions for using this platform.',
5
+ });
6
+
7
+ const siteName = useSiteName();
8
+ </script>
9
+
10
+ <template>
11
+ <div class="cpub-legal">
12
+ <div class="cpub-legal-header">
13
+ <h1 class="cpub-legal-title">Terms of Service</h1>
14
+ <p class="cpub-legal-updated">Last updated: April 2026</p>
15
+ </div>
16
+
17
+ <div class="cpub-legal-body">
18
+ <section class="cpub-legal-section">
19
+ <h2>1. Acceptance</h2>
20
+ <p>By creating an account or using {{ siteName }}, you agree to these Terms of Service and our <NuxtLink to="/privacy">Privacy Policy</NuxtLink>. If you do not agree, do not use the service.</p>
21
+ </section>
22
+
23
+ <section class="cpub-legal-section">
24
+ <h2>2. Your Account</h2>
25
+ <ul>
26
+ <li>You must provide accurate information when creating an account.</li>
27
+ <li>You are responsible for keeping your login credentials secure.</li>
28
+ <li>You must be at least 16 years old to create an account (or the minimum age required by your jurisdiction).</li>
29
+ <li>One person, one account. Automated or bot accounts require prior approval from the instance administrator.</li>
30
+ </ul>
31
+ </section>
32
+
33
+ <section class="cpub-legal-section">
34
+ <h2>3. Your Content</h2>
35
+ <p>You retain ownership of content you create on {{ siteName }}. By publishing content, you grant us a license to display, distribute, and federate it as part of operating the platform.</p>
36
+ <ul>
37
+ <li>You must have the right to publish any content you upload (no plagiarism, no copyright infringement).</li>
38
+ <li>Content you import must be your own original work.</li>
39
+ <li>You may delete your content at any time. Federated copies on remote instances are outside our control.</li>
40
+ <li>We may remove content that violates these terms or applicable law.</li>
41
+ </ul>
42
+ </section>
43
+
44
+ <section class="cpub-legal-section">
45
+ <h2>4. Acceptable Use</h2>
46
+ <p>You agree not to:</p>
47
+ <ul>
48
+ <li>Post illegal content or content that infringes others' rights</li>
49
+ <li>Harass, threaten, or abuse other users</li>
50
+ <li>Spam, phish, or distribute malware</li>
51
+ <li>Attempt to gain unauthorized access to the platform or other users' accounts</li>
52
+ <li>Scrape, crawl, or automatically collect data from the platform beyond what public APIs allow</li>
53
+ <li>Impersonate another person or entity</li>
54
+ <li>Use the platform for commercial advertising without permission</li>
55
+ </ul>
56
+ </section>
57
+
58
+ <section class="cpub-legal-section">
59
+ <h2>5. Moderation</h2>
60
+ <p>The instance administrator may, at their discretion:</p>
61
+ <ul>
62
+ <li>Remove content that violates these terms</li>
63
+ <li>Suspend or delete accounts that violate these terms</li>
64
+ <li>Block federation with other instances</li>
65
+ </ul>
66
+ <p>We aim to handle moderation decisions transparently and fairly.</p>
67
+ </section>
68
+
69
+ <section class="cpub-legal-section">
70
+ <h2>6. Availability and Changes</h2>
71
+ <p>We provide {{ siteName }} on an "as is" basis. We do not guarantee uninterrupted availability. We may modify or discontinue the service at any time.</p>
72
+ <p>These terms may be updated. Continued use after changes constitutes acceptance.</p>
73
+ </section>
74
+
75
+ <section class="cpub-legal-section">
76
+ <h2>7. Limitation of Liability</h2>
77
+ <p>To the fullest extent permitted by law, the instance operator is not liable for any indirect, incidental, or consequential damages arising from your use of the platform. This includes data loss, service interruptions, or actions of other users or federated instances.</p>
78
+ </section>
79
+
80
+ <section class="cpub-legal-section">
81
+ <h2>8. Account Deletion</h2>
82
+ <p>You may delete your account at any time from your <NuxtLink to="/settings/account">account settings</NuxtLink>. Account deletion is permanent and removes all your data, content, and activity from this instance. See our <NuxtLink to="/privacy">Privacy Policy</NuxtLink> for details on data retention and federation.</p>
83
+ </section>
84
+
85
+ <section class="cpub-legal-section">
86
+ <h2>9. Contact</h2>
87
+ <p>For questions about these terms, contact the administrator of this {{ siteName }} instance.</p>
88
+ </section>
89
+ </div>
90
+ </div>
91
+ </template>
92
+
93
+ <style scoped>
94
+ .cpub-legal {
95
+ max-width: 740px;
96
+ margin: 0 auto;
97
+ padding: 48px 24px 80px;
98
+ }
99
+
100
+ .cpub-legal-header {
101
+ margin-bottom: 40px;
102
+ }
103
+
104
+ .cpub-legal-title {
105
+ font-size: 28px;
106
+ font-weight: 700;
107
+ margin-bottom: 8px;
108
+ }
109
+
110
+ .cpub-legal-updated {
111
+ font-size: 12px;
112
+ color: var(--text-faint);
113
+ font-family: var(--font-mono);
114
+ }
115
+
116
+ .cpub-legal-body {
117
+ display: flex;
118
+ flex-direction: column;
119
+ gap: 32px;
120
+ }
121
+
122
+ .cpub-legal-section h2 {
123
+ font-size: 16px;
124
+ font-weight: 600;
125
+ margin-bottom: 12px;
126
+ }
127
+
128
+ .cpub-legal-section p {
129
+ font-size: 14px;
130
+ line-height: 1.7;
131
+ color: var(--text-dim);
132
+ margin-bottom: 8px;
133
+ }
134
+
135
+ .cpub-legal-section ul {
136
+ padding-left: 20px;
137
+ margin: 8px 0;
138
+ }
139
+
140
+ .cpub-legal-section li {
141
+ font-size: 14px;
142
+ line-height: 1.7;
143
+ color: var(--text-dim);
144
+ margin-bottom: 4px;
145
+ }
146
+
147
+ .cpub-legal-section strong {
148
+ color: var(--text);
149
+ }
150
+
151
+ .cpub-legal-section a {
152
+ color: var(--accent);
153
+ text-decoration: none;
154
+ }
155
+
156
+ .cpub-legal-section a:hover {
157
+ text-decoration: underline;
158
+ }
159
+
160
+ @media (max-width: 640px) {
161
+ .cpub-legal {
162
+ padding: 24px 16px 60px;
163
+ }
164
+ .cpub-legal-title {
165
+ font-size: 22px;
166
+ }
167
+ }
168
+ </style>
@@ -0,0 +1,55 @@
1
+ import { deleteUser, federateDelete, listContent } from '@commonpub/server';
2
+ import { contentItems } from '@commonpub/schema';
3
+ import { eq, and } from 'drizzle-orm';
4
+
5
+ export default defineEventHandler(async (event): Promise<{ success: true }> => {
6
+ const user = requireAuth(event);
7
+ const db = useDB();
8
+ const config = useConfig();
9
+
10
+ // Prevent deleting the last admin
11
+ if (user.role === 'admin') {
12
+ const { users } = await import('@commonpub/schema');
13
+ const admins = await db
14
+ .select({ id: users.id })
15
+ .from(users)
16
+ .where(eq(users.role, 'admin'))
17
+ .limit(2);
18
+ if (admins.length <= 1) {
19
+ throw createError({
20
+ statusCode: 400,
21
+ statusMessage: 'Cannot delete the only admin account',
22
+ });
23
+ }
24
+ }
25
+
26
+ // Federation cleanup: send Delete activities for published content
27
+ if (config.features.federation) {
28
+ const domain = config.instance.domain;
29
+ if (domain) {
30
+ const published = await db
31
+ .select({ id: contentItems.id })
32
+ .from(contentItems)
33
+ .where(and(
34
+ eq(contentItems.authorId, user.id),
35
+ eq(contentItems.status, 'published'),
36
+ ));
37
+
38
+ for (const item of published) {
39
+ try {
40
+ await federateDelete(db, item.id, domain, user.username);
41
+ } catch {
42
+ // Best-effort — don't block deletion if federation fails
43
+ }
44
+ }
45
+ }
46
+ }
47
+
48
+ // Delete the user (cascades to all related data)
49
+ await deleteUser(db, user.id, user.id);
50
+
51
+ // Clear the session cookie
52
+ deleteCookie(event, 'better-auth.session_token', { path: '/' });
53
+
54
+ return { success: true };
55
+ });
@@ -0,0 +1,15 @@
1
+ import { exportUserData } from '@commonpub/server';
2
+
3
+ export default defineEventHandler(async (event) => {
4
+ const user = requireAuth(event);
5
+ const db = useDB();
6
+
7
+ const data = await exportUserData(db, user.id);
8
+
9
+ const filename = `commonpub-export-${user.username}-${new Date().toISOString().split('T')[0]}.json`;
10
+
11
+ setHeader(event, 'Content-Type', 'application/json');
12
+ setHeader(event, 'Content-Disposition', `attachment; filename="${filename}"`);
13
+
14
+ return data;
15
+ });