v0.6.3: hide the paid city lots from users without a ParkSmarter account
All checks were successful
build-apk / build (push) Successful in 9m54s
All checks were successful
build-apk / build (push) Successful in 9m54s
The green lots are the map's only paid category ("City lots - Paid hourly or
permit"); paying for them goes through ParkSmarter, so they are no use to
someone browsing without an account. Every other category is free street
parking with a posted time limit and needs nothing.
Gated by category rather than by an id list. Two lots were named (off Oak St
and off N 3rd Ave) and then the beach ones, which together is every green lot
on the map — and an id list would silently break the next time the map is
regenerated from a new PDF, since ids are positional.
- Paid lots are filtered out of the overlay when signed out, so they are
neither drawn nor tappable.
- "Park here" still detects them, so standing in one explains that it needs an
account instead of reporting no parking nearby.
- The area screen guards too, in case one is reached with a stale nav param.
- Server carries an optional per-area requiresAccount override for a lot that
turns out to take payment another way. Null means "use the category
default", so an unset value can't be confused with an explicit false.
The new column needs a real migration: CREATE TABLE IF NOT EXISTS does not add
a column to a table that already exists, so an already-deployed server would
have kept the old schema. Covered by a test that builds the pre-migration
table and then opens it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
6af3dad762
commit
4217e88338
8 changed files with 159 additions and 18 deletions
|
|
@ -33,6 +33,12 @@ export interface ParkingArea {
|
|||
shape: 'line' | 'polygon';
|
||||
/** GeoJSON geometry (LineString or Polygon), lon/lat. */
|
||||
geometry: unknown;
|
||||
/**
|
||||
* Overrides the client's by-category default (paid city lots need a
|
||||
* ParkSmarter account, free time-limited streets don't). Null means "use the
|
||||
* default"; set it only to correct a specific lot.
|
||||
*/
|
||||
requiresAccount: boolean | null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -141,6 +147,16 @@ export class LabelDb {
|
|||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
`);
|
||||
|
||||
// CREATE TABLE IF NOT EXISTS won't add a column to a table that already
|
||||
// exists, so added columns need an explicit migration.
|
||||
this.addColumnIfMissing('parking_areas', 'requires_account', 'INTEGER');
|
||||
}
|
||||
|
||||
private addColumnIfMissing(table: string, column: string, type: string): void {
|
||||
const cols = this.db.prepare(`PRAGMA table_info(${table})`).all() as Array<{ name: string }>;
|
||||
if (cols.some((c) => c.name === column)) return;
|
||||
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${type}`);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------ city parking-map areas */
|
||||
|
|
@ -159,6 +175,8 @@ export class LabelDb {
|
|||
color: r.color,
|
||||
shape: r.shape as 'line' | 'polygon',
|
||||
geometry: JSON.parse(r.geometry),
|
||||
// SQLite has no boolean; null stays null so the client applies its default.
|
||||
requiresAccount: r.requires_account == null ? null : !!r.requires_account,
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
@ -182,13 +200,20 @@ export class LabelDb {
|
|||
replaceAreas(areas: ParkingArea[]): number {
|
||||
const now = Date.now();
|
||||
const insert = this.db.prepare(
|
||||
`INSERT INTO parking_areas (id, kind, name, label, legend, hours, color, shape, geometry, updated_at)
|
||||
VALUES (@id, @kind, @name, @label, @legend, @hours, @color, @shape, @geometry, @updatedAt)`,
|
||||
`INSERT INTO parking_areas
|
||||
(id, kind, name, label, legend, hours, color, shape, geometry, requires_account, updated_at)
|
||||
VALUES
|
||||
(@id, @kind, @name, @label, @legend, @hours, @color, @shape, @geometry, @requiresAccount, @updatedAt)`,
|
||||
);
|
||||
this.db.transaction((items: ParkingArea[]) => {
|
||||
this.db.prepare('DELETE FROM parking_areas').run();
|
||||
for (const a of items) {
|
||||
insert.run({ ...a, geometry: JSON.stringify(a.geometry), updatedAt: now });
|
||||
insert.run({
|
||||
...a,
|
||||
geometry: JSON.stringify(a.geometry),
|
||||
requiresAccount: a.requiresAccount == null ? null : a.requiresAccount ? 1 : 0,
|
||||
updatedAt: now,
|
||||
});
|
||||
}
|
||||
})(areas);
|
||||
return areas.length;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue