feat: add back to main page buttons in admin and login
This commit is contained in:
1 parent
696f2ef50a
commit
611cd82763
5 files changed
+66
-51
No files matched your search
+49
-48
@@ -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
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 }),
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in new issue
Block a user