Number entries on publish, delete media, read the cover

- drafts stay unnumbered, the number is handed out when an entry goes live
- DELETE /api/v1/media/:id and a delete control in the media library
- cover_media_id in the draft read, coverMediaId and coverUrl in the archive read
This commit is contained in:
Matthias G
2026-08-01 12:25:01 +02:00
parent afaa323848
commit 2fd71fd406
31 changed files with 2286 additions and 76 deletions
+63 -14
View File
@@ -3,10 +3,12 @@ import { asc, eq } from 'drizzle-orm'
import { db } from '~/data/db'
import { posts } from '~/data/schema'
import {
assignEntryNumber,
createEntry,
listEntries,
listEntryBlocks,
replaceEntryBlocks,
setEntryStatus,
type EntryCreateValues,
} from '~/data/repositories/entries'
import { makeBrand, makeProject } from '../support/admin'
@@ -26,7 +28,7 @@ function values(projectId: string, slug: string, title = 'Beitrag'): EntryCreate
}
}
async function numbersOf(projectId: string): Promise<number[]> {
async function numbersOf(projectId: string): Promise<(number | null)[]> {
const rows = await db
.select({ number: posts.number })
.from(posts)
@@ -36,28 +38,62 @@ async function numbersOf(projectId: string): Promise<number[]> {
return rows.map(row => row.number)
}
describe('createEntry vergibt Beitragsnummern', () => {
it('zählt je Projekt getrennt hoch', async () => {
async function publish(id: string): Promise<number | null> {
const post = await setEntryStatus(id, 'published', new Date())
return post?.number ?? null
}
describe('Beitragsnummern', () => {
it('lässt Entwürfe ohne Nummer', async () => {
const brand = await makeBrand()
const project = await makeProject(brand.id)
const created = await createEntry(values(project.id, 'entwurf'))
expect(created.number).toBeNull()
expect(await numbersOf(project.id)).toEqual([null])
})
it('zählt beim Veröffentlichen je Projekt getrennt hoch', async () => {
const brand = await makeBrand()
const trakk = await makeProject(brand.id, 'trakk', 'Trakk')
const orbit = await makeProject(brand.id, 'orbit', 'Orbit')
await createEntry(values(trakk.id, 'eins'))
await createEntry(values(trakk.id, 'zwei'))
await createEntry(values(orbit.id, 'eins'))
const first = await createEntry(values(trakk.id, 'eins'))
const second = await createEntry(values(trakk.id, 'zwei'))
const third = await createEntry(values(orbit.id, 'eins'))
expect(await numbersOf(trakk.id)).toEqual([1, 2])
expect(await numbersOf(orbit.id)).toEqual([1])
expect(await publish(first.id)).toBe(1)
expect(await publish(second.id)).toBe(2)
expect(await publish(third.id)).toBe(1)
})
it('vergibt bei gleichzeitigem Anlegen lückenlose Nummern ohne Doppelung', async () => {
it('lässt keine Lücke, wenn ein Entwurf dazwischen gelöscht wird', async () => {
const brand = await makeBrand()
const project = await makeProject(brand.id)
await Promise.all(
const first = await createEntry(values(project.id, 'eins'))
const scrapped = await createEntry(values(project.id, 'verworfen'))
const second = await createEntry(values(project.id, 'zwei'))
await publish(first.id)
await db.delete(posts).where(eq(posts.id, scrapped.id))
await publish(second.id)
expect(await numbersOf(project.id)).toEqual([1, 2])
})
it('vergibt bei gleichzeitigem Veröffentlichen lückenlose Nummern ohne Doppelung', async () => {
const brand = await makeBrand()
const project = await makeProject(brand.id)
const created = await Promise.all(
Array.from({ length: 20 }, (_, index) => createEntry(values(project.id, `beitrag-${index}`))),
)
await Promise.all(created.map(entry => publish(entry.id)))
expect(await numbersOf(project.id)).toEqual(Array.from({ length: 20 }, (_, index) => index + 1))
})
@@ -69,10 +105,23 @@ describe('createEntry vergibt Beitragsnummern', () => {
const created = await createEntry(values(project.id, 'neuer-beitrag'))
expect(created.number).toBe(143)
expect(await publish(created.id)).toBe(143)
})
it('hängt die Nummer an, wenn der Slug im Projekt schon vergeben ist', async () => {
it('vergibt eine bereits gesetzte Nummer nicht neu', async () => {
const brand = await makeBrand()
const project = await makeProject(brand.id)
const created = await createEntry(values(project.id, 'eins'))
await publish(created.id)
const again = await assignEntryNumber(created.id)
expect(again?.number).toBe(1)
})
it('hängt eine Zahl an, wenn der Slug im Projekt schon vergeben ist', async () => {
const brand = await makeBrand()
const project = await makeProject(brand.id)
@@ -80,7 +129,7 @@ describe('createEntry vergibt Beitragsnummern', () => {
const second = await createEntry(values(project.id, 'regel-engine'))
expect(first.slug).toBe('regel-engine')
expect(second.slug).toBe(`regel-engine-${second.number}`)
expect(second.slug).toBe('regel-engine-2')
})
it('übernimmt Titel, Autor und Zielgruppe', async () => {
@@ -94,7 +143,7 @@ describe('createEntry vergibt Beitragsnummern', () => {
authorName: 'Paul',
audience: 'customer',
status: 'draft',
number: 1,
number: null,
})
})
})