Manager-Content (Go Server)
Rola w ekosystemie
Manager-content pełni podwójną rolę:
- Web UI — panel webowy dla rodzica/szkoły (dashboard, content manager, custom worlds)
- Endpointy
/app/*— REST/JSON dla Flutter app (dane, auth, content) - Endpointy
/api/*— REST/JSON do integracji z managerem (jeśli potrzebne)
To jest jedyny serwer w ekosystemie — przechowuje wszystkie dane i obsługuje przeglądarkę (HTML), aplikację mobilną (/app/*) i ewentualne integracje (/api/*). Podział na /app/ i /api/ wyraźnie rozróżnia klientów.
Tech Stack
| Technologia | Zastosowanie |
|---|---|
| Go | Backend, /app/*, /api/*, server-side rendering |
| chi | HTTP router |
| html/template | Szablony HTML (web UI) |
| HTMX | Interaktywność bez pełnego JS (web UI) |
| Alpine.js | Lekka reaktywność na frontendzie (web UI) |
| TailwindCSS | Utility-first styling (web UI) |
| SQLite / PostgreSQL | Baza danych |
Architektura
Dwa interfejsy, jeden serwer
Manager-content obsługuje dwa typy klientów z tego samego serwera Go:
- Przeglądarka (web UI) → SSR + HTMX + Alpine.js → zwraca HTML
- Flutter app (
/app/*) → endpointy dedykowane dla aplikacji mobilnej → zwraca JSON - Integracje (
/api/*) → endpointy do komunikacji z managerem → zwraca JSON
┌──────────────────┐ ┌──────────────────┐
│ Przeglądarka │ │ Flutter App │
│ (rodzic/szkoła)│ │ (dziecko) │
│ │ │ │
│ HTMX + Alpine │ │ Dio (HTTP) │
└────────┬─────────┘ └────────┬─────────┘
│ HTML fragments │ JSON
│ │
┌────────▼─────────────────────────────▼─────────┐
│ Go Server │
│ │
│ ┌─────────────┐ ┌──────────┐ ┌───────────┐ │
│ │ Web Handlers│ │ Services │ │API Handlers│ │
│ │ (HTML/HTMX) │──▶│ (logic) │◀──│ (JSON) │ │
│ └─────────────┘ └────┬─────┘ └───────────┘ │
│ │ │
│ ┌──────────┐ ┌────▼──────┐ │
│ │Templates │ │ Database │ │
│ │(html/tpl)│ └───────────┘ │
│ └──────────┘ │
└────────────────────────────────────────────────┘Jak działa HTMX
Zamiast pełnego przeładowania strony, HTMX wysyła żądania AJAX i podmienia fragmenty HTML:
<!-- Przycisk ładujący listę zadań -->
<button hx-get="/tasks" hx-target="#task-list" hx-swap="innerHTML">
Załaduj zadania
</button>
<!-- Kontener na wynik -->
<div id="task-list"></div>Serwer Go zwraca fragment HTML (nie JSON), który HTMX wstawia do DOM.
Jak działa Alpine.js
Alpine.js obsługuje lekki stan po stronie klienta — dropdowny, modale, walidacja formularzy:
<!-- Dropdown z Alpine.js -->
<div x-data="{ open: false }">
<button @click="open = !open">Menu</button>
<div x-show="open" @click.away="open = false">
<a href="/dashboard">Dashboard</a>
<a href="/content">Content</a>
</div>
</div>Struktura Projektu
manager-content/
├── cmd/
│ └── server/
│ └── main.go # Entry point
│
├── internal/
│ ├── handler/ # HTTP handlers (chi)
│ │ ├── web/ # Web UI handlers (zwracają HTML)
│ │ │ ├── dashboard.go
│ │ │ ├── content.go
│ │ │ ├── worlds.go
│ │ │ └── todo.go
│ │ ├── app/ # /app/* handlers dla Flutter (JSON)
│ │ │ ├── worlds.go
│ │ │ ├── tasks.go
│ │ │ ├── users.go
│ │ │ └── auth.go
│ │ ├── api/ # /api/* handlers do integracji (JSON)
│ │ │ └── ...
│ │ └── auth.go # Logowanie (kod z app / email)
│ │
│ ├── service/ # Logika biznesowa
│ │ ├── content.go
│ │ ├── world.go
│ │ ├── todo.go
│ │ └── auth.go
│ │
│ ├── model/ # Modele danych
│ │ ├── world.go
│ │ ├── location.go
│ │ ├── task.go
│ │ ├── user.go
│ │ └── todo.go
│ │
│ ├── repository/ # Warstwa dostępu do danych
│ │ ├── world_repo.go
│ │ ├── task_repo.go
│ │ └── user_repo.go
│ │
│ └── middleware/ # Middleware (auth, logging)
│ ├── auth.go
│ └── logging.go
│
├── web/
│ ├── templates/ # Szablony Go html/template
│ │ ├── layouts/
│ │ │ └── base.html # Layout bazowy
│ │ ├── pages/
│ │ │ ├── dashboard.html
│ │ │ ├── content.html
│ │ │ └── worlds.html
│ │ ├── partials/ # Fragmenty HTMX
│ │ │ ├── task-list.html
│ │ │ ├── task-card.html
│ │ │ └── todo-item.html
│ │ └── components/ # Reużywalne komponenty UI
│ │ ├── button.html
│ │ ├── card.html
│ │ ├── modal.html
│ │ └── form-field.html
│ │
│ ├── static/
│ │ ├── css/
│ │ │ └── output.css # TailwindCSS (skompilowany)
│ │ └── js/
│ │ ├── htmx.min.js
│ │ └── alpine.min.js
│ │
│ └── tailwind.config.js # Konfiguracja TailwindCSS
│
├── go.mod
└── go.sumKluczowe Ekrany
1. Logowanie
Dwa tryby logowania:
- Kod z aplikacji — 8-znakowy kod alfanumeryczny (wielkie litery + cyfry) wygenerowany w Flutter app
- Kod emailowy — 8-znakowy kod alfanumeryczny wysyłany na email (dla rodziców bez app)
2. Dashboard Rodzica
Przegląd statystyk dziecka:
- Postępy w zadaniach (ukończone / w trakcie)
- Aktywność (ostatnie zadania, czas gry)
- XP i poziom
- Todo lista z deadline'ami
3. Content Manager
Tworzenie i edycja treści edukacyjnych:
- Formularz tworzenia quizu (pytania, odpowiedzi, punkty)
- Upload video i audio
- Przypisywanie do lokacji i grup wiekowych
- Podgląd zadania
4. Custom Worlds
Tworzenie własnych planet:
- Formularz tworzenia planety (nazwa, opis, grafika)
- Dodawanie lokacji na mapę planety
- Przypisywanie zadań do lokacji
- Zarządzanie dostępem (otwarta / zamknięta)
5. Todo Lista
Zarządzanie zadaniami dziecka:
- Dodawanie zadań realnych (np. "posprzątaj pokój")
- Linkowanie z zadaniami in-game
- Ustawianie deadline'ów
- Śledzenie statusu
Routing (chi)
r := chi.NewRouter()
r.Use(middleware.Logger)
// === Web UI (HTML) — dla przeglądarki ===
r.Group(func(r chi.Router) {
r.Use(middleware.WebAuth)
r.Get("/dashboard", webHandler.Dashboard)
r.Route("/content", func(r chi.Router) {
r.Get("/", webHandler.ListContent)
r.Post("/", webHandler.CreateTask)
r.Get("/{id}", webHandler.GetTask)
r.Put("/{id}", webHandler.UpdateTask)
r.Delete("/{id}", webHandler.DeleteTask)
})
r.Route("/worlds", func(r chi.Router) {
r.Get("/", webHandler.ListWorlds)
r.Post("/", webHandler.CreateWorld)
})
})
// === /app/* (JSON) — dla Flutter app ===
r.Route("/app", func(r chi.Router) {
r.Use(middleware.AppAuth)
r.Get("/worlds", appHandler.ListWorlds)
r.Get("/worlds/{id}", appHandler.GetWorld)
r.Get("/worlds/{id}/locations", appHandler.GetLocations)
r.Get("/locations/{id}/tasks", appHandler.GetTasks)
r.Get("/users/{id}", appHandler.GetUser)
r.Post("/auth/login", appHandler.Login)
r.Post("/auth/manager-code", appHandler.GenerateManagerCode)
})
// === /api/* (JSON) — integracje z managerem ===
r.Route("/api", func(r chi.Router) {
r.Use(middleware.APIAuth)
// endpointy dodawane w miarę potrzeb
})Web Handler (HTML)
func (h *WebContentHandler) CreateTask(w http.ResponseWriter, r *http.Request) {
var task model.Task
if err := json.NewDecoder(r.Body).Decode(&task); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
created, err := h.service.CreateTask(r.Context(), task)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Dla HTMX: zwracamy fragment HTML
if r.Header.Get("HX-Request") == "true" {
h.renderPartial(w, "task-card.html", created)
return
}
// Dla zwykłego żądania: redirect
http.Redirect(w, r, "/content", http.StatusSeeOther)
}App Handler (JSON — Flutter)
func (h *APIWorldHandler) ListWorlds(w http.ResponseWriter, r *http.Request) {
worlds, err := h.service.GetWorlds(r.Context())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(worlds)
}Wszystkie handlery (web, /app/*, /api/*) korzystają z tych samych services — logika biznesowa jest współdzielona, różni się tylko format odpowiedzi (HTML vs JSON) i kontekst klienta.
Design System
Manager-content ma odseparowany design system oparty na TailwindCSS:
- Tokeny — kolory, spacing, typografia w
tailwind.config.js - Komponenty — szablony Go (button, card, modal, form-field)
- Interaktywność — HTMX dla dynamicznych fragmentów, Alpine.js dla stanu UI
Szczegóły: Design Systemy
Internacjonalizacja (i18n)
Na start: polski + angielski.
- Tłumaczenia w plikach JSON (
locales/pl.json,locales/en.json), ładowane przez Go server - Przełączanie języka: ikony flag (🇵🇱 / 🇬🇧) w top bar managera, obok avatara użytkownika
- Wybrany język zapisywany w session cookie (
lang), domyślnie"pl" - Szablony Go korzystają z funkcji
twFuncMapdo tłumaczenia stringów (np.t .Lang "dashboard.title")