feat: add title columns and migration tracking
This commit is contained in:
@@ -17,6 +17,56 @@ import (
|
||||
"malago/internal/templates"
|
||||
)
|
||||
|
||||
func runMigrations(db *sql.DB) error {
|
||||
// Create migration tracking table
|
||||
_, err := db.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS migration_version (
|
||||
name TEXT PRIMARY KEY,
|
||||
applied_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
migrations := []string{
|
||||
"migrations/001_init.sql",
|
||||
"migrations/002_add_anime_titles.sql",
|
||||
}
|
||||
|
||||
for _, migrationFile := range migrations {
|
||||
// Check if migration already applied
|
||||
var exists int
|
||||
err := db.QueryRow("SELECT COUNT(*) FROM migration_version WHERE name = ?", migrationFile).Scan(&exists)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists > 0 {
|
||||
log.Printf("migration %s already applied, skipping", migrationFile)
|
||||
continue
|
||||
}
|
||||
|
||||
// Read and execute migration
|
||||
migrationSQL, err := os.ReadFile(migrationFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := db.Exec(string(migrationSQL)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Mark as applied
|
||||
_, err = db.Exec("INSERT INTO migration_version (name) VALUES (?)", migrationFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("migration %s applied successfully", migrationFile)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
dbFile := os.Getenv("DATABASE_FILE")
|
||||
if dbFile == "" {
|
||||
@@ -29,12 +79,8 @@ func main() {
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Run migrations (assuming local dev setup, simplistic execution)
|
||||
migrationSQL, err := os.ReadFile("migrations/001_init.sql")
|
||||
if err != nil {
|
||||
log.Fatalf("failed to read migrations: %v", err)
|
||||
}
|
||||
if _, err := db.Exec(string(migrationSQL)); err != nil {
|
||||
// Run migrations with tracking
|
||||
if err := runMigrations(db); err != nil {
|
||||
log.Fatalf("failed to run migrations: %v", err)
|
||||
}
|
||||
|
||||
|
||||
12
internal/database/helpers.go
Normal file
12
internal/database/helpers.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package database
|
||||
|
||||
// DisplayTitle returns the English title if available, otherwise Japanese, otherwise original
|
||||
func (r GetUserWatchListRow) DisplayTitle() string {
|
||||
if r.TitleEnglish.Valid && r.TitleEnglish.String != "" {
|
||||
return r.TitleEnglish.String
|
||||
}
|
||||
if r.TitleJapanese.Valid && r.TitleJapanese.String != "" {
|
||||
return r.TitleJapanese.String
|
||||
}
|
||||
return r.TitleOriginal
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -17,10 +18,12 @@ type Account struct {
|
||||
}
|
||||
|
||||
type Anime struct {
|
||||
ID int64 `json:"id"`
|
||||
Title string `json:"title"`
|
||||
ImageUrl string `json:"image_url"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
ID int64 `json:"id"`
|
||||
TitleOriginal string `json:"title_original"`
|
||||
ImageUrl string `json:"image_url"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
TitleEnglish sql.NullString `json:"title_english"`
|
||||
TitleJapanese sql.NullString `json:"title_japanese"`
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
|
||||
@@ -24,10 +24,12 @@ DELETE FROM session WHERE id = ?;
|
||||
DELETE FROM session WHERE user_id = ?;
|
||||
|
||||
-- name: UpsertAnime :one
|
||||
INSERT INTO anime (id, title, image_url)
|
||||
VALUES (?, ?, ?)
|
||||
INSERT INTO anime (id, title_original, title_english, title_japanese, image_url)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
title = excluded.title,
|
||||
title_original = excluded.title_original,
|
||||
title_english = excluded.title_english,
|
||||
title_japanese = excluded.title_japanese,
|
||||
image_url = excluded.image_url
|
||||
RETURNING *;
|
||||
|
||||
@@ -47,7 +49,12 @@ SELECT * FROM watch_list_entry
|
||||
WHERE user_id = ? AND anime_id = ? LIMIT 1;
|
||||
|
||||
-- name: GetUserWatchList :many
|
||||
SELECT e.*, a.title, a.image_url
|
||||
SELECT
|
||||
e.*,
|
||||
a.title_original,
|
||||
a.title_english,
|
||||
a.title_japanese,
|
||||
a.image_url
|
||||
FROM watch_list_entry e
|
||||
JOIN anime a ON e.anime_id = a.id
|
||||
WHERE e.user_id = ?
|
||||
|
||||
@@ -7,6 +7,7 @@ package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -92,7 +93,7 @@ func (q *Queries) DeleteWatchListEntry(ctx context.Context, arg DeleteWatchListE
|
||||
}
|
||||
|
||||
const getAnime = `-- name: GetAnime :one
|
||||
SELECT id, title, image_url, created_at FROM anime WHERE id = ? LIMIT 1
|
||||
SELECT id, title_original, image_url, created_at, title_english, title_japanese FROM anime WHERE id = ? LIMIT 1
|
||||
`
|
||||
|
||||
func (q *Queries) GetAnime(ctx context.Context, id int64) (Anime, error) {
|
||||
@@ -100,9 +101,11 @@ func (q *Queries) GetAnime(ctx context.Context, id int64) (Anime, error) {
|
||||
var i Anime
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Title,
|
||||
&i.TitleOriginal,
|
||||
&i.ImageUrl,
|
||||
&i.CreatedAt,
|
||||
&i.TitleEnglish,
|
||||
&i.TitleJapanese,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -156,7 +159,12 @@ func (q *Queries) GetUserByUsername(ctx context.Context, username string) (User,
|
||||
}
|
||||
|
||||
const getUserWatchList = `-- name: GetUserWatchList :many
|
||||
SELECT e.id, e.user_id, e.anime_id, e.status, e.created_at, e.updated_at, a.title, a.image_url
|
||||
SELECT
|
||||
e.id, e.user_id, e.anime_id, e.status, e.created_at, e.updated_at,
|
||||
a.title_original,
|
||||
a.title_english,
|
||||
a.title_japanese,
|
||||
a.image_url
|
||||
FROM watch_list_entry e
|
||||
JOIN anime a ON e.anime_id = a.id
|
||||
WHERE e.user_id = ?
|
||||
@@ -164,14 +172,16 @@ ORDER BY e.updated_at DESC
|
||||
`
|
||||
|
||||
type GetUserWatchListRow struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
AnimeID int64 `json:"anime_id"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Title string `json:"title"`
|
||||
ImageUrl string `json:"image_url"`
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
AnimeID int64 `json:"anime_id"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
TitleOriginal string `json:"title_original"`
|
||||
TitleEnglish sql.NullString `json:"title_english"`
|
||||
TitleJapanese sql.NullString `json:"title_japanese"`
|
||||
ImageUrl string `json:"image_url"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetUserWatchList(ctx context.Context, userID string) ([]GetUserWatchListRow, error) {
|
||||
@@ -190,7 +200,9 @@ func (q *Queries) GetUserWatchList(ctx context.Context, userID string) ([]GetUse
|
||||
&i.Status,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Title,
|
||||
&i.TitleOriginal,
|
||||
&i.TitleEnglish,
|
||||
&i.TitleJapanese,
|
||||
&i.ImageUrl,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
@@ -231,28 +243,40 @@ func (q *Queries) GetWatchListEntry(ctx context.Context, arg GetWatchListEntryPa
|
||||
}
|
||||
|
||||
const upsertAnime = `-- name: UpsertAnime :one
|
||||
INSERT INTO anime (id, title, image_url)
|
||||
VALUES (?, ?, ?)
|
||||
INSERT INTO anime (id, title_original, title_english, title_japanese, image_url)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
title = excluded.title,
|
||||
title_original = excluded.title_original,
|
||||
title_english = excluded.title_english,
|
||||
title_japanese = excluded.title_japanese,
|
||||
image_url = excluded.image_url
|
||||
RETURNING id, title, image_url, created_at
|
||||
RETURNING id, title_original, image_url, created_at, title_english, title_japanese
|
||||
`
|
||||
|
||||
type UpsertAnimeParams struct {
|
||||
ID int64 `json:"id"`
|
||||
Title string `json:"title"`
|
||||
ImageUrl string `json:"image_url"`
|
||||
ID int64 `json:"id"`
|
||||
TitleOriginal string `json:"title_original"`
|
||||
TitleEnglish sql.NullString `json:"title_english"`
|
||||
TitleJapanese sql.NullString `json:"title_japanese"`
|
||||
ImageUrl string `json:"image_url"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpsertAnime(ctx context.Context, arg UpsertAnimeParams) (Anime, error) {
|
||||
row := q.db.QueryRowContext(ctx, upsertAnime, arg.ID, arg.Title, arg.ImageUrl)
|
||||
row := q.db.QueryRowContext(ctx, upsertAnime,
|
||||
arg.ID,
|
||||
arg.TitleOriginal,
|
||||
arg.TitleEnglish,
|
||||
arg.TitleJapanese,
|
||||
arg.ImageUrl,
|
||||
)
|
||||
var i Anime
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Title,
|
||||
&i.TitleOriginal,
|
||||
&i.ImageUrl,
|
||||
&i.CreatedAt,
|
||||
&i.TitleEnglish,
|
||||
&i.TitleJapanese,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@@ -42,6 +43,8 @@ func (h *WatchlistHandler) HandleUpdateWatchlist(w http.ResponseWriter, r *http.
|
||||
|
||||
animeIDStr := r.FormValue("anime_id")
|
||||
animeTitle := r.FormValue("anime_title")
|
||||
animeTitleEnglish := r.FormValue("anime_title_english")
|
||||
animeTitleJapanese := r.FormValue("anime_title_japanese")
|
||||
animeImage := r.FormValue("anime_image")
|
||||
status := r.FormValue("status")
|
||||
|
||||
@@ -53,9 +56,11 @@ func (h *WatchlistHandler) HandleUpdateWatchlist(w http.ResponseWriter, r *http.
|
||||
|
||||
// Ensure the anime exists in our local DB first (foreign key constraint)
|
||||
_, err = h.db.UpsertAnime(r.Context(), database.UpsertAnimeParams{
|
||||
ID: animeID,
|
||||
Title: animeTitle,
|
||||
ImageUrl: animeImage,
|
||||
ID: animeID,
|
||||
TitleOriginal: animeTitle,
|
||||
TitleEnglish: sql.NullString{String: animeTitleEnglish, Valid: animeTitleEnglish != ""},
|
||||
TitleJapanese: sql.NullString{String: animeTitleJapanese, Valid: animeTitleJapanese != ""},
|
||||
ImageUrl: animeImage,
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to save anime reference: %v", err), http.StatusInternalServerError)
|
||||
@@ -75,7 +80,16 @@ func (h *WatchlistHandler) HandleUpdateWatchlist(w http.ResponseWriter, r *http.
|
||||
return
|
||||
}
|
||||
|
||||
templates.WatchlistDropdown(int(animeID), animeTitle, animeImage, status).Render(r.Context(), w)
|
||||
// Determine display title (prefer English)
|
||||
displayTitle := animeTitleEnglish
|
||||
if displayTitle == "" {
|
||||
displayTitle = animeTitleJapanese
|
||||
}
|
||||
if displayTitle == "" {
|
||||
displayTitle = animeTitle
|
||||
}
|
||||
|
||||
templates.WatchlistDropdown(int(animeID), displayTitle, animeImage, status).Render(r.Context(), w)
|
||||
}
|
||||
|
||||
func (h *WatchlistHandler) HandleDeleteWatchlist(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -121,8 +135,18 @@ func (h *WatchlistHandler) HandleDeleteWatchlist(w http.ResponseWriter, r *http.
|
||||
return
|
||||
}
|
||||
|
||||
// Determine display title for dropdown (prefer English)
|
||||
displayTitle := ""
|
||||
if anime.TitleEnglish.Valid && anime.TitleEnglish.String != "" {
|
||||
displayTitle = anime.TitleEnglish.String
|
||||
} else if anime.TitleJapanese.Valid && anime.TitleJapanese.String != "" {
|
||||
displayTitle = anime.TitleJapanese.String
|
||||
} else {
|
||||
displayTitle = anime.TitleOriginal
|
||||
}
|
||||
|
||||
// Otherwise return updated dropdown for anime page
|
||||
templates.WatchlistDropdown(int(animeID), anime.Title, anime.ImageUrl, "").Render(r.Context(), w)
|
||||
templates.WatchlistDropdown(int(animeID), displayTitle, anime.ImageUrl, "").Render(r.Context(), w)
|
||||
}
|
||||
|
||||
func (h *WatchlistHandler) HandleGetWatchlist(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -206,7 +230,7 @@ func (h *WatchlistHandler) HandleExportWatchlist(w http.ResponseWriter, r *http.
|
||||
for i, entry := range entries {
|
||||
export.Entries[i] = WatchlistExportEntry{
|
||||
AnimeID: entry.AnimeID,
|
||||
Title: entry.Title,
|
||||
Title: entry.DisplayTitle(),
|
||||
ImageURL: entry.ImageUrl,
|
||||
Status: entry.Status,
|
||||
UpdatedAt: entry.UpdatedAt.Format(time.RFC3339),
|
||||
@@ -251,11 +275,13 @@ func (h *WatchlistHandler) HandleImportWatchlist(w http.ResponseWriter, r *http.
|
||||
|
||||
imported := 0
|
||||
for _, entry := range export.Entries {
|
||||
// Upsert anime
|
||||
// Upsert anime - store title as original (we don't know which type it is from export)
|
||||
_, err := h.db.UpsertAnime(r.Context(), database.UpsertAnimeParams{
|
||||
ID: entry.AnimeID,
|
||||
Title: entry.Title,
|
||||
ImageUrl: entry.ImageURL,
|
||||
ID: entry.AnimeID,
|
||||
TitleOriginal: entry.Title,
|
||||
TitleEnglish: sql.NullString{},
|
||||
TitleJapanese: sql.NullString{},
|
||||
ImageUrl: entry.ImageURL,
|
||||
})
|
||||
if err != nil {
|
||||
continue
|
||||
|
||||
@@ -194,7 +194,7 @@ templ statusOption(anime jikan.Anime, status string, currentStatus string) {
|
||||
<button
|
||||
class={ "dropdown-item", templ.KV("active", status == currentStatus) }
|
||||
hx-post="/api/watchlist"
|
||||
hx-vals={ fmt.Sprintf(`{"anime_id": "%d", "anime_title": "%s", "anime_image": "%s", "status": "%s"}`, anime.MalID, anime.DisplayTitle(), anime.ImageURL(), status) }
|
||||
hx-vals={ fmt.Sprintf(`{"anime_id": "%d", "anime_title": "%s", "anime_title_english": "%s", "anime_title_japanese": "%s", "anime_image": "%s", "status": "%s"}`, anime.MalID, anime.Title, anime.TitleEnglish, anime.TitleJapanese, anime.ImageURL(), status) }
|
||||
hx-target="#watchlist-dropdown"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
|
||||
@@ -700,9 +700,9 @@ func statusOption(anime jikan.Anime, status string, currentStatus string) templ.
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var34 string
|
||||
templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf(`{"anime_id": "%d", "anime_title": "%s", "anime_image": "%s", "status": "%s"}`, anime.MalID, anime.DisplayTitle(), anime.ImageURL(), status))
|
||||
templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf(`{"anime_id": "%d", "anime_title": "%s", "anime_title_english": "%s", "anime_title_japanese": "%s", "anime_image": "%s", "status": "%s"}`, anime.MalID, anime.Title, anime.TitleEnglish, anime.TitleJapanese, anime.ImageURL(), status))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/anime.templ`, Line: 197, Col: 164}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/anime.templ`, Line: 197, Col: 255}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
|
||||
@@ -49,12 +49,12 @@ templ Watchlist(entries []database.GetUserWatchListRow, layout string, currentSt
|
||||
<div class="catalog-item watchlist-item" id={ fmt.Sprintf("watchlist-entry-%d", entry.AnimeID) }>
|
||||
<a href={ templ.URL(fmt.Sprintf("/anime/%d", entry.AnimeID)) }>
|
||||
if entry.ImageUrl != "" {
|
||||
<img src={ entry.ImageUrl } alt={ entry.Title } class="catalog-thumb" loading="lazy" />
|
||||
<img src={ entry.ImageUrl } alt={ entry.DisplayTitle() } class="catalog-thumb" loading="lazy" />
|
||||
} else {
|
||||
<div class="no-image">no image</div>
|
||||
}
|
||||
</a>
|
||||
<div class="catalog-title">{ entry.Title }</div>
|
||||
<div class="catalog-title">{ entry.DisplayTitle() }</div>
|
||||
<div class="watchlist-status">{ entry.Status }</div>
|
||||
<button
|
||||
class="remove-btn"
|
||||
@@ -80,12 +80,12 @@ templ Watchlist(entries []database.GetUserWatchListRow, layout string, currentSt
|
||||
<tr id={ fmt.Sprintf("watchlist-entry-%d", entry.AnimeID) }>
|
||||
<td>
|
||||
<a href={ templ.SafeURL(fmt.Sprintf("/anime/%d", entry.AnimeID)) }>
|
||||
<img src={ entry.ImageUrl } alt={ entry.Title } class="thumb" loading="lazy"/>
|
||||
<img src={ entry.ImageUrl } alt={ entry.DisplayTitle() } class="thumb" loading="lazy"/>
|
||||
</a>
|
||||
</td>
|
||||
<td class="title-cell">
|
||||
<a href={ templ.SafeURL(fmt.Sprintf("/anime/%d", entry.AnimeID)) }>
|
||||
{ entry.Title }
|
||||
{ entry.DisplayTitle() }
|
||||
</a>
|
||||
</td>
|
||||
<td class="status-cell">{ entry.Status }</td>
|
||||
|
||||
@@ -406,9 +406,9 @@ func Watchlist(entries []database.GetUserWatchListRow, layout string, currentSta
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var30 string
|
||||
templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Title)
|
||||
templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(entry.DisplayTitle())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/watchlist.templ`, Line: 52, Col: 54}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/watchlist.templ`, Line: 52, Col: 63}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -429,9 +429,9 @@ func Watchlist(entries []database.GetUserWatchListRow, layout string, currentSta
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var31 string
|
||||
templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Title)
|
||||
templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(entry.DisplayTitle())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/watchlist.templ`, Line: 57, Col: 47}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/watchlist.templ`, Line: 57, Col: 56}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -535,9 +535,9 @@ func Watchlist(entries []database.GetUserWatchListRow, layout string, currentSta
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var38 string
|
||||
templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Title)
|
||||
templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(entry.DisplayTitle())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/watchlist.templ`, Line: 83, Col: 55}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/watchlist.templ`, Line: 83, Col: 64}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -561,9 +561,9 @@ func Watchlist(entries []database.GetUserWatchListRow, layout string, currentSta
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var40 string
|
||||
templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Title)
|
||||
templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinStringErrs(entry.DisplayTitle())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/watchlist.templ`, Line: 88, Col: 23}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/watchlist.templ`, Line: 88, Col: 32}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
|
||||
6
migrations/002_add_anime_titles.sql
Normal file
6
migrations/002_add_anime_titles.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
-- Add English and Japanese title columns to anime table
|
||||
ALTER TABLE anime ADD COLUMN title_english TEXT;
|
||||
ALTER TABLE anime ADD COLUMN title_japanese TEXT;
|
||||
|
||||
-- Rename existing title to title_original for clarity
|
||||
ALTER TABLE anime RENAME COLUMN title TO title_original;
|
||||
Reference in New Issue
Block a user