From b02f1fdf5db8bd729a5a3db32bea083dc9a989f5 Mon Sep 17 00:00:00 2001 From: NextFire Date: Fri, 23 Aug 2024 22:37:55 +0200 Subject: [PATCH 1/9] server/ui: patch artist/media/author tags --- server/artists.go | 90 +++++++++++++---- server/authors.go | 72 ++++++++++++-- server/karaberus.go | 3 + server/media.go | 93 +++++++++++++---- server/mugen.go | 4 +- ui/eslint.config.js | 3 +- ui/src/components/ArtistEditor.tsx | 28 +++--- ui/src/components/AuthorEditor.tsx | 20 ++-- ui/src/components/KaraEditor.tsx | 155 +++++++++++++++-------------- ui/src/components/MediaEditor.tsx | 33 +++--- ui/src/components/TokenForm.tsx | 7 +- ui/src/pages/settings.tsx | 5 +- ui/src/pages/tags/artist.tsx | 150 +++++++++++++++++++--------- ui/src/pages/tags/author.tsx | 132 ++++++++++++++++-------- ui/src/pages/tags/media.tsx | 154 ++++++++++++++++++---------- 15 files changed, 640 insertions(+), 309 deletions(-) diff --git a/server/artists.go b/server/artists.go index f3ea9068..f7095ee9 100644 --- a/server/artists.go +++ b/server/artists.go @@ -37,34 +37,90 @@ func GetArtist(ctx context.Context, input *GetArtistInput) (*ArtistOutput, error return artist_output, nil } +type ArtistInfo struct { + Name string `json:"name"` + AdditionalNames []string `json:"additional_names"` +} + type CreateArtistInput struct { - Body struct { - Name string `json:"name"` - AdditionalNames []string `json:"additional_names"` - } + Body ArtistInfo } -func createArtist(tx *gorm.DB, name string, additional_names []string, artist *Artist) error { - return tx.Transaction( - func(tx *gorm.DB) error { - additional_names_db := createAdditionalNames(additional_names) - artist.Name = name - artist.AdditionalNames = additional_names_db - err := tx.Create(artist).Error - return DBErrToHumaErr(err) - }) +func createArtist(db *gorm.DB, artist *Artist, info *ArtistInfo) error { + return db.Transaction(func(tx *gorm.DB) error { + if err := info.to_Artist(artist); err != nil { + return err + } + err := tx.Create(artist).Error + return DBErrToHumaErr(err) + }) } func CreateArtist(ctx context.Context, input *CreateArtistInput) (*ArtistOutput, error) { - artist_output := &ArtistOutput{} + db := GetDB(ctx) + output := ArtistOutput{} + + err := db.Transaction(func(tx *gorm.DB) error { + artist := Artist{} + if err := createArtist(tx, &artist, &input.Body); err != nil { + return err + } + output.Body.Artist = artist + return nil + }) + + return &output, err +} + +type UpdateArtistInput struct { + Id uint `path:"id"` + Body ArtistInfo +} + +func updateArtist(tx *gorm.DB, artist *Artist) error { + err := tx.Model(&artist).Select("*").Updates(&artist).Error + if err != nil { + return err + } + prev_context := tx.Statement.Context + tx = WithAssociationsUpdate(tx) + defer tx.WithContext(prev_context) + err = tx.Model(&artist).Association("AdditionalName").Replace(&artist.AdditionalNames) + if err != nil { + return err + } + return nil +} +func UpdateArtist(ctx context.Context, input *UpdateArtistInput) (*ArtistOutput, error) { db := GetDB(ctx) - err := createArtist(db, input.Body.Name, input.Body.AdditionalNames, &artist_output.Body.Artist) + artist := Artist{} + err := db.First(&artist, input.Id).Error if err != nil { - return nil, DBErrToHumaErr(err) + return nil, err + } + err = input.Body.to_Artist(&artist) + if err != nil { + return nil, err } - return artist_output, nil + err = db.Transaction(func(tx *gorm.DB) error { + return updateArtist(tx, &artist) + }) + if err != nil { + return nil, err + } + + out := &ArtistOutput{} + out.Body.Artist = artist + + return out, nil +} + +func (info ArtistInfo) to_Artist(artist *Artist) error { + artist.Name = info.Name + artist.AdditionalNames = createAdditionalNames(info.AdditionalNames) + return nil } type DeleteArtistResponse struct { diff --git a/server/authors.go b/server/authors.go index a1f44368..274e8278 100644 --- a/server/authors.go +++ b/server/authors.go @@ -37,24 +37,74 @@ func GetAuthor(ctx context.Context, input *GetAuthorInput) (*AuthorOutput, error return author_output, nil } +type AuthorInfo struct { + Name string `json:"name"` +} + type CreateAuthorInput struct { - Body struct { - Name string `json:"name"` - } + Body AuthorInfo } func CreateAuthor(ctx context.Context, input *CreateAuthorInput) (*AuthorOutput, error) { db := GetDB(ctx) - author_output := &AuthorOutput{} + output := AuthorOutput{} + + err := db.Transaction(func(tx *gorm.DB) error { + author := TimingAuthor{} + err := input.Body.to_TimingAuthor(&author) + if err != nil { + return err + } + output.Body.Author = author + + err = tx.Create(&output.Body.Author).Error + return err + }) + + return &output, err +} + +type UpdateAuthorInput struct { + Id uint `path:"id"` + Body AuthorInfo +} + +func updateAuthor(tx *gorm.DB, author *TimingAuthor) error { + err := tx.Model(&author).Select("*").Updates(&author).Error + if err != nil { + return err + } + return nil +} - err := db.Transaction( - func(tx *gorm.DB) error { - author_output.Body.Author = TimingAuthor{Name: input.Body.Name} - err := tx.Create(&author_output.Body.Author).Error - return DBErrToHumaErr(err) - }) +func UpdateAuthor(ctx context.Context, input *UpdateAuthorInput) (*AuthorOutput, error) { + db := GetDB(ctx) + author := TimingAuthor{} + err := db.First(&author, input.Id).Error + if err != nil { + return nil, err + } + err = input.Body.to_TimingAuthor(&author) + if err != nil { + return nil, err + } + + err = db.Transaction(func(tx *gorm.DB) error { + return updateAuthor(tx, &author) + }) + if err != nil { + return nil, err + } + + out := &AuthorOutput{} + out.Body.Author = author + + return out, nil +} - return author_output, err +func (info AuthorInfo) to_TimingAuthor(author *TimingAuthor) error { + author.Name = info.Name + return nil } type DeleteAuthorResponse struct { diff --git a/server/karaberus.go b/server/karaberus.go index 8df3daba..3c7a8fbf 100644 --- a/server/karaberus.go +++ b/server/karaberus.go @@ -62,12 +62,14 @@ func addRoutes(api huma.API) { huma.Get(api, "/api/tags/author/search", FindAuthor, setSecurity(kara_ro_security)) huma.Get(api, "/api/tags/author/{id}", GetAuthor, setSecurity(kara_ro_security)) huma.Delete(api, "/api/tags/author/{id}", DeleteAuthor, setSecurity(kara_security)) + huma.Patch(api, "/api/tags/author/{id}", UpdateAuthor, setSecurity(kara_security)) huma.Post(api, "/api/tags/author", CreateAuthor, setSecurity(kara_security)) huma.Get(api, "/api/tags/artist", GetAllArtists, setSecurity(kara_ro_security)) huma.Get(api, "/api/tags/artist/search", FindArtist, setSecurity(kara_ro_security)) huma.Get(api, "/api/tags/artist/{id}", GetArtist, setSecurity(kara_ro_security)) huma.Delete(api, "/api/tags/artist/{id}", DeleteArtist, setSecurity(kara_security)) + huma.Patch(api, "/api/tags/artist/{id}", UpdateArtist, setSecurity(kara_security)) huma.Post(api, "/api/tags/artist", CreateArtist, setSecurity(kara_security)) huma.Get(api, "/api/tags/media", GetAllMedias, setSecurity(kara_ro_security)) @@ -75,6 +77,7 @@ func addRoutes(api huma.API) { huma.Get(api, "/api/tags/media/search", FindMedia, setSecurity(kara_ro_security)) huma.Get(api, "/api/tags/media/{id}", GetMedia, setSecurity(kara_ro_security)) huma.Delete(api, "/api/tags/media/{id}", DeleteMedia, setSecurity(kara_security)) + huma.Patch(api, "/api/tags/media/{id}", UpdateMedia, setSecurity(kara_security)) huma.Post(api, "/api/tags/media", CreateMedia, setSecurity(kara_security)) huma.Post(api, "/api/mugen", ImportMugenKara, setSecurity(kara_security)) diff --git a/server/media.go b/server/media.go index 4e4372c6..177e6bad 100644 --- a/server/media.go +++ b/server/media.go @@ -6,12 +6,14 @@ import ( "gorm.io/gorm" ) +type MediaInfo struct { + Name string `json:"name" example:"Shinseiki Evangelion"` + MediaType string `json:"media_type" example:"ANIME"` + AdditionalNames []string `json:"additional_names" example:"[]"` +} + type CreateMediaInput struct { - Body struct { - Name string `json:"name" example:"Shinseiki Evangelion"` - MediaType string `json:"media_type" example:"ANIME"` - AdditionalNames []string `json:"additional_names" example:"[]"` - } + Body MediaInfo } type MediaOutput struct { @@ -44,27 +46,82 @@ func getMediaByID(tx *gorm.DB, Id uint) (MediaDB, error) { // return media, DBErrToHumaErr(err) // } -func createMedia(tx *gorm.DB, name string, media_type MediaType, additional_names []string, media *MediaDB) error { - err := tx.Transaction(func(tx *gorm.DB) error { - media.Name = name - media.Type = media_type.ID - media.AdditionalNames = createAdditionalNames(additional_names) - - err := tx.Create(&media).Error +func createMedia(db *gorm.DB, media *MediaDB, info *MediaInfo) error { + return db.Transaction(func(tx *gorm.DB) error { + if err := info.to_MediaDB(media); err != nil { + return err + } + err := tx.Create(media).Error return DBErrToHumaErr(err) }) - - return err } func CreateMedia(ctx context.Context, input *CreateMediaInput) (*MediaOutput, error) { - media_output := &MediaOutput{} - media_type := getMediaType(input.Body.MediaType) + db := GetDB(ctx) + output := MediaOutput{} + + err := db.Transaction(func(tx *gorm.DB) error { + media := MediaDB{} + if err := createMedia(tx, &media, &input.Body); err != nil { + return err + } + output.Body.Media = media + return nil + }) + + return &output, err +} +type UpdateMediaInput struct { + Id uint `path:"id"` + Body MediaInfo +} + +func updateMedia(tx *gorm.DB, media *MediaDB) error { + err := tx.Model(&media).Select("*").Updates(&media).Error + if err != nil { + return err + } + prev_context := tx.Statement.Context + tx = WithAssociationsUpdate(tx) + defer tx.WithContext(prev_context) + err = tx.Model(&media).Association("AdditionalName").Replace(&media.AdditionalNames) + if err != nil { + return err + } + return nil +} + +func UpdateMedia(ctx context.Context, input *UpdateMediaInput) (*MediaOutput, error) { db := GetDB(ctx) - err := createMedia(db, input.Body.Name, media_type, input.Body.AdditionalNames, &media_output.Body.Media) + media := MediaDB{} + err := db.First(&media, input.Id).Error + if err != nil { + return nil, err + } + err = input.Body.to_MediaDB(&media) + if err != nil { + return nil, err + } + + err = db.Transaction(func(tx *gorm.DB) error { + return updateMedia(tx, &media) + }) + if err != nil { + return nil, err + } + + out := &MediaOutput{} + out.Body.Media = media + + return out, nil +} - return media_output, err +func (info MediaInfo) to_MediaDB(media *MediaDB) error { + media.Name = info.Name + media.Type = getMediaType(info.MediaType).ID + media.AdditionalNames = createAdditionalNames(info.AdditionalNames) + return nil } type DeleteMediaResponse struct { diff --git a/server/mugen.go b/server/mugen.go index 2b01c428..7c594146 100644 --- a/server/mugen.go +++ b/server/mugen.go @@ -66,7 +66,7 @@ func getMugenMedia(tx *gorm.DB, tag mugen.MugenTag, origins []mugen.MugenTag, co additional_names := []string{} err := findMedia(tx, []string{tag.Name}, media) if errors.Is(err, gorm.ErrRecordNotFound) { - err = createMedia(tx, tag.Name, *media_type, additional_names, media) + err = createMedia(tx, media, &MediaInfo{tag.Name, media_type.ID, additional_names}) } if err != nil { return err @@ -80,7 +80,7 @@ func getMugenArtist(tx *gorm.DB, mugen_artist mugen.MugenTag, karaberus_artist * err := findArtist(tx, artistNames, karaberus_artist) if errors.Is(err, gorm.ErrRecordNotFound) { - err = createArtist(tx, mugen_artist.Name, mugen_artist.Aliases, karaberus_artist) + err = createArtist(tx, karaberus_artist, &ArtistInfo{mugen_artist.Name, mugen_artist.Aliases}) } return err } diff --git a/ui/eslint.config.js b/ui/eslint.config.js index 8aa47d48..c21af627 100644 --- a/ui/eslint.config.js +++ b/ui/eslint.config.js @@ -6,10 +6,9 @@ import tseslint from "typescript-eslint"; export default tseslint.config( eslint.configs.recommended, ...tseslint.configs.recommended, - // @ts-expect-error: prettier types are not up-to-date eslintPluginPrettierRecommended, { - ignores: ["dist", "src/utils/karaberus.d.ts"], + ignores: ["dist", "src/utils/karaberus.d.ts", "src-tauri"], }, { rules: { diff --git a/ui/src/components/ArtistEditor.tsx b/ui/src/components/ArtistEditor.tsx index 2bcd3fde..c1713581 100644 --- a/ui/src/components/ArtistEditor.tsx +++ b/ui/src/components/ArtistEditor.tsx @@ -1,27 +1,25 @@ import { createSignal, type JSX } from "solid-js"; import type { components } from "../utils/karaberus"; -import { karaberus } from "../utils/karaberus-client"; export default function ArtistEditor(props: { - onAdd: (artist: components["schemas"]["Artist"]) => void; + artist?: components["schemas"]["Artist"]; + onSubmit: (artist: components["schemas"]["ArtistInfo"]) => void; + reset?: boolean; }) { - const [getName, setName] = createSignal(""); - const [getAdditionalNames, setAdditionalNames] = createSignal(""); + const [getName, setName] = createSignal(props.artist?.Name ?? ""); + const [getAdditionalNames, setAdditionalNames] = createSignal( + props.artist?.AdditionalNames?.join("\n") ?? "", + ); - const onsubmit: JSX.EventHandler = async (e) => { + const onsubmit: JSX.EventHandler = (e) => { e.preventDefault(); - const resp = await karaberus.POST("/api/tags/artist", { - body: { - name: getName(), - additional_names: getAdditionalNames().trim().split("\n"), - }, + props.onSubmit({ + name: getName(), + additional_names: getAdditionalNames().trim().split("\n"), }); - if (resp.error) { - alert(resp.error.title); - return; + if (props.reset) { + (e.target as HTMLFormElement).reset(); } - (e.target as HTMLFormElement).reset(); - props.onAdd(resp.data.artist); }; return ( diff --git a/ui/src/components/AuthorEditor.tsx b/ui/src/components/AuthorEditor.tsx index 7066af10..e22c6c6c 100644 --- a/ui/src/components/AuthorEditor.tsx +++ b/ui/src/components/AuthorEditor.tsx @@ -1,23 +1,19 @@ import { createSignal, type JSX } from "solid-js"; import type { components } from "../utils/karaberus"; -import { karaberus } from "../utils/karaberus-client"; export default function AuthorEditor(props: { - onAdd: (author: components["schemas"]["TimingAuthor"]) => void; + author?: components["schemas"]["TimingAuthor"]; + onSubmit: (author: components["schemas"]["AuthorInfo"]) => void; + reset?: boolean; }) { - const [getName, setName] = createSignal(""); + const [getName, setName] = createSignal(props.author?.Name ?? ""); - const onsubmit: JSX.EventHandler = async (e) => { + const onsubmit: JSX.EventHandler = (e) => { e.preventDefault(); - const resp = await karaberus.POST("/api/tags/author", { - body: { name: getName() }, - }); - if (resp.error) { - alert(resp.error.title); - return; + props.onSubmit({ name: getName() }); + if (props.reset) { + (e.target as HTMLFormElement).reset(); } - (e.target as HTMLFormElement).reset(); - props.onAdd(resp.data.author); }; return ( diff --git a/ui/src/components/KaraEditor.tsx b/ui/src/components/KaraEditor.tsx index 873b3f3f..fac3c46c 100644 --- a/ui/src/components/KaraEditor.tsx +++ b/ui/src/components/KaraEditor.tsx @@ -9,7 +9,8 @@ import MediaEditor from "./MediaEditor"; export default function KaraEditor(props: { kara?: components["schemas"]["KaraInfoDB"]; - onSubmit: (info: components["schemas"]["KaraInfo"]) => void; + onSubmit: (kara: components["schemas"]["KaraInfo"]) => void; + reset?: boolean; }) { //#region Resources const [getAllAuthors, { refetch: refetchAuthors }] = createResource( @@ -37,72 +38,55 @@ export default function KaraEditor(props: { return resp.data; }); - const getAudioTag = (tagId: string) => + const getAudioTag = (tagId: components["schemas"]["AudioTag"]["ID"]) => (getAllAudioTags() || []).find((t) => t.ID == tagId); //#endregion //#region Signals - const [getTitle, setTitle] = createSignal(""); - const [getExtraTitles, setExtraTitles] = createSignal(""); + const [getTitle, setTitle] = createSignal(props.kara?.Title ?? ""); + const [getExtraTitles, setExtraTitles] = createSignal( + props.kara?.ExtraTitles?.map((v) => v.Name).join("\n") ?? "", + ); const [getAuthors, setAuthors] = createSignal< components["schemas"]["TimingAuthor"][] - >([]); + >(props.kara?.Authors ?? []); const [getArtists, setArtists] = createSignal< components["schemas"]["Artist"][] - >([]); - const [getSourceMedia, setSourceMedia] = - createSignal(); - const [getSongOrder, setSongOrder] = createSignal(); + >(props.kara?.Artists ?? []); + const [getSourceMedia, setSourceMedia] = createSignal< + components["schemas"]["MediaDB"] | undefined + >(props.kara?.SourceMedia); + const [getSongOrder, setSongOrder] = createSignal( + props.kara?.SongOrder, + ); const [getMedias, setMedias] = createSignal< components["schemas"]["MediaDB"][] - >([]); + >(props.kara?.Medias ?? []); const [getAudioTags, setAudioTags] = createSignal< components["schemas"]["AudioTagDB"][] - >([]); + >(props.kara?.AudioTags ?? []); const [getVideoTags, setVideoTags] = createSignal< components["schemas"]["VideoTagDB"][] - >([]); - const [getComment, setComment] = createSignal(""); - const [getVersion, setVersion] = createSignal(""); - const [getLanguage, setLanguage] = createSignal(""); - - if (props.kara) { - setTitle(props.kara.Title); - if (props.kara.ExtraTitles) { - setExtraTitles(props.kara.ExtraTitles.map((v) => v.Name).join("\n")); - } - if (props.kara.Authors) { - setAuthors(props.kara.Authors); - } - if (props.kara.Artists) { - setArtists(props.kara.Artists); - } - if (props.kara.SourceMedia) { - setSourceMedia(props.kara.SourceMedia); - } - setSongOrder(props.kara.SongOrder); - if (props.kara.Medias) { - setMedias(props.kara.Medias); - } - if (props.kara.AudioTags) { - setAudioTags(props.kara.AudioTags); - } - if (props.kara.VideoTags) { - setVideoTags(props.kara.VideoTags); - } - setComment(props.kara.Comment); - setVersion(props.kara.Version); - setLanguage(props.kara.Language); - } + >(props.kara?.VideoTags ?? []); + const [getComment, setComment] = createSignal(props.kara?.Comment ?? ""); + const [getVersion, setVersion] = createSignal(props.kara?.Version ?? ""); + const [getLanguage, setLanguage] = createSignal(props.kara?.Language ?? ""); //#endregion //#region Handlers let modalRef!: HTMLDialogElement; + const [getModalForm, setModalForm] = createSignal(); - const onsubmit: JSX.EventHandler = async (e) => { - e.preventDefault(); + const [getToast, setToast] = createSignal(); - const payload: components["schemas"]["KaraInfo"] = { + const showToast = (msg: string) => { + setToast(msg); + setTimeout(() => setToast(), 3000); + }; + + const onsubmit: JSX.EventHandler = (e) => { + e.preventDefault(); + props.onSubmit({ title: getTitle(), title_aliases: getExtraTitles().trim().split("\n") || null, authors: getAuthors().map((author) => author.ID) || null, @@ -115,49 +99,60 @@ export default function KaraEditor(props: { comment: getComment(), version: getVersion(), language: getLanguage(), - }; - - props.onSubmit(payload); + }); + if (props.reset) { + (e.target as HTMLFormElement).reset(); + } }; - const [getModalForm, setModalForm] = createSignal(); + const postAuthor = async (author: components["schemas"]["AuthorInfo"]) => { + const resp = await karaberus.POST("/api/tags/author", { body: author }); + if (resp.error) { + alert(resp.error.title); + return; + } + showToast("Author added!"); + refetchAuthors(); + modalRef.close(); + }; const openAddAuthorModal: JSX.EventHandler = (e) => { e.preventDefault(); - setModalForm( - { - refetchAuthors(); - modalRef.close(); - }} - />, - ); + setModalForm(); modalRef.showModal(); }; + const postArtist = async (artist: components["schemas"]["ArtistInfo"]) => { + const resp = await karaberus.POST("/api/tags/artist", { body: artist }); + if (resp.error) { + alert(resp.error.title); + return; + } + showToast("Artist added!"); + refetchArtists(); + modalRef.close(); + }; + const openAddArtistModal: JSX.EventHandler = (e) => { e.preventDefault(); - setModalForm( - { - refetchArtists(); - modalRef.close(); - }} - />, - ); + setModalForm(); modalRef.showModal(); }; + const postMedia = async (media: components["schemas"]["MediaInfo"]) => { + const resp = await karaberus.POST("/api/tags/media", { body: media }); + if (resp.error) { + alert(resp.error.title); + return; + } + showToast("Media added!"); + refetchMedia(); + modalRef.close(); + }; + const openAddMediaModal: JSX.EventHandler = (e) => { e.preventDefault(); - setModalForm( - { - refetchMedia(); - modalRef.close(); - }} - />, - ); + setModalForm(); modalRef.showModal(); }; //#endregion @@ -473,6 +468,16 @@ export default function KaraEditor(props: { + + + {(getToast) => ( +
+
+ {getToast()} +
+
+ )} +
); } diff --git a/ui/src/components/MediaEditor.tsx b/ui/src/components/MediaEditor.tsx index 65bcb844..bbc20c3b 100644 --- a/ui/src/components/MediaEditor.tsx +++ b/ui/src/components/MediaEditor.tsx @@ -3,32 +3,33 @@ import type { components } from "../utils/karaberus"; import { karaberus } from "../utils/karaberus-client"; export default function MediaEditor(props: { - onAdd: (media: components["schemas"]["MediaDB"]) => void; + media?: components["schemas"]["MediaDB"]; + onSubmit: (media: components["schemas"]["MediaInfo"]) => void; + reset?: boolean; }) { const [getAllMediaTypes] = createResource(async () => { const resp = await karaberus.GET("/api/tags/media/types"); return resp.data; }); - const [getMediaType, setMediaType] = createSignal("ANIME"); - const [getName, setName] = createSignal(""); - const [getAdditionalNames, setAdditionalNames] = createSignal(""); + const [getMediaType, setMediaType] = createSignal( + props.media?.media_type ?? "ANIME", + ); + const [getName, setName] = createSignal(props.media?.name ?? ""); + const [getAdditionalNames, setAdditionalNames] = createSignal( + props.media?.additional_name?.join("\n") ?? "", + ); - const onsubmit: JSX.EventHandler = async (e) => { + const onsubmit: JSX.EventHandler = (e) => { e.preventDefault(); - const resp = await karaberus.POST("/api/tags/media", { - body: { - media_type: getMediaType(), - name: getName(), - additional_names: getAdditionalNames().trim().split("\n"), - }, + props.onSubmit({ + media_type: getMediaType(), + name: getName(), + additional_names: getAdditionalNames().trim().split("\n"), }); - if (resp.error) { - alert(resp.error.title); - return; + if (props.reset) { + (e.target as HTMLFormElement).reset(); } - (e.target as HTMLFormElement).reset(); - props.onAdd(resp.data.media); }; return ( diff --git a/ui/src/components/TokenForm.tsx b/ui/src/components/TokenForm.tsx index 7fb06998..12156cc3 100644 --- a/ui/src/components/TokenForm.tsx +++ b/ui/src/components/TokenForm.tsx @@ -1,7 +1,12 @@ import { createSignal, type JSX } from "solid-js"; +import type { components } from "../utils/karaberus"; import { karaberus } from "../utils/karaberus-client"; -export default function TokenForm(props: { onToken: (token: string) => void }) { +export default function TokenForm(props: { + onToken: ( + token: components["schemas"]["CreateTokenOutputBody"]["token"], + ) => void; +}) { const [getName, setName] = createSignal(""); const [getKaraChecked, setKaraChecked] = createSignal(false); const [getKaraROChecked, setKaraROChecked] = createSignal(false); diff --git a/ui/src/pages/settings.tsx b/ui/src/pages/settings.tsx index 1968cd2c..342702b3 100644 --- a/ui/src/pages/settings.tsx +++ b/ui/src/pages/settings.tsx @@ -2,6 +2,7 @@ import { isTauri } from "@tauri-apps/api/core"; import { HiSolidTrash } from "solid-icons/hi"; import { createResource, createSignal, Index, Show } from "solid-js"; import TokenForm from "../components/TokenForm"; +import type { paths } from "../utils/karaberus"; import { karaberus } from "../utils/karaberus-client"; import { getTauriStore, PLAYER_TOKEN_KEY } from "../utils/tauri"; @@ -14,7 +15,9 @@ export default function Settings() { ); const [getToken, setToken] = createSignal(); - const deleteToken = async (id: number) => { + const deleteToken = async ( + id: paths["/api/token/{token}"]["delete"]["parameters"]["path"]["token"], + ) => { if (!confirm("Confirm deletion?")) { return; } diff --git a/ui/src/pages/tags/artist.tsx b/ui/src/pages/tags/artist.tsx index ff731682..7c366a79 100644 --- a/ui/src/pages/tags/artist.tsx +++ b/ui/src/pages/tags/artist.tsx @@ -1,6 +1,7 @@ -import { HiSolidTrash } from "solid-icons/hi"; -import { createResource, createSignal, Index, Show } from "solid-js"; +import { HiSolidPencil, HiSolidTrash } from "solid-icons/hi"; +import { createResource, createSignal, Index, Show, type JSX } from "solid-js"; import ArtistEditor from "../../components/ArtistEditor"; +import type { components } from "../../utils/karaberus"; import { karaberus } from "../../utils/karaberus-client"; import { isAdmin } from "../../utils/session"; @@ -10,13 +11,46 @@ export default function TagsArtist() { return resp.data; }); + let modalRef!: HTMLDialogElement; + const [getModalForm, setModalForm] = createSignal(); + const [getToast, setToast] = createSignal(); - const deleteArtist = async (id: number) => { - if (!confirm("Confirm deletion?")) { + const showToast = (msg: string) => { + setToast(msg); + setTimeout(() => setToast(), 3000); + }; + + const postArtist = async (artist: components["schemas"]["ArtistInfo"]) => { + const resp = await karaberus.POST("/api/tags/artist", { body: artist }); + if (resp.error) { + alert(resp.error.title); return; } + showToast("Artist added!"); + refetch(); + }; + + const patchArtist = + (id: components["schemas"]["Artist"]["ID"]) => + async (artist: components["schemas"]["ArtistInfo"]) => { + const resp = await karaberus.PATCH("/api/tags/artist/{id}", { + params: { path: { id } }, + body: artist, + }); + if (resp.error) { + alert(resp.error.title); + return; + } + modalRef.close(); + showToast("Artist edited!"); + refetch(); + }; + const deleteArtist = async (id: components["schemas"]["Artist"]["ID"]) => { + if (!confirm("Confirm deletion?")) { + return; + } const resp = await karaberus.DELETE("/api/tags/artist/{id}", { params: { path: { id } }, }); @@ -24,6 +58,7 @@ export default function TagsArtist() { alert(resp.error.title); return; } + showToast("Artist deleted!"); refetch(); }; @@ -33,54 +68,71 @@ export default function TagsArtist() {

Add artist

- { - setToast("Artist added!"); - setTimeout(() => setToast(), 3000); - refetch(); - }} - /> +

Browse

- - - - - - - - - - - - {(getArtist) => ( - - - - - - - )} - - -
NameAdditional Names
{getArtist().ID}{getArtist().Name} -
    - - {(getAdditionalName) => ( -
  • {getAdditionalName().Name}
  • - )} -
    -
-
- -
+
+ + + + + + + + + + + + {(getArtist) => ( + + + + + + + )} + + +
NameAdditional Names
{getArtist().ID}{getArtist().Name} +
    + + {(getAdditionalName) => ( +
  • {getAdditionalName().Name}
  • + )} +
    +
+
+ + +
+
+ + + + + {(getToast) => ( diff --git a/ui/src/pages/tags/author.tsx b/ui/src/pages/tags/author.tsx index 8f19e89d..7191cd30 100644 --- a/ui/src/pages/tags/author.tsx +++ b/ui/src/pages/tags/author.tsx @@ -1,6 +1,7 @@ -import { HiSolidTrash } from "solid-icons/hi"; -import { createResource, createSignal, Index, Show } from "solid-js"; +import { HiSolidPencil, HiSolidTrash } from "solid-icons/hi"; +import { createResource, createSignal, Index, Show, type JSX } from "solid-js"; import AuthorEditor from "../../components/AuthorEditor"; +import type { components } from "../../utils/karaberus"; import { karaberus } from "../../utils/karaberus-client"; import { isAdmin } from "../../utils/session"; @@ -10,13 +11,48 @@ export default function TagsAuthor() { return resp.data; }); + let modalRef!: HTMLDialogElement; + const [getModalForm, setModalForm] = createSignal(); + const [getToast, setToast] = createSignal(); - const deleteAuthor = async (id: number) => { - if (!confirm("Confirm deletion?")) { + const showToast = (msg: string) => { + setToast(msg); + setTimeout(() => setToast(), 3000); + }; + + const postAuthor = async (author: components["schemas"]["AuthorInfo"]) => { + const resp = await karaberus.POST("/api/tags/author", { body: author }); + if (resp.error) { + alert(resp.error.title); return; } + showToast("Author added!"); + refetch(); + }; + + const patchAuthor = + (id: components["schemas"]["TimingAuthor"]["ID"]) => + async (author: components["schemas"]["AuthorInfo"]) => { + const resp = await karaberus.PATCH("/api/tags/author/{id}", { + params: { path: { id } }, + body: author, + }); + if (resp.error) { + alert(resp.error.title); + return; + } + modalRef.close(); + showToast("Author edited!"); + refetch(); + }; + const deleteAuthor = async ( + id: components["schemas"]["TimingAuthor"]["ID"], + ) => { + if (!confirm("Confirm deletion?")) { + return; + } const resp = await karaberus.DELETE("/api/tags/author/{id}", { params: { path: { id } }, }); @@ -24,6 +60,7 @@ export default function TagsAuthor() { alert(resp.error.title); return; } + showToast("Author deleted!"); refetch(); }; @@ -33,44 +70,61 @@ export default function TagsAuthor() {

Add author

- { - setToast("Author added!"); - setTimeout(() => setToast(), 3000); - refetch(); - }} - /> +

Browse

- - - - - - - - - - - {(getAuthor) => ( - - - - - - )} - - -
Name
{getAuthor().ID}{getAuthor().Name} - -
+
+ + + + + + + + + + + {(getAuthor) => ( + + + + + + )} + + +
Name
{getAuthor().ID}{getAuthor().Name} + + +
+
+ + + + + {(getToast) => ( diff --git a/ui/src/pages/tags/media.tsx b/ui/src/pages/tags/media.tsx index 9b40aa87..37b9d8a0 100644 --- a/ui/src/pages/tags/media.tsx +++ b/ui/src/pages/tags/media.tsx @@ -1,6 +1,7 @@ -import { HiSolidTrash } from "solid-icons/hi"; -import { createResource, createSignal, Index, Show } from "solid-js"; +import { HiSolidPencil, HiSolidTrash } from "solid-icons/hi"; +import { createResource, createSignal, Index, Show, type JSX } from "solid-js"; import MediaEditor from "../../components/MediaEditor"; +import type { components } from "../../utils/karaberus"; import { karaberus } from "../../utils/karaberus-client"; import { isAdmin } from "../../utils/session"; @@ -10,13 +11,46 @@ export default function TagsMedia() { return resp.data; }); + let modalRef!: HTMLDialogElement; + const [getModalForm, setModalForm] = createSignal(); + const [getToast, setToast] = createSignal(); - const deleteArtist = async (id: number) => { - if (!confirm("Confirm deletion?")) { + const showToast = (msg: string) => { + setToast(msg); + setTimeout(() => setToast(), 3000); + }; + + const postMedia = async (media: components["schemas"]["MediaInfo"]) => { + const resp = await karaberus.POST("/api/tags/media", { body: media }); + if (resp.error) { + alert(resp.error.title); return; } + showToast("Media added!"); + refetch(); + }; + + const patchMedia = + (id: components["schemas"]["MediaDB"]["ID"]) => + async (media: components["schemas"]["MediaInfo"]) => { + const resp = await karaberus.PATCH("/api/tags/media/{id}", { + params: { path: { id } }, + body: media, + }); + if (resp.error) { + alert(resp.error.title); + return; + } + modalRef.close(); + showToast("Media edited!"); + refetch(); + }; + const deleteMedia = async (id: components["schemas"]["MediaDB"]["ID"]) => { + if (!confirm("Confirm deletion?")) { + return; + } const resp = await karaberus.DELETE("/api/tags/media/{id}", { params: { path: { id } }, }); @@ -24,6 +58,7 @@ export default function TagsMedia() { alert(resp.error.title); return; } + showToast("Media deleted!"); refetch(); }; @@ -33,56 +68,73 @@ export default function TagsMedia() {

Add media

- { - setToast("Media added!"); - setTimeout(() => setToast(), 3000); - refetch(); - }} - /> +

Browse

- - - - - - - - - - - - - {(getMedia) => ( - - - - - - - - )} - - -
TypeNameAdditional Names
{getMedia().ID}{getMedia().media_type}{getMedia().name} -
    - - {(getAdditionalName) => ( -
  • {getAdditionalName().Name}
  • - )} -
    -
-
- -
+
+ + + + + + + + + + + + + {(getMedia) => ( + + + + + + + + )} + + +
TypeNameAdditional Names
{getMedia().ID}{getMedia().media_type}{getMedia().name} +
    + + {(getAdditionalName) => ( +
  • {getAdditionalName().Name}
  • + )} +
    +
+
+ + +
+
+ + + + + {(getToast) => ( From cca7e9b0dc897dd4d0f29ca2ddbde0695438e339 Mon Sep 17 00:00:00 2001 From: NextFire Date: Fri, 23 Aug 2024 22:45:54 +0200 Subject: [PATCH 2/9] server: missing isAssociationsUpdate checks --- server/model.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/model.go b/server/model.go index 824b1458..3b85f5a5 100644 --- a/server/model.go +++ b/server/model.go @@ -141,6 +141,9 @@ func (a *Artist) AfterUpdate(tx *gorm.DB) error { } func (a *Artist) BeforeUpdate(tx *gorm.DB) error { + if isAssociationsUpdate(tx) { + return nil + } orig_artist := &Artist{} err := tx.First(orig_artist, a.ID).Error if err != nil { @@ -196,6 +199,9 @@ func (m *MediaDB) AfterUpdate(tx *gorm.DB) error { } func (m *MediaDB) BeforeUpdate(tx *gorm.DB) error { + if isAssociationsUpdate(tx) { + return nil + } orig_media := &MediaDB{} err := tx.First(orig_media, m.ID).Error if err != nil { From 9d17dd8cad7eb90dce4177722967073f14c9f186 Mon Sep 17 00:00:00 2001 From: NextFire Date: Sat, 24 Aug 2024 14:54:41 +0200 Subject: [PATCH 3/9] ui: alert error detail --- ui/src/components/KaraEditor.tsx | 6 +++--- ui/src/components/MpvKaraPlayer.tsx | 2 +- ui/src/components/MugenImport.tsx | 2 +- ui/src/components/TokenForm.tsx | 2 +- ui/src/pages/karaoke/browse/[id].tsx | 4 ++-- ui/src/pages/karaoke/new.tsx | 2 +- ui/src/pages/settings.tsx | 2 +- ui/src/pages/tags/artist.tsx | 6 +++--- ui/src/pages/tags/author.tsx | 6 +++--- ui/src/pages/tags/media.tsx | 6 +++--- 10 files changed, 19 insertions(+), 19 deletions(-) diff --git a/ui/src/components/KaraEditor.tsx b/ui/src/components/KaraEditor.tsx index fac3c46c..339a2860 100644 --- a/ui/src/components/KaraEditor.tsx +++ b/ui/src/components/KaraEditor.tsx @@ -108,7 +108,7 @@ export default function KaraEditor(props: { const postAuthor = async (author: components["schemas"]["AuthorInfo"]) => { const resp = await karaberus.POST("/api/tags/author", { body: author }); if (resp.error) { - alert(resp.error.title); + alert(resp.error.detail); return; } showToast("Author added!"); @@ -125,7 +125,7 @@ export default function KaraEditor(props: { const postArtist = async (artist: components["schemas"]["ArtistInfo"]) => { const resp = await karaberus.POST("/api/tags/artist", { body: artist }); if (resp.error) { - alert(resp.error.title); + alert(resp.error.detail); return; } showToast("Artist added!"); @@ -142,7 +142,7 @@ export default function KaraEditor(props: { const postMedia = async (media: components["schemas"]["MediaInfo"]) => { const resp = await karaberus.POST("/api/tags/media", { body: media }); if (resp.error) { - alert(resp.error.title); + alert(resp.error.detail); return; } showToast("Media added!"); diff --git a/ui/src/components/MpvKaraPlayer.tsx b/ui/src/components/MpvKaraPlayer.tsx index e1eff782..b753a0af 100644 --- a/ui/src/components/MpvKaraPlayer.tsx +++ b/ui/src/components/MpvKaraPlayer.tsx @@ -42,7 +42,7 @@ export default function MpvKaraPlayer(props: { }, }); if (resp.error) { - throw new Error(resp.error.title); + throw new Error(resp.error.detail); } token = resp.data.token; await getTauriStore().set(PLAYER_TOKEN_KEY, token); diff --git a/ui/src/components/MugenImport.tsx b/ui/src/components/MugenImport.tsx index 445f15c6..2962a966 100644 --- a/ui/src/components/MugenImport.tsx +++ b/ui/src/components/MugenImport.tsx @@ -30,7 +30,7 @@ export default function MugenImport(props: { }); if (resp.error) { - alert(resp.error.title); + alert(resp.error.detail); return; } diff --git a/ui/src/components/TokenForm.tsx b/ui/src/components/TokenForm.tsx index 12156cc3..67a1d904 100644 --- a/ui/src/components/TokenForm.tsx +++ b/ui/src/components/TokenForm.tsx @@ -27,7 +27,7 @@ export default function TokenForm(props: { }); if (resp.error) { - alert(resp.error.title); + alert(resp.error.detail); return; } diff --git a/ui/src/pages/karaoke/browse/[id].tsx b/ui/src/pages/karaoke/browse/[id].tsx index 65999055..8d60e93a 100644 --- a/ui/src/pages/karaoke/browse/[id].tsx +++ b/ui/src/pages/karaoke/browse/[id].tsx @@ -52,7 +52,7 @@ export default function KaraokeBrowseId() { }); if (resp.error) { - alert(resp.error.title); + alert(resp.error.detail); return; } @@ -75,7 +75,7 @@ export default function KaraokeBrowseId() { }); if (resp.error) { - alert(resp.error.title); + alert(resp.error.detail); return; } diff --git a/ui/src/pages/karaoke/new.tsx b/ui/src/pages/karaoke/new.tsx index 38350ba2..ece1a3ed 100644 --- a/ui/src/pages/karaoke/new.tsx +++ b/ui/src/pages/karaoke/new.tsx @@ -12,7 +12,7 @@ export default function KaraokeNew() { }); if (resp.error) { - alert(resp.error.title); + alert(resp.error.detail); return; } diff --git a/ui/src/pages/settings.tsx b/ui/src/pages/settings.tsx index 342702b3..544355d4 100644 --- a/ui/src/pages/settings.tsx +++ b/ui/src/pages/settings.tsx @@ -28,7 +28,7 @@ export default function Settings() { }, }); if (resp.error) { - alert(resp.error.title); + alert(resp.error.detail); return; } refetchTokens(); diff --git a/ui/src/pages/tags/artist.tsx b/ui/src/pages/tags/artist.tsx index 7c366a79..e544dcae 100644 --- a/ui/src/pages/tags/artist.tsx +++ b/ui/src/pages/tags/artist.tsx @@ -24,7 +24,7 @@ export default function TagsArtist() { const postArtist = async (artist: components["schemas"]["ArtistInfo"]) => { const resp = await karaberus.POST("/api/tags/artist", { body: artist }); if (resp.error) { - alert(resp.error.title); + alert(resp.error.detail); return; } showToast("Artist added!"); @@ -39,7 +39,7 @@ export default function TagsArtist() { body: artist, }); if (resp.error) { - alert(resp.error.title); + alert(resp.error.detail); return; } modalRef.close(); @@ -55,7 +55,7 @@ export default function TagsArtist() { params: { path: { id } }, }); if (resp.error) { - alert(resp.error.title); + alert(resp.error.detail); return; } showToast("Artist deleted!"); diff --git a/ui/src/pages/tags/author.tsx b/ui/src/pages/tags/author.tsx index 7191cd30..99c6dcae 100644 --- a/ui/src/pages/tags/author.tsx +++ b/ui/src/pages/tags/author.tsx @@ -24,7 +24,7 @@ export default function TagsAuthor() { const postAuthor = async (author: components["schemas"]["AuthorInfo"]) => { const resp = await karaberus.POST("/api/tags/author", { body: author }); if (resp.error) { - alert(resp.error.title); + alert(resp.error.detail); return; } showToast("Author added!"); @@ -39,7 +39,7 @@ export default function TagsAuthor() { body: author, }); if (resp.error) { - alert(resp.error.title); + alert(resp.error.detail); return; } modalRef.close(); @@ -57,7 +57,7 @@ export default function TagsAuthor() { params: { path: { id } }, }); if (resp.error) { - alert(resp.error.title); + alert(resp.error.detail); return; } showToast("Author deleted!"); diff --git a/ui/src/pages/tags/media.tsx b/ui/src/pages/tags/media.tsx index 37b9d8a0..7f8ee97e 100644 --- a/ui/src/pages/tags/media.tsx +++ b/ui/src/pages/tags/media.tsx @@ -24,7 +24,7 @@ export default function TagsMedia() { const postMedia = async (media: components["schemas"]["MediaInfo"]) => { const resp = await karaberus.POST("/api/tags/media", { body: media }); if (resp.error) { - alert(resp.error.title); + alert(resp.error.detail); return; } showToast("Media added!"); @@ -39,7 +39,7 @@ export default function TagsMedia() { body: media, }); if (resp.error) { - alert(resp.error.title); + alert(resp.error.detail); return; } modalRef.close(); @@ -55,7 +55,7 @@ export default function TagsMedia() { params: { path: { id } }, }); if (resp.error) { - alert(resp.error.title); + alert(resp.error.detail); return; } showToast("Media deleted!"); From 260148009efbdaaeb21af53f5f41482b5c3079d3 Mon Sep 17 00:00:00 2001 From: NextFire Date: Sat, 24 Aug 2024 15:06:42 +0200 Subject: [PATCH 4/9] server: uniqueIndex where clauses --- server/artists.go | 2 +- server/media.go | 2 +- server/model.go | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/server/artists.go b/server/artists.go index f7095ee9..ca249419 100644 --- a/server/artists.go +++ b/server/artists.go @@ -85,7 +85,7 @@ func updateArtist(tx *gorm.DB, artist *Artist) error { prev_context := tx.Statement.Context tx = WithAssociationsUpdate(tx) defer tx.WithContext(prev_context) - err = tx.Model(&artist).Association("AdditionalName").Replace(&artist.AdditionalNames) + err = tx.Model(&artist).Association("AdditionalNames").Replace(&artist.AdditionalNames) if err != nil { return err } diff --git a/server/media.go b/server/media.go index 177e6bad..bc6a27ef 100644 --- a/server/media.go +++ b/server/media.go @@ -85,7 +85,7 @@ func updateMedia(tx *gorm.DB, media *MediaDB) error { prev_context := tx.Statement.Context tx = WithAssociationsUpdate(tx) defer tx.WithContext(prev_context) - err = tx.Model(&media).Association("AdditionalName").Replace(&media.AdditionalNames) + err = tx.Model(&media).Association("AdditionalNames").Replace(&media.AdditionalNames) if err != nil { return err } diff --git a/server/model.go b/server/model.go index 3b85f5a5..737fd8fe 100644 --- a/server/model.go +++ b/server/model.go @@ -122,7 +122,7 @@ type TokenV2 struct { type Artist struct { gorm.Model - Name string `gorm:"uniqueIndex:idx_artist_name"` + Name string `gorm:"uniqueIndex:idx_artist_name,where:current_artist_id IS NULL"` AdditionalNames []AdditionalName `gorm:"many2many:artists_additional_name"` CurrentArtistID *uint CurrentArtist *Artist @@ -179,8 +179,8 @@ var MediaTypes []MediaType = []MediaType{ type MediaDB struct { gorm.Model - Name string `json:"name" example:"Shinseiki Evangelion" gorm:"uniqueIndex:idx_media_name_type"` - Type string `json:"media_type" example:"ANIME" gorm:"uniqueIndex:idx_media_name_type"` + Name string `json:"name" example:"Shinseiki Evangelion" gorm:"uniqueIndex:idx_media_name_type,where:current_media_id IS NULL"` + Type string `json:"media_type" example:"ANIME" gorm:"uniqueIndex:idx_media_name_type,where:current_media_id IS NULL"` AdditionalNames []AdditionalName `json:"additional_name" gorm:"many2many:media_additional_name"` CurrentMediaID *uint CurrentMedia *MediaDB From c7ea4993a5b1a323e8c4d1299f20a410531a60c8 Mon Sep 17 00:00:00 2001 From: NextFire Date: Sat, 24 Aug 2024 15:22:18 +0200 Subject: [PATCH 5/9] ui: artist/media editor nullify empty fields --- ui/src/components/ArtistEditor.tsx | 8 ++++++-- ui/src/components/KaraEditor.tsx | 10 ++++++---- ui/src/components/MediaEditor.tsx | 8 ++++++-- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/ui/src/components/ArtistEditor.tsx b/ui/src/components/ArtistEditor.tsx index c1713581..50d50f8f 100644 --- a/ui/src/components/ArtistEditor.tsx +++ b/ui/src/components/ArtistEditor.tsx @@ -8,14 +8,18 @@ export default function ArtistEditor(props: { }) { const [getName, setName] = createSignal(props.artist?.Name ?? ""); const [getAdditionalNames, setAdditionalNames] = createSignal( - props.artist?.AdditionalNames?.join("\n") ?? "", + props.artist?.AdditionalNames?.map((n) => n.Name).join("\n") ?? "", ); const onsubmit: JSX.EventHandler = (e) => { e.preventDefault(); + const additionalNamesStr = getAdditionalNames().trim(); + const additionalNames = additionalNamesStr + ? additionalNamesStr.split("\n") + : null; props.onSubmit({ name: getName(), - additional_names: getAdditionalNames().trim().split("\n"), + additional_names: additionalNames, }); if (props.reset) { (e.target as HTMLFormElement).reset(); diff --git a/ui/src/components/KaraEditor.tsx b/ui/src/components/KaraEditor.tsx index 339a2860..8a390508 100644 --- a/ui/src/components/KaraEditor.tsx +++ b/ui/src/components/KaraEditor.tsx @@ -56,9 +56,9 @@ export default function KaraEditor(props: { const [getSourceMedia, setSourceMedia] = createSignal< components["schemas"]["MediaDB"] | undefined >(props.kara?.SourceMedia); - const [getSongOrder, setSongOrder] = createSignal( - props.kara?.SongOrder, - ); + const [getSongOrder, setSongOrder] = createSignal< + components["schemas"]["KaraInfo"]["song_order"] | undefined + >(props.kara?.SongOrder); const [getMedias, setMedias] = createSignal< components["schemas"]["MediaDB"][] >(props.kara?.Medias ?? []); @@ -86,9 +86,11 @@ export default function KaraEditor(props: { const onsubmit: JSX.EventHandler = (e) => { e.preventDefault(); + const extraTitlesStr = getExtraTitles().trim(); + const extraTitles = extraTitlesStr ? extraTitlesStr.split("\n") : null; props.onSubmit({ title: getTitle(), - title_aliases: getExtraTitles().trim().split("\n") || null, + title_aliases: extraTitles, authors: getAuthors().map((author) => author.ID) || null, artists: getArtists().map((artist) => artist.ID) || null, source_media: getSourceMedia()?.ID || 0, diff --git a/ui/src/components/MediaEditor.tsx b/ui/src/components/MediaEditor.tsx index bbc20c3b..4b320dcc 100644 --- a/ui/src/components/MediaEditor.tsx +++ b/ui/src/components/MediaEditor.tsx @@ -17,15 +17,19 @@ export default function MediaEditor(props: { ); const [getName, setName] = createSignal(props.media?.name ?? ""); const [getAdditionalNames, setAdditionalNames] = createSignal( - props.media?.additional_name?.join("\n") ?? "", + props.media?.additional_name?.map((n) => n.Name).join("\n") ?? "", ); const onsubmit: JSX.EventHandler = (e) => { e.preventDefault(); + const additionalNamesStr = getAdditionalNames().trim(); + const additionalNames = additionalNamesStr + ? additionalNamesStr.split("\n") + : null; props.onSubmit({ media_type: getMediaType(), name: getName(), - additional_names: getAdditionalNames().trim().split("\n"), + additional_names: additionalNames, }); if (props.reset) { (e.target as HTMLFormElement).reset(); From faea66fe9151607d3dab510de1b850e1f1016618 Mon Sep 17 00:00:00 2001 From: NextFire Date: Sat, 24 Aug 2024 15:41:43 +0200 Subject: [PATCH 6/9] server: trim author/token name --- server/model.go | 12 +++++++++++- ui/src/components/AuthorEditor.tsx | 4 +++- ui/src/components/MugenImport.tsx | 5 ++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/server/model.go b/server/model.go index 737fd8fe..8e2e2684 100644 --- a/server/model.go +++ b/server/model.go @@ -86,6 +86,11 @@ type TimingAuthor struct { MugenID *uuid.UUID `gorm:"uniqueIndex:idx_timing_author_mugen_id"` } +func (name *TimingAuthor) BeforeSave(tx *gorm.DB) error { + name.Name = trimWhitespace(name.Name) + return nil +} + type Scopes struct { Kara bool `json:"kara"` KaraRO bool `json:"kara_ro"` @@ -118,6 +123,11 @@ type TokenV2 struct { Scopes Scopes `gorm:"embedded" json:"scopes"` } +func (name *TokenV2) BeforeSave(tx *gorm.DB) error { + name.Name = trimWhitespace(name.Name) + return nil +} + // Artists type Artist struct { @@ -258,7 +268,7 @@ type AdditionalName struct { } func trimWhitespace(s string) string { - return strings.Trim(s, " \n") + return strings.Trim(s, " \t\n") } func (name *AdditionalName) BeforeSave(tx *gorm.DB) error { diff --git a/ui/src/components/AuthorEditor.tsx b/ui/src/components/AuthorEditor.tsx index e22c6c6c..ee2a02c1 100644 --- a/ui/src/components/AuthorEditor.tsx +++ b/ui/src/components/AuthorEditor.tsx @@ -10,7 +10,9 @@ export default function AuthorEditor(props: { const onsubmit: JSX.EventHandler = (e) => { e.preventDefault(); - props.onSubmit({ name: getName() }); + props.onSubmit({ + name: getName(), + }); if (props.reset) { (e.target as HTMLFormElement).reset(); } diff --git a/ui/src/components/MugenImport.tsx b/ui/src/components/MugenImport.tsx index 2962a966..834c25a9 100644 --- a/ui/src/components/MugenImport.tsx +++ b/ui/src/components/MugenImport.tsx @@ -8,7 +8,10 @@ export default function MugenImport(props: { const [getInput, setInput] = createSignal(""); const [getName, setName] = createSignal(""); - const getKid = () => getInput().split("/").pop() ?? getInput(); + const getKid = () => { + const input = getInput().trim(); + return input.split("/").pop() ?? input; + }; createEffect(async () => { if (getKid().length !== 36) { From b19d3e146f3893e4f58450c500e5fe35515dc52c Mon Sep 17 00:00:00 2001 From: NextFire Date: Sat, 24 Aug 2024 15:50:38 +0200 Subject: [PATCH 7/9] server: add missings models in automigrate --- server/model.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/server/model.go b/server/model.go index 8e2e2684..0363818c 100644 --- a/server/model.go +++ b/server/model.go @@ -442,7 +442,19 @@ type Font struct { } func init_model(db *gorm.DB) { - err := db.AutoMigrate(&KaraInfoDB{}, &User{}, &TokenV2{}, &MediaDB{}, &Artist{}, &Font{}, &MugenImport{}) + err := db.AutoMigrate( + &User{}, + &TimingAuthor{}, + &TokenV2{}, + &Artist{}, + &MediaDB{}, + &AdditionalName{}, + &VideoTagDB{}, + &AudioTagDB{}, + &KaraInfoDB{}, + &MugenImport{}, + &Font{}, + ) if err != nil { panic(err) } From b2f80cc428c279b303cdcaae93c712dcf65b9f75 Mon Sep 17 00:00:00 2001 From: NextFire Date: Sat, 24 Aug 2024 15:54:16 +0200 Subject: [PATCH 8/9] server: uniqueIndex ignore deleted items --- server/model.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/model.go b/server/model.go index 0363818c..83c27ef0 100644 --- a/server/model.go +++ b/server/model.go @@ -132,7 +132,7 @@ func (name *TokenV2) BeforeSave(tx *gorm.DB) error { type Artist struct { gorm.Model - Name string `gorm:"uniqueIndex:idx_artist_name,where:current_artist_id IS NULL"` + Name string `gorm:"uniqueIndex:idx_artist_name,where:current_artist_id IS NULL AND deleted_at IS NULL"` AdditionalNames []AdditionalName `gorm:"many2many:artists_additional_name"` CurrentArtistID *uint CurrentArtist *Artist @@ -189,8 +189,8 @@ var MediaTypes []MediaType = []MediaType{ type MediaDB struct { gorm.Model - Name string `json:"name" example:"Shinseiki Evangelion" gorm:"uniqueIndex:idx_media_name_type,where:current_media_id IS NULL"` - Type string `json:"media_type" example:"ANIME" gorm:"uniqueIndex:idx_media_name_type,where:current_media_id IS NULL"` + Name string `json:"name" example:"Shinseiki Evangelion" gorm:"uniqueIndex:idx_media_name_type,where:current_media_id IS NULL AND deleted_at IS NULL"` + Type string `json:"media_type" example:"ANIME" gorm:"uniqueIndex:idx_media_name_type,where:current_media_id IS NULL AND deleted_at IS NULL"` AdditionalNames []AdditionalName `json:"additional_name" gorm:"many2many:media_additional_name"` CurrentMediaID *uint CurrentMedia *MediaDB From 0c6c391427ae1b8870e92cc3a7c5fddffa1dd269 Mon Sep 17 00:00:00 2001 From: NextFire Date: Sat, 24 Aug 2024 16:07:44 +0200 Subject: [PATCH 9/9] server: artist/media create new index, drop previous one --- server/model.go | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/server/model.go b/server/model.go index 83c27ef0..e9e662f8 100644 --- a/server/model.go +++ b/server/model.go @@ -132,7 +132,7 @@ func (name *TokenV2) BeforeSave(tx *gorm.DB) error { type Artist struct { gorm.Model - Name string `gorm:"uniqueIndex:idx_artist_name,where:current_artist_id IS NULL AND deleted_at IS NULL"` + Name string `gorm:"uniqueIndex:idx_artist_name_v2,where:current_artist_id IS NULL AND deleted_at IS NULL"` AdditionalNames []AdditionalName `gorm:"many2many:artists_additional_name"` CurrentArtistID *uint CurrentArtist *Artist @@ -189,8 +189,8 @@ var MediaTypes []MediaType = []MediaType{ type MediaDB struct { gorm.Model - Name string `json:"name" example:"Shinseiki Evangelion" gorm:"uniqueIndex:idx_media_name_type,where:current_media_id IS NULL AND deleted_at IS NULL"` - Type string `json:"media_type" example:"ANIME" gorm:"uniqueIndex:idx_media_name_type,where:current_media_id IS NULL AND deleted_at IS NULL"` + Name string `json:"name" example:"Shinseiki Evangelion" gorm:"uniqueIndex:idx_media_name_type_v2,where:current_media_id IS NULL AND deleted_at IS NULL"` + Type string `json:"media_type" example:"ANIME" gorm:"uniqueIndex:idx_media_name_type_v2,where:current_media_id IS NULL AND deleted_at IS NULL"` AdditionalNames []AdditionalName `json:"additional_name" gorm:"many2many:media_additional_name"` CurrentMediaID *uint CurrentMedia *MediaDB @@ -458,6 +458,20 @@ func init_model(db *gorm.DB) { if err != nil { panic(err) } + + // PR #73 + if db.Migrator().HasIndex(&Artist{}, "idx_artist_name") { + err = db.Migrator().DropIndex(&Artist{}, "idx_artist_name") + if err != nil { + panic(err) + } + } + if db.Migrator().HasIndex(&MediaDB{}, "idx_media_name_type") { + err = db.Migrator().DropIndex(&MediaDB{}, "idx_media_name_type") + if err != nil { + panic(err) + } + } } func createAdditionalNames(names []string) []AdditionalName {