Release 202506101742

This commit is contained in:
pluja
2025-06-10 17:42:42 +00:00
parent 459d7c91f7
commit 812937d2c7
50 changed files with 1347 additions and 335 deletions

View File

@@ -0,0 +1,41 @@
import rss from '@astrojs/rss'
import { SITE_URL } from 'astro:env/client'
import { getEventTypeInfo } from '../../constants/eventTypes'
import { getEvents } from '../../lib/feeds'
import type { APIRoute } from 'astro'
export const GET: APIRoute = async (context) => {
try {
const origin = context.site?.origin ?? new URL(SITE_URL).origin
const result = await getEvents()
if (!result.success) return new Response(result.error.message, result.error.responseInit)
const { events } = result.data
return await rss({
title: 'KYCnot.me - Service Events',
description: 'Latest events and updates from privacy-focused services tracked on KYCnot.me',
site: origin,
xmlns: { atom: 'http://www.w3.org/2005/Atom' },
items: events.map((event) => {
const eventTypeInfo = getEventTypeInfo(event.type)
const isOngoing = !event.endedAt || event.endedAt > new Date()
const statusText = isOngoing ? 'Ongoing' : 'Resolved'
return {
title: `${event.service.name}: ${event.title}`,
pubDate: event.createdAt,
description: `${event.content}${event.source ? `\n\nSource: ${event.source}` : ''}`,
link: `/service/${event.service.slug}/#event-${String(event.id)}`,
categories: [eventTypeInfo.label, event.service.name, statusText],
}
}),
customData: `<language>en-us</language><atom:link href="${context.url.href}" rel="self" type="application/rss+xml"/>`,
})
} catch (error) {
console.error('Error generating events RSS feed:', error)
return new Response('Error generating RSS feed', { status: 500 })
}
}

View File

@@ -0,0 +1,87 @@
---
import { Icon } from 'astro-icon/components'
import MiniLayout from '../../layouts/MiniLayout.astro'
import { takeCounts } from '../../lib/feeds'
const user = Astro.locals.user
const feeds = [
{
title: user ? 'Your notifications' : 'User notifications',
description: `Last ${takeCounts.userNotifications.toLocaleString()} of your notifications`,
rss: user ? `/feeds/user/${user.feedId}/notifications.xml` : '/feeds/user/[feedId]/notifications.xml',
icon: 'ri:notification-line',
replacementDescription: user
? null
: "Replace [feedId] with the user feed ID, found in the user's notifications page",
},
{
title: 'Service comments',
description: `Last ${takeCounts.serviceComments.toLocaleString()} comments (and reviews) from a specific service`,
rss: '/feeds/service/[slug]/comments.xml',
icon: 'ri:chat-3-line',
replacementDescription: 'Replace [slug] with the actual service slug',
},
{
title: 'Service events',
description: `Last ${takeCounts.serviceEvents.toLocaleString()} events from a specific service`,
rss: '/feeds/service/[slug]/events.xml',
icon: 'ri:calendar-event-line',
replacementDescription: 'Replace [slug] with the actual service slug',
},
{
title: 'All events',
description: `Last ${takeCounts.allEvents.toLocaleString()} events from all listed services`,
rss: '/feeds/events.xml',
icon: 'ri:calendar-2-line',
replacementDescription: null,
},
] as const satisfies {
title: string
description: string
rss: `/feeds/${string}`
icon: string
replacementDescription: string | null
}[]
---
<MiniLayout
pageTitle="RSS Feeds"
description="Subscribe to RSS feeds to stay updated with the latest comments and events on KYCnot.me"
ogImage={{
template: 'generic',
title: 'RSS Feeds',
description: 'Subscribe to stay updated',
icon: 'ri:rss-line',
}}
layoutHeader={{
icon: 'ri:rss-line',
title: 'RSS Feeds',
subtitle: 'Copy the feed URL to your RSS reader',
}}
size="md"
>
<div class="space-y-8">
{
feeds.map((feed) => (
<div>
<div class="flex flex-row items-center justify-center gap-2">
<Icon name={feed.icon} class="inline-block size-6 shrink-0 text-white" />
<h2 class="text-left text-lg leading-tight font-bold text-white">{feed.title}</h2>
</div>
<p class="text-day-300 mt-1 text-center text-sm text-balance">{feed.description}</p>
<div
class="border-night-500 bg-night-600 relative mt-2 rounded-lg border px-4 py-2 font-mono break-all text-white"
set:text={`${Astro.url.origin}${feed.rss}`}
/>
{!!feed.replacementDescription && (
<p class="text-day-500 mt-1 text-center text-xs italic">{feed.replacementDescription}</p>
)}
</div>
))
}
</div>
</MiniLayout>

View File

