Release 202507031117

This commit is contained in:
pluja
2025-07-03 11:17:39 +00:00
parent 5a54352d95
commit 3166349dfb
2 changed files with 147 additions and 149 deletions

View File

@@ -275,7 +275,7 @@ CREATE OR REPLACE FUNCTION handle_suggestion_status_change()
RETURNS TRIGGER AS $$ RETURNS TRIGGER AS $$
DECLARE DECLARE
service_name TEXT; service_name TEXT;
service_visibility "ServiceVisibility"; service_visibility "serviceVisibility";
is_user_admin_or_moderator BOOLEAN; is_user_admin_or_moderator BOOLEAN;
BEGIN BEGIN
-- Award karma for first approval -- Award karma for first approval
@@ -283,7 +283,7 @@ BEGIN
-- and ensure it wasn't already APPROVED. -- and ensure it wasn't already APPROVED.
IF OLD.status IS DISTINCT FROM 'APPROVED' AND NEW.status = 'APPROVED' THEN IF OLD.status IS DISTINCT FROM 'APPROVED' AND NEW.status = 'APPROVED' THEN
-- Fetch service details for the description -- Fetch service details for the description
SELECT name, visibility INTO service_name, service_visibility FROM "Service" WHERE id = NEW."serviceId"; SELECT name, serviceVisibility INTO service_name, service_visibility FROM "Service" WHERE id = NEW."serviceId";
-- Only award karma if the service is public -- Only award karma if the service is public
IF service_visibility = 'PUBLIC' THEN IF service_visibility = 'PUBLIC' THEN

View File

