add MediaMusic
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 8s
Gitea Actions Demo / build-and-test (push) Failing after 20m33s

This commit is contained in:
Thomas Peetz
2026-10-27 16:15:34 +01:00
parent 72c08f9731
commit 4c681c33dc
15 changed files with 135515 additions and 162 deletions
+5
View File
@@ -46,6 +46,7 @@ type ExportData struct {
MediaArticle []models.MediaArticleDTO `json:"media_article"`
MediaVideo []models.MediaVideoDTO `json:"media_video"`
MediaLoFi models.MediaLoFiDTOList `json:"media_lofi"`
MediaMusic models.MediaMusicDTOList `json:"media_music"`
MediaFile []models.MediaFileDTO `json:"media_file"`
MediaActor []models.MediaActorDTO `json:"media_actor"`
MediaActorFile []models.MediaActorFileDTO `json:"media_actor_file"`
@@ -101,6 +102,7 @@ var (
Mediaactorfiles []models.MediaActorFileDTO
Mediafiles []models.MediaFileDTO
Medialofis models.MediaLoFiDTOList
MediaMusics models.MediaMusicDTOList
Profiles []models.ProfileDTO
Permissions []models.PermissionDTO
Assignments []models.AssignmentDTO
@@ -172,6 +174,7 @@ func GetData(server config.Server, apiVersion string) (data *ExportData, err err
MediaActorFile: Mediaactorfiles,
MediaFile: Mediafiles,
MediaLoFi: Medialofis,
MediaMusic: MediaMusics,
Profile: Profiles,
Permission: Permissions,
Assignment: Assignments,
@@ -184,6 +187,7 @@ func GetData(server config.Server, apiVersion string) (data *ExportData, err err
}
func DeleteData(target config.Server, path, id string) {
slog.Info("DeleteData")
url := fmt.Sprintf("%s/%s/%s/%s", target.Url, config.Config.ApiVersion, path, id)
client := resty.New()
client.SetLoggerWarnLevel(true)
@@ -295,6 +299,7 @@ func init() {
"media/files": &Mediafiles,
"media/actorfiles": &Mediaactorfiles,
"media/lofis": &Medialofis,
"media/musics": &MediaMusics,
"user/profiles": &Profiles,
"user/permissions": &Permissions,
"user/assignments": &Assignments,
+2
View File
@@ -8,6 +8,7 @@ type MediaHandler struct {
MediaActorHandler *MediaActorHandler
MediaFileHandler *MediaFileHandler
MediaLoFiHandler *MediaLoFiHandler
MediaMusicHandler *MediaMusicHandler
MediaVideoHandler *MediaVideoHandler
MediaActorFileHandler *MediaActorFileHandler
}
@@ -17,6 +18,7 @@ func NewMediaHandler(db *gorm.DB) *MediaHandler {
MediaActorHandler: NewMediaActorHandler(db),
MediaFileHandler: NewMediaFileHandler(db),
MediaLoFiHandler: NewMediaLoFiHandler(db),
MediaMusicHandler: NewMediaMusicHandler(db),
MediaVideoHandler: NewMediaVideoHandler(db),
MediaActorFileHandler: NewMediaActorFileHandler(db),
}
+183
View File
@@ -0,0 +1,183 @@
package handlers
import (
"kontor-api-gin/internal/globals"
"kontor-api-gin/internal/models"
"kontor-api-gin/internal/repository"
"kontor-api-gin/internal/service"
"net/http"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/slog"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type MediaMusicHandler struct {
Service *service.MediaMusicService
}
func NewMediaMusicHandler(db *gorm.DB) *MediaMusicHandler {
mediaMusicHandler := &MediaMusicHandler{
Service: service.NewMediaMusicService(repository.NewMediaMusicRepository(db)),
}
return mediaMusicHandler
}
func (handler *MediaMusicHandler) List(c *gin.Context) {
musics, err := handler.Service.List(c.Request.Context())
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
dtos := models.ConvertMediaMusicsToDtos(musics)
c.JSON(http.StatusOK, dtos)
}
func (handler *MediaMusicHandler) Get(c *gin.Context) {
id := c.Param("id")
music, err := handler.Service.Get(c.Request.Context(), id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "MediaMusic not found"})
return
}
c.JSON(http.StatusOK, music)
}
func (handler *MediaMusicHandler) Add(c *gin.Context) {
var input models.MediaLofiAdd
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
music, err := handler.Service.Add(c.Request.Context(), input.Url)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "MediaMusic not added"})
return
}
c.JSON(http.StatusOK, music)
}
func (handler *MediaMusicHandler) Update(c *gin.Context) {
var input models.MediaMusicDTO
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
music, err := handler.Service.Update(c.Request.Context(), input)
if err != nil {
logger := slog.Get(c)
logger.Info("MediaMusicHandler#Update", "error", err.Error())
c.JSON(http.StatusNotFound, gin.H{"error": "MediaMusic not updated"})
return
}
c.JSON(http.StatusOK, music)
}
func (handler *MediaMusicHandler) Delete(c *gin.Context) {
id := c.Param("id")
err := handler.Service.Delete(c.Request.Context(), id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "MediaMusic not found"})
return
}
c.JSON(http.StatusOK, gin.H{"ok": "true"})
}
func (handler *MediaMusicHandler) ListPage(c *gin.Context) {
session := sessions.Default(c)
user := session.Get(globals.UserKey)
logger := slog.Get(c)
var (
review bool
download bool
)
paramReview, existsReview := c.GetQuery("review")
if existsReview {
if paramReview == "on" {
review = true
} else {
review = false
}
}
paramDownload, existsDownload := c.GetQuery("should_download")
if existsDownload {
if paramDownload == "on" {
download = true
} else {
download = false
}
}
musics, err := handler.Service.List(c.Request.Context())
if err != nil {
logger.Info("MediaMusicHandler#ListPage", "error", err.Error())
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
logger.Info("MediaMusicHandler#ListPage", "MediaMusic count", len(musics))
data := globals.GetDefaultValues(user, "MediaMusic...")
data["media_links"] = musics
data["MediaLinksUrl"] = "/web/media-musics"
data["isReview"] = review
data["isDownload"] = download
c.HTML(http.StatusOK, "media_links", data)
}
func (handler *MediaMusicHandler) EditPage(c *gin.Context) {
session := sessions.Default(c)
user := session.Get(globals.UserKey)
id := c.Param("id")
logger := slog.Get(c)
music, err := handler.Service.Get(c.Request.Context(), id)
if err != nil {
logger.Info("MediaMusicHandler#EditPage", "error", err.Error())
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
logger.Info("MediaMusicHandler#EditPage", "music", music)
data := globals.GetDefaultValues(user, "MediaMusic...")
data["lofi"] = music
c.HTML(http.StatusOK, "lofi_detail", data)
}
func (handler *MediaMusicHandler) UpdatePage(c *gin.Context) {
session := sessions.Default(c)
user := session.Get(globals.UserKey)
id := c.Param("id")
logger := slog.Get(c)
music, err := handler.Service.Get(c.Request.Context(), id)
if err != nil {
logger.Info("MediaMusicHandler#UpdatePage", "error", err.Error())
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
var form models.MediaMusicForm
if err := c.ShouldBind(&form); err != nil {
logger.Info("MediaMusicHandler#UpdatePage", "error", err.Error())
data := globals.GetDefaultValues(user, "MediaMusic...")
data["Form"] = form
data["Errors"] = err.Error()
c.HTML(http.StatusBadRequest, "lofi_detail", data)
return
}
logger.Info("MediaMusicHandler#UpdatePage", "music", music)
//handler.Service.Update(c.Request.Context(), form.ConvertFormToMediaLofiDTO())
c.Redirect(http.StatusFound, "web/media-musics")
}
func (handler *MediaMusicHandler) DeletePage(c *gin.Context) {
// session := sessions.Default(c)
// user := session.Get(globals.UserKey)
id := c.Param("id")
logger := slog.Get(c)
err := handler.Service.Delete(c.Request.Context(), id)
if err != nil {
logger.Info("MediaMusicHandler#DeletePage", "error", err.Error())
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
logger.Info("MediaMusicHandler#DeletePage", "music", id)
c.Redirect(http.StatusFound, "web/media-musics")
}
+177
View File
@@ -0,0 +1,177 @@
package models
import (
"fmt"
"kontor-api-gin/internal/config"
"kontor-api-gin/internal/helpers"
"log/slog"
"time"
)
type MediaMusic struct {
ID string `gorm:"type:string;primary_key;" json:"id"`
CreatedAt time.Time `gorm:"column:created_date" json:"created_date"`
UpdatedAt time.Time `gorm:"column:last_modified_date" json:"last_modified_date"`
DeletedAt *time.Time `gorm:"column:deleted_date" json:"deleted_date"`
Version int `gorm:"default:0" json:"version"`
WebLink string `gorm:"column:url" json:"url"`
Review bool `gorm:"column:review" json:"review"`
ShouldDownload bool `gorm:"column:should_download" json:"should_download"`
Title string `gorm:"size:300;column:title" json:"title"`
FileName string `gorm:"column:file_name" json:"file_name"`
}
func (media_music *MediaMusic) Update(update MediaMusicDTO) {
media_music.CreatedAt = update.CreatedAt.Time
media_music.UpdatedAt = time.Now()
media_music.Version = media_music.Version + 1
media_music.WebLink = update.WebLink
media_music.Review = update.Review
media_music.ShouldDownload = update.ShouldDownload
media_music.Title = update.Title
media_music.FileName = update.FileName
}
func (media_music *MediaMusic) TableName() string {
return "media_music"
}
func (media_music *MediaMusic) ConvertMediaMusicToDTO() MediaMusicDTO {
dto := MediaMusicDTO{
ID: media_music.ID,
CreatedAt: helpers.KontorTime{Time: media_music.CreatedAt},
UpdatedAt: helpers.KontorTime{Time: media_music.UpdatedAt},
DeletedAt: media_music.DeletedAt,
Version: media_music.Version,
WebLink: media_music.WebLink,
Review: media_music.Review,
ShouldDownload: media_music.ShouldDownload,
Title: media_music.Title,
FileName: media_music.FileName,
}
return dto
}
type MediaMusicAdd struct {
Url string `json:"url"`
}
type MediaMusicForm struct {
Title string `form:"title" binding:"required"`
Url string `form:"url" binding:"required"`
}
func (form *MediaMusicForm) ConvertFormToMediaMusicDTO() MediaMusicDTO {
media_music := MediaMusicDTO{}
return media_music
}
type MediaMusicDTO struct {
ID string `json:"id" binding:"required"`
CreatedAt helpers.KontorTime `json:"created_date"`
UpdatedAt helpers.KontorTime `json:"last_modified_date"`
DeletedAt *time.Time `json:"deleted_date,omitempty"`
Version int `json:"version"`
FileName string `json:"file_name"`
Review bool `json:"review"`
ShouldDownload bool `json:"should_download"`
Title string `json:"title"`
WebLink string `json:"url"`
}
func (source MediaMusicDTO) Equals(target MediaMusicDTO) bool {
if config.Verbose > 1 {
slog.Info("MediaMusicDTO#Equals", "id", source.ID)
}
equals := true
if source.Title != target.Title {
if config.Verbose > 1 {
slog.Info("MediaMusicDTO#Equals: Title changed")
}
equals = false
}
if source.WebLink != target.WebLink {
if config.Verbose > 1 {
slog.Info("MediaMusicDTO#Equals: WebLink changed")
}
equals = false
}
if source.Version != target.Version {
if config.Verbose > 1 {
slog.Info("MediaMusicDTO#Equals: Version changed")
}
equals = false
}
if source.FileName != target.FileName {
if config.Verbose > 1 {
slog.Info("MediaMusicDTO#Equals: FileName changed")
}
equals = false
}
if source.Review != target.Review {
if config.Verbose > 1 {
slog.Info("MediaMusicDTO#Equals: Review changed")
}
equals = false
}
if source.ShouldDownload != target.ShouldDownload {
if config.Verbose > 1 {
slog.Info("MediaMusicDTO#Equals: ShouldDownload changed")
}
equals = false
}
return equals
}
type MediaMusicDTOList []MediaMusicDTO
func (source MediaMusicDTOList) GetMediaMusic(music_id string) *MediaMusicDTO {
for _, music := range source {
if music.ID == music_id {
return &music
}
}
return nil
}
func (source *MediaMusicDTOList) CompareIds(target *MediaMusicDTOList) (missing, obsolete, changed []string) {
sources := make(map[string]MediaMusicDTO, len(*source))
targets := make(map[string]MediaMusicDTO, len(*target))
for _, source := range *source {
sources[source.ID] = source
}
for _, target := range *target {
targets[target.ID] = target
}
// iterate source IDs
for id := range sources {
if _, ok := targets[id]; ok {
if config.Verbose > 1 {
slog.Info(fmt.Sprintf("ID %s found in both lists", id))
}
if !sources[id].Equals(targets[id]) {
changed = append(changed, id)
}
} else {
missing = append(missing, id)
}
}
for id := range targets {
if _, ok := sources[id]; ok {
slog.Debug(fmt.Sprintf("ID %s found in both lists", id))
} else {
obsolete = append(obsolete, id)
}
}
slog.Info("CompareIds", "missing", len(missing), "obsolete", len(obsolete), "changed", len(changed))
slog.Debug("CompareIds", "missing", missing, "obsolete", obsolete)
return missing, obsolete, changed
}
func ConvertMediaMusicsToDtos(media_musics []MediaMusic) MediaMusicDTOList {
dtos := make(MediaMusicDTOList, len(media_musics))
for i, media_music := range media_musics {
dtos[i] = media_music.ConvertMediaMusicToDTO()
}
return dtos
}
@@ -0,0 +1,103 @@
package repository
import (
"context"
"errors"
"fmt"
"kontor-api-gin/internal/helpers"
"kontor-api-gin/internal/models"
"log/slog"
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type MediaMusicRepository struct {
db *gorm.DB
}
func NewMediaMusicRepository(db *gorm.DB) *MediaMusicRepository {
repo := &MediaMusicRepository{db: db}
return repo
}
func (repository *MediaMusicRepository) FindAll(context context.Context) ([]models.MediaMusic, error) {
var musics []models.MediaMusic
result := repository.db.Find(&musics)
slog.Info("MediaMusicRepository#FindAll", "count", result.RowsAffected)
return musics, nil
}
func (repository *MediaMusicRepository) GetById(context context.Context, id string) (*models.MediaMusic, error) {
var music = models.MediaMusic{ID: id}
slog.Info("MediaMusicRepository#GetById", "id", id)
result := repository.db.First(&music)
if result.Error != nil {
slog.Info("MediaMusicRepository#GetById", "error", result.Error.Error())
}
return &music, result.Error
}
func (repository *MediaMusicRepository) Add(context context.Context, url string) (*models.MediaMusic, error) {
slog.Info("MediaMusicRepository#Add", "url", url)
webLink := helpers.StripUrl(url, "v")
var music models.MediaMusic
search_result := repository.db.Where("url = ?", webLink).First(&music)
if search_result.RowsAffected > 0 {
slog.Info("search for existing url", "result", search_result, "music", music)
return &music, nil
}
if errors.Is(search_result.Error, gorm.ErrRecordNotFound) {
slog.Info("MediaMusicRepository#Add URL not found", "url", webLink)
music = models.MediaMusic{
ID: uuid.NewString(),
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
DeletedAt: nil,
Version: 0,
WebLink: webLink,
ShouldDownload: false,
Review: true,
Title: "",
FileName: "",
}
add_result := repository.db.Create(&music)
if add_result.Error != nil {
slog.Info("add new MediaMusic", "error", add_result.Error)
return nil, add_result.Error
}
slog.Info("add new MediaMusic", "music", music)
return &music, nil
}
if search_result.Error != nil {
return nil, search_result.Error
}
return nil, fmt.Errorf("MediaMusicRepository#Add unknown error")
}
func (repository *MediaMusicRepository) Update(context context.Context, update models.MediaMusicDTO) (*models.MediaMusic, error) {
slog.Info("MediaMusicRepository#Update", "music", update)
var music = models.MediaMusic{ID: update.ID}
slog.Info("MediaMusicRepository#Update", "id", update.ID)
result := repository.db.First(&music)
if result.Error != nil {
slog.Info("MediaMusicRepository#Update", "error", result.Error.Error())
return nil, result.Error
}
music.Update(update)
repository.db.Save(&music)
return &music, nil
}
func (repository *MediaMusicRepository) Delete(context context.Context, id string) error {
var actor = models.MediaMusic{ID: id}
slog.Info("MediaMusicRepository#Delete", "id", id)
result := repository.db.Delete(&actor)
if result.Error != nil {
slog.Info("MediaMusicRepository#Delete", "error", result.Error.Error())
}
return result.Error
}
+4
View File
@@ -71,6 +71,10 @@ func New(admin *handlers.AdminHandler, bookshelf *handlers.BookshelfHandler, com
m.POST("/media-lofis/:id/edit", media.MediaLoFiHandler.UpdatePage)
m.GET("/media-lofis/:id/change-review", media.MediaLoFiHandler.ChangeReviewPage)
m.GET("/media-lofis/:id/delete", media.MediaLoFiHandler.DeletePage)
m.GET("/media-musics", media.MediaMusicHandler.ListPage)
m.GET("/media-musics/:id", media.MediaMusicHandler.EditPage)
m.POST("/media-musics/:id", media.MediaMusicHandler.UpdatePage)
m.GET("/media-musics/:id/delete", media.MediaMusicHandler.DeletePage)
api := router.Group("/api")
api.POST("/login", admin.AuthHandler.Login)
@@ -0,0 +1,42 @@
package service
import (
"context"
"kontor-api-gin/internal/models"
"kontor-api-gin/internal/repository"
"log/slog"
)
type MediaMusicService struct {
repository *repository.MediaMusicRepository
}
func NewMediaMusicService(repo *repository.MediaMusicRepository) *MediaMusicService {
service := &MediaMusicService{repository: repo}
return service
}
func (service *MediaMusicService) List(context context.Context) ([]models.MediaMusic, error) {
slog.Info("MediaMusicService#List")
return service.repository.FindAll(context)
}
func (service *MediaMusicService) Get(context context.Context, id string) (*models.MediaMusic, error) {
slog.Info("MediaMusicService#Get")
return service.repository.GetById(context, id)
}
func (service *MediaMusicService) Add(context context.Context, url string) (*models.MediaMusic, error) {
slog.Info("MediaMusicService#Add")
return service.repository.Add(context, url)
}
func (service *MediaMusicService) Update(context context.Context, music models.MediaMusicDTO) (*models.MediaMusic, error) {
slog.Info("MediaMusicService#Update")
return service.repository.Update(context, music)
}
func (service *MediaMusicService) Delete(context context.Context, id string) error {
slog.Info("MediaMusicService#Delete")
return service.repository.Delete(context, id)
}
File diff suppressed because it is too large Load Diff
+1
View File
@@ -24,6 +24,7 @@
<a class="dropdown-item" href="/web/media-files">Media Files</a>
<a class="dropdown-item" href="/web/actors">Media Actors</a>
<a class="dropdown-item" href="/web/media-lofis">Media Lofi</a>
<a class="dropdown-item" href="/web/media-musics">Media Music</a>
<a class="dropdown-item" href="/web/media-videos">Media Videos</a>
<a class="dropdown-item" href="/web/articles">Artikel</a>
</div>
@@ -0,0 +1,71 @@
{{ define "media_links" }}
<!doctype html>
<html lang="de">
{{ template "head" . }}
<body>
{{ template "navbar" . }}
<main>
<div class="row">
<form class="form-inline" action="{{ .MediaLinksUrl }}">
<input type="text" placeholder="Search" name="query">
<label>
<input type="checkbox" name="review" {{ if .Review }}checked{{ end }}>Review
</label>
<label>
<input type="checkbox" name="download" {{ if .ShouldDownload }}checked{{ end }}>Download
</label>
<button type="submit">Search</button>
<div class="pill-nav"><a href="{{ .MediaLinksUrl }}/add">Add MediaFile</a></div>
</form>
</div>
<div class="row">
<div class="maincolumn">
<table class="table table-hover">
<thead>
<tr>
<th scope="col">Title</th>
<th scope="col">Weblink</th>
<th scope="col">Review</th>
<th scope="col">Download</th>
<th colspan="2">Actions</th>
</tr>
</thead>
<tbody>
{{ range .media_links }}
<tr>
<th scope="row"><a href="{{ .MediaLinksUrl }}/{{.ID}}">{{.Title}}</a></th>
{{ if .WebLink }}
<td><a href="{{.WebLink}}">{{.WebLink}}</a></td>
{{ else }}
<td>no link</td>
{{ end }}
<td>
{{ if .Review }}
<img src="/public/icons/tick.png" alt="true" width="24" height="24">
{{ else }}
<img src="/public/icons/cross.png" alt="true" width="24" height="24">
{{ end }}
</td>
<td>
{{ if .ShouldDownload }}
<img src="/public/icons/tick.png" alt="true" width="24" height="24">
{{ else }}
<img src="/public/icons/cross.png" alt="true" width="24" height="24">
{{ end }}
</td>
<td><a href="{{ .MediaLinksUrl }}/{{.ID}}" class="btn btn-outline-primary btn-sm active" role="button"
aria-pressed="true">Edit</a></td>
<td><a href="{ .MediaLinksUrl }/{{.ID}}/delete" class="btn btn-outline-danger btn-sm active"
role="button" aria-pressed="true">Delete</a></td>
</tr>
{{ end }}
</tbody>
</table>
</div>
</div>
</main>
{{ template "footer" . }}
</body>
</html>
{{ end }}