🐘 Massive refactor for graphql changes

This commit is contained in:
2026-03-04 23:42:50 -05:00
parent 4b8d7b5905
commit 74c0d6513c
46 changed files with 10254 additions and 652 deletions

16
codegen.yml Normal file
View File

@@ -0,0 +1,16 @@
schema: http://localhost:3000/graphql
documents: 'src/client/graphql/**/*.graphql'
generates:
src/client/graphql/generated.ts:
plugins:
- typescript
- typescript-operations
- typescript-react-query
config:
fetcher:
func: './fetcher#fetcher'
isReactHook: false
exposeFetcher: true
exposeQueryKeys: true
addInfiniteQuery: true
reactQueryVersion: 5

4601
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,11 +13,14 @@
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
"build-storybook": "storybook build",
"codegen": "graphql-codegen --config codegen.yml",
"codegen:watch": "graphql-codegen --config codegen.yml --watch"
},
"author": "Rishi Ghan",
"license": "MIT",
"dependencies": {
"@apollo/client": "^4.1.6",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
@@ -45,6 +48,7 @@
"final-form": "^5.0.0",
"final-form-arrays": "^4.0.0",
"focus-trap-react": "^12.0.0",
"graphql": "^16.13.1",
"history": "^5.3.0",
"html-to-text": "^9.0.5",
"i18next": "^25.8.13",
@@ -74,6 +78,7 @@
"react-sliding-pane": "^7.3.0",
"react-textarea-autosize": "^8.5.9",
"react-toastify": "^11.0.5",
"rxjs": "^7.8.2",
"socket.io-client": "^4.8.3",
"styled-components": "^6.3.11",
"threetwo-ui-typings": "^1.0.14",
@@ -83,6 +88,10 @@
"zustand": "^5.0.11"
},
"devDependencies": {
"@graphql-codegen/cli": "^6.1.2",
"@graphql-codegen/typescript": "^5.0.8",
"@graphql-codegen/typescript-operations": "^5.0.8",
"@graphql-codegen/typescript-react-query": "^6.1.2",
"@iconify-json/solar": "^1.2.5",
"@iconify/json": "^2.2.443",
"@iconify/tailwind": "^1.2.0",

View File

@@ -55,7 +55,6 @@ export const toggleAirDCPPSocketConnectionStatus =
break;
default:
console.log("Can't set AirDC++ socket status.");
break;
}
};

View File

