跳到正文
cccskills
免费在 GitHub 公开

tech

Use when writing or reviewing Go code in fusionaly-oss — adding routes, handlers, domain contexts, background jobs, the manager CLI, wiring the app, or writing tests. Covers the cartridge framework, matcha for the manager, Phoenix Contexts, lifecycle/shutdown, and the test conventions.

中文简介正在准备中。当前显示原文说明。

集成工具Docker
查看安装方法

包含的文件(1)

  • SKILL.md7.3 KB

SKILL.md(原文)

安装前可以查看交给 AI 代理的指令内容。

Fusionaly Tech & Conventions

Overview

Fusionaly is a Go monolith (Fiber + GORM + SQLite, React/Inertia frontend). The guiding principle: the libraries do the heavy lifting; app code stays thin and domain-focused. Boilerplate — HTTP server, routing, sessions, CSRF, rate limiting, static serving, Inertia, SQLite WAL/serialized writes, graceful shutdown, deploys — lives in cartridge (the app) and matcha (the manager). The app reuses them; it does not re-implement them.

This is a deliberate, pragmatic stance: Go favors explicit, no-magic code, but here we push recurring infrastructure into a shared library so each app is a small amount of clean domain code on top. Don't hand-roll what the library already provides.

The stack at a glance

ConcernOwnerWhere
HTTP server, lifecycle, graceful shutdown, routing, sessions, CSRF, rate limiting, static/Inertiacartridgecartridge.NewApplication(...)
SQLite connection, WAL, serialized writes, migrations plumbingcartridge/sqlite + internal/databasedbManager, cartridge.Write
Background jobs lifecycle (start/stop with the server)cartridge BackgroundWorkerinternal/jobs
Install / update / deploy / backup / image swap (self-hosted ops)matchacmd/manager
Domain logic (analytics, events, websites, feed, agent, …)app (Phoenix Contexts)internal/<domain>/
FrontendReact 19 + Inertiaweb/src

cartridge: configure, don't reinvent

cartridge is our own framework — repo https://github.com/karloscodes/cartridge (Go module github.com/karloscodes/cartridge). When something feels like boilerplate, check there before writing it.

The whole app boots by handing cartridge a config struct. internal/app.go is the one place that wires it, and it's ~120 lines:

app, err := cartridge.NewApplication(cartridge.ApplicationOptions{
    Config:            cfg,
    Logger:            logger,                 // cartridge.NewLogger(cfg, nil)
    DBManager:         dbManager,              // wraps cartridge/sqlite
    ServerConfig:      serverConfig,           // CORS, SecFetchSite, ProxyHeader, StaticFS
    RouteMountFunc:    MountAppRoutes,         // your routes
    BackgroundWorkers: []cartridge.BackgroundWorker{jobsManager},
})
// Application embeds *cartridge.Application and adds DBManager.

cartridge then owns the server, signal handling, and graceful shutdown — including stopping the registered BackgroundWorkers. The app never writes a main() event loop or shutdown handler.

