Release-Ankündigungen + öffentliche Changelog-Seite
- Gitea-Release-Webhook: Releases werden archiviert (releases-Tabelle) und neue 'published'-Releases als oranges 🚀-Embed mit Buttons angekündigt - Release-Kanal auf der Setup-Seite wählbar (leer = Feature aus) + Test-Button - /changelog: öffentliche Seite mit Tag-Chips, Pre-Release-Badge, Release-Notes - /api/releases öffentlich; Updates/Redeliveries posten nicht doppelt Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+28
-12
@@ -1,9 +1,9 @@
|
||||
// REST-API fürs Webinterface: Devlogs öffentlich, Commits + Settings nur für den Admin
|
||||
import { EmbedBuilder } from 'discord.js';
|
||||
import { listDevlogs, searchDevlogs, listCommits, archiveStats, getSetting, setSetting } from '../db.js';
|
||||
import { listDevlogs, searchDevlogs, listCommits, listReleases, archiveStats, getSetting, setSetting } from '../db.js';
|
||||
import { config } from '../config.js';
|
||||
import { removeDevlog } from '../bot/devlog-archive.js';
|
||||
import { commitChannelId, devlogChannelId } from '../runtime-settings.js';
|
||||
import { commitChannelId, devlogChannelId, releaseChannelId } from '../runtime-settings.js';
|
||||
import { getSessionUser, isAdmin } from './auth.js';
|
||||
|
||||
const PAGE_SIZE = 20;
|
||||
@@ -42,6 +42,13 @@ export function registerApiRoutes(app, client) {
|
||||
return { items: mapped, total, page, pageSize: PAGE_SIZE };
|
||||
});
|
||||
|
||||
// Changelog — öffentlich (Releases aller Repos)
|
||||
app.get('/api/releases', async (request) => {
|
||||
const { limit, offset, page } = paging(request);
|
||||
const { items, total } = listReleases(limit, offset);
|
||||
return { items, total, page, pageSize: PAGE_SIZE };
|
||||
});
|
||||
|
||||
// RSS-Feed der Devlogs — öffentlich abonnierbar
|
||||
app.get('/feed.xml', async (request, reply) => {
|
||||
const { items } = listDevlogs(20, 0);
|
||||
@@ -113,6 +120,7 @@ ${rssItems}
|
||||
return {
|
||||
commit_channel_id: commitChannelId(),
|
||||
devlog_channel_id: devlogChannelId(),
|
||||
release_channel_id: releaseChannelId() ?? '',
|
||||
commit_feed_enabled: getSetting('commit_feed_enabled') !== '0',
|
||||
commit_branch_filter: getSetting('commit_branch_filter') ?? '',
|
||||
ignored_repos: getSetting('ignored_repos') ?? '',
|
||||
@@ -140,15 +148,19 @@ ${rssItems}
|
||||
|
||||
const body = request.body ?? {};
|
||||
|
||||
// Kanäle: müssen existierende Textkanäle sein
|
||||
for (const key of ['commit_channel_id', 'devlog_channel_id']) {
|
||||
if (body[key] !== undefined) {
|
||||
const ch = client.channels.cache.get(String(body[key]));
|
||||
if (!ch?.isTextBased?.()) {
|
||||
return reply.code(400).send({ error: `${key}: Kanal nicht gefunden` });
|
||||
}
|
||||
setSetting(key, String(body[key]));
|
||||
// Kanäle: müssen existierende Textkanäle sein (release darf leer sein = deaktiviert)
|
||||
for (const key of ['commit_channel_id', 'devlog_channel_id', 'release_channel_id']) {
|
||||
if (body[key] === undefined) continue;
|
||||
const value = String(body[key]);
|
||||
if (value === '' && key === 'release_channel_id') {
|
||||
setSetting(key, '');
|
||||
continue;
|
||||
}
|
||||
const ch = client.channels.cache.get(value);
|
||||
if (!ch?.isTextBased?.()) {
|
||||
return reply.code(400).send({ error: `${key}: Kanal nicht gefunden` });
|
||||
}
|
||||
setSetting(key, value);
|
||||
}
|
||||
if (body.commit_feed_enabled !== undefined) {
|
||||
setSetting('commit_feed_enabled', body.commit_feed_enabled ? '1' : '0');
|
||||
@@ -168,7 +180,11 @@ ${rssItems}
|
||||
if (requireAdmin(request, reply)) return;
|
||||
|
||||
const target = request.params.target;
|
||||
const channelId = target === 'commit' ? commitChannelId() : target === 'devlog' ? devlogChannelId() : null;
|
||||
const channelId =
|
||||
target === 'commit' ? commitChannelId()
|
||||
: target === 'devlog' ? devlogChannelId()
|
||||
: target === 'release' ? releaseChannelId()
|
||||
: null;
|
||||
if (!channelId) {
|
||||
return reply.code(400).send({ error: 'Kein Kanal konfiguriert' });
|
||||
}
|
||||
@@ -180,7 +196,7 @@ ${rssItems}
|
||||
.setColor(0xf5c518)
|
||||
.setTitle('🔧 Test')
|
||||
.setDescription(
|
||||
`Test-Nachricht für den **${target === 'commit' ? 'Commit' : 'Devlog'}-Kanal** — von der Settings-Seite ausgelöst.`
|
||||
`Test-Nachricht für den **${{ commit: 'Commit', devlog: 'Devlog', release: 'Release' }[target]}-Kanal** — von der Settings-Seite ausgelöst.`
|
||||
)
|
||||
.setFooter({ text: 'D4RKST3R // SETUP' }),
|
||||
],
|
||||
|
||||
+38
-2
@@ -8,9 +8,10 @@ import { existsSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { config } from '../config.js';
|
||||
import { saveCommits } from '../db.js';
|
||||
import { saveCommits, saveRelease } from '../db.js';
|
||||
import { commitFeedEnabled, branchAllowed, repoIgnored } from '../runtime-settings.js';
|
||||
import { postPushEmbed } from '../bot/commit-feed.js';
|
||||
import { postReleaseEmbed } from '../bot/release-feed.js';
|
||||
import { registerAuthRoutes } from './auth.js';
|
||||
import { registerApiRoutes } from './api.js';
|
||||
import { registerDevlogEndpoint } from './devlog-endpoint.js';
|
||||
@@ -103,8 +104,43 @@ export async function startWebServer(client) {
|
||||
return reply.code(401).send({ error: 'invalid signature' });
|
||||
}
|
||||
|
||||
// Andere Events (Tag, Issue, …) freundlich ignorieren
|
||||
const event = request.headers['x-gitea-event'];
|
||||
|
||||
// Release-Events → Ankündigung + Changelog-Archiv
|
||||
if (event === 'release') {
|
||||
const payload = request.body;
|
||||
const repo = payload.repository?.full_name ?? 'unbekannt';
|
||||
if (repoIgnored(repo) || !payload.release) {
|
||||
return { ok: true, ignored: `release ${repo}` };
|
||||
}
|
||||
const release = {
|
||||
repo,
|
||||
tag: payload.release.tag_name ?? 'unbekannt',
|
||||
name: payload.release.name ?? null,
|
||||
body: payload.release.body ?? null,
|
||||
url: payload.release.html_url ?? null,
|
||||
prerelease: payload.release.prerelease ? 1 : 0,
|
||||
published_at: payload.release.published_at ?? new Date().toISOString(),
|
||||
};
|
||||
const isNew = saveRelease(release);
|
||||
request.log.info(`Release ${release.tag} in ${repo} (${payload.action}, neu: ${isNew})`);
|
||||
|
||||
// Nur neue Releases ankündigen — Updates/Löschungen nur still archivieren
|
||||
let posted = false;
|
||||
if (isNew && payload.action === 'published') {
|
||||
try {
|
||||
posted = await postReleaseEmbed(client, {
|
||||
...release,
|
||||
prerelease: Boolean(release.prerelease),
|
||||
});
|
||||
} catch (error) {
|
||||
request.log.error({ err: error }, 'Release-Embed konnte nicht gepostet werden');
|
||||
}
|
||||
}
|
||||
return { ok: true, release: release.tag, posted };
|
||||
}
|
||||
|
||||
// Andere Events (Tag, Issue, …) freundlich ignorieren
|
||||
if (event !== 'push') {
|
||||
return { ok: true, ignored: event };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user