diff --git a/.cursor/rules/database.mdc b/.cursor/rules/database.mdc index a9bda76..0bca92b 100644 --- a/.cursor/rules/database.mdc +++ b/.cursor/rules/database.mdc @@ -5,6 +5,7 @@ alwaysApply: false --- - We use Prisma as ORM. - Remember to check the prisma schema [schema.prisma](mdc:web/prisma/schema.prisma) when doing things related to the database. +- After making changes to the [schema.prisma](mdc:web/prisma/schema.prisma) database or [faker.ts](mdc:web/scripts/faker.ts), you can run `npm run db-reset` (from `/web/` folder) [package.json](mdc:web/package.json). - Import the types from prisma instead of hardcoding duplicates. Specially use the Prisma.___GetPayload type and the enums. Like this: ```ts type Props = { diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e69de29 diff --git a/web/package.json b/web/package.json index 180a113..355ba84 100644 --- a/web/package.json +++ b/web/package.json @@ -12,7 +12,7 @@ "db-push": "prisma migrate dev", "db-triggers": "just import-triggers", "db-update": "prisma migrate dev && just import-triggers", - "db-reset": "prisma migrate reset && prisma migrate dev && just import-triggers && tsx scripts/faker.ts", + "db-reset": "prisma migrate reset -f && prisma migrate dev && just import-triggers && tsx scripts/faker.ts", "db-fill": "tsx scripts/faker.ts", "db-fill-clean": "tsx scripts/faker.ts --cleanup", "format": "prettier --write .", diff --git a/web/prisma/migrations/20250525101939_archieved/migration.sql b/web/prisma/migrations/20250525101939_archieved/migration.sql new file mode 100644 index 0000000..0fc8b63 --- /dev/null +++ b/web/prisma/migrations/20250525101939_archieved/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "ServiceVisibility" ADD VALUE 'ARCHIVED'; diff --git a/web/prisma/schema.prisma b/web/prisma/schema.prisma index eb17ec9..c5f719a 100644 --- a/web/prisma/schema.prisma +++ b/web/prisma/schema.prisma @@ -87,6 +87,7 @@ enum ServiceVisibility { PUBLIC UNLISTED HIDDEN + ARCHIVED } enum Currency { diff --git a/web/scripts/faker.ts b/web/scripts/faker.ts index 20937de..f13c83f 100755 --- a/web/scripts/faker.ts +++ b/web/scripts/faker.ts @@ -2,19 +2,20 @@ import crypto from 'crypto' import { faker } from '@faker-js/faker' import { + AnnouncementType, AttributeCategory, AttributeType, CommentStatus, Currency, + EventType, PrismaClient, ServiceSuggestionStatus, ServiceSuggestionType, + ServiceUserRole, VerificationStatus, type Prisma, - EventType, type User, - ServiceUserRole, - AnnouncementType, + type ServiceVisibility, } from '@prisma/client' import { uniqBy } from 'lodash-es' import { generateUsername } from 'unique-username-generator' @@ -611,7 +612,12 @@ const generateFakeEvent = (serviceId: number) => { } const generateFakeService = (users: User[]) => { - const status = faker.helpers.arrayElement(Object.values(VerificationStatus)) + const status = faker.helpers.weightedArrayElement([ + { weight: 20, value: 'VERIFICATION_SUCCESS' }, + { weight: 30, value: 'APPROVED' }, + { weight: 40, value: 'COMMUNITY_CONTRIBUTED' }, + { weight: 10, value: 'VERIFICATION_FAILED' }, + ]) const name = faker.helpers.arrayElement(serviceNames) const slug = `${faker.helpers.slugify(name).toLowerCase()}-${faker.string.alphanumeric({ length: 6, casing: 'lower' })}` @@ -623,6 +629,12 @@ const generateFakeService = (users: User[]) => { overallScore: 0, privacyScore: 0, trustScore: 0, + serviceVisibility: faker.helpers.weightedArrayElement([ + { weight: 80, value: 'PUBLIC' }, + { weight: 10, value: 'UNLISTED' }, + { weight: 5, value: 'HIDDEN' }, + { weight: 5, value: 'ARCHIVED' }, + ]), verificationStatus: status, verificationSummary: status === 'VERIFICATION_SUCCESS' || status === 'VERIFICATION_FAILED' ? faker.lorem.paragraph() : null, diff --git a/web/src/components/CommentItem.astro b/web/src/components/CommentItem.astro index 9ae825a..25c7c47 100644 --- a/web/src/components/CommentItem.astro +++ b/web/src/components/CommentItem.astro @@ -150,7 +150,7 @@ const commentUrl = makeCommentUrl({ serviceSlug, commentId: comment.id, origin: checked={comment.suspicious} /> -
+
-
- - + + +
{/* Edit Event Form - Hidden by default */} @@ -673,17 +688,30 @@ if (!service) return Astro.rewrite('/404')

-
@@ -844,21 +872,34 @@ if (!service) return Astro.rewrite('/404')

{method.value}

-
diff --git a/web/src/pages/admin/services/index.astro b/web/src/pages/admin/services/index.astro index 2b5a8b4..f5edf34 100644 --- a/web/src/pages/admin/services/index.astro +++ b/web/src/pages/admin/services/index.astro @@ -8,7 +8,8 @@ import MyPicture from '../../../components/MyPicture.astro' import SortArrowIcon from '../../../components/SortArrowIcon.astro' import Tooltip from '../../../components/Tooltip.astro' import { getKycLevelInfo } from '../../../constants/kycLevels' -import { getVerificationStatusInfo } from '../../../constants/verificationStatus' +import { serviceVisibilities } from '../../../constants/serviceVisibility' +import { getVerificationStatusInfo, verificationStatuses } from '../../../constants/verificationStatus' import BaseLayout from '../../../layouts/BaseLayout.astro' import { cn } from '../../../lib/cn' import { zodParseQueryParamsStoringErrors } from '../../../lib/parseUrlFilters' @@ -209,11 +210,11 @@ const truncate = (text: string, length: number) => { id="visibility" class="mt-1 w-full rounded-md border border-zinc-700 bg-zinc-900 px-3 py-2 text-sm text-zinc-200 focus:border-blue-500 focus:ring-blue-500 focus:outline-none" > - + { - Object.values(ServiceVisibility).map((status) => ( - )) } @@ -227,11 +228,11 @@ const truncate = (text: string, length: number) => { id="verificationStatus" class="mt-1 w-full rounded-md border border-zinc-700 bg-zinc-900 px-3 py-2 text-sm text-zinc-200 focus:border-blue-500 focus:ring-blue-500 focus:outline-none" > - + { - Object.values(VerificationStatus).map((status) => ( - )) } diff --git a/web/src/pages/index.astro b/web/src/pages/index.astro index cedf43b..54c953d 100644 --- a/web/src/pages/index.astro +++ b/web/src/pages/index.astro @@ -222,7 +222,9 @@ const where = { verificationStatus: { in: includeScams ? uniq([...filters.verification, 'VERIFICATION_FAILED'] as const) : filters.verification, }, - serviceVisibility: ServiceVisibility.PUBLIC, + serviceVisibility: { + in: [ServiceVisibility.PUBLIC, ServiceVisibility.ARCHIVED], + }, overallScore: { gte: filters['min-score'] }, acceptedCurrencies: filters.currencies.length ? filters['currency-mode'] === 'and' @@ -372,6 +374,7 @@ const [categories, [services, totalServices], countCommunityOnly, attributes] = imageUrl: true, verificationStatus: true, acceptedCurrencies: true, + serviceVisibility: true, attributes: { select: { attribute: { diff --git a/web/src/pages/service/[slug].astro b/web/src/pages/service/[slug].astro index ba9d0e7..efed1e4 100644 --- a/web/src/pages/service/[slug].astro +++ b/web/src/pages/service/[slug].astro @@ -30,7 +30,7 @@ import { formatContactMethod } from '../../constants/contactMethods' import { currencies, getCurrencyInfo } from '../../constants/currencies' import { getEventTypeInfo } from '../../constants/eventTypes' import { getKycLevelInfo, kycLevels } from '../../constants/kycLevels' -import { serviceVisibilitiesById } from '../../constants/serviceVisibility' +import { getServiceVisibilityInfo } from '../../constants/serviceVisibility' import { getTosHighlightRatingInfo } from '../../constants/tosHighlightRating' import { getUserSentimentInfo } from '../../constants/userSentiment' import { getVerificationStatusInfo, verificationStatusesByValue } from '../../constants/verificationStatus' @@ -240,7 +240,11 @@ const watchingDetails = makeWatchingDetails(dbNotificationPreferences, service?. if (!service) return Astro.rewrite('/404') -if (service.serviceVisibility !== 'PUBLIC' && service.serviceVisibility !== 'UNLISTED') { +if ( + service.serviceVisibility !== 'PUBLIC' && + service.serviceVisibility !== 'UNLISTED' && + service.serviceVisibility !== 'ARCHIVED' +) { return Astro.rewrite('/404') } @@ -356,6 +360,8 @@ const ogImageTemplateData = { score: service.overallScore, imageUrl: service.imageUrl, } satisfies OgImageAllTemplatesWithProps + +const serviceVisibilityInfo = getServiceVisibilityInfo(service.serviceVisibility) --- { - service.serviceVisibility === 'UNLISTED' && ( -
+ (serviceVisibilityInfo.value === 'UNLISTED' || serviceVisibilityInfo.value === 'ARCHIVED') && ( +
- Unlisted service, only accessible via direct link and won't appear in searches. + {serviceVisibilityInfo.longDescription}
) } +
@@ -1245,7 +1252,8 @@ const ogImageTemplateData = {
{ service.verificationStatus !== 'VERIFICATION_SUCCESS' && - service.verificationStatus !== 'VERIFICATION_FAILED' && ( + service.verificationStatus !== 'VERIFICATION_FAILED' && + service.serviceVisibility !== 'ARCHIVED' && (