Key sub-packages you reuse (don't reinvent): cartridge/inertia (inertia.RenderPage), cartridge/cache (cache.Cache[K,V]), cartridge.Write / ctx.WriteTx (queued writes; sqlite.ErrBusy when the queue is full), cartridge/testsupport (test DBs), flash, session.

Phoenix Contexts: thin handlers, fat contexts

Each domain is a package under internal/<domain>/ exposing top-level functions that take *gorm.DB — no service objects, no HTTP imports.

// internal/settings/setting.go  — a context: plain functions + a model
type Setting struct { ID uint; Key string; Value string /* ... */ }

func GetAgentAPIKey(db *gorm.DB) (string, error) { /* ... */ }
func RegenerateAgentAPIKey(db *gorm.DB) (string, error) { /* ... */ }
// internal/http/*_handler.go — handlers stay thin: parse, delegate, render
func DashboardAction(ctx *cartridge.Context) error {
    metrics := analytics.GetDashboardMetrics(ctx.DB(), websiteID, tf)  // context does the work
    return inertia.RenderPage(ctx.Ctx, "Dashboard", inertia.Props{"metrics": metrics})
}

Rules: contexts never import internal/http; handlers never hold business logic; writes that can come in volume (ingest, event processing, jobs) go through cartridge.Write(ctx, dbManager, fn) or ctx.WriteTx(fn): one write at a time, in order, and sqlite.ErrBusy (answer 503 + Retry-After) when the queue is full. Never retry on the server, and never mark an event failed because of ErrBusy. Rare user actions (password change, logout) may use db.Transaction. Pages use Inertia + PRG (no fetch() for page data).

matcha: the manager owns ops

matcha is our own deploy/update tool — repo https://github.com/karloscodes/matcha (Go module github.com/karloscodes/matcha). cmd/manager is a thin wrapper over matcha — install, update, deploy, backup, image swap are library calls, not bespoke shell:

m := matcha.New(matcha.Config{Name: "fusionaly", AppImage: "karloscodes/fusionaly:latest",
    HealthPath: "/_health", Volumes: []string{"/app/storage", "/app/logs"},
    CronUpdates: true, Backups: true, ManagerRepo: "karloscodes/fusionaly-oss"})
// commands delegate: m.Install(), m.Update(), m.Deploy(), m.BackupDB(), m.SetImage(...)

Add a manager command by mirroring an existing one (runMigrateToOSS mirrors the structure of the others). Don't script Docker by hand.

Background jobs & shutdown

Jobs are a BackgroundWorker registered with cartridge, so they start and stop with the server. The scheduler runs each job on a ticker through executeJobSafely (panic-safe, single-flight). A job is just a struct with Run() error — the scheduler owns timing; jobs never spin their own goroutine/ticker. See internal/jobs/scheduler.go + internal/jobs/feed.go.

Testing conventions

  • Real behavior, no mocks. Use a real SQLite test DB via testsupport.SetupTestDBManager(t) (wraps cartridge/testsupport). Register new models in testsupport.allModels().
  • Env: tests require FUSIONALY_ENV=test — run with make test (sets it for you), not bare go test.
  • Four phases with blank lines between: setup, exercise, verify, teardown. Assert with testify assert/require.
  • Contexts via t.Run for related scenarios ("with valid key", "with no key").
  • Table-driven only when the cases are open-ended/homogeneous — e.g. a list of SQL strings each expected to be rejected (internal/agent/agent_test.go TestValidateReadOnlyQuery). Don't force a table when a few t.Run blocks read clearer.
  • E2E (e2e/) is Playwright, sequential (workers: 1, story order 001→…); specs build on each other (onboarding creates the user — setup does not). Wait on conditions, never race networkidle.

Common mistakes

MistakeInstead
Writing a main() loop, shutdown handler, or middleware cartridge already providesConfigure ApplicationOptions; let cartridge own it
Business logic in an internal/http handlerMove it to the domain context; handler just parses + renders
A context importing internal/httpContexts are HTTP-agnostic; pass *gorm.DB
Raw db.Exec writes scattered aroundcartridge.Write(...) (single-writer queue)
Hand-rolled Docker/deploy logic in the managerA matcha call
Mocks in testsReal test DB via testsupport
A job that starts its own ticker/goroutineRun() error + register in the scheduler
fetch() for page dataInertia props + PRG

评价

还没有评价。欢迎分享你的使用感受。

评论

无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。

还没有评论。来写第一条吧。

提交后会立即公开。请勿填写邮箱、电话等个人信息。URL 最多 2 个。

0 / 1000 字

此表单受 reCAPTCHA 保护,适用 Google 的隐私权政策和服务条款。

同一仓库的技能

connect

免费

Use when the user wants to connect, set up, or configure Fusionaly in their AI client, or when the fusionaly MCP tools are missing. Verifies the Fusionaly URL and agent key, then adds the MCP server to Claude Code, Codex, Cursor, Gemini CLI, or another client.

中文简介正在准备中。当前显示原文说明。

karloscodes/fusionaly-oss152026年10月19日 更新

fusionaly

免费

Use when user asks about website analytics, traffic, visitors, page views, referrers, or mentions "fusionaly". Queries Fusionaly analytics via SQL API.

中文简介正在准备中。当前显示原文说明。

karloscodes/fusionaly-oss152026年10月19日 更新

fusionaly

免费

Use when the user asks about website analytics, traffic, visitors, page views, referrers, conversions, goals, or mentions "fusionaly". Answers from a self-hosted Fusionaly instance through its MCP tools (or its SQL API), in plain English with numbers.

中文简介正在准备中。当前显示原文说明。

karloscodes/fusionaly-oss152026年10月19日 更新

Use ONLY when explicitly invoked. Puts Fusionaly on a Linux server you reach over SSH with a key. Optionally creates a Hetzner server and the Cloudflare DNS record. The user runs the one interactive install command; the agent prepares and checks everything around it.

中文简介正在准备中。当前显示原文说明。

karloscodes/fusionaly-oss152026年10月19日 更新

qa

免费

Use after code changes, before releases, or when testing features - runs the right level of QA based on what changed

中文简介正在准备中。当前显示原文说明。

karloscodes/fusionaly-oss152026年10月19日 更新

查看 karloscodes 的全部技能

报告此技能的问题