feat: add WatchMedium field, general review editing, configurable deploy
Some checks failed
CI / Check / Test (push) Failing after 27m0s
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:
@@ -132,6 +132,7 @@ impl ApObjectHandler for ReviewObjectHandler {
|
||||
source: ReviewSource::Remote {
|
||||
actor_url: actor_url_str,
|
||||
},
|
||||
watch_medium: None,
|
||||
});
|
||||
|
||||
self.review_store
|
||||
|
||||
@@ -29,6 +29,7 @@ fn review_to_ap_object_includes_two_hashtags() {
|
||||
created_at: NaiveDateTime::parse_from_str("2024-01-01 00:00:00", "%Y-%m-%d %H:%M:%S")
|
||||
.unwrap(),
|
||||
source: ReviewSource::Local,
|
||||
watch_medium: None,
|
||||
});
|
||||
let obj = review_to_ap_object(
|
||||
&review,
|
||||
@@ -67,6 +68,7 @@ fn review_to_ap_object_has_public_addressing() {
|
||||
created_at: NaiveDateTime::parse_from_str("2024-06-01 00:00:00", "%Y-%m-%d %H:%M:%S")
|
||||
.unwrap(),
|
||||
source: ReviewSource::Local,
|
||||
watch_medium: None,
|
||||
});
|
||||
let actor_url: url::Url = "https://example.com/users/abc".parse().unwrap();
|
||||
let obj = review_to_ap_object(
|
||||
|
||||
@@ -61,6 +61,7 @@ fn make_entry_full(
|
||||
.unwrap()
|
||||
.and_hms_opt(0, 0, 0)
|
||||
.unwrap(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
DiaryEntry::new(movie, review)
|
||||
|
||||
@@ -106,6 +106,7 @@ impl ReviewRow {
|
||||
watched_at,
|
||||
created_at,
|
||||
source,
|
||||
watch_medium: None,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE reviews ADD COLUMN watch_medium TEXT;
|
||||
@@ -60,7 +60,8 @@ impl PostgresDiaryRepository {
|
||||
r.id AS review_id, r.movie_id, r.user_id, r.rating, r.comment,
|
||||
to_char(r.watched_at AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS') AS watched_at,
|
||||
to_char(r.created_at AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS') AS created_at,
|
||||
r.remote_actor_url
|
||||
r.remote_actor_url,
|
||||
r.watch_medium
|
||||
FROM reviews r
|
||||
INNER JOIN movies m ON m.id = r.movie_id
|
||||
ORDER BY {}
|
||||
@@ -93,7 +94,8 @@ impl PostgresDiaryRepository {
|
||||
r.id AS review_id, r.movie_id, r.user_id, r.rating, r.comment,
|
||||
to_char(r.watched_at AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS') AS watched_at,
|
||||
to_char(r.created_at AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS') AS created_at,
|
||||
r.remote_actor_url
|
||||
r.remote_actor_url,
|
||||
r.watch_medium
|
||||
FROM reviews r
|
||||
INNER JOIN movies m ON m.id = r.movie_id
|
||||
WHERE r.movie_id = $1
|
||||
@@ -178,7 +180,8 @@ impl PostgresDiaryRepository {
|
||||
r.id AS review_id, r.movie_id, r.user_id, r.rating, r.comment,
|
||||
to_char(r.watched_at AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS') AS watched_at,
|
||||
to_char(r.created_at AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS') AS created_at,
|
||||
r.remote_actor_url
|
||||
r.remote_actor_url,
|
||||
r.watch_medium
|
||||
FROM reviews r
|
||||
INNER JOIN movies m ON m.id = r.movie_id
|
||||
WHERE r.user_id = $1{remote_clause}{search_clause}
|
||||
@@ -339,6 +342,7 @@ impl DiaryRepository for PostgresDiaryRepository {
|
||||
to_char(r.watched_at AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS') AS watched_at,
|
||||
to_char(r.created_at AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS') AS created_at,
|
||||
r.remote_actor_url,
|
||||
r.watch_medium,
|
||||
COALESCE(u.email, a.handle, r.remote_actor_url) AS user_email
|
||||
FROM reviews r
|
||||
INNER JOIN movies m ON m.id = r.movie_id
|
||||
@@ -432,7 +436,8 @@ impl DiaryRepository for PostgresDiaryRepository {
|
||||
r.id AS review_id, r.movie_id, r.user_id, r.rating, r.comment,
|
||||
to_char(r.watched_at AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS') AS watched_at,
|
||||
to_char(r.created_at AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS') AS created_at,
|
||||
r.remote_actor_url
|
||||
r.remote_actor_url,
|
||||
r.watch_medium
|
||||
FROM reviews r
|
||||
INNER JOIN movies m ON m.id = r.movie_id
|
||||
WHERE r.user_id = $1
|
||||
|
||||
@@ -90,6 +90,7 @@ pub(crate) struct ReviewRow {
|
||||
pub watched_at: String,
|
||||
pub created_at: String,
|
||||
pub remote_actor_url: Option<String>,
|
||||
pub watch_medium: Option<String>,
|
||||
}
|
||||
|
||||
impl ReviewRow {
|
||||
@@ -105,6 +106,7 @@ impl ReviewRow {
|
||||
None => ReviewSource::Local,
|
||||
Some(url) => ReviewSource::Remote { actor_url: url },
|
||||
};
|
||||
let watch_medium = self.watch_medium.map(|s| s.parse()).transpose()?;
|
||||
Ok(Review::from_persistence(PersistedReview {
|
||||
id,
|
||||
movie_id,
|
||||
@@ -114,6 +116,7 @@ impl ReviewRow {
|
||||
watched_at,
|
||||
created_at,
|
||||
source,
|
||||
watch_medium,
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -134,6 +137,7 @@ pub(crate) struct DiaryRow {
|
||||
pub watched_at: String,
|
||||
pub created_at: String,
|
||||
pub remote_actor_url: Option<String>,
|
||||
pub watch_medium: Option<String>,
|
||||
}
|
||||
|
||||
impl DiaryRow {
|
||||
@@ -156,6 +160,7 @@ impl DiaryRow {
|
||||
watched_at: self.watched_at,
|
||||
created_at: self.created_at,
|
||||
remote_actor_url: self.remote_actor_url,
|
||||
watch_medium: self.watch_medium,
|
||||
}
|
||||
.into_domain()?;
|
||||
Ok(DiaryEntry::new(movie, review))
|
||||
@@ -178,6 +183,7 @@ pub(crate) struct FeedRow {
|
||||
pub watched_at: String,
|
||||
pub created_at: String,
|
||||
pub remote_actor_url: Option<String>,
|
||||
pub watch_medium: Option<String>,
|
||||
pub user_email: String,
|
||||
}
|
||||
|
||||
@@ -198,6 +204,7 @@ impl FeedRow {
|
||||
watched_at: self.watched_at,
|
||||
created_at: self.created_at,
|
||||
remote_actor_url: self.remote_actor_url,
|
||||
watch_medium: self.watch_medium,
|
||||
}
|
||||
.into_domain()?;
|
||||
Ok(FeedEntry::new(diary, self.user_email))
|
||||
|
||||
@@ -41,8 +41,8 @@ impl ReviewRepository for PostgresReviewRepository {
|
||||
};
|
||||
|
||||
sqlx::query(
|
||||
"INSERT INTO reviews (id, movie_id, user_id, rating, comment, watched_at, created_at, remote_actor_url)
|
||||
VALUES ($1, $2, $3, $4, $5, $6::timestamptz, $7::timestamptz, $8)",
|
||||
"INSERT INTO reviews (id, movie_id, user_id, rating, comment, watched_at, created_at, remote_actor_url, watch_medium)
|
||||
VALUES ($1, $2, $3, $4, $5, $6::timestamptz, $7::timestamptz, $8, $9)",
|
||||
)
|
||||
.bind(&id)
|
||||
.bind(&movie_id)
|
||||
@@ -52,6 +52,7 @@ impl ReviewRepository for PostgresReviewRepository {
|
||||
.bind(&watched_at)
|
||||
.bind(&created_at)
|
||||
.bind(&remote_actor_url)
|
||||
.bind(review.watch_medium().map(|wm| wm.to_string()))
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(Self::map_err)?;
|
||||
@@ -71,7 +72,8 @@ impl ReviewRepository for PostgresReviewRepository {
|
||||
"SELECT id, movie_id, user_id, rating, comment,
|
||||
to_char(watched_at AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS') AS watched_at,
|
||||
to_char(created_at AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS') AS created_at,
|
||||
remote_actor_url
|
||||
remote_actor_url,
|
||||
watch_medium
|
||||
FROM reviews WHERE id = $1",
|
||||
)
|
||||
.bind(&id)
|
||||
@@ -82,6 +84,28 @@ impl ReviewRepository for PostgresReviewRepository {
|
||||
.transpose()
|
||||
}
|
||||
|
||||
async fn update_review(&self, review: &Review) -> Result<(), DomainError> {
|
||||
let id = review.id().value().to_string();
|
||||
let rating = review.rating().value() as i64;
|
||||
let comment = review.comment().map(|c| c.value().to_string());
|
||||
let watched_at = datetime_to_str(review.watched_at());
|
||||
let watch_medium = review.watch_medium().map(|wm| wm.to_string());
|
||||
|
||||
sqlx::query(
|
||||
"UPDATE reviews SET rating = $1, comment = $2, watched_at = $3::timestamptz, watch_medium = $4 WHERE id = $5",
|
||||
)
|
||||
.bind(rating)
|
||||
.bind(&comment)
|
||||
.bind(&watched_at)
|
||||
.bind(&watch_medium)
|
||||
.bind(&id)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(Self::map_err)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_review(&self, review_id: &ReviewId) -> Result<(), DomainError> {
|
||||
let id = review_id.value().to_string();
|
||||
sqlx::query("DELETE FROM reviews WHERE id = $1")
|
||||
@@ -98,7 +122,8 @@ impl ReviewRepository for PostgresReviewRepository {
|
||||
"SELECT id, movie_id, user_id, rating, comment,
|
||||
to_char(watched_at AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS') AS watched_at,
|
||||
to_char(created_at AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS') AS created_at,
|
||||
remote_actor_url
|
||||
remote_actor_url,
|
||||
watch_medium
|
||||
FROM reviews WHERE user_id = $1 ORDER BY watched_at DESC",
|
||||
)
|
||||
.bind(&uid)
|
||||
|
||||
@@ -106,6 +106,7 @@ impl ReviewRow {
|
||||
watched_at,
|
||||
created_at,
|
||||
source,
|
||||
watch_medium: None,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
1
crates/adapters/sqlite/migrations/0029_watch_medium.sql
Normal file
1
crates/adapters/sqlite/migrations/0029_watch_medium.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE reviews ADD COLUMN watch_medium TEXT;
|
||||
@@ -57,7 +57,7 @@ impl SqliteDiaryRepository {
|
||||
};
|
||||
let sql = format!(
|
||||
"SELECT m.id, m.external_metadata_id, m.title, m.release_year, m.director, m.poster_path,
|
||||
r.id AS review_id, r.movie_id, r.user_id, r.rating, r.comment, r.watched_at, r.created_at, r.remote_actor_url
|
||||
r.id AS review_id, r.movie_id, r.user_id, r.rating, r.comment, r.watched_at, r.created_at, r.remote_actor_url, r.watch_medium
|
||||
FROM reviews r
|
||||
INNER JOIN movies m ON m.id = r.movie_id
|
||||
ORDER BY {}
|
||||
@@ -87,7 +87,7 @@ impl SqliteDiaryRepository {
|
||||
};
|
||||
let sql = format!(
|
||||
"SELECT m.id, m.external_metadata_id, m.title, m.release_year, m.director, m.poster_path,
|
||||
r.id AS review_id, r.movie_id, r.user_id, r.rating, r.comment, r.watched_at, r.created_at, r.remote_actor_url
|
||||
r.id AS review_id, r.movie_id, r.user_id, r.rating, r.comment, r.watched_at, r.created_at, r.remote_actor_url, r.watch_medium
|
||||
FROM reviews r
|
||||
INNER JOIN movies m ON m.id = r.movie_id
|
||||
WHERE r.movie_id = ?
|
||||
@@ -161,7 +161,7 @@ impl SqliteDiaryRepository {
|
||||
};
|
||||
let sql = format!(
|
||||
"SELECT m.id, m.external_metadata_id, m.title, m.release_year, m.director, m.poster_path,
|
||||
r.id AS review_id, r.movie_id, r.user_id, r.rating, r.comment, r.watched_at, r.created_at, r.remote_actor_url
|
||||
r.id AS review_id, r.movie_id, r.user_id, r.rating, r.comment, r.watched_at, r.created_at, r.remote_actor_url, r.watch_medium
|
||||
FROM reviews r
|
||||
INNER JOIN movies m ON m.id = r.movie_id
|
||||
WHERE r.user_id = ?{remote_clause}{search_clause}
|
||||
@@ -308,7 +308,7 @@ impl DiaryRepository for SqliteDiaryRepository {
|
||||
let select_sql = format!(
|
||||
"SELECT m.id, m.external_metadata_id, m.title, m.release_year, m.director, m.poster_path,
|
||||
r.id AS review_id, r.movie_id, r.user_id, r.rating, r.comment,
|
||||
r.watched_at, r.created_at, r.remote_actor_url,
|
||||
r.watched_at, r.created_at, r.remote_actor_url, r.watch_medium,
|
||||
COALESCE(u.email, a.handle, r.remote_actor_url) AS user_email
|
||||
FROM reviews r
|
||||
INNER JOIN movies m ON m.id = r.movie_id
|
||||
@@ -395,7 +395,7 @@ impl DiaryRepository for SqliteDiaryRepository {
|
||||
let uid = user_id.value().to_string();
|
||||
let rows = sqlx::query_as::<_, DiaryRow>(
|
||||
"SELECT m.id, m.external_metadata_id, m.title, m.release_year, m.director, m.poster_path,
|
||||
r.id AS review_id, r.movie_id, r.user_id, r.rating, r.comment, r.watched_at, r.created_at, r.remote_actor_url
|
||||
r.id AS review_id, r.movie_id, r.user_id, r.rating, r.comment, r.watched_at, r.created_at, r.remote_actor_url, r.watch_medium
|
||||
FROM reviews r
|
||||
INNER JOIN movies m ON m.id = r.movie_id
|
||||
WHERE r.user_id = ?
|
||||
@@ -418,7 +418,7 @@ impl DiaryRepository for SqliteDiaryRepository {
|
||||
Box::pin(async_stream::stream! {
|
||||
let mut rows = sqlx::query_as::<_, DiaryRow>(
|
||||
"SELECT m.id, m.external_metadata_id, m.title, m.release_year, m.director, m.poster_path,
|
||||
r.id AS review_id, r.movie_id, r.user_id, r.rating, r.comment, r.watched_at, r.created_at, r.remote_actor_url
|
||||
r.id AS review_id, r.movie_id, r.user_id, r.rating, r.comment, r.watched_at, r.created_at, r.remote_actor_url, r.watch_medium
|
||||
FROM reviews r
|
||||
INNER JOIN movies m ON m.id = r.movie_id
|
||||
WHERE r.user_id = ?
|
||||
@@ -475,7 +475,7 @@ impl DiaryRepository for SqliteDiaryRepository {
|
||||
let rows = sqlx::query_as::<_, FeedRow>(
|
||||
"SELECT m.id, m.external_metadata_id, m.title, m.release_year, m.director, m.poster_path,
|
||||
r.id AS review_id, r.movie_id, r.user_id, r.rating, r.comment,
|
||||
r.watched_at, r.created_at, r.remote_actor_url,
|
||||
r.watched_at, r.created_at, r.remote_actor_url, r.watch_medium,
|
||||
CASE WHEN r.remote_actor_url IS NOT NULL THEN r.remote_actor_url
|
||||
WHEN u.email IS NOT NULL THEN u.email
|
||||
ELSE r.user_id END AS user_email
|
||||
|
||||
@@ -94,6 +94,7 @@ pub(crate) struct ReviewRow {
|
||||
pub watched_at: String,
|
||||
pub created_at: String,
|
||||
pub remote_actor_url: Option<String>,
|
||||
pub watch_medium: Option<String>,
|
||||
}
|
||||
|
||||
impl ReviewRow {
|
||||
@@ -109,6 +110,7 @@ impl ReviewRow {
|
||||
None => ReviewSource::Local,
|
||||
Some(url) => ReviewSource::Remote { actor_url: url },
|
||||
};
|
||||
let watch_medium = self.watch_medium.map(|s| s.parse()).transpose()?;
|
||||
Ok(Review::from_persistence(PersistedReview {
|
||||
id,
|
||||
movie_id,
|
||||
@@ -118,6 +120,7 @@ impl ReviewRow {
|
||||
watched_at,
|
||||
created_at,
|
||||
source,
|
||||
watch_medium,
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -139,6 +142,7 @@ pub(crate) struct DiaryRow {
|
||||
pub watched_at: String,
|
||||
pub created_at: String,
|
||||
pub remote_actor_url: Option<String>,
|
||||
pub watch_medium: Option<String>,
|
||||
}
|
||||
|
||||
impl DiaryRow {
|
||||
@@ -162,6 +166,7 @@ impl DiaryRow {
|
||||
watched_at: self.watched_at,
|
||||
created_at: self.created_at,
|
||||
remote_actor_url: self.remote_actor_url,
|
||||
watch_medium: self.watch_medium,
|
||||
}
|
||||
.into_domain()?;
|
||||
|
||||
@@ -215,6 +220,7 @@ pub(crate) struct FeedRow {
|
||||
pub watched_at: String,
|
||||
pub created_at: String,
|
||||
pub remote_actor_url: Option<String>,
|
||||
pub watch_medium: Option<String>,
|
||||
pub user_email: String,
|
||||
}
|
||||
|
||||
@@ -235,6 +241,7 @@ impl FeedRow {
|
||||
watched_at: self.watched_at,
|
||||
created_at: self.created_at,
|
||||
remote_actor_url: self.remote_actor_url,
|
||||
watch_medium: self.watch_medium,
|
||||
}
|
||||
.into_domain()?;
|
||||
Ok(FeedEntry::new(diary, self.user_email))
|
||||
|
||||
@@ -41,8 +41,8 @@ impl ReviewRepository for SqliteReviewRepository {
|
||||
};
|
||||
|
||||
sqlx::query(
|
||||
"INSERT INTO reviews (id, movie_id, user_id, rating, comment, watched_at, created_at, remote_actor_url)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
"INSERT INTO reviews (id, movie_id, user_id, rating, comment, watched_at, created_at, remote_actor_url, watch_medium)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
)
|
||||
.bind(&id)
|
||||
.bind(&movie_id)
|
||||
@@ -52,6 +52,7 @@ impl ReviewRepository for SqliteReviewRepository {
|
||||
.bind(&watched_at)
|
||||
.bind(&created_at)
|
||||
.bind(&remote_actor_url)
|
||||
.bind(review.watch_medium().map(|wm| wm.to_string()))
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(Self::map_err)?;
|
||||
@@ -68,7 +69,7 @@ impl ReviewRepository for SqliteReviewRepository {
|
||||
async fn get_review_by_id(&self, review_id: &ReviewId) -> Result<Option<Review>, DomainError> {
|
||||
let id = review_id.value().to_string();
|
||||
sqlx::query_as::<_, ReviewRow>(
|
||||
"SELECT id, movie_id, user_id, rating, comment, watched_at, created_at, remote_actor_url
|
||||
"SELECT id, movie_id, user_id, rating, comment, watched_at, created_at, remote_actor_url, watch_medium
|
||||
FROM reviews WHERE id = ?",
|
||||
)
|
||||
.bind(&id)
|
||||
@@ -79,6 +80,28 @@ impl ReviewRepository for SqliteReviewRepository {
|
||||
.transpose()
|
||||
}
|
||||
|
||||
async fn update_review(&self, review: &Review) -> Result<(), DomainError> {
|
||||
let id = review.id().value().to_string();
|
||||
let rating = review.rating().value() as i64;
|
||||
let comment = review.comment().map(|c| c.value().to_string());
|
||||
let watched_at = datetime_to_str(review.watched_at());
|
||||
let watch_medium = review.watch_medium().map(|wm| wm.to_string());
|
||||
|
||||
sqlx::query(
|
||||
"UPDATE reviews SET rating = ?, comment = ?, watched_at = ?, watch_medium = ? WHERE id = ?",
|
||||
)
|
||||
.bind(rating)
|
||||
.bind(&comment)
|
||||
.bind(&watched_at)
|
||||
.bind(&watch_medium)
|
||||
.bind(&id)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(Self::map_err)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_review(&self, review_id: &ReviewId) -> Result<(), DomainError> {
|
||||
let id = review_id.value().to_string();
|
||||
sqlx::query("DELETE FROM reviews WHERE id = ?")
|
||||
@@ -92,7 +115,7 @@ impl ReviewRepository for SqliteReviewRepository {
|
||||
async fn get_all_reviews_for_user(&self, user_id: &UserId) -> Result<Vec<Review>, DomainError> {
|
||||
let uid = user_id.value().to_string();
|
||||
sqlx::query_as::<_, ReviewRow>(
|
||||
"SELECT id, movie_id, user_id, rating, comment, watched_at, created_at, remote_actor_url
|
||||
"SELECT id, movie_id, user_id, rating, comment, watched_at, created_at, remote_actor_url, watch_medium
|
||||
FROM reviews WHERE user_id = ? ORDER BY watched_at DESC",
|
||||
)
|
||||
.bind(&uid)
|
||||
|
||||
@@ -42,6 +42,9 @@
|
||||
<span class="star {% if filled %}filled{% else %}empty{% endif %}">★</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if let Some(wm) = entry.review().watch_medium() %}
|
||||
<span class="watch-medium">{{ wm }}</span>
|
||||
{% endif %}
|
||||
{% if let Some(comment) = entry.review().comment() %}
|
||||
<div class="comment">{{ comment.value() }}</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -35,6 +35,19 @@
|
||||
Comment<br>
|
||||
<textarea name="comment"></textarea>
|
||||
</label>
|
||||
<label>
|
||||
Watched via <span class="optional">(optional)</span><br>
|
||||
<select name="watch_medium">
|
||||
<option value="">—</option>
|
||||
<option value="cinema">Cinema</option>
|
||||
<option value="streaming">Streaming</option>
|
||||
<option value="tv">TV</option>
|
||||
<option value="physical_media">Physical Media</option>
|
||||
<option value="download">Download</option>
|
||||
<option value="media_server">Media Server</option>
|
||||
<option value="other">Other</option>
|
||||
</select>
|
||||
</label>
|
||||
<input type="hidden" name="_csrf" value="{{ ctx.csrf_token }}">
|
||||
<button type="submit">Log Review</button>
|
||||
</form>
|
||||
|
||||
Reference in New Issue
Block a user