all repos — weimar @ 74fc3e1e24b47e370bcf5ea6b4e016cb50630920

Unnamed repository; edit this file 'description' to name the repository.

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
commit

74fc3e1e24b47e370bcf5ea6b4e016cb50630920

parent

da5efcda59a5087b914f716d312d389a1d356143

M internal/api/images.gointernal/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.gointernal/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.svelteweb/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">&middot;</span> {current.width}&times;{current.height} <span class="meta-dot">&middot;</span> {formatSize(current.file_size)} <span class="meta-dot">&middot;</span> {current.mime_type} + <span class="meta-dot">&middot;</span> + UPLOADED BY {current.uploader_username} </div> </div> <div class="viewer-info-right">
M web/src/lib/api.tsweb/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.svelteweb/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} &middot; {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.svelteweb/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} &times; {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>