diff --git a/.gitea/workflows/release.yaml b/.gitea/workflows/release.yaml index 7e320ad..f119efb 100644 --- a/.gitea/workflows/release.yaml +++ b/.gitea/workflows/release.yaml @@ -190,17 +190,6 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Fetch releases from Gitea API - run: | - set -euo pipefail - RESPONSE=$(curl -sfL \ - -H "Accept: application/json" \ - "http://gitea.kalekber.cc/api/v1/repos/kamil/libnovel/releases?limit=50&page=1") - # Validate JSON before writing — fails hard if response is not a JSON array - COUNT=$(echo "$RESPONSE" | jq 'if type == "array" then length else error("expected array, got \(type)") end') - echo "$RESPONSE" > ui/static/releases.json - echo "Fetched $COUNT releases" - - uses: docker/setup-buildx-action@v3 - name: Log in to Docker Hub @@ -278,8 +267,25 @@ jobs: with: fetch-depth: 0 + - name: Extract release notes from tag commit + id: notes + run: | + set -euo pipefail + # Subject line (first line of commit message) → release title + SUBJECT=$(git log -1 --format="%s" "${{ gitea.sha }}") + # Body (everything after the blank line) → release body + BODY=$(git log -1 --format="%b" "${{ gitea.sha }}" | sed '/^Co-Authored-By:/d' | sed '/^[[:space:]]*$/{ N; /^\n$/d }' | sed 's/^[[:space:]]*$//' | awk 'NF || !p; {p = !NF}') + echo "title=${SUBJECT}" >> "$GITHUB_OUTPUT" + # Use a heredoc delimiter to safely handle multi-line body + { + echo "body<> "$GITHUB_OUTPUT" + - name: Create release uses: https://gitea.com/actions/gitea-release-action@v1 with: token: ${{ secrets.GITEA_TOKEN }} - generate_release_notes: true + title: ${{ steps.notes.outputs.title }} + body: ${{ steps.notes.outputs.body }} diff --git a/ui/.gitignore b/ui/.gitignore index f149927..b9e091f 100644 --- a/ui/.gitignore +++ b/ui/.gitignore @@ -22,5 +22,4 @@ Thumbs.db vite.config.js.timestamp-* vite.config.ts.timestamp-* -# Generated by CI at build time — do not commit -/static/releases.json + diff --git a/ui/messages/en.json b/ui/messages/en.json index 6cfaa87..11b6249 100644 --- a/ui/messages/en.json +++ b/ui/messages/en.json @@ -368,6 +368,7 @@ "admin_nav_logs": "Logs", "admin_nav_uptime": "Uptime", "admin_nav_push": "Push", + "admin_nav_gitea": "Gitea", "admin_scrape_status_idle": "Idle", "admin_scrape_status_running": "Running", diff --git a/ui/messages/fr.json b/ui/messages/fr.json index e5b581c..f0cfa2b 100644 --- a/ui/messages/fr.json +++ b/ui/messages/fr.json @@ -430,5 +430,6 @@ "feed_reader_label": "lit", "feed_chapters_label": "{n} chapitres", "feed_browse_cta": "Parcourir le catalogue", - "feed_find_users_cta": "Trouver des lecteurs" + "feed_find_users_cta": "Trouver des lecteurs", + "admin_nav_gitea": "Gitea" } diff --git a/ui/messages/id.json b/ui/messages/id.json index 114f65d..2e52bad 100644 --- a/ui/messages/id.json +++ b/ui/messages/id.json @@ -430,5 +430,6 @@ "feed_reader_label": "membaca", "feed_chapters_label": "{n} bab", "feed_browse_cta": "Jelajahi katalog", - "feed_find_users_cta": "Temukan pembaca" + "feed_find_users_cta": "Temukan pembaca", + "admin_nav_gitea": "Gitea" } diff --git a/ui/messages/pt.json b/ui/messages/pt.json index 268064f..283788c 100644 --- a/ui/messages/pt.json +++ b/ui/messages/pt.json @@ -430,5 +430,6 @@ "feed_reader_label": "lendo", "feed_chapters_label": "{n} capítulos", "feed_browse_cta": "Ver catálogo", - "feed_find_users_cta": "Encontrar leitores" + "feed_find_users_cta": "Encontrar leitores", + "admin_nav_gitea": "Gitea" } diff --git a/ui/messages/ru.json b/ui/messages/ru.json index aa4f0e6..91a0fa5 100644 --- a/ui/messages/ru.json +++ b/ui/messages/ru.json @@ -430,5 +430,6 @@ "feed_reader_label": "читает", "feed_chapters_label": "{n} глав", "feed_browse_cta": "Каталог", - "feed_find_users_cta": "Найти читателей" + "feed_find_users_cta": "Найти читателей", + "admin_nav_gitea": "Gitea" } diff --git a/ui/src/lib/paraglide/messages/_index.js b/ui/src/lib/paraglide/messages/_index.js index 5325700..56a6fc2 100644 --- a/ui/src/lib/paraglide/messages/_index.js +++ b/ui/src/lib/paraglide/messages/_index.js @@ -339,6 +339,7 @@ export * from './admin_nav_analytics.js' export * from './admin_nav_logs.js' export * from './admin_nav_uptime.js' export * from './admin_nav_push.js' +export * from './admin_nav_gitea.js' export * from './admin_scrape_status_idle.js' export * from './admin_scrape_full_catalogue.js' export * from './admin_scrape_single_book.js' diff --git a/ui/src/lib/paraglide/messages/admin_nav_gitea.js b/ui/src/lib/paraglide/messages/admin_nav_gitea.js new file mode 100644 index 0000000..e376ab3 --- /dev/null +++ b/ui/src/lib/paraglide/messages/admin_nav_gitea.js @@ -0,0 +1,44 @@ +/* eslint-disable */ +import { getLocale, experimentalStaticLocale } from '../runtime.js'; + +/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */ + +/** @typedef {{}} Admin_Nav_GiteaInputs */ + +const en_admin_nav_gitea = /** @type {(inputs: Admin_Nav_GiteaInputs) => LocalizedString} */ () => { + return /** @type {LocalizedString} */ (`Gitea`) +}; + +const ru_admin_nav_gitea = /** @type {(inputs: Admin_Nav_GiteaInputs) => LocalizedString} */ () => { + return /** @type {LocalizedString} */ (`Gitea`) +}; + +const id_admin_nav_gitea = /** @type {(inputs: Admin_Nav_GiteaInputs) => LocalizedString} */ () => { + return /** @type {LocalizedString} */ (`Gitea`) +}; + +const pt_admin_nav_gitea = /** @type {(inputs: Admin_Nav_GiteaInputs) => LocalizedString} */ () => { + return /** @type {LocalizedString} */ (`Gitea`) +}; + +const fr_admin_nav_gitea = /** @type {(inputs: Admin_Nav_GiteaInputs) => LocalizedString} */ () => { + return /** @type {LocalizedString} */ (`Gitea`) +}; + +/** +* | output | +* | --- | +* | "Gitea" | +* +* @param {Admin_Nav_GiteaInputs} inputs +* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options +* @returns {LocalizedString} +*/ +export const admin_nav_gitea = /** @type {((inputs?: Admin_Nav_GiteaInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata} */ ((inputs = {}, options = {}) => { + const locale = experimentalStaticLocale ?? options.locale ?? getLocale() + if (locale === "en") return en_admin_nav_gitea(inputs) + if (locale === "ru") return ru_admin_nav_gitea(inputs) + if (locale === "id") return id_admin_nav_gitea(inputs) + if (locale === "pt") return pt_admin_nav_gitea(inputs) + return fr_admin_nav_gitea(inputs) +}); \ No newline at end of file diff --git a/ui/src/routes/admin/+layout.svelte b/ui/src/routes/admin/+layout.svelte index d6323ff..d60acb8 100644 --- a/ui/src/routes/admin/+layout.svelte +++ b/ui/src/routes/admin/+layout.svelte @@ -15,7 +15,8 @@ { href: 'https://analytics.libnovel.cc', label: () => m.admin_nav_analytics() }, { href: 'https://logs.libnovel.cc', label: () => m.admin_nav_logs() }, { href: 'https://uptime.libnovel.cc', label: () => m.admin_nav_uptime() }, - { href: 'https://push.libnovel.cc', label: () => m.admin_nav_push() } + { href: 'https://push.libnovel.cc', label: () => m.admin_nav_push() }, + { href: 'https://gitea.kalekber.cc/kamil/libnovel', label: () => m.admin_nav_gitea() } ]; interface Props { diff --git a/ui/src/routes/admin/changelog/+page.server.ts b/ui/src/routes/admin/changelog/+page.server.ts index eb3d604..01d6e79 100644 --- a/ui/src/routes/admin/changelog/+page.server.ts +++ b/ui/src/routes/admin/changelog/+page.server.ts @@ -1,5 +1,3 @@ -import { readFileSync } from 'fs'; -import { join } from 'path'; import type { PageServerLoad } from './$types'; export interface Release { @@ -12,13 +10,18 @@ export interface Release { draft: boolean; } -export const load: PageServerLoad = async () => { +const GITEA_RELEASES_URL = + 'https://gitea.kalekber.cc/api/v1/repos/kamil/libnovel/releases?limit=50&page=1'; + +export const load: PageServerLoad = async ({ fetch }) => { try { - // releases.json is baked into the image at build time by CI. - // SvelteKit Node adapter copies static/ → build/client/, so the file - // lives at /build/client/releases.json in production. - const raw = readFileSync(join(process.cwd(), 'build', 'client', 'releases.json'), 'utf-8'); - const releases: Release[] = JSON.parse(raw); + const res = await fetch(GITEA_RELEASES_URL, { + headers: { Accept: 'application/json' } + }); + if (!res.ok) { + return { releases: [], error: `Gitea API returned ${res.status}` }; + } + const releases: Release[] = await res.json(); return { releases: releases.filter((r) => !r.draft) }; } catch (e) { return { releases: [], error: String(e) };