Compare commits
2 Commits
release-47
...
release-49
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e17bc8a521 | ||
|
|
ec1215f2ae |
@@ -0,0 +1,5 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Service" ADD COLUMN "previousSlugs" TEXT[] DEFAULT ARRAY[]::TEXT[];
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Service_previousSlugs_idx" ON "Service"("previousSlugs");
|
||||
@@ -336,6 +336,7 @@ model Service {
|
||||
id Int @id @default(autoincrement())
|
||||
name String
|
||||
slug String @unique
|
||||
previousSlugs String[] @default([])
|
||||
description String
|
||||
categories Category[] @relation("ServiceToCategory")
|
||||
kycLevel Int @default(4)
|
||||
@@ -396,6 +397,7 @@ model Service {
|
||||
@@index([createdAt])
|
||||
@@index([updatedAt])
|
||||
@@index([slug])
|
||||
@@index([previousSlugs])
|
||||
}
|
||||
|
||||
model ServiceContactMethod {
|
||||
|
||||
@@ -612,6 +612,7 @@ const generateFakeService = (users: User[]) => {
|
||||
return {
|
||||
name,
|
||||
slug,
|
||||
previousSlugs: faker.helpers.maybe(() => [`${slug}-old`], { probability: 0.5 }),
|
||||
description: faker.helpers.arrayElement(serviceDescriptions),
|
||||
kycLevel: faker.helpers.arrayElement(kycLevels.map((level) => level.value)),
|
||||
overallScore: 0,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Currency, ServiceVisibility, VerificationStatus } from '@prisma/client'
|
||||
import { z } from 'astro/zod'
|
||||
import { ActionError } from 'astro:actions'
|
||||
import { uniq } from 'lodash-es'
|
||||
import slugify from 'slugify'
|
||||
|
||||
import { defineProtectedAction } from '../../lib/defineProtectedAction'
|
||||
@@ -164,11 +165,22 @@ export const adminServiceActions = {
|
||||
|
||||
const existingService = await prisma.service.findUnique({
|
||||
where: { id: input.id },
|
||||
include: {
|
||||
categories: true,
|
||||
select: {
|
||||
slug: true,
|
||||
previousSlugs: true,
|
||||
categories: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
attributes: {
|
||||
include: {
|
||||
attribute: true,
|
||||
select: {
|
||||
attributeId: true,
|
||||
attribute: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -213,6 +225,14 @@ export const adminServiceActions = {
|
||||
serviceVisibility: input.serviceVisibility,
|
||||
slug: input.slug,
|
||||
overallScore: input.overallScore,
|
||||
previousSlugs:
|
||||
existingService.slug !== input.slug
|
||||
? {
|
||||
set: uniq([...existingService.previousSlugs, existingService.slug]).filter(
|
||||
(slug) => slug !== input.slug
|
||||
),
|
||||
}
|
||||
: undefined,
|
||||
|
||||
imageUrl,
|
||||
categories: {
|
||||
|
||||
@@ -44,7 +44,30 @@ export const apiServiceActions = {
|
||||
.flatMap((url) => [url, url.endsWith('/') ? url.slice(0, -1) : `${url}/`])
|
||||
: undefined
|
||||
|
||||
const service = await prisma.service.findFirst({
|
||||
const select = {
|
||||
id: true,
|
||||
name: true,
|
||||
slug: true,
|
||||
description: true,
|
||||
kycLevel: true,
|
||||
verificationStatus: true,
|
||||
categories: {
|
||||
select: {
|
||||
name: true,
|
||||
slug: true,
|
||||
},
|
||||
},
|
||||
serviceUrls: true,
|
||||
onionUrls: true,
|
||||
i2pUrls: true,
|
||||
tosUrls: true,
|
||||
referral: true,
|
||||
listedAt: true,
|
||||
verifiedAt: true,
|
||||
serviceVisibility: true,
|
||||
} as const satisfies Prisma.ServiceSelect
|
||||
|
||||
let service = await prisma.service.findFirst({
|
||||
where: {
|
||||
listedAt: { lte: new Date() },
|
||||
serviceVisibility: { in: ['PUBLIC', 'ARCHIVED', 'UNLISTED'] },
|
||||
@@ -61,30 +84,21 @@ export const apiServiceActions = {
|
||||
: []),
|
||||
],
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
slug: true,
|
||||
description: true,
|
||||
kycLevel: true,
|
||||
verificationStatus: true,
|
||||
categories: {
|
||||
select: {
|
||||
name: true,
|
||||
slug: true,
|
||||
},
|
||||
},
|
||||
serviceUrls: true,
|
||||
onionUrls: true,
|
||||
i2pUrls: true,
|
||||
tosUrls: true,
|
||||
referral: true,
|
||||
listedAt: true,
|
||||
verifiedAt: true,
|
||||
serviceVisibility: true,
|
||||
},
|
||||
select,
|
||||
})
|
||||
|
||||
if (!service && input.slug) {
|
||||
service = await prisma.service.findFirst({
|
||||
where: {
|
||||
listedAt: { lte: new Date() },
|
||||
serviceVisibility: { in: ['PUBLIC', 'ARCHIVED', 'UNLISTED'] },
|
||||
|
||||
previousSlugs: { has: input.slug },
|
||||
},
|
||||
select,
|
||||
})
|
||||
}
|
||||
|
||||
if (
|
||||
!service ||
|
||||
(service.serviceVisibility !== 'PUBLIC' &&
|
||||
|
||||
@@ -102,7 +102,7 @@ const commentUrl = makeCommentUrl({ serviceSlug, commentId: comment.id, origin:
|
||||
{...htmlProps}
|
||||
id={`comment-${comment.id.toString()}`}
|
||||
class={cn([
|
||||
'group',
|
||||
'group bg-night-700',
|
||||
depth > 0 && 'ml-3 border-b-0 border-l border-zinc-800 pt-2 pl-2 sm:ml-4',
|
||||
comment.author.serviceAffiliations.some((affiliation) => affiliation.service.slug === serviceSlug) &&
|
||||
'bg-[#182a1f]',
|
||||
@@ -270,12 +270,6 @@ const commentUrl = makeCommentUrl({ serviceSlug, commentId: comment.id, origin:
|
||||
|
||||
{comment.suspicious && <BadgeSmall icon="ri:spam-2-fill" color="red" text="Potential SPAM" inlineIcon />}
|
||||
|
||||
{
|
||||
comment.requiresAdminReview && isAuthorOrPrivileged && (
|
||||
<BadgeSmall icon="ri:alert-fill" color="yellow" text="Reported" inlineIcon />
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
comment.rating !== null && !comment.parentId && (
|
||||
<Tooltip
|
||||
@@ -320,6 +314,19 @@ const commentUrl = makeCommentUrl({ serviceSlug, commentId: comment.id, origin:
|
||||
color={commentStatusById.REJECTED.color}
|
||||
text={commentStatusById.REJECTED.label}
|
||||
inlineIcon
|
||||
endIcon="ri:lock-line"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
comment.requiresAdminReview && isAuthorOrPrivileged && (
|
||||
<BadgeSmall
|
||||
icon="ri:alert-fill"
|
||||
color="yellow"
|
||||
text="Needs admin review"
|
||||
inlineIcon
|
||||
endIcon="ri:lock-line"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ if (!user || !user.admin || !user.moderator) return null
|
||||
data-comment-id={comment.id}
|
||||
data-user-id={user.id}
|
||||
>
|
||||
{comment.requiresAdminReview ? 'No Admin Review' : 'Admin Review'}
|
||||
{comment.requiresAdminReview ? 'No Admin Review' : 'Needs Admin Review'}
|
||||
</button>
|
||||
|
||||
<button
|
||||
|
||||
@@ -92,7 +92,7 @@ const userCommentsDisabled = user ? user.karmaUnlocks.commentsDisabled : false
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<InputRating name="rating" label="Rating" />
|
||||
|
||||
<InputWrapper label="Tags" name="tags">
|
||||
<InputWrapper label="I experienced..." name="tags">
|
||||
<label class="flex cursor-pointer items-center gap-2">
|
||||
<input type="checkbox" name="issueKycRequested" class="text-red-400" />
|
||||
<span class="flex items-center gap-1 text-xs text-red-400">
|
||||
|
||||
@@ -87,6 +87,25 @@ function makeLink(url: string, referral: string | null) {
|
||||
}
|
||||
}
|
||||
|
||||
const bitcointalkMatch = /^(?:https?:\/\/)?(?:www\.)?bitcointalk\.org$/.exec(hostname)
|
||||
if (bitcointalkMatch) {
|
||||
return {
|
||||
type: 'clearnet' as const,
|
||||
url: urlWithReferral,
|
||||
textBits: [
|
||||
{
|
||||
style: 'normal',
|
||||
text: 'BitcoinTalk ',
|
||||
},
|
||||
{
|
||||
style: 'irrelevant',
|
||||
text: 'thread',
|
||||
},
|
||||
],
|
||||
icon: networksBySlug.clearnet.icon,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'clearnet' as const,
|
||||
url: urlWithReferral,
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
verificationStepStatuses,
|
||||
} from '../../../../constants/verificationStepStatus'
|
||||
import BaseLayout from '../../../../layouts/BaseLayout.astro'
|
||||
import { DEPLOYMENT_MODE } from '../../../../lib/envVariables'
|
||||
import { makeAdminApiCallInfo } from '../../../../lib/makeAdminApiCallInfo'
|
||||
import { pluralize } from '../../../../lib/pluralize'
|
||||
import { prisma } from '../../../../lib/prisma'
|
||||
@@ -182,7 +183,21 @@ const [service, categories, attributes] = await Astro.locals.banners.tryMany([
|
||||
],
|
||||
])
|
||||
|
||||
if (!service) return Astro.rewrite('/404')
|
||||
if (!service) {
|
||||
try {
|
||||
const serviceWithOldSlug = await prisma.service.findFirst({
|
||||
where: { previousSlugs: { has: slug } },
|
||||
select: { slug: true },
|
||||
})
|
||||
if (serviceWithOldSlug) {
|
||||
return Astro.redirect(`/admin/services/${serviceWithOldSlug.slug}/edit`, 301)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
return Astro.rewrite('/404')
|
||||
}
|
||||
|
||||
const apiCalls = await Astro.locals.banners.try(
|
||||
'Error fetching api calls',
|
||||
@@ -263,7 +278,9 @@ const apiCalls = await Astro.locals.banners.try(
|
||||
|
||||
<InputText
|
||||
label="Slug"
|
||||
description="Auto-generated if empty"
|
||||
description={`Auto-generated if empty. ${
|
||||
service.previousSlugs.length > 0 ? `Old slugs: ${service.previousSlugs.join(', ')}` : ''
|
||||
}`}
|
||||
name="slug"
|
||||
inputProps={{
|
||||
value: service.slug,
|
||||
@@ -1118,6 +1135,14 @@ const apiCalls = await Astro.locals.banners.try(
|
||||
</FormSection>
|
||||
|
||||
<FormSection title="API">
|
||||
{
|
||||
DEPLOYMENT_MODE === 'staging' && (
|
||||
<p class="rounded-lg bg-red-900/30 p-4 text-sm text-red-200">
|
||||
<Icon name="ri:alert-line" class="inline-block size-4 align-[-0.2em] text-red-400" />
|
||||
This endpoints section doesn't work in PRE. Use curl commands instead.
|
||||
</p>
|
||||
)
|
||||
}
|
||||
{
|
||||
apiCalls.map((call) => (
|
||||
<FormSubSection title={`${call.method} ${call.path}`}>
|
||||
|
||||
@@ -67,7 +67,11 @@ const [service, dbNotificationPreferences] = await Astro.locals.banners.tryMany(
|
||||
'Error fetching service',
|
||||
async () =>
|
||||
prisma.service.findUnique({
|
||||
where: { slug },
|
||||
where: {
|
||||
slug,
|
||||
serviceVisibility: { in: ['PUBLIC', 'UNLISTED', 'ARCHIVED'] },
|
||||
listedAt: { lte: new Date() },
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
slug: true,
|
||||
@@ -219,6 +223,34 @@ const [service, dbNotificationPreferences] = await Astro.locals.banners.tryMany(
|
||||
],
|
||||
])
|
||||
|
||||
if (!service) {
|
||||
try {
|
||||
const serviceWithOldSlug = await prisma.service.findFirst({
|
||||
where: {
|
||||
previousSlugs: { has: slug },
|
||||
serviceVisibility: { in: ['PUBLIC', 'UNLISTED', 'ARCHIVED'] },
|
||||
listedAt: { lte: new Date() },
|
||||
},
|
||||
select: { slug: true },
|
||||
})
|
||||
if (serviceWithOldSlug) {
|
||||
return Astro.redirect(`/service/${serviceWithOldSlug.slug}`, 301)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
return Astro.rewrite('/404')
|
||||
}
|
||||
|
||||
if (
|
||||
service.serviceVisibility !== 'PUBLIC' &&
|
||||
service.serviceVisibility !== 'UNLISTED' &&
|
||||
service.serviceVisibility !== 'ARCHIVED'
|
||||
) {
|
||||
return Astro.rewrite('/404')
|
||||
}
|
||||
|
||||
const makeWatchingDetails = (
|
||||
dbNotificationPreferences: Prisma.NotificationPreferencesGetPayload<{
|
||||
select: {
|
||||
@@ -254,17 +286,7 @@ const makeWatchingDetails = (
|
||||
} as const
|
||||
}
|
||||
|
||||
const watchingDetails = makeWatchingDetails(dbNotificationPreferences, service?.id)
|
||||
|
||||
if (!service) return Astro.rewrite('/404')
|
||||
|
||||
if (
|
||||
service.serviceVisibility !== 'PUBLIC' &&
|
||||
service.serviceVisibility !== 'UNLISTED' &&
|
||||
service.serviceVisibility !== 'ARCHIVED'
|
||||
) {
|
||||
return Astro.rewrite('/404')
|
||||
}
|
||||
const watchingDetails = makeWatchingDetails(dbNotificationPreferences, service.id)
|
||||
|
||||
const statusIcon = {
|
||||
...verificationStatusesByValue,
|
||||
|
||||
Reference in New Issue
Block a user