@@ -7,186 +7,184 @@ import { makeSearchFiltersOptions } from '../../lib/searchFiltersOptions'
import type { APIRoute } from 'astro' import type { APIRoute } from 'astro'
export const GET: APIRoute = async ({ site }) => { export const GET: APIRoute = async ({ site }) => {
if (!site) { if (!site) return new Response('Site URL not configured', { status: 500 })
return new Response('Site URL not configured', { status: 500 })
}
const searchUrls = await generateSEOSitemapUrls(site.href) try {
const searchUrls = await generateSEOSitemapUrls(site.href)
const result = ` const result = `
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${searchUrls.map((url) => `<url><loc>${he.encode(url)}</loc></url>`).join('\n')} ${searchUrls.map((url) => `<url><loc>${he.encode(url)}</loc></url>`).join('\n')}
</urlset> </urlset>
`.trim() `.trim()
return new Response(result, { return new Response(result, {
headers: { headers: {
'Content-Type': 'application/xml', 'Content-Type': 'application/xml',
}, },
}) })
} catch (error) {
console.error('Failed to generate SEO sitemap URLs:', error)
return new Response('Failed to generate SEO sitemap URLs', { status: 500 })
}
} }
async function generateSEOSitemapUrls(siteUrl: string) { async function generateSEOSitemapUrls(siteUrl: string) {
try { const [categories, attributes] = await Promise.all([
const [categories, attributes] = await Promise.all([ prisma.category.findMany({
prisma.category.findMany({ select: {
select: { name: true,
name: true, namePluralLong: true,
namePluralLong: true, slug: true,
slug: true, icon: true,
icon: true, _count: {
_count: { select: {
select: { services: {
services: { where: {
where: { serviceVisibility: { in: ['PUBLIC', 'ARCHIVED'] },
},
},
},
},
},
}),
prisma.attribute.findMany({
select: {
id: true,
slug: true,
title: true,
category: true,
type: true,
_count: {
select: {
services: {
where: {
service: {
serviceVisibility: { in: ['PUBLIC', 'ARCHIVED'] }, serviceVisibility: { in: ['PUBLIC', 'ARCHIVED'] },
}, },
}, },
}, },
}, },
}, },
}), },
prisma.attribute.findMany({ orderBy: [{ category: 'asc' }, { type: 'asc' }, { title: 'asc' }],
select: { }),
id: true, ])
slug: true,
title: true,
category: true,
type: true,
_count: {
select: {
services: {
where: {
service: {
serviceVisibility: { in: ['PUBLIC', 'ARCHIVED'] },
},
},
},
},
},
},
orderBy: [{ category: 'asc' }, { type: 'asc' }, { title: 'asc' }],
}),
])
const filtersOptions = makeSearchFiltersOptions({ const filtersOptions = makeSearchFiltersOptions({
filters: null, filters: null,
categories, categories,
attributes, attributes,
}) })
const byCategory = filtersOptions.categories.map( const byCategory = filtersOptions.categories.map(
(category) => (category) =>
new URLSearchParams({
categories: category.slug,
})
)
const byVerificationStatus = filtersOptions.verification.map(
(status) =>
new URLSearchParams({
verification: status.slug,
})
)
const byKycLevel = filtersOptions.kycLevels.map(
(level) =>
new URLSearchParams({
'max-kyc': level.id,
})
)
const byCurrency = filtersOptions.currencies.map(
(currency) =>
new URLSearchParams({
currencies: currency.slug,
})
)
const withOneAttribute = filtersOptions.attributesByCategory
.flatMap(({ attributes }) => attributes)
.map(
(attribute) =>
new URLSearchParams({ new URLSearchParams({
categories: category.slug, [`attr-${attribute.id.toString()}`]: 'yes',
})
)
const withoutOneAttribute = filtersOptions.attributesByCategory
.flatMap(({ attributes }) => attributes)
.map(
(attribute) =>
new URLSearchParams({
[`attr-${attribute.id.toString()}`]: 'no',
}) })
) )
const byVerificationStatus = filtersOptions.verification.map( const byCategoryAndCurrency = filtersOptions.categories.flatMap((category) =>
(status) => filtersOptions.currencies.map(
new URLSearchParams({
verification: status.slug,
})
)
const byKycLevel = filtersOptions.kycLevels.map(
(level) =>
new URLSearchParams({
'max-kyc': level.id,
})
)
const byCurrency = filtersOptions.currencies.map(
(currency) => (currency) =>
new URLSearchParams({ new URLSearchParams({
categories: category.slug,
currencies: currency.slug, currencies: currency.slug,
}) })
) )
)
const withOneAttribute = filtersOptions.attributesByCategory const byCategoryAndAttributes = filtersOptions.categories.flatMap((category) =>
filtersOptions.attributesByCategory
.flatMap(({ attributes }) => attributes) .flatMap(({ attributes }) => attributes)
.map( .flatMap((attribute) => [
(attribute) => new URLSearchParams({
new URLSearchParams({ categories: category.slug,
[`attr-${attribute.id.toString()}`]: 'yes', [`attr-${attribute.id.toString()}`]: 'yes',
}) }),
) new URLSearchParams({
const withoutOneAttribute = filtersOptions.attributesByCategory categories: category.slug,
[`attr-${attribute.id.toString()}`]: 'no',
}),
])
)
const relevantCurrencies = [
'xmr',
'btc',
] as const satisfies (typeof filtersOptions.currencies)[number]['slug'][]
const byCategoryAndAttributesAndRelevantCurrency = filtersOptions.categories.flatMap((category) =>
filtersOptions.attributesByCategory
.flatMap(({ attributes }) => attributes) .flatMap(({ attributes }) => attributes)
.map( .flatMap((attribute) =>
(attribute) => relevantCurrencies.map(
new URLSearchParams({ (currency) =>
[`attr-${attribute.id.toString()}`]: 'no', new URLSearchParams({
}) categories: category.slug,
) currencies: currency,
[`attr-${attribute.id.toString()}`]:
const byCategoryAndCurrency = filtersOptions.categories.flatMap((category) => attribute.type === 'GOOD' || attribute.type === 'INFO' ? 'yes' : 'no',
filtersOptions.currencies.map( })
(currency) =>
new URLSearchParams({
categories: category.slug,
currencies: currency.slug,
})
)
)
const byCategoryAndAttributes = filtersOptions.categories.flatMap((category) =>
filtersOptions.attributesByCategory
.flatMap(({ attributes }) => attributes)
.flatMap((attribute) => [
new URLSearchParams({
categories: category.slug,
[`attr-${attribute.id.toString()}`]: 'yes',
}),
new URLSearchParams({
categories: category.slug,
[`attr-${attribute.id.toString()}`]: 'no',
}),
])
)
const relevantCurrencies = [
'xmr',
'btc',
] as const satisfies (typeof filtersOptions.currencies)[number]['slug'][]
const byCategoryAndAttributesAndRelevantCurrency = filtersOptions.categories.flatMap((category) =>
filtersOptions.attributesByCategory
.flatMap(({ attributes }) => attributes)
.flatMap((attribute) =>
relevantCurrencies.map(
(currency) =>
new URLSearchParams({
categories: category.slug,
currencies: currency,
[`attr-${attribute.id.toString()}`]:
attribute.type === 'GOOD' || attribute.type === 'INFO' ? 'yes' : 'no',
})
)
) )
) )
)
const allQueryParams = [ const allQueryParams = [
...byCategory, ...byCategory,
...byVerificationStatus, ...byVerificationStatus,
...byKycLevel, ...byKycLevel,
...byCurrency, ...byCurrency,
...withOneAttribute, ...withOneAttribute,
...withoutOneAttribute, ...withoutOneAttribute,
...byCategoryAndCurrency, ...byCategoryAndCurrency,
...byCategoryAndAttributes, ...byCategoryAndAttributes,
...byCategoryAndAttributesAndRelevantCurrency, ...byCategoryAndAttributesAndRelevantCurrency,
] satisfies URLSearchParams[] ] satisfies URLSearchParams[]
return allQueryParams.map((queryParams) => { return allQueryParams.map((queryParams) => {
const url = new URL(siteUrl) const url = new URL(siteUrl)
url.search = queryParams.toString() url.search = queryParams.toString()
return url.href return url.href
}) })
} catch (error) {
console.error('Failed to generate SEO sitemap URLs:', error)
return []
}
} }