Release 202506111039

This commit is contained in:
pluja
2025-06-11 10:39:20 +00:00
parent d43402e162
commit 99cb730bc0
3 changed files with 50 additions and 36 deletions

View File

@@ -333,28 +333,32 @@ def remove_service_attribute_by_slug(service_id: int, attribute_slug: str) -> bo
def save_tos_review(service_id: int, review: Optional[TosReviewType]): def save_tos_review(service_id: int, review: Optional[TosReviewType]):
""" """Persist a TOS review and/or update the timestamp for a service.
Save a TOS review for a specific service.
Args: If *review* is ``None`` the existing review (if any) is preserved while
service_id: The ID of the service. only the ``tosReviewAt`` column is updated. This ensures we still track
review: A TypedDict containing the review data. when the review task last ran even if the review generation failed or
produced no changes.
""" """
try: try:
# Only serialize to JSON if review is not None
review_json = json.dumps(review) if review is not None else None
with get_db_connection() as conn: with get_db_connection() as conn:
with conn.cursor(row_factory=dict_row) as cursor: with conn.cursor(row_factory=dict_row) as cursor:
cursor.execute( if review is None:
""" cursor.execute(
UPDATE "Service" 'UPDATE "Service" SET "tosReviewAt" = NOW() WHERE id = %s AND "tosReview" IS NULL',
SET "tosReview" = %s, "tosReviewAt" = NOW() (service_id,),
WHERE id = %s )
""", else:
(review_json, service_id), review_json = json.dumps(review)
) cursor.execute(
'UPDATE "Service" SET "tosReview" = %s, "tosReviewAt" = NOW() WHERE id = %s',
(review_json, service_id),
)
conn.commit() conn.commit()
logger.info(f"Successfully saved TOS review for service {service_id}") logger.info(
f"Successfully saved TOS review (updated={review is not None}) for service {service_id}"
)
except Exception as e: except Exception as e:
logger.error(f"Error saving TOS review for service {service_id}: {e}") logger.error(f"Error saving TOS review for service {service_id}: {e}")

View File

