From 10011d38866a5c93786c3ef42ca1120f05814f9f Mon Sep 17 00:00:00 2001
From: advplyr <advplyr@protonmail.com>
Date: Sun, 24 Sep 2023 17:06:32 -0500
Subject: [PATCH] Add:Remove option for authors & show authors with 0 books on
 authors page #2124

---
 .../components/modals/authors/EditModal.vue   | 32 ++++++++++++++++++-
 client/strings/de.json                        |  1 +
 client/strings/en-us.json                     |  1 +
 client/strings/es.json                        |  1 +
 client/strings/fr.json                        |  1 +
 client/strings/gu.json                        |  1 +
 client/strings/hi.json                        |  1 +
 client/strings/hr.json                        |  1 +
 client/strings/it.json                        |  1 +
 client/strings/lt.json                        |  1 +
 client/strings/nl.json                        |  1 +
 client/strings/no.json                        |  4 ++-
 client/strings/pl.json                        |  1 +
 client/strings/ru.json                        |  1 +
 client/strings/zh-cn.json                     |  1 +
 server/controllers/AuthorController.js        | 24 ++++++++++++++
 server/controllers/LibraryController.js       |  2 +-
 server/models/BookAuthor.js                   |  8 +++--
 server/routers/ApiRouter.js                   |  1 +
 19 files changed, 79 insertions(+), 5 deletions(-)

diff --git a/client/components/modals/authors/EditModal.vue b/client/components/modals/authors/EditModal.vue
index 40292dca..3af64249 100644
--- a/client/components/modals/authors/EditModal.vue
+++ b/client/components/modals/authors/EditModal.vue
@@ -33,8 +33,10 @@
             </div>
 
             <div class="flex pt-2 px-2">
-              <ui-btn type="button" @click="searchAuthor">{{ $strings.ButtonQuickMatch }}</ui-btn>
+              <ui-btn v-if="userCanDelete" small color="error" type="button" @click.stop="removeClick">{{ $strings.ButtonRemove }}</ui-btn>
               <div class="flex-grow" />
+              <ui-btn type="button" class="mx-2" @click="searchAuthor">{{ $strings.ButtonQuickMatch }}</ui-btn>
+
               <ui-btn type="submit">{{ $strings.ButtonSave }}</ui-btn>
             </div>
           </div>
