Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ jobs:
touch apps/cms/.env
echo ${{ secrets.APP_CMS_DOT_ENV }} > apps/cms/.env

- name: Display .env contents
run: cat apps/cms/.env

# Prepend any command with "nx-cloud record --" to record its logs to Nx Cloud
# - run: npx nx-cloud record -- echo Hello World
# Nx Affected runs only tasks affected by the changes in this PR/commit. Learn more: https://nx.dev/ci/features/affected
Expand Down
2 changes: 1 addition & 1 deletion apps/cloud-functions/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"cjs"
],
"bundle": false,
"main": "apps/cloud-functions/src/main.ts",
"main": "apps/cloud-functions/src/index.ts",
"tsConfig": "apps/cloud-functions/tsconfig.app.json",
"assets": [
"apps/cloud-functions/src/assets"
Expand Down
1 change: 0 additions & 1 deletion apps/cloud-functions/src/main.ts

This file was deleted.

25 changes: 13 additions & 12 deletions apps/cloud-functions/src/triggers/document-publish.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import {TanamDocument} from "@tanam/domain-backend";
import * as admin from "firebase-admin";
import {getFirestore} from "firebase-admin/firestore";
import {getFunctions} from "firebase-admin/functions";
import {getStorage} from "firebase-admin/storage";
import {logger} from "firebase-functions/v2";
import {onDocumentWritten} from "firebase-functions/v2/firestore";
import {onTaskDispatched} from "firebase-functions/v2/tasks";

const db = admin.firestore();
const storage = getStorage().bucket();

// Document publish change handler
// This function is handling updates when a document is published or unpublished.
// It will ignore updates that does not change the publish status of the document.
Expand Down Expand Up @@ -73,8 +70,8 @@ export const taskPublishDocument = onTaskDispatched(
},
async (req) => {
const documentId = req.data.documentId;
const documentRef = db.collection("tanam-documents").doc(documentId);
const publicDocumentRef = db.collection("tanam-public").doc(documentId);
const documentRef = getFirestore().collection("tanam-documents").doc(documentId);
const publicDocumentRef = getFirestore().collection("tanam-public").doc(documentId);
const snap = await documentRef.get();

if (!snap.exists) {
Expand All @@ -96,10 +93,12 @@ export const taskPublishDocument = onTaskDispatched(
promises.push(publicDocumentRef.set(document.data));

// Copy associated files to public directory
const [files] = await storage.getFiles({prefix: `tanam-documents/${documentId}/`});
const [files] = await getStorage()
.bucket()
.getFiles({prefix: `tanam-documents/${documentId}/`});
for (const file of files) {
const publishedFileName = file.name.replace("tanam-documents/", "tanam-public/");
promises.push(storage.file(file.name).copy(storage.file(publishedFileName)));
promises.push(getStorage().bucket().file(file.name).copy(getStorage().bucket().file(publishedFileName)));
}

await Promise.all(promises);
Expand All @@ -122,8 +121,8 @@ export const taskUnpublishDocument = onTaskDispatched(
},
async (req) => {
const documentId = req.data.documentId;
const publicDocumentRef = db.collection("tanam-public").doc(documentId);
const documentRef = db.collection("tanam-documents").doc(documentId);
const publicDocumentRef = getFirestore().collection("tanam-public").doc(documentId);
const documentRef = getFirestore().collection("tanam-documents").doc(documentId);
const snap = await documentRef.get();

const document = TanamDocument.fromFirestore(snap);
Expand All @@ -138,9 +137,11 @@ export const taskUnpublishDocument = onTaskDispatched(
const promises = [publicDocumentRef.delete()];

// Delete associated files from public directory
const [files] = await storage.getFiles({prefix: `tanam-public/${documentId}/`});
const [files] = await getStorage()
.bucket()
.getFiles({prefix: `tanam-public/${documentId}/`});
for (const file of files) {
promises.push(storage.file(file.name).delete().then());
promises.push(getStorage().bucket().file(file.name).delete().then());
}

await Promise.all(promises);
Expand Down
32 changes: 15 additions & 17 deletions apps/cloud-functions/src/triggers/users.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,30 @@
import {TanamRole, TanamUser} from "@tanam/domain-backend";
import axios from "axios";
import * as admin from "firebase-admin";
import {getAuth} from "firebase-admin/auth";
import {getFirestore} from "firebase-admin/firestore";
import {getStorage} from "firebase-admin/storage";
import {logger} from "firebase-functions/v2";
import {onDocumentCreated, onDocumentDeleted, onDocumentUpdated} from "firebase-functions/v2/firestore";
import {onObjectFinalized} from "firebase-functions/v2/storage";
import sharp from "sharp";

const auth = admin.auth();
const db = admin.firestore();
const storage = admin.storage();

// Function to validate and assign role on document creation
// This function will scaffold and create a new user document with a role field
// and assert that all the document fields are populated.
export const tanamNewUserInit = onDocumentCreated("tanam-users/{docId}", async (event) => {
const uid = event.params.docId;
const docRef = db.collection("tanam-users").doc(uid);
const docRef = getFirestore().collection("tanam-users").doc(uid);

logger.info(`Validating User ID: ${uid}`);
try {
await auth.getUser(uid);
await getAuth().getUser(uid);
} catch (error) {
console.log("Document ID does not match any Firebase Auth UID, deleting document");
return docRef.delete();
}

const firebaseUser = await auth.getUser(uid);
const existingDocs = await db.collection("tanam-users").get();
const firebaseUser = await getAuth().getUser(uid);
const existingDocs = await getFirestore().collection("tanam-users").get();
const tanamUser = new TanamUser(
uid,
undefined,
Expand All @@ -36,15 +34,15 @@ export const tanamNewUserInit = onDocumentCreated("tanam-users/{docId}", async (
);
logger.info("Creating User", tanamUser.toJson());

const customClaimsBefore = (await auth.getUser(uid)).customClaims || {};
const customClaimsBefore = (await getAuth().getUser(uid)).customClaims || {};
const customClaimsAfter = {...customClaimsBefore, tanamRole: tanamUser.role};

logger.info(`Setting custom claims for ${uid}`, {
customClaimsBefore,
customClaimsAfter,
});

return Promise.all([auth.setCustomUserClaims(uid, customClaimsAfter), docRef.set(tanamUser.toJson())]);
return Promise.all([getAuth().setCustomUserClaims(uid, customClaimsAfter), docRef.set(tanamUser.toJson())]);
});

// Function to enforce role management on document update
Expand All @@ -69,30 +67,30 @@ export const onTanamUserRoleChange = onDocumentUpdated("tanam-users/{docId}", as
}

logger.info(`Role change detected for ${uid}.`, {before: beforeData.role, after: afterData.role});
return auth.setCustomUserClaims(uid, {tanamRole: afterData.role});
return getAuth().setCustomUserClaims(uid, {tanamRole: afterData.role});
});

// Function to remove role on document deletion
export const onTanamUserDeleted = onDocumentDeleted("tanam-users/{docId}", async (event) => {
const uid = event.params.docId;

console.log(`Document deleted: ${uid}, removing custom claims`);
const customClaims = (await auth.getUser(uid)).customClaims || {};
const customClaims = (await getAuth().getUser(uid)).customClaims || {};
customClaims.tanamRole = undefined;

logger.info(`Tanam user deleted, removing custom claims for ${uid}`, {
customClaims,
});

await auth.setCustomUserClaims(uid, customClaims);
await getAuth().setCustomUserClaims(uid, customClaims);
});

// Function to download and store user profile image
// This function will download the user's profile image from Firebase Auth
// and store it in Cloud Storage when a new user is created.
export const tanamNewUserGetImage = onDocumentCreated("tanam-users/{docId}", async (event) => {
const uid = event.params.docId;
const firebaseUser = await auth.getUser(uid);
const firebaseUser = await getAuth().getUser(uid);

const imageUrl = firebaseUser.photoURL;
if (!imageUrl) {
Expand All @@ -107,7 +105,7 @@ export const tanamNewUserGetImage = onDocumentCreated("tanam-users/{docId}", asy

// Define the file path in Cloud Storage
const filePath = `tanam-users/${uid}/new-profile-image`;
const file = storage.bucket().file(filePath);
const file = getStorage().bucket().file(filePath);

// Upload the image to Cloud Storage
await file.save(buffer, {
Expand All @@ -129,7 +127,7 @@ export const tanamNewUserGetImage = onDocumentCreated("tanam-users/{docId}", asy
export const processUserProfileImage = onObjectFinalized(async (event) => {
const filePath = event.data.name;
const contentType = event.data.contentType;
const bucket = storage.bucket(event.bucket);
const bucket = getStorage().bucket(event.bucket);
const promises = [];
if (!filePath || !contentType) {
logger.error("File path or content type is missing", {filePath, contentType});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export default function DocumentDetailsPage() {
if (document) {
setTitle(document.data.title as string);
setDescription(document.data.blurb as string);
setTags(document.data.tags as string[]);
setTags((document.data.tags ?? []) as string[]);
}

return () => {
Expand All @@ -55,7 +55,7 @@ export default function DocumentDetailsPage() {
function pruneState() {
setTitle("");
setDescription("");
setTags((document?.data.tags as string[]) ?? []);
setTags((document?.data.tags ?? []) as string[]);
}

async function fetchDocumentUpdate(title: string, blurb: string, tags: string[]) {
Expand Down
2 changes: 1 addition & 1 deletion libs/ui-components/src/Tiptap/CodeBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import "./styles/code-block.scss";
interface CodeBlockProps {
node: {
attrs: {
language: string;
language?: string;
};
};
updateAttributes: (attrs: {language: string}) => void;
Expand Down
5 changes: 5 additions & 0 deletions nx.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,17 @@
}
],
"targetDefaults": {
"codegen": {
"cache": true
},
"lint": {
"cache": true,
"dependsOn": [
"codegen"
]
},
"build": {
"cache": true,
"dependsOn": ["lint", "^lint", "^build"]
},
"e2e-ci--**/*": {
Expand Down
Loading
Loading