diff --git a/database/base.sql b/database/base.sql index 179b49f7..a7a2d9b1 100644 --- a/database/base.sql +++ b/database/base.sql @@ -867,7 +867,6 @@ CREATE TABLE public.folder ( description text, special text, sort text, - locnid bigint, view text, viewproperty text, thumbarchivenbr text, @@ -882,7 +881,32 @@ CREATE TABLE public.folder ( type text, publicdt timestamp with time zone, createddt timestamp with time zone, - updateddt timestamp with time zone + updateddt timestamp with time zone, + location_displayname text, + location_geocodelookup text, + location_streetnumber text, + location_streetname text, + location_postalcode text, + location_locality text, + location_adminonename text, + location_adminonecode text, + location_admintwoname text, + location_admintwocode text, + location_country text, + location_countrycode text, + location_geometrytype text, + location_latitude double precision, + location_longitude double precision, + location_boundsouth double precision, + location_boundwest double precision, + location_boundnorth double precision, + location_boundeast double precision, + location_geometryasarray text, + location_geocodetype text, + location_sublocation text, + location_altitudemeters double precision, + location_locationprecision text CONSTRAINT folder_location_locationprecision_check CHECK (location_locationprecision IS NULL OR location_locationprecision IN ('approximate', 'uncertain', 'unknown')), + location_rawmetadata jsonb ); @@ -1637,7 +1661,6 @@ CREATE TABLE public.record ( derivedenddt timestamp with time zone, derivedcreateddt timestamp with time zone, timezoneid bigint, - locnid bigint, view text, viewproperty text, imageratio numeric(6,2), @@ -1655,7 +1678,32 @@ CREATE TABLE public.record ( type text NOT NULL, processeddt timestamp with time zone, createddt timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, - updateddt timestamp with time zone + updateddt timestamp with time zone, + location_displayname text, + location_geocodelookup text, + location_streetnumber text, + location_streetname text, + location_postalcode text, + location_locality text, + location_adminonename text, + location_adminonecode text, + location_admintwoname text, + location_admintwocode text, + location_country text, + location_countrycode text, + location_geometrytype text, + location_latitude double precision, + location_longitude double precision, + location_boundsouth double precision, + location_boundwest double precision, + location_boundnorth double precision, + location_boundeast double precision, + location_geometryasarray text, + location_geocodetype text, + location_sublocation text, + location_altitudemeters double precision, + location_locationprecision text CONSTRAINT record_location_locationprecision_check CHECK (location_locationprecision IS NULL OR location_locationprecision IN ('approximate', 'uncertain', 'unknown')), + location_rawmetadata jsonb ); @@ -3459,13 +3507,6 @@ CREATE INDEX idx_19281_displayname ON public.folder USING gin (to_tsvector('simp CREATE INDEX idx_19281_folder_archiveid ON public.folder USING btree (archiveid); --- --- Name: idx_19281_folder_locnid; Type: INDEX; Schema: public; Owner: postgres --- - -CREATE INDEX idx_19281_folder_locnid ON public.folder USING btree (locnid); - - -- -- Name: idx_19281_folder_timezoneid; Type: INDEX; Schema: public; Owner: postgres -- @@ -3837,13 +3878,6 @@ CREATE INDEX idx_19402_processeddt ON public.record USING btree (processeddt); CREATE INDEX idx_19402_publicdt ON public.record USING btree (publicdt); --- --- Name: idx_19402_record_locnid; Type: INDEX; Schema: public; Owner: postgres --- - -CREATE INDEX idx_19402_record_locnid ON public.record USING btree (locnid); - - -- -- Name: idx_19402_record_refarchivenbr; Type: INDEX; Schema: public; Owner: postgres -- diff --git a/packages/api/docs/src/models/location.yaml b/packages/api/docs/src/models/location.yaml index f2647c71..12f16a43 100644 --- a/packages/api/docs/src/models/location.yaml +++ b/packages/api/docs/src/models/location.yaml @@ -1,10 +1,7 @@ location: description: A geographic location object type: object - required: [id] properties: - id: - type: string name: type: string sublocation: diff --git a/packages/api/docs/src/paths/record.yaml b/packages/api/docs/src/paths/record.yaml index c7da7046..1c0d1c7d 100644 --- a/packages/api/docs/src/paths/record.yaml +++ b/packages/api/docs/src/paths/record.yaml @@ -102,8 +102,9 @@ records/{id}: properties: displayName: type: string - locationId: - type: integer + location: + $ref: "../models/location.yaml#/location" + nullable: true description: type: string displayTime: diff --git a/packages/api/src/common/models.ts b/packages/api/src/common/models.ts index d4e81be9..0704ee52 100644 --- a/packages/api/src/common/models.ts +++ b/packages/api/src/common/models.ts @@ -8,7 +8,6 @@ export enum AccessRole { } export interface Location { - id: string; name?: string; sublocation?: string; city?: string; diff --git a/packages/api/src/folder/controller/get_folder.test.ts b/packages/api/src/folder/controller/get_folder.test.ts index ff6b6186..c8fd8606 100644 --- a/packages/api/src/folder/controller/get_folder.test.ts +++ b/packages/api/src/folder/controller/get_folder.test.ts @@ -200,7 +200,6 @@ describe("GET /folder", () => { expect(folders[0].size).toEqual(0); expect(folders[0].location).toBeDefined(); if (folders[0].location !== undefined) { - expect(folders[0].location.id).toEqual("1"); expect(folders[0].location.name).toEqual("Jean Valjean's House"); expect(folders[0].location.sublocation).toEqual("55 Rue Plumet"); expect(folders[0].location.city).toEqual("Paris"); diff --git a/packages/api/src/folder/controller/get_folder_children.test.ts b/packages/api/src/folder/controller/get_folder_children.test.ts index bc3321bb..ef7efd17 100644 --- a/packages/api/src/folder/controller/get_folder_children.test.ts +++ b/packages/api/src/folder/controller/get_folder_children.test.ts @@ -72,7 +72,6 @@ describe("GET /folder/{id}/children", () => { expect(folder.size).toEqual(0); expect(folder.location).toBeDefined(); if (folder.location !== undefined) { - expect(folder.location.id).toEqual("1"); expect(folder.location.name).toEqual("Jean Valjean's House"); expect(folder.location.sublocation).toEqual("55 Rue Plumet"); expect(folder.location.city).toEqual("Paris"); diff --git a/packages/api/src/folder/controller/utils_test.ts b/packages/api/src/folder/controller/utils_test.ts index efb1b54c..4c4d6ab3 100644 --- a/packages/api/src/folder/controller/utils_test.ts +++ b/packages/api/src/folder/controller/utils_test.ts @@ -4,7 +4,6 @@ export const loadFixtures = async (): Promise => { await db.sql("folder.fixtures.create_test_accounts"); await db.sql("folder.fixtures.create_test_archives"); await db.sql("folder.fixtures.create_test_account_archives"); - await db.sql("folder.fixtures.create_test_locns"); await db.sql("folder.fixtures.create_test_folders"); await db.sql("folder.fixtures.create_test_records"); await db.sql("folder.fixtures.create_test_files"); diff --git a/packages/api/src/folder/fixtures/create_test_folders.sql b/packages/api/src/folder/fixtures/create_test_folders.sql index 7dc48c8b..3e075f63 100644 --- a/packages/api/src/folder/fixtures/create_test_folders.sql +++ b/packages/api/src/folder/fixtures/create_test_folders.sql @@ -14,7 +14,6 @@ folder ( thumburl1000, thumburl2000, thumbnail256, - locnid, updateddt, description, displaydt, @@ -23,7 +22,20 @@ folder ( displaytimelowerbound, imageratio, sort, - view + view, + location_displayname, + location_streetnumber, + location_streetname, + location_locality, + location_admintwoname, + location_latitude, + location_longitude, + location_country, + location_countrycode, + location_sublocation, + location_postalcode, + location_altitudemeters, + location_locationprecision ) VALUES ( @@ -50,6 +62,18 @@ VALUES NULL, NULL, NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, NULL ), ( @@ -67,7 +91,6 @@ VALUES 'https://test-folder-thumbnail-1000', 'https://test-folder-thumbnail-2000', 'https://test-folder-thumbnail-256', - 1, '2025-01-01', 'A test folder', '2025-01-01', @@ -76,7 +99,20 @@ VALUES '2025-01-01', 1.00, 'sort.alphabetical_asc', - 'folder.view.grid' + 'folder.view.grid', + 'Jean Valjean''s House', + '55', + 'Rue Plumet', + 'Paris', + 'Ile-de-France', + 48.838608548520966, + 2.3069214988665303, + 'France', + 'FR', + '55 Rue Plumet', + '75007', + 35.0, + 'approximate' ), ( 3, @@ -102,6 +138,18 @@ VALUES NULL, NULL, NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, NULL ), ( @@ -128,6 +176,18 @@ VALUES NULL, NULL, NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, NULL ), ( @@ -154,6 +214,18 @@ VALUES NULL, NULL, NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, NULL ), ( @@ -180,6 +252,18 @@ VALUES NULL, NULL, NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, NULL ), ( @@ -206,6 +290,18 @@ VALUES NULL, NULL, NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, NULL ), ( @@ -232,6 +328,18 @@ VALUES NULL, NULL, NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, NULL ), ( @@ -258,6 +366,18 @@ VALUES NULL, NULL, NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, NULL ), ( @@ -282,8 +402,20 @@ VALUES NULL, NULL, NULL, - NULL, 'sort.alphabetical_asc', + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, NULL ), ( @@ -310,6 +442,18 @@ VALUES NULL, NULL, NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, NULL ), ( @@ -336,6 +480,18 @@ VALUES NULL, NULL, NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, NULL ), ( @@ -362,6 +518,18 @@ VALUES NULL, NULL, NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, NULL ), ( @@ -388,6 +556,18 @@ VALUES NULL, NULL, NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, NULL ), ( @@ -407,13 +587,25 @@ VALUES NULL, NULL, NULL, - NULL, '2020-01-01', NULL, NULL, '2025-01-01', NULL, NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, NULL ), ( @@ -433,12 +625,24 @@ VALUES NULL, NULL, NULL, - NULL, '2025-01-01', NULL, NULL, '2020-01-01', NULL, NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, NULL ); diff --git a/packages/api/src/folder/fixtures/create_test_locns.sql b/packages/api/src/folder/fixtures/create_test_locns.sql deleted file mode 100644 index b4a2cd2e..00000000 --- a/packages/api/src/folder/fixtures/create_test_locns.sql +++ /dev/null @@ -1,39 +0,0 @@ -INSERT INTO locn ( - locnid, - streetnumber, - streetname, - locality, - admintwoname, - latitude, - longitude, - country, - countrycode, - displayname, - name, - sublocation, - city, - postalcode, - altitudemeters, - locationprecision, - status, - type -) VALUES ( - 1, - '55', - 'Rue Plumet', - 'Paris', - 'Ile-de-France', - 48.838608548520966, - 2.3069214988665303, - 'France', - 'FR', - 'Jean Valjean''s House', - 'Jean Valjean''s House', - '55 Rue Plumet', - 'Paris', - '75007', - 35.0, - 'approximate', - 'status.generic.ok', - 'type.account.standard' -); diff --git a/packages/api/src/folder/queries/get_folders.sql b/packages/api/src/folder/queries/get_folders.sql index da3f7b2d..3e945505 100644 --- a/packages/api/src/folder/queries/get_folders.sql +++ b/packages/api/src/folder/queries/get_folders.sql @@ -244,40 +244,38 @@ SELECT THEN aggregated_pending_shares.pending_shares_as_json END AS "pendingShares", JSON_BUILD_OBJECT( - 'id', - locn.locnid::text, 'name', - locn.name, + folder.location_displayname, 'sublocation', - locn.sublocation, + folder.location_sublocation, 'city', - locn.city, + folder.location_locality, 'state', - locn.adminonename, + folder.location_adminonename, 'postalCode', - locn.postalcode, + folder.location_postalcode, 'country', - locn.country, + folder.location_country, 'latitude', - locn.latitude, + folder.location_latitude, 'longitude', - locn.longitude, + folder.location_longitude, 'altitudeMeters', - locn.altitudemeters, + folder.location_altitudemeters, 'precision', - locn.locationprecision, + folder.location_locationprecision, 'streetNumber', - locn.streetnumber, + folder.location_streetnumber, 'streetName', - locn.streetname, + folder.location_streetname, 'locality', - locn.locality, + folder.location_locality, 'county', - locn.admintwoname, + folder.location_admintwoname, 'countryCode', - locn.countrycode, + folder.location_countrycode, 'displayName', - locn.displayname + folder.location_displayname ) AS location, JSON_BUILD_OBJECT( 'id', @@ -350,10 +348,6 @@ LEFT JOIN aggregated_tags ON folder.folderid = aggregated_tags.refid -LEFT JOIN - locn - ON - folder.locnid = locn.locnid LEFT JOIN account_by_share ON diff --git a/packages/api/src/record/controller.test.ts b/packages/api/src/record/controller.test.ts index 07a1f79d..ce37c1da 100644 --- a/packages/api/src/record/controller.test.ts +++ b/packages/api/src/record/controller.test.ts @@ -26,7 +26,6 @@ const setupDatabase = async (): Promise => { await db.sql("record.fixtures.create_test_accounts"); await db.sql("record.fixtures.create_test_archives"); await db.sql("record.fixtures.create_test_account_archives"); - await db.sql("record.fixtures.create_test_locations"); await db.sql("record.fixtures.create_test_records"); await db.sql("record.fixtures.create_complete_test_record"); await db.sql("record.fixtures.create_test_folders"); @@ -117,7 +116,6 @@ describe("GET /records/:recordId", () => { updatedAt: "2023-06-21T00:00:00.000Z", altText: "An image", location: { - id: "1", name: "Jean Valjean's House", sublocation: "55 Rue Plumet", city: "Paris", @@ -719,36 +717,45 @@ describe("PATCH /records", () => { await agent.patch("/api/v2/records/1").expect(400); }); - test("expect location id is updated", async () => { + test("expect location is updated", async () => { await agent .patch("/api/v2/records/1") - .send({ locationId: 123 }) + .send({ + location: { + name: "Test Place", + city: "Pittsburgh", + latitude: 40.44, + longitude: -79.99, + }, + }) .expect(200); const result = await db.query( - "SELECT locnid FROM record WHERE recordId = :recordId", - { - recordId: 1, - }, + "SELECT location_displayname, location_locality, location_latitude, location_longitude " + + "FROM record WHERE recordId = :recordId", + { recordId: 1 }, ); - expect(result.rows[0]).toEqual({ locnid: "123" }); + expect(result.rows[0]).toEqual({ + location_displayname: "Test Place", + location_locality: "Pittsburgh", + location_latitude: 40.44, + location_longitude: -79.99, + }); }); - test("expect location id is updated when set to null", async () => { - await agent - .patch("/api/v2/records/8") - .send({ locationId: null }) - .expect(200); + test("expect location is cleared when set to null", async () => { + await agent.patch("/api/v2/records/8").send({ location: null }).expect(200); const result = await db.query( - "SELECT locnid FROM record WHERE recordId = :recordId", - { - recordId: 8, - }, + "SELECT location_displayname, location_latitude FROM record WHERE recordId = :recordId", + { recordId: 8 }, ); - expect(result.rows[0]).toStrictEqual({ locnid: null }); + expect(result.rows[0]).toStrictEqual({ + location_displayname: null, + location_latitude: null, + }); }); test("expect description is updated when set to null", async () => { @@ -767,11 +774,11 @@ describe("PATCH /records", () => { expect(result.rows[0]).toStrictEqual({ description: null }); }); - test("expect 400 error if location id is wrong type", async () => { + test("expect 400 error if location is wrong type", async () => { await agent .patch("/api/v2/records/1") .send({ - locationId: false, + location: false, }) .expect(400); }); @@ -835,7 +842,7 @@ describe("PATCH /records", () => { await agent .patch("/api/v2/records/1") - .send({ locationId: 123 }) + .send({ location: { name: "Test" } }) .expect(500); expect(logger.error).toHaveBeenCalledWith(testError); @@ -862,8 +869,21 @@ describe("PATCH /records", () => { .calledWith("record.queries.update_record", { recordId: "1", displayName: undefined, - locationId: 123, - setLocationIdToNull: false, + setLocationToNull: false, + locationDisplayName: "Test", + locationSublocation: null, + locationLocality: null, + locationAdminOneName: null, + locationAdminTwoName: null, + locationPostalCode: null, + locationCountry: null, + locationCountryCode: null, + locationStreetNumber: null, + locationStreetName: null, + locationLatitude: null, + locationLongitude: null, + locationAltitudeMeters: null, + locationLocationPrecision: null, description: undefined, setDescriptionToNull: false, displayTime: undefined, @@ -873,7 +893,7 @@ describe("PATCH /records", () => { await agent .patch("/api/v2/records/1") - .send({ locationId: 123 }) + .send({ location: { name: "Test" } }) .expect(500); expect(logger.error).toHaveBeenCalledWith(testError); @@ -886,7 +906,7 @@ describe("PATCH /records", () => { ); await agent .patch("/api/v2/records/1") - .send({ locationId: 123 }) + .send({ location: { name: "Test" } }) .expect(403); }); @@ -897,7 +917,7 @@ describe("PATCH /records", () => { ); await agent .patch("/api/v2/records/1") - .send({ locationId: 123 }) + .send({ location: { name: "Test" } }) .expect(404); }); @@ -921,8 +941,21 @@ describe("PATCH /records", () => { .calledWith("record.queries.update_record", { recordId: "1", displayName: undefined, - locationId: 123, - setLocationIdToNull: false, + setLocationToNull: false, + locationDisplayName: "Test", + locationSublocation: null, + locationLocality: null, + locationAdminOneName: null, + locationAdminTwoName: null, + locationPostalCode: null, + locationCountry: null, + locationCountryCode: null, + locationStreetNumber: null, + locationStreetName: null, + locationLatitude: null, + locationLongitude: null, + locationAltitudeMeters: null, + locationLocationPrecision: null, description: undefined, setDescriptionToNull: false, displayTime: undefined, @@ -936,7 +969,7 @@ describe("PATCH /records", () => { await agent .patch("/api/v2/records/1") - .send({ locationId: 123 }) + .send({ location: { name: "Test" } }) .expect(404); }); @@ -981,12 +1014,12 @@ describe("PATCH /records", () => { .send({ displayName: "All Fields Name", description: "All fields description", - locationId: 456, + location: { name: "All Fields Place" }, }) .expect(200); const result = await db.query( - "SELECT displayname, description, locnid FROM record WHERE recordId = :recordId", + "SELECT displayname, description, location_displayname FROM record WHERE recordId = :recordId", { recordId: 1, }, @@ -995,7 +1028,7 @@ describe("PATCH /records", () => { expect(result.rows[0]).toEqual({ displayname: "All Fields Name", description: "All fields description", - locnid: "456", + location_displayname: "All Fields Place", }); }); diff --git a/packages/api/src/record/fixtures/create_complete_test_record.sql b/packages/api/src/record/fixtures/create_complete_test_record.sql index aec213cd..53ed0327 100644 --- a/packages/api/src/record/fixtures/create_complete_test_record.sql +++ b/packages/api/src/record/fixtures/create_complete_test_record.sql @@ -24,8 +24,20 @@ record ( createddt, updateddt, alttext, - locnid, - originalfilecreationtime + originalfilecreationtime, + location_displayname, + location_streetnumber, + location_streetname, + location_locality, + location_admintwoname, + location_latitude, + location_longitude, + location_country, + location_countrycode, + location_sublocation, + location_postalcode, + location_altitudemeters, + location_locationprecision ) VALUES ( @@ -53,6 +65,18 @@ VALUES '2023-06-21T00:00:00.000Z', '2023-06-21T00:00:00.000Z', 'An image', - 1, - '1985-04-12T23:20:30Z' + '1985-04-12T23:20:30Z', + 'Jean Valjean''s House', + '55', + 'Rue Plumet', + 'Paris', + 'Ile-de-France', + 48.838608548520966, + 2.3069214988665303, + 'France', + 'FR', + '55 Rue Plumet', + '75007', + 35.0, + 'approximate' ); diff --git a/packages/api/src/record/fixtures/create_test_locations.sql b/packages/api/src/record/fixtures/create_test_locations.sql deleted file mode 100644 index b4a2cd2e..00000000 --- a/packages/api/src/record/fixtures/create_test_locations.sql +++ /dev/null @@ -1,39 +0,0 @@ -INSERT INTO locn ( - locnid, - streetnumber, - streetname, - locality, - admintwoname, - latitude, - longitude, - country, - countrycode, - displayname, - name, - sublocation, - city, - postalcode, - altitudemeters, - locationprecision, - status, - type -) VALUES ( - 1, - '55', - 'Rue Plumet', - 'Paris', - 'Ile-de-France', - 48.838608548520966, - 2.3069214988665303, - 'France', - 'FR', - 'Jean Valjean''s House', - 'Jean Valjean''s House', - '55 Rue Plumet', - 'Paris', - '75007', - 35.0, - 'approximate', - 'status.generic.ok', - 'type.account.standard' -); diff --git a/packages/api/src/record/models.ts b/packages/api/src/record/models.ts index 034a34af..05472464 100644 --- a/packages/api/src/record/models.ts +++ b/packages/api/src/record/models.ts @@ -113,7 +113,7 @@ export interface ArchiveFile { export interface PatchRecordRequest { emailFromAuthToken: string; - locationId?: bigint | null; + location?: Location | null; description?: string | null; displayName?: string; displayTime?: string | null; diff --git a/packages/api/src/record/queries/get_records.sql b/packages/api/src/record/queries/get_records.sql index b6455648..3088deed 100644 --- a/packages/api/src/record/queries/get_records.sql +++ b/packages/api/src/record/queries/get_records.sql @@ -243,40 +243,38 @@ SELECT DISTINCT ON (record.recordid) record.thumbnail256 ) AS "thumbnailUrls", JSON_BUILD_OBJECT( - 'id', - locn.locnid::TEXT, 'name', - locn.name, + record.location_displayname, 'sublocation', - locn.sublocation, + record.location_sublocation, 'city', - locn.city, + record.location_locality, 'state', - locn.adminonename, + record.location_adminonename, 'postalCode', - locn.postalcode, + record.location_postalcode, 'country', - locn.country, + record.location_country, 'latitude', - locn.latitude, + record.location_latitude, 'longitude', - locn.longitude, + record.location_longitude, 'altitudeMeters', - locn.altitudemeters, + record.location_altitudemeters, 'precision', - locn.locationprecision, + record.location_locationprecision, 'streetNumber', - locn.streetnumber, + record.location_streetnumber, 'streetName', - locn.streetname, + record.location_streetname, 'locality', - locn.locality, + record.location_locality, 'county', - locn.admintwoname, + record.location_admintwoname, 'countryCode', - locn.countrycode, + record.location_countrycode, 'displayName', - locn.displayname + record.location_displayname ) AS location, JSON_BUILD_OBJECT( 'id', @@ -315,9 +313,6 @@ INNER JOIN LEFT JOIN aggregated_tags ON record.recordid = aggregated_tags.refid -LEFT JOIN - locn - ON record.locnid = locn.locnid INNER JOIN folder_link ON diff --git a/packages/api/src/record/queries/update_record.sql b/packages/api/src/record/queries/update_record.sql index 0ca0cbe5..66f46e8d 100644 --- a/packages/api/src/record/queries/update_record.sql +++ b/packages/api/src/record/queries/update_record.sql @@ -2,9 +2,61 @@ UPDATE record SET displayname = COALESCE(:displayName, displayname), - locnid = CASE - WHEN :setLocationIdToNull THEN NULL - ELSE COALESCE(:locationId, locnid) + location_displayname = CASE + WHEN :setLocationToNull THEN NULL + ELSE COALESCE(:locationDisplayName, location_displayname) + END, + location_sublocation = CASE + WHEN :setLocationToNull THEN NULL + ELSE COALESCE(:locationSublocation, location_sublocation) + END, + location_locality = CASE + WHEN :setLocationToNull THEN NULL + ELSE COALESCE(:locationLocality, location_locality) + END, + location_adminonename = CASE + WHEN :setLocationToNull THEN NULL + ELSE COALESCE(:locationAdminOneName, location_adminonename) + END, + location_admintwoname = CASE + WHEN :setLocationToNull THEN NULL + ELSE COALESCE(:locationAdminTwoName, location_admintwoname) + END, + location_postalcode = CASE + WHEN :setLocationToNull THEN NULL + ELSE COALESCE(:locationPostalCode, location_postalcode) + END, + location_country = CASE + WHEN :setLocationToNull THEN NULL + ELSE COALESCE(:locationCountry, location_country) + END, + location_countrycode = CASE + WHEN :setLocationToNull THEN NULL + ELSE COALESCE(:locationCountryCode, location_countrycode) + END, + location_streetnumber = CASE + WHEN :setLocationToNull THEN NULL + ELSE COALESCE(:locationStreetNumber, location_streetnumber) + END, + location_streetname = CASE + WHEN :setLocationToNull THEN NULL + ELSE COALESCE(:locationStreetName, location_streetname) + END, + location_latitude = CASE + WHEN :setLocationToNull THEN NULL + ELSE COALESCE(:locationLatitude, location_latitude) + END, + location_longitude = CASE + WHEN :setLocationToNull THEN NULL + ELSE COALESCE(:locationLongitude, location_longitude) + END, + location_altitudemeters = CASE + WHEN :setLocationToNull THEN NULL + ELSE COALESCE(:locationAltitudeMeters, location_altitudemeters) + END, + location_locationprecision = CASE + WHEN :setLocationToNull THEN NULL + ELSE COALESCE(:locationLocationPrecision, location_locationprecision) END, description = CASE WHEN :setDescriptionToNull THEN NULL diff --git a/packages/api/src/record/service.ts b/packages/api/src/record/service.ts index b73d6522..a41f7466 100644 --- a/packages/api/src/record/service.ts +++ b/packages/api/src/record/service.ts @@ -10,6 +10,31 @@ import { getRecordAccessRole, accessRoleLessThan } from "../access/permission"; import { AccessRole } from "../access/models"; import { shareLinkService } from "../share_link/service"; import type { ShareLink } from "../share_link/models"; +import type { Location } from "../common/models"; + +const orNull = (v: T | undefined | null): T | null => v ?? null; + +const buildLocationUpdateParams = ( + location: Location | undefined, +): Record => { + const loc = location ?? {}; + return { + locationDisplayName: orNull(loc.displayName ?? loc.name), + locationSublocation: orNull(loc.sublocation), + locationLocality: orNull(loc.locality ?? loc.city), + locationAdminOneName: orNull(loc.state), + locationAdminTwoName: orNull(loc.county), + locationPostalCode: orNull(loc.postalCode), + locationCountry: orNull(loc.country), + locationCountryCode: orNull(loc.countryCode), + locationStreetNumber: orNull(loc.streetNumber), + locationStreetName: orNull(loc.streetName), + locationLatitude: orNull(loc.latitude), + locationLongitude: orNull(loc.longitude), + locationAltitudeMeters: orNull(loc.altitudeMeters), + locationLocationPrecision: orNull(loc.precision), + }; +}; export const getRecords = async (requestQuery: { recordIds: string[] | undefined; @@ -72,12 +97,14 @@ export const patchRecord = async ( ): Promise => { await validateCanPatchRecord(recordId, recordData.emailFromAuthToken); + const setLocationToNull = recordData.location === null; + const location = recordData.location ?? undefined; const result = await db .sql("record.queries.update_record", { recordId, displayName: recordData.displayName, - locationId: recordData.locationId, - setLocationIdToNull: recordData.locationId === null, + setLocationToNull, + ...buildLocationUpdateParams(location), description: recordData.description, setDescriptionToNull: recordData.description === null, displayTime: recordData.displayTime, diff --git a/packages/api/src/record/validators.test.ts b/packages/api/src/record/validators.test.ts index a533f3ff..73ca25bf 100644 --- a/packages/api/src/record/validators.test.ts +++ b/packages/api/src/record/validators.test.ts @@ -7,7 +7,7 @@ describe("validatePatchRecordRequest", () => { validatePatchRecordRequest({ emailFromAuthToken: "user@example.com", userSubjectFromAuthToken: "5c3473b6-cf2e-4c55-a80e-8db51d1bc5fd", - locationId: 123, + location: { name: "Pittsburgh" }, description: "description", }); } catch (err) { @@ -22,7 +22,7 @@ describe("validatePatchRecordRequest", () => { validatePatchRecordRequest({ emailFromAuthToken: "user@example.com", userSubjectFromAuthToken: "5c3473b6-cf2e-4c55-a80e-8db51d1bc5fd", - locationId: 123, + location: { name: "Pittsburgh" }, }); } catch (err) { error = err; @@ -36,7 +36,7 @@ describe("validatePatchRecordRequest", () => { validatePatchRecordRequest({ emailFromAuthToken: "user@example.com", userSubjectFromAuthToken: "5c3473b6-cf2e-4c55-a80e-8db51d1bc5fd", - locationId: null, + location: null, description: null, }); } catch (err) { @@ -46,13 +46,13 @@ describe("validatePatchRecordRequest", () => { } }); - test("should raise an error if locationId is wrong type", () => { + test("should raise an error if location is wrong type", () => { let error = null; try { validatePatchRecordRequest({ emailFromAuthToken: "user@example.com", userSubjectFromAuthToken: "5c3473b6-cf2e-4c55-a80e-8db51d1bc5fd", - locationId: true, + location: true, }); } catch (err) { error = err; diff --git a/packages/api/src/record/validators.ts b/packages/api/src/record/validators.ts index 350530a2..b80fcd6d 100644 --- a/packages/api/src/record/validators.ts +++ b/packages/api/src/record/validators.ts @@ -42,7 +42,30 @@ export const validatePatchRecordRequest: ( const validation = Joi.object() .keys({ ...fieldsFromUserAuthentication, - locationId: Joi.number().integer().optional().allow(null), + location: Joi.object() + .keys({ + name: Joi.string().optional().allow(null), + sublocation: Joi.string().optional().allow(null), + city: Joi.string().optional().allow(null), + state: Joi.string().optional().allow(null), + postalCode: Joi.string().optional().allow(null), + country: Joi.string().optional().allow(null), + latitude: Joi.number().optional().allow(null), + longitude: Joi.number().optional().allow(null), + altitudeMeters: Joi.number().optional().allow(null), + precision: Joi.string() + .valid("approximate", "uncertain", "unknown") + .optional() + .allow(null), + streetNumber: Joi.string().optional().allow(null), + streetName: Joi.string().optional().allow(null), + locality: Joi.string().optional().allow(null), + county: Joi.string().optional().allow(null), + countryCode: Joi.string().optional().allow(null), + displayName: Joi.string().optional().allow(null), + }) + .optional() + .allow(null), description: Joi.string().optional().allow(null), displayName: Joi.string().min(1).optional(), displayTime: Joi.string() @@ -59,7 +82,7 @@ export const validatePatchRecordRequest: ( }) // We can't use .min(1) here due to the auth fields being in the body // See: https://github.com/PermanentOrg/stela/issues/407 - .or("locationId", "description", "displayName", "displayTime") + .or("location", "description", "displayName", "displayTime") .unknown(false) .validate(data);