@@ -91,6 +93,9 @@ export default {
     },
     libraryProvider() {
       return this.$store.getters['libraries/getLibraryProvider'](this.currentLibraryId) || 'google'
+    },
+    userCanDelete() {
+      return this.$store.getters['user/getUserCanDelete']
     }
   },
   methods: {
@@ -100,6 +105,31 @@ export default {
       this.authorCopy.description = this.author.description
       this.authorCopy.imagePath = this.author.imagePath
     },
+    removeClick() {
+      const payload = {
+        message: this.$getString('MessageConfirmRemoveAuthor', [this.author.name]),
+        callback: (confirmed) => {
+          if (confirmed) {
+            this.processing = true
+            this.$axios
+              .$delete(`/api/authors/${this.authorId}`)
+              .then(() => {
+                this.$toast.success('Author removed')
+                this.show = false
+              })
+              .catch((error) => {
+                console.error('Failed to remove author', error)
+                this.$toast.error('Failed to remove author')
+              })
+              .finally(() => {
+                this.processing = false
+              })
+          }
+        },
+        type: 'yesNo'
+      }
+      this.$store.commit('globals/setConfirmPrompt', payload)
+    },
     async submitForm() {
       var keysToCheck = ['name', 'asin', 'description', 'imagePath']
       var updatePayload = {}
diff --git a/client/strings/de.json b/client/strings/de.json
index 29842ee9..e7484df6 100644
--- a/client/strings/de.json
+++ b/client/strings/de.json
@@ -534,6 +534,7 @@
   "MessageConfirmMarkSeriesFinished": "Sind Sie sicher, dass Sie alle Medien dieser Reihe als abgeschlossen markieren wollen?",
   "MessageConfirmMarkSeriesNotFinished": "Sind Sie sicher, dass Sie alle Medien dieser Reihe als nicht abgeschlossen markieren wollen?",
   "MessageConfirmRemoveAllChapters": "Sind Sie sicher, dass Sie alle Kapitel entfernen möchten?",
+  "MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
   "MessageConfirmRemoveCollection": "Sind Sie sicher, dass Sie die Sammlung \"{0}\" löschen wollen?",
   "MessageConfirmRemoveEpisode": "Sind Sie sicher, dass Sie die Episode \"{0}\" löschen möchten?",
   "MessageConfirmRemoveEpisodes": "Sind Sie sicher, dass Sie {0} Episoden löschen wollen?",
diff --git a/client/strings/en-us.json b/client/strings/en-us.json
index c2cd3c85..ae018d4b 100644
--- a/client/strings/en-us.json
+++ b/client/strings/en-us.json
@@ -534,6 +534,7 @@
   "MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
   "MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
   "MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
+  "MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
   "MessageConfirmRemoveCollection": "Are you sure you want to remove collection \"{0}\"?",
   "MessageConfirmRemoveEpisode": "Are you sure you want to remove episode \"{0}\"?",
   "MessageConfirmRemoveEpisodes": "Are you sure you want to remove {0} episodes?",
diff --git a/client/strings/es.json b/client/strings/es.json
index 05272ce7..b872ca4e 100644
--- a/client/strings/es.json
+++ b/client/strings/es.json
@@ -534,6 +534,7 @@
   "MessageConfirmMarkSeriesFinished": "Esta seguro que desea marcar todos los libros en esta serie como terminados?",
   "MessageConfirmMarkSeriesNotFinished": "Esta seguro que desea marcar todos los libros en esta serie como no terminados?",
   "MessageConfirmRemoveAllChapters": "Esta seguro que desea remover todos los capitulos?",
+  "MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
   "MessageConfirmRemoveCollection": "Esta seguro que desea remover la colección \"{0}\"?",
   "MessageConfirmRemoveEpisode": "Esta seguro que desea remover el episodio \"{0}\"?",
   "MessageConfirmRemoveEpisodes": "Esta seguro que desea remover {0} episodios?",
diff --git a/client/strings/fr.json b/client/strings/fr.json
index 5bb654af..bcc4efd7 100644
--- a/client/strings/fr.json
+++ b/client/strings/fr.json
@@ -534,6 +534,7 @@
   "MessageConfirmMarkSeriesFinished": "Êtes-vous sûr de vouloir marquer tous les livres de cette série comme terminées ?",
   "MessageConfirmMarkSeriesNotFinished": "Êtes-vous sûr de vouloir marquer tous les livres de cette série comme comme non terminés ?",
   "MessageConfirmRemoveAllChapters": "Êtes-vous sûr de vouloir supprimer tous les chapitres ?",
+  "MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
   "MessageConfirmRemoveCollection": "Êtes-vous sûr de vouloir supprimer la collection « {0} » ?",
   "MessageConfirmRemoveEpisode": "Êtes-vous sûr de vouloir supprimer l’épisode « {0} » ?",
   "MessageConfirmRemoveEpisodes": "Êtes-vous sûr de vouloir supprimer {0} épisodes ?",
diff --git a/client/strings/gu.json b/client/strings/gu.json
index 8ab74889..6b63c96a 100644
--- a/client/strings/gu.json
+++ b/client/strings/gu.json
@@ -534,6 +534,7 @@
   "MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
   "MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
   "MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
+  "MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
   "MessageConfirmRemoveCollection": "Are you sure you want to remove collection \"{0}\"?",
   "MessageConfirmRemoveEpisode": "Are you sure you want to remove episode \"{0}\"?",
   "MessageConfirmRemoveEpisodes": "Are you sure you want to remove {0} episodes?",
diff --git a/client/strings/hi.json b/client/strings/hi.json
index bbf2d4ee..20120e9a 100644
--- a/client/strings/hi.json
+++ b/client/strings/hi.json
@@ -534,6 +534,7 @@
   "MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
   "MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
   "MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
+  "MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
   "MessageConfirmRemoveCollection": "Are you sure you want to remove collection \"{0}\"?",
   "MessageConfirmRemoveEpisode": "Are you sure you want to remove episode \"{0}\"?",
   "MessageConfirmRemoveEpisodes": "Are you sure you want to remove {0} episodes?",
diff --git a/client/strings/hr.json b/client/strings/hr.json
index feee0627..3d217a2c 100644
--- a/client/strings/hr.json
+++ b/client/strings/hr.json
@@ -534,6 +534,7 @@
   "MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
   "MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
   "MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
+  "MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
   "MessageConfirmRemoveCollection": "AJeste li sigurni da želite obrisati kolekciju \"{0}\"?",
   "MessageConfirmRemoveEpisode": "Jeste li sigurni da želite obrisati epizodu \"{0}\"?",
   "MessageConfirmRemoveEpisodes": "Jeste li sigurni da želite obrisati {0} epizoda/-u?",
diff --git a/client/strings/it.json b/client/strings/it.json
index 671f2167..cb418bfb 100644
--- a/client/strings/it.json
+++ b/client/strings/it.json
@@ -534,6 +534,7 @@
   "MessageConfirmMarkSeriesFinished": "Sei sicuro di voler contrassegnare tutti i libri di questa serie come completati?",
   "MessageConfirmMarkSeriesNotFinished": "Sei sicuro di voler contrassegnare tutti i libri di questa serie come non completati?",
   "MessageConfirmRemoveAllChapters": "Sei sicuro di voler rimuovere tutti i capitoli?",
+  "MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
   "MessageConfirmRemoveCollection": "Sei sicuro di voler rimuovere la Raccolta \"{0}\"?",
   "MessageConfirmRemoveEpisode": "Sei sicuro di voler rimuovere l'episodio \"{0}\"?",
   "MessageConfirmRemoveEpisodes": "Sei sicuro di voler rimuovere  {0} episodi?",
diff --git a/client/strings/lt.json b/client/strings/lt.json
index e788083a..625ca66b 100644
--- a/client/strings/lt.json
+++ b/client/strings/lt.json
@@ -534,6 +534,7 @@
   "MessageConfirmMarkSeriesFinished": "Ar tikrai norite pažymėti visas knygas šioje serijoje kaip užbaigtas?",
   "MessageConfirmMarkSeriesNotFinished": "Ar tikrai norite pažymėti visas knygas šioje serijoje kaip nebaigtas?",
   "MessageConfirmRemoveAllChapters": "Ar tikrai norite pašalinti visus skyrius?",
+  "MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
   "MessageConfirmRemoveCollection": "Ar tikrai norite pašalinti kolekciją \"{0}\"?",
   "MessageConfirmRemoveEpisode": "Ar tikrai norite pašalinti epizodą \"{0}\"?",
   "MessageConfirmRemoveEpisodes": "Ar tikrai norite pašalinti {0} epizodus?",
diff --git a/client/strings/nl.json b/client/strings/nl.json
index 77c53f18..9bd34635 100644
--- a/client/strings/nl.json
+++ b/client/strings/nl.json
@@ -534,6 +534,7 @@
   "MessageConfirmMarkSeriesFinished": "Weet je zeker dat je alle boeken in deze serie wil markeren als voltooid?",
   "MessageConfirmMarkSeriesNotFinished": "Weet je zeker dat je alle boeken in deze serie wil markeren als niet voltooid?",
   "MessageConfirmRemoveAllChapters": "Weet je zeker dat je alle hoofdstukken wil verwijderen?",
+  "MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
   "MessageConfirmRemoveCollection": "Weet je zeker dat je de collectie \"{0}\" wil verwijderen?",
   "MessageConfirmRemoveEpisode": "Weet je zeker dat je de aflevering \"{0}\" wil verwijderen?",
   "MessageConfirmRemoveEpisodes": "Weet je zeker dat je {0} afleveringen wil verwijderen?",
diff --git a/client/strings/no.json b/client/strings/no.json
index c5cd6ea8..a2ba3d90 100644
--- a/client/strings/no.json
+++ b/client/strings/no.json
@@ -186,6 +186,7 @@
   "LabelAuthors": "Forfattere",
   "LabelAutoDownloadEpisodes": "Last ned episoder automatisk",
   "LabelBackToUser": "Tilbake til bruker",
+  "LabelBackupLocation": "Backup Location",
   "LabelBackupsEnableAutomaticBackups": "Aktiver automatisk sikkerhetskopi",
   "LabelBackupsEnableAutomaticBackupsHelp": "Sikkerhetskopier lagret under /metadata/backups",
   "LabelBackupsMaxBackupSize": "Maks sikkerhetskopi størrelse (i GB)",
@@ -533,6 +534,7 @@
   "MessageConfirmMarkSeriesFinished": "Er du sikker på at du vil markere alle bøkene i serien som fullført?",
   "MessageConfirmMarkSeriesNotFinished": "Er du sikker på at du vil markere alle bøkene i serien som ikke fullført?",
   "MessageConfirmRemoveAllChapters": "Er du sikker på at du vil fjerne alle kapitler?",
+  "MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
   "MessageConfirmRemoveCollection": "Er du sikker på at du vil fjerne samling\"{0}\"?",
   "MessageConfirmRemoveEpisode": "Er du sikker på at du vil fjerne episode \"{0}\"?",
   "MessageConfirmRemoveEpisodes": "Er du sikker på at du vil fjerne {0} episoder?",
@@ -711,4 +713,4 @@
   "ToastSocketFailedToConnect": "Misslykkes å koble til Socket",
   "ToastUserDeleteFailed": "Misslykkes å slette bruker",
   "ToastUserDeleteSuccess": "Bruker slettet"
-}
+}
\ No newline at end of file
diff --git a/client/strings/pl.json b/client/strings/pl.json
index 84d19667..5804dd8a 100644
--- a/client/strings/pl.json
+++ b/client/strings/pl.json
@@ -534,6 +534,7 @@
   "MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
   "MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
   "MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
