From 9386cc320bb1fb445ebf488d28ab1808338f1795 Mon Sep 17 00:00:00 2001 From: hay-kot Date: Sat, 7 Aug 2021 15:12:25 -0800 Subject: [PATCH] refactor(frontend): :construction: Migrate Dashboard to Nuxt Add API and Functinality for Admin Dashboard. Stills needs to clean-up. See // TODO's --- frontend/api/class-interfaces/_base.ts | 10 +- frontend/api/class-interfaces/backups.ts | 69 +++++++++++ frontend/api/class-interfaces/debug.ts | 51 ++++++++ .../class-interfaces/event-notifications.ts | 0 frontend/api/class-interfaces/events.ts | 49 ++++++++ frontend/api/class-interfaces/groups.ts | 4 +- frontend/api/class-interfaces/recipes.ts | 5 +- frontend/api/class-interfaces/upload.ts | 7 ++ frontend/api/class-interfaces/users.ts | 4 +- frontend/api/index.ts | 12 ++ .../Domain/Admin/AdminBackupDialog.vue | 4 +- .../Domain/Admin/AdminBackupImportDialog.vue | 16 +-- .../Domain/Admin/AdminBackupImportOptions.vue | 20 +++- .../Domain/Admin/AdminBackupViewer.vue | 111 ++++++++++++++---- .../Domain/Admin/AdminEventViewer.vue | 19 ++- .../Domain/Recipe/RecipeCardSection.vue | 2 +- .../components/Domain/Recipe/RecipeChips.vue | 4 +- .../Domain/Recipe/RecipeFavoriteBadge.vue | 1 - .../components/Layout/AppFloatingButton.vue | 21 +++- frontend/components/Layout/TheSnackbar.vue | 57 +++++++++ .../components/global/AppButtonUpload.vue | 31 ++++- frontend/components/global/BaseButton.vue | 12 +- frontend/components/global/BaseDialog.vue | 14 ++- frontend/composables/use-backups.ts | 84 +++++++++++++ frontend/composables/use-toast.ts | 65 ++++++++++ frontend/composables/use-utils.ts | 3 + frontend/layouts/admin.vue | 5 +- frontend/nuxt.config.js | 4 +- frontend/pages/admin/dashboard.vue | 76 +++++++++--- frontend/pages/index.vue | 14 ++- frontend/pages/user/login.vue | 5 +- frontend/pages/user/sign-up.vue | 5 +- 32 files changed, 671 insertions(+), 113 deletions(-) create mode 100644 frontend/api/class-interfaces/backups.ts create mode 100644 frontend/api/class-interfaces/debug.ts create mode 100644 frontend/api/class-interfaces/event-notifications.ts create mode 100644 frontend/api/class-interfaces/events.ts create mode 100644 frontend/api/class-interfaces/upload.ts create mode 100644 frontend/components/Layout/TheSnackbar.vue create mode 100644 frontend/composables/use-backups.ts create mode 100644 frontend/composables/use-toast.ts create mode 100644 frontend/composables/use-utils.ts diff --git a/frontend/api/class-interfaces/_base.ts b/frontend/api/class-interfaces/_base.ts index ce034f70e8c2..e1ec27f7a3b7 100644 --- a/frontend/api/class-interfaces/_base.ts +++ b/frontend/api/class-interfaces/_base.ts @@ -44,15 +44,17 @@ export const crudMixins = ( return { getAll, getOne, updateOne, patchOne, deleteOne, createOne }; }; -export abstract class BaseAPIClass implements CrudAPIInterface { +export abstract class BaseAPI { requests: ApiRequestInstance; - abstract baseRoute: string; - abstract itemRoute(itemId: string | number): string; - constructor(requests: ApiRequestInstance) { this.requests = requests; } +} + +export abstract class BaseCRUDAPI extends BaseAPI implements CrudAPIInterface { + abstract baseRoute: string; + abstract itemRoute(itemId: string | number): string; async getAll(start = 0, limit = 9999) { return await this.requests.get(this.baseRoute, { diff --git a/frontend/api/class-interfaces/backups.ts b/frontend/api/class-interfaces/backups.ts new file mode 100644 index 000000000000..b1d645ed3c41 --- /dev/null +++ b/frontend/api/class-interfaces/backups.ts @@ -0,0 +1,69 @@ +import { BaseAPI } from "./_base"; + +export interface BackupOptions { + recipes?: boolean; + settings?: boolean; + pages?: boolean; + themes?: boolean; + groups?: boolean; + users?: boolean; + notifications?: boolean; +} + +export interface ImportBackup extends BackupOptions { + name: string; +} + +export interface BackupJob { + tag?: string; + options: BackupOptions; + templates?: string[]; +} + +export interface BackupFile { + name: string; + date: string; +} + +export interface AllBackups { + imports: BackupFile[]; + templates: string[]; +} + +const prefix = "/api"; + +const routes = { + backupsAvailable: `${prefix}/backups/available`, + backupsExportDatabase: `${prefix}/backups/export/database`, + backupsUpload: `${prefix}/backups/upload`, + + backupsFileNameDownload: (fileName: string) => `${prefix}/backups/${fileName}/download`, + backupsFileNameImport: (fileName: string) => `${prefix}/backups/${fileName}/import`, + backupsFileNameDelete: (fileName: string) => `${prefix}/backups/${fileName}/delete`, +}; + +export class BackupAPI extends BaseAPI { + /** Returns a list of avaiable .zip files for import into Mealie. + */ + async getAll() { + return await this.requests.get(routes.backupsAvailable); + } + + /** Generates a backup of the recipe database in json format. + */ + async createOne(payload: BackupJob) { + return await this.requests.post(routes.backupsExportDatabase, payload); + } + + /** Import a database backup file generated from Mealie. + */ + async restoreDatabase(fileName: string, payload: BackupOptions) { + return await this.requests.post(routes.backupsFileNameImport(fileName), payload); + } + + /** Removes a database backup from the file system + */ + async deleteOne(fileName: string) { + return await this.requests.delete(routes.backupsFileNameDelete(fileName)); + } +} diff --git a/frontend/api/class-interfaces/debug.ts b/frontend/api/class-interfaces/debug.ts new file mode 100644 index 000000000000..3bc0450dfdc3 --- /dev/null +++ b/frontend/api/class-interfaces/debug.ts @@ -0,0 +1,51 @@ +import { BaseAPI } from "./_base"; + +export interface AppStatistics { + totalRecipes: number; + totalUsers: number; + totalGroups: number; + uncategorizedRecipes: number; + untaggedRecipes: number; +} + +const prefix = "/api"; + +const routes = { + debugVersion: `${prefix}/debug/version`, + debug: `${prefix}/debug`, + debugStatistics: `${prefix}/debug/statistics`, + debugLastRecipeJson: `${prefix}/debug/last-recipe-json`, + debugLog: `${prefix}/debug/log`, + + debugLogNum: (num: number) => `${prefix}/debug/log/${num}`, +}; + +export class DebugAPI extends BaseAPI { + /** Returns the current version of mealie + */ + async getMealieVersion() { + return await this.requests.get(routes.debugVersion); + } + + /** Returns general information about the application for debugging + */ + async getDebugInfo() { + return await this.requests.get(routes.debug); + } + + async getAppStatistics() { + return await this.requests.get(routes.debugStatistics); + } + + /** Doc Str + */ + async getLog(num: number) { + return await this.requests.get(routes.debugLogNum(num)); + } + + /** Returns a token to download a file + */ + async getLogFile() { + return await this.requests.get(routes.debugLog); + } +} diff --git a/frontend/api/class-interfaces/event-notifications.ts b/frontend/api/class-interfaces/event-notifications.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/frontend/api/class-interfaces/events.ts b/frontend/api/class-interfaces/events.ts new file mode 100644 index 000000000000..c7c45e093150 --- /dev/null +++ b/frontend/api/class-interfaces/events.ts @@ -0,0 +1,49 @@ +import { BaseAPI } from "./_base"; + +export type EventCategory = "general" | "recipe" | "backup" | "scheduled" | "migration" | "group" | "user"; + +export interface Event { + id?: number; + title: string; + text: string; + timeStamp?: string; + category?: EventCategory & string; +} + +export interface EventsOut { + total: number; + events: Event[]; +} + +const prefix = "/api"; + +const routes = { + aboutEvents: `${prefix}/about/events`, + aboutEventsNotifications: `${prefix}/about/events/notifications`, + aboutEventsNotificationsTest: `${prefix}/about/events/notifications/test`, + + aboutEventsId: (id: number) => `${prefix}/about/events/${id}`, + aboutEventsNotificationsId: (id: number) => `${prefix}/about/events/notifications/${id}`, +}; + +export class EventsAPI extends BaseAPI { + /** Get event from the Database + */ + async getEvents() { + return await this.requests.get(routes.aboutEvents); + } + + /** Get event from the Database + */ + async deleteEvents() { + return await this.requests.delete(routes.aboutEvents); + } + + /** Delete event from the Database + */ + async deleteEvent(id: number) { + return await this.requests.delete(routes.aboutEventsId(id)); + } + /** Get all event_notification from the Database + */ +} diff --git a/frontend/api/class-interfaces/groups.ts b/frontend/api/class-interfaces/groups.ts index fb846fc351b4..87f6660b59c4 100644 --- a/frontend/api/class-interfaces/groups.ts +++ b/frontend/api/class-interfaces/groups.ts @@ -1,5 +1,5 @@ import { requests } from "../requests"; -import { BaseAPIClass } from "./_base"; +import { BaseCRUDAPI } from "./_base"; import { GroupInDB } from "~/types/api-types/user"; const prefix = "/api"; @@ -15,7 +15,7 @@ export interface CreateGroup { name: string; } -export class GroupAPI extends BaseAPIClass { +export class GroupAPI extends BaseCRUDAPI { baseRoute = routes.groups; itemRoute = routes.groupsId; /** Returns the Group Data for the Current User diff --git a/frontend/api/class-interfaces/recipes.ts b/frontend/api/class-interfaces/recipes.ts index 147204f84cc4..061c2c5edfe6 100644 --- a/frontend/api/class-interfaces/recipes.ts +++ b/frontend/api/class-interfaces/recipes.ts @@ -1,4 +1,4 @@ -import { BaseAPIClass } from "./_base"; +import { BaseCRUDAPI } from "./_base"; import { Recipe } from "~/types/api-types/admin"; import { CreateRecipe } from "~/types/api-types/recipe"; @@ -18,7 +18,7 @@ const routes = { recipesRecipeSlugAssets: (recipe_slug: string) => `${prefix}/recipes/${recipe_slug}/assets`, }; -export class RecipeAPI extends BaseAPIClass { +export class RecipeAPI extends BaseCRUDAPI { baseRoute: string = routes.recipesBase; itemRoute = routes.recipesRecipeSlug; @@ -31,6 +31,7 @@ export class RecipeAPI extends BaseAPIClass { updateImage(slug: string, fileObject: File) { const formData = new FormData(); formData.append("image", fileObject); + // @ts-ignore formData.append("extension", fileObject.name.split(".").pop()); return this.requests.put(routes.recipesRecipeSlugImage(slug), formData); diff --git a/frontend/api/class-interfaces/upload.ts b/frontend/api/class-interfaces/upload.ts new file mode 100644 index 000000000000..01433803c6e3 --- /dev/null +++ b/frontend/api/class-interfaces/upload.ts @@ -0,0 +1,7 @@ +import { BaseAPI } from "./_base"; + +export class UploadFile extends BaseAPI { + file(url: string, fileObject: any) { + return this.requests.post(url, fileObject); + } +} diff --git a/frontend/api/class-interfaces/users.ts b/frontend/api/class-interfaces/users.ts index f0cd63772906..eb48026c5870 100644 --- a/frontend/api/class-interfaces/users.ts +++ b/frontend/api/class-interfaces/users.ts @@ -1,4 +1,4 @@ -import { BaseAPIClass } from "./_base"; +import { BaseCRUDAPI } from "./_base"; import { UserIn, UserOut } from "~/types/api-types/user"; // Interfaces @@ -31,7 +31,7 @@ const routes = { usersApiTokensTokenId: (token_id: string) => `${prefix}/users/api-tokens/${token_id}`, }; -export class UserApi extends BaseAPIClass { +export class UserApi extends BaseCRUDAPI { baseRoute: string = routes.users; itemRoute = (itemid: string) => routes.usersId(itemid); diff --git a/frontend/api/index.ts b/frontend/api/index.ts index ea134cdb45bb..1fc08199f2e4 100644 --- a/frontend/api/index.ts +++ b/frontend/api/index.ts @@ -1,6 +1,10 @@ import { RecipeAPI } from "./class-interfaces/recipes"; import { UserApi } from "./class-interfaces/users"; import { GroupAPI } from "./class-interfaces/groups"; +import { DebugAPI } from "./class-interfaces/debug"; +import { EventsAPI } from "./class-interfaces/events"; +import { BackupAPI } from "./class-interfaces/backups"; +import { UploadFile } from "./class-interfaces/upload"; import { ApiRequestInstance } from "~/types/api"; class Api { @@ -8,6 +12,10 @@ class Api { public recipes: RecipeAPI; public users: UserApi; public groups: GroupAPI; + public debug: DebugAPI; + public events: EventsAPI; + public backups: BackupAPI; + public upload: UploadFile; constructor(requests: ApiRequestInstance) { if (Api.instance instanceof Api) { @@ -17,6 +25,10 @@ class Api { this.recipes = new RecipeAPI(requests); this.users = new UserApi(requests); this.groups = new GroupAPI(requests); + this.debug = new DebugAPI(requests); + this.events = new EventsAPI(requests); + this.backups = new BackupAPI(requests); + this.upload = new UploadFile(requests); Object.freeze(this); Api.instance = this; diff --git a/frontend/components/Domain/Admin/AdminBackupDialog.vue b/frontend/components/Domain/Admin/AdminBackupDialog.vue index 54bb6ad65347..fb21bd899051 100644 --- a/frontend/components/Domain/Admin/AdminBackupDialog.vue +++ b/frontend/components/Domain/Admin/AdminBackupDialog.vue @@ -25,7 +25,7 @@

{{ $t("general.options") }}

- +

{{ $t("general.templates") }}

@@ -47,11 +47,9 @@ diff --git a/frontend/components/Domain/Admin/AdminBackupImportOptions.vue b/frontend/components/Domain/Admin/AdminBackupImportOptions.vue index 4795a619a90e..fe73d2a9b082 100644 --- a/frontend/components/Domain/Admin/AdminBackupImportOptions.vue +++ b/frontend/components/Domain/Admin/AdminBackupImportOptions.vue @@ -9,12 +9,28 @@ :label="option.text" @change="emitValue()" > + diff --git a/frontend/components/Domain/Admin/AdminEventViewer.vue b/frontend/components/Domain/Admin/AdminEventViewer.vue index 5a13affc348d..b22a8b82e310 100644 --- a/frontend/components/Domain/Admin/AdminEventViewer.vue +++ b/frontend/components/Domain/Admin/AdminEventViewer.vue @@ -1,3 +1,5 @@ +// TODO: Fix date/time Localization +