Move the demo market to Mexico City, priced in US dollars
The showcase was a Barcelona market: Catalan names, +34 numbers, euro rates and "Carrer Example 12" on every job. Presented to a Mexican client, all of that reads as somebody else's product. City comes from NEXT_PUBLIC_CITY_* as before, now Ciudad de México at 19.4326/-99.1332, with MAPBOX_COUNTRY=mx. The seed's fallbacks were Barcelona literals, so an unset env quietly seeded a different city than the app rendered — they now agree. Two db tests pinned the Barcelona centre as a hardcoded constant, which is why the deck returned zero cards on the first run here: every pro was a continent outside the radius. They read the same env as the seed now, so the trap cannot recur. Money: formatCents defaults to USD/en-US, and the nine hardcoded euro signs across the card, search rows, quote strip and forms are dollars. The rate NUMBERS are unchanged and still read high for CDMX — that is a pricing decision, not a currency one, and is left alone deliberately. Seed people are Mexican, addressed on real Roma/Condesa streets rotated by index rather than one placeholder repeated. Phones moved to +52 55, which moves the demo login to +525500000000 / 000000. Also in here, from the same session: - Sending a job now confirms. The mutation always succeeded; the sheet just closed with no receipt, which from the customer's side is indistinguishable from a dead button. Dismissing that receipt resolves as 'sent', so the card does not return to the deck. - Media moves to DigitalOcean Spaces, with the public origin derived from bucket and region instead of a second env var to keep in sync. - Managed-Postgres TLS: DATABASE_CA_CERT takes a path or inline PEM. - The client-facing project panel beside the running app. - Two profiles removed and four renamed to match their photos. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@linkder/api",
|
||||
"name": "@linkdr/api",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
@@ -14,11 +14,11 @@
|
||||
"test": "vitest run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@linkder/db": "workspace:*",
|
||||
"@linkder/geocode": "workspace:*",
|
||||
"@linkder/notify": "workspace:*",
|
||||
"@linkder/shared": "workspace:*",
|
||||
"@linkder/storage": "workspace:*",
|
||||
"@linkdr/db": "workspace:*",
|
||||
"@linkdr/geocode": "workspace:*",
|
||||
"@linkdr/notify": "workspace:*",
|
||||
"@linkdr/shared": "workspace:*",
|
||||
"@linkdr/storage": "workspace:*",
|
||||
"@opentelemetry/api": "1.9.1",
|
||||
"@trpc/server": "^11.18.0",
|
||||
"drizzle-orm": "0.38.4",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Db } from '@linkder/db';
|
||||
import type { Role, VerificationStatus } from '@linkder/shared';
|
||||
import type { Db } from '@linkdr/db';
|
||||
import type { Role, VerificationStatus } from '@linkdr/shared';
|
||||
|
||||
/**
|
||||
* The session shape the API depends on.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { forward, GeocodeError, isConfigured, type GeocodeResult } from '@linkder/geocode';
|
||||
import type { LatLng, LocationInput, LocationPrecision } from '@linkder/shared';
|
||||
import { forward, GeocodeError, isConfigured, type GeocodeResult } from '@linkdr/geocode';
|
||||
import type { LatLng, LocationInput, LocationPrecision } from '@linkdr/shared';
|
||||
|
||||
/**
|
||||
* The one place a stored coordinate is decided.
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
import { and, asc, count, desc, eq, inArray } from 'drizzle-orm';
|
||||
import { z } from 'zod';
|
||||
import { recomputeProStats, schema } from '@linkder/db';
|
||||
import { notify } from '@linkder/notify';
|
||||
import { createPresignedDownload } from '@linkder/storage';
|
||||
import { assertTransition, VERIFICATION_STATUSES } from '@linkder/shared';
|
||||
import { recomputeProStats, schema } from '@linkdr/db';
|
||||
import { notify } from '@linkdr/notify';
|
||||
import { createPresignedDownload } from '@linkdr/storage';
|
||||
import { assertTransition, VERIFICATION_STATUSES } from '@linkdr/shared';
|
||||
import { adminProcedure, router } from '../trpc';
|
||||
|
||||
/**
|
||||
@@ -20,7 +20,7 @@ import { adminProcedure, router } from '../trpc';
|
||||
* everyone else: an admin surface that announces itself is a target.
|
||||
*
|
||||
* Nothing in this router trusts a status it was handed. Each transition goes
|
||||
* through the graph in @linkder/shared, and each writes an `audit_log` row —
|
||||
* through the graph in @linkdr/shared, and each writes an `audit_log` row —
|
||||
* these are the decisions that a regulator, an insurer or a court would ask us
|
||||
* to account for.
|
||||
*/
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
import { desc, eq } from 'drizzle-orm';
|
||||
import { z } from 'zod';
|
||||
import { recomputeProStats, schema, type Db } from '@linkder/db';
|
||||
import { assertTransition, cancellationOutcome, type BookingStatus } from '@linkder/shared';
|
||||
import { recomputeProStats, schema, type Db } from '@linkdr/db';
|
||||
import { assertTransition, cancellationOutcome, type BookingStatus } from '@linkdr/shared';
|
||||
import { requireMatchParticipant } from './message';
|
||||
import { protectedProcedure, router } from '../trpc';
|
||||
|
||||
@@ -162,7 +162,7 @@ export const bookingRouter = router({
|
||||
* Call it off.
|
||||
*
|
||||
* Either side may, and who cancelled decides who pays — `cancellationOutcome`
|
||||
* in @linkder/shared owns that rule and is already tested. The result is
|
||||
* in @linkdr/shared owns that rule and is already tested. The result is
|
||||
* written into the audit log now, while the scheduled time and the quote are
|
||||
* still the facts they were; recomputing it later from a slot that has since
|
||||
* passed would give a different answer.
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
import { and, count, desc, eq, inArray, sql } from 'drizzle-orm';
|
||||
import { z } from 'zod';
|
||||
import { getDeck, getDeckCount, getShowcaseDeck, schema } from '@linkder/db';
|
||||
import { notify } from '@linkder/notify';
|
||||
import { getDeck, getDeckCount, getShowcaseDeck, schema } from '@linkdr/db';
|
||||
import { notify } from '@linkdr/notify';
|
||||
import {
|
||||
DECK_PAGE_SIZE,
|
||||
MAX_OPEN_REQUESTS_PER_JOB,
|
||||
REQUEST_TTL_HOURS,
|
||||
swipeSchema,
|
||||
} from '@linkder/shared';
|
||||
} from '@linkdr/shared';
|
||||
import { clientProcedure, publicProcedure, router } from '../trpc';
|
||||
import type { Context } from '../context';
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
import { and, desc, eq, gt, isNull, or, sql } from 'drizzle-orm';
|
||||
import { z } from 'zod';
|
||||
import { schema } from '@linkder/db';
|
||||
import { ENQUIRY_STALE_DAYS, MAX_OPEN_ENQUIRIES } from '@linkder/shared';
|
||||
import { schema } from '@linkdr/db';
|
||||
import { ENQUIRY_STALE_DAYS, MAX_OPEN_ENQUIRIES } from '@linkdr/shared';
|
||||
import { clientProcedure, proProcedure, protectedProcedure, router } from '../trpc';
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,8 +7,8 @@ import {
|
||||
MAX_SUGGESTIONS,
|
||||
reverse,
|
||||
type GeocodeResult,
|
||||
} from '@linkder/geocode';
|
||||
import { latLngSchema } from '@linkder/shared';
|
||||
} from '@linkdr/geocode';
|
||||
import { latLngSchema } from '@linkdr/shared';
|
||||
import { protectedProcedure, router } from '../trpc';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
import { and, desc, eq, sql } from 'drizzle-orm';
|
||||
import { z } from 'zod';
|
||||
import { schema } from '@linkder/db';
|
||||
import { ACTIVE_JOB_STATUSES, assertTransition, createJobSchema } from '@linkder/shared';
|
||||
import { schema } from '@linkdr/db';
|
||||
import { ACTIVE_JOB_STATUSES, assertTransition, createJobSchema } from '@linkdr/shared';
|
||||
import { resolveLocation } from '../location';
|
||||
import { clientProcedure, proProcedure, publicProcedure, router } from '../trpc';
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
import { and, desc, eq, isNull, ne, or, sql } from 'drizzle-orm';
|
||||
import { z } from 'zod';
|
||||
import { schema, type Db } from '@linkder/db';
|
||||
import { PAST_JOB_STATUSES, sendMessageSchema, type JobStatus } from '@linkder/shared';
|
||||
import { schema, type Db } from '@linkdr/db';
|
||||
import { PAST_JOB_STATUSES, sendMessageSchema, type JobStatus } from '@linkdr/shared';
|
||||
import { protectedProcedure, router } from '../trpc';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { z } from 'zod';
|
||||
import { schema } from '@linkder/db';
|
||||
import { schema } from '@linkdr/db';
|
||||
import { protectedProcedure, router } from '../trpc';
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,8 +2,8 @@ import { TRPCError } from '@trpc/server';
|
||||
import { and, desc, eq, inArray, lt } from 'drizzle-orm';
|
||||
import { alias } from 'drizzle-orm/pg-core';
|
||||
import { z } from 'zod';
|
||||
import { eligibleProAtAnyDistance, schema, searchPros } from '@linkder/db';
|
||||
import { notify } from '@linkder/notify';
|
||||
import { eligibleProAtAnyDistance, schema, searchPros } from '@linkdr/db';
|
||||
import { notify } from '@linkdr/notify';
|
||||
import {
|
||||
assertTransition,
|
||||
credentialSchema,
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
REVIEWS_PAGE_SIZE,
|
||||
searchProsSchema,
|
||||
updateSkillsSchema,
|
||||
} from '@linkder/shared';
|
||||
} from '@linkdr/shared';
|
||||
import { resolveLocation } from '../location';
|
||||
import { proProcedure, publicProcedure, router } from '../trpc';
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
import { and, desc, eq } from 'drizzle-orm';
|
||||
import { z } from 'zod';
|
||||
import { schema } from '@linkder/db';
|
||||
import { schema } from '@linkdr/db';
|
||||
import {
|
||||
assertTransition,
|
||||
createBookingSchema,
|
||||
createQuoteSchema,
|
||||
QUOTE_VALIDITY_HOURS,
|
||||
} from '@linkder/shared';
|
||||
} from '@linkdr/shared';
|
||||
import { requireMatchParticipant } from './message';
|
||||
import { clientProcedure, protectedProcedure, router, verifiedProProcedure } from '../trpc';
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
import { and, eq, gt, sql } from 'drizzle-orm';
|
||||
import { z } from 'zod';
|
||||
import { recomputeProStats, schema } from '@linkder/db';
|
||||
import { notify } from '@linkder/notify';
|
||||
import { assertTransition } from '@linkder/shared';
|
||||
import { recomputeProStats, schema } from '@linkdr/db';
|
||||
import { notify } from '@linkdr/notify';
|
||||
import { assertTransition } from '@linkdr/shared';
|
||||
import { proProcedure, router, verifiedProProcedure } from '../trpc';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
import { and, eq, ne, sql } from 'drizzle-orm';
|
||||
import { z } from 'zod';
|
||||
import { recomputeProStats, schema } from '@linkder/db';
|
||||
import { recomputeProStats, schema } from '@linkdr/db';
|
||||
import {
|
||||
createReviewSchema,
|
||||
REVIEW_EMBARGO_HOURS,
|
||||
REVIEW_WINDOW_DAYS,
|
||||
} from '@linkder/shared';
|
||||
} from '@linkdr/shared';
|
||||
import { requireMatchParticipant } from './message';
|
||||
import { protectedProcedure, router } from '../trpc';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
import { createPresignedUpload, publicUrl, uploadRequestSchema, StorageError } from '@linkder/storage';
|
||||
import { createPresignedUpload, publicUrl, uploadRequestSchema, StorageError } from '@linkdr/storage';
|
||||
import { protectedProcedure, router } from '../trpc';
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,8 +2,8 @@ import { randomUUID } from 'node:crypto';
|
||||
import { TRPCError } from '@trpc/server';
|
||||
import { and, desc, eq, isNull } from 'drizzle-orm';
|
||||
import { z } from 'zod';
|
||||
import { schema } from '@linkder/db';
|
||||
import { assertTransition, isContactableEmail, updateLocationSchema } from '@linkder/shared';
|
||||
import { schema } from '@linkdr/db';
|
||||
import { assertTransition, isContactableEmail, updateLocationSchema } from '@linkdr/shared';
|
||||
import { resolveLocation } from '../location';
|
||||
import { protectedProcedure, publicProcedure, router } from '../trpc';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
import { and, desc, eq, isNull, sql } from 'drizzle-orm';
|
||||
import { z } from 'zod';
|
||||
import { schema } from '@linkder/db';
|
||||
import { schema } from '@linkdr/db';
|
||||
import { protectedProcedure, router } from '../trpc';
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,7 +2,7 @@ import { TRPCError, initTRPC } from '@trpc/server';
|
||||
import superjson from 'superjson';
|
||||
import { ZodError } from 'zod';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { schema } from '@linkder/db';
|
||||
import { schema } from '@linkdr/db';
|
||||
import type { Context } from './context';
|
||||
|
||||
const t = initTRPC.context<Context>().create({
|
||||
|
||||
@@ -23,7 +23,7 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
||||
|
||||
config({ path: '../../.env' });
|
||||
|
||||
const { closePool, db } = await import('@linkder/db');
|
||||
const { closePool, db } = await import('@linkdr/db');
|
||||
const { appRouter } = await import('../src/root');
|
||||
const { createInnerContext } = await import('../src/context');
|
||||
const { createCallerFactory } = await import('../src/trpc');
|
||||
@@ -86,7 +86,7 @@ async function makePro(name: string, status: string): Promise<string> {
|
||||
${id}, 'Admin probe', 'Exists only for the admin router tests.', 3000,
|
||||
-- Right on the city centre, so an approval is visible to a search run
|
||||
-- from there and the "it lands on every surface" assertions are real.
|
||||
ST_SetSRID(ST_MakePoint(2.1686, 41.3874), 4326)::geography, 20000, ${status}
|
||||
ST_SetSRID(ST_MakePoint(-99.1332, 19.4326), 4326)::geography, 20000, ${status}
|
||||
)
|
||||
`);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Integration tests for the deck router, run against the live seeded database.
|
||||
*
|
||||
* pnpm services:up && pnpm db:migrate && pnpm db:seed
|
||||
* pnpm --filter @linkder/api test
|
||||
* pnpm --filter @linkdr/api test
|
||||
*
|
||||
* The point of these is authorization. The swipe path previously lived in a Next
|
||||
* server action that trusted whatever jobId it was handed, so anyone could swipe
|
||||
@@ -11,11 +11,11 @@
|
||||
import { config } from 'dotenv';
|
||||
import { sql } from 'drizzle-orm';
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { MAX_OPEN_REQUESTS_PER_JOB } from '@linkder/shared';
|
||||
import { MAX_OPEN_REQUESTS_PER_JOB } from '@linkdr/shared';
|
||||
|
||||
config({ path: '../../.env' });
|
||||
|
||||
const { closePool, db } = await import('@linkder/db');
|
||||
const { closePool, db } = await import('@linkdr/db');
|
||||
const { appRouter } = await import('../src/root');
|
||||
const { createInnerContext } = await import('../src/context');
|
||||
const { createCallerFactory } = await import('../src/trpc');
|
||||
|
||||
@@ -12,11 +12,11 @@
|
||||
import { config } from 'dotenv';
|
||||
import { sql } from 'drizzle-orm';
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { MAX_OPEN_ENQUIRIES } from '@linkder/shared';
|
||||
import { MAX_OPEN_ENQUIRIES } from '@linkdr/shared';
|
||||
|
||||
config({ path: '../../.env' });
|
||||
|
||||
const { closePool, db } = await import('@linkder/db');
|
||||
const { closePool, db } = await import('@linkdr/db');
|
||||
const { appRouter } = await import('../src/root');
|
||||
const { createInnerContext } = await import('../src/context');
|
||||
const { createCallerFactory } = await import('../src/trpc');
|
||||
|
||||
@@ -14,7 +14,7 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
||||
|
||||
config({ path: '../../.env' });
|
||||
|
||||
const { closePool, db } = await import('@linkder/db');
|
||||
const { closePool, db } = await import('@linkdr/db');
|
||||
const { appRouter } = await import('../src/root');
|
||||
const { createInnerContext } = await import('../src/context');
|
||||
const { createCallerFactory } = await import('../src/trpc');
|
||||
@@ -37,8 +37,8 @@ const clientSession = (userId: string): Session => ({
|
||||
|
||||
const RUN = Math.random().toString(36).slice(2, 8);
|
||||
const CITY = {
|
||||
lat: Number(process.env.NEXT_PUBLIC_CITY_LAT ?? 41.3874),
|
||||
lng: Number(process.env.NEXT_PUBLIC_CITY_LNG ?? 2.1686),
|
||||
lat: Number(process.env.NEXT_PUBLIC_CITY_LAT ?? 19.4326),
|
||||
lng: Number(process.env.NEXT_PUBLIC_CITY_LNG ?? -99.1332),
|
||||
};
|
||||
|
||||
let client: string;
|
||||
@@ -140,13 +140,13 @@ describe('job.create resolves the point server-side', () => {
|
||||
const job = await callerFor(clientSession(client)).job.create({
|
||||
...base,
|
||||
categoryId: plumberCat,
|
||||
place: { source: 'device', lat: 41.4036, lng: 2.1744, label: 'Gracia' },
|
||||
place: { source: 'device', lat: 19.4194, lng: -99.1655, label: 'Condesa' },
|
||||
});
|
||||
|
||||
const row = await readJob(job.id);
|
||||
// A handset fix is real, so the coordinates are kept as sent...
|
||||
expect(Number(row.lat)).toBeCloseTo(41.4036, 4);
|
||||
expect(Number(row.lng)).toBeCloseTo(2.1744, 4);
|
||||
expect(Number(row.lat)).toBeCloseTo(19.4194, 4);
|
||||
expect(Number(row.lng)).toBeCloseTo(-99.1655, 4);
|
||||
// ...but it is metres out on a good day, so it must not rank as a rooftop.
|
||||
expect(row.precision).toBe('approximate');
|
||||
});
|
||||
@@ -157,7 +157,7 @@ describe('job.create resolves the point server-side', () => {
|
||||
const job = await callerFor(clientSession(client)).job.create({
|
||||
...base,
|
||||
categoryId: plumberCat,
|
||||
place: { source: 'place', placeId: 'made-up-id', label: 'Carrer de Sants 12' },
|
||||
place: { source: 'place', placeId: 'made-up-id', label: 'Av. Álvaro Obregón 12' },
|
||||
});
|
||||
|
||||
const row = await readJob(job.id);
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
import { config } from 'dotenv';
|
||||
import { sql } from 'drizzle-orm';
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
||||
import { REVIEW_EMBARGO_HOURS } from '@linkder/shared';
|
||||
import { REVIEW_EMBARGO_HOURS } from '@linkdr/shared';
|
||||
|
||||
config({ path: '../../.env' });
|
||||
|
||||
const { closePool, db } = await import('@linkder/db');
|
||||
const { closePool, db } = await import('@linkdr/db');
|
||||
const { appRouter } = await import('../src/root');
|
||||
const { createInnerContext } = await import('../src/context');
|
||||
const { createCallerFactory } = await import('../src/trpc');
|
||||
@@ -98,7 +98,7 @@ beforeAll(async () => {
|
||||
)
|
||||
VALUES (
|
||||
${pro}, 'Lifecycle pro', 'Exists only for the lifecycle tests.', 4000,
|
||||
ST_SetSRID(ST_MakePoint(2.1686, 41.3874), 4326)::geography, 'exact',
|
||||
ST_SetSRID(ST_MakePoint(-99.1332, 19.4326), 4326)::geography, 'exact',
|
||||
15000, 'verified', now(), false
|
||||
)
|
||||
`);
|
||||
@@ -109,8 +109,8 @@ beforeAll(async () => {
|
||||
VALUES (
|
||||
${owner}, ${plumber!.id}, 'Lifecycle fixture job',
|
||||
'A job that exists to be quoted, booked, completed and reviewed.',
|
||||
ST_SetSRID(ST_MakePoint(2.1686, 41.3874), 4326)::geography, 'exact',
|
||||
'Carrer de Prova 1', 'matched'
|
||||
ST_SetSRID(ST_MakePoint(-99.1332, 19.4326), 4326)::geography, 'exact',
|
||||
'Calle Colima 1', 'matched'
|
||||
)
|
||||
RETURNING id
|
||||
`);
|
||||
@@ -220,7 +220,7 @@ describe('booking', () => {
|
||||
)
|
||||
VALUES (
|
||||
${other}, 'Also waiting', 'A second pro left hanging on the same job.', 3500,
|
||||
ST_SetSRID(ST_MakePoint(2.1686, 41.3874), 4326)::geography, 'exact',
|
||||
ST_SetSRID(ST_MakePoint(-99.1332, 19.4326), 4326)::geography, 'exact',
|
||||
15000, 'verified', now(), false
|
||||
)
|
||||
`);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Integration tests for chat, against the live seeded database.
|
||||
*
|
||||
* pnpm services:up && pnpm db:migrate && pnpm db:seed
|
||||
* pnpm --filter @linkder/api test
|
||||
* pnpm --filter @linkdr/api test
|
||||
*
|
||||
* A thread is a private conversation between exactly two people, so most of this
|
||||
* file is about the third person: a stranger must not be able to read it, write
|
||||
@@ -18,7 +18,7 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
||||
|
||||
config({ path: '../../.env' });
|
||||
|
||||
const { closePool, db } = await import('@linkder/db');
|
||||
const { closePool, db } = await import('@linkdr/db');
|
||||
const { appRouter } = await import('../src/root');
|
||||
const { createInnerContext } = await import('../src/context');
|
||||
const { createCallerFactory } = await import('../src/trpc');
|
||||
@@ -78,8 +78,8 @@ async function insertJobWithMatch(categoryId: string, status: string): Promise<{
|
||||
VALUES (
|
||||
${owner}, ${categoryId}, 'Chat fixture job',
|
||||
'A job that exists only so a conversation can hang off it.',
|
||||
ST_SetSRID(ST_MakePoint(2.1686, 41.3874), 4326)::geography,
|
||||
'Carrer de Prova 1', ${sql.raw(`'${status}'`)}
|
||||
ST_SetSRID(ST_MakePoint(-99.1332, 19.4326), 4326)::geography,
|
||||
'Calle Colima 1', ${sql.raw(`'${status}'`)}
|
||||
)
|
||||
RETURNING id
|
||||
`);
|
||||
@@ -116,7 +116,7 @@ beforeAll(async () => {
|
||||
)
|
||||
VALUES (
|
||||
${pro}, 'Chat fixture pro', 'Exists only for the message router tests.', 4000,
|
||||
ST_SetSRID(ST_MakePoint(2.1686, 41.3874), 4326)::geography, 15000, 'verified', now()
|
||||
ST_SetSRID(ST_MakePoint(-99.1332, 19.4326), 4326)::geography, 15000, 'verified', now()
|
||||
)
|
||||
`);
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
||||
|
||||
config({ path: '../../.env' });
|
||||
|
||||
const { closePool, db } = await import('@linkder/db');
|
||||
const { closePool, db } = await import('@linkdr/db');
|
||||
const { appRouter } = await import('../src/root');
|
||||
const { createInnerContext } = await import('../src/context');
|
||||
const { createCallerFactory } = await import('../src/trpc');
|
||||
|
||||
@@ -19,7 +19,7 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
||||
|
||||
config({ path: '../../.env' });
|
||||
|
||||
const { closePool, db } = await import('@linkder/db');
|
||||
const { closePool, db } = await import('@linkdr/db');
|
||||
const { appRouter } = await import('../src/root');
|
||||
const { createInnerContext } = await import('../src/context');
|
||||
const { createCallerFactory } = await import('../src/trpc');
|
||||
@@ -45,12 +45,12 @@ let probeClient: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const [marc] = await db.execute<{ id: string }>(
|
||||
sql`SELECT id FROM users WHERE name = 'Marc Oliveras' LIMIT 1`,
|
||||
sql`SELECT id FROM users WHERE name = 'Sergio Fabela' LIMIT 1`,
|
||||
);
|
||||
reviewedPro = marc!.id;
|
||||
|
||||
const [arnau] = await db.execute<{ id: string }>(
|
||||
sql`SELECT id FROM users WHERE name = 'Away Arnau' LIMIT 1`,
|
||||
sql`SELECT id FROM users WHERE name = 'Away Arturo' LIMIT 1`,
|
||||
);
|
||||
awayPro = arnau!.id;
|
||||
|
||||
@@ -194,7 +194,7 @@ describe('pro.reviews', () => {
|
||||
});
|
||||
|
||||
it('is not a way to read a pro who is off the deck', async () => {
|
||||
// Away Arnau has a seeded review history and is verified — only holiday mode
|
||||
// Away Arturo has a seeded review history and is verified — only holiday mode
|
||||
// hides him. If this stopped 404ing, reviews would be the way around
|
||||
// publicProfile rather than a view onto it.
|
||||
await expect(anon().pro.reviews({ proId: awayPro })).rejects.toThrow(/NOT_FOUND|not found/i);
|
||||
@@ -205,7 +205,7 @@ describe('pro.reviews', () => {
|
||||
|
||||
it('404s for an unverified pro, exactly as the profile does', async () => {
|
||||
const [ulla] = await db.execute<{ id: string }>(
|
||||
sql`SELECT id FROM users WHERE name = 'Unverified Ulla' LIMIT 1`,
|
||||
sql`SELECT id FROM users WHERE name = 'Unverified Ulises' LIMIT 1`,
|
||||
);
|
||||
await expect(anon().pro.reviews({ proId: ulla!.id })).rejects.toThrow(/NOT_FOUND|not found/i);
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
||||
|
||||
config({ path: '../../.env' });
|
||||
|
||||
const { closePool, db } = await import('@linkder/db');
|
||||
const { closePool, db } = await import('@linkdr/db');
|
||||
const { appRouter } = await import('../src/root');
|
||||
const { createInnerContext } = await import('../src/context');
|
||||
const { createCallerFactory } = await import('../src/trpc');
|
||||
@@ -62,12 +62,12 @@ beforeAll(async () => {
|
||||
client = aClient!.id;
|
||||
|
||||
const [marc] = await db.execute<{ id: string }>(
|
||||
sql`SELECT id FROM users WHERE name = 'Marc Oliveras' LIMIT 1`,
|
||||
sql`SELECT id FROM users WHERE name = 'Sergio Fabela' LIMIT 1`,
|
||||
);
|
||||
verifiedPro = marc!.id;
|
||||
|
||||
const [arnau] = await db.execute<{ id: string }>(
|
||||
sql`SELECT id FROM users WHERE name = 'Away Arnau' LIMIT 1`,
|
||||
sql`SELECT id FROM users WHERE name = 'Away Arturo' LIMIT 1`,
|
||||
);
|
||||
awayPro = arnau!.id;
|
||||
|
||||
@@ -121,7 +121,7 @@ describe('pro.search', () => {
|
||||
const ids = results.map((p) => p.proId);
|
||||
const names = results.map((p) => p.name);
|
||||
|
||||
expect(names).not.toContain('Unverified Ulla');
|
||||
expect(names).not.toContain('Unverified Ulises');
|
||||
expect(ids).not.toContain(awayPro);
|
||||
expect(ids).not.toContain(bannedPro);
|
||||
});
|
||||
@@ -131,7 +131,7 @@ describe('pro.search', () => {
|
||||
// the pros next to the city centre must fall out of range.
|
||||
await db.execute(sql`
|
||||
UPDATE users
|
||||
SET location = ST_SetSRID(ST_MakePoint(2.1686, 41.5674), 4326)::geography,
|
||||
SET location = ST_SetSRID(ST_MakePoint(-99.1332, 19.6126), 4326)::geography,
|
||||
search_radius_m = 2000
|
||||
WHERE id = ${client}
|
||||
`);
|
||||
@@ -166,7 +166,7 @@ describe('pro.publicProfile', () => {
|
||||
|
||||
it('refuses a pro who was never verified', async () => {
|
||||
const [ulla] = await db.execute<{ id: string }>(
|
||||
sql`SELECT id FROM users WHERE name = 'Unverified Ulla' LIMIT 1`,
|
||||
sql`SELECT id FROM users WHERE name = 'Unverified Ulises' LIMIT 1`,
|
||||
);
|
||||
await expect(callerFor(null).pro.publicProfile({ proId: ulla!.id })).rejects.toThrow();
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
||||
|
||||
config({ path: '../../.env' });
|
||||
|
||||
const { closePool, db } = await import('@linkder/db');
|
||||
const { closePool, db } = await import('@linkdr/db');
|
||||
const { appRouter } = await import('../src/root');
|
||||
const { createInnerContext } = await import('../src/context');
|
||||
const { createCallerFactory } = await import('../src/trpc');
|
||||
@@ -258,15 +258,15 @@ describe('location and range', () => {
|
||||
// coordinates the server takes at face value, so this test does not need a
|
||||
// geocoder to be configured.
|
||||
await caller.user.updateLocation({
|
||||
place: { source: 'device', lat: 41.4036, lng: 2.1744, label: 'Gracia, Barcelona' },
|
||||
place: { source: 'device', lat: 19.4194, lng: -99.1655, label: 'Condesa, Ciudad de México' },
|
||||
radiusM: 8_000,
|
||||
});
|
||||
|
||||
const location = await caller.user.location();
|
||||
expect(location.addressText).toBe('Gracia, Barcelona');
|
||||
expect(location.addressText).toBe('Condesa, Ciudad de México');
|
||||
expect(location.radiusM).toBe(8_000);
|
||||
expect(location.location?.lat).toBeCloseTo(41.4036, 4);
|
||||
expect(location.location?.lng).toBeCloseTo(2.1744, 4);
|
||||
expect(location.location?.lat).toBeCloseTo(19.4194, 4);
|
||||
expect(location.location?.lng).toBeCloseTo(-99.1655, 4);
|
||||
});
|
||||
|
||||
it('changes only what it was given', async () => {
|
||||
@@ -276,7 +276,7 @@ describe('location and range', () => {
|
||||
const location = await caller.user.location();
|
||||
expect(location.radiusM).toBe(25_000);
|
||||
// The pin saved by the previous test is still there.
|
||||
expect(location.location?.lat).toBeCloseTo(41.4036, 4);
|
||||
expect(location.location?.lat).toBeCloseTo(19.4194, 4);
|
||||
});
|
||||
|
||||
it('refuses a radius outside the supported range', async () => {
|
||||
|
||||
Reference in New Issue
Block a user