No description
  • Go 60.6%
  • Svelte 20.8%
  • TypeScript 12.5%
  • JavaScript 2.8%
  • CSS 2.6%
  • Other 0.7%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
SimbaFs 4a1639dc69
All checks were successful
Build and publish image / verify (push) Successful in 3m3s
Build and publish image / build-and-push (push) Successful in 45s
ci: verify 直接在 job 映像中執行,不再另啟 docker 容器
job 映像(git.1li.tw/1li/ci)已提供 Node.js、Go、gcc 與 chromium。

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-31 02:05:54 +08:00
.forgejo/workflows ci: verify 直接在 job 映像中執行,不再另啟 docker 容器 2026-10-31 02:05:54 +08:00
api feat: 模型選單加回「Codex 預設模型」,送出時明確帶入 Codex 標示為預設的模型 2026-10-29 12:58:01 +08:00
cmd/lai fix(web): 登入按鈕整頁導向 OIDC,正式版不再顯示本機測試登入 2026-10-31 01:31:11 +08:00
deploy chore: 部署(單一 Dockerfile、compose、k8s、systemd)、justfile、CI 與文件改寫;修正 request context 被 gin 重用的 race 2026-10-29 02:38:52 +08:00
docs feat: 「已發布」改為需確認的稿件 checkbox,每次上傳一併寫入 POSU 2026-10-29 02:38:54 +08:00
internal refactor(oidc): 改用 go-oidc 與 x/oauth2,不再自行實作 JWT 驗證 2026-10-30 04:46:00 +08:00
sdk refactor(sdk): 移除以 <p> 包圖片的 ImageEmbed,正文只能用結構化區塊寫入 2026-10-29 13:00:51 +08:00
web fix(web): 登入按鈕整頁導向 OIDC,正式版不再顯示本機測試登入 2026-10-31 01:31:11 +08:00
.dockerignore chore: 移除舊建置產物與過時的忽略規則 2026-10-29 02:38:55 +08:00
.env.example chore: 部署(單一 Dockerfile、compose、k8s、systemd)、justfile、CI 與文件改寫;修正 request context 被 gin 重用的 race 2026-10-29 02:38:52 +08:00
.gitignore chore: 移除舊建置產物與過時的忽略規則 2026-10-29 02:38:55 +08:00
AGENTS.md chore: 部署(單一 Dockerfile、compose、k8s、systemd)、justfile、CI 與文件改寫;修正 request context 被 gin 重用的 race 2026-10-29 02:38:52 +08:00
compose.yaml chore: 部署(單一 Dockerfile、compose、k8s、systemd)、justfile、CI 與文件改寫;修正 request context 被 gin 重用的 race 2026-10-29 02:38:52 +08:00
config.example.yaml chore: 部署(單一 Dockerfile、compose、k8s、systemd)、justfile、CI 與文件改寫;修正 request context 被 gin 重用的 race 2026-10-29 02:38:52 +08:00
Dockerfile chore: 部署(單一 Dockerfile、compose、k8s、systemd)、justfile、CI 與文件改寫;修正 request context 被 gin 重用的 race 2026-10-29 02:38:52 +08:00
go.mod refactor(oidc): 改用 go-oidc 與 x/oauth2,不再自行實作 JWT 驗證 2026-10-30 04:46:00 +08:00
go.sum refactor(oidc): 改用 go-oidc 與 x/oauth2,不再自行實作 JWT 驗證 2026-10-30 04:46:00 +08:00
justfile chore: 部署(單一 Dockerfile、compose、k8s、systemd)、justfile、CI 與文件改寫;修正 request context 被 gin 重用的 race 2026-10-29 02:38:52 +08:00
README.md chore: 部署(單一 Dockerfile、compose、k8s、systemd)、justfile、CI 與文件改寫;修正 request context 被 gin 重用的 race 2026-10-29 02:38:52 +08:00
REFACTOR.md refactor(store): 將 00002 併入 00001,schema 不再保留舊的發布狀態 2026-10-29 13:02:11 +08:00
sqlc.yaml feat: 新增設定、可觀測性與 SQLite store(sqlc + goose migration) 2026-10-29 02:38:46 +08:00

lai-media

專案概況、架構、部署與測試請從文件導覽開始。下文是 SDK 的使用範例。

Go SDK for the server-rendered PHP backend observed at sys.posu.tw.

The SDK models the discovered form-based protocol rather than pretending the backend is a JSON REST API. It supports authenticated reads and the article write operations that were observed during the read-only/API exploration:

  • login and session cookies;
  • article/news list pages and title/date filters;
  • article create and update forms;
  • article cover uploads and image-library uploads;
  • TinyMCE image and same-paragraph caption HTML generation;
  • article sort updates, view-count clearing, and deletion;
  • article/news traffic pages and news monthly hot-page queries;
  • article/news preview pages;
  • generic HTML access for account/profile pages.

ArticleInput.Published defaults to false. Ordinary create/update methods reject Published=true; publication requires the separately named PublishArticle method and an explicit confirmation argument.

Example

import (
	"context"
	"fmt"
	"os"

	sdk "github.com/simbafs/lai-media/sdk"
)

ctx := context.Background()

client, err := sdk.New("https://sys.posu.tw")
if err != nil {
	return err
}

if err := client.Login(ctx, sdk.Credentials{
		Username: os.Getenv("POSU_USERNAME"),
		Password: os.Getenv("POSU_PASSWORD"),
	}); err != nil {
	return err
}

articles, err := client.ListArticles(ctx, sdk.ArticleListOptions{Page: 1})
if err != nil {
	return err
}

	draft, err := client.CreateArticle(ctx, sdk.ArticleInput{
		Title:     "Mock draft",
		Brief:     "A draft created by the SDK.",
		Content:   "<p>Not published.</p>",
		Published: false,
		Platform:  sdk.PlatformNone,
	}, nil)
if err != nil {
	return err
}

fmt.Println(len(articles.Articles), draft.ID)

The package is intentionally small and uses the standard net/http client. Inject WithHTTPClient with an httptest.Server in tests; no live-site tests are included.

News bodies are written as structured blocks; the SDK renders the HTML POSU expects, including the caption styling and alt text:

_, err = client.CreateNews(ctx, sdk.NewsInput{
	Title: "標題",
	Brief: "摘要",
	Body: []sdk.Block{
		sdk.BodyImage{URL: "https://uploads.posu.tw/path/image.png", Width: 1920, Height: 1080, Caption: "▲ 圖說"},
		sdk.Paragraph{Lines: []string{"第一行", "同一段的第二行"}},
	},
}, &cover)

Each paragraph becomes one <p> (lines joined by <br>), and each image its own <div> containing only the image and its caption. GetNews parses the stored content back into News.Body, so a write can be verified by comparing blocks. Selecting an image in the browser's image library is a TinyMCE client-side action, not a separate server API call.

Test coverage

Run just coverage to test all Go packages and generate coverage.html in the project root. Open the file in a browser to inspect coverage by source file. The intermediate coverage profile is written to coverage.out.

Web editor

cmd/lai is the news editor: Codex-assisted writing, SQLite storage, images in a filesystem or S3 object store, and verified unpublished uploads to POSU. The SvelteKit frontend in web/ is embedded in the binary. Start with the documentation index.