feat: show uploader username and database ID on media - Added UploaderUsername field to Image model, populated from users table in both list and detail API responses - Shows uploader username in browse card overlay, ImageViewer info bar, and image detail page - Shows database ID (#id) alongside metadata in all three locations for admin CLI convenience 💘 Generated with Crush Assisted-by: Crush:deepseek-reasoner
Maxwell Jensen maxwelljensen@posteo.net
Wed, 13 May 2026 16:31:26 +0200
6 files changed,
82 insertions(+),
39 deletions(-)
M
internal/api/images.go
→
internal/api/images.go
@@ -26,19 +26,25 @@ writeError(w, http.StatusInternalServerError, "failed to list images")
return } - // Load tags for each image. + // Load tags and uploader for each image. for i := range result.Images { - tags, err := a.DB.GetImageTags(r.Context(), result.Images[i].ID) - if err != nil { - continue + img := &result.Images[i] + + tags, err := a.DB.GetImageTags(r.Context(), img.ID) + if err == nil { + img.Tags = tags } - result.Images[i].Tags = tags + + user, err := a.DB.GetUserByID(r.Context(), img.UploadedBy) + if err == nil { + img.UploaderUsername = user.Username + } } writeJSON(w, http.StatusOK, map[string]any{ - "images": result.Images, - "total": result.Total, - "page": page, + "images": result.Images, + "total": result.Total, + "page": page, "per_page": perPage, }) }@@ -60,21 +66,28 @@
tagsDetail, err := a.DB.GetImageTagsWithCounts(r.Context(), id) if err != nil { tagsDetail = make([]db.Tag, 0) + } + + uploaderUsername := "" + user, err := a.DB.GetUserByID(r.Context(), img.UploadedBy) + if err == nil { + uploaderUsername = user.Username } writeJSON(w, http.StatusOK, map[string]any{ - "id": img.ID, - "filename": img.Filename, - "storage_path": img.StoragePath, - "thumbnail_path": img.ThumbnailPath, - "uploaded_by": img.UploadedBy, - "file_size": img.FileSize, - "width": img.Width, - "height": img.Height, - "mime_type": img.MimeType, - "created_at": img.CreatedAt, - "updated_at": img.UpdatedAt, - "tags": img.Tags, - "tags_detail": tagsDetail, + "id": img.ID, + "filename": img.Filename, + "storage_path": img.StoragePath, + "thumbnail_path": img.ThumbnailPath, + "uploaded_by": img.UploadedBy, + "uploader_username": uploaderUsername, + "file_size": img.FileSize, + "width": img.Width, + "height": img.Height, + "mime_type": img.MimeType, + "created_at": img.CreatedAt, + "updated_at": img.UpdatedAt, + "tags": img.Tags, + "tags_detail": tagsDetail, }) }
M
internal/db/models.go
→
internal/db/models.go
@@ -20,18 +20,19 @@ CreatedAt time.Time `json:"created_at"`
} type Image struct { - ID int64 `json:"id"` - Filename string `json:"filename"` - StoragePath string `json:"storage_path"` - ThumbnailPath *string `json:"thumbnail_path"` - UploadedBy int64 `json:"uploaded_by"` - FileSize int64 `json:"file_size"` - Width int `json:"width"` - Height int `json:"height"` - MimeType string `json:"mime_type"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Tags []string `json:"tags,omitempty"` + ID int64 `json:"id"` + Filename string `json:"filename"` + StoragePath string `json:"storage_path"` + ThumbnailPath *string `json:"thumbnail_path"` + UploadedBy int64 `json:"uploaded_by"` + UploaderUsername string `json:"uploader_username"` + FileSize int64 `json:"file_size"` + Width int `json:"width"` + Height int `json:"height"` + MimeType string `json:"mime_type"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Tags []string `json:"tags,omitempty"` } type Tag struct {
M
web/src/lib/ImageViewer.svelte
→
web/src/lib/ImageViewer.svelte
@@ -150,11 +150,15 @@ <div class="viewer-info" class:controls-hidden={!showControls}>
<div class="viewer-info-left"> <div class="viewer-filename">{current.filename}</div> <div class="viewer-meta"> + #{current.id} + <span class="meta-dot">·</span> {current.width}×{current.height} <span class="meta-dot">·</span> {formatSize(current.file_size)} <span class="meta-dot">·</span> {current.mime_type} + <span class="meta-dot">·</span> + UPLOADED BY {current.uploader_username} </div> </div> <div class="viewer-info-right">
M
web/src/lib/api.ts
→
web/src/lib/api.ts
@@ -14,6 +14,7 @@ filename: string;
storage_path: string; thumbnail_path: string | null; uploaded_by: number; + uploader_username: string; file_size: number; width: number; height: number;
M
web/src/routes/browse/+page.svelte
→
web/src/routes/browse/+page.svelte
@@ -1,5 +1,5 @@
<script lang="ts"> - import { onMount } from 'svelte'; + import { onMount, onDestroy } from 'svelte'; import { listImages, thumbnailUrl, type Image } from '$lib/api'; import ImageViewer from '$lib/ImageViewer.svelte';@@ -13,9 +13,22 @@ let viewerIndex = $state(-1);
const perPage = 60; onMount(() => { + const mainEl = document.querySelector('.main') as HTMLElement; + if (mainEl) { + mainEl.style.padding = '0'; + mainEl.style.maxWidth = 'none'; + } const params = new URLSearchParams(window.location.search); filterTag = params.get('tag') || ''; loadImages(); + }); + + onDestroy(() => { + const mainEl = document.querySelector('.main') as HTMLElement; + if (mainEl) { + mainEl.style.padding = ''; + mainEl.style.maxWidth = ''; + } }); async function loadImages() {@@ -98,6 +111,7 @@ {/if}
</div> <div class="card-overlay"> <div class="card-title">{img.filename}</div> + <div class="card-user">#{img.id} · {img.uploader_username}</div> </div> </button> {/each}@@ -119,11 +133,6 @@ />
{/if} <style> - :global(.main) { - padding: 0 !important; - max-width: none !important; - } - .header { padding: 1.5rem 1.5rem 0.75rem; }@@ -287,6 +296,13 @@ font-weight: 600;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + } + .card-user { + color: rgba(255, 255, 255, 0.55); + font-family: 'Inter', sans-serif; + font-size: 0.68rem; + font-weight: 500; + margin-top: 0.05rem; } .pagination {
M
web/src/routes/image/[id]/+page.svelte
→
web/src/routes/image/[id]/+page.svelte
@@ -51,6 +51,10 @@ </div>
<div class="meta"> <div class="meta-block"> + <span class="meta-label">ID</span> + <span class="meta-value">#{img.id}</span> + </div> + <div class="meta-block"> <span class="meta-label">DIMENSIONS</span> <span class="meta-value">{img.width} × {img.height}</span> </div>@@ -61,6 +65,10 @@ </div>
<div class="meta-block"> <span class="meta-label">TYPE</span> <span class="meta-value">{img.mime_type}</span> + </div> + <div class="meta-block"> + <span class="meta-label">UPLOADED BY</span> + <span class="meta-value">{img.uploader_username}</span> </div> <div class="meta-block"> <span class="meta-label">UPLOADED</span>