using beego framework
This commit is contained in:
209
README.md
Normal file
209
README.md
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
# 🗂️ Gitea Projekt-Dashboard
|
||||||
|
|
||||||
|
Ein selbst gehostetes Dashboard zur zentralen Verwaltung aller persönlichen Projekte – technische wie nicht-technische. Projekte können mit einer Gitea-Repository verknüpft sein, müssen es aber nicht.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🏷️ Funktionsprinzip: Tag-basierte Projektanzeige
|
||||||
|
|
||||||
|
Jedes Repository, das auf dieser Gitea-Instanz mit dem Topic-Tag `dashboard` versehen ist, wird automatisch im Dashboard angezeigt.
|
||||||
|
|
||||||
|
### So funktioniert es:
|
||||||
|
|
||||||
|
1. Du setzt in einem Gitea-Repository unter **Settings → Topics** das Tag `dashboard`
|
||||||
|
2. Der Backend-Service pollt regelmäßig (oder per Webhook) die Gitea API nach allen Repos mit diesem Tag
|
||||||
|
3. Die gefundenen Repos werden in der PostgreSQL-Datenbank gespeichert und gecacht
|
||||||
|
4. Das Frontend zeigt alle getaggten Repos als Projektkarten mit Live-Daten an
|
||||||
|
|
||||||
|
> **Beispiel:** Repo `mein-projekt` bekommt das Topic `dashboard` → erscheint sofort im Dashboard mit Issues, letztem Commit, Status und Beschreibung.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧱 Architektur
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────┐ Gitea REST API v1 ┌──────────────────────┐
|
||||||
|
│ Gitea Server │ ◄────────────────────────► │ Backend Service │
|
||||||
|
│ (Repos + Topics)│ │ (Go, net/http) │
|
||||||
|
└─────────────────┘ └──────────┬───────────┘
|
||||||
|
│
|
||||||
|
┌──────────▼───────────┐
|
||||||
|
│ PostgreSQL DB │
|
||||||
|
│ (Repos, Issues, │
|
||||||
|
│ Milestones, Cache) │
|
||||||
|
└──────────┬───────────┘
|
||||||
|
│
|
||||||
|
┌──────────▼───────────┐
|
||||||
|
│ Frontend │
|
||||||
|
│ (SvelteKit) │
|
||||||
|
└──────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🖥️ Frontend: **SvelteKit**
|
||||||
|
|
||||||
|
**Warum SvelteKit?**
|
||||||
|
- Leichtgewichtig und schnell – ideal für ein internes Dashboard
|
||||||
|
- Server-Side Rendering (SSR) out of the box – kein Flackern beim Laden
|
||||||
|
- Einfache Reaktivität ohne Overhead
|
||||||
|
- Perfekt für datengetriebene Dashboards mit Echtzeit-Updates via SSE oder WebSocket
|
||||||
|
|
||||||
|
**Features im Frontend:**
|
||||||
|
- Projektkarten mit Repo-Name, Beschreibung, letztem Commit, offenen Issues
|
||||||
|
- Filterfunktion nach Topics, Sprache, Aktivität
|
||||||
|
- Detailansicht: Issues & Milestones direkt im Dashboard bearbeiten (bi-direktional)
|
||||||
|
- Live-Updates via Webhook-Events (Server-Sent Events)
|
||||||
|
- Dark Mode, responsive Design
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ Backend: **Go (net/http + pgx)**
|
||||||
|
|
||||||
|
Go eignet sich hervorragend als Backend-Sprache – die Standardbibliothek ist so vollständig, dass kein Web-Framework nötig ist. `net/http` liefert alles was gebraucht wird: Routing, Handler, Middleware. Das Ergebnis ist eine dependency-arme, gut lesbare Codebasis.
|
||||||
|
|
||||||
|
**Warum kein Framework?**
|
||||||
|
- `net/http` aus der Standardbibliothek reicht für ~6 Endpoints vollständig aus
|
||||||
|
- Kein Framework-Overhead, keine Breaking Changes durch externe Dependencies
|
||||||
|
- Go-typischer Ansatz: explizit, simpel, lesbar
|
||||||
|
- Kompiliert zu einer einzigen statischen Binary – minimaler Docker-Footprint
|
||||||
|
|
||||||
|
**Externe Dependencies (minimal):**
|
||||||
|
- `pgx` – PostgreSQL-Treiber (direktes SQL, kein ORM)
|
||||||
|
- `godotenv` – `.env`-Datei laden
|
||||||
|
- `golang.org/x/oauth2` – OAuth2-Flow für Gitea-Login
|
||||||
|
|
||||||
|
**Backend-Aufgaben:**
|
||||||
|
- `GET /api/projects` – alle getaggten Repos aus der DB zurückgeben
|
||||||
|
- `POST /api/webhook` – Gitea Webhook-Listener für Push, Issue, Tag-Events
|
||||||
|
- `GET /api/projects/{id}/issues` – Issues eines Repos live aus Gitea holen
|
||||||
|
- Hintergrund-Goroutine: alle 5 Minuten Gitea API nach Repos mit Tag `dashboard` abfragen
|
||||||
|
- Repo-Daten in PostgreSQL cachen (inkl. Topics, letzter Aktivität, Issue-Count)
|
||||||
|
|
||||||
|
**Beispiel – HTTP-Server ohne Framework:**
|
||||||
|
```go
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("GET /api/projects", h.listProjects)
|
||||||
|
mux.HandleFunc("POST /api/webhook", h.handleWebhook)
|
||||||
|
mux.HandleFunc("GET /api/projects/{id}/issues", h.listIssues)
|
||||||
|
|
||||||
|
log.Fatal(http.ListenAndServe(":8080", mux))
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔐 Auth: **Gitea OAuth2**
|
||||||
|
|
||||||
|
Gitea kann selbst als OAuth2-Provider fungieren – Nutzer loggen sich mit ihrem Gitea-Account im Dashboard ein, genau wie "Login with GitHub".
|
||||||
|
|
||||||
|
### Setup in Gitea:
|
||||||
|
|
||||||
|
1. In Gitea unter **Settings → Applications → OAuth2 Applications** eine neue App registrieren
|
||||||
|
2. `Client ID` und `Client Secret` in die `.env` eintragen
|
||||||
|
3. Redirect URI auf `https://dashboard.example.com/auth/callback` setzen
|
||||||
|
|
||||||
|
### Flow:
|
||||||
|
|
||||||
|
```
|
||||||
|
Nutzer klickt "Login mit Gitea"
|
||||||
|
→ Weiterleitung zur Gitea-Instanz (Authorization Endpoint)
|
||||||
|
→ Nutzer bestätigt Zugriff
|
||||||
|
→ Gitea leitet mit Authorization Code zurück
|
||||||
|
→ Backend tauscht Code gegen Access Token
|
||||||
|
→ Nutzer ist eingeloggt, Gitea-Identität bekannt
|
||||||
|
```
|
||||||
|
|
||||||
|
**Vorteile:**
|
||||||
|
- Kein eigenes Auth-System nötig – Gitea übernimmt Passwörter und Sessions
|
||||||
|
- Nutzeridentität direkt bekannt → Repos und Issues können nutzerbasiert gefiltert werden
|
||||||
|
- Schreibrechte (Issues erstellen/schließen) nur für den jeweiligen Repo-Owner
|
||||||
|
- Implementiert mit `golang.org/x/oauth2` – offizielles Go-Paket, keine Drittanbieter-Lib nötig
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🗄️ Datenbank: **PostgreSQL**
|
||||||
|
|
||||||
|
**Schema-Übersicht:**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Gecachte Repo-Informationen
|
||||||
|
CREATE TABLE projects (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
gitea_id INTEGER UNIQUE NOT NULL,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
full_name VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
html_url TEXT,
|
||||||
|
topics TEXT[], -- z.B. ["dashboard", "freelancer"]
|
||||||
|
language VARCHAR(100),
|
||||||
|
open_issues INTEGER DEFAULT 0,
|
||||||
|
last_push TIMESTAMPTZ,
|
||||||
|
is_private BOOLEAN DEFAULT false,
|
||||||
|
synced_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Gemanagte Issues / Aufgaben
|
||||||
|
CREATE TABLE issues (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
gitea_id INTEGER NOT NULL,
|
||||||
|
project_id INTEGER REFERENCES projects(id),
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
state VARCHAR(20), -- open / closed
|
||||||
|
assignee VARCHAR(100),
|
||||||
|
milestone TEXT,
|
||||||
|
updated_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Webhook-Event-Log
|
||||||
|
CREATE TABLE webhook_events (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
event_type VARCHAR(50),
|
||||||
|
payload JSONB,
|
||||||
|
received_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Roadmap
|
||||||
|
|
||||||
|
- [ ] **v0.1** – Repo-Listing via Tag `dashboard`, Polling alle 5 min
|
||||||
|
- [ ] **v0.2** – Webhook-Listener für Echtzeit-Updates
|
||||||
|
- [ ] **v0.3** – Issues & Milestones im Dashboard anzeigen
|
||||||
|
- [ ] **v0.4** – Issues direkt aus dem Dashboard erstellen/schließen (bi-direktional)
|
||||||
|
- [ ] **v0.5** – Gitea OAuth2 Login
|
||||||
|
- [ ] **v0.6** – Verknüpfung mit Freelancer-Dashboard (Repos = Projekte)
|
||||||
|
- [ ] **v1.0** – Multi-User, öffentliche Projektsseiten
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Tech Stack
|
||||||
|
|
||||||
|
| Schicht | Technologie |
|
||||||
|
|--------------|--------------------------------|
|
||||||
|
| Frontend | SvelteKit + TailwindCSS |
|
||||||
|
| Backend | Go + net/http (Standardlib) |
|
||||||
|
| Datenbank | PostgreSQL + pgx |
|
||||||
|
| Auth | Gitea OAuth2 + golang.org/x/oauth2 |
|
||||||
|
| API | Gitea REST API v1 |
|
||||||
|
| Deployment | Docker Compose |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 Getting Started
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Repo klonen
|
||||||
|
git clone https://gitea.starfour.de/Jannis/gitea-projekt-dashboard
|
||||||
|
|
||||||
|
# Umgebungsvariablen setzen
|
||||||
|
cp .env.example .env
|
||||||
|
# GITEA_URL, GITEA_TOKEN, GITEA_CLIENT_ID, GITEA_CLIENT_SECRET, DATABASE_URL, DASHBOARD_TAG eintragen
|
||||||
|
|
||||||
|
# Mit Docker starten
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Dieses Projekt ist Teil der persönlichen Projekt-Ideen-Sammlung. Zugehöriges Übersichts-Repo: [projekt-ideen](https://gitea.starfour.de/Jannis/projekt-ideen)*
|
||||||
3
conf/app.conf
Normal file
3
conf/app.conf
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
appname = gitea-projekt-dashboard
|
||||||
|
httpport = 8080
|
||||||
|
runmode = dev
|
||||||
15
controllers/default.go
Normal file
15
controllers/default.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
beego "github.com/beego/beego/v2/server/web"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MainController struct {
|
||||||
|
beego.Controller
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MainController) Get() {
|
||||||
|
c.Data["Website"] = "beego.vip"
|
||||||
|
c.Data["Email"] = "astaxie@gmail.com"
|
||||||
|
c.TplName = "index.tpl"
|
||||||
|
}
|
||||||
6
go.mod
Normal file
6
go.mod
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
module gitea-projekt-dashboard
|
||||||
|
|
||||||
|
go 1.26
|
||||||
|
|
||||||
|
require github.com/beego/beego/v2 v2.1.0
|
||||||
|
require github.com/smartystreets/goconvey v1.6.4
|
||||||
11
main.go
Normal file
11
main.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "gitea-projekt-dashboard/routers"
|
||||||
|
beego "github.com/beego/beego/v2/server/web"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
beego.Run()
|
||||||
|
}
|
||||||
|
|
||||||
10
routers/router.go
Normal file
10
routers/router.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package routers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitea-projekt-dashboard/controllers"
|
||||||
|
beego "github.com/beego/beego/v2/server/web"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
beego.Router("/", &controllers.MainController{})
|
||||||
|
}
|
||||||
1
static/js/reload.min.js
vendored
Normal file
1
static/js/reload.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
function b(a){var c=new WebSocket(a);c.onclose=function(){setTimeout(function(){b(a)},2E3)};c.onmessage=function(){location.reload()}}try{if(window.WebSocket)try{b("ws://localhost:12450/reload")}catch(a){console.error(a)}else console.log("Your browser does not support WebSockets.")}catch(a){console.error("Exception during connecting to Reload:",a)};
|
||||||
42
tests/default_test.go
Normal file
42
tests/default_test.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
"runtime"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/beego/beego/v2/core/logs"
|
||||||
|
|
||||||
|
_ "gitea-projekt-dashboard/routers"
|
||||||
|
|
||||||
|
beego "github.com/beego/beego/v2/server/web"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
_, file, _, _ := runtime.Caller(0)
|
||||||
|
apppath, _ := filepath.Abs(filepath.Dir(filepath.Join(file, ".." + string(filepath.Separator))))
|
||||||
|
beego.TestBeegoInit(apppath)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TestBeego is a sample to run an endpoint test
|
||||||
|
func TestBeego(t *testing.T) {
|
||||||
|
r, _ := http.NewRequest("GET", "/", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
beego.BeeApp.Handlers.ServeHTTP(w, r)
|
||||||
|
|
||||||
|
logs.Trace("testing", "TestBeego", "Code[%d]\n%s", w.Code, w.Body.String())
|
||||||
|
|
||||||
|
Convey("Subject: Test Station Endpoint\n", t, func() {
|
||||||
|
Convey("Status Code Should Be 200", func() {
|
||||||
|
So(w.Code, ShouldEqual, 200)
|
||||||
|
})
|
||||||
|
Convey("The Result Should Not Be Empty", func() {
|
||||||
|
So(w.Body.Len(), ShouldBeGreaterThan, 0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
95
views/index.tpl
Normal file
95
views/index.tpl
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user