diff --git a/CHANGELOG.md b/CHANGELOG.md index 045545b..3f36fa1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,44 @@ # Changelog +## [3.6.0-1](https://github.com/agrc/plss/compare/v3.5.20...v3.6.0-1) (2025-02-06) + + +### Features + +* **api:** migrate to v2 functions and triggers ([f544082](https://github.com/agrc/plss/commit/f544082dbecc0a75258e6a0467f6b45c54f99a59)) + + +### Bug Fixes + +* **api:** replace undefined as its not a valid type ([ce5d3b5](https://github.com/agrc/plss/commit/ce5d3b53550ba888895c2074388fe984f8499983)) +* correct a bug where files uploaded with periods break the preview ([682ca0b](https://github.com/agrc/plss/commit/682ca0b12985afc26320156a46f1ca833d3e9667)) +* correct login avatar fallback missing image ([60034f2](https://github.com/agrc/plss/commit/60034f248e8ed3ee5516e6379322527fa8acb2ef)) +* update to node.js 22 runtime ([ca2e29f](https://github.com/agrc/plss/commit/ca2e29f736ab1fe4059485ea06cccfae396f9bcc)) + + +### Styles + +* add missing bg to text areas ([7f232ac](https://github.com/agrc/plss/commit/7f232ac62b8b1f6d4067f1431b924b5e8093c723)) + +## [3.6.0-0](https://github.com/agrc/plss/compare/v3.5.20...v3.6.0-0) (2025-02-06) + + +### Features + +* **api:** migrate to v2 functions and triggers ([532ef1c](https://github.com/agrc/plss/commit/532ef1c2592b927f9f29397968eeb400bc2e55cc)) + + +### Bug Fixes + +* **api:** replace undefined as its not a valid type ([ce5d3b5](https://github.com/agrc/plss/commit/ce5d3b53550ba888895c2074388fe984f8499983)) +* correct a bug where files uploaded with periods break the preview ([682ca0b](https://github.com/agrc/plss/commit/682ca0b12985afc26320156a46f1ca833d3e9667)) +* correct login avatar fallback missing image ([60034f2](https://github.com/agrc/plss/commit/60034f248e8ed3ee5516e6379322527fa8acb2ef)) + + +### Styles + +* add missing bg to text areas ([7f232ac](https://github.com/agrc/plss/commit/7f232ac62b8b1f6d4067f1431b924b5e8093c723)) + ## [3.5.20](https://github.com/agrc/plss/compare/v3.5.19...v3.5.20) (2025-02-05) diff --git a/firebase.json b/firebase.json index a2ed94d..360d3ed 100644 --- a/firebase.json +++ b/firebase.json @@ -2,7 +2,7 @@ "functions": [ { "source": "functions", - "runtime": "nodejs20", + "runtime": "nodejs22", "codebase": "default", "ignore": ["node_modules", ".git", "firebase-debug.log", "firebase-debug.*.log"] } diff --git a/functions/auth/onCreate.js b/functions/auth/onCreate.js index d147363..4000169 100644 --- a/functions/auth/onCreate.js +++ b/functions/auth/onCreate.js @@ -1,4 +1,4 @@ -import { logger } from 'firebase-functions/v1'; +import { logger } from 'firebase-functions/v2'; import { getFirestore } from 'firebase-admin/firestore'; const db = getFirestore(); diff --git a/functions/database/submissions/onCancelSubmission.js b/functions/database/submissions/onCancelSubmission.js index cb58368..3a3234f 100644 --- a/functions/database/submissions/onCancelSubmission.js +++ b/functions/database/submissions/onCancelSubmission.js @@ -1,4 +1,4 @@ -import { logger } from 'firebase-functions/v1'; +import { logger } from 'firebase-functions/v2'; import { getFirestore } from 'firebase-admin/firestore'; import { safelyInitializeApp } from '../../firebase.js'; import { getContactsToNotify, notify } from '../../emailHelpers.js'; diff --git a/functions/database/submissions/onCreateAddLocation.js b/functions/database/submissions/onCreateAddLocation.js index 0c91d32..e537ad1 100644 --- a/functions/database/submissions/onCreateAddLocation.js +++ b/functions/database/submissions/onCreateAddLocation.js @@ -1,4 +1,4 @@ -import { logger } from 'firebase-functions/v1'; +import { logger } from 'firebase-functions/v2'; import { getFirestore, GeoPoint } from 'firebase-admin/firestore'; import ky from 'ky'; import { safelyInitializeApp } from '../../firebase.js'; @@ -8,6 +8,8 @@ safelyInitializeApp(); const client = ky.extend({ prefixUrl: 'https://services1.arcgis.com/99lidPhWCzftIe9K/arcgis/rest/services', + timeout: 30000, + retry: 3, }); const getLocationFromId = async (id) => { diff --git a/functions/database/submissions/onCreateMonument.js b/functions/database/submissions/onCreateMonument.js index 1a36c8e..f4478e6 100644 --- a/functions/database/submissions/onCreateMonument.js +++ b/functions/database/submissions/onCreateMonument.js @@ -1,4 +1,4 @@ -import { logger } from 'firebase-functions/v1'; +import { logger } from 'firebase-functions/v2'; import { getFirestore } from 'firebase-admin/firestore'; import { getStorage } from 'firebase-admin/storage'; import { diff --git a/functions/emailHelpers.js b/functions/emailHelpers.js index 8e34b19..f9ccd68 100644 --- a/functions/emailHelpers.js +++ b/functions/emailHelpers.js @@ -1,6 +1,6 @@ import client from '@sendgrid/client'; import { Base64Encode } from 'base64-stream'; -import { logger } from 'firebase-functions/v1'; +import { logger } from 'firebase-functions/v2'; export const notify = (key, template) => { if (process.env.NODE_ENV !== 'production') { diff --git a/functions/https/getMyContent.js b/functions/https/getMyContent.js index c45898e..c7bb612 100644 --- a/functions/https/getMyContent.js +++ b/functions/https/getMyContent.js @@ -1,4 +1,4 @@ -import { logger } from 'firebase-functions/v1'; +import { logger } from 'firebase-functions/v2'; import { getFirestore } from 'firebase-admin/firestore'; import { graphicConverter, myContentConverter } from '../converters.js'; import { safelyInitializeApp } from '../firebase.js'; diff --git a/functions/https/getMyProfile.js b/functions/https/getMyProfile.js index 9a16134..7933817 100644 --- a/functions/https/getMyProfile.js +++ b/functions/https/getMyProfile.js @@ -1,4 +1,4 @@ -import { https, logger } from 'firebase-functions/v1'; +import { https, logger } from 'firebase-functions/v2'; import { getFirestore } from 'firebase-admin/firestore'; import { safelyInitializeApp } from '../firebase.js'; diff --git a/functions/https/postCancelCorner.js b/functions/https/postCancelCorner.js index 4aa39d6..0059500 100644 --- a/functions/https/postCancelCorner.js +++ b/functions/https/postCancelCorner.js @@ -1,4 +1,4 @@ -import { https, logger } from 'firebase-functions/v1'; +import { https, logger } from 'firebase-functions/v2'; import { getFirestore } from 'firebase-admin/firestore'; import { getStorage } from 'firebase-admin/storage'; import { safelyInitializeApp } from '../firebase.js'; diff --git a/functions/https/postCorner.js b/functions/https/postCorner.js index 1616718..7f1a614 100644 --- a/functions/https/postCorner.js +++ b/functions/https/postCorner.js @@ -1,4 +1,4 @@ -import { https, logger } from 'firebase-functions/v1'; +import { https, logger } from 'firebase-functions/v2'; import { getFirestore, GeoPoint } from 'firebase-admin/firestore'; import { parseDms } from 'dms-conversion'; import * as schemas from '../shared/cornerSubmission/Schema.js'; @@ -159,7 +159,7 @@ export const formatNewCorner = (data, metadata) => { zone: data.grid.zone, unit: data.grid.unit, elevation: data.grid.elevation, - verticalDatum: data.grid.verticalDatum, + verticalDatum: data.grid.verticalDatum ?? null, }, geographic: { northing: { @@ -208,7 +208,7 @@ export const formatExistingCorner = (data, metadata) => { zone: data.grid.zone, unit: data.grid.unit, elevation: data.grid.elevation, - verticalDatum: data.grid.verticalDatum, + verticalDatum: data.grid.verticalDatum ?? null, }, geographic: { northing: { diff --git a/functions/https/postGeneratePreview.js b/functions/https/postGeneratePreview.js index c7d3a18..80b3256 100644 --- a/functions/https/postGeneratePreview.js +++ b/functions/https/postGeneratePreview.js @@ -1,6 +1,6 @@ import { getFirestore } from 'firebase-admin/firestore'; import { getStorage } from 'firebase-admin/storage'; -import { https, logger } from 'firebase-functions/v1'; +import { https, logger } from 'firebase-functions/v2'; import { createPdfDocument, generatePdfDefinition, diff --git a/functions/https/postProfile.js b/functions/https/postProfile.js index 8b968aa..6522f9c 100644 --- a/functions/https/postProfile.js +++ b/functions/https/postProfile.js @@ -1,4 +1,4 @@ -import { https, logger } from 'firebase-functions/v1'; +import { https, logger } from 'firebase-functions/v2'; import { getFirestore } from 'firebase-admin/firestore'; import { profileSchema } from '../shared/cornerSubmission/Schema.js'; import { safelyInitializeApp } from '../firebase.js'; diff --git a/functions/index.js b/functions/index.js index f745837..15ec570 100644 --- a/functions/index.js +++ b/functions/index.js @@ -1,17 +1,21 @@ +import { logger } from 'firebase-functions/v2'; import { - auth, - logger, - firestore, - runWith, - storage, -} from 'firebase-functions/v1'; // v2 does not support auth triggers as of july/23 + onDocumentUpdated, + onDocumentCreated, + onDocumentDeleted, +} from 'firebase-functions/v2/firestore'; +import { + onObjectFinalized, + onObjectDeleted, +} from 'firebase-functions/v2/storage'; +import { beforeUserCreated } from 'firebase-functions/v2/identity'; import { https } from 'firebase-functions/v2'; -import { safelyInitializeApp } from './firebase.js'; import { defineSecret } from 'firebase-functions/params'; +import { safelyInitializeApp } from './firebase.js'; const cors = [ /localhost/, - /ut-dts-agrc-web-api-dev-self-service\.web\.app$/, + /ut-dts-agrc-plss-dev\.web\.app$/, /plss\.(?:dev\.)?utah\.gov/, ]; @@ -35,11 +39,11 @@ const config = safelyInitializeApp(); const sendGridApiKey = defineSecret('SENDGRID_API_KEY'); // Firebase authentication -export const onCreateUser = auth.user().onCreate(async (user) => { +export const onCreateUser = beforeUserCreated(async (event) => { logger.debug('[auth::user::onCreate] importing createUser'); const createUser = (await import('./auth/onCreate.js')).createUser; - const result = await createUser(user); + const result = await createUser(event.data); logger.debug('[auth::user::onCreate]', result); @@ -47,15 +51,20 @@ export const onCreateUser = auth.user().onCreate(async (user) => { }); // Firestore triggers -export const onCancelSubmission = runWith({ secrets: [sendGridApiKey] }) - .firestore.document('/submissions/{docId}') - .onUpdate(async (change, context) => { - const current = change.after.get('status.user.cancelled'); - const previous = change.before.get('status.user.cancelled'); +export const onCancelSubmission = onDocumentUpdated( + { + document: '/submissions/{docId}', + secrets: [sendGridApiKey], + }, + async (event) => { + const after = event.data.after.data(); + const current = after.status.user.cancelled; + const before = event.data.before.data(); + const previous = before.status.user.cancelled; logger.debug( '[database::submissions::onCancel] trigger: submission document updated', - context.params.docId, + event.params.docId, { structuredData: true, }, @@ -99,17 +108,18 @@ export const onCancelSubmission = runWith({ secrets: [sendGridApiKey] }) structuredData: true, }); - const result = await cancelSubmission(change.before); + const result = await cancelSubmission(event.data.before); logger.debug('[database::submissions::onCancel]', result); return result; - }); + }, +); -export const onCreateAddLocation = firestore - .document('/submissions/{docId}') - .onCreate(async (snap, context) => { - const record = snap.data(); +export const onCreateAddLocation = onDocumentCreated( + '/submissions/{docId}', + async (event) => { + const record = event.data.data(); if (record.type === 'new') { logger.debug( @@ -143,23 +153,27 @@ export const onCreateAddLocation = firestore ); const result = await createAddLocation( - context.params.docId, + event.params.docId, record.blm_point_id, ); logger.debug('[database::submissions::onCreateAddLocation]', result); return result; - }); + }, +); -export const onCreateMonumentRecord = runWith({ memory: '512MB' }) - .firestore.document('/submissions/{docId}') - .onCreate(async (snap, context) => { - const record = snap.data(); +export const onCreateMonumentRecord = onDocumentCreated( + { + document: '/submissions/{docId}', + memory: '512MiB', + }, + async (event) => { + const record = event.data.data(); logger.debug( '[database::submissions::onCreateMonumentRecord] trigger: new submission for', - context.params.docId, + event.params.docId, record.type, { structuredData: true, @@ -173,21 +187,22 @@ export const onCreateMonumentRecord = runWith({ memory: '512MB' }) await import('./database/submissions/onCreateMonument.js') ).createMonumentRecord; - const result = await createMonumentRecord(record, context.params.docId); + const result = await createMonumentRecord(record, event.params.docId); logger.debug('[database::submissions::onCreateMonumentRecord]', result); return result; - }); + }, +); -export const onCleanUpPointAttachments = firestore - .document('/submitters/{userId}/points/{docId}') - .onDelete(async (snap, context) => { - const record = snap.data(); +export const onCleanUpPointAttachments = onDocumentDeleted( + '/submitters/{userId}/points/{docId}', + async (event) => { + const record = event.data.data(); logger.debug( '[database::submitters::onCleanUpPointAttachments] trigger: point deleted', - context, + event, record, { structuredData: true, @@ -222,7 +237,8 @@ export const onCleanUpPointAttachments = firestore logger.debug('[database::submissions::onCleanUpPointAttachments]', result); return result; - }); + }, +); // HTTPS triggers export const getMyContent = https.onCall({ cors }, async (request) => { @@ -289,7 +305,7 @@ export const postCorner = https.onCall({ cors }, async ({ data, auth }) => { }); export const postGeneratePreview = https.onCall( - { cors, memory: '512MB' }, + { cors, memory: '512MiB' }, async ({ data, auth }) => { logger.debug('[https::postGeneratePreview] starting'); @@ -335,11 +351,13 @@ export const postProfile = https.onCall(async ({ data, auth }) => { }); // Storage triggers -export const onCreateNotify = runWith({ secrets: [sendGridApiKey] }) - .storage.bucket(config.storageBucket) - .object() - .onFinalize(async (object) => { - const { name, contentType, bucket: fileBucket } = object; +export const onCreateNotify = onObjectFinalized( + { + secrets: [sendGridApiKey], + bucket: config.storageBucket, + }, + async (event) => { + const { name, contentType, bucket: fileBucket } = event.data; logger.debug( '[storage::finalize::onCreateNotify] trigger: storage object created', @@ -382,13 +400,13 @@ export const onCreateNotify = runWith({ secrets: [sendGridApiKey] }) logger.debug('[storage::finalize::onCreateNotify]', result); return result; - }); + }, +); -export const syncProfileImage = storage - .bucket(config.storageBucket) - .object() - .onDelete(async (object) => { - const { name } = object; +export const syncProfileImage = onObjectDeleted( + { bucket: config.storageBucket }, + async (event) => { + const { name } = event.data; logger.debug( '[storage::onDelete::syncProfileImage] trigger: storage object deleted', @@ -421,4 +439,5 @@ export const syncProfileImage = storage logger.debug('[storage::onDelete::syncProfileImage]', result); return result; - }); + }, +); diff --git a/functions/package-lock.json b/functions/package-lock.json index 5fdd415..a413b4a 100644 --- a/functions/package-lock.json +++ b/functions/package-lock.json @@ -24,7 +24,7 @@ "vitest": "^3.0.5" }, "engines": { - "node": "20" + "node": "22" } }, "node_modules/@ampproject/remapping": { diff --git a/functions/package.json b/functions/package.json index d67cc67..9e1efe3 100644 --- a/functions/package.json +++ b/functions/package.json @@ -54,6 +54,6 @@ "semi": true }, "engines": { - "node": "20" + "node": "22" } } diff --git a/functions/pdfHelpers.js b/functions/pdfHelpers.js index 7357382..c206453 100644 --- a/functions/pdfHelpers.js +++ b/functions/pdfHelpers.js @@ -1,6 +1,6 @@ import { Base64Encode } from 'base64-stream'; import { Buffer } from 'buffer'; -import { logger } from 'firebase-functions/v1'; +import { logger } from 'firebase-functions/v2'; import path from 'path'; import { PDFDocument } from 'pdf-lib'; import PdfPrinter from 'pdfmake'; diff --git a/functions/storage/onCreateNotify.js b/functions/storage/onCreateNotify.js index d615ce5..567aa78 100644 --- a/functions/storage/onCreateNotify.js +++ b/functions/storage/onCreateNotify.js @@ -1,4 +1,4 @@ -import { logger } from 'firebase-functions/v1'; +import { logger } from 'firebase-functions/v2'; import { getStorage } from 'firebase-admin/storage'; import { getFirestore } from 'firebase-admin/firestore'; import { safelyInitializeApp } from '../firebase.js'; diff --git a/functions/storage/onDelete.js b/functions/storage/onDelete.js index 5e35b44..cb30c9c 100644 --- a/functions/storage/onDelete.js +++ b/functions/storage/onDelete.js @@ -1,4 +1,4 @@ -import { https, logger } from 'firebase-functions/v1'; +import { https, logger } from 'firebase-functions/v2'; import { getFirestore } from 'firebase-admin/firestore'; import { safelyInitializeApp } from '../firebase.js'; diff --git a/package-lock.json b/package-lock.json index 3e2c460..ddbca9e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ugrc/plss-app", - "version": "3.5.20", + "version": "3.6.0-1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ugrc/plss-app", - "version": "3.5.20", + "version": "3.6.0-1", "license": "MIT", "dependencies": { "@arcgis/core": "^4.31.6", @@ -91,7 +91,7 @@ "wait-on": "^8.0.2" }, "engines": { - "node": "20" + "node": "22" } }, "node_modules/@adobe/css-tools": { diff --git a/package.json b/package.json index 7a8733a..06ad6b6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ugrc/plss-app", - "version": "3.5.20", + "version": "3.6.0-1", "private": true, "description": "A React app for the UGRC PLSS", "license": "MIT", @@ -132,6 +132,6 @@ "wait-on": "^8.0.2" }, "engines": { - "node": "20" + "node": "22" } } diff --git a/src/components/formElements/FileUpload.jsx b/src/components/formElements/FileUpload.jsx index 4aaa7db..f7c0365 100644 --- a/src/components/formElements/FileUpload.jsx +++ b/src/components/formElements/FileUpload.jsx @@ -47,7 +47,7 @@ const FileUpload = ({ defaultFileName, path, contentTypes, maxFileSize, value, o } if (defaultFileName) { - const [, ext] = fileName.split('.'); + const ext = fileName.split('.').pop(); fileName = `${defaultFileName}.${ext}`; } diff --git a/src/components/formElements/LimitedTextarea.jsx b/src/components/formElements/LimitedTextarea.jsx index 0bc5ef1..d75255a 100644 --- a/src/components/formElements/LimitedTextarea.jsx +++ b/src/components/formElements/LimitedTextarea.jsx @@ -28,7 +28,7 @@ export const LimitedTextarea = ({ maxLength={limit} placeholder={placeholder} className={clsx( - 'rounded-sm border border-slate-400 px-2 text-sm text-slate-800 placeholder:text-sm placeholder:text-slate-600', + 'rounded-sm border border-slate-400 bg-white px-2 text-sm text-slate-800 placeholder:text-sm placeholder:text-slate-600', className, )} {...field} diff --git a/src/components/pageElements/Login.jsx b/src/components/pageElements/Login.jsx index 8ca6d8f..67be669 100644 --- a/src/components/pageElements/Login.jsx +++ b/src/components/pageElements/Login.jsx @@ -9,7 +9,7 @@ import Card from '../formElements/Card.jsx'; import usePageView from '../hooks/usePageView.jsx'; const size = 160; -const fallback = encodeURI('https://gis.utah.gov/images/plss_gcdb_lg.jpg'); +const fallback = 'mp'; export default function Login({ dispatch }) { const { data } = useSigninCheck(); @@ -109,7 +109,7 @@ Profile.propTypes = { }; const Gravatar = ({ email }) => { - const gravatar = `https://www.gravatar.com/avatar/${md5(email.toLowerCase())}?s=${size}&default=${fallback}`; + const gravatar = `https://www.gravatar.com/avatar/${md5(email.toLowerCase())}?r=pg&size=${size}&default=${fallback}`; return Gravatar; };