feat: opaque session、安装/发帖 SSR 与最小 Admin 后台

浏览器登录改为 DB sessions(可吊销);敏感词与 OIDC PEM 入 settings;
落地安装向导、注册发帖与 /admin 仪表盘/板块/审核/设置。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-29 05:44:16 +08:00
parent 3f50316ad0
commit fde5f628ec
80 changed files with 4148 additions and 1849 deletions

View File

@@ -0,0 +1,47 @@
{{define "admin/boards"}}
{{template "base/head" .}}
{{template "base/navbar" .}}
<main class="j13-main j13-admin">
<h1>板块管理</h1>
{{template "admin/nav" .}}
{{template "base/alert" .}}
<h2>新建板块</h2>
<form method="post" action="/admin/boards" class="j13-form j13-admin-form">
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
<label>名称 <input name="name" required value="{{.Form.Name}}"/></label>
<label>简介 <input name="description" value="{{.Form.Description}}"/></label>
<label>图标(可选) <input name="icon" value="{{.Form.Icon}}"/></label>
<label>色号 <input name="color_index" type="number" value="{{.Form.ColorIndex}}"/></label>
<label>排序 <input name="sort_order" type="number" value="{{.Form.SortOrder}}"/></label>
<button type="submit">创建</button>
</form>
<h2>已有板块</h2>
{{if .Boards}}
<ul class="j13-admin-list">
{{range .Boards}}
<li class="j13-admin-card">
<form method="post" action="/admin/boards/{{.ID}}" class="j13-form j13-admin-form">
<input type="hidden" name="_csrf" value="{{$.CSRF}}"/>
<label>名称 <input name="name" required value="{{.Name}}"/></label>
<label>简介 <input name="description" value="{{.Description}}"/></label>
<label>图标 <input name="icon" value="{{.Icon}}"/></label>
<label>色号 <input name="color_index" type="number" value="{{.ColorIndex}}"/></label>
<label>排序 <input name="sort_order" type="number" value="{{.SortOrder}}"/></label>
<p class="j13-muted">已发布帖 {{.PostCount}}</p>
<button type="submit">保存</button>
</form>
<form method="post" action="/admin/boards/{{.ID}}/delete" class="j13-inline-form" onsubmit="return confirm('确认删除该板块?');">
<input type="hidden" name="_csrf" value="{{$.CSRF}}"/>
<button type="submit" class="j13-linkbtn">删除</button>
</form>
</li>
{{end}}
</ul>
{{else}}
<p class="j13-empty">暂无板块</p>
{{end}}
</main>
{{template "base/footer" .}}
{{end}}

View File

@@ -0,0 +1,18 @@
{{define "admin/dashboard"}}
{{template "base/head" .}}
{{template "base/navbar" .}}
<main class="j13-main j13-admin">
<h1>管理后台</h1>
{{template "admin/nav" .}}
{{template "base/alert" .}}
<ul class="j13-admin-stats">
<li><strong>{{.UserCount}}</strong><span>用户</span></li>
<li><strong>{{.PostCount}}</strong><span>已发布帖</span></li>
<li><strong>{{.BoardCount}}</strong><span>板块</span></li>
<li><strong>{{.PendingPosts}}</strong><span>待审帖</span></li>
<li><strong>{{.PendingComments}}</strong><span>待审评</span></li>
</ul>
<p class="j13-muted">有待审内容时请前往 <a href="/admin/moderation">内容审核</a>。</p>
</main>
{{template "base/footer" .}}
{{end}}

View File

