Compare commits
5 Commits
ece85cff7c
...
v1.5.2
| Author | SHA1 | Date | |
|---|---|---|---|
| 3308bc9d49 | |||
|
|
a056ca42ee | ||
|
|
fe83e0fe27 | ||
| 327c03c098 | |||
|
|
5e48f5737f |
@@ -95,6 +95,9 @@ NOVELPIA_USERNAME=your-username
|
|||||||
NOVELPIA_PASSWORD=your-password
|
NOVELPIA_PASSWORD=your-password
|
||||||
DEEPL_API_KEY=your-api-key
|
DEEPL_API_KEY=your-api-key
|
||||||
|
|
||||||
|
# ExpressVPN (used by the `vpn` container that tunnels novel-service into Korea)
|
||||||
|
EXPRESSVPN_ACTIVATION_CODE=your-expressvpn-activation-code
|
||||||
|
|
||||||
# S3 Storage
|
# S3 Storage
|
||||||
S3_ENDPOINT=https://s3.example.com
|
S3_ENDPOINT=https://s3.example.com
|
||||||
S3_BUCKET=fictionarchive
|
S3_BUCKET=fictionarchive
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ for svc in selected_services:
|
|||||||
# Export schema
|
# Export schema
|
||||||
run([
|
run([
|
||||||
"dotnet", "run",
|
"dotnet", "run",
|
||||||
|
"-c", "Release",
|
||||||
"--no-build",
|
"--no-build",
|
||||||
"--no-launch-profile",
|
"--no-launch-profile",
|
||||||
"--",
|
"--",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||||
<PackageReference Include="MassTransit" Version="8.5.7" />
|
<PackageReference Include="MassTransit" Version="8.5.9" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.11" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.11" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||||
<PackageReference Include="NodaTime.Testing" Version="3.3.0" />
|
<PackageReference Include="NodaTime.Testing" Version="3.3.0" />
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using FictionArchive.Service.NovelService.Models.Novels;
|
|||||||
using FictionArchive.Service.NovelService.Models.SourceAdapters;
|
using FictionArchive.Service.NovelService.Models.SourceAdapters;
|
||||||
using FictionArchive.Service.NovelService.Services;
|
using FictionArchive.Service.NovelService.Services;
|
||||||
using FictionArchive.Service.NovelService.Services.SourceAdapters;
|
using FictionArchive.Service.NovelService.Services.SourceAdapters;
|
||||||
|
using FictionArchive.Service.Shared.Constants;
|
||||||
using FictionArchive.Service.Shared.Contracts.Events;
|
using FictionArchive.Service.Shared.Contracts.Events;
|
||||||
using HotChocolate.Authorization;
|
using HotChocolate.Authorization;
|
||||||
using HotChocolate.Types;
|
using HotChocolate.Types;
|
||||||
@@ -21,19 +22,8 @@ public class Mutation
|
|||||||
return await service.QueueNovelImport(novelUrl);
|
return await service.QueueNovelImport(novelUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize]
|
|
||||||
public async Task<ChapterPullRequested> FetchChapterContents(
|
|
||||||
Guid importId,
|
|
||||||
uint novelId,
|
|
||||||
uint volumeId,
|
|
||||||
uint chapterOrder,
|
|
||||||
NovelUpdateService service)
|
|
||||||
{
|
|
||||||
return await service.QueueChapterPull(importId, novelId, volumeId, chapterOrder);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Error<KeyNotFoundException>]
|
[Error<KeyNotFoundException>]
|
||||||
[Authorize]
|
[Authorize(Roles = [AuthorizationConstants.Roles.Admin])]
|
||||||
public async Task<bool> DeleteNovel(uint novelId, NovelUpdateService service)
|
public async Task<bool> DeleteNovel(uint novelId, NovelUpdateService service)
|
||||||
{
|
{
|
||||||
await service.DeleteNovel(novelId);
|
await service.DeleteNovel(novelId);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||||
<PackageReference Include="MassTransit" Version="8.5.7" />
|
<PackageReference Include="MassTransit" Version="8.5.9" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.11" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.11" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||||
<PackageReference Include="NodaTime.Testing" Version="3.3.0" />
|
<PackageReference Include="NodaTime.Testing" Version="3.3.0" />
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ services:
|
|||||||
# VPN Container
|
# VPN Container
|
||||||
# ===========================================
|
# ===========================================
|
||||||
vpn:
|
vpn:
|
||||||
image: dperson/openvpn-client
|
image: misioslav/expressvpn:latest
|
||||||
networks:
|
networks:
|
||||||
fictionarchive:
|
fictionarchive:
|
||||||
ipv4_address: 172.20.0.20
|
ipv4_address: 172.20.0.20
|
||||||
@@ -51,23 +51,25 @@ services:
|
|||||||
- novel-service
|
- novel-service
|
||||||
cap_add:
|
cap_add:
|
||||||
- NET_ADMIN
|
- NET_ADMIN
|
||||||
|
- SYS_PTRACE
|
||||||
devices:
|
devices:
|
||||||
- /dev/net/tun
|
- /dev/net/tun
|
||||||
volumes:
|
|
||||||
- /srv/docker_volumes/korean_vpn:/vpn
|
|
||||||
dns:
|
|
||||||
- 192.168.3.1
|
|
||||||
environment:
|
environment:
|
||||||
- DNS=1.1.1.1,8.8.8.8
|
CODE: ${EXPRESSVPN_ACTIVATION_CODE}
|
||||||
|
SERVER: krsi
|
||||||
|
PROTOCOL: lightwayudp
|
||||||
|
WHITELIST_DNS: 1.1.1.1,8.8.8.8
|
||||||
|
CONNECTION_CHECK_INTERVAL: 30
|
||||||
|
RECONNECT_FAILURE_THRESHOLD: 3
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
- "postgres:172.20.0.10"
|
- "postgres:172.20.0.10"
|
||||||
- "rabbitmq:172.20.0.11"
|
- "rabbitmq:172.20.0.11"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "ping", "-c", "1", "-W", "5", "1.1.1.1"]
|
test: ["CMD-SHELL", "test ! -f /tmp/expressvpn/reconnect-failure.flag && expressvpnctl status | grep -q Connected"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
start_period: 30s
|
start_period: 60s
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
# ===========================================
|
# ===========================================
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { writable, derived } from 'svelte/store';
|
import { writable, derived, get } from 'svelte/store';
|
||||||
import type { User } from 'oidc-client-ts';
|
import type { User } from 'oidc-client-ts';
|
||||||
import { userManager, isOidcConfigured } from './oidcConfig';
|
import { userManager, isOidcConfigured } from './oidcConfig';
|
||||||
|
|
||||||
@@ -8,6 +8,15 @@ export const isLoading = writable(true);
|
|||||||
export const isAuthenticated = derived(user, ($user) => $user !== null);
|
export const isAuthenticated = derived(user, ($user) => $user !== null);
|
||||||
export const isConfigured = isOidcConfigured;
|
export const isConfigured = isOidcConfigured;
|
||||||
|
|
||||||
|
export const userGroups = derived(user, ($user) => {
|
||||||
|
const groups = $user?.profile?.groups;
|
||||||
|
return Array.isArray(groups) ? (groups as string[]) : [];
|
||||||
|
});
|
||||||
|
|
||||||
|
export function hasGroup(groupName: string): boolean {
|
||||||
|
return get(userGroups).includes(groupName);
|
||||||
|
}
|
||||||
|
|
||||||
// Cookie management
|
// Cookie management
|
||||||
function setCookieFromUser(u: User) {
|
function setCookieFromUser(u: User) {
|
||||||
if (!u?.access_token) return;
|
if (!u?.access_token) return;
|
||||||
|
|||||||
@@ -35,7 +35,8 @@
|
|||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { client } from '$lib/graphql/client';
|
import { client } from '$lib/graphql/client';
|
||||||
import { NovelDocument, ImportNovelDocument, DeleteNovelDocument, GetBookmarksDocument } from '$lib/graphql/__generated__/graphql';
|
import { NovelDocument, ImportNovelDocument, DeleteNovelDocument, GetBookmarksDocument } from '$lib/graphql/__generated__/graphql';
|
||||||
import { isAuthenticated } from '$lib/auth/authStore';
|
import { isAuthenticated, hasGroup } from '$lib/auth/authStore';
|
||||||
|
import { Groups } from '$lib/constants/groups';
|
||||||
import { Card, CardContent, CardHeader } from '$lib/components/ui/card';
|
import { Card, CardContent, CardHeader } from '$lib/components/ui/card';
|
||||||
import { Badge } from '$lib/components/ui/badge';
|
import { Badge } from '$lib/components/ui/badge';
|
||||||
import { Button } from '$lib/components/ui/button';
|
import { Button } from '$lib/components/ui/button';
|
||||||
@@ -483,6 +484,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
{#if hasGroup(Groups.Admin)}
|
||||||
<Button
|
<Button
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -492,6 +494,7 @@
|
|||||||
<Trash2 class="h-3 w-3" />
|
<Trash2 class="h-3 w-3" />
|
||||||
Delete
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
|
{/if}
|
||||||
<AddToReadingListButton novelId={novel.id} />
|
<AddToReadingListButton novelId={novel.id} />
|
||||||
{/if}
|
{/if}
|
||||||
{#if refreshSuccess}
|
{#if refreshSuccess}
|
||||||
|
|||||||
3
fictionarchive-web-astro/src/lib/constants/groups.ts
Normal file
3
fictionarchive-web-astro/src/lib/constants/groups.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export const Groups = {
|
||||||
|
Admin: 'admin'
|
||||||
|
} as const;
|
||||||
Reference in New Issue
Block a user