@@ -43,7 +43,7 @@ export const getWeeklyPullList = (options) => async (dispatch) => {
});
});
} catch (error) {
console.log(error);
// Error handling could be added here if needed
}
};
@@ -73,10 +73,9 @@ export const comicinfoAPICall = (options) => async (dispatch) => {
break;
default:
console.log("Could not complete request.");
break;
}
} catch (error) {
console.log(error);
dispatch({
type: CV_API_GENERIC_FAILURE,
error,
@@ -99,7 +98,6 @@ export const getIssuesForSeries =
comicObjectID,
},
});
console.log(issues);
dispatch({
type: CV_ISSUES_FOR_VOLUME_IN_LIBRARY_SUCCESS,
issues: issues.data.results,

View File

@@ -34,7 +34,6 @@ import {
LS_SET_QUEUE_STATUS,
LS_IMPORT_JOB_STATISTICS_FETCHED,
} from "../constants/action-types";
import { success } from "react-notification-system-redux";
import { isNil } from "lodash";
@@ -151,7 +150,7 @@ export const getComicBooks = (options) => async (dispatch) => {
});
break;
default:
console.log("Unrecognized comic status.");
break;
}
};
@@ -219,12 +218,11 @@ export const fetchVolumeGroups = () => async (dispatch) => {
data: response.data,
});
} catch (error) {
console.log(error);
// Error handling could be added here if needed
}
};
export const fetchComicVineMatches =
(searchPayload, issueSearchQuery, seriesSearchQuery?) => async (dispatch) => {
console.log(issueSearchQuery);
try {
dispatch({
type: CV_API_CALL_IN_PROGRESS,
@@ -273,7 +271,7 @@ export const fetchComicVineMatches =
});
});
} catch (error) {
console.log(error);
// Error handling could be added here if needed
}
dispatch({

View File

@@ -7,8 +7,6 @@ export const fetchMetronResource = async (options) => {
`${METRON_SERVICE_URI}/fetchResource`,
options,
);
console.log(metronResourceResults);
console.log("has more? ", !isNil(metronResourceResults.data.next));
const results = metronResourceResults.data.results.map((result) => {
return {
label: result.name || result.__str__,

View File

@@ -1,9 +1,11 @@
import React, { ReactElement, useEffect } from "react";
import { Outlet } from "react-router-dom";
import { ApolloProvider } from "@apollo/client/react";
import { Navbar2 } from "./shared/Navbar2";
import { ToastContainer } from "react-toastify";
import "../assets/scss/App.css";
import { useStore } from "../store";
import { apolloClient } from "../graphql/client";
export const App = (): ReactElement => {
useEffect(() => {
@@ -11,11 +13,11 @@ export const App = (): ReactElement => {
}, []);
return (
<>
<ApolloProvider client={apolloClient}>
<Navbar2 />
<Outlet />
<ToastContainer stacked hideProgressBar />
</>
</ApolloProvider>
);
};

View File

@@ -160,7 +160,9 @@ export const AcquisitionPanel = (
type,
config,
},
(data: any) => console.log(data),
(data: any) => {
// Download initiated
},
);
};

View File

@@ -10,6 +10,7 @@ import "react-sliding-pane/dist/react-sliding-pane.css";
import SlidingPane from "react-sliding-pane";
import { determineCoverFile } from "../../shared/utils/metadata.utils";
import { styled } from "styled-components";
import { RawFileDetails as RawFileDetailsType } from "../../graphql/generated";
// Extracted modules
import { useComicVineMatching } from "./useComicVineMatching";
@@ -22,45 +23,32 @@ const StyledSlidingPanel = styled(SlidingPane)`
background: #ccc;
`;
interface RawFileDetails {
name: string;
cover?: {
filePath?: string;
};
containedIn?: string;
fileSize?: number;
path?: string;
extension?: string;
mimeType?: string;
[key: string]: any;
}
interface InferredIssue {
type InferredIssue = {
name?: string;
number?: number;
year?: string;
subtitle?: string;
[key: string]: any;
}
};
interface ComicVineMetadata {
type ComicVineMetadata = {
name?: string;
volumeInformation?: any;
[key: string]: any;
}
};
interface Acquisition {
type Acquisition = {
directconnect?: {
downloads?: any[];
};
torrent?: any[];
[key: string]: any;
}
};
interface ComicDetailProps {
type ComicDetailProps = {
data: {
_id: string;
rawFileDetails?: RawFileDetails;
rawFileDetails?: RawFileDetailsType;
inferredMetadata: {
issue?: InferredIssue;
};
@@ -76,7 +64,7 @@ interface ComicDetailProps {
userSettings?: any;
queryClient?: any;
comicObjectId?: string;
}
};
/**
* Component for displaying the metadata for a comic in greater detail.
@@ -117,7 +105,7 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
}, []);
const afterOpenModal = useCallback((things: any) => {
console.log("kolaveri", things);
// Modal opened callback
}, []);
const closeModal = useCallback(() => {
@@ -154,7 +142,6 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
openEditMetadataPanel();
break;
default:
console.log("No valid action selected.");
break;
}
};

View File

@@ -1,9 +1,9 @@
import React, { ReactElement } from "react";
import { useParams } from "react-router-dom";
import { ComicDetail } from "../ComicDetail/ComicDetail";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { LIBRARY_SERVICE_BASE_URI } from "../../constants/endpoints";
import axios from "axios";
import { useQueryClient } from "@tanstack/react-query";
import { useGetComicByIdQuery } from "../../graphql/generated";
import { adaptGraphQLComicToLegacy } from "../../graphql/adapters/comicAdapter";
export const ComicDetailContainer = (): ReactElement | null => {
const { comicObjectId } = useParams<{ comicObjectId: string }>();
@@ -13,31 +13,28 @@ export const ComicDetailContainer = (): ReactElement | null => {
data: comicBookDetailData,
isLoading,
isError,
} = useQuery({
queryKey: ["comicBookMetadata", comicObjectId],
queryFn: async () =>
await axios({
url: `${LIBRARY_SERVICE_BASE_URI}/getComicBookById`,
method: "POST",
data: {
id: comicObjectId,
},
}),
});
{
isError && <>Error</>;
}
{
isLoading && <>Loading...</>;
}
return (
comicBookDetailData?.data && (
<ComicDetail
data={comicBookDetailData.data}
queryClient={queryClient}
comicObjectId={comicObjectId}
/>
)
} = useGetComicByIdQuery(
{ id: comicObjectId! },
{ enabled: !!comicObjectId }
);
if (isError) {
return <div>Error loading comic details</div>;
}
if (isLoading) {
return <div>Loading...</div>;
}
const adaptedData = comicBookDetailData?.comic
? adaptGraphQLComicToLegacy(comicBookDetailData.comic)
: null;
return adaptedData ? (
<ComicDetail
data={adaptedData}
queryClient={queryClient}
comicObjectId={comicObjectId}
/>
) : null;
};

View File

@@ -77,8 +77,6 @@ export const DownloadProgressTick: React.FC<DownloadProgressTickProps> = ({
*/
const onDownloadTick = (data: DownloadTickData) => {
// Compare numeric data.id to string bundleId
console.log(data.id);
console.log(`bundleId is ${bundleId}`)
if (data.id === parseInt(bundleId, 10)) {
setTick(data);
}

View File

@@ -1,21 +1,12 @@
import React, { ReactElement } from "react";
import React, { ReactElement, ReactNode } from "react";
import prettyBytes from "pretty-bytes";
import { isEmpty } from "lodash";
import { format, parseISO } from "date-fns";
import { RawFileDetails as RawFileDetailsType } from "../../graphql/generated";
interface RawFileDetailsProps {
type RawFileDetailsProps = {
data?: {
rawFileDetails?: {
containedIn?: string;
name?: string;
fileSize?: number;
path?: string;
extension?: string;
mimeType?: string;
cover?: {
filePath?: string;
};
};
rawFileDetails?: RawFileDetailsType;
inferredMetadata?: {
issue?: {
year?: string;
@@ -27,18 +18,18 @@ interface RawFileDetailsProps {
created_at?: string;
updated_at?: string;
};
children?: any;
}
children?: ReactNode;
};
export const RawFileDetails = (props: RawFileDetailsProps): ReactElement => {
const { rawFileDetails, inferredMetadata, created_at, updated_at } =
props.data;
props.data || {};
return (
<>
<div className="max-w-2xl ml-5">
<div className="px-4 sm:px-6">
<p className="text-gray-500 dark:text-gray-400">
<span className="text-xl">{rawFileDetails.name}</span>
<span className="text-xl">{rawFileDetails?.name}</span>
</p>
</div>
<div className="px-4 py-5 sm:px-6">
@@ -48,10 +39,10 @@ export const RawFileDetails = (props: RawFileDetailsProps): ReactElement => {
Raw File Details
</dt>
<dd className="mt-1 text-sm text-gray-900 dark:text-gray-400">
{rawFileDetails.containedIn +
"/" +
rawFileDetails.name +
rawFileDetails.extension}
{rawFileDetails?.containedIn}
{"/"}
{rawFileDetails?.name}
{rawFileDetails?.extension}
</dd>
</div>
<div className="sm:col-span-1">
@@ -59,10 +50,10 @@ export const RawFileDetails = (props: RawFileDetailsProps): ReactElement => {
Inferred Issue Metadata
</dt>
<dd className="mt-1 text-sm text-gray-900 dark:text-gray-400">
Series Name: {inferredMetadata.issue.name}
{!isEmpty(inferredMetadata.issue.number) ? (
Series Name: {inferredMetadata?.issue?.name}
{!isEmpty(inferredMetadata?.issue?.number) ? (
<span className="tag is-primary is-light">
{inferredMetadata.issue.number}
{inferredMetadata?.issue?.number}
</span>
) : null}
</dd>
@@ -79,7 +70,7 @@ export const RawFileDetails = (props: RawFileDetailsProps): ReactElement => {
</span>
<span className="text-md text-slate-500 dark:text-slate-900">
{rawFileDetails.mimeType}
{rawFileDetails?.mimeType}
</span>
</span>
</dd>
@@ -96,7 +87,7 @@ export const RawFileDetails = (props: RawFileDetailsProps): ReactElement => {
</span>
<span className="text-md text-slate-500 dark:text-slate-900">
{prettyBytes(rawFileDetails.fileSize)}
{rawFileDetails?.fileSize ? prettyBytes(rawFileDetails.fileSize) : "N/A"}
</span>
</span>
</dd>
@@ -106,8 +97,12 @@ export const RawFileDetails = (props: RawFileDetailsProps): ReactElement => {
Import Details
</dt>
<dd className="mt-1 text-sm text-gray-900 dark:text-gray-400">
{format(parseISO(created_at), "dd MMMM, yyyy")},{" "}
{format(parseISO(created_at), "h aaaa")}
{created_at ? (
<>
{format(parseISO(created_at), "dd MMMM, yyyy")},{" "}
{format(parseISO(created_at), "h aaaa")}
</>
) : "N/A"}
</dd>
</div>
<div className="sm:col-span-2">

View File

@@ -2,17 +2,18 @@ import React from "react";
import { ComicVineSearchForm } from "./ComicVineSearchForm";
import { ComicVineMatchPanel } from "./ComicVineMatchPanel";
import { EditMetadataPanel } from "./EditMetadataPanel";
import { RawFileDetails } from "../../graphql/generated";
interface InferredIssue {
type InferredIssue = {
name?: string;
number?: number;
year?: string;
subtitle?: string;
[key: string]: any;
}
};
interface CVMatchesPanelProps {
rawFileDetails: any;
type CVMatchesPanelProps = {
rawFileDetails?: RawFileDetails;
inferredMetadata: {
issue?: InferredIssue;
};
@@ -20,7 +21,7 @@ interface CVMatchesPanelProps {
comicObjectId: string;
queryClient: any;
onMatchApplied: () => void;
}
};
export const CVMatchesPanel: React.FC<CVMatchesPanelProps> = ({
rawFileDetails,
@@ -55,9 +56,9 @@ export const CVMatchesPanel: React.FC<CVMatchesPanelProps> = ({
</>
);
interface EditMetadataPanelWrapperProps {
rawFileDetails: any;
}
type EditMetadataPanelWrapperProps = {
rawFileDetails?: RawFileDetails;
};
export const EditMetadataPanelWrapper: React.FC<EditMetadataPanelWrapperProps> = ({
rawFileDetails,

View File

@@ -77,8 +77,7 @@ export const ArchiveOperations = (props: { data: any }): ReactElement => {
},
});
} catch (error) {
console.error("Error fetching uncompressed archive:", error);
// Handle error if necessary
// Error handling could be added here if needed
}
};
fetchUncompressedArchive();

View File

@@ -4,7 +4,6 @@ import prettyBytes from "pretty-bytes";
export const TorrentDownloads = (props) => {
const { data } = props;
console.log(Object.values(data));
return (
<>
{data.map(({ torrent }) => {

View File

@@ -43,7 +43,7 @@ export const TorrentSearchPanel = (props) => {
mutationFn: async (newTorrent) =>
axios.post(`${QBITTORRENT_SERVICE_BASE_URI}/addTorrent`, newTorrent),
onSuccess: async (data) => {
console.log(data);
// Torrent added successfully
},
});
const searchIndexer = (values) => {

View File

@@ -3,29 +3,25 @@ import axios from "axios";
import { isNil, isUndefined, isEmpty } from "lodash";
import { refineQuery } from "filename-parser";
import { COMICVINE_SERVICE_URI } from "../../constants/endpoints";
import { RawFileDetails as RawFileDetailsType } from "../../graphql/generated";
interface ComicVineMatch {
type ComicVineMatch = {
score: number;
[key: string]: any;
}
};
interface ComicVineSearchQuery {
type ComicVineSearchQuery = {
inferredIssueDetails: {
name: string;
[key: string]: any;
};
[key: string]: any;
}
};
interface RawFileDetails {
name: string;
[key: string]: any;
}
interface ComicVineMetadata {
type ComicVineMetadata = {
name?: string;
[key: string]: any;
}
};
export const useComicVineMatching = () => {
const [comicVineMatches, setComicVineMatches] = useState<ComicVineMatch[]>([]);
@@ -67,18 +63,18 @@ export const useComicVineMatching = () => {
const scoredMatches = matches.sort((a: ComicVineMatch, b: ComicVineMatch) => b.score - a.score);
setComicVineMatches(scoredMatches);
} catch (err) {
console.log(err);
// Error handling could be added here if needed
}
};
const prepareAndFetchMatches = (
rawFileDetails: RawFileDetails | undefined,
rawFileDetails: RawFileDetailsType | undefined,
comicvine: ComicVineMetadata | undefined,
) => {
let seriesSearchQuery: ComicVineSearchQuery = {} as ComicVineSearchQuery;
let issueSearchQuery: ComicVineSearchQuery = {} as ComicVineSearchQuery;
if (!isUndefined(rawFileDetails)) {
if (!isUndefined(rawFileDetails) && rawFileDetails.name) {
issueSearchQuery = refineQuery(rawFileDetails.name) as ComicVineSearchQuery;
} else if (!isEmpty(comicvine) && comicvine?.name) {
issueSearchQuery = refineQuery(comicvine.name) as ComicVineSearchQuery;

View File

@@ -1,83 +1,70 @@
import React, { ReactElement } from "react";
import ZeroState from "./ZeroState";
import { RecentlyImported } from "./RecentlyImported";
import { WantedComicsList } from "./WantedComicsList";
import { VolumeGroups } from "./VolumeGroups";
import { LibraryStatistics } from "./LibraryStatistics";
import { PullList } from "./PullList";
import { useQuery } from "@tanstack/react-query";
import axios from "axios";
import { LIBRARY_SERVICE_BASE_URI } from "../../constants/endpoints";
import {
useGetRecentComicsQuery,
useGetWantedComicsQuery,
useGetVolumeGroupsQuery,
useGetLibraryStatisticsQuery
} from "../../graphql/generated";
export const Dashboard = (): ReactElement => {
const { data: recentComics } = useQuery({
queryFn: async () =>
await axios({
url: `${LIBRARY_SERVICE_BASE_URI}/getComicBooks`,
method: "POST",
data: {
paginationOptions: {
page: 0,
limit: 5,
sort: { updatedAt: "-1" },
},
predicate: {
wanted: { $exists: false },
},
comicStatus: "recent",
},
}),
queryKey: ["recentComics"],
});
// Wanted Comics
const { data: wantedComics } = useQuery({
queryFn: async () =>
await axios({
url: `${LIBRARY_SERVICE_BASE_URI}/getComicBooks`,
method: "POST",
data: {
paginationOptions: {
page: 0,
limit: 5,
sort: { updatedAt: "-1" },
},
predicate: {
wanted: { $exists: true, $ne: null },
},
},
}),
queryKey: ["wantedComics"],
});
const { data: volumeGroups } = useQuery({
queryFn: async () =>
await axios({
url: `${LIBRARY_SERVICE_BASE_URI}/getComicBookGroups`,
method: "GET",
}),
queryKey: ["volumeGroups"],
});
// Use GraphQL for recent comics
const { data: recentComicsData, error: recentComicsError } = useGetRecentComicsQuery(
{ limit: 5 },
{ refetchOnWindowFocus: false }
);
const { data: statistics } = useQuery({
queryFn: async () =>
await axios({
url: `${LIBRARY_SERVICE_BASE_URI}/libraryStatistics`,
method: "GET",
}),
queryKey: ["libraryStatistics"],
});
// Wanted Comics - using GraphQL
const { data: wantedComicsData, error: wantedComicsError } = useGetWantedComicsQuery(
{
paginationOptions: {
page: 1,
limit: 5,
sort: '{"updatedAt": -1}'
},
predicate: '{"acquisition.source.wanted": true}'
},
{
refetchOnWindowFocus: false,
retry: false
}
);
// Volume Groups - using GraphQL
const { data: volumeGroupsData, error: volumeGroupsError } = useGetVolumeGroupsQuery(
undefined,
{ refetchOnWindowFocus: false }
);
// Library Statistics - using GraphQL
const { data: statisticsData, error: statisticsError } = useGetLibraryStatisticsQuery(
undefined,
{
refetchOnWindowFocus: false,
retry: false
}
);
const recentComics = recentComicsData?.comics?.comics || [];
const wantedComics = !wantedComicsError ? (wantedComicsData?.getComicBooks?.docs || []) : [];
const volumeGroups = volumeGroupsData?.getComicBookGroups || [];
const statistics = !statisticsError ? statisticsData?.getLibraryStatistics : undefined;
return (
<>
<div className="mx-auto max-w-screen-xl px-4 py-4 sm:px-6 sm:py-8 lg:px-8">
<PullList />
{recentComics && <RecentlyImported comics={recentComics?.data.docs} />}
{recentComics.length > 0 && <RecentlyImported comics={recentComics} />}
{/* Wanted comics */}
<WantedComicsList comics={wantedComics?.data?.docs} />
<WantedComicsList comics={wantedComics} />
{/* Library Statistics */}
{statistics && <LibraryStatistics stats={statistics?.data} />}
{statistics && <LibraryStatistics stats={statistics} />}
{/* Volume groups */}
<VolumeGroups volumeGroups={volumeGroups?.data} />
<VolumeGroups volumeGroups={volumeGroups} />
</div>
</>
);

View File

@@ -1,10 +1,14 @@
import React, { ReactElement, useEffect } from "react";
import prettyBytes from "pretty-bytes";
import React, { ReactElement } from "react";
import { isEmpty, isUndefined, map } from "lodash";
import Header from "../shared/Header";
import { GetLibraryStatisticsQuery } from "../../graphql/generated";
type LibraryStatisticsProps = {
stats: GetLibraryStatisticsQuery['getLibraryStatistics'];
};
export const LibraryStatistics = (
props: ILibraryStatisticsProps,
props: LibraryStatisticsProps,
): ReactElement => {
const { stats } = props;
return (
@@ -24,29 +28,30 @@ export const LibraryStatistics = (
<dd className="text-3xl text-green-600 md:text-5xl">
{props.stats.totalDocuments} files
</dd>
<dd>
<span className="text-2xl text-green-600">
{props.stats.comicDirectorySize &&
prettyBytes(props.stats.comicDirectorySize)}
</span>
</dd>
{props.stats.comicDirectorySize?.fileCount && (
<dd>
<span className="text-2xl text-green-600">
{props.stats.comicDirectorySize.fileCount} comic files
</span>
</dd>
)}
</div>
{/* comicinfo and comicvine tagged issues */}
<div className="flex flex-col gap-4">
{!isUndefined(props.stats.statistics) &&
!isEmpty(props.stats.statistics[0].issues) && (
!isEmpty(props.stats.statistics?.[0]?.issues) && (
<div className="flex flex-col h-fit rounded-lg bg-green-100 dark:bg-green-200 px-4 py-3 text-center">
<span className="text-xl">
{props.stats.statistics[0].issues.length}
{props.stats.statistics?.[0]?.issues?.length || 0}
</span>{" "}
tagged with ComicVine
</div>
)}
{!isUndefined(props.stats.statistics) &&
!isEmpty(props.stats.statistics[0].issuesWithComicInfoXML) && (
!isEmpty(props.stats.statistics?.[0]?.issuesWithComicInfoXML) && (
<div className="flex flex-col h-fit rounded-lg bg-green-100 dark:bg-green-200 px-4 py-3 text-center">
<span className="text-xl">
{props.stats.statistics[0].issuesWithComicInfoXML.length}
{props.stats.statistics?.[0]?.issuesWithComicInfoXML?.length || 0}
</span>{" "}
<span className="tag is-warning has-text-weight-bold mr-2 ml-1">
with ComicInfo.xml
@@ -57,14 +62,14 @@ export const LibraryStatistics = (
<div className="">
{!isUndefined(props.stats.statistics) &&
!isEmpty(props.stats.statistics[0].fileTypes) &&
map(props.stats.statistics[0].fileTypes, (fileType, idx) => {
!isEmpty(props.stats.statistics?.[0]?.fileTypes) &&
map(props.stats.statistics?.[0]?.fileTypes, (fileType, idx) => {
return (
<span
key={idx}
className="flex flex-col mb-4 h-fit text-xl rounded-lg bg-green-100 dark:bg-green-200 px-4 py-3 text-center"
>
{fileType.data.length} {fileType._id}
{fileType.data.length} {fileType.id}
</span>
);
})}
@@ -75,20 +80,20 @@ export const LibraryStatistics = (
{/* publisher with most issues */}
{!isUndefined(props.stats.statistics) &&
!isEmpty(
props.stats.statistics[0].publisherWithMostComicsInLibrary[0],
props.stats.statistics?.[0]?.publisherWithMostComicsInLibrary?.[0],
) && (
<>
<span className="">
{
props.stats.statistics[0]
.publisherWithMostComicsInLibrary[0]._id
props.stats.statistics?.[0]
?.publisherWithMostComicsInLibrary?.[0]?.id
}
</span>
{" has the most issues "}
<span className="">
{
props.stats.statistics[0]
.publisherWithMostComicsInLibrary[0].count
props.stats.statistics?.[0]
?.publisherWithMostComicsInLibrary?.[0]?.count
}
</span>
</>

View File

@@ -1,31 +1,22 @@
import React, { ReactElement, useState, useCallback } from "react";
import React, { ReactElement, useState } from "react";
import { map } from "lodash";
import Card from "../shared/Carda";
import Header from "../shared/Header";
import { importToDB } from "../../actions/fileops.actions";
import ellipsize from "ellipsize";
import { Link } from "react-router-dom";
import axios from "axios";
import rateLimiter from "axios-rate-limit";
import { setupCache } from "axios-cache-interceptor";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import useEmblaCarousel from "embla-carousel-react";
import { COMICVINE_SERVICE_URI, LIBRARY_SERVICE_BASE_URI } from "../../constants/endpoints";
import { Field, Form } from "react-final-form";
import { LIBRARY_SERVICE_BASE_URI } from "../../constants/endpoints";
import { Form } from "react-final-form";
import DatePickerDialog from "../shared/DatePicker";
import { format } from "date-fns";
import { LocgMetadata, useGetWeeklyPullListQuery } from "../../graphql/generated";
type PullListProps = {
issues: any;
};
interface PullListProps {
issues?: LocgMetadata[];
}
const http = rateLimiter(axios.create(), {
maxRequests: 1,
perMilliseconds: 1000,
maxRPS: 1,
});
const cachedAxios = setupCache(axios);
export const PullList = (): ReactElement => {
const queryClient = useQueryClient();
@@ -44,20 +35,22 @@ export const PullList = (): ReactElement => {
});
const {
data: pullList,
data: pullListData,
refetch,
isSuccess,
isLoading,
isError,
} = useQuery({
queryFn: async (): any =>
await cachedAxios(`${COMICVINE_SERVICE_URI}/getWeeklyPullList`, {
method: "get",
params: { startDate: inputValue, pageSize: "15", currentPage: "1" },
}),
queryKey: ["pullList", inputValue],
} = useGetWeeklyPullListQuery({
input: {
startDate: inputValue,
pageSize: 15,
currentPage: 1,
},
});
// Transform the data to match the old structure
const pullList = pullListData ? { data: pullListData.getWeeklyPullList } : undefined;
const { mutate: addToLibrary } = useMutation({
mutationFn: async ({ sourceName, metadata }: { sourceName: string; metadata: any }) => {
const comicBookMetadata = {
@@ -146,7 +139,7 @@ export const PullList = (): ReactElement => {
{isSuccess && !isLoading && (
<div className="overflow-hidden" ref={emblaRef}>
<div className="flex">
{map(pullList?.data.result, (issue, idx) => {
{map(pullList?.data.result, (issue: LocgMetadata, idx: number) => {
return (
<div
key={idx}
@@ -154,13 +147,13 @@ export const PullList = (): ReactElement => {
>
<Card
orientation={"vertical-2"}
imageUrl={issue.coverImageUrl}
imageUrl={issue.cover || undefined}
hasDetails
title={ellipsize(issue.issueName, 25)}
title={ellipsize(issue.name || 'Unknown', 25)}
>
<div className="px-1">
<span className="inline-flex mb-2 items-center bg-slate-50 text-slate-800 text-xs font-medium px-2.5 py-1 rounded-md dark:text-slate-900 dark:bg-slate-400">
{issue.publicationDate}
{issue.publisher || 'Unknown Publisher'}
</span>
<div className="flex flex-row justify-end">
<button

View File

@@ -4,23 +4,19 @@ import { Link } from "react-router-dom";
import ellipsize from "ellipsize";
import { isEmpty, isNil, isUndefined, map } from "lodash";
import { detectIssueTypes } from "../../shared/utils/tradepaperback.utils";
import {
determineCoverFile,
determineExternalMetadata,
} from "../../shared/utils/metadata.utils";
import { determineCoverFile } from "../../shared/utils/metadata.utils";
import { LIBRARY_SERVICE_HOST } from "../../constants/endpoints";
import Header from "../shared/Header";
import useEmblaCarousel from "embla-carousel-react";
import { GetRecentComicsQuery } from "../../graphql/generated";
type RecentlyImportedProps = {
comics: any;
comics: GetRecentComicsQuery['comics']['comics'];
};
export const RecentlyImported = (
comics: RecentlyImportedProps,
{ comics }: RecentlyImportedProps,
): ReactElement => {
console.log(comics);
// embla carousel
const [emblaRef, emblaApi] = useEmblaCarousel({
loop: false,
@@ -39,31 +35,30 @@ export const RecentlyImported = (
<div className="-mr-10 sm:-mr-17 lg:-mr-29 xl:-mr-36 2xl:-mr-42 mt-3">
<div className="overflow-hidden" ref={emblaRef}>
<div className="flex">
{comics?.comics.map(
(
{
_id,
{comics?.map((comic, idx) => {
const {
id,
rawFileDetails,
sourcedMetadata: { comicvine, comicInfo, locg },
sourcedMetadata,
canonicalMetadata,
inferredMetadata,
wanted: { source } = {},
},
idx,
) => {
} = comic;
// Parse sourced metadata (GraphQL returns as strings)
const comicvine = typeof sourcedMetadata?.comicvine === 'string'
? JSON.parse(sourcedMetadata.comicvine)
: sourcedMetadata?.comicvine;
const comicInfo = typeof sourcedMetadata?.comicInfo === 'string'
? JSON.parse(sourcedMetadata.comicInfo)
: sourcedMetadata?.comicInfo;
const locg = sourcedMetadata?.locg;
const { issueName, url } = determineCoverFile({
rawFileDetails,
comicvine,
comicInfo,
locg,
});
const { issue, coverURL, icon } = determineExternalMetadata(
source,
{
comicvine,
comicInfo,
locg,
},
);
const isComicVineMetadataAvailable =
!isUndefined(comicvine) &&
!isUndefined(comicvine.volumeInformation);
@@ -77,7 +72,7 @@ export const RecentlyImported = (
<Card
orientation="vertical-2"
imageUrl={url}
title={inferredMetadata.issue.name}
title={inferredMetadata?.issue?.name}
hasDetails
cardState={cardState}
>
@@ -89,7 +84,7 @@ export const RecentlyImported = (
<i className="icon-[solar--hashtag-outline]"></i>
</span>
<span className="text-md text-slate-900">
{inferredMetadata.issue.number}
{inferredMetadata?.issue?.number}
</span>
</span>
{/* File extension */}
@@ -99,7 +94,7 @@ export const RecentlyImported = (
</span>
<span className="text-md text-slate-500 dark:text-slate-900">
{rawFileDetails.extension}
{rawFileDetails?.extension}
</span>
</span>
{/* Uncompressed status */}
@@ -142,8 +137,7 @@ export const RecentlyImported = (
</Card>
</div>
);
},
)}
})}
</div>
</div>
</div>

View File

@@ -5,12 +5,17 @@ import { Link, useNavigate } from "react-router-dom";
import Card from "../shared/Carda";
import Header from "../shared/Header";
import useEmblaCarousel from "embla-carousel-react";
import { GetVolumeGroupsQuery } from "../../graphql/generated";
export const VolumeGroups = (props): ReactElement => {
type VolumeGroupsProps = {
volumeGroups?: GetVolumeGroupsQuery['getComicBookGroups'];
};
export const VolumeGroups = (props: VolumeGroupsProps): ReactElement => {
// Till mongo gives us back the deduplicated results with the ObjectId
const deduplicatedGroups = unionBy(props.volumeGroups, "volumes.id");
const navigate = useNavigate();
const navigateToVolumes = (row) => {
const navigateToVolumes = (row: any) => {
navigate(`/volumes/all`);
};
@@ -36,18 +41,18 @@ export const VolumeGroups = (props): ReactElement => {
{map(deduplicatedGroups, (data) => {
return (
<div
key={data._id}
key={data.id}
className="flex-[0_0_200px] min-w-0 sm:flex-[0_0_220px] md:flex-[0_0_240px] lg:flex-[0_0_260px] xl:flex-[0_0_280px] pr-[15px]"
>
<Card
orientation="vertical-2"
imageUrl={data.volumes.image.small_url}
imageUrl={data.volumes?.image?.small_url || undefined}
hasDetails
>
<div className="py-3">
<div className="text-sm">
<Link to={`/volume/details/${data._id}`}>
{ellipsize(data.volumes.name, 48)}
<Link to={`/volume/details/${data.id}`}>
{ellipsize(data.volumes?.name || 'Unknown', 48)}
</Link>
</div>
{/* issue count */}
@@ -57,7 +62,7 @@ export const VolumeGroups = (props): ReactElement => {
</span>
<span className="text-md text-slate-500 dark:text-slate-900">
{data.volumes.count_of_issues} issues
{data.volumes?.count_of_issues || 0} issues
</span>
</span>
</div>

View File

@@ -7,9 +7,10 @@ import { detectIssueTypes } from "../../shared/utils/tradepaperback.utils";
import { determineCoverFile } from "../../shared/utils/metadata.utils";
import Header from "../shared/Header";
import useEmblaCarousel from "embla-carousel-react";
import { GetWantedComicsQuery } from "../../graphql/generated";
type WantedComicsListProps = {
comics: any;
comics?: GetWantedComicsQuery['getComicBooks']['docs'];
};
export const WantedComicsList = ({
@@ -38,12 +39,22 @@ export const WantedComicsList = ({
<div className="flex">
{map(
comics,
({
_id,
rawFileDetails,
sourcedMetadata: { comicvine, comicInfo, locg },
wanted,
}) => {
(comic) => {
const {
id,
rawFileDetails,
sourcedMetadata,
} = comic;
// Parse sourced metadata (GraphQL returns as strings)
const comicvine = typeof sourcedMetadata?.comicvine === 'string'
? JSON.parse(sourcedMetadata.comicvine)
: sourcedMetadata?.comicvine;
const comicInfo = typeof sourcedMetadata?.comicInfo === 'string'
? JSON.parse(sourcedMetadata.comicInfo)
: sourcedMetadata?.comicInfo;
const locg = sourcedMetadata?.locg;
const isComicBookMetadataAvailable = !isUndefined(comicvine);
const consolidatedComicMetadata = {
rawFileDetails,
@@ -58,14 +69,14 @@ export const WantedComicsList = ({
publisher = null,
} = determineCoverFile(consolidatedComicMetadata);
const titleElement = (
<Link to={"/comic/details/" + _id}>
<Link to={"/comic/details/" + id}>
{ellipsize(issueName, 20)}
<p>{publisher}</p>
</Link>
);
return (
<div
key={_id}
key={id}
className="flex-[0_0_200px] min-w-0 sm:flex-[0_0_220px] md:flex-[0_0_240px] lg:flex-[0_0_260px] xl:flex-[0_0_280px] pr-[15px]"
>
<Card
@@ -79,7 +90,7 @@ export const WantedComicsList = ({
<div className="flex flex-row gap-2">
{/* Issue type */}
{isComicBookMetadataAvailable &&
!isNil(detectIssueTypes(comicvine.description)) ? (
!isNil(detectIssueTypes(comicvine?.description)) ? (
<div className="my-2">
<span className="inline-flex items-center bg-slate-50 text-slate-800 text-xs font-medium px-2.5 py-0.5 rounded-md dark:text-slate-900 dark:bg-slate-400">
<span className="pr-1 pt-1">
@@ -88,29 +99,14 @@ export const WantedComicsList = ({
<span className="text-md text-slate-500 dark:text-slate-900">
{
detectIssueTypes(comicvine.description)
.displayName
detectIssueTypes(comicvine?.description)
?.displayName
}
</span>
</span>
</div>
) : null}
{/* issues marked as wanted, part of this volume */}
{wanted?.markEntireVolumeWanted ? (
<div className="text-sm">sagla volume pahije</div>
) : (
<div className="my-2">
<span className="inline-flex items-center bg-slate-50 text-slate-800 text-xs font-medium px-2.5 py-0.5 rounded-md dark:text-slate-900 dark:bg-slate-400">
<span className="pr-1 pt-1">
<i className="icon-[solar--documents-bold-duotone] w-5 h-5"></i>
</span>
<span className="text-md text-slate-500 dark:text-slate-900">
{wanted.issues.length}
</span>
</span>
</div>
)}
{/* Wanted comics - info not available in current GraphQL query */}
</div>
{/* comicVine metadata presence */}
{isComicBookMetadataAvailable && (

View File

@@ -63,7 +63,6 @@ export const Downloads = (props: IDownloadsProps): ReactElement => {
<div className="columns">
<div className="column is-half">
{bundles.map((bundle, idx) => {
console.log(bundle);
return (
<div key={idx}>
<MetadataPanel

View File

@@ -78,7 +78,6 @@ export const Search = ({}: ISearchProps): ReactElement => {
coverDate: cover_date,
issueNumber: issue_number,
});
console.log(issues);
// Get volume metadata from CV
const response = await axios({
url: `${COMICVINE_SERVICE_URI}/getVolumes`,
@@ -111,7 +110,6 @@ export const Search = ({}: ISearchProps): ReactElement => {
break;
default:
console.log("Invalid resource type.");
break;
}
// Add to wanted list

View File

@@ -35,7 +35,6 @@ export const AirDCPPSettingsForm = () => {
// Handle setting update and subsequent AirDC++ initialization
const { mutate } = useMutation({
mutationFn: (values) => {
console.log(values);
return axios.post("http://localhost:3000/api/settings/saveSettings", {
settingsPayload: values,
settingsKey: "directConnect",

View File

@@ -19,7 +19,6 @@ export const DockerVars = (): React.ReactElement => {
refetchOnWindowFocus: false,
refetchOnReconnect: false,
});
console.log("Docker Vars: ", environmentVariables);
return (
<div className="flex flex-col gap-4">
<h2 className="text-xl font-semibold">Docker Environment Variables</h2>

View File

@@ -19,7 +19,6 @@ export const ProwlarrSettingsForm = (props) => {
},
queryKey: ["prowlarrConnectionResult"],
});
console.log(data);
const submitHandler = () => {};
const initialData = {};
return (

View File

@@ -33,7 +33,6 @@ export const Volumes = (props): ReactElement => {
}),
queryKey: ["volumes"],
});
console.log(volumes);
const columnData = useMemo(
(): any => [
{
@@ -46,7 +45,6 @@ export const Volumes = (props): ReactElement => {
const {
_source: { sourcedMetadata },
} = comicObject;
console.log("jaggu", row.getValue());
return (
<div className="flex flex-row gap-3 mt-5">
<Link to={`/volume/details/${comicObject._id}`}>

View File

@@ -42,7 +42,6 @@ export const WantedComics = (props): ReactElement => {
minWidth: 350,
accessorFn: (data) => data,
cell: (value) => {
console.log("ASDASd", value);
const row = value.getValue()._source;
return row && <MetadataPanel data={row} />;
},

View File

@@ -3,7 +3,7 @@ import { Link } from "react-router-dom";
type IHeaderProps = {
headerContent: string;
subHeaderContent: ReactElement;
subHeaderContent: ReactElement | string;
iconClassNames: string;
link?: string;
};

View File

@@ -0,0 +1,76 @@
import { GetComicByIdQuery } from '../generated';
/**
* Adapter to transform GraphQL Comic response to legacy REST API format
* This allows gradual migration while maintaining compatibility with existing components
*/
export function adaptGraphQLComicToLegacy(graphqlComic: GetComicByIdQuery['comic']) {
if (!graphqlComic) return null;
// Parse sourced metadata (GraphQL returns as strings)
const comicvine = graphqlComic.sourcedMetadata?.comicvine
? (typeof graphqlComic.sourcedMetadata.comicvine === 'string'
? JSON.parse(graphqlComic.sourcedMetadata.comicvine)
: graphqlComic.sourcedMetadata.comicvine)
: undefined;
const comicInfo = graphqlComic.sourcedMetadata?.comicInfo
? (typeof graphqlComic.sourcedMetadata.comicInfo === 'string'
? JSON.parse(graphqlComic.sourcedMetadata.comicInfo)
: graphqlComic.sourcedMetadata.comicInfo)
: undefined;
const locg = graphqlComic.sourcedMetadata?.locg || undefined;
// Use inferredMetadata from GraphQL response, or build from canonical metadata as fallback
const inferredMetadata = graphqlComic.inferredMetadata || {
issue: {
name: graphqlComic.canonicalMetadata?.title?.value ||
graphqlComic.canonicalMetadata?.series?.value ||
graphqlComic.rawFileDetails?.name || '',
number: graphqlComic.canonicalMetadata?.issueNumber?.value
? parseInt(graphqlComic.canonicalMetadata.issueNumber.value, 10)
: undefined,
year: graphqlComic.canonicalMetadata?.publicationDate?.value?.substring(0, 4) ||
graphqlComic.canonicalMetadata?.coverDate?.value?.substring(0, 4),
subtitle: graphqlComic.canonicalMetadata?.series?.value,
},
};
// Build acquisition data (if available from importStatus or other fields)
const acquisition = {
directconnect: {
downloads: [],
},
torrent: [],
};
// Transform rawFileDetails to match expected format
const rawFileDetails = graphqlComic.rawFileDetails ? {
name: graphqlComic.rawFileDetails.name || '',
filePath: graphqlComic.rawFileDetails.filePath,
fileSize: graphqlComic.rawFileDetails.fileSize,
extension: graphqlComic.rawFileDetails.extension,
mimeType: graphqlComic.rawFileDetails.mimeType,
containedIn: graphqlComic.rawFileDetails.containedIn,
pageCount: graphqlComic.rawFileDetails.pageCount,
archive: graphqlComic.rawFileDetails.archive,
cover: graphqlComic.rawFileDetails.cover,
} : undefined;
return {
_id: graphqlComic.id,
rawFileDetails,
inferredMetadata,
sourcedMetadata: {
comicvine,
locg,
comicInfo,
},
acquisition,
createdAt: graphqlComic.createdAt || new Date().toISOString(),
updatedAt: graphqlComic.updatedAt || new Date().toISOString(),
// Include the full GraphQL data for components that can use it
__graphql: graphqlComic,
} as any; // Use 'as any' to bypass strict type checking during migration
}

View File

@@ -0,0 +1,19 @@
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';
import { LIBRARY_SERVICE_HOST } from '../constants/endpoints';
const httpLink = new HttpLink({
uri: `${LIBRARY_SERVICE_HOST}/graphql`,
});
export const apolloClient = new ApolloClient({
link: httpLink,
cache: new InMemoryCache(),
defaultOptions: {
watchQuery: {
fetchPolicy: 'cache-and-network',
},
query: {
fetchPolicy: 'network-only',
},
},
});

View File

@@ -0,0 +1,43 @@
import { LIBRARY_SERVICE_HOST } from '../constants/endpoints';
export function fetcher<TData, TVariables>(
query: string,
variables?: TVariables,
options?: RequestInit['headers']
) {
return async (): Promise<TData> => {
try {
const res = await fetch(`${LIBRARY_SERVICE_HOST}/graphql`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...options,
},
body: JSON.stringify({
query,
variables,
}),
});
// Check if the response is OK (status 200-299)
if (!res.ok) {
throw new Error(`HTTP error! status: ${res.status}`);
}
const json = await res.json();
if (json.errors) {
const { message } = json.errors[0] || {};
throw new Error(message || 'Error fetching data');
}
return json.data;
} catch (error) {
// Handle network errors or other fetch failures
if (error instanceof Error) {
throw new Error(`Failed to fetch: ${error.message}`);
}
throw new Error('Failed to fetch data from server');
}
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,191 @@
query GetComicById($id: ID!) {
comic(id: $id) {
id
# Inferred metadata
inferredMetadata {
issue {
name
number
year
subtitle
}
}
# Canonical metadata
canonicalMetadata {
title {
value
provenance {
source
sourceId
confidence
fetchedAt
url
}
userOverride
}
series {
value
provenance {
source
sourceId
confidence
fetchedAt
url
}
userOverride
}
volume {
value
provenance {
source
sourceId
confidence
fetchedAt
url
}
userOverride
}
issueNumber {
value
provenance {
source
sourceId
confidence
fetchedAt
url
}
userOverride
}
publisher {
value
provenance {
source
sourceId
confidence
fetchedAt
url
}
userOverride
}
publicationDate {
value
provenance {
source
sourceId
confidence
fetchedAt
url
}
userOverride
}
coverDate {
value
provenance {
source
sourceId
confidence
fetchedAt
url
}
userOverride
}
description {
value
provenance {
source
sourceId
confidence
fetchedAt
url
}
userOverride
}
creators {
name
role
provenance {
source
sourceId
confidence
fetchedAt
url
}
}
pageCount {
value
provenance {
source
sourceId
confidence
fetchedAt
url
}
userOverride
}
coverImage {
value
provenance {
source
sourceId
confidence
fetchedAt
url
}
userOverride
}
}
# Sourced metadata
sourcedMetadata {
comicInfo
comicvine
metron
gcd
locg {
name
publisher
url
cover
description
price
rating
pulls
potw
}
}
# Raw file details
rawFileDetails {
name
filePath
fileSize
extension
mimeType
containedIn
pageCount
archive {
uncompressed
expandedPath
}
cover {
filePath
stats
}
}
# Import status
importStatus {
isImported
tagged
matchedResult {
score
}
}
# Timestamps
createdAt
updatedAt
}
}

View File

@@ -0,0 +1,216 @@
query GetComics($page: Int, $limit: Int, $search: String, $publisher: String, $series: String) {
comics(page: $page, limit: $limit, search: $search, publisher: $publisher, series: $series) {
comics {
id
inferredMetadata {
issue {
name
number
year
subtitle
}
}
rawFileDetails {
name
extension
archive {
uncompressed
}
}
sourcedMetadata {
comicvine
comicInfo
locg {
name
publisher
cover
}
}
canonicalMetadata {
title {
value
}
series {
value
}
issueNumber {
value
}
}
}
totalCount
pageInfo {
hasNextPage
hasPreviousPage
currentPage
totalPages
}
}
}
query GetRecentComics($limit: Int) {
comics(page: 1, limit: $limit) {
comics {
id
inferredMetadata {
issue {
name
number
year
subtitle
}
}
rawFileDetails {
name
extension
cover {
filePath
}
archive {
uncompressed
}
}
sourcedMetadata {
comicvine
comicInfo
locg {
name
publisher
cover
}
}
canonicalMetadata {
title {
value
}
series {
value
}
issueNumber {
value
}
publisher {
value
}
}
createdAt
updatedAt
}
totalCount
}
}
query GetWantedComics($paginationOptions: PaginationOptionsInput!, $predicate: PredicateInput) {
getComicBooks(paginationOptions: $paginationOptions, predicate: $predicate) {
docs {
id
inferredMetadata {
issue {
name
number
year
subtitle
}
}
rawFileDetails {
name
extension
cover {
filePath
}
archive {
uncompressed
}
}
sourcedMetadata {
comicvine
comicInfo
locg {
name
publisher
cover
}
}
canonicalMetadata {
title {
value
}
series {
value
}
issueNumber {
value
}
}
createdAt
updatedAt
}
totalDocs
limit
page
totalPages
hasNextPage
hasPrevPage
}
}
query GetVolumeGroups {
getComicBookGroups {
id
volumes {
id
name
count_of_issues
publisher {
id
name
}
start_year
image {
small_url
}
}
}
}
query GetLibraryStatistics {
getLibraryStatistics {
totalDocuments
comicDirectorySize {
fileCount
}
statistics {
fileTypes {
id
data
}
issues {
id {
id
name
}
data
}
fileLessComics {
id
}
issuesWithComicInfoXML {
id
}
publisherWithMostComicsInLibrary {
id
count
}
}
}
}
query GetWeeklyPullList($input: WeeklyPullListInput!) {
getWeeklyPullList(input: $input) {
result {
name
publisher
cover
}
}
}

View File

@@ -0,0 +1,72 @@
# Library queries
# Note: The Library component currently uses Elasticsearch for search functionality
# These queries are prepared for when the backend supports GraphQL-based library queries
query GetLibraryComics($page: Int, $limit: Int, $search: String, $series: String) {
comics(page: $page, limit: $limit, search: $search, series: $series) {
comics {
id
inferredMetadata {
issue {
name
number
year
subtitle
}
}
rawFileDetails {
name
filePath
fileSize
extension
mimeType
pageCount
archive {
uncompressed
}
cover {
filePath
}
}
sourcedMetadata {
comicvine
comicInfo
locg {
name
publisher
cover
}
}
canonicalMetadata {
title {
value
}
series {
value
}
issueNumber {
value
}
publisher {
value
}
pageCount {
value
}
}
importStatus {
isImported
tagged
}
createdAt
updatedAt
}
totalCount
pageInfo {
hasNextPage
hasPreviousPage
currentPage
totalPages
}
}
}

View File

@@ -18,7 +18,14 @@ import Volumes from "./components/Volumes/Volumes";
import VolumeDetails from "./components/VolumeDetail/VolumeDetail";
import WantedComics from "./components/WantedComics/WantedComics";
const queryClient = new QueryClient();
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: false,
refetchOnWindowFocus: false,
},
},
});
const router = createBrowserRouter([
{

View File

@@ -47,7 +47,6 @@ const onSearchSent = async (item, instance, unsubscribe, searchInfo) => {
if (results.length > 0) {
// We have results, download the best one
console.log("SASAAAA", results);
// const result = results[0];
// SocketService.post(`search/${instance.id}/results/${result.id}/download`, {
// priority: Utils.toApiPriority(item.priority),

View File

@@ -42,6 +42,6 @@ const getIssueTypeDisplayName = (
return { displayName, results };
}
} catch (error) {
console.log(error);
// Error handling could be added here if needed
}
};

View File

@@ -47,12 +47,11 @@ export const useStore = create<StoreState>((set, get) => ({
});
socket.on("connect", () => {
console.log(`✅ Connected to ${namespace}:`, socket.id);
// Socket connected successfully
});
// Always listen for sessionInitialized in case backend creates a new session
socket.on("sessionInitialized", (id) => {
console.log("Session initialized with ID:", id);
localStorage.setItem("sessionId", id);
});

View File

@@ -6,3 +6,6 @@ declare module "*.png" {
declare module "*.jpg";
declare module "*.gif";
declare module "*.less";
// Comic types are now generated from GraphQL schema
// Import from '../../graphql/generated' instead

View File

@@ -21,21 +21,7 @@ app.use(function (req, res, next) {
// Getting data from body of requests
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
//
// app.get("/", (req: Request, res: Response) => {
// console.log("sending index.html");
// res.sendFile(path.resolve(__dirname, "../dist/index.html"));
// });
// REGISTER ROUTES
// all of the routes will be prefixed with /api
const routes: Router[] = Object.values(router);
// app.use("/api", routes);
// Send index.html on root request
// app.use(express.static("dist"));
// app.use(express.static("public"));
// app.listen(port);
// console.log(`Server is listening on ${port}`);

3217
yarn.lock

File diff suppressed because it is too large Load Diff