From 17363959864054a0668fa100285ea2c2f042b72a Mon Sep 17 00:00:00 2001
From: mytlogos <mytlogos@hotmail.com>
Date: Wed, 23 Feb 2022 12:36:24 +0100
Subject: [PATCH] feat: add /user/load api

---
 .../database/contexts/internalListContext.ts  | 10 ++++-
 .../src/database/contexts/mediumContext.ts    | 10 +++++
 .../core/src/database/contexts/partContext.ts | 26 +++++++----
 .../src/database/contexts/queryContext.ts     | 44 +++++++++++++++++++
 .../core/src/database/storages/storage.ts     |  6 +++
 packages/core/src/types.ts                    | 28 ++++++++++++
 packages/server/src/api/user.ts               |  9 ++++
 7 files changed, 123 insertions(+), 10 deletions(-)

diff --git a/packages/core/src/database/contexts/internalListContext.ts b/packages/core/src/database/contexts/internalListContext.ts
index 67d1e520..a1de2f67 100644
--- a/packages/core/src/database/contexts/internalListContext.ts
+++ b/packages/core/src/database/contexts/internalListContext.ts
@@ -1,5 +1,5 @@
 import { SubContext } from "./subContext";
-import { List, Medium, Uuid, MultiSingleNumber, MinList, StorageList, ListMedia } from "../../types";
+import { List, Medium, Uuid, MultiSingleNumber, MinList, StorageList, ListMedia, PromiseMultiSingle } from "../../types";
 import { Errors, promiseMultiSingle, multiSingle } from "../../tools";
 import { storeModifications } from "../sqlTools";
 
@@ -51,6 +51,14 @@ export class InternalListContext extends SubContext {
     return { list: lists, media: loadedMedia };
   }
 
+  public async getShallowList<T extends MultiSingleNumber>(listId: T, uuid: Uuid): PromiseMultiSingle<T, List> {
+    // TODO: 29.06.2019 replace with id IN (...)
+    return promiseMultiSingle(listId, async (id: number) => {
+      const result = await this.query("SELECT * FROM reading_list WHERE uuid = ? AND id = ?;", [uuid, id]);
+      return this.createShallowList(result[0]);
+    });
+  }
+
   /**
    * Recreates a list from storage.
    */
diff --git a/packages/core/src/database/contexts/mediumContext.ts b/packages/core/src/database/contexts/mediumContext.ts
index 2bb7c60d..d4f47f2e 100644
--- a/packages/core/src/database/contexts/mediumContext.ts
+++ b/packages/core/src/database/contexts/mediumContext.ts
@@ -466,6 +466,16 @@ export class MediumContext extends SubContext {
     ) as Promise<FullMediumToc[]>;
   }
 
