Compare commits
8 Commits
feature/FA
...
feature/FA
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f830773af5 | ||
| 7185b95c65 | |||
|
|
8b44cf2f0c | ||
| ac48889f4c | |||
|
|
5c52d29da9 | ||
| 16004ad938 | |||
|
|
d109db2155 | ||
| 89dff0980b |
@@ -24,6 +24,8 @@ RUN dotnet build "./FictionArchive.API.csproj" -c $BUILD_CONFIGURATION -o /app/b
|
||||
FROM build AS publish
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
RUN dotnet publish "./FictionArchive.API.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false /p:SkipFusionBuild=true
|
||||
# Copy pre-composed gateway.fgp from CI build
|
||||
COPY FictionArchive.API/gateway.fgp /app/publish/
|
||||
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
|
||||
@@ -25,8 +25,6 @@ public class Program
|
||||
.ConfigureFromFile("gateway.fgp")
|
||||
.CoreBuilder.ApplySaneDefaults();
|
||||
|
||||
builder.Services.AddOidcAuthentication(builder.Configuration);
|
||||
|
||||
#endregion
|
||||
|
||||
var allowedOrigin = builder.Configuration["Cors:AllowedOrigin"] ?? "http://localhost:4321";
|
||||
|
||||
@@ -137,11 +137,9 @@ public class NovelpiaAdapter : ISourceAdapter
|
||||
uint page = 0;
|
||||
List<ChapterMetadata> chapters = new List<ChapterMetadata>();
|
||||
List<uint> seenChapterIds = new List<uint>();
|
||||
uint chapterOrder = 0;
|
||||
uint chapterOrder = 1;
|
||||
while (true)
|
||||
{
|
||||
await Task.Delay(500);
|
||||
_logger.LogInformation("Next chapter batch");
|
||||
var response = await _httpClient.PostAsync(EpisodeListEndpoint, new FormUrlEncodedContent(new Dictionary<string, string>
|
||||
{
|
||||
{"novel_no", novelId.ToString()},
|
||||
|
||||
14
fictionarchive-web-astro/package-lock.json
generated
14
fictionarchive-web-astro/package-lock.json
generated
@@ -12,6 +12,7 @@
|
||||
"@astrojs/svelte": "^7.2.2",
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
"@urql/core": "^6.0.1",
|
||||
"@urql/exchange-auth": "^3.0.0",
|
||||
"@urql/svelte": "^5.0.0",
|
||||
"astro": "^5.16.2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
@@ -4304,6 +4305,19 @@
|
||||
"wonka": "^6.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@urql/exchange-auth": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@urql/exchange-auth/-/exchange-auth-3.0.0.tgz",
|
||||
"integrity": "sha512-tj09xiOR2f1J2h8TE9uZWjRZipCdmDoTewEytOacDQ+0Teo+yIZxm3ppHxolQtiA51OHrGYiNTkMte8HtfvaBw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@urql/core": "^6.0.0",
|
||||
"wonka": "^6.3.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@urql/core": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@urql/svelte": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@urql/svelte/-/svelte-5.0.0.tgz",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"@astrojs/svelte": "^7.2.2",
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
"@urql/core": "^6.0.1",
|
||||
"@urql/exchange-auth": "^3.0.0",
|
||||
"@urql/svelte": "^5.0.0",
|
||||
"astro": "^5.16.2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
|
||||
@@ -117,3 +117,14 @@ export async function logout() {
|
||||
user.set(null);
|
||||
}
|
||||
}
|
||||
|
||||
export async function refreshToken(): Promise<User | null> {
|
||||
if (!userManager) return null;
|
||||
try {
|
||||
const newUser = await userManager.signinSilent();
|
||||
return newUser;
|
||||
} catch (e) {
|
||||
console.error('Token refresh failed:', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ const authority = import.meta.env.PUBLIC_OIDC_AUTHORITY;
|
||||
const clientId = import.meta.env.PUBLIC_OIDC_CLIENT_ID;
|
||||
const redirectUri = import.meta.env.PUBLIC_OIDC_REDIRECT_URI;
|
||||
const postLogoutRedirectUri = import.meta.env.PUBLIC_OIDC_POST_LOGOUT_REDIRECT_URI ?? redirectUri;
|
||||
const scope = import.meta.env.PUBLIC_OIDC_SCOPE ?? 'openid profile email';
|
||||
const scope = import.meta.env.PUBLIC_OIDC_SCOPE ?? 'openid profile email offline_access';
|
||||
|
||||
export const isOidcConfigured =
|
||||
Boolean(authority) && Boolean(clientId) && Boolean(redirectUri);
|
||||
@@ -20,7 +20,7 @@ function buildSettings(): UserManagerSettings | null {
|
||||
response_type: 'code',
|
||||
scope,
|
||||
loadUserInfo: true,
|
||||
automaticSilentRenew: true,
|
||||
automaticSilentRenew: false, // We handle refresh reactively via authExchange
|
||||
userStore:
|
||||
typeof window !== 'undefined'
|
||||
? new WebStorageStateStore({ store: window.localStorage })
|
||||
|
||||
@@ -51,6 +51,9 @@
|
||||
import ChevronDown from '@lucide/svelte/icons/chevron-down';
|
||||
import ChevronUp from '@lucide/svelte/icons/chevron-up';
|
||||
import RefreshCw from '@lucide/svelte/icons/refresh-cw';
|
||||
import X from '@lucide/svelte/icons/x';
|
||||
import ChevronLeft from '@lucide/svelte/icons/chevron-left';
|
||||
import ChevronRight from '@lucide/svelte/icons/chevron-right';
|
||||
|
||||
interface Props {
|
||||
novelId?: string;
|
||||
@@ -66,10 +69,22 @@
|
||||
let refreshError: string | null = $state(null);
|
||||
let refreshSuccess = $state(false);
|
||||
|
||||
// Image viewer state
|
||||
type GalleryImage = {
|
||||
src: string;
|
||||
alt: string;
|
||||
chapterId?: number;
|
||||
chapterOrder?: number;
|
||||
chapterName?: string;
|
||||
isCover: boolean;
|
||||
};
|
||||
let viewerOpen = $state(false);
|
||||
let viewerIndex = $state(0);
|
||||
|
||||
const DESCRIPTION_PREVIEW_LENGTH = 300;
|
||||
|
||||
// Derived values
|
||||
const coverSrc = $derived(novel?.coverImage?.newPath ?? novel?.coverImage?.originalPath);
|
||||
const coverSrc = $derived(novel?.coverImage?.newPath);
|
||||
const status = $derived(novel?.rawStatus ?? 'UNKNOWN');
|
||||
const statusColor = $derived(statusColors[status]);
|
||||
const statusLabel = $derived(statusLabels[status]);
|
||||
@@ -102,6 +117,65 @@
|
||||
return lastUpdated.getTime() < sixHoursAgo;
|
||||
});
|
||||
|
||||
// Gallery images - cover + chapter images
|
||||
const galleryImages = $derived.by(() => {
|
||||
const images: GalleryImage[] = [];
|
||||
|
||||
// Add cover image first
|
||||
if (coverSrc && novel) {
|
||||
images.push({ src: coverSrc, alt: `${novel.name} cover`, isCover: true });
|
||||
}
|
||||
|
||||
// Add chapter images
|
||||
for (const chapter of sortedChapters) {
|
||||
for (const img of chapter.images ?? []) {
|
||||
if (img.newPath) {
|
||||
images.push({
|
||||
src: img.newPath,
|
||||
alt: `Image from ${chapter.name}`,
|
||||
chapterId: chapter.id,
|
||||
chapterOrder: chapter.order,
|
||||
chapterName: chapter.name,
|
||||
isCover: false
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return images;
|
||||
});
|
||||
|
||||
const currentImage = $derived(galleryImages[viewerIndex]);
|
||||
|
||||
// Image viewer functions
|
||||
function openImageViewer(index: number) {
|
||||
viewerIndex = index;
|
||||
viewerOpen = true;
|
||||
}
|
||||
|
||||
function closeViewer() {
|
||||
viewerOpen = false;
|
||||
}
|
||||
|
||||
function nextImage() {
|
||||
if (galleryImages.length > 0) {
|
||||
viewerIndex = (viewerIndex + 1) % galleryImages.length;
|
||||
}
|
||||
}
|
||||
|
||||
function prevImage() {
|
||||
if (galleryImages.length > 0) {
|
||||
viewerIndex = (viewerIndex - 1 + galleryImages.length) % galleryImages.length;
|
||||
}
|
||||
}
|
||||
|
||||
function handleViewerKeydown(e: KeyboardEvent) {
|
||||
if (!viewerOpen) return;
|
||||
if (e.key === 'Escape') closeViewer();
|
||||
if (e.key === 'ArrowRight') nextImage();
|
||||
if (e.key === 'ArrowLeft') prevImage();
|
||||
}
|
||||
|
||||
async function fetchNovel() {
|
||||
if (!novelId) {
|
||||
error = 'No novel ID provided';
|
||||
@@ -371,18 +445,17 @@
|
||||
Chapters
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="comments"
|
||||
disabled
|
||||
class="rounded-md data-[state=active]:bg-background data-[state=active]:shadow-sm px-3 py-1.5 text-sm font-medium transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
value="gallery"
|
||||
class="rounded-md data-[state=active]:bg-background data-[state=active]:shadow-sm px-3 py-1.5 text-sm font-medium transition-all"
|
||||
>
|
||||
Comments
|
||||
Gallery
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="recommendations"
|
||||
value="bookmarks"
|
||||
disabled
|
||||
class="rounded-md data-[state=active]:bg-background data-[state=active]:shadow-sm px-3 py-1.5 text-sm font-medium transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
Recommendations
|
||||
Bookmarks
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</CardHeader>
|
||||
@@ -420,15 +493,32 @@
|
||||
{/if}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="comments" class="mt-0">
|
||||
<p class="text-muted-foreground text-sm py-8 text-center">
|
||||
Comments coming soon.
|
||||
</p>
|
||||
<TabsContent value="gallery" class="mt-0">
|
||||
{#if galleryImages.length === 0}
|
||||
<p class="text-muted-foreground text-sm py-4 text-center">
|
||||
No images available.
|
||||
</p>
|
||||
{:else}
|
||||
<div class="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-5 gap-2">
|
||||
{#each galleryImages as image, index (image.src)}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => openImageViewer(index)}
|
||||
class="relative aspect-square overflow-hidden rounded-md bg-muted/50 hover:ring-2 ring-primary transition-all"
|
||||
>
|
||||
<img src={image.src} alt={image.alt} class="h-full w-full object-cover" />
|
||||
{#if image.isCover}
|
||||
<Badge class="absolute top-1 left-1 text-xs">Cover</Badge>
|
||||
{/if}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="recommendations" class="mt-0">
|
||||
<TabsContent value="bookmarks" class="mt-0">
|
||||
<p class="text-muted-foreground text-sm py-8 text-center">
|
||||
Recommendations coming soon.
|
||||
Bookmarks coming soon.
|
||||
</p>
|
||||
</TabsContent>
|
||||
</CardContent>
|
||||
@@ -436,3 +526,72 @@
|
||||
</Card>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Image Viewer Modal -->
|
||||
{#if viewerOpen && currentImage}
|
||||
<div
|
||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black/90 backdrop-blur-sm"
|
||||
onclick={closeViewer}
|
||||
onkeydown={handleViewerKeydown}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
tabindex="-1"
|
||||
>
|
||||
<!-- Close button -->
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="absolute top-4 right-4 text-white hover:bg-white/10"
|
||||
onclick={closeViewer}
|
||||
>
|
||||
<X class="h-6 w-6" />
|
||||
</Button>
|
||||
|
||||
<!-- Navigation arrows -->
|
||||
{#if galleryImages.length > 1}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="absolute left-4 text-white hover:bg-white/10 h-12 w-12"
|
||||
onclick={(e: MouseEvent) => { e.stopPropagation(); prevImage(); }}
|
||||
>
|
||||
<ChevronLeft class="h-8 w-8" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="absolute right-4 top-1/2 -translate-y-1/2 text-white hover:bg-white/10 h-12 w-12"
|
||||
onclick={(e: MouseEvent) => { e.stopPropagation(); nextImage(); }}
|
||||
>
|
||||
<ChevronRight class="h-8 w-8" />
|
||||
</Button>
|
||||
{/if}
|
||||
|
||||
<!-- Image container -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div class="flex flex-col items-center max-w-[90vw] max-h-[90vh]" onclick={(e: MouseEvent) => e.stopPropagation()}>
|
||||
<img
|
||||
src={currentImage.src}
|
||||
alt={currentImage.alt}
|
||||
class="max-w-full max-h-[80vh] object-contain rounded-lg"
|
||||
/>
|
||||
|
||||
<!-- Chapter link (if not cover) -->
|
||||
{#if !currentImage.isCover && currentImage.chapterOrder}
|
||||
<a
|
||||
href="/novels/{novelId}/chapters/{currentImage.chapterOrder}"
|
||||
class="text-white/80 hover:text-white text-sm inline-flex items-center gap-1 mt-3"
|
||||
>
|
||||
From: Ch. {currentImage.chapterOrder} - {currentImage.chapterName}
|
||||
<ExternalLink class="h-3 w-3" />
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
<!-- Image counter -->
|
||||
<div class="text-white/60 text-sm mt-2">
|
||||
{viewerIndex + 1} / {galleryImages.length}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -114,7 +114,6 @@ export type ImageDto = {
|
||||
id: Scalars['UUID']['output'];
|
||||
lastUpdatedTime: Scalars['Instant']['output'];
|
||||
newPath: Maybe<Scalars['String']['output']>;
|
||||
originalPath: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type ImageDtoFilterInput = {
|
||||
@@ -124,7 +123,6 @@ export type ImageDtoFilterInput = {
|
||||
lastUpdatedTime?: InputMaybe<InstantFilterInput>;
|
||||
newPath?: InputMaybe<StringOperationFilterInput>;
|
||||
or?: InputMaybe<Array<ImageDtoFilterInput>>;
|
||||
originalPath?: InputMaybe<StringOperationFilterInput>;
|
||||
};
|
||||
|
||||
export type ImageDtoSortInput = {
|
||||
@@ -132,7 +130,6 @@ export type ImageDtoSortInput = {
|
||||
id?: InputMaybe<SortEnumType>;
|
||||
lastUpdatedTime?: InputMaybe<SortEnumType>;
|
||||
newPath?: InputMaybe<SortEnumType>;
|
||||
originalPath?: InputMaybe<SortEnumType>;
|
||||
};
|
||||
|
||||
export type ImportNovelInput = {
|
||||
@@ -730,7 +727,7 @@ export type NovelQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type NovelQuery = { novels: { nodes: Array<{ id: any, name: string, description: string, url: string, rawLanguage: Language, rawStatus: NovelStatus, statusOverride: NovelStatus | null, externalId: string, createdTime: any, lastUpdatedTime: any, author: { id: any, name: string, externalUrl: string | null }, source: { id: any, name: string, key: string, url: string }, coverImage: { newPath: string | null } | null, tags: Array<{ id: any, key: string, displayName: string, tagType: TagType }>, chapters: Array<{ id: any, order: any, name: string, lastUpdatedTime: any }> }> | null } | null };
|
||||
export type NovelQuery = { novels: { nodes: Array<{ id: any, name: string, description: string, url: string, rawLanguage: Language, rawStatus: NovelStatus, statusOverride: NovelStatus | null, externalId: string, createdTime: any, lastUpdatedTime: any, author: { id: any, name: string, externalUrl: string | null }, source: { id: any, name: string, key: string, url: string }, coverImage: { newPath: string | null } | null, tags: Array<{ id: any, key: string, displayName: string, tagType: TagType }>, chapters: Array<{ id: any, order: any, name: string, lastUpdatedTime: any, images: Array<{ id: any, newPath: string | null }> }> }> | null } | null };
|
||||
|
||||
export type NovelsQueryVariables = Exact<{
|
||||
first?: InputMaybe<Scalars['Int']['input']>;
|
||||
@@ -744,5 +741,5 @@ export type NovelsQuery = { novels: { edges: Array<{ cursor: string, node: { id:
|
||||
|
||||
export const ImportNovelDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ImportNovel"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ImportNovelInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"importNovel"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"novelUpdateRequestedEvent"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"novelUrl"}}]}}]}}]}}]} as unknown as DocumentNode<ImportNovelMutation, ImportNovelMutationVariables>;
|
||||
export const GetChapterDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetChapter"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"novelId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UnsignedInt"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"chapterOrder"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UnsignedInt"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"chapter"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"novelId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"novelId"}}},{"kind":"Argument","name":{"kind":"Name","value":"chapterOrder"},"value":{"kind":"Variable","name":{"kind":"Name","value":"chapterOrder"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"order"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"body"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"revision"}},{"kind":"Field","name":{"kind":"Name","value":"createdTime"}},{"kind":"Field","name":{"kind":"Name","value":"lastUpdatedTime"}},{"kind":"Field","name":{"kind":"Name","value":"images"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"newPath"}}]}},{"kind":"Field","name":{"kind":"Name","value":"novelId"}},{"kind":"Field","name":{"kind":"Name","value":"novelName"}},{"kind":"Field","name":{"kind":"Name","value":"totalChapters"}},{"kind":"Field","name":{"kind":"Name","value":"prevChapterOrder"}},{"kind":"Field","name":{"kind":"Name","value":"nextChapterOrder"}}]}}]}}]} as unknown as DocumentNode<GetChapterQuery, GetChapterQueryVariables>;
|
||||
export const NovelDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Novel"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UnsignedInt"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"novels"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"eq"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}}]}},{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"nodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"rawLanguage"}},{"kind":"Field","name":{"kind":"Name","value":"rawStatus"}},{"kind":"Field","name":{"kind":"Name","value":"statusOverride"}},{"kind":"Field","name":{"kind":"Name","value":"externalId"}},{"kind":"Field","name":{"kind":"Name","value":"createdTime"}},{"kind":"Field","name":{"kind":"Name","value":"lastUpdatedTime"}},{"kind":"Field","name":{"kind":"Name","value":"author"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"externalUrl"}}]}},{"kind":"Field","name":{"kind":"Name","value":"source"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"url"}}]}},{"kind":"Field","name":{"kind":"Name","value":"coverImage"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"newPath"}}]}},{"kind":"Field","name":{"kind":"Name","value":"tags"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"tagType"}}]}},{"kind":"Field","name":{"kind":"Name","value":"chapters"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"order"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"lastUpdatedTime"}}]}}]}}]}}]}}]} as unknown as DocumentNode<NovelQuery, NovelQueryVariables>;
|
||||
export const NovelDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Novel"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UnsignedInt"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"novels"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"eq"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}}]}},{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"nodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"rawLanguage"}},{"kind":"Field","name":{"kind":"Name","value":"rawStatus"}},{"kind":"Field","name":{"kind":"Name","value":"statusOverride"}},{"kind":"Field","name":{"kind":"Name","value":"externalId"}},{"kind":"Field","name":{"kind":"Name","value":"createdTime"}},{"kind":"Field","name":{"kind":"Name","value":"lastUpdatedTime"}},{"kind":"Field","name":{"kind":"Name","value":"author"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"externalUrl"}}]}},{"kind":"Field","name":{"kind":"Name","value":"source"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"url"}}]}},{"kind":"Field","name":{"kind":"Name","value":"coverImage"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"newPath"}}]}},{"kind":"Field","name":{"kind":"Name","value":"tags"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"tagType"}}]}},{"kind":"Field","name":{"kind":"Name","value":"chapters"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"order"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"lastUpdatedTime"}},{"kind":"Field","name":{"kind":"Name","value":"images"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"newPath"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode<NovelQuery, NovelQueryVariables>;
|
||||
export const NovelsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Novels"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"first"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"after"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"where"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"NovelDtoFilterInput"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"novels"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"after"},"value":{"kind":"Variable","name":{"kind":"Name","value":"after"}}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"Variable","name":{"kind":"Name","value":"where"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"coverImage"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"newPath"}}]}},{"kind":"Field","name":{"kind":"Name","value":"rawStatus"}},{"kind":"Field","name":{"kind":"Name","value":"lastUpdatedTime"}},{"kind":"Field","name":{"kind":"Name","value":"chapters"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"order"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"tags"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasNextPage"}},{"kind":"Field","name":{"kind":"Name","value":"endCursor"}}]}}]}}]}}]} as unknown as DocumentNode<NovelsQuery, NovelsQueryVariables>;
|
||||
@@ -1,19 +1,36 @@
|
||||
import { Client, cacheExchange, fetchExchange } from '@urql/core';
|
||||
import { authExchange } from '@urql/exchange-auth';
|
||||
import { get } from 'svelte/store';
|
||||
import { user } from '../auth/authStore';
|
||||
import { user, refreshToken } from '../auth/authStore';
|
||||
|
||||
export function createClient() {
|
||||
return new Client({
|
||||
url: import.meta.env.PUBLIC_GRAPHQL_URI,
|
||||
exchanges: [cacheExchange, fetchExchange],
|
||||
fetchOptions: () => {
|
||||
const currentUser = get(user);
|
||||
return {
|
||||
headers: currentUser?.access_token
|
||||
? { Authorization: `Bearer ${currentUser.access_token}` }
|
||||
: {},
|
||||
};
|
||||
},
|
||||
exchanges: [
|
||||
cacheExchange,
|
||||
authExchange(async (utils) => ({
|
||||
addAuthToOperation(operation) {
|
||||
const currentUser = get(user);
|
||||
if (!currentUser?.access_token) return operation;
|
||||
return utils.appendHeaders(operation, {
|
||||
Authorization: `Bearer ${currentUser.access_token}`,
|
||||
});
|
||||
},
|
||||
didAuthError(error) {
|
||||
return error.graphQLErrors?.some(
|
||||
(e) => e.extensions?.code === 'AUTH_NOT_AUTHENTICATED'
|
||||
);
|
||||
},
|
||||
async refreshAuth() {
|
||||
const newUser = await refreshToken();
|
||||
if (!newUser) {
|
||||
// Refresh failed, redirect to login
|
||||
window.location.href = '/';
|
||||
}
|
||||
},
|
||||
})),
|
||||
fetchExchange,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,10 @@ query Novel($id: UnsignedInt!) {
|
||||
order
|
||||
name
|
||||
lastUpdatedTime
|
||||
images {
|
||||
id
|
||||
newPath
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user