+  "MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
   "MessageConfirmRemoveCollection": "Czy na pewno chcesz usunąć kolekcję \"{0}\"?",
   "MessageConfirmRemoveEpisode": "Czy na pewno chcesz usunąć odcinek \"{0}\"?",
   "MessageConfirmRemoveEpisodes": "Czy na pewno chcesz usunąć {0} odcinki?",
diff --git a/client/strings/ru.json b/client/strings/ru.json
index 1ce7f322..50b93cc9 100644
--- a/client/strings/ru.json
+++ b/client/strings/ru.json
@@ -534,6 +534,7 @@
   "MessageConfirmMarkSeriesFinished": "Вы уверены, что хотите отметить все книги этой серии как завершенные?",
   "MessageConfirmMarkSeriesNotFinished": "Вы уверены, что хотите отметить все книги этой серии как не завершенные?",
   "MessageConfirmRemoveAllChapters": "Вы уверены, что хотите удалить все главы?",
+  "MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
   "MessageConfirmRemoveCollection": "Вы уверены, что хотите удалить коллекцию \"{0}\"?",
   "MessageConfirmRemoveEpisode": "Вы уверены, что хотите удалить эпизод \"{0}\"?",
   "MessageConfirmRemoveEpisodes": "Вы уверены, что хотите удалить {0} эпизодов?",
