Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix embeds #285

Merged
merged 3 commits into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions web/__tests__/components/GroupedChapterList.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ import { render, screen } from '@testing-library/react';
import { vi } from 'vitest';

import {
GroupedChapterList,
ChapterGroupWithCover,
ChapterWithLink,
GroupedChapterList,
} from '../../src/components/GroupedChapterList';
import { testChapterUrlFormat } from '../constants';
import {
formatChapterTitle,
formatChapterUrl,
} from '../../src/utils/formatting';
import { setupFaker, generateNSchemas, LatestChapter } from '../schemas';
import { generateNSchemas, LatestChapter, setupFaker } from '../schemas';


describe('ChapterGroupWithCover', () => {
Expand Down Expand Up @@ -71,7 +71,7 @@ describe('ChapterWithLink', () => {
expect(screen.getByRole('listitem')).toBeInTheDocument();

// Chapter title should be properly formatted
expect(screen.getByText(new RegExp(formatChapterTitle(chapter) + '.+?'))).toBeInTheDocument();
expect(screen.getByText(formatChapterTitle(chapter))).toBeInTheDocument();

// Link button should exist
const linkBtn = screen.getByRole('button', { name: /Open chapter in new tab/i });
Expand Down
17 changes: 16 additions & 1 deletion web/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,27 @@ import { resolve as resolveTs } from 'ts-node/esm';
// eslint-disable-next-line import/no-extraneous-dependencies
import * as tsConfigPaths from 'tsconfig-paths';
import { pathToFileURL } from 'url';
import fs from 'fs';

const { absoluteBaseUrl, paths } = tsConfigPaths.loadConfig();
const matchPath = tsConfigPaths.createMatchPath(absoluteBaseUrl, paths);

export function resolve(specifier, ctx, defaultResolve) {
const match = matchPath(specifier);
let match = matchPath(specifier);
// Only resolve extensions for path shortcuts
if (specifier.startsWith('@') && match && match.indexOf('.') === -1) {
// If match is a directory point to the index file
if (fs.existsSync(match) && fs.lstatSync(match).isDirectory()) {
match = `${match}/index`;
}

// First try .ts extension and then .js
const newFile = `${match}.ts`;
match = fs.existsSync(newFile) ?
newFile :
`${match}.js`;
}

return match ?
resolveTs(pathToFileURL(`${match}`).href, ctx, defaultResolve) :
resolveTs(specifier, ctx, defaultResolve);
Expand Down
61 changes: 36 additions & 25 deletions web/server/db/chapter.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import type { ChapterRelease, ChapterReleaseDates, MangaChapter, } from '@/types/api/chapter';
import camelcaseKeys from 'camelcase-keys';
import type { PendingQuery } from 'postgres';
import type {
ChapterRelease,
ChapterReleaseDates,
MangaChapter,
} from '@/types/api/chapter';
import type { Chapter } from '@/types/db/chapter';
import type { DatabaseId, MangaId } from '@/types/dbTypes';
import type { DefaultExcept, PartialExcept } from '@/types/utility';
import camelcaseKeys from 'camelcase-keys';
import type { PendingQuery } from 'postgres';
import { NO_GROUP } from '../utils/constants.js';
import { db } from './helpers';
import { generateUpdate } from './utils';
Expand All @@ -16,28 +20,35 @@ export const getChapterReleases = (mangaId: MangaId) => {

export const getLatestChapters = (limit: number, offset: number, userId?: DatabaseId) => {
return db.manyOrNone<ChapterRelease>`
${userId ? db.sql`WITH follows AS (SELECT DISTINCT manga_id, service_id FROM user_follows WHERE user_id=${userId})` : db.sql``}
SELECT
chapter_id,
chapters.title,
chapter_number,
chapter_decimal,
release_date,
g.name as "group",
chapters.service_id,
chapter_identifier,
m.title as manga,
m.manga_id,
ms.title_id,
mi.cover
FROM chapters
INNER JOIN groups g ON g.group_id = chapters.group_id
INNER JOIN manga m ON chapters.manga_id = m.manga_id
INNER JOIN manga_service ms ON chapters.manga_id = ms.manga_id AND chapters.service_id=ms.service_id
LEFT JOIN manga_info mi ON m.manga_id = mi.manga_id
${userId ? db.sql`INNER JOIN follows f ON f.manga_id=m.manga_id AND (f.service_id IS NULL OR f.service_id=ms.service_id)` : db.sql``}
ORDER BY release_date DESC
LIMIT ${limit} ${offset ? db.sql`OFFSET ${offset}` : db.sql``}`;
${userId ? db.sql`WITH follow_all AS (
SELECT manga_id FROM user_follows WHERE user_id=${userId} GROUP BY manga_id HAVING COUNT(*) != COUNT(service_id)
),
follows AS (
SELECT DISTINCT manga_id, service_id FROM user_follows WHERE user_id=${userId} AND manga_id NOT IN (SELECT manga_id FROM follow_all)
UNION ALL
SELECT manga_id, NULL as service_id FROM follow_all
)` : db.sql``}
SELECT
chapter_id,
chapters.title,
chapter_number,
chapter_decimal,
release_date,
g.name as "group",
chapters.service_id,
chapter_identifier,
m.title as manga,
m.manga_id,
ms.title_id,
mi.cover
FROM chapters
INNER JOIN groups g ON g.group_id = chapters.group_id
INNER JOIN manga m ON chapters.manga_id = m.manga_id
INNER JOIN manga_service ms ON chapters.manga_id = ms.manga_id AND chapters.service_id=ms.service_id
LEFT JOIN manga_info mi ON m.manga_id = mi.manga_id
${userId ? db.sql`INNER JOIN follows f ON f.manga_id=m.manga_id AND (f.service_id IS NULL OR f.service_id=ms.service_id)` : db.sql``}
ORDER BY release_date DESC
LIMIT ${limit} ${offset ? db.sql`OFFSET ${offset}` : db.sql``}`;
};

export type AddChapter = DefaultExcept<Omit<Chapter, 'chapterId'>,
Expand Down
35 changes: 22 additions & 13 deletions web/server/db/manga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import {
INVALID_TEXT_REPRESENTATION,
NUMERIC_VALUE_OUT_OF_RANGE,
} from 'pg-error-constants';

import { db } from './helpers';
import { fetchExtraInfo, MANGADEX_ID } from './mangadex.js';
import type { MangaInfoData } from '@/types/api/manga';
import type { Follow } from '@/types/db/follows';
import type { Manga } from '@/types/db/manga';
import type { DatabaseId, MangaId } from '@/types/dbTypes';
import { HttpError } from '../utils/errors.js';
import { mangadexLogger } from '../utils/logging.js';
import type { DatabaseId, MangaId } from '@/types/dbTypes';
import type { Manga } from '@/types/db/manga';
import type { Follow } from '@/types/db/follows';
import { db } from './helpers';
import { fetchExtraInfo, MANGADEX_ID } from './mangadex.js';

const links = {
al: 'https://anilist.co/manga/',
Expand All @@ -33,23 +33,32 @@ export function formatLinks(row: Record<string, string>) {
});
}

export type MangaData = {
mangaId: number,
title: string,
releaseInterval?: Date | null,
latestRelease?: Date | null,
estimatedRelease?: Date | null,
latestChapter?: number | null,
lastUpdated? : Date | null,
} & Omit<MangaInfoData, 'lastUpdated'>

export type FullManga = {
services?: object[],
chapters?: object[],
aliases?: string[]
manga: object
manga: MangaData
}

type FullMangaUnformatted = {
services: any[],
chapters?: any[],
aliases: string[],
[key: string]: any
}
} & MangaData

function formatFullManga(obj: Partial<FullMangaUnformatted>): FullManga {
const out: FullManga = {
manga: {},
manga: {} as any,
};

if (obj.services) {
Expand All @@ -66,7 +75,7 @@ function formatFullManga(obj: Partial<FullMangaUnformatted>): FullManga {
out.aliases = obj.aliases;
delete obj.aliases;
}
out.manga = obj;
out.manga = obj as MangaData;

return out;
}
Expand All @@ -91,12 +100,12 @@ export function getFullManga(mangaId: MangaId): Promise<FullManga | null> {

const mdIdx = row.services.findIndex(v => v.serviceId === MANGADEX_ID);
// If info doesn't exist or 2 weeks since last update
if ((!row.lastUpdated || (Date.now() - row.lastUpdated)/8.64E7 > 14) && mdIdx >= 0) {
if ((!row.lastUpdated || (Date.now() - (row.lastUpdated as any))/8.64E7 > 14) && mdIdx >= 0) {
fetchExtraInfo(row.services[mdIdx].titleId, mangaId)
.catch(mangadexLogger.error);
}

formatLinks(row);
formatLinks(row as any);
return formatFullManga(row);
});
}
Expand Down
2 changes: 1 addition & 1 deletion web/src/api/services.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ServiceForApi } from '@/types/api/services';
import { handleError, handleResponse } from './utilities';
import { ServiceForApi } from '../../types/api/services';

/**
* Fetches all services
Expand Down
39 changes: 31 additions & 8 deletions web/src/components/GroupedChapterList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
IconButton,
Paper,
Skeleton,
Tooltip,
Typography,
} from '@mui/material';

Expand All @@ -19,6 +20,8 @@ import { formatChapterTitle, formatChapterUrl } from '../utils/formatting';
import { MangaCover } from './MangaCover';
import type { ServiceForApi } from '@/types/api/services';
import type { ChapterRelease } from '@/types/api/chapter';
import { defaultDateFormat } from '@/webUtils/utilities';
import type { DatabaseId } from '@/types/dbTypes';

export type ChapterComponentProps = {
chapter: ChapterRelease
Expand All @@ -27,9 +30,10 @@ export type ChapterComponentProps = {
export type GroupComponentProps = PropsWithChildren<{
groupString: string | React.ReactNode
group: string
mangaId: DatabaseId
}>

export const ChapterGroupBase: FC<Omit<GroupComponentProps, 'group'>> = ({ groupString, children }) => (
export const ChapterGroupBase: FC<Omit<GroupComponentProps, 'group' | 'mangaId'>> = ({ groupString, children }) => (
<Paper sx={{
mb: 1,
pt: 2,
Expand All @@ -42,15 +46,19 @@ export const ChapterGroupBase: FC<Omit<GroupComponentProps, 'group'>> = ({ group
</Paper>
);

export const ChapterGroupWithCover = (mangaToCover: Record<string, string>): FC<GroupComponentProps> => ({ group, groupString, children }) => (
export const ChapterGroupWithCover = (mangaToCover: Record<string, string>): FC<GroupComponentProps> => (
{ mangaId, group, groupString, children }
) => (
<ChapterGroupBase groupString={groupString}>
<div style={{ display: 'flex' }}>
<div>
<MangaCover
url={mangaToCover[group]}
alt={groupString}
maxWidth={96}
/>
<a href={`/manga/${mangaId}`} target='_blank' rel='noopener noreferrer'>
<MangaCover
url={mangaToCover[group]}
alt={groupString}
maxWidth={96}
/>
</a>
</div>
<div>
{children}
Expand All @@ -64,7 +72,21 @@ export const ChapterWithLink = (services: Record<number, ServiceForApi>): FC<Cha
return (
<li>
<div>
{formatChapterTitle(chapter)}
<Tooltip
title={defaultDateFormat(new Date(chapter.releaseDate))}
arrow
slotProps={{
tooltip: {
sx: {
fontSize: '1rem',
},
},
}}
>
<span>
{formatChapterTitle(chapter)}
</span>
</Tooltip>
<a
href={formatChapterUrl(service.chapterUrlFormat, chapter.chapterIdentifier, chapter.titleId)}
target='_blank'
Expand Down Expand Up @@ -134,6 +156,7 @@ export const GroupedChapterList: FC<GroupedChapterListProps> = ({
<GroupComponent
groupString={groupToString(group.group, group.arr)}
group={group.group}
mangaId={group.arr[0].mangaId}
/* eslint-disable-next-line react/no-array-index-key */
key={`${idx}`}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,41 @@
import { NextSeo } from 'next-seo';
import React from 'react';

import { getUserFollows } from '@/db/db';
import { getFullManga } from '@/db/manga';
import type { FullMangaData } from '@/types/api/manga.js';
import type { GetServerSidePropsExpress } from '@/types/nextjs';
import { isInteger } from '@/webUtils/utilities';
import Manga from '../../components/Manga';
import withError from '../../utils/withError';
import { isInteger } from '../../utils/utilities';

import { getFullManga } from '../../../server/db/manga';
import { getUserFollows } from '../../../server/db/db';

function MangaPage(props) {
function MangaPage(props: { manga: FullMangaData, follows: number[] }) {
const {
manga,
manga: fullManga,
follows,
} = props;

const manga = fullManga.manga;

return (
<>
<NextSeo
title={manga.title}
openGraph={{
title: manga.title,
images: [{
images: manga.cover ? [{
url: manga.cover,
alt: `${manga.title} cover art`,
}],
}] : undefined,
}}
/>
<Manga mangaData={{ ...manga }} userFollows={follows} />
<Manga mangaData={{ ...fullManga }} userFollows={follows as any} />
</>
);
}

export async function getServerSideProps({ req, params }) {
if (!isInteger(params.mangaId)) {
export const getServerSideProps: GetServerSidePropsExpress = async ({ req, params }) => {
if (!params || !isInteger(params.mangaId)) {
return { props: { error: 404 }};
}

Expand All @@ -49,7 +52,7 @@ export async function getServerSideProps({ req, params }) {
.map(service => service.serviceId);
}
} catch (e) {
return { props: { error: e?.status || 404 }};
return { props: { error: (e as any)?.status || 404 }};
}

if (!manga) {
Expand All @@ -64,5 +67,5 @@ export async function getServerSideProps({ req, params }) {
follows: userFollows || [],
},
};
}
};
export default withError(MangaPage);
6 changes: 3 additions & 3 deletions web/src/utils/utilities.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { format, formatDistanceToNowStrict } from 'date-fns';
import enLocale from 'date-fns/locale/en-GB';
import throttle from 'lodash.throttle';
import type { MouseEvent, MouseEventHandler } from 'react';
import { csrfHeader } from './csrf';
import type { DatabaseId, MangaId } from '@/types/dbTypes';
import type { FormValues } from '@/components/notifications/types';
import type { NotificationField } from '@/types/api/notifications';
import type { FormValues } from '@/components/notifications/types';
import { csrfHeader } from './csrf';

export const followUnfollow = (csrf: string, mangaId: MangaId, serviceId: DatabaseId | null): MouseEventHandler => {
const url = serviceId ? `/api/user/follows?mangaId=${mangaId}&serviceId=${serviceId}` :
Expand Down Expand Up @@ -190,7 +190,7 @@ export const statusToString = (status: number | string) => {
}
};

export const isInteger = (s: any) => (
export const isInteger = (s: any): s is number | string => (
Number.isInteger(s) ||
/^-?\d+$/.test(s)
);
Expand Down
Loading