@@ -11,6 +11,7 @@ type EventTypeInfo<T extends string | null | undefined = string> = {
description: string description: string
classNames: { classNames: {
dot: string dot: string
banner?: string
} }
icon: string icon: string
color: TailwindColor color: TailwindColor
@@ -34,6 +35,7 @@ export const {
description: '', description: '',
classNames: { classNames: {
dot: 'bg-zinc-700 text-zinc-300 ring-zinc-700/50', dot: 'bg-zinc-700 text-zinc-300 ring-zinc-700/50',
banner: 'bg-zinc-900/50 text-zinc-300 hover:bg-zinc-800/60 focus-visible:bg-zinc-800/60',
}, },
icon: 'ri:question-fill', icon: 'ri:question-fill',
color: 'gray', color: 'gray',
@@ -48,6 +50,7 @@ export const {
description: 'Potential issues that users should be aware of', description: 'Potential issues that users should be aware of',
classNames: { classNames: {
dot: 'bg-amber-900 text-amber-300 ring-amber-900/50', dot: 'bg-amber-900 text-amber-300 ring-amber-900/50',
banner: 'bg-yellow-900/50 text-yellow-300 hover:bg-yellow-800/60 focus-visible:bg-yellow-800/60',
}, },
icon: 'ri:alert-fill', icon: 'ri:alert-fill',
color: 'yellow', color: 'yellow',
@@ -61,6 +64,7 @@ export const {
description: 'A previously reported warning has been solved', description: 'A previously reported warning has been solved',
classNames: { classNames: {
dot: 'bg-amber-900 text-amber-300 ring-amber-900/50', dot: 'bg-amber-900 text-amber-300 ring-amber-900/50',
banner: 'bg-yellow-900/50 text-yellow-300 hover:bg-yellow-800/60 focus-visible:bg-yellow-800/60',
}, },
icon: 'ri:alert-fill', icon: 'ri:alert-fill',
color: 'green', color: 'green',
@@ -74,6 +78,7 @@ export const {
description: 'Critical issues affecting service functionality', description: 'Critical issues affecting service functionality',
classNames: { classNames: {
dot: 'bg-red-900 text-red-300 ring-red-900/50', dot: 'bg-red-900 text-red-300 ring-red-900/50',
banner: 'bg-red-900/50 text-red-300 hover:bg-red-800/60 focus-visible:bg-red-800/60',
}, },
icon: 'ri:spam-fill', icon: 'ri:spam-fill',
color: 'red', color: 'red',
@@ -87,6 +92,7 @@ export const {
description: 'A previously reported alert has been solved', description: 'A previously reported alert has been solved',
classNames: { classNames: {
dot: 'bg-red-900 text-red-300 ring-red-900/50', dot: 'bg-red-900 text-red-300 ring-red-900/50',
banner: 'bg-red-900/50 text-red-300 hover:bg-red-800/60 focus-visible:bg-red-800/60',
}, },
icon: 'ri:spam-fill', icon: 'ri:spam-fill',
color: 'green', color: 'green',
@@ -100,6 +106,7 @@ export const {
description: 'General information about the service', description: 'General information about the service',
classNames: { classNames: {
dot: 'bg-blue-900 text-blue-300 ring-blue-900/50', dot: 'bg-blue-900 text-blue-300 ring-blue-900/50',
banner: 'bg-blue-900/50 text-blue-300 hover:bg-blue-800/60 focus-visible:bg-blue-800/60',
}, },
icon: 'ri:information-fill', icon: 'ri:information-fill',
color: 'sky', color: 'sky',
@@ -113,6 +120,7 @@ export const {
description: 'Regular service update or announcement', description: 'Regular service update or announcement',
classNames: { classNames: {
dot: 'bg-zinc-700 text-zinc-300 ring-zinc-700/50', dot: 'bg-zinc-700 text-zinc-300 ring-zinc-700/50',
banner: 'bg-zinc-900/50 text-zinc-300 hover:bg-zinc-800/60 focus-visible:bg-zinc-800/60',
}, },
icon: 'ri:notification-fill', icon: 'ri:notification-fill',
color: 'green', color: 'green',
@@ -126,6 +134,7 @@ export const {
description: 'Service details were updated on kycnot.me', description: 'Service details were updated on kycnot.me',
classNames: { classNames: {
dot: 'bg-sky-900 text-sky-300 ring-sky-900/50', dot: 'bg-sky-900 text-sky-300 ring-sky-900/50',
banner: 'bg-sky-900/50 text-sky-300 hover:bg-sky-800/60 focus-visible:bg-sky-800/60',
}, },
icon: 'ri:pencil-fill', icon: 'ri:pencil-fill',
color: 'sky', color: 'sky',

View File

@@ -407,9 +407,12 @@ const ogImageTemplateData = {
const serviceVisibilityInfo = getServiceVisibilityInfo(service.serviceVisibility) const serviceVisibilityInfo = getServiceVisibilityInfo(service.serviceVisibility)
const activeAlertOrWarningEvents = service.events.filter( const activeAlertOrWarningEvents = service.events
(event) => getEventTypeInfo(event.type).showBanner && (event.endedAt === null || event.endedAt >= now) .map((event) => ({
) ...event,
typeInfo: getEventTypeInfo(event.type),
}))
.filter((event) => event.typeInfo.showBanner && (event.endedAt === null || event.endedAt >= now))
const activeEventToShow = const activeEventToShow =
activeAlertOrWarningEvents.find((event) => event.type === EventType.ALERT) ?? activeAlertOrWarningEvents[0] activeAlertOrWarningEvents.find((event) => event.type === EventType.ALERT) ?? activeAlertOrWarningEvents[0]
--- ---
@@ -518,15 +521,10 @@ const activeEventToShow =
href="#events" href="#events"
class={cn( class={cn(
'group mb-4 block rounded-md px-3 py-2 text-sm transition-colors duration-200', 'group mb-4 block rounded-md px-3 py-2 text-sm transition-colors duration-200',
activeEventToShow.type === EventType.ALERT activeEventToShow.typeInfo.classNames.banner
? 'bg-red-900/50 text-red-300 hover:bg-red-800/60 focus-visible:bg-red-800/60'
: 'bg-yellow-900/50 text-yellow-300 hover:bg-yellow-800/60 focus-visible:bg-yellow-800/60'
)} )}
> >
<Icon <Icon name={activeEventToShow.typeInfo.icon} class="me-1.5 inline-block size-4 align-[-0.15em]" />
name={activeEventToShow.type === EventType.ALERT ? 'ri:alert-fill' : 'ri:alarm-warning-fill'}
class="me-1.5 inline-block size-4 align-[-0.15em]"
/>
<span class="font-bold">{activeEventToShow.title}</span> — {activeEventToShow.content} <span class="font-bold">{activeEventToShow.title}</span> — {activeEventToShow.content}
{activeAlertOrWarningEvents.length >= 2 && <>+{activeAlertOrWarningEvents.length - 1} more events.</>} {activeAlertOrWarningEvents.length >= 2 && <>+{activeAlertOrWarningEvents.length - 1} more events.</>}
<span class="underline">Go to events</span> <span class="underline">Go to events</span>
@@ -1070,6 +1068,9 @@ const activeEventToShow =
) )
} }
<p class="text-day-400 mt-3 text-center text-xs">
<span class="hover:text-day-200 transition-colors">Overall = 60% Privacy + 40% Trust (Rounded)</span>
</p>
<div class="xs:gap-x-6 mt-2 flex flex-wrap justify-center gap-x-4 gap-y-2 text-xs"> <div class="xs:gap-x-6 mt-2 flex flex-wrap justify-center gap-x-4 gap-y-2 text-xs">
<a <a
href="/about#service-scores" href="/about#service-scores"
@@ -1083,7 +1084,7 @@ const activeEventToShow =
class="text-day-400 hover:text-day-200 inline-flex items-center gap-1 transition-colors hover:underline" class="text-day-400 hover:text-day-200 inline-flex items-center gap-1 transition-colors hover:underline"
> >
<Icon name="ri:information-line" class="size-3" /> <Icon name="ri:information-line" class="size-3" />
Attributes list All attributes list
</a> </a>
</div> </div>
@@ -1194,15 +1195,15 @@ const activeEventToShow =
<p class="text-day-500 mt-1 text-sm text-balance"> <p class="text-day-500 mt-1 text-sm text-balance">
Maybe due to captchas, client side rendering, DDoS protections, or non-text format. Maybe due to captchas, client side rendering, DDoS protections, or non-text format.
</p> </p>
{service.tosUrls.length > 0 && ( <p class="mt-2 text-xs">
<p class="mt-2 text-xs"> Reviewed <TimeFormatted date={service.tosReviewAt} hourPrecision />
{service.tosUrls.map((url) => ( {service.tosUrls.length > 0 && 'from'}
<a href={url} class="hover:underline"> {service.tosUrls.map((url) => (
{urlDomain(url)} <a href={url} class="hover:underline">
</a> {urlDomain(url)}
))} </a>
</p> ))}
)} </p>
</div> </div>
) )
) : ( ) : (