feat: add back to main page buttons in admin and login

This commit is contained in:
Jannik committed 2026-09-24 04:24:06 +02:00
1 parent 696f2ef50a
commit 611cd82763
5 files changed
+66 -51

No files matched your search

+49 -48
View File
@@ -15,52 +15,53 @@ enum MediaType {
}
enum PlacementMode {
RANKING // Position determined by likes ranking
FIXED_POSITION // Position manually set by admin
RANKING // Position determined by likes ranking
FIXED_POSITION // Position manually set by admin
FIXED_SIZE_RANKING // Position from ranking, but size is fixed
FIXED_POSITION_ADJUSTABLE // Position and size set by admin
}
model MediaItem {
id Int @id @default(autoincrement())
relativePath String @unique // Path relative to MEDIA_ROOT, e.g. "Fotobox/001.jpg"
filename String // Just the filename, e.g. "001.jpg"
directory String // Parent directory, e.g. "Fotobox"
mediaType MediaType @default(IMAGE)
mimeType String @default("application/octet-stream")
fileSize BigInt @default(0)
width Int? // Pixels, for images and videos
height Int? // Pixels, for images and videos
duration Float? // Seconds, for video and audio
id Int @id @default(autoincrement())
relativePath String @unique // Path relative to MEDIA_ROOT, e.g. "Fotobox/001.jpg"
filename String // Just the filename, e.g. "001.jpg"
directory String // Parent directory, e.g. "Fotobox"
mediaType MediaType @default(IMAGE)
mimeType String @default("application/octet-stream")
fileSize BigInt @default(0)
width Int? // Pixels, for images and videos
height Int? // Pixels, for images and videos
duration Float? // Seconds, for video and audio
// Publication
published Boolean @default(false)
published Boolean @default(false)
wasOncePublished Boolean @default(false)
// Layout / Placement
featured Boolean @default(false) // Prominent/title image
featured Boolean @default(false) // Prominent/title image
placementMode PlacementMode @default(RANKING)
gridColumn Int? // For fixed position (grid column start)
gridRow Int? // For fixed position (grid row start)
gridColumn Int? // For fixed position (grid column start)
gridRow Int? // For fixed position (grid row start)
gridWidth Int @default(1) // Width in grid units
gridHeight Int @default(1) // Height in grid units
sortOrder Int @default(0) // Manual sort order / ranking tiebreak
// Processing status
thumbnailReady Boolean @default(false)
previewReady Boolean @default(false)
thumbnailReady Boolean @default(false)
previewReady Boolean @default(false)
// File tracking
fileHash String? // For detecting file changes
lastModified DateTime? // File's mtime from filesystem
lastScanned DateTime @default(now())
fileHash String? // For detecting file changes
lastModified DateTime? // File's mtime from filesystem
lastScanned DateTime @default(now())
// Timestamps
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
likes Like[]
likes Like[]
@@index([published])
@@index([directory])
@@index([mediaType])
@@ -71,12 +72,12 @@ model MediaItem {
model Like {
id Int @id @default(autoincrement())
mediaItemId Int
ipHash String // SHA-256 hash of IP address
fingerprint String? // Optional browser fingerprint hash
ipHash String // SHA-256 hash of IP address
fingerprint String? // Optional browser fingerprint hash
createdAt DateTime @default(now())
mediaItem MediaItem @relation(fields: [mediaItemId], references: [id], onDelete: Cascade)
mediaItem MediaItem @relation(fields: [mediaItemId], references: [id], onDelete: Cascade)
@@unique([mediaItemId, ipHash]) // One like per IP per media item
@@index([ipHash])
@@index([mediaItemId])
@@ -84,14 +85,14 @@ model Like {
}
model AppSettings {
id Int @id @default(1)
votingOpen Boolean @default(false)
zipVersion Int @default(0)
zipReady Boolean @default(false)
zipBuildAt DateTime?
zipFilePath String? // Path to current valid ZIP
lastScanAt DateTime?
updatedAt DateTime @updatedAt
id Int @id @default(1)
votingOpen Boolean @default(false)
zipVersion Int @default(0)
zipReady Boolean @default(false)
zipBuildAt DateTime?
zipFilePath String? // Path to current valid ZIP
lastScanAt DateTime?
updatedAt DateTime @updatedAt
}
model AdminSession {
@@ -100,18 +101,18 @@ model AdminSession {
userAgent String @default("")
createdAt DateTime @default(now())
expiresAt DateTime
@@index([expiresAt])
}
model RateLimitEntry {
id Int @id @default(autoincrement())
ipHash String
action String // e.g. "login", "like"
action String // e.g. "login", "like"
windowStart DateTime
count Int @default(1)
@@unique([ipHash, action, windowStart])
@@index([ipHash, action, windowStart])
@@index([windowStart]) // For cleanup of old entries
@@index([windowStart]) // For cleanup of old entries
}
+4
View File
@@ -2,6 +2,7 @@
import React, { useState } from 'react';
import { useRouter } from 'next/navigation';
import Link from 'next/link';
export default function AdminLoginPage() {
const [password, setPassword] = useState('');
@@ -56,6 +57,9 @@ export default function AdminLoginPage() {
<button type="submit" className="btn-primary" style={{ justifyContent: 'center' }} disabled={loading}>
{loading ? 'Laden...' : 'Anmelden'}
</button>
<Link href="/" className="btn-secondary" style={{ display: 'flex', justifyContent: 'center', marginTop: '10px', textDecoration: 'none' }}>
Zurück zur Hauptseite
</Link>
</form>
</div>
);
+8 -2
View File
@@ -9,6 +9,7 @@ type MediaItem = {
relativePath: string;
filename: string;
published: boolean;
wasOncePublished: boolean;
featured: boolean;
mediaType: string;
};
@@ -77,7 +78,7 @@ export default function AdminDashboard() {
const togglePublished = async (id: number, current: boolean) => {
// Optimistic UI
setItems((prev) =>
prev.map((i) => (i.id === id ? { ...i, published: !current } : i))
prev.map((i) => (i.id === id ? { ...i, published: !current, ...( !current ? { wasOncePublished: true } : {} ) } : i))
);
setStats((prev) => ({
...prev,
@@ -123,7 +124,11 @@ export default function AdminDashboard() {
// Optimistic UI
setItems((prev) =>
prev.map((i) => ({ ...i, published: isPublished }))
prev.map((i) => ({
...i,
published: isPublished,
...(isPublished ? { wasOncePublished: true } : {})
}))
);
setStats({
total: items.length,
@@ -150,6 +155,7 @@ export default function AdminDashboard() {
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px', flexWrap: 'wrap', gap: '10px' }}>
<h2 style={{ fontSize: '1.5rem', fontFamily: 'var(--font-display)' }}>Admin Dashboard</h2>
<div style={{ display: 'flex', gap: '10px' }}>
<Link href="/" className="btn-secondary" style={{ textDecoration: 'none' }}>Zurück zur Hauptseite</Link>
<button className="btn-secondary" onClick={handleScan}>Neu Scannen</button>
<Link href="/admin/layout" className="btn-secondary" style={{ textDecoration: 'none' }}>Layout Editor</Link>
<button className="btn-primary" onClick={handleLogout}>Logout</button>
+1
View File
@@ -37,6 +37,7 @@ export async function PATCH(
where: { id },
data: {
...(published !== undefined && { published }),
...(published === true && { wasOncePublished: true }),
...(featured !== undefined && { featured }),
...(placementMode !== undefined && { placementMode }),
...(gridColumn !== undefined && { gridColumn }),
+4 -1
View File
@@ -24,7 +24,10 @@ export async function PATCH(request: NextRequest) {
where: {
id: { in: ids }
},
data: { published }
data: {
published,
...(published === true ? { wasOncePublished: true } : {})
}
});
if (result.count > 0) {