diff --git a/client/strings/zh-cn.json b/client/strings/zh-cn.json
index feb6ee09..3d408590 100644
--- a/client/strings/zh-cn.json
+++ b/client/strings/zh-cn.json
@@ -534,6 +534,7 @@
   "MessageConfirmMarkSeriesFinished": "你确定要将此系列中的所有书籍都标记为已听完吗?",
   "MessageConfirmMarkSeriesNotFinished": "你确定要将此系列中的所有书籍都标记为未听完吗?",
   "MessageConfirmRemoveAllChapters": "你确定要移除所有章节吗?",
+  "MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
   "MessageConfirmRemoveCollection": "您确定要移除收藏 \"{0}\"?",
   "MessageConfirmRemoveEpisode": "您确定要移除剧集 \"{0}\"?",
   "MessageConfirmRemoveEpisodes": "你确定要移除 {0} 剧集?",
diff --git a/server/controllers/AuthorController.js b/server/controllers/AuthorController.js
index 531d2b42..0cd243fd 100644
--- a/server/controllers/AuthorController.js
+++ b/server/controllers/AuthorController.js
@@ -167,6 +167,30 @@ class AuthorController {
     }
   }
 
+  /**
+   * DELETE: /api/authors/:id
+   * Remove author from all books and delete
+   * 
+   * @param {import('express').Request} req 
+   * @param {import('express').Response} res 
+   */
+  async delete(req, res) {
+    Logger.info(`[AuthorController] Removing author "${req.author.name}"`)
+
+    await Database.authorModel.removeById(req.author.id)
+
+    if (req.author.imagePath) {
+      await CacheManager.purgeImageCache(req.author.id) // Purge cache
+    }
+
+    SocketAuthority.emitter('author_removed', req.author.toJSON())
+
+    // Update filter data
+    Database.removeAuthorFromFilterData(req.author.libraryId, req.author.id)
+
+    res.sendStatus(200)
+  }
+
   async match(req, res) {
     let authorData = null
     const region = req.body.region || 'us'
diff --git a/server/controllers/LibraryController.js b/server/controllers/LibraryController.js
index 19e4dbdc..f768bb93 100644
--- a/server/controllers/LibraryController.js
+++ b/server/controllers/LibraryController.js
@@ -620,7 +620,7 @@ class LibraryController {
         model: Database.bookModel,
         attributes: ['id', 'tags', 'explicit'],
         where: bookWhere,
-        required: true,
+        required: false,
         through: {
           attributes: []
         }
diff --git a/server/models/BookAuthor.js b/server/models/BookAuthor.js
index 9f8860ee..671e9470 100644
--- a/server/models/BookAuthor.js
+++ b/server/models/BookAuthor.js
@@ -47,10 +47,14 @@ class BookAuthor extends Model {
     book.belongsToMany(author, { through: BookAuthor })
     author.belongsToMany(book, { through: BookAuthor })
 
-    book.hasMany(BookAuthor)
+    book.hasMany(BookAuthor, {
+      onDelete: 'CASCADE'
+    })
     BookAuthor.belongsTo(book)
 
-    author.hasMany(BookAuthor)
+    author.hasMany(BookAuthor, {
+      onDelete: 'CASCADE'
+    })
     BookAuthor.belongsTo(author)
   }
 }
diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js
index 74d8aa56..dc816b44 100644
--- a/server/routers/ApiRouter.js
+++ b/server/routers/ApiRouter.js
@@ -199,6 +199,7 @@ class ApiRouter {
     //
     this.router.get('/authors/:id', AuthorController.middleware.bind(this), AuthorController.findOne.bind(this))
     this.router.patch('/authors/:id', AuthorController.middleware.bind(this), AuthorController.update.bind(this))
+    this.router.delete('/authors/:id', AuthorController.middleware.bind(this), AuthorController.delete.bind(this))
     this.router.post('/authors/:id/match', AuthorController.middleware.bind(this), AuthorController.match.bind(this))
     this.router.get('/authors/:id/image', AuthorController.middleware.bind(this), AuthorController.getImage.bind(this))