+  public getTocs(tocIds: number[]): Promise<FullMediumToc[]> {
+    return this.queryInList(
+      "SELECT id, medium_id as mediumId, link, " +
+        "countryOfOrigin, languageOfOrigin, author, title," +
+        "medium, artist, lang, stateOrigin, stateTL, series, universe " +
+        "FROM medium_toc WHERE id IN (??);",
+      [tocIds],
+    ) as Promise<FullMediumToc[]>;
+  }
+
   public async removeMediumToc(mediumId: number, link: string): Promise<boolean> {
     const domainRegMatch = /https?:\/\/(.+?)(\/|$)/.exec(link);
 
diff --git a/packages/core/src/database/contexts/partContext.ts b/packages/core/src/database/contexts/partContext.ts
index 73b1d9a9..327d1efe 100644
--- a/packages/core/src/database/contexts/partContext.ts
+++ b/packages/core/src/database/contexts/partContext.ts
@@ -152,22 +152,25 @@ export class PartContext extends SubContext {
   /**
    * Returns all parts of an medium.
    */
-  public async getParts<T extends MultiSingleNumber>(partId: T, uuid: Uuid): Promise<Part[]> {
+  public async getParts<T extends MultiSingleNumber>(partId: T, uuid: Uuid, full = true): Promise<Part[]> {
     const parts: Optional<any[]> = await this.queryInList("SELECT * FROM part WHERE id IN (??);", [partId]);
     if (!parts || !parts.length) {
       return [];
     }
     const partIdMap = new Map<number, any>();
-    const episodesResult: Optional<any[]> = await this.queryInList("SELECT id FROM episode WHERE part_id IN (??);", [
-      parts.map((value) => {
-        partIdMap.set(value.id, value);
-        return value.id;
-      }),
-    ]);
+    const episodesResult: Optional<any[]> = await this.queryInList(
+      "SELECT id, part_id FROM episode WHERE part_id IN (??);",
+      [
+        parts.map((value) => {
+          partIdMap.set(value.id, value);
+          return value.id;
+        }),
+      ],
+    );
 
-    const episodes = episodesResult || [];
+    const episodes: Array<{ id: number; part_id: number }> = episodesResult || [];
 
-    if (episodes) {
+    if (full) {
       const episodeIds = episodes.map((value) => value.id);
       const fullEpisodes = await this.parentContext.episodeContext.getEpisode(episodeIds, uuid);
       fullEpisodes.forEach((value) => {
@@ -180,6 +183,11 @@ export class PartContext extends SubContext {
         }
         part.episodes.push(value);
       });
+    } else {
+      episodes.forEach((value) => {
+        const part: Part = partIdMap.get(value.part_id);
+        (part.episodes as number[]).push(value.id);
+      });
     }
     return parts.map((part) => {
       return {
diff --git a/packages/core/src/database/contexts/queryContext.ts b/packages/core/src/database/contexts/queryContext.ts
index e4870a03..b6b10ac5 100644
--- a/packages/core/src/database/contexts/queryContext.ts
+++ b/packages/core/src/database/contexts/queryContext.ts
@@ -14,6 +14,8 @@ import {
   Primitive,
   DataStats,
   NewData,
+  QueryItems,
+  QueryItemsResult,
 } from "../../types";
 import { Errors, getElseSet, getElseSetObj, ignore, multiSingle, promiseMultiSingle, batch } from "../../tools";
 import logger from "../../logger";
@@ -730,6 +732,48 @@ export class QueryContext implements ConnectionContext {
     };
   }
 
+  public async queryItems(uuid: Uuid, query: QueryItems): Promise<QueryItemsResult> {
+    const [
+      externalUser,
+      externalMediaLists,
+      mediaLists,
+      mediaTocs,
+      tocs,
+      media,
+      parts,
+      partReleases,
+      partEpisodes,
+      episodes,
+      episodeReleases,
+    ] = await Promise.all([
+      this.externalUserContext.getExternalUser(query.externalUser),
+      Promise.all(query.externalMediaLists.map((id) => this.externalListContext.getExternalList(id))),
+      this.internalListContext.getShallowList(query.mediaLists, uuid),
+      this.mediumContext.getMediumTocs(query.mediaTocs),
+      this.mediumContext.getTocs(query.tocs),
+      this.mediumContext.getSimpleMedium(query.media),
+      this.partContext.getParts(query.parts, uuid, false),
+      this.partContext.getPartReleases(query.partReleases),
+      this.partContext.getPartItems(query.partEpisodes),
+      this.episodeContext.getEpisode(query.episodes, uuid),
+      this.episodeContext.getReleases(query.episodeReleases),
+    ]);
+
+    return {
+      episodeReleases, // by episode id
+      episodes,
+      partEpisodes, // by part id
+      partReleases, // by part id
+      parts,
+      media,
+      tocs, // by toc id
+      mediaTocs, // by medium id
+      mediaLists,
+      externalMediaLists,
+      externalUser,
+    };
+  }
+
   private async _batchFunction<T>(
     value: T[],
     query: string,
diff --git a/packages/core/src/database/storages/storage.ts b/packages/core/src/database/storages/storage.ts
index 39507b5d..f5da9164 100644
--- a/packages/core/src/database/storages/storage.ts
+++ b/packages/core/src/database/storages/storage.ts
@@ -13,6 +13,8 @@ import {
   Nullable,
   DataStats,
   NewData,
+  QueryItems,
+  QueryItemsResult,
 } from "../../types";
 import logger from "../../logger";
 import { databaseSchema } from "../databaseSchema";
@@ -341,6 +343,10 @@ export class Storage {
     return inContext((context) => context.getNew(uuid, date));
   }
 
+  public queryItems(uuid: Uuid, query: QueryItems): Promise<QueryItemsResult> {
+    return inContext((context) => context.queryItems(uuid, query));
+  }
+
   /**
    *
    * @param result
diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts
index a0637329..9598fa1f 100644
--- a/packages/core/src/types.ts
+++ b/packages/core/src/types.ts
@@ -1871,3 +1871,31 @@ export interface CustomHook {
   hookState: HookState;
   comment: string;
 }
+
+export interface QueryItems {
+  episodeReleases: number[]; // by episode id
+  episodes: number[];
+  partEpisodes: number[]; // by part id
+  partReleases: number[]; // by part id
+  parts: number[];
+  media: number[];
+  tocs: number[]; // by toc id
+  mediaTocs: number[]; // by medium id
+  mediaLists: number[];
+  externalMediaLists: number[];
+  externalUser: string[];
+}
+
+export interface QueryItemsResult {
+  episodeReleases: EpisodeRelease[]; // by episode id
+  episodes: Episode[];
+  partEpisodes: Record<number, number[]>; // by part id
+  partReleases: Record<number, SimpleRelease[]>; // by part id
+  parts: Part[];
+  media: SimpleMedium[];
+  tocs: FullMediumToc[]; // by toc id
+  mediaTocs: FullMediumToc[]; // by medium id
+  mediaLists: List[];
+  externalMediaLists: ExternalList[];
+  externalUser: ExternalUser[];
+}
diff --git a/packages/server/src/api/user.ts b/packages/server/src/api/user.ts
index 53a098b9..5eb3d2cb 100644
--- a/packages/server/src/api/user.ts
+++ b/packages/server/src/api/user.ts
@@ -31,6 +31,9 @@ import {
   AppEventProgram,
   AppEventType,
   AppEvent,
+  QueryItems,
+  QueryItemsResult,
+  Uuid,
 } from "enterprise-core/dist/types";
 import { Handler, Router } from "express";
 import { extractQueryParam, createHandler } from "./apiTools";
@@ -386,6 +389,11 @@ const getStatus = createHandler(async (): Promise<Status> => {
   };
 });
 
+const postLoad = createHandler(async (request): Promise<QueryItemsResult> => {
+  const { items, uuid }: { items: QueryItems; uuid: Uuid } = request.body;
+  return storage.queryItems(uuid, items);
+});
+
 /**
  * Creates the User Api Router.
  *
@@ -891,6 +899,7 @@ export function userRouter(): Router {
    */
   router.get("/events", getAllAppEvents);
   router.get("/status", getStatus);
+  router.post("/load", postLoad);
 
   router.use("/medium", mediumRouter());
   router.use("/jobs", jobsRouter());