Skip to content

Commit

Permalink
IndexedDB and Firebase Emulator Suite dependency enhancements (#46)
Browse files Browse the repository at this point in the history
* Add Cypress support.

* Merged from upstream.

* Add Microsoft GitHub Codespaces configuration file dependencies.

* Format continuous integration and development configuration files.

* Update README.

* Refactor the client-side firebase-donations API getDonations() method .

* Add Dexie dependencies.

* Introduce id to Donation model (#45)

* Add Dexie relationships package dependency (#45)

* Update .gitignore allow SCM to track the package-lock.json file (#45)

* Refactor Firebase Emulator Suite setup (#45)

* Add blobToArrayBuffer() utility method (#45)

* Add Donation Cache Interface (#45)

* Remove versioning modifier from dexie dependency declaration (#45)

* Modify application dependencies; remove dexie-relationships as a dependency and add indexeddbshim as a dependency (#45)

* Update invocations to access the FIREBASE_CONFIG environment variable to conform to Next JS and Firebase Tools standards.

* Update package dependency versions (#45)

* Update the Next JS configuration file to set the FIREBASE_CONFIG environment variable (#45)

* Update firebase-donations.ts remove unused method _getDonations() (#45)

* Omit unused NEXT_PUBLIC_FIREBASE_CONFIG environment variable.
  • Loading branch information
NathanWEdwards committed Mar 7, 2024
1 parent 74e1ff7 commit ac8175d
Show file tree
Hide file tree
Showing 12 changed files with 128 additions and 124 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
/.pnp
.pnp.js
.cache
package-lock.json

# testing
/coverage
Expand Down
16 changes: 7 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# The Baby Equipment Exchange

## Introduction
## Introduction...

This project assists the collection and distribution of unused and gently used baby and child equipment. Over twenty different organizations are served by this exchange.
This project assists the collection and distribution of unused and gently used baby and child equipment. Over twenty dif.ferent organizations are served by this exchange.

## Dev remote Setup (Recommended for consistency, you can dev local if you don't want to work with docker)

Expand All @@ -26,11 +26,12 @@ docker run -dit -p 3000:3000 -p 4000:4000 -p 5000:5000 -p 4400:4400 -p 4500:4500
12. open folder and navigate to /home/user/projects/baby-equipment-exchange/
13. open a new terminal in VScode (verify that it's connected to the container not your host machine) the following command should start the emulators and the app:
```
export FIREBASE_CONFIG="$(cat /home/user/projects/baby-equipment-exchange/firebaseConfig.json)"
export NEXT_PUBLIC_FIREBASE_CONFIG="$(cat /home/user/projects/baby-equipment-exchange/firebaseConfig.json)"
npm run dev
```
13. see the output you can run in your host machine browser http://localhost:3000
14. see the output you can run in your host machine browser http://localhost:3000


## Dev Local Setup
Expand Down Expand Up @@ -73,12 +74,9 @@ sudo echo 'GOOGLE_APPLICATION_CREDENTIALS="/home/user/projects/baby-equipment-ex
sudo echo 'FIREBASE_EMULATORS_IMPORT_DIRECTORY="./data_directory"' >> /home/user/projects/baby-equipment-exchange/.env.local
sudo apt-get install jq
echo FIREBASE_CONFIG=\"$(jq -c . < firebaseConfig.json)\" >> .env.local
sudo echo NEXT_PUBLIC_FIREBASE_CONFIG=\"$(jq -c . < firebaseConfig.json)\" >> .env.local
export FIREBASE_EMULATORS_IMPORT_DIRECTORY="./data_directory"
export GOOGLE_APPLICATION_CREDENTIALS="/home/user/projects/baby-equipment-exchange/serviceAccount.json"
export FIREBASE_CONFIG="$(cat /home/user/projects/baby-equipment-exchange/firebaseConfig.json)"
export NEXT_PUBLIC_FIREBASE_CONFIG="$(cat /home/user/projects/baby-equipment-exchange/firebaseConfig.json)"
export FIREBASE_CONFIG="$(cat /home/user/projects/baby-equipment-exchange/firebaseConfig.json)"
```

3. install npm requirments and build project
Expand Down Expand Up @@ -168,7 +166,7 @@ Scroll to Custom claims. Claims should already be present. If the text field is

![Firebase Emulator Auth edit existing user](https://raw.githubusercontent.com/codeforbtv/baby-equipment-exchange/main/docs/images/account_creation_3.png)

(Clicking outside the Edit user pop-up closes it) Scroll the slider down and select the **\*\*\*\***Save**\*\*\*\*** button:
(Clicking outside the Edit user pop-up closes it) Scroll the slider down and select the **Save** button:

![Firebase Emulator Auth save button](https://raw.githubusercontent.com/codeforbtv/baby-equipment-exchange/main/docs/images/account_creation_3_5.png)

Expand Down
6 changes: 1 addition & 5 deletions docker_image_build_files/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,7 @@ COPY firebase_emulator_files/ ${REPO_DIR}/
WORKDIR ${REPO_DIR}
# Set default environment variables (these will be overwritten before build with terraform)
ARG FIREBASE_CONFIG
ARG NEXT_PUBLIC_FIREBASE_CONFIG
ENV FIREBASE_CONFIG=$FIREBASE_CONFIG
ENV NEXT_PUBLIC_FIREBASE_CONFIG=$NEXT_PUBLIC_FIREBASE_CONFIG
RUN chmod +x /home/user/projects/baby-equipment-exchange/


Expand All @@ -49,15 +47,13 @@ RUN touch /home/user/projects/baby-equipment-exchange/.env.local
# the below envinroment variables should be passed through terraform
RUN echo 'GOOGLE_APPLICATION_CREDENTIALS="/home/user/projects/baby-equipment-exchange/serviceAccount.json"' >> /home/user/projects/baby-equipment-exchange/.env.local
RUN echo 'FIREBASE_EMULATORS_IMPORT_DIRECTORY="./data_directory"' >> /home/user/projects/baby-equipment-exchange/.env.local
RUN echo FIREBASE_CONFIG=\"$(jq -c . < firebaseConfig.json)\" >> .env.local && \
echo NEXT_PUBLIC_FIREBASE_CONFIG=\"$(jq -c . < firebaseConfig.json)\" >> .env.local
RUN echo FIREBASE_CONFIG=\"$(jq -c . < firebaseConfig.json)\" >> .env.local
ENV FIREBASE_EMULATORS_IMPORT_DIRECTORY="./data_directory"
ENV GOOGLE_APPLICATION_CREDENTIALS="/home/user/projects/baby-equipment-exchange/serviceAccount.json"
ENV CYPRESS_INSTALL_BINARY=0

# Use a RUN command to export the environment variables
RUN export FIREBASE_CONFIG="$(cat /home/user/projects/baby-equipment-exchange/firebaseConfig.json | jq -c .)" && \
export NEXT_PUBLIC_FIREBASE_CONFIG="$(cat /home/user/projects/baby-equipment-exchange/firebaseConfig.json | jq -c .)" && \
npm install && \
cd /home/user/projects/baby-equipment-exchange/functions && \
npm install && \
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#!/bin/sh
export FIREBASE_CONFIG="$(cat /home/user/projects/baby-equipment-exchange/firebaseConfig.json)"
export NEXT_PUBLIC_FIREBASE_CONFIG="$(cat /home/user/projects/baby-equipment-exchange/firebaseConfig.json)"
# Execute the Docker CMD
exec "$@"
# Then open a new shell
Expand Down
2 changes: 1 addition & 1 deletion functions/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ type Event = {
modifiedAt: string;
};

const firebaseConfig = JSON.parse(process.env.NEXT_PUBLIC_FIREBASE_CONFIG ?? process.env.FIREBASE_CONFIG ?? '{}');
const firebaseConfig = JSON.parse(process.env.FIREBASE_CONFIG ?? '{}');

const app: App = admin.initializeApp({
credential: applicationDefault(),
Expand Down
6 changes: 6 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@

const FIREBASE_CONFIG = process.env.FIREBASE_CONFIG;

/** @type {import('next').NextConfig} */
const nextConfig = {
env: {
FIREBASE_CONFIG
},
experimental: {
serverActions: true
}
Expand Down
19 changes: 10 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "node ./src/utils/setup.cjs --cmd=\"next dev\"",
"dev": "node ./src/utils/setup.cjs --cmd=0",
"test:cypress": "npx cypress run --headless --component",
"test:unit": "node ./src/utils/setup.cjs --cmd=\"node ./node_modules/jest/bin/jest.js .*/.*\\.unit\\.test\\.ts.*\"",
"test:unit": "node ./src/utils/setup.cjs --cmd=1",
"build": "next build",
"start": "next start",
"lint": "next lint",
Expand Down Expand Up @@ -37,19 +37,20 @@
"@types/react-burger-menu": "^2.8.4",
"@types/react-dom": "18.2.8",
"autoprefixer": "10.4.16",
"dexie": "3.2.4",
"dexie-react-hooks": "^1.1.7",
"eslint": "8.51.0",
"dexie": "^3.2.4",
"dexie-react-hooks": "1.1.7",
"firebase": "^10.6.0",
"firebase-admin": "^11.11.0",
"firebase-functions": "^4.3.1",
"indexeddbshim": "13.0.0",
"next": "^13.5.0",
"postcss": "8.4.31",
"react": "18.2.0",
"react-burger-menu": "^3.0.9",
"react-dom": "18.2.0",
"sass": "1.69.5",
"tailwindcss": "3.3.3",
"tailwindcss": "3.4.1",
"typescript": "5.2.2",
"uuid": "^9.0.1"
},
Expand All @@ -59,9 +60,9 @@
"@testing-library/jest-dom": "^6.1.3",
"@testing-library/react": "^14.0.0",
"@types/jest": "^29.5.5",
"@types/node": "^20.8.2",
"@types/react": "^18.2.24",
"@types/react-dom": "^18.2.8",
"@types/node": "^20.11.6",
"@types/react": "^18.2.51",
"@types/react-dom": "^18.2.18",
"@types/react-redux": "^7.1.27",
"@types/uuid": "^9.0.4",
"@typescript-eslint/eslint-plugin": "^6.7.4",
Expand All @@ -80,6 +81,6 @@
"prettier": "^3.0.0",
"puppeteer": "^21.3.8",
"ts-node": "^10.9.1",
"typescript": "^5.2.2"
"typescript": "^5.3.3"
}
}
120 changes: 48 additions & 72 deletions src/api/firebase-donations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const DONATION_DETAILS_COLLECTION = 'DonationDetails';
const donationConverter = {
toFirestore(donation: Donation): DocumentData {
const donationData: IDonation = {
id: donation.getId(),
category: donation.getCategory(),
brand: donation.getBrand(),
model: donation.getModel(),
Expand All @@ -52,6 +53,7 @@ const donationConverter = {
fromFirestore(snapshot: QueryDocumentSnapshot, options: SnapshotOptions): Donation {
const data = snapshot.data(options);
const donationData: IDonation = {
id: data.id,
category: data.category,
brand: data.brand,
model: data.model,
Expand Down Expand Up @@ -175,80 +177,54 @@ export async function getDonations(filter: null | undefined): Promise<Donation[]
return Promise.reject();
}

async function _getDonations(...donationDetails: DonationDetail[]): Promise<Donation[]> {
const donations: Donation[] = [];
for (const donationDetail of donationDetails) {
const donationRef = donationDetail.getDonation().withConverter(donationConverter);
const donationSnapshot = await getDoc(donationRef);
if (donationSnapshot.exists()) {
const donation = donationSnapshot.data();
const imagesRef: DocumentReference<Image>[] = donation.getImages() as DocumentReference<Image>[];
imagesRef.push(...(donationDetail.getImages() as DocumentReference<Image>[]));
donation.images = await imageReferenceConverter(...imagesRef);
donations.push(donation);
}
}
return donations;
}

export async function addDonation(newDonation: DonationBody) {
try {
const userId: string = await getUserId();
const donationParams: IDonation = {
category: newDonation.category,
brand: newDonation.brand,
model: newDonation.model,
description: newDonation.description,
active: false,
images: [], // Only approved images display here.
createdAt: serverTimestamp() as Timestamp,
modifiedAt: serverTimestamp() as Timestamp
};

const donationDetailParams: IDonationDetail = {
donation: doc(db, `${DONATIONS_COLLECTION}/${userId}`),
availability: undefined,
donor: doc(db, `${USERS_COLLECTION}/${userId}`),
tagNumber: undefined,
tagNumberForItemDelivered: undefined,
sku: undefined,
recipientOrganization: undefined,
images: newDonation.images,
recipientContact: undefined,
recipientAddress: undefined,
requestor: undefined,
storage: undefined,
dateReceived: undefined,
dateDistributed: undefined,
scheduledPickupDate: undefined,
dateOrderFulfilled: undefined,
createdAt: serverTimestamp() as Timestamp,
modifiedAt: serverTimestamp() as Timestamp
};

const donation = new Donation(donationParams);
const donationDetail = new DonationDetail(donationDetailParams);

try {
await runTransaction(db, async (transaction) => {
// Generate document references with firebase-generated IDs
const donationRef = doc(collection(db, DONATIONS_COLLECTION));
const donationDetailRef = doc(collection(db, DONATION_DETAILS_COLLECTION));
// Assign donation reference to donation detail
donationDetail.setDonation(donationRef);

transaction.set(donationRef, donationConverter.toFirestore(donation));

transaction.set(donationDetailRef, donationDetailsConverter.toFirestore(donationDetail));
});
} catch (error: any) {
const keys: any[] = [];
for (const key in error) {
keys.push(key);
}
addEvent({ location: 'addDonation', keys: keys });
await runTransaction(db, async (transaction) => {
// Generate document references with firebase-generated IDs
const donationRef = doc(collection(db, DONATIONS_COLLECTION));
const donationDetailRef = doc(collection(db, DONATION_DETAILS_COLLECTION));
const userId: string = await getUserId();
const donationParams: IDonation = {
id: donationRef.id,
category: newDonation.category,
brand: newDonation.brand,
model: newDonation.model,
description: newDonation.description,
active: false,
images: [], // Only approved images display here.
createdAt: serverTimestamp() as Timestamp,
modifiedAt: serverTimestamp() as Timestamp
};
const donationDetailParams: IDonationDetail = {
donation: donationRef,
availability: undefined,
donor: doc(db, `${USERS_COLLECTION}/${userId}`),
tagNumber: undefined,
tagNumberForItemDelivered: undefined,
sku: undefined,
recipientOrganization: undefined,
images: newDonation.images,
recipientContact: undefined,
recipientAddress: undefined,
requestor: undefined,
storage: undefined,
dateReceived: undefined,
dateDistributed: undefined,
scheduledPickupDate: undefined,
dateOrderFulfilled: undefined,
createdAt: serverTimestamp() as Timestamp,
modifiedAt: serverTimestamp() as Timestamp
};
const donation = new Donation(donationParams);
const donationDetail = new DonationDetail(donationDetailParams);
transaction.set(donationRef, donationConverter.toFirestore(donation));
transaction.set(donationDetailRef, donationDetailsConverter.toFirestore(donationDetail));
});
} catch (error: any) {
const keys: any[] = [];
for (const key in error) {
keys.push(key);
}
} catch (error) {
addEvent(newDonation);
addEvent({ location: 'addDonation', keys: keys });
}
}
22 changes: 5 additions & 17 deletions src/api/firebase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
} from 'firebase/auth';
import { AccountInformation, UserBody } from '@/types/post-data';

const firebaseConfig = JSON.parse(process.env.NEXT_PUBLIC_FIREBASE_CONFIG ?? '{}');
const firebaseConfig = JSON.parse(process.env.FIREBASE_CONFIG ?? '{}');

export const app: FirebaseApp = initializeApp(firebaseConfig);
export const db: Firestore = initDb();
Expand All @@ -25,10 +25,7 @@ const functions = initFunctions();

function initDb(): Firestore {
let _db: Firestore;
if (
(process?.env?.NODE_ENV === 'test' || process?.env?.NODE_ENV === 'development') &&
process?.env?.NEXT_PUBLIC_FIREBASE_EMULATORS_IMPORT_DIRECTORY !== undefined
) {
if ((process?.env?.NODE_ENV === 'test' || process?.env?.NODE_ENV === 'development') && process?.env?.NEXT_PUBLIC_IMPORT_DIRECTORY != null) {
const FIREBASE_EMULATORS_FIRESTORE_PORT = Number.parseInt(process.env.NEXT_PUBLIC_FIREBASE_EMULATORS_FIRESTORE_PORT ?? '8080');
_db = getFirestore(app);
connectFirestoreEmulator(_db, '127.0.0.1', FIREBASE_EMULATORS_FIRESTORE_PORT);
Expand All @@ -40,10 +37,7 @@ function initDb(): Firestore {

function initFirebaseAuth() {
let _auth: Auth;
if (
(process?.env?.NODE_ENV === 'test' || process?.env?.NODE_ENV === 'development') &&
process.env.NEXT_PUBLIC_FIREBASE_EMULATORS_IMPORT_DIRECTORY !== undefined
) {
if ((process?.env?.NODE_ENV === 'test' || process?.env?.NODE_ENV === 'development') && process.env.NEXT_PUBLIC_IMPORT_DIRECTORY != null) {
const FIREBASE_EMULATORS_AUTH_PORT = Number.parseInt(process.env.NEXT_PUBLIC_FIREBASE_EMULATORS_AUTH_PORT ?? '9099');
_auth = getAuth(app);
connectAuthEmulator(_auth, `http://127.0.0.1:${FIREBASE_EMULATORS_AUTH_PORT}`);
Expand All @@ -55,10 +49,7 @@ function initFirebaseAuth() {

function initFirebaseStorage() {
let _storage: FirebaseStorage;
if (
(process?.env?.NODE_ENV === 'test' || process?.env?.NODE_ENV === 'development') &&
process?.env?.NEXT_PUBLIC_FIREBASE_EMULATORS_IMPORT_DIRECTORY !== undefined
) {
if ((process?.env?.NODE_ENV === 'test' || process?.env?.NODE_ENV === 'development') && process?.env?.NEXT_PUBLIC_IMPORT_DIRECTORY != null) {
const FIREBASE_EMULATORS_STORAGE_PORT = Number.parseInt(process.env.NEXT_PUBLIC_FIREBASE_EMULATORS_STORAGE_PORT ?? '9199');
_storage = getStorage(app);
connectStorageEmulator(_storage, '127.0.0.1', FIREBASE_EMULATORS_STORAGE_PORT);
Expand All @@ -70,10 +61,7 @@ function initFirebaseStorage() {

function initFunctions() {
let _functions: Functions;
if (
(process?.env?.NODE_ENV === 'test' || process?.env?.NODE_ENV === 'development') &&
process?.env?.NEXT_PUBLIC_FIREBASE_EMULATORS_IMPORT_DIRECTORY !== undefined
) {
if ((process?.env?.NODE_ENV === 'test' || process?.env?.NODE_ENV === 'development') && process?.env?.NEXT_PUBLIC_IMPORT_DIRECTORY != null) {
const FIREBASE_EMULATORS_FUNCTIONS_PORT = Number.parseInt(process.env.NEXT_PUBLIC_FIREBASE_EMULATORS_FUNCTIONS_PORT ?? '5001');
_functions = getFunctions(app, 'us-east1');
connectFunctionsEmulator(_functions, '127.0.0.1', FIREBASE_EMULATORS_FUNCTIONS_PORT);
Expand Down

0 comments on commit ac8175d

Please sign in to comment.