@@ -0,0 +1,60 @@
{{define "admin/moderation"}}
{{template "base/head" .}}
{{template "base/navbar" .}}
<main class="j13-main j13-admin">
<h1>内容审核</h1>
{{template "admin/nav" .}}
{{template "base/alert" .}}
<h2>待审帖子({{len .Posts}}</h2>
{{if .Posts}}
<ul class="j13-admin-list">
{{range .Posts}}
<li class="j13-admin-card">
<p><a href="/post/{{.ID}}">{{.Title}}</a></p>
<p class="j13-muted">{{.AuthorName}} · {{.BoardName}} · {{.CreatedAt}}</p>
<div class="j13-admin-actions">
<form method="post" action="/admin/posts/{{.ID}}/approve" class="j13-inline-form">
<input type="hidden" name="_csrf" value="{{$.CSRF}}"/>
<button type="submit">通过</button>
</form>
<form method="post" action="/admin/posts/{{.ID}}/reject" class="j13-form j13-admin-reject">
<input type="hidden" name="_csrf" value="{{$.CSRF}}"/>
<input name="reason" required placeholder="拒绝原因" />
<button type="submit" class="j13-btn-secondary">拒绝</button>
</form>
</div>
</li>
{{end}}
</ul>
{{else}}
<p class="j13-empty">无待审帖子</p>
{{end}}
<h2>待审评论({{len .Comments}}</h2>
{{if .Comments}}
<ul class="j13-admin-list">
{{range .Comments}}
<li class="j13-admin-card">
<p class="j13-muted">#{{.Floor}} · {{.AuthorName}} · {{.CreatedAt}} · <a href="/post/{{.PostID}}">{{.PostTitle}}</a></p>
<p>{{.Excerpt}}</p>
<div class="j13-admin-actions">
<form method="post" action="/admin/comments/{{.ID}}/approve" class="j13-inline-form">
<input type="hidden" name="_csrf" value="{{$.CSRF}}"/>
<button type="submit">通过</button>
</form>
<form method="post" action="/admin/comments/{{.ID}}/reject" class="j13-form j13-admin-reject">
<input type="hidden" name="_csrf" value="{{$.CSRF}}"/>
<input name="reason" required placeholder="拒绝原因" />
<button type="submit" class="j13-btn-secondary">拒绝</button>
</form>
</div>
</li>
{{end}}
</ul>
{{else}}
<p class="j13-empty">无待审评论</p>
{{end}}
</main>
{{template "base/footer" .}}
{{end}}

9
templates/admin/nav.tmpl Normal file
View File

@@ -0,0 +1,9 @@
{{define "admin/nav"}}
<nav class="j13-admin-nav">
<a href="/admin/dashboard"{{if eq .NavActive "dashboard"}} class="is-active"{{end}}>仪表盘</a>
<a href="/admin/boards"{{if eq .NavActive "boards"}} class="is-active"{{end}}>板块</a>
<a href="/admin/moderation"{{if eq .NavActive "moderation"}} class="is-active"{{end}}>审核</a>
<a href="/admin/settings"{{if eq .NavActive "settings"}} class="is-active"{{end}}>设置</a>
<a href="/">返回前台</a>
</nav>
{{end}}

View File

@@ -0,0 +1,43 @@
{{define "admin/settings"}}
{{template "base/head" .}}
{{template "base/navbar" .}}
<main class="j13-main j13-admin">
<h1>站点设置</h1>
{{template "admin/nav" .}}
{{template "base/alert" .}}
<h2>品牌</h2>
<form method="post" action="/admin/settings/brand" class="j13-form j13-admin-form">
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
<label>站点名称 <input name="name" required value="{{.Brand.Name}}"/></label>
<label>标语 <input name="slogan" value="{{.Brand.Slogan}}"/></label>
<label>简介 <textarea name="description" rows="3">{{.Brand.Description}}</textarea></label>
<label>关键词 <input name="keywords" value="{{.Brand.Keywords}}"/></label>
<label>Logo 字标(单字) <input name="logo_mark" maxlength="4" value="{{.Brand.LogoMark}}"/></label>
<label>ICP 备案号 <input name="icp_beian" value="{{.Brand.ICPBeian}}"/></label>
<label>ICP 链接 <input name="icp_beian_url" value="{{.Brand.ICPBeianURL}}"/></label>
<button type="submit">保存品牌</button>
</form>
<h2>基础限流</h2>
<p class="j13-muted">窗口内允许的操作次数(秒)。</p>
<form method="post" action="/admin/settings/limits" class="j13-form j13-admin-form">
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
<label>发帖 <input name="rate_limit_post" type="number" min="1" value="{{.RatePost}}"/></label>
<label>评论 <input name="rate_limit_comment" type="number" min="1" value="{{.RateComment}}"/></label>
<label>注册 <input name="rate_limit_register" type="number" min="1" value="{{.RateReg}}"/></label>
<label>登录 <input name="rate_limit_login" type="number" min="1" value="{{.RateLogin}}"/></label>
<label>窗口秒数 <input name="rate_limit_window_sec" type="number" min="10" value="{{.RateWindow}}"/></label>
<button type="submit">保存限流</button>
</form>
<h2>敏感词(有效 {{.FilterCount}} 个)</h2>
<p class="j13-muted">每行一个词,# 开头为注释。保存后立即热更新。</p>
<form method="post" action="/admin/settings/filter-words" class="j13-form j13-admin-form">
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
<label>词表 <textarea name="filter_words" rows="12">{{.FilterWords}}</textarea></label>
<button type="submit">保存敏感词</button>
</form>
</main>
{{template "base/footer" .}}
{{end}}