本文へ移動
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.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md7.3 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

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

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

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-oss142026年10月11日 更新

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-oss142026年10月11日 更新

fusionaly

無料

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

日本語の概要は準備中です。原文の説明を表示しています。

karloscodes/fusionaly-oss142026年10月11日 更新

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-oss142026年10月11日 更新

qa

無料

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

日本語の概要は準備中です。原文の説明を表示しています。

karloscodes/fusionaly-oss142026年10月11日 更新

karloscodes のスキルをすべて見る

このスキルの問題を報告する