app: lists on meta tables, schema-qualified SQL, typed columns
- lists read/write real lists.* tables via meta.tables/meta.columns and the SECURITY DEFINER meta functions; typed extra columns in the item forms - old numeric list URLs 308-redirect to the slug - include_recurring is boolean, osu accuracy/pp/rate numeric - raw SQL qualified with the new schemas
This commit is contained in:
@@ -28,7 +28,7 @@ export const load: PageServerLoad = async (event) => {
|
||||
sightingAcceptedAt: sightings.acceptedAt,
|
||||
sightingDeniedAt: sightings.deniedAt,
|
||||
photoUrl: sql<string>`(
|
||||
SELECT url FROM photos
|
||||
SELECT url FROM animaldex.sighting_photos
|
||||
WHERE sighting_id = ${sightings.id}
|
||||
ORDER BY sort_order ASC
|
||||
LIMIT 1
|
||||
@@ -40,7 +40,7 @@ export const load: PageServerLoad = async (event) => {
|
||||
and(
|
||||
eq(sightings.animalId, animals.id),
|
||||
sql`${sightings.id} = (
|
||||
SELECT id FROM sightings
|
||||
SELECT id FROM animaldex.sightings
|
||||
WHERE animal_id = ${animals.id}
|
||||
ORDER BY submitted_at ASC
|
||||
LIMIT 1
|
||||
|
||||
@@ -45,7 +45,7 @@ export const GET: RequestHandler = async ({ url }) => {
|
||||
lat: sightings.lat,
|
||||
lng: sightings.lng,
|
||||
photoUrl: sql<string>`(
|
||||
SELECT url FROM photos
|
||||
SELECT url FROM animaldex.sighting_photos
|
||||
WHERE sighting_id = ${sightings.id}
|
||||
ORDER BY sort_order ASC
|
||||
LIMIT 1
|
||||
|
||||
@@ -5,13 +5,13 @@ import type { PageServerLoad } from './$types';
|
||||
export const load: PageServerLoad = async () => {
|
||||
const [stats, dimensions, markets] = await Promise.all([
|
||||
db.execute(sql`SELECT
|
||||
(SELECT count(*)::int FROM cs_market_items) AS items,
|
||||
(SELECT greatest(reltuples::bigint, 0) FROM pg_class WHERE oid = 'cs_price_observations'::regclass) AS observations,
|
||||
(SELECT min(observed_at) FROM cs_price_observations) AS earliest,
|
||||
(SELECT max(observed_at) FROM cs_price_observations) AS latest`),
|
||||
(SELECT count(*)::int FROM cs.items) AS items,
|
||||
(SELECT greatest(reltuples::bigint, 0) FROM pg_class WHERE oid = 'cs.price_observations'::regclass) AS observations,
|
||||
(SELECT min(observed_at) FROM cs.price_observations) AS earliest,
|
||||
(SELECT max(observed_at) FROM cs.price_observations) AS latest`),
|
||||
db.execute(sql`SELECT key, count(DISTINCT value)::int AS values
|
||||
FROM cs_item_dimensions GROUP BY key ORDER BY key`),
|
||||
db.execute(sql`SELECT source, currency, kind FROM cs_price_streams ORDER BY 1, 2, 3`)
|
||||
FROM cs.item_dimensions GROUP BY key ORDER BY key`),
|
||||
db.execute(sql`SELECT source, currency, kind FROM cs.price_streams ORDER BY 1, 2, 3`)
|
||||
]);
|
||||
return {
|
||||
stats: stats[0] as {
|
||||
|
||||
@@ -10,14 +10,14 @@ export const GET: RequestHandler = async ({ url }) => {
|
||||
const pattern = `%${q.replaceAll('\\', '\\\\').replaceAll('%', '\\%').replaceAll('_', '\\_')}%`;
|
||||
if (dimension === 'item') {
|
||||
const rows = await db.execute(sql`SELECT id, name, market_hash_name, icon_url
|
||||
FROM cs_market_items WHERE name ILIKE ${pattern} ESCAPE '\'
|
||||
FROM cs.items WHERE name ILIKE ${pattern} ESCAPE '\'
|
||||
ORDER BY name, id LIMIT ${limit} OFFSET ${offset}`);
|
||||
return Response.json({ results: rows });
|
||||
}
|
||||
if (!/^[a-z][a-z0-9_]*$/.test(dimension))
|
||||
return Response.json({ error: 'Invalid dimension' }, { status: 400 });
|
||||
const rows = await db.execute(sql`SELECT value, count(*)::int AS items
|
||||
FROM cs_item_dimensions WHERE key = ${dimension} AND value ILIKE ${pattern} ESCAPE '\'
|
||||
FROM cs.item_dimensions WHERE key = ${dimension} AND value ILIKE ${pattern} ESCAPE '\'
|
||||
GROUP BY value ORDER BY value LIMIT ${limit} OFFSET ${offset}`);
|
||||
return Response.json({ results: rows });
|
||||
};
|
||||
|
||||
@@ -34,11 +34,11 @@ export const POST: RequestHandler = async ({ request }) => {
|
||||
const seconds = Math.max(60, Math.ceil((+to - +from) / 1000 / 240 / 60) * 60);
|
||||
const selection =
|
||||
dimension === 'item'
|
||||
? sql`JOIN cs_market_items i ON i.id = p.item_id AND i.id IN (${sql.join(
|
||||
? sql`JOIN cs.items i ON i.id = p.item_id AND i.id IN (${sql.join(
|
||||
ids.map((id) => sql`${id}`),
|
||||
sql`,`
|
||||
)})`
|
||||
: sql`JOIN cs_item_dimensions d ON d.item_id = p.item_id AND d.key = ${dimension}
|
||||
: sql`JOIN cs.item_dimensions d ON d.item_id = p.item_id AND d.key = ${dimension}
|
||||
AND d.value IN (${sql.join(
|
||||
values.map((value) => sql`${value}`),
|
||||
sql`,`
|
||||
@@ -53,7 +53,7 @@ export const POST: RequestHandler = async ({ request }) => {
|
||||
sum(p.volume) AS volume,
|
||||
min(p.price_cents) AS min_cents,
|
||||
max(p.price_cents) AS max_cents
|
||||
FROM cs_price_observations p ${selection}
|
||||
FROM cs.price_observations p ${selection}
|
||||
WHERE p.source = ${source} AND p.currency = ${currency} AND p.kind = ${kind}
|
||||
AND p.observed_at >= ${from.toISOString()}::timestamptz
|
||||
AND p.observed_at <= ${to.toISOString()}::timestamptz
|
||||
|
||||
@@ -82,9 +82,9 @@ export const actions = {
|
||||
duration,
|
||||
combo,
|
||||
misses,
|
||||
accuracy: t('accuracy') || null,
|
||||
pp: t('pp') || null,
|
||||
rate: t('rate'),
|
||||
accuracy: t('accuracy') ? Number(t('accuracy')) : null,
|
||||
pp: t('pp') ? Number(t('pp')) : null,
|
||||
rate: Number(t('rate')),
|
||||
mods: t('mods') || 'NM',
|
||||
beatmapUrl: t('beatmapUrl') || null,
|
||||
scoreUrl: t('scoreUrl') || null,
|
||||
|
||||
@@ -91,7 +91,7 @@ export const load: PageServerLoad = async (event) => {
|
||||
.limit(400),
|
||||
db.execute(sql`SELECT city_slug AS slug, min(city) AS name,
|
||||
count(*) FILTER (WHERE removed_at IS NULL)::int AS active
|
||||
FROM house_listings WHERE offer = ${offer} AND city_slug IS NOT NULL
|
||||
FROM house.listings WHERE offer = ${offer} AND city_slug IS NOT NULL
|
||||
GROUP BY city_slug ORDER BY active DESC, slug LIMIT 200`),
|
||||
db.execute(sql`SELECT coalesce(min(city), city_slug) AS city,
|
||||
count(*) FILTER (WHERE removed_at IS NULL)::int AS active,
|
||||
@@ -103,18 +103,18 @@ export const load: PageServerLoad = async (event) => {
|
||||
percentile_cont(0.5) WITHIN GROUP (
|
||||
ORDER BY extract(epoch FROM removed_at - coalesce(offered_at, first_seen_at)) / 86400)
|
||||
FILTER (WHERE removed_at > now() - interval '90 days') AS median_days_listed
|
||||
FROM house_listings WHERE offer = ${offer} AND city_slug IS NOT NULL
|
||||
FROM house.listings WHERE offer = ${offer} AND city_slug IS NOT NULL
|
||||
GROUP BY city_slug ORDER BY active DESC LIMIT 30`),
|
||||
db.select().from(houseAlertRules).orderBy(asc(houseAlertRules.id)),
|
||||
db.select().from(houseWorkerRuns).orderBy(desc(houseWorkerRuns.createdAt)).limit(25),
|
||||
db.execute(sql`SELECT
|
||||
(SELECT max(created_at) FROM house_worker_runs WHERE status = 'ok') AS last_ok,
|
||||
(SELECT max(created_at) FROM house_worker_runs WHERE status = 'challenged') AS last_challenge,
|
||||
(SELECT count(*)::int FROM house_listings) AS total,
|
||||
(SELECT count(*)::int FROM house_listings WHERE alerted_at > now() - interval '7 days') AS alerts_week,
|
||||
(SELECT max(created_at) FROM house.worker_runs WHERE status = 'ok') AS last_ok,
|
||||
(SELECT max(created_at) FROM house.worker_runs WHERE status = 'challenged') AS last_challenge,
|
||||
(SELECT count(*)::int FROM house.listings) AS total,
|
||||
(SELECT count(*)::int FROM house.listings WHERE alerted_at > now() - interval '7 days') AS alerts_week,
|
||||
(SELECT percentile_cont(0.5) WITHIN GROUP (
|
||||
ORDER BY extract(epoch FROM first_seen_at - offered_at) / 60)
|
||||
FROM house_listings WHERE first_seen_lane = 'fast' AND offered_at IS NOT NULL
|
||||
FROM house.listings WHERE first_seen_lane = 'fast' AND offered_at IS NOT NULL
|
||||
AND first_seen_at >= offered_at AND first_seen_at - offered_at < interval '3 days'
|
||||
AND first_seen_at > now() - interval '30 days')
|
||||
AS median_detect_minutes`)
|
||||
|
||||
@@ -300,7 +300,7 @@ export const actions: Actions = {
|
||||
fixedMonthlyCents,
|
||||
annualReturnPercent,
|
||||
contributionMode,
|
||||
includeRecurring: form.get('includeRecurring') === 'on' ? 1 : 0,
|
||||
includeRecurring: form.get('includeRecurring') === 'on',
|
||||
endDate
|
||||
};
|
||||
await db
|
||||
|
||||
@@ -324,7 +324,7 @@
|
||||
</div>
|
||||
<p class="muted projection-note">
|
||||
Includes future dated transactions and estimates{data.projectionSettings
|
||||
?.includeRecurring !== 0
|
||||
?.includeRecurring !== false
|
||||
? ', plus recurring monthly equivalents from next month'
|
||||
: ''}. {data.projectionSettings?.investmentAccountId
|
||||
? `The selected investment account and new contributions use a hypothetical ${data.projectionSettings.annualReturnPercent}% annual return.`
|
||||
@@ -991,8 +991,11 @@
|
||||
></label
|
||||
>
|
||||
<label class="field check-field"
|
||||
><Checkbox name="includeRecurring" value="on" checked={active.includeRecurring !== 0} /> Include
|
||||
recurring items from next month</label
|
||||
><Checkbox
|
||||
name="includeRecurring"
|
||||
value="on"
|
||||
checked={active.includeRecurring !== false}
|
||||
/> Include recurring items from next month</label
|
||||
>
|
||||
{:else if mode === 'settings'}
|
||||
<input type="hidden" name="kind" value={active.kind ?? selectedUtility} />
|
||||
|
||||
Reference in New Issue
Block a user