본문으로 건너뛰기
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(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

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 / 1,000자

이 양식은 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의 스킬 모두 보기

이 스킬의 문제 신고