umstellung auf Service und Repository Verwendung - WIP
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 4s

This commit is contained in:
Thomas Peetz
2026-09-25 16:57:23 +02:00
parent 131296c644
commit f0f1819a1f
42 changed files with 998 additions and 351 deletions
+29
View File
@@ -3,6 +3,7 @@ package controllers
import (
"kontor-api-gin/pkg/globals"
"kontor-api-gin/pkg/helpers"
"kontor-api-gin/pkg/schema"
"net/http"
"github.com/gin-contrib/sessions"
@@ -11,6 +12,34 @@ import (
"github.com/gin-gonic/gin"
)
type LoginInput struct {
Email string `json:"email" binding:"required"`
Password string `json:"password" binding:"required"`
}
func LoginHandler(c *gin.Context) {
var input LoginInput
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
profile := schema.Profile{}
profile.Email = input.Email
profile.Password = input.Password
token, err := schema.LoginCheck(profile.Email, profile.Password)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "username or password is incorrect."})
return
}
c.JSON(http.StatusOK, gin.H{"token": token})
}
func LoginGetHandler(c *gin.Context) {
session := sessions.Default(c)
user := session.Get(globals.UserKey)
+2
View File
@@ -3,6 +3,7 @@ package controllers
import (
"kontor-api-gin/pkg/globals"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
@@ -11,5 +12,6 @@ func HealthGetHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"status": "healthy",
"version": globals.Version,
"time": time.Now().UTC().Format(time.RFC3339),
})
}
+1 -4
View File
@@ -18,10 +18,7 @@ func ListMediaFiles(c *gin.Context) {
// result := query.Find(&files)
result := schema.DB.Find(&files)
logger.Info("ListMediaFiles", "count", result.RowsAffected)
c.JSON(http.StatusOK, gin.H{
"message": "All MediaFiles",
"files": files,
})
c.JSON(http.StatusOK, files)
}
func ListMediaLoFis(c *gin.Context) {
-32
View File
@@ -1,32 +0,0 @@
package globals
import (
"fmt"
"log/slog"
"strings"
"github.com/spf13/viper"
)
func InitConfig() error {
viper.SetConfigName("kontor-gin")
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
viper.AddConfigPath("$HOME/.config")
viper.AddConfigPath("/etc/kontor")
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.AutomaticEnv()
// Read the config file
if err := viper.ReadInConfig(); err != nil {
// Handle the case where config file is not found
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
slog.Info("No config file found, using defaults")
return nil
}
return fmt.Errorf("error reading config file: %w", err)
}
slog.Info("initConfig", "Using config file: ", viper.ConfigFileUsed())
return nil
}
-11
View File
@@ -1,11 +0,0 @@
package globals
var Secret = []byte("secret")
const UserKey = "user"
const Version = "0.4.0"
const LoginUrl = "/login"
const LogoutUrl = "/auth/logout"
-11
View File
@@ -1,11 +0,0 @@
package helpers
import "strings"
func CheckUserPass(username, password string) bool {
return true
}
func EmptyUserPass(username, password string) bool {
return strings.Trim(username, " ") == "" || strings.Trim(password, " ") == ""
}
@@ -1,7 +1,8 @@
package middleware
import (
"kontor-api-gin/pkg/globals"
"kontor-api-gin/internal/globals"
"kontor-api-gin/internal/helpers"
"net/http"
"github.com/gin-contrib/sessions"
@@ -22,3 +23,15 @@ func AuthRequired(c *gin.Context) {
}
c.Next()
}
func JwtRequired(c *gin.Context) {
logger := slog.Get(c)
err := helpers.TokenValid(c)
logger.Info("JwtRequired", "TokenValid", err)
if err != nil {
c.String(http.StatusUnauthorized, "Unauthorized")
c.Abort()
return
}
c.Next()
}
+5 -1
View File
@@ -1,10 +1,14 @@
package routes
import (
"kontor-api-gin/pkg/controllers"
"github.com/gin-gonic/gin"
)
func SetupApiRoutes(router *gin.Engine) *gin.RouterGroup {
v1 := router.Group("api/v1")
api := router.Group("/api")
api.POST("/login", controllers.LoginHandler)
v1 := api.Group("/v1")
return v1
}
-22
View File
@@ -1,22 +0,0 @@
package schema
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type Base struct {
ID string `gorm:"type:string;primary_key;"`
CreatedAt time.Time `json:"created_date"`
UpdatedAt time.Time `json:"last_modified_date"`
DeletedAt *time.Time `json:"deleted_date"`
Version int `gorm:"default:0" json:"version"`
}
// BeforeCreate will set a UUID rather than numeric ID.
func (b *Base) BeforeCreate(tx *gorm.DB) (err error) {
b.ID = uuid.New().String()
return
}
-151
View File
@@ -1,151 +0,0 @@
package schema
import (
"time"
)
type Publisher struct {
Base
Name string `gorm:"uniqueIndex" json:"name"`
WebLink string `json:"weblink"`
ParentPublisherID *string `json:"parent_publisher_id"`
ParentPublisher *Publisher `gorm:"foreignKey:ParentPublisherID"`
Imprints []Publisher `gorm:"foreignKey:ParentPublisherID"`
Comics []Comic `gorm:"foreignKey:PublisherID"`
}
func (publisher *Publisher) TableName() string {
return "publisher"
}
type Comic struct {
Base
Title string `gorm:"uniqueIndex" json:"title"`
CurrentOrder bool `json:"current_order"`
Completed bool `json:"completed"`
WebLink string `json:"weblink"`
PublisherID *string `json:"publisher_id"`
Publisher *Publisher `gorm:"foreignKey:PublisherID"`
Issues []Issue `gorm:"foreignKey:ComicID"`
StoryArcs []StoryArc `gorm:"foreignKey:ComicID"`
TradePaperbacks []TradePaperback `gorm:"foreignKey:ComicID"`
Volumes []Volume `gorm:"foreignKey:ComicID"`
ComicWorks []ComicWork `gorm:"foreignKey:ComicID"`
}
func (comic *Comic) TableName() string {
return "comic"
}
type Artist struct {
Base
Name string `gorm:"uniqueIndex" json:"name"`
WebLink string `json:"weblink"`
}
func (artist *Artist) TableName() string {
return "artist"
}
type Issue struct {
Base
InStock bool `json:"in_stock"`
IsRead bool `json:"is_read"`
IssueNumber string `json:"issue_number"`
Title string `json:"title"`
PublishedOn time.Time `json:"published_on"`
ComicID *string `json:"comic_id"`
Comic *Comic `gorm:"foreignKey:ComicID"`
StoryArcID *string `json:"story_arc_id"`
StoryArc *StoryArc `gorm:"foreignKey:StoryArcID"`
VolumeID *string `json:"volume_id"`
Volume *Volume `gorm:"foreignKey:VolumeID"`
}
func (issue *Issue) TableName() string {
return "issue"
}
type StoryArc struct {
Base
Name string `gorm:"uniqueIndex" json:"name"`
ComicID *string `json:"comic_id"`
Comic *Comic `gorm:"foreignKey:ComicID"`
VolumeID *string `json:"volume_id"`
Volume *Volume `gorm:"foreignKey:VolumeID"`
}
func (story_arc *StoryArc) TableName() string {
return "story_arc"
}
type TradePaperback struct {
Base
Name string `gorm:"uniqueIndex" json:"name"`
IssueStart int `json:"issue_start"`
IssueEnd int `json:"issue_end"`
ComicID *string `json:"comic_id"`
Comic *Comic `gorm:"foreignKey:ComicID"`
}
func (trade_paperback *TradePaperback) TableName() string {
return "trade_paperback"
}
type Volume struct {
Base
Name string `gorm:"uniqueIndex" json:"name"`
ComicID *string `json:"comic_id"`
Comic *Comic `gorm:"foreignKey:ComicID"`
}
func (volume *Volume) TableName() string {
return "volume"
}
type WorkType struct {
Base
Name string `gorm:"uniqueIndex" json:"name"`
}
func (worktype *WorkType) TableName() string {
return "worktype"
}
type ComicWork struct {
Base
ArtistID *string `json:"artist_id"`
Artist *Artist `gorm:"foreignKey:ArtistID"`
ComicID *string `json:"comic_id"`
Comic *Comic `gorm:"foreignKey:ComicID"`
WorkTypeID *string `json:"work_type_id"`
WorkType *WorkType `gorm:"foreignKey:WorkTypeID"`
}
func (comic_work *ComicWork) TableName() string {
return "comic_work"
}
type IssueWork struct {
Base
ArtistID *string `json:"artist_id"`
Artist *Artist `gorm:"foreignKey:ArtistID"`
IssueID *string `json:"issue_id"`
Issue *Issue `gorm:"foreignKey:IssueID"`
WorkTypeID *string `json:"work_type_id"`
WorkType *WorkType `gorm:"foreignKey:WorkTypeID"`
}
func (issue_work *IssueWork) TableName() string {
return "issue_work"
}
-69
View File
@@ -1,69 +0,0 @@
package schema
import (
"fmt"
"log"
"log/slog"
"time"
"github.com/spf13/viper"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
var DB *gorm.DB
func Connect() {
// Build the connection string from environment variables
// This keeps sensitive credentials out of the codebase
dsn := fmt.Sprintf(
"host=%s user=%s password=%s dbname=%s port=%d sslmode=disable",
viper.GetString("db.host"),
viper.GetString("db.user"),
viper.GetString("db.password"),
viper.GetString("db.name"),
viper.GetInt("db.port"),
)
// Configure GORM with custom settings
// The logger helps with debugging SQL queries during development
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
})
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
// Get the underlying SQL database connection for pool configuration
sqlDB, err := db.DB()
if err != nil {
log.Fatal("Failed to get database instance:", err)
}
// Configure connection pool settings
// MaxIdleConns: number of connections kept open when idle
sqlDB.SetMaxIdleConns(10)
// MaxOpenConns: maximum total connections to the database
sqlDB.SetMaxOpenConns(100)
// ConnMaxLifetime: how long a connection can be reused
sqlDB.SetConnMaxLifetime(time.Hour)
DB = db
slog.Info("Database connected successfully")
}
func RunMigrations() {
// AutoMigrate creates tables, missing columns, and missing indexes
// It will NOT delete unused columns to protect your data
err := DB.AutoMigrate(
&MediaActor{},
&MediaFile{},
&MediaActorFile{},
)
if err != nil {
log.Fatal("Migration failed:", err)
}
slog.Info("Database migration completed")
}
-92
View File
@@ -1,92 +0,0 @@
package schema
import "time"
type MediaLoFi struct {
ID string `gorm:"type:string;primary_key;"`
Created_Date time.Time `json:"created_date"`
Last_Modified_date time.Time `json:"last_modified_date"`
Deleted_Date *time.Time `json:"deleted_date"`
Version int `gorm:"default:0" json:"version"`
Url string `gorm:"uniqueIndex" json:"url"`
Review bool `json:"review"`
ShouldDownload bool `json:"should_download"`
Title string `gorm:"size:300" json:"title"`
FileName string `json:"file_name"`
}
func (media_lofi *MediaLoFi) TableName() string {
return "media_lofi"
}
type MediaFile struct {
Base
CloudLink string `json:"cloud_link"`
FileName string `json:"file_name"`
Path string `json:"path"`
Review bool `json:"review"`
Title string `gorm:"size:300" json:"title"`
WebLink string `gorm:"uniqueIndex" json:"url"`
ShouldDownload bool `json:"should_download"`
MediaActorFiles []MediaActorFile `gorm:"foreignKey:MediaFileID" json:"media_actor_files"`
}
func (media_file *MediaFile) TableName() string {
return "media_file"
}
type MediaActor struct {
Base
Name string `json:"name"`
WebLink string `gorm:"uniqueIndex" json:"url"`
MediaActorFiles []MediaActorFile `gorm:"foreignKey:MediaActorID" json:"media_actor_files"`
}
func (media_actor *MediaActor) TableName() string {
return "media_actor"
}
type MediaActorFile struct {
Base
MediaActorID *string `json:"media_actor_id"`
MediaActor *MediaActor `gorm:"foreignKey:MediaActorID" json:"media_actor"`
MediaFileID *string `json:"media_file_id"`
MediaFile *MediaFile `gorm:"foreignKey:MediaFileID" json:"media_file"`
}
func (media_file *MediaActorFile) TableName() string {
return "media_actor_file"
}
// type MediaArticle struct {
// bun.BaseModel `bun:"table:media_article"`
// ID string `bun:"id,pk"`
// CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
// UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
// Version int `bun:"version,default:0"`
// Review bool `bun:"review"`
// Title string `bun:"title"`
// WebLink string `bun:"url,unique:url"`
// }
// type MediaVideo struct {
// bun.BaseModel `bun:"table:media_article"`
// ID string `bun:"id,pk"`
// CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
// UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
// Version int `bun:"version,default:0"`
// CloudLink string `bun:"cloud_link"`
// FileName string `bun:"file_name"`
// Path string `bun:"path"`
// Review bool `bun:"review"`
// Title string `bun:"title"`
// WebLink string `bun:"url,unique:url"`
// ShouldDownload bool `bun:"should_download"`
// }