Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
023b1f7fec | ||
|
|
7e99fc6d70 | ||
|
|
12d6d30fb0 | ||
|
|
f9c14685b3 |
@@ -135,6 +135,35 @@ jobs:
|
||||
cache-from: type=registry,ref=${{ secrets.DOCKER_USER }}/libnovel-runner:latest
|
||||
cache-to: type=inline
|
||||
|
||||
# ── ui: source map upload ─────────────────────────────────────────────────────
|
||||
# Builds the UI with source maps and uploads them to GlitchTip so that error
|
||||
# stack traces resolve to original .svelte/.ts file names and line numbers.
|
||||
# Runs in parallel with docker-ui (both need check-ui to pass first).
|
||||
upload-sourcemaps:
|
||||
name: Upload source maps
|
||||
runs-on: ubuntu-latest
|
||||
needs: [check-ui]
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ui
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "22"
|
||||
cache: npm
|
||||
cache-dependency-path: ui/package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build with source maps and upload to GlitchTip
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.GLITCHTIP_AUTH_TOKEN }}
|
||||
BUILD_VERSION: ${{ gitea.ref_name }}
|
||||
run: npm run build
|
||||
|
||||
# ── docker: ui ────────────────────────────────────────────────────────────────
|
||||
docker-ui:
|
||||
name: Docker / ui
|
||||
@@ -213,7 +242,7 @@ jobs:
|
||||
release:
|
||||
name: Gitea Release
|
||||
runs-on: ubuntu-latest
|
||||
needs: [docker-backend, docker-runner, docker-ui, docker-caddy]
|
||||
needs: [docker-backend, docker-runner, docker-ui, docker-caddy, upload-sourcemaps]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
|
||||
@@ -248,23 +248,30 @@ func (r *Runner) poll(ctx context.Context, scrapeSem, audioSem chan struct{}, wg
|
||||
}
|
||||
|
||||
// ── Audio tasks ───────────────────────────────────────────────────────
|
||||
// Only claim tasks when there is a free slot in the semaphore.
|
||||
// This avoids the old bug where we claimed (status→running) a task and
|
||||
// then couldn't dispatch it, leaving it orphaned until the reaper fired.
|
||||
audioLoop:
|
||||
for {
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
// Check capacity before claiming to avoid orphaning tasks.
|
||||
select {
|
||||
case audioSem <- struct{}{}:
|
||||
// Slot acquired — proceed to claim a task.
|
||||
default:
|
||||
// All slots busy; leave remaining pending tasks for next tick.
|
||||
break audioLoop
|
||||
}
|
||||
task, ok, err := r.deps.Consumer.ClaimNextAudioTask(ctx, r.cfg.WorkerID)
|
||||
if err != nil {
|
||||
<-audioSem // release the pre-acquired slot
|
||||
r.deps.Log.Error("runner: ClaimNextAudioTask failed", "err", err)
|
||||
break
|
||||
}
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
select {
|
||||
case audioSem <- struct{}{}:
|
||||
default:
|
||||
r.deps.Log.Warn("runner: audio semaphore full, will retry next tick",
|
||||
"task_id", task.ID)
|
||||
<-audioSem // release the pre-acquired slot; queue empty
|
||||
break
|
||||
}
|
||||
r.tasksRunning.Add(1)
|
||||
|
||||
@@ -247,8 +247,9 @@ func (c *pbClient) claimRecord(ctx context.Context, collection, workerID string,
|
||||
}
|
||||
|
||||
claim := map[string]any{
|
||||
"status": string(domain.TaskStatusRunning),
|
||||
"worker_id": workerID,
|
||||
"status": string(domain.TaskStatusRunning),
|
||||
"worker_id": workerID,
|
||||
"heartbeat_at": time.Now().UTC().Format(time.RFC3339),
|
||||
}
|
||||
for k, v := range extraClaim {
|
||||
claim[k] = v
|
||||
|
||||
1
ui/package-lock.json
generated
1
ui/package-lock.json
generated
@@ -17,6 +17,7 @@
|
||||
"pocketbase": "^0.26.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sentry/vite-plugin": "^5.1.1",
|
||||
"@sveltejs/adapter-auto": "^7.0.0",
|
||||
"@sveltejs/adapter-node": "^5.5.4",
|
||||
"@sveltejs/kit": "^2.50.2",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sentry/vite-plugin": "^5.1.1",
|
||||
"@sveltejs/adapter-auto": "^7.0.0",
|
||||
"@sveltejs/adapter-node": "^5.5.4",
|
||||
"@sveltejs/kit": "^2.50.2",
|
||||
|
||||
@@ -6,7 +6,10 @@ import { env } from '$env/dynamic/public';
|
||||
if (env.PUBLIC_GLITCHTIP_DSN) {
|
||||
Sentry.init({
|
||||
dsn: env.PUBLIC_GLITCHTIP_DSN,
|
||||
tracesSampleRate: 0.1
|
||||
tracesSampleRate: 0.1,
|
||||
// Must match the release name used when uploading source maps in CI
|
||||
// (BUILD_VERSION injected by Dockerfile as PUBLIC_BUILD_VERSION).
|
||||
release: env.PUBLIC_BUILD_VERSION || undefined
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,10 @@ import { drain as drainPresignCache } from '$lib/server/presignCache';
|
||||
if (pubEnv.PUBLIC_GLITCHTIP_DSN) {
|
||||
Sentry.init({
|
||||
dsn: pubEnv.PUBLIC_GLITCHTIP_DSN,
|
||||
tracesSampleRate: 0.1
|
||||
tracesSampleRate: 0.1,
|
||||
// Must match the release name used when uploading source maps in CI
|
||||
// (BUILD_VERSION injected by Dockerfile as PUBLIC_BUILD_VERSION).
|
||||
release: pubEnv.PUBLIC_BUILD_VERSION || undefined
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { getSettings } from '$lib/server/pocketbase';
|
||||
import { log } from '$lib/server/logger';
|
||||
|
||||
// Routes that are accessible without being logged in
|
||||
const PUBLIC_ROUTES = new Set(['/login']);
|
||||
const PUBLIC_ROUTES = new Set(['/login', '/disclaimer', '/privacy', '/dmca', '/terms']);
|
||||
|
||||
export const load: LayoutServerLoad = async ({ locals, url }) => {
|
||||
// Allow /auth/* (OAuth initiation + callbacks) without login
|
||||
|
||||
51
ui/src/routes/terms/+page.svelte
Normal file
51
ui/src/routes/terms/+page.svelte
Normal file
@@ -0,0 +1,51 @@
|
||||
<svelte:head>
|
||||
<title>Terms of Service — libnovel</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="max-w-2xl mx-auto py-10 px-4">
|
||||
<h1 class="text-2xl font-bold text-zinc-100 mb-6">Terms of Service</h1>
|
||||
|
||||
<div class="space-y-5 text-sm text-zinc-400 leading-relaxed">
|
||||
<p>
|
||||
By using libnovel you agree to these terms. If you do not agree, please do not use the service.
|
||||
</p>
|
||||
|
||||
<h2 class="text-base font-semibold text-zinc-200 mt-6">Use of the service</h2>
|
||||
<ul class="list-disc list-inside space-y-2 pl-1">
|
||||
<li>libnovel is provided for personal, non-commercial reading use only.</li>
|
||||
<li>You may not scrape, crawl, or systematically download content from the site.</li>
|
||||
<li>You may not use the service for any unlawful purpose.</li>
|
||||
<li>Accounts may be suspended or terminated for abuse.</li>
|
||||
</ul>
|
||||
|
||||
<h2 class="text-base font-semibold text-zinc-200 mt-6">Content</h2>
|
||||
<p>
|
||||
libnovel aggregates publicly available web novel content from third-party sources for
|
||||
personal reading convenience. We do not claim ownership of any novel content displayed on
|
||||
the site. If you are a rights holder and wish to have content removed, please see our
|
||||
<a href="/dmca" class="text-amber-400 hover:text-amber-300 transition-colors">DMCA policy</a>.
|
||||
</p>
|
||||
|
||||
<h2 class="text-base font-semibold text-zinc-200 mt-6">Accounts</h2>
|
||||
<p>
|
||||
You are responsible for maintaining the security of your account. libnovel is not liable
|
||||
for any loss or damage resulting from unauthorised access to your account.
|
||||
</p>
|
||||
|
||||
<h2 class="text-base font-semibold text-zinc-200 mt-6">Disclaimer of warranties</h2>
|
||||
<p>
|
||||
The service is provided "as is" without warranty of any kind. We do not guarantee
|
||||
availability, accuracy, or completeness of any content. See our full
|
||||
<a href="/disclaimer" class="text-amber-400 hover:text-amber-300 transition-colors">disclaimer</a>
|
||||
for details.
|
||||
</p>
|
||||
|
||||
<h2 class="text-base font-semibold text-zinc-200 mt-6">Changes to these terms</h2>
|
||||
<p>
|
||||
We may update these terms at any time. Continued use of the service after changes are
|
||||
posted constitutes acceptance of the revised terms.
|
||||
</p>
|
||||
|
||||
<p class="text-zinc-600 text-xs mt-8">Last updated: {new Date().getFullYear()}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,9 +1,36 @@
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
import { sentryVitePlugin } from '@sentry/vite-plugin';
|
||||
|
||||
// Source maps are always generated so that the CI pipeline can upload them to
|
||||
// GlitchTip after a release build. The sentryVitePlugin upload step is only
|
||||
// active when SENTRY_AUTH_TOKEN is present (i.e. in the release CI job).
|
||||
export default defineConfig({
|
||||
plugins: [tailwindcss(), sveltekit()],
|
||||
build: {
|
||||
sourcemap: true
|
||||
},
|
||||
plugins: [
|
||||
tailwindcss(),
|
||||
sveltekit(),
|
||||
sentryVitePlugin({
|
||||
org: 'libnovel',
|
||||
project: 'libnovel-ui',
|
||||
url: 'https://errors.libnovel.cc/',
|
||||
// Auth token injected by CI via SENTRY_AUTH_TOKEN env var.
|
||||
// When the env var is absent the plugin is a no-op.
|
||||
authToken: process.env.SENTRY_AUTH_TOKEN,
|
||||
// Release name matches the Docker image tag (e.g. "1.2.3").
|
||||
release: { name: process.env.BUILD_VERSION },
|
||||
// Don't upload source maps in local dev or CI type-check jobs.
|
||||
disable: !process.env.SENTRY_AUTH_TOKEN,
|
||||
// Source maps are uploaded to GlitchTip; do not ship them in the
|
||||
// production bundle served to browsers.
|
||||
sourceMapsUploadOptions: {
|
||||
filesToDeleteAfterUpload: ['./build/**/*.map']
|
||||
}
|
||||
})
|
||||
],
|
||||
ssr: {
|
||||
// Force these packages to be bundled into the server output rather than
|
||||
// treated as external requires. The production Docker image has no
|
||||
|
||||
Reference in New Issue
Block a user