feat: add WatchMedium field, general review editing, configurable deploy
Some checks failed
CI / Check / Test (push) Failing after 27m0s

- WatchMedium enum (cinema/streaming/tv/physical_media/download/media_server/other)
- PATCH /api/v1/reviews/:id partial update (rating, comment, watched_at, watch_medium)
- edit_review use case w/ ownership + remote review guard, best-effort AP Update broadcast
- SPA: icon picker, edit sheet (long-press mobile / pencil desktop), watch medium badge
- shared ReviewFormFields, EditableContextMenu, parse_watched_at/format_watched_at
- deploy.sh parameterized (--features, --tag), CORS allows PATCH
- CONTEXT.md glossary, ADR-0001 general review editing
This commit is contained in:
2026-07-10 00:01:14 +02:00
parent 9794babe06
commit 29cc68b07c
72 changed files with 1298 additions and 124 deletions

View File

@@ -157,6 +157,17 @@ export async function putForm<T = void>(
})
}
export async function patch<T = void>(
path: string,
body?: unknown,
): Promise<T> {
return request<T>(buildUrl(path), {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: body ? JSON.stringify(body) : undefined,
})
}
export async function del<T = void>(path: string): Promise<T> {
return request<T>(buildUrl(path), { method: "DELETE" })
}

View File

@@ -19,6 +19,7 @@ export const reviewDtoSchema = z.object({
rating: z.number(),
comment: z.string().optional(),
watched_at: z.string(),
watch_medium: z.string().optional(),
})
export type ReviewDto = z.infer<typeof reviewDtoSchema>

View File

@@ -1,7 +1,7 @@
import { z } from "zod"
import type { DiaryEntryDto, Paginated } from "./common"
import { diaryEntryDtoSchema, movieDtoSchema, paginatedSchema, reviewDtoSchema } from "./common"
import { del, get, post } from "./client"
import { del, get, patch, post } from "./client"
export const diaryQueryParamsSchema = z.object({
limit: z.number().optional(),
@@ -22,9 +22,18 @@ export const logReviewRequestSchema = z.object({
rating: z.number(),
comment: z.string().optional(),
watched_at: z.string(),
watch_medium: z.string().optional(),
})
export type LogReviewRequest = z.infer<typeof logReviewRequestSchema>
export const editReviewRequestSchema = z.object({
rating: z.number().optional(),
comment: z.string().nullable().optional(),
watched_at: z.string().optional(),
watch_medium: z.string().nullable().optional(),
})
export type EditReviewRequest = z.infer<typeof editReviewRequestSchema>
export const feedEntryDtoSchema = z.object({
movie: movieDtoSchema,
review: reviewDtoSchema,
@@ -58,6 +67,10 @@ export function logReview(data: LogReviewRequest) {
return post("/reviews", data)
}
export function editReview(id: string, data: EditReviewRequest) {
return patch(`/reviews/${id}`, data)
}
export function deleteReview(id: string) {
return del(`/reviews/${id}`)
}

View File

@@ -29,6 +29,7 @@ export const socialReviewDtoSchema = z.object({
comment: z.string().optional(),
watched_at: z.string(),
is_federated: z.boolean(),
watch_medium: z.string().optional(),
})
export type SocialReviewDto = z.infer<typeof socialReviewDtoSchema>

View File

@@ -0,0 +1,26 @@
import type { LucideIcon } from "lucide-react"
import {
Clapperboard,
Tv,
Radio,
Disc3,
Download,
Server,
Ellipsis,
} from "lucide-react"
export type WatchMediumDef = {
value: string
icon: LucideIcon
labelKey: string
}
export const WATCH_MEDIUMS: WatchMediumDef[] = [
{ value: "cinema", icon: Clapperboard, labelKey: "watchMedium.cinema" },
{ value: "streaming", icon: Tv, labelKey: "watchMedium.streaming" },
{ value: "tv", icon: Radio, labelKey: "watchMedium.tv" },
{ value: "physical_media", icon: Disc3, labelKey: "watchMedium.physicalMedia" },
{ value: "download", icon: Download, labelKey: "watchMedium.download" },
{ value: "media_server", icon: Server, labelKey: "watchMedium.mediaServer" },
{ value: "other", icon: Ellipsis, labelKey: "watchMedium.other" },
]