ADD: added basic backend function plus a mockup for a cli interface

This commit is contained in:
2025-12-13 21:44:48 +01:00
parent c6de2481e6
commit a047d57824
21 changed files with 657 additions and 51 deletions

8
backend/cmd/cli/main.go Normal file
View File

@@ -0,0 +1,8 @@
// cmd/cli/main.go
package main
import "studia/cmd/cli/root"
func main() {
root.Execute()
}

View File

@@ -0,0 +1,25 @@
package root
import (
"fmt"
"os"
"studia/cmd/cli/user"
"github.com/spf13/cobra"
)
var RootCmd = &cobra.Command{
Use: "myapp",
Short: "MyApp admin CLI",
}
func Execute() {
if err := RootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func init() {
RootCmd.AddCommand(user.UserCmd)
}

View File

@@ -0,0 +1,38 @@
// cmd/cli/user/get.go
package user
import (
"fmt"
// "studia/internal/db"
usersvc "studia/internal/user"
"github.com/spf13/cobra"
)
var id int64
var getCmd = &cobra.Command{
Use: "get",
Short: "Get user by ID",
RunE: func(cmd *cobra.Command, args []string) error {
database, err := db.New(getDSN())
if err != nil {
return err
}
service := usersvc.NewService(database)
user, err := service.GetByID(id)
if err != nil {
return err
}
fmt.Printf("ID: %d\nEmail: %s\nName: %s\n",
user.ID, user.Email, user.Name)
return nil
},
}
func init() {
getCmd.Flags().Int64Var(&id, "id", 0, "user ID")
getCmd.MarkFlagRequired("id")
}

View File

@@ -0,0 +1,32 @@
// cmd/cli/user/list.go
package user
import (
"fmt"
"myapp/internal/db"
usersvc "myapp/internal/user"
"github.com/spf13/cobra"
)
var listCmd = &cobra.Command{
Use: "list",
Short: "List users",
RunE: func(cmd *cobra.Command, args []string) error {
database, err := db.New(getDSN())
if err != nil {
return err
}
service := usersvc.NewService(database)
users, err := service.List()
if err != nil {
return err
}
for _, u := range users {
fmt.Printf("%d | %s | %s\n", u.ID, u.Email, u.Name)
}
return nil
},
}

View File

@@ -0,0 +1,13 @@
package user
import "github.com/spf13/cobra"
var UserCmd = &cobra.Command{
Use: "user",
Short: "User management",
}
func init() {
UserCmd.AddCommand(listCmd)
UserCmd.AddCommand(getCmd)
}

View File

@@ -1,12 +1,23 @@
package main
import (
"studia/internal/server"
"log"
"studia/internal/config"
"studia/internal/server"
)
func main() {
cfg := config.New()
cfg.DatabaseHost = "192.168.178.171"
cfg.DatabasePort = "5432"
cfg.DatabaseUser = "admin"
cfg.DatabasePassword = "12345678"
cfg.DatabaseName = "studia"
log.Println("Configuration loaded:", cfg)
log.Println("Starting server...")
server.StartServer()
server.StartServer(cfg)
}

View File

@@ -1,9 +1,21 @@
CREATE TABLE users (
id UUID PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(255) NOT NULL,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- Table: public.users
-- DROP TABLE IF EXISTS public.users;
CREATE TABLE IF NOT EXISTS public.users
(
id character varying(255) COLLATE pg_catalog."default" NOT NULL DEFAULT uuid_generate_v4(),
email character varying(255) COLLATE pg_catalog."default" NOT NULL,
name character varying(255) COLLATE pg_catalog."default" NOT NULL,
password_hash character varying(255) COLLATE pg_catalog."default" NOT NULL,
created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
updated_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT users_pkey PRIMARY KEY (id),
CONSTRAINT users_email_key UNIQUE (email)
)
TABLESPACE pg_default;
ALTER TABLE IF EXISTS public.users
OWNER to admin;

View File

@@ -7,27 +7,34 @@ require (
github.com/golang-jwt/jwt/v5 v5.3.0
)
require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/pflag v1.0.9 // indirect
)
require (
github.com/bytedance/sonic v1.14.0 // indirect
github.com/bytedance/sonic/loader v0.3.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
github.com/gin-contrib/cors v1.7.6
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.27.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-yaml v1.18.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lib/pq v1.10.9
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/quic-go/quic-go v0.54.0 // indirect
github.com/spf13/cobra v1.10.2
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.0 // indirect
go.uber.org/mock v0.5.0 // indirect

View File

@@ -4,11 +4,16 @@ github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZw
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY=
github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
@@ -23,6 +28,8 @@ github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHO
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
@@ -30,6 +37,8 @@ github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArs
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
@@ -42,6 +51,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
@@ -52,6 +63,11 @@ github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -67,6 +83,7 @@ github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=

View File

@@ -17,7 +17,7 @@ type LoginRequest struct {
var secret = []byte("secret")
func LoginHandler(c *gin.Context, db *sql.DB) {
func Login(c *gin.Context, db *sql.DB) {
var req LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})

View File

@@ -0,0 +1,48 @@
package config
import (
"os"
)
type Config struct {
Port string
Env string
DatabaseDriver string
DatabaseHost string
DatabasePort string
DatabaseName string
DatabaseUser string
DatabasePassword string
FrontendURL string
}
func New() *Config {
return generateConfig()
}
func generateConfig() *Config {
cfg := Config{
Port: getEnv("PORT", "8080"),
DatabaseDriver: getEnv("DB_DRIVER", "postgres"),
DatabaseHost: getEnv("database", "localhost"),
DatabasePort: getEnv("DB_PORT", "5432"),
DatabaseName: getEnv("DB_NAME", "studia"),
DatabaseUser: getEnv("DB_USER", "user"),
DatabasePassword: getEnv("DB_PASSWORD", "password"),
Env: getEnv("ENV", "development"),
FrontendURL: getEnv("FRONTEND_URL", "http://localhost:5173"),
}
return &cfg
}
// helper function to get env var or default
func getEnv(key, defaultVal string) string {
value, exists := os.LookupEnv(key)
if !exists || value == "" {
return defaultVal
}
return value
}

View File

@@ -0,0 +1,97 @@
package database
import (
"database/sql"
"fmt"
"log"
"studia/internal/config"
_ "github.com/lib/pq" // Import the PostgreSQL driver
)
var expectedTables = []string{
"users",
}
func New(cfg *config.Config) *sql.DB {
db := setupDatabase(cfg)
existing, err := getExistingTables(db, `
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'public'
`)
if err != nil {
log.Println("failed to query existing tables:", err)
}
missing := checkTables(expectedTables, existing)
if len(missing) > 0 {
log.Println("Missing tables detected:", missing)
// Here you would normally run migrations to create the missing tables
// For simplicity, we just log the missing tables
} else {
log.Println("All expected tables are present.")
}
return db
}
func setupDatabase(cfg *config.Config) *sql.DB {
// Database connection setup logic here
log.Println(cfg)
switch cfg.DatabaseDriver {
case "postgres":
// Setup Postgres connection
log.Println("Setting up Postgres connection")
psqlInfo := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
cfg.DatabaseHost, cfg.DatabasePort, cfg.DatabaseUser, cfg.DatabasePassword, cfg.DatabaseName)
db, err := sql.Open("postgres", psqlInfo)
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
return db
case "mysql":
// Setup MySQL connection
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s",
cfg.DatabaseUser, cfg.DatabasePassword, cfg.DatabaseHost, cfg.DatabasePort, cfg.DatabaseName)
db, err := sql.Open("mysql", dsn)
if err != nil {
// Handle error
}
return db
default:
// Handle unsupported database driver
}
return nil
}
func getExistingTables(db *sql.DB, query string) (map[string]bool, error) {
rows, err := db.Query(query)
if err != nil {
return nil, err
}
defer rows.Close()
tables := make(map[string]bool)
for rows.Next() {
var name string
if err := rows.Scan(&name); err != nil {
return nil, err
}
tables[name] = true
}
return tables, nil
}
func checkTables(expected []string, existing map[string]bool) []string {
var missing []string
for _, table := range expected {
if !existing[table] {
missing = append(missing, table)
}
}
return missing
}

View File

@@ -1,26 +1,60 @@
package server
import (
"fmt"
"log"
"os"
"studia/internal/auth"
"studia/internal/config"
"studia/internal/database"
"time"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
)
var secret = []byte("secret")
func StartServer(cfg *config.Config) {
func StartServer() {
router := gin.Default()
r := gin.Default()
db := database.New(cfg)
r.POST("/login", func(c *gin.Context) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"user": "demo",
"role": "admin",
"exp": time.Now().Add(24 * time.Hour).Unix(),
})
signed, _ := token.SignedString(secret)
c.JSON(200, gin.H{"token": signed})
// 2. CORS-Konfiguration
// Lese die Frontend-URL aus den Umgebungsvariablen
frontendURL := os.Getenv("FRONTEND_URL")
// Lokaler Fallback (wichtig für die Entwicklung)
allowedOrigins := []string{
"http://localhost:5173", // Gängiger Vite-Dev-Port
}
if frontendURL != "" {
allowedOrigins = append(allowedOrigins, frontendURL)
fmt.Printf("CORS: Erlaubte Produktiv-URL hinzugefügt: %s\n", frontendURL)
} else {
log.Println("ACHTUNG: FRONTEND_URL fehlt in den Umgebungsvariablen. Nur lokale URLs erlaubt.")
}
// CORS
// Konfiguriere die CORS-Middleware
config := cors.Config{
// Setze die erlaubten Ursprünge (deine React-URLs)
AllowOrigins: allowedOrigins,
// Erlaube die notwendigen HTTP-Methoden
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"},
// Erlaube Header (z.B. für JSON und Authentifizierung)
AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization"},
// Erlaube Cookies und Credentials (falls du Tokens oder Sessions nutzt)
AllowCredentials: true,
// Wie lange die Preflight-Anfrage (OPTIONS) gecacht werden darf
MaxAge: 12 * time.Hour,
}
router.Use(cors.New(config))
router.POST("/login", func(c *gin.Context) {
auth.Login(c, db) // Pass the actual DB connection instead of nil
})
router.Run(":" + cfg.Port)
}