Training content management platform for organizing video instructionals into structured curricula. Built for martial arts training (BJJ, JKD), but adaptable to any discipline-based learning.
- Collect and organize video links from YouTube and other platforms
- Tag and categorize techniques with hierarchical categories
- Build ordered curricula from techniques, video assets, and text notes
- Share public curricula with others
- YouTube metadata auto-extraction via oEmbed
frontend/ Vue 3 SPA — UI, routing, Firebase Auth
backend/ Go REST API — business logic, Firestore persistence
- Frontend: Vue 3 + Vite + PrimeVue 4 + Pinia + Firebase JS SDK
- Backend: Go + Chi v5 + Firebase Admin SDK + Cloud Firestore
- Auth: Firebase Authentication (Google sign-in + email/password)
- Database: Cloud Firestore (NoSQL document store)
- Hosting: Firebase Hosting (frontend) + Cloud Run (backend)
- Go 1.22+
- Node.js 20+
- Firebase CLI (
npm install -g firebase-tools) - Java Runtime (required for Firebase emulators)
- A Firebase project with Firestore and Authentication enabled
git clone <repo-url>
cd skillhiveCreate a Firebase project at console.firebase.google.com:
- Create a new project (or use existing)
- Enable Authentication with Google and Email/Password providers
- Create a Firestore Database (production mode, choose your region)
- Generate a service account key: Project Settings > Service accounts > Generate new private key
- Save the key as
backend/serviceAccountKey.json - Get your web app config: Project Settings > General > Your apps > Add web app
Backend:
cd backend
cp .env.example .envEdit backend/.env:
PORT=8080
GCP_PROJECT=your-firebase-project-id
FIREBASE_KEY_PATH=./serviceAccountKey.json
CORS_ALLOWED_ORIGINS=http://localhost:5173,http://localhost:5000
ENV=developmentFrontend:
cd frontend
cp .env.example .env # or edit the existing .envEdit frontend/.env with your Firebase auth config:
VITE_API_URL=http://localhost:8080
VITE_FIREBASE_API_KEY=your-api-key
VITE_FIREBASE_AUTH_DOMAIN=your-project.firebaseapp.com
VITE_FIREBASE_PROJECT_ID=your-project-idImportant: The frontend only uses Firebase for authentication. It has no direct access to Firestore, Storage, or any other cloud infrastructure. All data flows exclusively through the Go API.
Populate Firestore with initial disciplines (BJJ, JKD), categories, and sample techniques:
cd backend
go run cmd/seed/main.goTerminal 1 — Backend:
cd backend
go run .The API starts on http://localhost:8080. Verify with:
curl http://localhost:8080/healthz
# {"status":"ok"}Terminal 2 — Frontend:
cd frontend
npm install
npm run devThe app opens at http://localhost:5173.
For local development without connecting to production Firebase:
npx firebase-tools emulators:startThis starts:
- Auth emulator on port 9099
- Firestore emulator on port 8080
- Hosting emulator on port 5000
- Emulator UI at port 4000
Note: The Firestore emulator uses port 8080, which conflicts with the Go backend default port. When using emulators, either change the backend port or run the backend against the emulator.
# Backend
cd backend
go run . # Start dev server
go build . # Build binary
go vet ./... # Lint
go run cmd/seed/main.go # Seed Firestore
# Frontend
cd frontend
npm run dev # Start Vite dev server (port 5173)
npm run build # Type-check + production build
npx vue-tsc --noEmit # Type-check only
npx vite build # Build only (skip type-check)
npm run preview # Preview production buildskillhive/
├── backend/
│ ├── main.go # Entry point, router, middleware
│ ├── cmd/seed/main.go # Database seeder
│ ├── internal/
│ │ ├── config/ # Environment configuration
│ │ ├── handler/ # HTTP handlers (one per entity)
│ │ ├── middleware/ # Auth, CORS, logging
│ │ ├── model/ # Data structs
│ │ ├── store/ # Firestore client
│ │ └── validate/ # Input validation + sanitization
│ ├── Dockerfile
│ └── go.mod
├── frontend/
│ ├── src/
│ │ ├── components/ # Reusable UI components
│ │ ├── composables/ # Vue composables (useApi, useDebounce)
│ │ ├── plugins/ # PrimeVue + Firebase setup
│ │ ├── router/ # Routes + auth guard
│ │ ├── stores/ # Pinia stores (one per entity)
│ │ ├── types/ # TypeScript interfaces
│ │ ├── validation/ # Zod schemas
│ │ └── views/ # Page components
│ ├── package.json
│ └── vite.config.ts
├── firebase.json # Firebase project config
├── firestore.rules # Firestore security rules
├── firestore.indexes.json # Composite indexes
└── .github/workflows/ # CI/CD pipelines
All /api/v1/* routes require a Firebase Auth token in the Authorization: Bearer <token> header.
| Resource | Endpoints |
|---|---|
| Health | GET /healthz |
| Disciplines | GET /api/v1/disciplines |
| Tags | GET, POST /api/v1/tags |
| Categories | GET, POST /api/v1/categories |
| Techniques | GET, POST /api/v1/techniques |
| Assets | GET, POST /api/v1/assets |
| YouTube | POST /api/v1/youtube/resolve |
| Curricula | GET, POST /api/v1/curricula |
| Elements | GET, POST /api/v1/curricula/{id}/elements |
Common query parameters:
disciplineId— Filter by discipline (required for tags, categories, techniques, assets)q— Text search (techniques, assets)categoryId— Filter by category (techniques)tagId— Filter by tag (techniques, assets)limit,offset— Pagination
| Route | View | Description |
|---|---|---|
/ |
DashboardView | Quick stats, recent curricula, quick-save video |
/login |
LoginView | Firebase Auth (Google + email/password) |
/tags |
TagsView | Tag management with color picker |
/categories |
CategoriesView | Hierarchical category tree |
/techniques |
TechniquesView | Technique list with search and filters |
/techniques/:id |
TechniqueDetailView | Technique detail with edit/delete |
/assets |
AssetsView | Video asset list with search |
/assets/new |
SaveAssetView | Create new asset with YouTube URL resolver |
/assets/:id/edit |
SaveAssetView | Edit existing asset |
/curricula |
CurriculaView | User's curricula list |
/curricula/public |
PublicCurriculaView | Browse public curricula |
/curricula/:id |
CurriculumDetailView | Curriculum builder (add/reorder/delete elements) |
Build and deploy the Docker container:
cd backend
# Build
docker build -t skillhive-api .
# Deploy to Cloud Run
gcloud run deploy skillhive-api \
--image gcr.io/YOUR_PROJECT/skillhive-api \
--platform managed \
--region europe-west1 \
--allow-unauthenticated \
--set-env-vars="GCP_PROJECT=YOUR_PROJECT,ENV=production,CORS_ALLOWED_ORIGINS=https://your-domain.web.app"cd frontend
npm run build
# Deploy
npx firebase-tools deploy --only hostingGitHub Actions workflows are in .github/workflows/:
ci.yml— Runs on every push/PR: Go build + vet, Vue type-check + production builddeploy.yml— Runs on push tomain: deploys backend to Cloud Run, frontend to Firebase Hosting
Required GitHub secrets for deployment:
GCP_PROJECT_ID— Firebase/GCP project IDGCP_SA_KEY— Service account key JSON (base64 encoded)FIREBASE_TOKEN— Firebase CLI token (firebase login:ci)
| Collection | Key Fields | Notes |
|---|---|---|
disciplines |
name, slug, description |
Seeded, read-only |
tags |
name, slug, color, disciplineId, ownerUid |
Unique slug per discipline |
categories |
name, slug, parentId, disciplineId, ownerUid |
Hierarchical, self-referencing |
techniques |
name, slug, description, categoryIds[], tagIds[], disciplineId, ownerUid |
Arrays for many-to-many |
assets |
title, url, type, videoType, thumbnailUrl, originator, techniqueIds[], tagIds[], disciplineId, ownerUid |
Video metadata via oEmbed |
curricula |
title, description, isPublic, ownerUid |
Public curricula visible to all |
curricula/{id}/elements |
type, ord, techniqueId?, assetId?, title?, details? |
Subcollection, ordered |
All documents use Firestore auto-generated IDs. Owner-based access: users can only read/write their own data (except public curricula and seeded disciplines).
Private project.