@knowcode/doc-builder 1.7.4 → 1.7.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.
- package/.claude/settings.local.json +6 -1
- package/CHANGELOG.md +50 -0
- package/README.md +47 -7
- package/RELEASE-NOTES-1.7.5.md +64 -0
- package/add-user-clive.sql +35 -0
- package/add-user-lindsay-fixed.sql +85 -0
- package/add-user-lindsay.sql +68 -0
- package/add-user-pmorgan.sql +35 -0
- package/add-user-robbie.sql +35 -0
- package/add-wru-users.sql +105 -0
- package/cli.js +223 -8
- package/grant-access.sql +15 -0
- package/html/README.html +14 -4
- package/html/auth.js +97 -0
- package/html/documentation-index.html +14 -4
- package/html/guides/authentication-default-change.html +302 -0
- package/html/guides/authentication-guide.html +32 -14
- package/html/guides/cache-control-anti-pattern.html +361 -0
- package/html/guides/claude-workflow-guide.html +14 -4
- package/html/guides/documentation-standards.html +14 -4
- package/html/guides/next-steps-walkthrough.html +638 -0
- package/html/guides/phosphor-icons-guide.html +14 -4
- package/html/guides/public-site-deployment.html +363 -0
- package/html/guides/search-engine-verification-guide.html +14 -4
- package/html/guides/seo-guide.html +14 -4
- package/html/guides/seo-optimization-guide.html +14 -4
- package/html/guides/supabase-auth-implementation-plan.html +543 -0
- package/html/guides/supabase-auth-integration-plan.html +671 -0
- package/html/guides/supabase-auth-setup-guide.html +498 -0
- package/html/guides/troubleshooting-guide.html +14 -4
- package/html/guides/vercel-deployment-auth-setup.html +337 -0
- package/html/guides/windows-setup-guide.html +14 -4
- package/html/index.html +14 -4
- package/html/launch/README.html +14 -4
- package/html/launch/bubble-plugin-specification.html +14 -4
- package/html/launch/go-to-market-strategy.html +14 -4
- package/html/launch/launch-announcements.html +14 -4
- package/html/login.html +102 -0
- package/html/logout.html +18 -0
- package/html/sitemap.xml +69 -21
- package/html/vercel-cli-setup-guide.html +14 -4
- package/html/vercel-first-time-setup-guide.html +14 -4
- package/lib/config.js +33 -29
- package/lib/core-builder.js +142 -88
- package/lib/supabase-auth.js +295 -0
- package/manage-users.sql +191 -0
- package/package.json +2 -1
- package/public-config.js +22 -0
- package/public-html/404.html +115 -0
- package/public-html/README.html +149 -0
- package/public-html/css/notion-style.css +2036 -0
- package/public-html/index.html +149 -0
- package/public-html/js/main.js +1485 -0
- package/quick-test-commands.md +40 -0
- package/recordings/Screenshot 2025-07-24 at 18.22.01.png +0 -0
- package/setup-database.sql +41 -0
- package/test-auth-config.js +17 -0
- package/test-docs/README.md +39 -0
- package/test-html/404.html +115 -0
- package/test-html/README.html +172 -0
- package/test-html/auth.js +97 -0
- package/test-html/css/notion-style.css +2036 -0
- package/test-html/index.html +172 -0
- package/test-html/js/auth.js +97 -0
- package/test-html/js/main.js +1485 -0
- package/test-html/login.html +102 -0
- package/test-html/logout.html +18 -0
- package/update-domain.sql +9 -0
- package/view-all-users.sql +40 -0
- package/wru-auth-config.js +17 -0
- /package/{assets → public-html}/js/auth.js +0 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
const { createClient } = require('@supabase/supabase-js');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Supabase Authentication Module for @knowcode/doc-builder
|
|
5
|
+
*
|
|
6
|
+
* This module provides secure authentication functionality using Supabase's
|
|
7
|
+
* built-in auth system. It replaces the insecure basic auth implementation.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
class SupabaseAuth {
|
|
11
|
+
constructor(config) {
|
|
12
|
+
if (!config.supabaseUrl || !config.supabaseAnonKey) {
|
|
13
|
+
throw new Error('Supabase URL and anonymous key are required for authentication');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
this.config = config;
|
|
17
|
+
this.supabase = createClient(config.supabaseUrl, config.supabaseAnonKey, {
|
|
18
|
+
auth: {
|
|
19
|
+
persistSession: true,
|
|
20
|
+
autoRefreshToken: true,
|
|
21
|
+
detectSessionInUrl: true
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
this.siteId = config.siteId;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Generate client-side auth script for inclusion in HTML pages
|
|
30
|
+
*/
|
|
31
|
+
generateAuthScript() {
|
|
32
|
+
return `
|
|
33
|
+
/**
|
|
34
|
+
* Supabase Authentication for Documentation Site
|
|
35
|
+
* Generated by @knowcode/doc-builder
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
(function() {
|
|
39
|
+
'use strict';
|
|
40
|
+
|
|
41
|
+
// Skip auth check on login and logout pages
|
|
42
|
+
const currentPage = window.location.pathname;
|
|
43
|
+
if (currentPage === '/login.html' || currentPage === '/logout.html' ||
|
|
44
|
+
currentPage.includes('login') || currentPage.includes('logout')) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Initialize Supabase client
|
|
49
|
+
const { createClient } = supabase;
|
|
50
|
+
const supabaseClient = createClient('${this.config.supabaseUrl}', '${this.config.supabaseAnonKey}', {
|
|
51
|
+
auth: {
|
|
52
|
+
persistSession: true,
|
|
53
|
+
autoRefreshToken: true,
|
|
54
|
+
detectSessionInUrl: true
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Check authentication and site access
|
|
59
|
+
async function checkAuth() {
|
|
60
|
+
try {
|
|
61
|
+
// Get current user session
|
|
62
|
+
const { data: { user }, error: userError } = await supabaseClient.auth.getUser();
|
|
63
|
+
|
|
64
|
+
if (userError || !user) {
|
|
65
|
+
redirectToLogin();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Check if user has access to this site
|
|
70
|
+
const { data: access, error: accessError } = await supabaseClient
|
|
71
|
+
.from('docbuilder_access')
|
|
72
|
+
.select('*')
|
|
73
|
+
.eq('user_id', user.id)
|
|
74
|
+
.eq('site_id', '${this.config.siteId}')
|
|
75
|
+
.single();
|
|
76
|
+
|
|
77
|
+
if (accessError || !access) {
|
|
78
|
+
showAccessDenied();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// User is authenticated and has access
|
|
83
|
+
console.log('User authenticated and authorized');
|
|
84
|
+
document.body.classList.add('authenticated');
|
|
85
|
+
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error('Auth check failed:', error);
|
|
88
|
+
redirectToLogin();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Redirect to login page
|
|
93
|
+
function redirectToLogin() {
|
|
94
|
+
const currentUrl = window.location.pathname + window.location.search;
|
|
95
|
+
const loginUrl = '/login.html' + (currentUrl !== '/' ? '?redirect=' + encodeURIComponent(currentUrl) : '');
|
|
96
|
+
window.location.href = loginUrl;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Show access denied message
|
|
100
|
+
function showAccessDenied() {
|
|
101
|
+
document.body.classList.add('authenticated'); // Show the body
|
|
102
|
+
document.body.innerHTML = \`
|
|
103
|
+
<div style="display: flex; justify-content: center; align-items: center; height: 100vh; font-family: Inter, sans-serif;">
|
|
104
|
+
<div style="text-align: center; max-width: 400px;">
|
|
105
|
+
<h1 style="color: #ef4444; margin-bottom: 1rem;">Access Denied</h1>
|
|
106
|
+
<p style="color: #6b7280; margin-bottom: 2rem;">You don't have permission to view this documentation site.</p>
|
|
107
|
+
<a href="/login.html" style="background: #3b82f6; color: white; padding: 0.75rem 1.5rem; border-radius: 0.5rem; text-decoration: none;">Try Different Account</a>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
\`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Add logout functionality
|
|
114
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
115
|
+
const logoutLinks = document.querySelectorAll('a[href*="logout"]');
|
|
116
|
+
logoutLinks.forEach(link => {
|
|
117
|
+
link.addEventListener('click', async function(e) {
|
|
118
|
+
e.preventDefault();
|
|
119
|
+
await supabaseClient.auth.signOut();
|
|
120
|
+
window.location.href = '/logout.html';
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Run auth check
|
|
126
|
+
checkAuth();
|
|
127
|
+
|
|
128
|
+
})();
|
|
129
|
+
`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Generate login page HTML
|
|
134
|
+
*/
|
|
135
|
+
generateLoginPage(config) {
|
|
136
|
+
const siteName = config.siteName || 'Documentation';
|
|
137
|
+
|
|
138
|
+
return `<!DOCTYPE html>
|
|
139
|
+
<html lang="en">
|
|
140
|
+
<head>
|
|
141
|
+
<meta charset="UTF-8">
|
|
142
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
143
|
+
<title>Login - ${siteName}</title>
|
|
144
|
+
<link rel="stylesheet" href="css/notion-style.css">
|
|
145
|
+
<script src="https://unpkg.com/@supabase/supabase-js@2"></script>
|
|
146
|
+
</head>
|
|
147
|
+
<body class="auth-page">
|
|
148
|
+
<div class="auth-container">
|
|
149
|
+
<div class="auth-box">
|
|
150
|
+
<h1>Login to ${siteName}</h1>
|
|
151
|
+
<form id="login-form">
|
|
152
|
+
<div class="form-group">
|
|
153
|
+
<label for="email">Email</label>
|
|
154
|
+
<input type="email" id="email" name="email" required>
|
|
155
|
+
</div>
|
|
156
|
+
<div class="form-group">
|
|
157
|
+
<label for="password">Password</label>
|
|
158
|
+
<input type="password" id="password" name="password" required>
|
|
159
|
+
</div>
|
|
160
|
+
<button type="submit" class="auth-button">Login</button>
|
|
161
|
+
</form>
|
|
162
|
+
<div id="error-message" class="error-message"></div>
|
|
163
|
+
<div class="auth-links">
|
|
164
|
+
<a href="#" id="forgot-password">Forgot Password?</a>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
<script>
|
|
170
|
+
// Initialize Supabase
|
|
171
|
+
const { createClient } = supabase;
|
|
172
|
+
const supabaseClient = createClient('${this.config.supabaseUrl}', '${this.config.supabaseAnonKey}');
|
|
173
|
+
|
|
174
|
+
// Handle login form
|
|
175
|
+
document.getElementById('login-form').addEventListener('submit', async function(e) {
|
|
176
|
+
e.preventDefault();
|
|
177
|
+
|
|
178
|
+
const email = document.getElementById('email').value;
|
|
179
|
+
const password = document.getElementById('password').value;
|
|
180
|
+
const errorDiv = document.getElementById('error-message');
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
// Sign in with Supabase
|
|
184
|
+
const { data, error } = await supabaseClient.auth.signInWithPassword({
|
|
185
|
+
email: email,
|
|
186
|
+
password: password
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
if (error) throw error;
|
|
190
|
+
|
|
191
|
+
// Check if user has access to this site
|
|
192
|
+
const { data: access, error: accessError } = await supabaseClient
|
|
193
|
+
.from('docbuilder_access')
|
|
194
|
+
.select('*')
|
|
195
|
+
.eq('user_id', data.user.id)
|
|
196
|
+
.eq('site_id', '${this.config.siteId}')
|
|
197
|
+
.single();
|
|
198
|
+
|
|
199
|
+
if (accessError || !access) {
|
|
200
|
+
await supabaseClient.auth.signOut();
|
|
201
|
+
throw new Error('You do not have access to this documentation site');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Redirect to requested page
|
|
205
|
+
const params = new URLSearchParams(window.location.search);
|
|
206
|
+
const redirect = params.get('redirect') || '/';
|
|
207
|
+
window.location.href = redirect;
|
|
208
|
+
|
|
209
|
+
} catch (error) {
|
|
210
|
+
errorDiv.textContent = error.message;
|
|
211
|
+
errorDiv.style.display = 'block';
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Handle forgot password
|
|
216
|
+
document.getElementById('forgot-password').addEventListener('click', async function(e) {
|
|
217
|
+
e.preventDefault();
|
|
218
|
+
|
|
219
|
+
const email = document.getElementById('email').value;
|
|
220
|
+
if (!email) {
|
|
221
|
+
alert('Please enter your email address first');
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
const { error } = await supabaseClient.auth.resetPasswordForEmail(email, {
|
|
227
|
+
redirectTo: window.location.origin + '/login.html'
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
if (error) throw error;
|
|
231
|
+
|
|
232
|
+
alert('Password reset email sent! Check your inbox.');
|
|
233
|
+
} catch (error) {
|
|
234
|
+
alert('Error sending reset email: ' + error.message);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
</script>
|
|
238
|
+
</body>
|
|
239
|
+
</html>`;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Generate logout page HTML
|
|
244
|
+
*/
|
|
245
|
+
generateLogoutPage(config) {
|
|
246
|
+
const siteName = config.siteName || 'Documentation';
|
|
247
|
+
|
|
248
|
+
return `<!DOCTYPE html>
|
|
249
|
+
<html lang="en">
|
|
250
|
+
<head>
|
|
251
|
+
<meta charset="UTF-8">
|
|
252
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
253
|
+
<title>Logged Out - ${siteName}</title>
|
|
254
|
+
<link rel="stylesheet" href="css/notion-style.css">
|
|
255
|
+
</head>
|
|
256
|
+
<body class="auth-page">
|
|
257
|
+
<div class="auth-container">
|
|
258
|
+
<div class="auth-box">
|
|
259
|
+
<h1>You have been logged out</h1>
|
|
260
|
+
<p>Thank you for using ${siteName}.</p>
|
|
261
|
+
<a href="login.html" class="auth-button">Login Again</a>
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
</body>
|
|
265
|
+
</html>`;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Validate Supabase configuration
|
|
270
|
+
*/
|
|
271
|
+
static validateConfig(config) {
|
|
272
|
+
const errors = [];
|
|
273
|
+
|
|
274
|
+
if (!config.auth.supabaseUrl) {
|
|
275
|
+
errors.push('auth.supabaseUrl is required');
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (!config.auth.supabaseAnonKey) {
|
|
279
|
+
errors.push('auth.supabaseAnonKey is required');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (!config.auth.siteId) {
|
|
283
|
+
errors.push('auth.siteId is required');
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Validate URL format
|
|
287
|
+
if (config.auth.supabaseUrl && !config.auth.supabaseUrl.match(/^https:\/\/\w+\.supabase\.co$/)) {
|
|
288
|
+
errors.push('auth.supabaseUrl must be a valid Supabase URL (https://xxx.supabase.co)');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return errors;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
module.exports = SupabaseAuth;
|
package/manage-users.sql
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
-- =====================================================
|
|
2
|
+
-- USER MANAGEMENT SQL COMMANDS FOR DOC-BUILDER
|
|
3
|
+
-- =====================================================
|
|
4
|
+
-- Run these in your Supabase SQL Editor
|
|
5
|
+
|
|
6
|
+
-- =====================================================
|
|
7
|
+
-- 1. VIEW YOUR SITES
|
|
8
|
+
-- =====================================================
|
|
9
|
+
-- See all your documentation sites
|
|
10
|
+
SELECT id, domain, name, created_at
|
|
11
|
+
FROM docbuilder_sites
|
|
12
|
+
ORDER BY created_at DESC;
|
|
13
|
+
|
|
14
|
+
-- =====================================================
|
|
15
|
+
-- 2. VIEW EXISTING USERS
|
|
16
|
+
-- =====================================================
|
|
17
|
+
-- See all users in your Supabase project
|
|
18
|
+
SELECT id, email, created_at, last_sign_in_at
|
|
19
|
+
FROM auth.users
|
|
20
|
+
ORDER BY created_at DESC;
|
|
21
|
+
|
|
22
|
+
-- =====================================================
|
|
23
|
+
-- 3. VIEW WHO HAS ACCESS TO A SPECIFIC SITE
|
|
24
|
+
-- =====================================================
|
|
25
|
+
-- Replace 'your-site-id' with actual site ID
|
|
26
|
+
SELECT
|
|
27
|
+
u.email,
|
|
28
|
+
u.id as user_id,
|
|
29
|
+
u.created_at as user_since,
|
|
30
|
+
da.created_at as access_granted,
|
|
31
|
+
ds.name as site_name,
|
|
32
|
+
ds.domain
|
|
33
|
+
FROM docbuilder_access da
|
|
34
|
+
JOIN auth.users u ON u.id = da.user_id
|
|
35
|
+
JOIN docbuilder_sites ds ON ds.id = da.site_id
|
|
36
|
+
WHERE da.site_id = 'your-site-id'
|
|
37
|
+
ORDER BY da.created_at DESC;
|
|
38
|
+
|
|
39
|
+
-- =====================================================
|
|
40
|
+
-- 4. ADD A SINGLE USER TO A SITE
|
|
41
|
+
-- =====================================================
|
|
42
|
+
-- First, create user in Supabase Dashboard (Authentication > Users > Invite)
|
|
43
|
+
-- Then grant access:
|
|
44
|
+
INSERT INTO docbuilder_access (user_id, site_id)
|
|
45
|
+
VALUES (
|
|
46
|
+
(SELECT id FROM auth.users WHERE email = 'user@example.com'),
|
|
47
|
+
'your-site-id'
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
-- =====================================================
|
|
51
|
+
-- 5. ADD MULTIPLE USERS TO A SITE
|
|
52
|
+
-- =====================================================
|
|
53
|
+
-- Add a list of users all at once
|
|
54
|
+
WITH users_to_add AS (
|
|
55
|
+
SELECT email FROM (VALUES
|
|
56
|
+
('user1@example.com'),
|
|
57
|
+
('user2@example.com'),
|
|
58
|
+
('user3@example.com'),
|
|
59
|
+
('user4@example.com')
|
|
60
|
+
) AS t(email)
|
|
61
|
+
)
|
|
62
|
+
INSERT INTO docbuilder_access (user_id, site_id)
|
|
63
|
+
SELECT u.id, 'your-site-id'
|
|
64
|
+
FROM auth.users u
|
|
65
|
+
JOIN users_to_add ua ON u.email = ua.email
|
|
66
|
+
WHERE NOT EXISTS (
|
|
67
|
+
-- Prevent duplicate entries
|
|
68
|
+
SELECT 1 FROM docbuilder_access
|
|
69
|
+
WHERE user_id = u.id AND site_id = 'your-site-id'
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
-- =====================================================
|
|
73
|
+
-- 6. GRANT USER ACCESS TO MULTIPLE SITES
|
|
74
|
+
-- =====================================================
|
|
75
|
+
-- Give one user access to several sites
|
|
76
|
+
WITH sites_to_grant AS (
|
|
77
|
+
SELECT id FROM docbuilder_sites
|
|
78
|
+
WHERE domain IN ('site1.com', 'site2.com', 'site3.com')
|
|
79
|
+
)
|
|
80
|
+
INSERT INTO docbuilder_access (user_id, site_id)
|
|
81
|
+
SELECT
|
|
82
|
+
(SELECT id FROM auth.users WHERE email = 'user@example.com'),
|
|
83
|
+
s.id
|
|
84
|
+
FROM sites_to_grant s
|
|
85
|
+
WHERE NOT EXISTS (
|
|
86
|
+
SELECT 1 FROM docbuilder_access
|
|
87
|
+
WHERE user_id = (SELECT id FROM auth.users WHERE email = 'user@example.com')
|
|
88
|
+
AND site_id = s.id
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
-- =====================================================
|
|
92
|
+
-- 7. REMOVE USER ACCESS FROM A SITE
|
|
93
|
+
-- =====================================================
|
|
94
|
+
-- Remove specific user from specific site
|
|
95
|
+
DELETE FROM docbuilder_access
|
|
96
|
+
WHERE user_id = (SELECT id FROM auth.users WHERE email = 'user@example.com')
|
|
97
|
+
AND site_id = 'your-site-id';
|
|
98
|
+
|
|
99
|
+
-- =====================================================
|
|
100
|
+
-- 8. REMOVE USER FROM ALL SITES
|
|
101
|
+
-- =====================================================
|
|
102
|
+
-- Completely remove user's access to all documentation
|
|
103
|
+
DELETE FROM docbuilder_access
|
|
104
|
+
WHERE user_id = (SELECT id FROM auth.users WHERE email = 'user@example.com');
|
|
105
|
+
|
|
106
|
+
-- =====================================================
|
|
107
|
+
-- 9. BULK REMOVE USERS
|
|
108
|
+
-- =====================================================
|
|
109
|
+
-- Remove multiple users from a site
|
|
110
|
+
WITH users_to_remove AS (
|
|
111
|
+
SELECT email FROM (VALUES
|
|
112
|
+
('olduser1@example.com'),
|
|
113
|
+
('olduser2@example.com')
|
|
114
|
+
) AS t(email)
|
|
115
|
+
)
|
|
116
|
+
DELETE FROM docbuilder_access
|
|
117
|
+
WHERE site_id = 'your-site-id'
|
|
118
|
+
AND user_id IN (
|
|
119
|
+
SELECT u.id FROM auth.users u
|
|
120
|
+
JOIN users_to_remove ur ON u.email = ur.email
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
-- =====================================================
|
|
124
|
+
-- 10. VIEW ACCESS SUMMARY
|
|
125
|
+
-- =====================================================
|
|
126
|
+
-- See how many users each site has
|
|
127
|
+
SELECT
|
|
128
|
+
ds.name as site_name,
|
|
129
|
+
ds.domain,
|
|
130
|
+
ds.id as site_id,
|
|
131
|
+
COUNT(da.user_id) as user_count,
|
|
132
|
+
MAX(da.created_at) as last_access_granted
|
|
133
|
+
FROM docbuilder_sites ds
|
|
134
|
+
LEFT JOIN docbuilder_access da ON ds.id = da.site_id
|
|
135
|
+
GROUP BY ds.id, ds.name, ds.domain
|
|
136
|
+
ORDER BY user_count DESC;
|
|
137
|
+
|
|
138
|
+
-- =====================================================
|
|
139
|
+
-- 11. FIND USERS WITHOUT ACCESS TO ANY SITE
|
|
140
|
+
-- =====================================================
|
|
141
|
+
-- Useful for cleanup
|
|
142
|
+
SELECT u.email, u.created_at, u.last_sign_in_at
|
|
143
|
+
FROM auth.users u
|
|
144
|
+
WHERE NOT EXISTS (
|
|
145
|
+
SELECT 1 FROM docbuilder_access da
|
|
146
|
+
WHERE da.user_id = u.id
|
|
147
|
+
)
|
|
148
|
+
ORDER BY u.created_at DESC;
|
|
149
|
+
|
|
150
|
+
-- =====================================================
|
|
151
|
+
-- 12. AUDIT LOG - RECENT ACCESS GRANTS
|
|
152
|
+
-- =====================================================
|
|
153
|
+
-- See who was granted access recently
|
|
154
|
+
SELECT
|
|
155
|
+
u.email,
|
|
156
|
+
ds.name as site_name,
|
|
157
|
+
ds.domain,
|
|
158
|
+
da.created_at as access_granted
|
|
159
|
+
FROM docbuilder_access da
|
|
160
|
+
JOIN auth.users u ON u.id = da.user_id
|
|
161
|
+
JOIN docbuilder_sites ds ON ds.id = da.site_id
|
|
162
|
+
WHERE da.created_at > NOW() - INTERVAL '30 days'
|
|
163
|
+
ORDER BY da.created_at DESC;
|
|
164
|
+
|
|
165
|
+
-- =====================================================
|
|
166
|
+
-- 13. COPY ACCESS FROM ONE USER TO ANOTHER
|
|
167
|
+
-- =====================================================
|
|
168
|
+
-- Useful when replacing team members
|
|
169
|
+
INSERT INTO docbuilder_access (user_id, site_id)
|
|
170
|
+
SELECT
|
|
171
|
+
(SELECT id FROM auth.users WHERE email = 'newuser@example.com'),
|
|
172
|
+
da.site_id
|
|
173
|
+
FROM docbuilder_access da
|
|
174
|
+
WHERE da.user_id = (SELECT id FROM auth.users WHERE email = 'olduser@example.com');
|
|
175
|
+
|
|
176
|
+
-- =====================================================
|
|
177
|
+
-- COMMON SITE IDs FOR REFERENCE
|
|
178
|
+
-- =====================================================
|
|
179
|
+
-- Your test site: 4d8a53bf-dcdd-48c0-98e0-cd1451518735
|
|
180
|
+
-- Add more site IDs here as you create them:
|
|
181
|
+
-- Production site: xxx
|
|
182
|
+
-- Staging site: xxx
|
|
183
|
+
|
|
184
|
+
-- =====================================================
|
|
185
|
+
-- TIPS
|
|
186
|
+
-- =====================================================
|
|
187
|
+
-- 1. Always check if users exist before granting access
|
|
188
|
+
-- 2. Use the Supabase Dashboard to invite new users first
|
|
189
|
+
-- 3. Run SELECT queries before DELETE to verify
|
|
190
|
+
-- 4. Keep this file updated with your site IDs
|
|
191
|
+
-- 5. Consider creating views for common queries
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knowcode/doc-builder",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.6",
|
|
4
4
|
"description": "Reusable documentation builder for markdown-based sites with Vercel deployment support",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"homepage": "https://github.com/wapdat/doc-builder#readme",
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@knowcode/doc-builder": "^1.4.21",
|
|
35
|
+
"@supabase/supabase-js": "^2.39.0",
|
|
35
36
|
"chalk": "^4.1.2",
|
|
36
37
|
"commander": "^11.0.0",
|
|
37
38
|
"fs-extra": "^11.2.0",
|
package/public-config.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
siteName: '✨ Test Documentation',
|
|
3
|
+
siteDescription: 'Public documentation site',
|
|
4
|
+
favicon: '✨',
|
|
5
|
+
footer: {
|
|
6
|
+
copyright: 'Test Documentation',
|
|
7
|
+
links: []
|
|
8
|
+
},
|
|
9
|
+
docsDir: 'test-docs',
|
|
10
|
+
outputDir: 'public-html',
|
|
11
|
+
// No authentication configured - this is a public site
|
|
12
|
+
features: {
|
|
13
|
+
authentication: false,
|
|
14
|
+
darkMode: true,
|
|
15
|
+
mermaid: true,
|
|
16
|
+
changelog: false,
|
|
17
|
+
normalizeTitle: true,
|
|
18
|
+
showPdfDownload: true,
|
|
19
|
+
menuDefaultOpen: true,
|
|
20
|
+
phosphorIcons: true
|
|
21
|
+
}
|
|
22
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Page Not Found - Redirecting...</title>
|
|
7
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
8
|
+
<style>
|
|
9
|
+
body {
|
|
10
|
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
11
|
+
margin: 0;
|
|
12
|
+
padding: 0;
|
|
13
|
+
display: flex;
|
|
14
|
+
align-items: center;
|
|
15
|
+
justify-content: center;
|
|
16
|
+
min-height: 100vh;
|
|
17
|
+
background-color: #f7f7f5;
|
|
18
|
+
color: #37352f;
|
|
19
|
+
}
|
|
20
|
+
.container {
|
|
21
|
+
text-align: center;
|
|
22
|
+
padding: 2rem;
|
|
23
|
+
max-width: 600px;
|
|
24
|
+
}
|
|
25
|
+
h1 {
|
|
26
|
+
font-size: 3rem;
|
|
27
|
+
margin: 0 0 1rem 0;
|
|
28
|
+
color: #37352f;
|
|
29
|
+
}
|
|
30
|
+
p {
|
|
31
|
+
font-size: 1.125rem;
|
|
32
|
+
line-height: 1.6;
|
|
33
|
+
color: #6b6b6b;
|
|
34
|
+
margin: 0 0 2rem 0;
|
|
35
|
+
}
|
|
36
|
+
a {
|
|
37
|
+
color: #0366d6;
|
|
38
|
+
text-decoration: none;
|
|
39
|
+
}
|
|
40
|
+
a:hover {
|
|
41
|
+
text-decoration: underline;
|
|
42
|
+
}
|
|
43
|
+
.emoji {
|
|
44
|
+
font-size: 4rem;
|
|
45
|
+
margin-bottom: 1rem;
|
|
46
|
+
}
|
|
47
|
+
.loading {
|
|
48
|
+
display: none;
|
|
49
|
+
color: #0366d6;
|
|
50
|
+
margin-top: 1rem;
|
|
51
|
+
}
|
|
52
|
+
.redirect-message {
|
|
53
|
+
display: none;
|
|
54
|
+
background-color: #e8f4fd;
|
|
55
|
+
border: 1px solid #c3e0f7;
|
|
56
|
+
padding: 1rem;
|
|
57
|
+
border-radius: 8px;
|
|
58
|
+
margin-top: 1rem;
|
|
59
|
+
}
|
|
60
|
+
</style>
|
|
61
|
+
</head>
|
|
62
|
+
<body>
|
|
63
|
+
<div class="container">
|
|
64
|
+
<div class="emoji">🔍</div>
|
|
65
|
+
<h1>404</h1>
|
|
66
|
+
<p id="message">The page you're looking for doesn't exist.</p>
|
|
67
|
+
<div id="redirect-message" class="redirect-message">
|
|
68
|
+
Redirecting to the correct page...
|
|
69
|
+
</div>
|
|
70
|
+
<p id="loading" class="loading">Redirecting...</p>
|
|
71
|
+
<p>
|
|
72
|
+
<a href="/" id="home-link">Go to Home</a>
|
|
73
|
+
</p>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<script>
|
|
77
|
+
// Check if the URL ends with .md
|
|
78
|
+
const pathname = window.location.pathname;
|
|
79
|
+
|
|
80
|
+
if (pathname.endsWith('.md')) {
|
|
81
|
+
// Convert .md to .html
|
|
82
|
+
const htmlPath = pathname.replace(/\.md$/, '.html');
|
|
83
|
+
|
|
84
|
+
// Show redirect message
|
|
85
|
+
document.getElementById('message').textContent = 'Found a markdown link. Redirecting to the HTML version...';
|
|
86
|
+
document.getElementById('redirect-message').style.display = 'block';
|
|
87
|
+
document.getElementById('loading').style.display = 'block';
|
|
88
|
+
|
|
89
|
+
// Redirect after a brief delay to show the message
|
|
90
|
+
setTimeout(() => {
|
|
91
|
+
window.location.replace(htmlPath);
|
|
92
|
+
}, 500);
|
|
93
|
+
} else {
|
|
94
|
+
// For true 404s, show the standard message
|
|
95
|
+
document.getElementById('message').textContent = "The page you're looking for doesn't exist.";
|
|
96
|
+
|
|
97
|
+
// Also check if we can suggest a similar page
|
|
98
|
+
// Remove common suffixes and try to find a match
|
|
99
|
+
const basePath = pathname
|
|
100
|
+
.replace(/\.(html|htm|php|asp|aspx)$/, '')
|
|
101
|
+
.replace(/\/$/, '');
|
|
102
|
+
|
|
103
|
+
if (basePath && basePath !== pathname) {
|
|
104
|
+
document.getElementById('message').innerHTML =
|
|
105
|
+
`The page you're looking for doesn't exist.<br>
|
|
106
|
+
<small>Did you mean <a href="${basePath}.html">${basePath}.html</a>?</small>`;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Update home link to use the correct base URL
|
|
111
|
+
const baseUrl = window.location.origin;
|
|
112
|
+
document.getElementById('home-link').href = baseUrl;
|
|
113
|
+
</script>
|
|
114
|
+
</body>
|
|
115
|
+
</html>
|