@polardbx/pxbaas 0.0.0-stage → 0.1.0-beta.1
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/bin/pxbaas.js +13 -0
- package/dist/app/preflight.d.ts +38 -0
- package/dist/app/preflight.d.ts.map +1 -0
- package/dist/app/preflight.js +79 -0
- package/dist/app/preflight.js.map +1 -0
- package/dist/app/spec-lint.d.ts +45 -0
- package/dist/app/spec-lint.d.ts.map +1 -0
- package/dist/app/spec-lint.js +407 -0
- package/dist/app/spec-lint.js.map +1 -0
- package/dist/app/templates.d.ts +143 -0
- package/dist/app/templates.d.ts.map +1 -0
- package/dist/app/templates.js +151 -0
- package/dist/app/templates.js.map +1 -0
- package/dist/cli/root.d.ts +3 -0
- package/dist/cli/root.d.ts.map +1 -0
- package/dist/cli/root.js +69 -0
- package/dist/cli/root.js.map +1 -0
- package/dist/commands/app/index.d.ts +8 -0
- package/dist/commands/app/index.d.ts.map +1 -0
- package/dist/commands/app/index.js +1267 -0
- package/dist/commands/app/index.js.map +1 -0
- package/dist/commands/auth/index.d.ts +3 -0
- package/dist/commands/auth/index.d.ts.map +1 -0
- package/dist/commands/auth/index.js +198 -0
- package/dist/commands/auth/index.js.map +1 -0
- package/dist/commands/completion/index.d.ts +3 -0
- package/dist/commands/completion/index.d.ts.map +1 -0
- package/dist/commands/completion/index.js +10 -0
- package/dist/commands/completion/index.js.map +1 -0
- package/dist/commands/data/index.d.ts +3 -0
- package/dist/commands/data/index.d.ts.map +1 -0
- package/dist/commands/data/index.js +245 -0
- package/dist/commands/data/index.js.map +1 -0
- package/dist/commands/db/index.d.ts +14 -0
- package/dist/commands/db/index.d.ts.map +1 -0
- package/dist/commands/db/index.js +355 -0
- package/dist/commands/db/index.js.map +1 -0
- package/dist/commands/doctor/index.d.ts +3 -0
- package/dist/commands/doctor/index.d.ts.map +1 -0
- package/dist/commands/doctor/index.js +305 -0
- package/dist/commands/doctor/index.js.map +1 -0
- package/dist/commands/functions/delete.d.ts +3 -0
- package/dist/commands/functions/delete.d.ts.map +1 -0
- package/dist/commands/functions/delete.js +55 -0
- package/dist/commands/functions/delete.js.map +1 -0
- package/dist/commands/functions/deploy.d.ts +3 -0
- package/dist/commands/functions/deploy.d.ts.map +1 -0
- package/dist/commands/functions/deploy.js +118 -0
- package/dist/commands/functions/deploy.js.map +1 -0
- package/dist/commands/functions/index.d.ts +3 -0
- package/dist/commands/functions/index.d.ts.map +1 -0
- package/dist/commands/functions/index.js +15 -0
- package/dist/commands/functions/index.js.map +1 -0
- package/dist/commands/functions/list.d.ts +6 -0
- package/dist/commands/functions/list.d.ts.map +1 -0
- package/dist/commands/functions/list.js +63 -0
- package/dist/commands/functions/list.js.map +1 -0
- package/dist/commands/functions/new.d.ts +3 -0
- package/dist/commands/functions/new.d.ts.map +1 -0
- package/dist/commands/functions/new.js +57 -0
- package/dist/commands/functions/new.js.map +1 -0
- package/dist/commands/gen/index.d.ts +3 -0
- package/dist/commands/gen/index.d.ts.map +1 -0
- package/dist/commands/gen/index.js +320 -0
- package/dist/commands/gen/index.js.map +1 -0
- package/dist/commands/instance/index.d.ts +8 -0
- package/dist/commands/instance/index.d.ts.map +1 -0
- package/dist/commands/instance/index.js +361 -0
- package/dist/commands/instance/index.js.map +1 -0
- package/dist/commands/login/index.d.ts +3 -0
- package/dist/commands/login/index.d.ts.map +1 -0
- package/dist/commands/login/index.js +185 -0
- package/dist/commands/login/index.js.map +1 -0
- package/dist/commands/login/login.d.ts +3 -0
- package/dist/commands/login/login.d.ts.map +1 -0
- package/dist/commands/login/login.js +185 -0
- package/dist/commands/login/login.js.map +1 -0
- package/dist/commands/logout/index.d.ts +3 -0
- package/dist/commands/logout/index.d.ts.map +1 -0
- package/dist/commands/logout/index.js +41 -0
- package/dist/commands/logout/index.js.map +1 -0
- package/dist/commands/profile/import-env.d.ts +13 -0
- package/dist/commands/profile/import-env.d.ts.map +1 -0
- package/dist/commands/profile/import-env.js +81 -0
- package/dist/commands/profile/import-env.js.map +1 -0
- package/dist/commands/profile/index.d.ts +3 -0
- package/dist/commands/profile/index.d.ts.map +1 -0
- package/dist/commands/profile/index.js +109 -0
- package/dist/commands/profile/index.js.map +1 -0
- package/dist/commands/rest/index.d.ts +3 -0
- package/dist/commands/rest/index.d.ts.map +1 -0
- package/dist/commands/rest/index.js +102 -0
- package/dist/commands/rest/index.js.map +1 -0
- package/dist/commands/skill/index.d.ts +11 -0
- package/dist/commands/skill/index.d.ts.map +1 -0
- package/dist/commands/skill/index.js +153 -0
- package/dist/commands/skill/index.js.map +1 -0
- package/dist/commands/status/index.d.ts +3 -0
- package/dist/commands/status/index.d.ts.map +1 -0
- package/dist/commands/status/index.js +59 -0
- package/dist/commands/status/index.js.map +1 -0
- package/dist/commands/storage/index.d.ts +3 -0
- package/dist/commands/storage/index.d.ts.map +1 -0
- package/dist/commands/storage/index.js +198 -0
- package/dist/commands/storage/index.js.map +1 -0
- package/dist/commands/whoami/index.d.ts +3 -0
- package/dist/commands/whoami/index.d.ts.map +1 -0
- package/dist/commands/whoami/index.js +82 -0
- package/dist/commands/whoami/index.js.map +1 -0
- package/dist/credentials/commit.d.ts +38 -0
- package/dist/credentials/commit.d.ts.map +1 -0
- package/dist/credentials/commit.js +57 -0
- package/dist/credentials/commit.js.map +1 -0
- package/dist/credentials/credential-store.d.ts +57 -0
- package/dist/credentials/credential-store.d.ts.map +1 -0
- package/dist/credentials/credential-store.js +180 -0
- package/dist/credentials/credential-store.js.map +1 -0
- package/dist/credentials/env-file.d.ts +44 -0
- package/dist/credentials/env-file.d.ts.map +1 -0
- package/dist/credentials/env-file.js +118 -0
- package/dist/credentials/env-file.js.map +1 -0
- package/dist/credentials/formatter.d.ts +39 -0
- package/dist/credentials/formatter.d.ts.map +1 -0
- package/dist/credentials/formatter.js +79 -0
- package/dist/credentials/formatter.js.map +1 -0
- package/dist/credentials/index.d.ts +11 -0
- package/dist/credentials/index.d.ts.map +1 -0
- package/dist/credentials/index.js +11 -0
- package/dist/credentials/index.js.map +1 -0
- package/dist/credentials/jwt-introspect.d.ts +32 -0
- package/dist/credentials/jwt-introspect.d.ts.map +1 -0
- package/dist/credentials/jwt-introspect.js +64 -0
- package/dist/credentials/jwt-introspect.js.map +1 -0
- package/dist/credentials/masker.d.ts +20 -0
- package/dist/credentials/masker.d.ts.map +1 -0
- package/dist/credentials/masker.js +37 -0
- package/dist/credentials/masker.js.map +1 -0
- package/dist/credentials/profile.d.ts +83 -0
- package/dist/credentials/profile.d.ts.map +1 -0
- package/dist/credentials/profile.js +184 -0
- package/dist/credentials/profile.js.map +1 -0
- package/dist/credentials/tenant-guard.d.ts +16 -0
- package/dist/credentials/tenant-guard.d.ts.map +1 -0
- package/dist/credentials/tenant-guard.js +53 -0
- package/dist/credentials/tenant-guard.js.map +1 -0
- package/dist/credentials/tty-guard.d.ts +19 -0
- package/dist/credentials/tty-guard.d.ts.map +1 -0
- package/dist/credentials/tty-guard.js +47 -0
- package/dist/credentials/tty-guard.js.map +1 -0
- package/dist/db/apply-schema.d.ts +57 -0
- package/dist/db/apply-schema.d.ts.map +1 -0
- package/dist/db/apply-schema.js +192 -0
- package/dist/db/apply-schema.js.map +1 -0
- package/dist/db/sql-file.d.ts +49 -0
- package/dist/db/sql-file.d.ts.map +1 -0
- package/dist/db/sql-file.js +167 -0
- package/dist/db/sql-file.js.map +1 -0
- package/dist/instance/provision.d.ts +89 -0
- package/dist/instance/provision.d.ts.map +1 -0
- package/dist/instance/provision.js +139 -0
- package/dist/instance/provision.js.map +1 -0
- package/dist/instance/zero-store.d.ts +47 -0
- package/dist/instance/zero-store.d.ts.map +1 -0
- package/dist/instance/zero-store.js +91 -0
- package/dist/instance/zero-store.js.map +1 -0
- package/dist/main.d.ts +3 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +46 -0
- package/dist/main.js.map +1 -0
- package/dist/skill/assets.d.ts +38 -0
- package/dist/skill/assets.d.ts.map +1 -0
- package/dist/skill/assets.js +111 -0
- package/dist/skill/assets.js.map +1 -0
- package/dist/skill/hint.d.ts +39 -0
- package/dist/skill/hint.d.ts.map +1 -0
- package/dist/skill/hint.js +109 -0
- package/dist/skill/hint.js.map +1 -0
- package/dist/skill/hosts.d.ts +21 -0
- package/dist/skill/hosts.d.ts.map +1 -0
- package/dist/skill/hosts.js +38 -0
- package/dist/skill/hosts.js.map +1 -0
- package/dist/skill/installer.d.ts +88 -0
- package/dist/skill/installer.d.ts.map +1 -0
- package/dist/skill/installer.js +203 -0
- package/dist/skill/installer.js.map +1 -0
- package/dist/skill/ownership.d.ts +35 -0
- package/dist/skill/ownership.d.ts.map +1 -0
- package/dist/skill/ownership.js +52 -0
- package/dist/skill/ownership.js.map +1 -0
- package/package.json +63 -3
- package/skills/pxbaas-app-spec/SKILL.md +163 -0
- package/skills/pxbaas-app-spec/references/deploy-options.md +157 -0
- package/skills/pxbaas-app-spec/references/guardrails-pitfalls.md +89 -0
- package/skills/pxbaas-app-spec/references/runtime-contract.md +151 -0
- package/skills/pxbaas-app-spec/references/ssr-session.md +121 -0
- package/skills/pxbaas-app-spec/references/templates-examples.md +308 -0
- package/skills/pxbaas-cli/SKILL.md +168 -0
- package/skills/pxbaas-cli/references/api-reference.md +68 -0
- package/skills/pxbaas-cli/references/app-deploy.md +143 -0
- package/skills/pxbaas-cli/references/auth-and-credentials.md +88 -0
- package/skills/pxbaas-cli/references/data-postgrest.md +85 -0
- package/skills/pxbaas-cli/references/db-and-meta.md +85 -0
- package/skills/pxbaas-cli/references/edge-functions.md +71 -0
- package/skills/pxbaas-cli/references/multi-tenant-apps.md +107 -0
- package/skills/pxbaas-cli/references/provision.md +86 -0
- package/skills/pxbaas-cli/references/safety-and-two-phase.md +49 -0
- package/skills/pxbaas-cli/references/storage.md +61 -0
- package/skills/pxbaas-cli/references/troubleshooting.md +73 -0
- package/templates/webapp/api/index.ts +55 -0
- package/templates/webapp/csr-bff/README.md +81 -0
- package/templates/webapp/csr-bff/backend/index.ts +168 -0
- package/templates/webapp/csr-bff/db/schema.sql +69 -0
- package/templates/webapp/csr-bff/frontend/dist/admin.html +101 -0
- package/templates/webapp/csr-bff/frontend/dist/article.html +44 -0
- package/templates/webapp/csr-bff/frontend/dist/blog.js +206 -0
- package/templates/webapp/csr-bff/frontend/dist/blogger.html +53 -0
- package/templates/webapp/csr-bff/frontend/dist/index.html +70 -0
- package/templates/webapp/csr-bff/frontend/dist/login.html +56 -0
- package/templates/webapp/csr-bff/frontend/dist/profile.html +59 -0
- package/templates/webapp/csr-bff/frontend/dist/signup.html +78 -0
- package/templates/webapp/csr-bff/frontend/dist/style.css +27 -0
- package/templates/webapp/manifest.json +296 -0
- package/templates/webapp/mpa/about.html +29 -0
- package/templates/webapp/mpa/app.js +42 -0
- package/templates/webapp/mpa/index.html +42 -0
- package/templates/webapp/mpa/index.ts +60 -0
- package/templates/webapp/mpa/style.css +42 -0
- package/templates/webapp/react-vite/frontend/index.html +12 -0
- package/templates/webapp/react-vite/frontend/package-lock.json +1826 -0
- package/templates/webapp/react-vite/frontend/package.json +23 -0
- package/templates/webapp/react-vite/frontend/src/App.tsx +34 -0
- package/templates/webapp/react-vite/frontend/src/main.tsx +13 -0
- package/templates/webapp/react-vite/frontend/src/style.css +46 -0
- package/templates/webapp/react-vite/frontend/tsconfig.json +20 -0
- package/templates/webapp/react-vite/frontend/vite.config.ts +10 -0
- package/templates/webapp/react-vite-hono/backend/index.ts +21 -0
- package/templates/webapp/react-vite-hono/frontend/index.html +12 -0
- package/templates/webapp/react-vite-hono/frontend/package-lock.json +1826 -0
- package/templates/webapp/react-vite-hono/frontend/package.json +23 -0
- package/templates/webapp/react-vite-hono/frontend/src/App.tsx +47 -0
- package/templates/webapp/react-vite-hono/frontend/src/main.tsx +13 -0
- package/templates/webapp/react-vite-hono/frontend/src/style.css +66 -0
- package/templates/webapp/react-vite-hono/frontend/tsconfig.json +20 -0
- package/templates/webapp/react-vite-hono/frontend/vite.config.ts +10 -0
- package/templates/webapp/spa/index.ts +119 -0
- package/templates/webapp/ssr-auth/backend/index.ts +133 -0
- package/templates/webapp/ssr-auth/frontend/dist/index.html +15 -0
- package/templates/webapp/ssr-blog/README.md +111 -0
- package/templates/webapp/ssr-blog/backend/index.ts +764 -0
- package/templates/webapp/ssr-blog/db/schema.sql +71 -0
- package/templates/webapp/ssr-blog/frontend/dist/index.html +39 -0
- package/templates/webapp/ssr-frameworks/README.md +64 -0
- package/templates/webapp/ssr-frameworks/backend/index.ts +241 -0
- package/templates/webapp/ssr-frameworks/frontend/dist/index.html +31 -0
- package/templates/webapp/ssr-hono/backend/index.ts +92 -0
- package/templates/webapp/ssr-hono/frontend/dist/index.html +12 -0
- package/templates/webapp/ssr-shop/README.md +80 -0
- package/templates/webapp/ssr-shop/backend/index.ts +491 -0
- package/templates/webapp/ssr-shop/db/schema.sql +120 -0
- package/templates/webapp/ssr-shop/frontend/dist/index.html +12 -0
- package/templates/webapp/vanilla-static/frontend/app.js +6 -0
- package/templates/webapp/vanilla-static/frontend/index.html +19 -0
- package/templates/webapp/vanilla-static/frontend/style.css +43 -0
- package/README.md +0 -3
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>博客广场 — MPA Blog</title>
|
|
7
|
+
<link rel="stylesheet" href="./style.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<h1>📚 博客广场</h1>
|
|
11
|
+
<nav>
|
|
12
|
+
<a href="./index.html">广场</a>
|
|
13
|
+
<a href="./admin.html" id="nav-admin" style="display:none">博主管理</a>
|
|
14
|
+
<a href="./profile.html" id="nav-profile" style="display:none">我的资料</a>
|
|
15
|
+
<span class="spacer"></span>
|
|
16
|
+
<span id="authbox" class="muted"></span>
|
|
17
|
+
</nav>
|
|
18
|
+
<div id="banner"></div>
|
|
19
|
+
<div class="muted" style="margin-bottom:8px">按博主展示:每位博主的最新 1~2 篇文章;点「更多文章」进入该博主主页(只看他/她的文章)。游客仅可浏览。</div>
|
|
20
|
+
<div id="list"><div class="muted">加载中…</div></div>
|
|
21
|
+
<script src="./blog.js"></script>
|
|
22
|
+
<script>
|
|
23
|
+
BLOG.init().then(function () {
|
|
24
|
+
if (BLOG.isSharedView()) {
|
|
25
|
+
document.getElementById('banner').innerHTML =
|
|
26
|
+
'<div class="banner">当前为分享只读视图:可浏览已发布文章;登录与管理请走函数原始 URL。</div>';
|
|
27
|
+
}
|
|
28
|
+
return BLOG.me();
|
|
29
|
+
}).then(function (m) {
|
|
30
|
+
var box = document.getElementById('authbox');
|
|
31
|
+
if (m.user) {
|
|
32
|
+
document.getElementById('nav-admin').style.display = '';
|
|
33
|
+
document.getElementById('nav-profile').style.display = '';
|
|
34
|
+
box.innerHTML = BLOG.esc(m.user.email) + ' · <a href="#" id="logout">退出</a>';
|
|
35
|
+
document.getElementById('logout').onclick = function (e) {
|
|
36
|
+
e.preventDefault(); BLOG.clearToken(); BLOG.setMeta({}); location.href = './index.html';
|
|
37
|
+
};
|
|
38
|
+
} else if (!BLOG.isSharedView()) {
|
|
39
|
+
box.innerHTML = '<a href="./login.html">登录</a> · <a href="./signup.html">注册</a>';
|
|
40
|
+
} else {
|
|
41
|
+
// 只读分享视图:仍给出相对路径的登录/注册入口(在 mount 前缀下同样有效)。
|
|
42
|
+
box.innerHTML = '<span class="muted">游客</span> · <a href="./login.html">登录</a> · <a href="./signup.html">注册</a>';
|
|
43
|
+
}
|
|
44
|
+
return BLOG.loadPosts();
|
|
45
|
+
}).then(function (rows) {
|
|
46
|
+
var el = document.getElementById('list');
|
|
47
|
+
// 广场为纯公开视图:只展示已发布文章(草稿仅作者本人在管理页可见)。
|
|
48
|
+
var published = (rows || []).filter(function (p) { return p.status === 'published'; });
|
|
49
|
+
var groups = BLOG.groupByAuthor(published);
|
|
50
|
+
if (!groups.length) { el.innerHTML = '<div class="muted">广场暂无已发布文章。</div>'; return; }
|
|
51
|
+
el.innerHTML = groups.map(function (g) {
|
|
52
|
+
var name = g.author_name || '匿名博主';
|
|
53
|
+
var latest = g.posts.slice(0, 2).map(function (p) {
|
|
54
|
+
return '<li><a href="./article.html#id=' + encodeURIComponent(p.id) + '">' + BLOG.esc(p.title) + '</a>' +
|
|
55
|
+
' <span class="muted">' + BLOG.esc(BLOG.fmtDate(p.created_at)) + '</span></li>';
|
|
56
|
+
}).join('');
|
|
57
|
+
var more = g.count > 2 ? '<span class="muted">共 ' + g.count + ' 篇(只显示最新 2 篇)</span>' : '<span class="muted">' + g.count + ' 篇</span>';
|
|
58
|
+
return '<div class="card blogger">' +
|
|
59
|
+
'<div class="row"><h2>👤 ' + BLOG.esc(name) + '</h2>' +
|
|
60
|
+
'<a class="more" href="./blogger.html#id=' + encodeURIComponent(g.author_id) + '">更多文章 →</a></div>' +
|
|
61
|
+
'<div class="muted" style="margin:2px 0 8px">' + more + '</div>' +
|
|
62
|
+
'<ul class="postlist">' + latest + '</ul>' +
|
|
63
|
+
'</div>';
|
|
64
|
+
}).join('');
|
|
65
|
+
}).catch(function (e) {
|
|
66
|
+
document.getElementById('list').innerHTML = '<div class="error">加载失败: ' + BLOG.esc(e) + '</div>';
|
|
67
|
+
});
|
|
68
|
+
</script>
|
|
69
|
+
</body>
|
|
70
|
+
</html>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>MPA Blog — 博主登录</title>
|
|
7
|
+
<link rel="stylesheet" href="./style.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<nav><a href="./index.html">← 返回广场</a></nav>
|
|
11
|
+
<h1>博主登录</h1>
|
|
12
|
+
<div class="card">
|
|
13
|
+
<input id="email" type="email" placeholder="邮箱" autocomplete="username">
|
|
14
|
+
<input id="password" type="password" placeholder="密码" autocomplete="current-password">
|
|
15
|
+
<div class="row">
|
|
16
|
+
<button id="login">登录</button>
|
|
17
|
+
<span class="muted">还没有账号?<a href="./signup.html">注册新博主</a></span>
|
|
18
|
+
</div>
|
|
19
|
+
<div id="msg" class="muted"></div>
|
|
20
|
+
</div>
|
|
21
|
+
<script src="./blog.js"></script>
|
|
22
|
+
<script>
|
|
23
|
+
var msg = document.getElementById('msg');
|
|
24
|
+
var btn = document.getElementById('login');
|
|
25
|
+
var emailEl = document.getElementById('email');
|
|
26
|
+
var pwEl = document.getElementById('password');
|
|
27
|
+
|
|
28
|
+
function doLogin() {
|
|
29
|
+
if (btn.disabled) return;
|
|
30
|
+
var email = emailEl.value.trim();
|
|
31
|
+
if (!email || !pwEl.value) { msg.className = 'error'; msg.textContent = '请填写邮箱和密码'; return; }
|
|
32
|
+
msg.className = 'muted'; msg.textContent = '登录中…';
|
|
33
|
+
btn.disabled = true;
|
|
34
|
+
BLOG.login(email, pwEl.value).then(function (r) {
|
|
35
|
+
return r.json().catch(function () { return {}; }).then(function (j) {
|
|
36
|
+
if (!r.ok || !j.access_token) {
|
|
37
|
+
msg.className = 'error'; msg.textContent = BLOG.authError(j, r.status); btn.disabled = false; return;
|
|
38
|
+
}
|
|
39
|
+
BLOG.setToken(j.access_token);
|
|
40
|
+
BLOG.setMeta((j.user && j.user.user_metadata) || {});
|
|
41
|
+
location.href = './admin.html';
|
|
42
|
+
});
|
|
43
|
+
}).catch(function (e) { msg.className = 'error'; msg.textContent = String(e); btn.disabled = false; });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
BLOG.init().then(function () { return BLOG.me(); }).then(function (m) {
|
|
47
|
+
if (m && m.user) { location.replace('./admin.html'); return; }
|
|
48
|
+
btn.onclick = doLogin;
|
|
49
|
+
[emailEl, pwEl].forEach(function (el) {
|
|
50
|
+
el.addEventListener('keydown', function (ev) { if (ev.key === 'Enter') { ev.preventDefault(); doLogin(); } });
|
|
51
|
+
});
|
|
52
|
+
emailEl.focus();
|
|
53
|
+
});
|
|
54
|
+
</script>
|
|
55
|
+
</body>
|
|
56
|
+
</html>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>MPA Blog — 我的资料</title>
|
|
7
|
+
<link rel="stylesheet" href="./style.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<nav><a href="./index.html">← 返回广场</a><a href="./admin.html">博主管理</a></nav>
|
|
11
|
+
<div id="gate"></div>
|
|
12
|
+
<div id="box" style="display:none">
|
|
13
|
+
<h1>我的资料</h1>
|
|
14
|
+
<div class="card">
|
|
15
|
+
<div class="muted">邮箱</div>
|
|
16
|
+
<div id="email" style="font-size:16px;margin:4px 0 12px"></div>
|
|
17
|
+
<div class="muted">注册时间</div>
|
|
18
|
+
<div id="created" style="margin:4px 0 12px"></div>
|
|
19
|
+
<div class="muted">展示名(发文署名)</div>
|
|
20
|
+
<input id="displayName" placeholder="展示名,留空则用邮箱前缀" maxlength="40">
|
|
21
|
+
<button id="save">保存资料</button>
|
|
22
|
+
<div id="msg" class="muted"></div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
<script src="./blog.js"></script>
|
|
26
|
+
<script>
|
|
27
|
+
var gate = document.getElementById('gate');
|
|
28
|
+
BLOG.init().then(function () {
|
|
29
|
+
if (BLOG.isSharedView()) {
|
|
30
|
+
gate.innerHTML = '<div class="banner">分享只读视图:资料管理已禁用。请通过函数原始 URL 打开本页。</div>';
|
|
31
|
+
return Promise.reject(new Error('shared-view'));
|
|
32
|
+
}
|
|
33
|
+
return BLOG.me().then(function (m) {
|
|
34
|
+
if (!m.user) { location.replace('./login.html'); return Promise.reject(new Error('anon')); }
|
|
35
|
+
return BLOG.getProfile().then(function (u) {
|
|
36
|
+
document.getElementById('box').style.display = '';
|
|
37
|
+
document.getElementById('email').textContent = u.email || m.user.email || '';
|
|
38
|
+
document.getElementById('created').textContent = BLOG.fmtDate(u.created_at);
|
|
39
|
+
var meta = u.user_metadata || {};
|
|
40
|
+
document.getElementById('displayName').value = meta.display_name || '';
|
|
41
|
+
BLOG.setMeta(meta);
|
|
42
|
+
document.getElementById('save').onclick = function () {
|
|
43
|
+
var msg = document.getElementById('msg');
|
|
44
|
+
var name = document.getElementById('displayName').value.trim();
|
|
45
|
+
BLOG.updateProfile({ display_name: name }).then(function (u2) {
|
|
46
|
+
BLOG.setMeta(u2.user_metadata || { display_name: name });
|
|
47
|
+
msg.className = 'muted'; msg.textContent = '已保存;新发文章将使用该署名。';
|
|
48
|
+
}).catch(function (e) { msg.className = 'error'; msg.textContent = String(e); });
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}).catch(function (e) {
|
|
53
|
+
if (String(e) !== 'Error: shared-view' && String(e) !== 'Error: anon') {
|
|
54
|
+
gate.innerHTML = '<div class="error">加载失败: ' + BLOG.esc(e) + '</div>';
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
</script>
|
|
58
|
+
</body>
|
|
59
|
+
</html>
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>MPA Blog — 注册新博主</title>
|
|
7
|
+
<link rel="stylesheet" href="./style.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<nav><a href="./index.html">← 返回广场</a></nav>
|
|
11
|
+
<h1>注册新博主</h1>
|
|
12
|
+
<div class="card">
|
|
13
|
+
<input id="email" type="email" placeholder="邮箱" autocomplete="username">
|
|
14
|
+
<input id="password" type="password" placeholder="密码(≥6 位)" autocomplete="new-password">
|
|
15
|
+
<input id="password2" type="password" placeholder="确认密码" autocomplete="new-password">
|
|
16
|
+
<div class="row">
|
|
17
|
+
<button id="signup">注册</button>
|
|
18
|
+
<span class="muted">已有账号?<a href="./login.html">去登录</a></span>
|
|
19
|
+
</div>
|
|
20
|
+
<div id="msg" class="muted"></div>
|
|
21
|
+
</div>
|
|
22
|
+
<script src="./blog.js"></script>
|
|
23
|
+
<script>
|
|
24
|
+
var msg = document.getElementById('msg');
|
|
25
|
+
var btn = document.getElementById('signup');
|
|
26
|
+
var emailEl = document.getElementById('email');
|
|
27
|
+
var pwEl = document.getElementById('password');
|
|
28
|
+
var pw2El = document.getElementById('password2');
|
|
29
|
+
|
|
30
|
+
function finish(j) {
|
|
31
|
+
BLOG.setToken(j.access_token);
|
|
32
|
+
BLOG.setMeta((j.user && j.user.user_metadata) || {});
|
|
33
|
+
location.href = './profile.html';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function doSignup() {
|
|
37
|
+
if (btn.disabled) return;
|
|
38
|
+
var email = emailEl.value.trim();
|
|
39
|
+
var pw = pwEl.value;
|
|
40
|
+
var pw2 = pw2El.value;
|
|
41
|
+
msg.className = 'muted';
|
|
42
|
+
if (!email || pw.length < 6) { msg.className = 'error'; msg.textContent = '邮箱必填、密码至少 6 位'; return; }
|
|
43
|
+
if (pw !== pw2) { msg.className = 'error'; msg.textContent = '两次密码不一致'; return; }
|
|
44
|
+
msg.textContent = '注册中…';
|
|
45
|
+
btn.disabled = true;
|
|
46
|
+
BLOG.signup(email, pw).then(function (r) {
|
|
47
|
+
return r.json().catch(function () { return {}; }).then(function (j) {
|
|
48
|
+
if ((!r.ok && r.status !== 201)) {
|
|
49
|
+
msg.className = 'error'; msg.textContent = BLOG.authError(j, r.status); btn.disabled = false; return;
|
|
50
|
+
}
|
|
51
|
+
// 免邮箱验证时 signup 直接返回 access_token;否则回退到登录。
|
|
52
|
+
if (j.access_token) { finish(j); return; }
|
|
53
|
+
msg.textContent = '注册成功,正在登录…';
|
|
54
|
+
return BLOG.login(email, pw).then(function (r2) {
|
|
55
|
+
return r2.json().catch(function () { return {}; }).then(function (j2) {
|
|
56
|
+
if (!r2.ok || !j2.access_token) {
|
|
57
|
+
msg.className = 'error';
|
|
58
|
+
msg.textContent = '注册成功但自动登录失败:' + BLOG.authError(j2, r2.status) + ',请前往登录页重试。';
|
|
59
|
+
btn.disabled = false; return;
|
|
60
|
+
}
|
|
61
|
+
finish(j2);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}).catch(function (e) { msg.className = 'error'; msg.textContent = String(e); btn.disabled = false; });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
BLOG.init().then(function () { return BLOG.me(); }).then(function (m) {
|
|
69
|
+
if (m && m.user) { location.replace('./admin.html'); return; }
|
|
70
|
+
btn.onclick = doSignup;
|
|
71
|
+
[emailEl, pwEl, pw2El].forEach(function (el) {
|
|
72
|
+
el.addEventListener('keydown', function (ev) { if (ev.key === 'Enter') { ev.preventDefault(); doSignup(); } });
|
|
73
|
+
});
|
|
74
|
+
emailEl.focus();
|
|
75
|
+
});
|
|
76
|
+
</script>
|
|
77
|
+
</body>
|
|
78
|
+
</html>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
* { box-sizing: border-box; }
|
|
2
|
+
body { margin: 0 auto; max-width: 780px; padding: 24px; font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif; background: #0f1115; color: #e6e6e6; }
|
|
3
|
+
h1 { font-size: 24px; }
|
|
4
|
+
h2 { font-size: 20px; }
|
|
5
|
+
a { color: #7fb9ff; text-decoration: none; }
|
|
6
|
+
a:hover { text-decoration: underline; }
|
|
7
|
+
nav { margin: 12px 0 20px; display: flex; gap: 16px; align-items: center; }
|
|
8
|
+
nav .spacer { flex: 1; }
|
|
9
|
+
.muted { color: #9aa0a6; font-size: 13px; }
|
|
10
|
+
.card { background: #1a1d24; border: 1px solid #2a2e37; border-radius: 10px; padding: 16px; margin: 12px 0; }
|
|
11
|
+
.row { display: flex; justify-content: space-between; align-items: center; gap: 12px; }
|
|
12
|
+
.badge { font-size: 12px; padding: 2px 8px; border-radius: 999px; border: 1px solid #2a2e37; }
|
|
13
|
+
.badge.draft { color: #e6c07b; }
|
|
14
|
+
.badge.published { color: #98c379; }
|
|
15
|
+
p.content { white-space: pre-wrap; line-height: 1.7; margin: 8px 0; }
|
|
16
|
+
input, textarea, select { width: 100%; background: #0f1115; color: #e6e6e6; border: 1px solid #2a2e37; border-radius: 8px; padding: 10px; font: inherit; margin: 6px 0; }
|
|
17
|
+
button { background: #2f6fed; color: #fff; border: 0; border-radius: 8px; padding: 9px 16px; font: inherit; cursor: pointer; }
|
|
18
|
+
button:disabled { opacity: .6; cursor: not-allowed; }
|
|
19
|
+
button.danger { background: #b3261e; padding: 6px 12px; font-size: 13px; }
|
|
20
|
+
button.ghost { background: #2a2e37; padding: 6px 12px; font-size: 13px; }
|
|
21
|
+
.banner { background: #3a2f14; border: 1px solid #7a6122; color: #ffd97a; border-radius: 10px; padding: 10px 14px; margin: 12px 0; font-size: 14px; }
|
|
22
|
+
.error { color: #ff8a80; font-size: 14px; margin: 8px 0; }
|
|
23
|
+
.card.blogger h2 { font-size: 18px; margin: 0; }
|
|
24
|
+
a.more { white-space: nowrap; font-size: 14px; }
|
|
25
|
+
ul.postlist { list-style: none; padding: 0; margin: 0; }
|
|
26
|
+
ul.postlist li { padding: 6px 0; border-top: 1px solid #2a2e37; }
|
|
27
|
+
ul.postlist li:first-child { border-top: 0; }
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"templates": [
|
|
4
|
+
{
|
|
5
|
+
"id": "spa",
|
|
6
|
+
"description": "Self-contained single-page webapp: inline HTML/CSS/JS + a built-in JSON API via ?action=. Best for shareable demos.",
|
|
7
|
+
"entrypoint": "index.ts"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": "api",
|
|
11
|
+
"description": "Headless JSON backend (Deno.serve) with ?action= routing and CORS. Best for webhooks / API-only demos.",
|
|
12
|
+
"entrypoint": "index.ts"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"id": "mpa",
|
|
16
|
+
"description": "Multi-page static webapp: real index.html/about.html/style.css/app.js served by path via the proxy static branch, plus a worker JSON API via ?action=. Best for traditional multi-page sites.",
|
|
17
|
+
"entrypoint": "index.ts",
|
|
18
|
+
"mode": "mpa",
|
|
19
|
+
"files": [
|
|
20
|
+
"index.ts",
|
|
21
|
+
"index.html",
|
|
22
|
+
"about.html",
|
|
23
|
+
"style.css",
|
|
24
|
+
"app.js"
|
|
25
|
+
],
|
|
26
|
+
"pages": [
|
|
27
|
+
"index.html",
|
|
28
|
+
"about.html"
|
|
29
|
+
],
|
|
30
|
+
"assets": [
|
|
31
|
+
"style.css",
|
|
32
|
+
"app.js"
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"id": "vanilla-static",
|
|
37
|
+
"description": "Minimal text-only static app for validating the /apps mount and release lifecycle.",
|
|
38
|
+
"entrypoint": "frontend/index.html",
|
|
39
|
+
"mode": "webapp",
|
|
40
|
+
"files": [
|
|
41
|
+
"frontend/index.html",
|
|
42
|
+
"frontend/style.css",
|
|
43
|
+
"frontend/app.js"
|
|
44
|
+
],
|
|
45
|
+
"frontend": {
|
|
46
|
+
"root": "frontend/dist",
|
|
47
|
+
"routing": "files",
|
|
48
|
+
"index": "index.html",
|
|
49
|
+
"fallback": null
|
|
50
|
+
},
|
|
51
|
+
"backend": null
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"id": "react-vite",
|
|
55
|
+
"description": "React + Vite static app with HashRouter, relative base, plain CSS, and exact dependency versions.",
|
|
56
|
+
"entrypoint": "frontend/src/App.tsx",
|
|
57
|
+
"mode": "webapp",
|
|
58
|
+
"files": [
|
|
59
|
+
"frontend/package.json",
|
|
60
|
+
"frontend/package-lock.json",
|
|
61
|
+
"frontend/index.html",
|
|
62
|
+
"frontend/tsconfig.json",
|
|
63
|
+
"frontend/vite.config.ts",
|
|
64
|
+
"frontend/src/main.tsx",
|
|
65
|
+
"frontend/src/App.tsx",
|
|
66
|
+
"frontend/src/style.css"
|
|
67
|
+
],
|
|
68
|
+
"frontend": {
|
|
69
|
+
"root": "frontend/dist",
|
|
70
|
+
"routing": "files",
|
|
71
|
+
"index": "index.html",
|
|
72
|
+
"fallback": null
|
|
73
|
+
},
|
|
74
|
+
"backend": null
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"id": "react-vite-hono",
|
|
78
|
+
"description": "React + Vite + HashRouter frontend with a Deno-served, platform-versioned Hono backend.",
|
|
79
|
+
"entrypoint": "backend/index.ts",
|
|
80
|
+
"mode": "webapp",
|
|
81
|
+
"files": [
|
|
82
|
+
"frontend/package.json",
|
|
83
|
+
"frontend/package-lock.json",
|
|
84
|
+
"frontend/index.html",
|
|
85
|
+
"frontend/tsconfig.json",
|
|
86
|
+
"frontend/vite.config.ts",
|
|
87
|
+
"frontend/src/main.tsx",
|
|
88
|
+
"frontend/src/App.tsx",
|
|
89
|
+
"frontend/src/style.css",
|
|
90
|
+
"backend/index.ts"
|
|
91
|
+
],
|
|
92
|
+
"frontend": {
|
|
93
|
+
"root": "frontend/dist",
|
|
94
|
+
"routing": "files",
|
|
95
|
+
"index": "index.html",
|
|
96
|
+
"fallback": null
|
|
97
|
+
},
|
|
98
|
+
"backend": {
|
|
99
|
+
"entrypoint": "backend/index.ts",
|
|
100
|
+
"framework": "hono",
|
|
101
|
+
"pathMode": "strip-app-api-prefix",
|
|
102
|
+
"verifyJwt": false,
|
|
103
|
+
"secrets": [],
|
|
104
|
+
"egressPolicy": "deny"
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"id": "ssr-hono",
|
|
109
|
+
"description": "SSR-shell webapp: Hono worker server-renders the page shell at /page (static-miss fallthrough, fallback=worker); browser hydrates and fetches data client-side. Single-file self-contained worker per D16.",
|
|
110
|
+
"entrypoint": "backend/index.ts",
|
|
111
|
+
"mode": "webapp",
|
|
112
|
+
"files": [
|
|
113
|
+
"backend/index.ts",
|
|
114
|
+
"frontend/dist/index.html"
|
|
115
|
+
],
|
|
116
|
+
"frontend": {
|
|
117
|
+
"root": "frontend/dist",
|
|
118
|
+
"routing": "files",
|
|
119
|
+
"index": "index.html",
|
|
120
|
+
"fallback": "worker"
|
|
121
|
+
},
|
|
122
|
+
"backend": {
|
|
123
|
+
"entrypoint": "backend/index.ts",
|
|
124
|
+
"framework": "hono",
|
|
125
|
+
"pathMode": "strip-app-api-prefix",
|
|
126
|
+
"verifyJwt": false,
|
|
127
|
+
"secrets": [],
|
|
128
|
+
"egressPolicy": "deny"
|
|
129
|
+
},
|
|
130
|
+
"ssrProbePath": "/page"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"id": "ssr-auth",
|
|
134
|
+
"description": "SSR webapp with proxy-injected session (方案 C): the worker server-renders authenticated pages from trusted X-App-User-* headers; it never reads cookies, never sets Set-Cookie, and never puts tokens in URLs. Requires capabilities.sessionMode=proxy-injected-header.",
|
|
135
|
+
"entrypoint": "backend/index.ts",
|
|
136
|
+
"mode": "webapp",
|
|
137
|
+
"files": [
|
|
138
|
+
"backend/index.ts",
|
|
139
|
+
"frontend/dist/index.html"
|
|
140
|
+
],
|
|
141
|
+
"frontend": {
|
|
142
|
+
"root": "frontend/dist",
|
|
143
|
+
"routing": "files",
|
|
144
|
+
"index": "index.html",
|
|
145
|
+
"fallback": null
|
|
146
|
+
},
|
|
147
|
+
"backend": {
|
|
148
|
+
"entrypoint": "backend/index.ts",
|
|
149
|
+
"framework": "deno",
|
|
150
|
+
"pathMode": "strip-app-api-prefix",
|
|
151
|
+
"verifyJwt": false,
|
|
152
|
+
"secrets": [],
|
|
153
|
+
"egressPolicy": "deny",
|
|
154
|
+
"session": "proxy-injected"
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"id": "ssr-blog",
|
|
159
|
+
"description": "Full-site SSR blog (zero npm deps) with proxy-injected session: the worker renders every page server-side from the instance internal base and reads identity ONLY from X-App-User-* headers. Requires capabilities.sessionMode=proxy-injected-header.",
|
|
160
|
+
"entrypoint": "backend/index.ts",
|
|
161
|
+
"mode": "webapp",
|
|
162
|
+
"files": [
|
|
163
|
+
"README.md",
|
|
164
|
+
"backend/index.ts",
|
|
165
|
+
"db/schema.sql",
|
|
166
|
+
"frontend/dist/index.html"
|
|
167
|
+
],
|
|
168
|
+
"frontend": {
|
|
169
|
+
"root": "frontend/dist",
|
|
170
|
+
"routing": "files",
|
|
171
|
+
"index": "index.html",
|
|
172
|
+
"fallback": "worker"
|
|
173
|
+
},
|
|
174
|
+
"backend": {
|
|
175
|
+
"entrypoint": "backend/index.ts",
|
|
176
|
+
"framework": "deno",
|
|
177
|
+
"pathMode": "strip-app-api-prefix",
|
|
178
|
+
"verifyJwt": false,
|
|
179
|
+
"secrets": [],
|
|
180
|
+
"egressPolicy": "deny",
|
|
181
|
+
"session": "proxy-injected"
|
|
182
|
+
},
|
|
183
|
+
"ssrProbePath": "/posts",
|
|
184
|
+
"nextSteps": [
|
|
185
|
+
"Initialize the database BEFORE deploy: run `pxbaas app db-init <name> --dir <appsDir>` (single-tenant). It reads db/schema.sql, substitutes __SCHEMA__=public, shows the full DDL + RLS + seed plan, and applies it only after you authorize (--dry-run to preview, --yes for non-interactive). The CLI never auto-runs DDL. Multi-tenant: use POST /pg/query with the global service_role key and tenant_<id>.<table> names.",
|
|
186
|
+
"Requires capabilities.sessionMode=proxy-injected-header.",
|
|
187
|
+
"Data-plane base and anon key come from the platform-in SUPABASE_INTERNAL_URL / SUPABASE_ANON_KEY (edge-runtime MR #30497903); no plaintext key editing is needed."
|
|
188
|
+
]
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
"id": "csr-bff",
|
|
192
|
+
"description": "Front/back separated CSR blog: real static pages served by path plus a same-origin Deno BFF worker that proxies /api/* to the instance internal REST/Auth. Identity comes from proxy-injected X-App-User-Token (client Authorization is stripped on /apps).",
|
|
193
|
+
"entrypoint": "backend/index.ts",
|
|
194
|
+
"mode": "webapp",
|
|
195
|
+
"files": [
|
|
196
|
+
"README.md",
|
|
197
|
+
"backend/index.ts",
|
|
198
|
+
"db/schema.sql",
|
|
199
|
+
"frontend/dist/admin.html",
|
|
200
|
+
"frontend/dist/article.html",
|
|
201
|
+
"frontend/dist/blog.js",
|
|
202
|
+
"frontend/dist/blogger.html",
|
|
203
|
+
"frontend/dist/index.html",
|
|
204
|
+
"frontend/dist/login.html",
|
|
205
|
+
"frontend/dist/profile.html",
|
|
206
|
+
"frontend/dist/signup.html",
|
|
207
|
+
"frontend/dist/style.css"
|
|
208
|
+
],
|
|
209
|
+
"frontend": {
|
|
210
|
+
"root": "frontend/dist",
|
|
211
|
+
"routing": "files",
|
|
212
|
+
"index": "index.html",
|
|
213
|
+
"fallback": null
|
|
214
|
+
},
|
|
215
|
+
"backend": {
|
|
216
|
+
"entrypoint": "backend/index.ts",
|
|
217
|
+
"framework": "deno",
|
|
218
|
+
"pathMode": "strip-app-api-prefix",
|
|
219
|
+
"verifyJwt": false,
|
|
220
|
+
"secrets": [],
|
|
221
|
+
"egressPolicy": "deny",
|
|
222
|
+
"session": "proxy-injected"
|
|
223
|
+
},
|
|
224
|
+
"nextSteps": [
|
|
225
|
+
"Initialize the database BEFORE deploy: run `pxbaas app db-init <name> --dir <appsDir>` (single-tenant). It reads db/schema.sql, substitutes __SCHEMA__=public, shows the full DDL + RLS + seed plan, and applies it only after you authorize (--dry-run to preview, --yes for non-interactive). The CLI never auto-runs DDL. Multi-tenant: use POST /pg/query with the global service_role key and tenant_<id>.<table> names.",
|
|
226
|
+
"Requires capabilities.sessionMode=proxy-injected-header.",
|
|
227
|
+
"The BFF forwards to the platform-injected SUPABASE_INTERNAL_URL with SUPABASE_ANON_KEY (edge-runtime MR #30497903); no plaintext key editing is needed."
|
|
228
|
+
]
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
"id": "ssr-shop",
|
|
232
|
+
"description": "React 18 + Hono SSR shop: catalog/detail/search are fully server-rendered from MySQL; cart/checkout/orders hydrate in the browser. Browser-side session (localStorage + Authorization direct to /rest,/auth); order gating enforced by RLS.",
|
|
233
|
+
"entrypoint": "backend/index.ts",
|
|
234
|
+
"mode": "webapp",
|
|
235
|
+
"files": [
|
|
236
|
+
"README.md",
|
|
237
|
+
"backend/index.ts",
|
|
238
|
+
"db/schema.sql",
|
|
239
|
+
"frontend/dist/index.html"
|
|
240
|
+
],
|
|
241
|
+
"frontend": {
|
|
242
|
+
"root": "frontend/dist",
|
|
243
|
+
"routing": "files",
|
|
244
|
+
"index": "index.html",
|
|
245
|
+
"fallback": "worker"
|
|
246
|
+
},
|
|
247
|
+
"backend": {
|
|
248
|
+
"entrypoint": "backend/index.ts",
|
|
249
|
+
"framework": "hono",
|
|
250
|
+
"pathMode": "strip-app-api-prefix",
|
|
251
|
+
"verifyJwt": false,
|
|
252
|
+
"secrets": [],
|
|
253
|
+
"egressPolicy": "deny",
|
|
254
|
+
"session": "browser-side"
|
|
255
|
+
},
|
|
256
|
+
"ssrProbePath": "/shop",
|
|
257
|
+
"nextSteps": [
|
|
258
|
+
"Initialize the database BEFORE deploy: run `pxbaas app db-init <name> --dir <appsDir>` (single-tenant). It reads db/schema.sql, substitutes __SCHEMA__=public, shows the full DDL + RLS (products public-read; orders and order_items are owner-gated by buyer_email = jwt.email) + seed plan, and applies it only after you authorize (--dry-run to preview, --yes for non-interactive). The CLI never auto-runs DDL. Multi-tenant: use POST /pg/query with the global service_role key and tenant_<id>.<table> names.",
|
|
259
|
+
"Create the admin account (admin@shop.demo) via GoTrue signup if the admin whitelist is needed.",
|
|
260
|
+
"Data-plane base and anon key come from the platform-injected SUPABASE_INTERNAL_URL / SUPABASE_ANON_KEY (edge-runtime MR #30497903); no plaintext key editing is needed. Requires the instance to report react/react-dom in capabilities.ssrLibs."
|
|
261
|
+
]
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
"id": "ssr-frameworks",
|
|
265
|
+
"description": "Four-framework SSR showcase (React / Vue / Preact / Solid on Hono) rendering the same UI spec server-side, plus /compare. No data plane and no credentials; heaviest cold start of the set (~60s), so warm the tenant sandbox before publish.",
|
|
266
|
+
"entrypoint": "backend/index.ts",
|
|
267
|
+
"mode": "webapp",
|
|
268
|
+
"files": [
|
|
269
|
+
"README.md",
|
|
270
|
+
"backend/index.ts",
|
|
271
|
+
"frontend/dist/index.html"
|
|
272
|
+
],
|
|
273
|
+
"frontend": {
|
|
274
|
+
"root": "frontend/dist",
|
|
275
|
+
"routing": "files",
|
|
276
|
+
"index": "index.html",
|
|
277
|
+
"fallback": "worker"
|
|
278
|
+
},
|
|
279
|
+
"backend": {
|
|
280
|
+
"entrypoint": "backend/index.ts",
|
|
281
|
+
"framework": "hono",
|
|
282
|
+
"pathMode": "strip-app-api-prefix",
|
|
283
|
+
"verifyJwt": false,
|
|
284
|
+
"secrets": [],
|
|
285
|
+
"egressPolicy": "deny",
|
|
286
|
+
"session": "none"
|
|
287
|
+
},
|
|
288
|
+
"ssrProbePath": "/react",
|
|
289
|
+
"nextSteps": [
|
|
290
|
+
"No data plane and no credentials are required.",
|
|
291
|
+
"Heaviest cold start of the set (~60s with four SSR renderers): warm the tenant sandbox before publish and retry APP_SSR_PROBE_FAILED 3-5 times.",
|
|
292
|
+
"Requires the instance to report react/react-dom, vue/@vue/server-renderer, preact/preact-render-to-string and solid-js in capabilities.ssrLibs, else deploy is blocked by no-remote-import / ssr-lib-not-allowed."
|
|
293
|
+
]
|
|
294
|
+
}
|
|
295
|
+
]
|
|
296
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
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" />
|
|
6
|
+
<title>{{APP_NAME}} — About</title>
|
|
7
|
+
<link rel="stylesheet" href="style.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<h1>About {{APP_NAME}}</h1>
|
|
11
|
+
<nav class="nav">
|
|
12
|
+
<a href="index.html">Home</a> · <a href="about.html">About</a>
|
|
13
|
+
</nav>
|
|
14
|
+
|
|
15
|
+
<div class="card">
|
|
16
|
+
<p>
|
|
17
|
+
This is a second static page (<code>about.html</code>). Navigating here from
|
|
18
|
+
the home page uses a plain relative link — no client-side router, no
|
|
19
|
+
single-file bundle. Each page is a real file served by path.
|
|
20
|
+
</p>
|
|
21
|
+
<p class="muted">
|
|
22
|
+
Add more pages by dropping new <code>.html</code> files into the app
|
|
23
|
+
directory and listing them under <code>pages</code> in <code>app.json</code>.
|
|
24
|
+
</p>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<script src="app.js"></script>
|
|
28
|
+
</body>
|
|
29
|
+
</html>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// {{APP_NAME}} — shared client script (MPA). Runs in the BROWSER on every page.
|
|
2
|
+
//
|
|
3
|
+
// Guardrails:
|
|
4
|
+
// - Asset links in the HTML are relative; here the API call uses a COMPUTED base
|
|
5
|
+
// (the function directory) + "?action=..." so the request returns through
|
|
6
|
+
// Kong -> proxy -> sandbox on the same /functions/v1/{{SLUG}}/ path and never
|
|
7
|
+
// escapes the prefix.
|
|
8
|
+
// - Defensive element lookups: the same script is included on every page, and
|
|
9
|
+
// not every page has every widget.
|
|
10
|
+
(function () {
|
|
11
|
+
// Directory of the current page, e.g. /functions/v1/{{SLUG}}/
|
|
12
|
+
var base = location.pathname.endsWith("/")
|
|
13
|
+
? location.pathname
|
|
14
|
+
: location.pathname.replace(/\/[^/]*$/, "/");
|
|
15
|
+
|
|
16
|
+
var urlEl = document.getElementById("url");
|
|
17
|
+
if (urlEl) urlEl.textContent = location.origin + base;
|
|
18
|
+
|
|
19
|
+
var n = 0;
|
|
20
|
+
var countEl = document.getElementById("count");
|
|
21
|
+
var incBtn = document.getElementById("inc");
|
|
22
|
+
if (incBtn) {
|
|
23
|
+
incBtn.addEventListener("click", function () {
|
|
24
|
+
n++;
|
|
25
|
+
if (countEl) countEl.textContent = String(n);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
var pingBtn = document.getElementById("ping");
|
|
30
|
+
var apiEl = document.getElementById("api");
|
|
31
|
+
if (pingBtn) {
|
|
32
|
+
pingBtn.addEventListener("click", async function () {
|
|
33
|
+
if (apiEl) apiEl.textContent = "loading…";
|
|
34
|
+
try {
|
|
35
|
+
var r = await fetch(base + "?action=time", { headers: { Accept: "application/json" } });
|
|
36
|
+
if (apiEl) apiEl.textContent = JSON.stringify(await r.json(), null, 2);
|
|
37
|
+
} catch (e) {
|
|
38
|
+
if (apiEl) apiEl.textContent = "error: " + e;
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
})();
|