@@ -0,0 +1,61 @@
import rss from '@astrojs/rss'
import { SITE_URL } from 'astro:env/client'
import { getServiceUserRoleInfo } from '../../../../constants/serviceUserRoles'
import { makeCommentUrl } from '../../../../lib/commentsWithReplies'
import { getCommentsForService } from '../../../../lib/feeds'
import type { APIRoute } from 'astro'
export const GET: APIRoute = async (context) => {
try {
const origin = context.site?.origin ?? new URL(SITE_URL).origin
const result = await getCommentsForService(context.params.slug)
if (!result.success) return new Response(result.error.message, result.error.responseInit)
const { service, comments } = result.data
return await rss({
title: `${service.name} - Comments & Reviews | KYCnot.me`,
description: `Latest comments and reviews about ${service.name} from KYCnot.me users`,
site: origin,
xmlns: { dc: 'http://purl.org/dc/elements/1.1/', atom: 'http://www.w3.org/2005/Atom' },
items: comments.map((comment) => {
const authorName = comment.author.displayName ?? comment.author.name
const isRating = comment.ratingActive && comment.rating
const title = isRating
? `${authorName} rated ${service.name} (${String(comment.rating)}/5 stars)`
: `${authorName} commented on ${service.name}`
const badges = [
comment.author.verified ? '✅' : null,
comment.author.spammer ? '(Spammer)' : null,
comment.author.admin ? '(Admin)' : null,
comment.author.moderator && !comment.author.admin ? '(Moderator)' : null,
...comment.author.serviceAffiliations.map(
(affiliation) =>
` (${getServiceUserRoleInfo(affiliation.role).label} at ${affiliation.service.name})`
),
].filter((badge) => badge !== null)
return {
title,
pubDate: comment.createdAt,
description: comment.content,
link: makeCommentUrl({
origin,
serviceSlug: service.slug,
commentId: comment.id,
}),
categories: isRating ? ['Rating'] : ['Comment'],
guid: `${service.slug}-comment-${String(comment.id)}`,
customData: `<dc:creator>${authorName}${badges.length > 0 ? ` ${badges.join(' ')}` : ''}</dc:creator>`,
}
}),
customData: `<language>en-us</language><atom:link href="${context.url.href}" rel="self" type="application/rss+xml"/>`,
})
} catch (error) {
console.error('Error generating service comments RSS feed:', error)
return new Response('Error generating RSS feed', { status: 500 })
}
}

View File

@@ -0,0 +1,41 @@
import rss from '@astrojs/rss'
import { SITE_URL } from 'astro:env/client'
import { getEventTypeInfo } from '../../../../constants/eventTypes'
import { getEventsForService } from '../../../../lib/feeds'
import type { APIRoute } from 'astro'
export const GET: APIRoute = async (context) => {
try {
const origin = context.site?.origin ?? new URL(SITE_URL).origin
const result = await getEventsForService(context.params.slug)
if (!result.success) return new Response(result.error.message, result.error.responseInit)
const { service, events } = result.data
return await rss({
title: `${service.name} - Events & Updates | KYCnot.me`,
description: `Latest events and updates for ${service.name} tracked on KYCnot.me`,
site: origin,
xmlns: { atom: 'http://www.w3.org/2005/Atom' },
items: events.map((event) => {
const eventTypeInfo = getEventTypeInfo(event.type)
const isOngoing = !event.endedAt || event.endedAt > new Date()
const statusText = isOngoing ? 'Ongoing' : 'Resolved'
return {
title: `${service.name}: ${event.title}`,
pubDate: event.createdAt,
description: `${event.content}${event.source ? `\n\nSource: ${event.source}` : ''}`,
link: `/service/${service.slug}/#event-${String(event.id)}`,
categories: [eventTypeInfo.label, statusText],
}
}),
customData: `<language>en-us</language><atom:link href="${context.url.href}" rel="self" type="application/rss+xml"/>`,
})
} catch (error) {
console.error('Error generating service events RSS feed:', error)
return new Response('Error generating RSS feed', { status: 500 })
}
}

View File

@@ -0,0 +1,52 @@
import rss from '@astrojs/rss'
import { SITE_URL } from 'astro:env/client'
import { getNotificationTypeInfo } from '../../../../constants/notificationTypes'
import { getUserNotifications } from '../../../../lib/feeds'
import {
makeNotificationActions,
makeNotificationContent,
makeNotificationTitle,
} from '../../../../lib/notifications'
import type { APIRoute } from 'astro'
export const GET: APIRoute = async (context) => {
try {
const origin = context.site?.origin ?? new URL(SITE_URL).origin
const feedId = context.params.feedId
const result = await getUserNotifications(feedId)
if (!result.success) return new Response(result.error.message, result.error.responseInit)
const { user, notifications } = result.data
const displayName = user.displayName ?? user.name
return await rss({
title: `${displayName}'s Notifications - KYCnot.me`,
description: `Notifications for ${displayName} on KYCnot.me - Privacy-focused service reviews and updates`,
site: origin,
xmlns: { atom: 'http://www.w3.org/2005/Atom' },
items: notifications.map((notification) => {
const typeInfo = getNotificationTypeInfo(notification.type)
const title = makeNotificationTitle(notification, user)
const description = makeNotificationContent(notification) ?? typeInfo.label
const actions = makeNotificationActions(notification, origin)
const link = actions[0]?.url ?? `${origin}/notifications`
return {
title,
pubDate: notification.createdAt,
description,
link,
categories: [typeInfo.label],
}
}),
customData: `<language>en-us</language><atom:link href="${context.url.href}" rel="self" type="application/rss+xml"/>`,
})
} catch (error) {
console.error('Error generating user notifications RSS feed:', error)
return new Response('Error generating RSS feed', { status: 500 })
}
}