From c7d6025ccc95da65c1970d29cb94ad175a9ea6f1 Mon Sep 17 00:00:00 2001 From: WidaSuryani Date: Wed, 19 Jun 2024 23:54:49 +0800 Subject: [PATCH 01/21] feat : add butoon to add document --- .../(protected)/content/[documentTypeId]/page.tsx | 14 +++++++++++++- .../src/app/(protected)/content/article/page.tsx | 15 ++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/hosting/src/app/(protected)/content/[documentTypeId]/page.tsx b/hosting/src/app/(protected)/content/[documentTypeId]/page.tsx index 0cb670a8..8ec5557c 100644 --- a/hosting/src/app/(protected)/content/[documentTypeId]/page.tsx +++ b/hosting/src/app/(protected)/content/[documentTypeId]/page.tsx @@ -22,7 +22,19 @@ export default function DocumentTypeDocumentsPage() { return ( <> }> - {documentType ? : } + {documentType ? ( +
+ {" "} + +
+ ) : ( + + )}
{notification && ( diff --git a/hosting/src/app/(protected)/content/article/page.tsx b/hosting/src/app/(protected)/content/article/page.tsx index b850db2a..fb253e82 100644 --- a/hosting/src/app/(protected)/content/article/page.tsx +++ b/hosting/src/app/(protected)/content/article/page.tsx @@ -20,8 +20,21 @@ export default function DocumentTypeDocumentsPage() { return ( <> }> - {documentType ? : } + {documentType ? ( +
+ + +
+ ) : ( + + )}
+ {notification && ( )} From e8eee02b4fd99aa99c333bc7cb3ba0f8949edad8 Mon Sep 17 00:00:00 2001 From: WidaSuryani Date: Fri, 21 Jun 2024 10:26:40 +0800 Subject: [PATCH 02/21] fix : add icon for loading --- hosting/package-lock.json | 12 ++++++++++-- hosting/package.json | 1 + hosting/tailwind.config.ts | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/hosting/package-lock.json b/hosting/package-lock.json index 97c04e60..a6610535 100644 --- a/hosting/package-lock.json +++ b/hosting/package-lock.json @@ -8,6 +8,7 @@ "name": "tanam-next", "version": "0.1.0", "dependencies": { + "@iconify-json/line-md": "^1.1.37", "@tiptap/pm": "^2.4.0", "@tiptap/react": "^2.4.0", "@tiptap/starter-kit": "^2.4.0", @@ -755,6 +756,14 @@ "@iconify/types": "*" } }, + "node_modules/@iconify-json/line-md": { + "version": "1.1.37", + "resolved": "https://registry.npmjs.org/@iconify-json/line-md/-/line-md-1.1.37.tgz", + "integrity": "sha512-qGezTafsQOX4N2STOV0gsSw/vr0u7DPXtg0jpVQTIa9ht1IXN68O+Qy7Ia2dHbuW4w5nz3UyhJzNrh7NB5T7BA==", + "dependencies": { + "@iconify/types": "*" + } + }, "node_modules/@iconify-json/ri": { "version": "1.1.20", "resolved": "https://registry.npmjs.org/@iconify-json/ri/-/ri-1.1.20.tgz", @@ -767,8 +776,7 @@ "node_modules/@iconify/types": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", - "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", - "dev": true + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==" }, "node_modules/@iconify/utils": { "version": "2.1.24", diff --git a/hosting/package.json b/hosting/package.json index 9b31a75d..604de9ae 100644 --- a/hosting/package.json +++ b/hosting/package.json @@ -12,6 +12,7 @@ "codecheck": "npm run prettier:fix && npm run lint:fix" }, "dependencies": { + "@iconify-json/line-md": "^1.1.37", "@tiptap/pm": "^2.4.0", "@tiptap/react": "^2.4.0", "@tiptap/starter-kit": "^2.4.0", diff --git a/hosting/tailwind.config.ts b/hosting/tailwind.config.ts index 26d3ac94..217fe52b 100644 --- a/hosting/tailwind.config.ts +++ b/hosting/tailwind.config.ts @@ -332,7 +332,7 @@ const config: Config = { plugins: [ require("@tailwindcss/typography"), iconsPlugin({ - collections: getIconCollections(["ic", "ri"]), + collections: getIconCollections(["ic", "ri", "line-md"]), }), ], }; From 20d14ba57ee88636130f36a3d6e1342a4d7476d8 Mon Sep 17 00:00:00 2001 From: WidaSuryani Date: Fri, 21 Jun 2024 10:27:12 +0800 Subject: [PATCH 03/21] fix : update function to create document --- .../app/(protected)/content/article/page.tsx | 17 +++++-- hosting/src/hooks/useTanamDocuments.tsx | 51 ++++++++++++++++++- 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/hosting/src/app/(protected)/content/article/page.tsx b/hosting/src/app/(protected)/content/article/page.tsx index fb253e82..2ceb716e 100644 --- a/hosting/src/app/(protected)/content/article/page.tsx +++ b/hosting/src/app/(protected)/content/article/page.tsx @@ -4,18 +4,23 @@ import Loader from "@/components/common/Loader"; import Notification from "@/components/common/Notification"; import PageHeader from "@/components/common/PageHeader"; import {useTanamDocumentType} from "@/hooks/useTanamDocumentTypes"; -import {useTanamDocuments} from "@/hooks/useTanamDocuments"; +import {useCreateTanamDocument, useTanamDocuments} from "@/hooks/useTanamDocuments"; import {UserNotification} from "@/models/UserNotification"; import {Suspense, useEffect, useState} from "react"; export default function DocumentTypeDocumentsPage() { const {data: documentType} = useTanamDocumentType("article"); + const {create, error: writeError, isLoading} = useCreateTanamDocument(documentType?.id); const {data: documents, error: docsError} = useTanamDocuments("article"); const [notification, setNotification] = useState(null); useEffect(() => { - setNotification(docsError); - }, [docsError]); + setNotification(docsError || writeError); + }, [docsError, writeError]); + + const addNewArticle = async () => { + await create(); + }; return ( <> @@ -26,8 +31,12 @@ export default function DocumentTypeDocumentsPage() { ) : ( diff --git a/hosting/src/hooks/useTanamDocuments.tsx b/hosting/src/hooks/useTanamDocuments.tsx index 83a06242..250a8d37 100644 --- a/hosting/src/hooks/useTanamDocuments.tsx +++ b/hosting/src/hooks/useTanamDocuments.tsx @@ -1,7 +1,17 @@ import {TanamDocumentClient} from "@/models/TanamDocumentClient"; import {firestore} from "@/plugins/firebase"; import {ITanamDocument} from "@functions/models/TanamDocument"; -import {Timestamp, collection, doc, onSnapshot, query, serverTimestamp, updateDoc, where} from "firebase/firestore"; +import { + Timestamp, + addDoc, + collection, + doc, + onSnapshot, + query, + serverTimestamp, + updateDoc, + where, +} from "firebase/firestore"; import {useEffect, useState} from "react"; import {UserNotification} from "@/models/UserNotification"; @@ -33,6 +43,7 @@ export function useTanamDocuments(documentTypeId?: string): UseTanamDocumentsRes q, (snapshot) => { const documents = snapshot.docs.map((doc) => TanamDocumentClient.fromFirestore(doc)); + console.log(documents); setData(documents); }, (err) => { @@ -111,6 +122,42 @@ export function useCrudTanamDocument(documentId?: string) { setIsLoading(false); } } - return {update, isLoading, error}; } + +export function useCreateTanamDocument(documentType?: string) { + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + async function create(): Promise { + setIsLoading(true); + try { + if (!documentType) { + setError(new UserNotification("error", "Missing parameter", "Document id parameter is missing")); + return; + } + const typeRef = collection(firestore, "tanam-documents"); + await addDoc(typeRef, { + createdAt: serverTimestamp(), + data: {content: ""}, + documentType, + publishedAt: null, + revision: 0, + status: "", + updatedAt: serverTimestamp(), + }); + } catch (err) { + setError( + new UserNotification( + "error", + "UserNotification updating document", + "An error occurred while updating the document", + ), + ); + } finally { + setIsLoading(false); + } + } + + return {create, isLoading, error}; +} From b3348097ee2af90a19875df1b785f8464bd6c1e4 Mon Sep 17 00:00:00 2001 From: WidaSuryani Date: Sat, 29 Jun 2024 11:02:42 +0800 Subject: [PATCH 04/21] fix : update error createdAt --- functions/src/models/TanamDocument.ts | 8 ++++---- hosting/src/app/(protected)/content/article/page.tsx | 6 +++++- .../components/DocumentType/DocumentTypeGenericList.tsx | 2 +- hosting/src/hooks/useTanamDocuments.tsx | 5 +++-- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/functions/src/models/TanamDocument.ts b/functions/src/models/TanamDocument.ts index 67b04439..0fe66009 100644 --- a/functions/src/models/TanamDocument.ts +++ b/functions/src/models/TanamDocument.ts @@ -9,8 +9,8 @@ export interface ITanamDocument { documentType: string; revision?: number; publishedAt?: Date; - createdAt: TimestampType; - updatedAt: TimestampType; + createdAt?: TimestampType; + updatedAt?: TimestampType; } export abstract class TanamDocument { @@ -29,8 +29,8 @@ export abstract class TanamDocument { public documentType: string; public publishedAt?: Date; public revision: number; - public readonly createdAt: TimestampType; - public readonly updatedAt: TimestampType; + public readonly createdAt?: TimestampType; + public readonly updatedAt?: TimestampType; get status(): TanamPublishStatus { if (!this.publishedAt) { diff --git a/hosting/src/app/(protected)/content/article/page.tsx b/hosting/src/app/(protected)/content/article/page.tsx index 2ceb716e..182c4488 100644 --- a/hosting/src/app/(protected)/content/article/page.tsx +++ b/hosting/src/app/(protected)/content/article/page.tsx @@ -6,6 +6,7 @@ import PageHeader from "@/components/common/PageHeader"; import {useTanamDocumentType} from "@/hooks/useTanamDocumentTypes"; import {useCreateTanamDocument, useTanamDocuments} from "@/hooks/useTanamDocuments"; import {UserNotification} from "@/models/UserNotification"; +import {useRouter} from "next/navigation"; import {Suspense, useEffect, useState} from "react"; export default function DocumentTypeDocumentsPage() { @@ -13,13 +14,16 @@ export default function DocumentTypeDocumentsPage() { const {create, error: writeError, isLoading} = useCreateTanamDocument(documentType?.id); const {data: documents, error: docsError} = useTanamDocuments("article"); const [notification, setNotification] = useState(null); + const router = useRouter(); useEffect(() => { setNotification(docsError || writeError); }, [docsError, writeError]); const addNewArticle = async () => { - await create(); + const result = await create(); + + router.push(`article/${result?.id}`); }; return ( diff --git a/hosting/src/components/DocumentType/DocumentTypeGenericList.tsx b/hosting/src/components/DocumentType/DocumentTypeGenericList.tsx index 99518c87..fd89ea32 100644 --- a/hosting/src/components/DocumentType/DocumentTypeGenericList.tsx +++ b/hosting/src/components/DocumentType/DocumentTypeGenericList.tsx @@ -20,7 +20,7 @@ export function DocumentTypeGenericList({documents, documentType}: TableOverview

,

- {document.createdAt.toDate().toUTCString()} + {document.createdAt?.toDate().toUTCString()}

, (null); - async function create(): Promise { + async function create() { setIsLoading(true); try { if (!documentType) { @@ -137,7 +137,7 @@ export function useCreateTanamDocument(documentType?: string) { return; } const typeRef = collection(firestore, "tanam-documents"); - await addDoc(typeRef, { + const result = await addDoc(typeRef, { createdAt: serverTimestamp(), data: {content: ""}, documentType, @@ -146,6 +146,7 @@ export function useCreateTanamDocument(documentType?: string) { status: "", updatedAt: serverTimestamp(), }); + return result; } catch (err) { setError( new UserNotification( From 85e2b81374c8132b4aebe00569da505901648b47 Mon Sep 17 00:00:00 2001 From: Dennis Alund Date: Sun, 30 Jun 2024 15:51:20 +0800 Subject: [PATCH 05/21] Updating config utility --- functions/src/config.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/functions/src/config.ts b/functions/src/config.ts index 3d13420c..34888262 100644 --- a/functions/src/config.ts +++ b/functions/src/config.ts @@ -1,4 +1,27 @@ export class TanamConfig { + static get projectId(): string { + const projectId = process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT || process.env.GCP_PROJECT; + + if (!projectId) { + throw new Error("Could not find project ID in any variable"); + } + + return projectId; + } + + static get cloudFunctionRegion(): string { + return "us-central1"; + } + + /** + * Get flag for whether the functions are running in emulator or not. + * This can be derived by checking the existence of any emulator provided + * variables. + */ + static get isEmulated(): boolean { + return !!process.env.FIREBASE_EMULATOR_HUB; + } + static get databaseName(): string { return process.env.database || "(default)"; } From 740316697d2a007906e59df2b2cf73b5574eafec Mon Sep 17 00:00:00 2001 From: Dennis Alund Date: Mon, 1 Jul 2024 21:00:46 +0700 Subject: [PATCH 06/21] Adding cloud function for publishing and unpublishing --- functions/src/document-publish.ts | 140 ++++++++++++++++++++++++++++++ functions/src/index.ts | 1 + 2 files changed, 141 insertions(+) create mode 100644 functions/src/document-publish.ts diff --git a/functions/src/document-publish.ts b/functions/src/document-publish.ts new file mode 100644 index 00000000..2407d77d --- /dev/null +++ b/functions/src/document-publish.ts @@ -0,0 +1,140 @@ +import * as admin from "firebase-admin"; +import {Timestamp} 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"; +import {ITanamDocument} from "./models/TanamDocument"; +import {TanamDocumentAdmin} from "./models/TanamDocumentAdmin"; + +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. +export const onPublishChange = onDocumentWritten("tanam-documents/{documentId}", async (event) => { + const documentId = event.params.documentId; + const unpublishQueue = getFunctions().taskQueue("taskUnpublishDocument"); + const publishQueue = getFunctions().taskQueue("taskPublishDocument"); + if (!event.data || !event.data.after.exists) { + logger.info("Document was deleted. Unpublishing document."); + return unpublishQueue.enqueue({documentId}); + } + + const documentBeforeData = (event.data.before.data() || {}) as ITanamDocument; + const documentBefore = new TanamDocumentAdmin(documentId, documentBeforeData); + + const documentAfterData = (event.data.after.data() || {}) as ITanamDocument; + const documentAfter = new TanamDocumentAdmin(documentId, documentAfterData); + + if (documentBefore.status === documentAfter.status) { + logger.info("Document status did not change. Skipping."); + return; + } + + if (documentAfter.status === "published") { + logger.info("Document was published."); + return publishQueue.enqueue({documentId}); + } else if (documentBefore.status === "published") { + logger.info("Document was unpublished", documentAfter.toJson()); + return unpublishQueue.enqueue({documentId}); + } +}); + +// Task to publish a document +// This task is responsible for copying the document data to the public collection +// and copying associated files to the cloud storage public directory. +export const taskPublishDocument = onTaskDispatched( + { + retryConfig: { + maxAttempts: 3, + minBackoffSeconds: 60, + }, + rateLimits: { + // Try to give room for concurrency of documents in firestore + maxDispatchesPerSecond: 1, + }, + }, + async (req) => { + const documentId = req.data.documentId; + const documentRef = db.collection("tanam-documents").doc(documentId); + const publicDocumentRef = db.collection("tanam-public").doc(documentId); + const snap = await documentRef.get(); + + if (!snap.exists) { + logger.error(`Document does not exist anymore: ${documentId}`); + return; + } + + const documentData = snap.data(); + if (!documentData) { + logger.error(`Document data is empty: ${documentId}`); + return; + } + const document = new TanamDocumentAdmin(documentId, documentData as ITanamDocument); + + if (document.status !== "published") { + // This could happen if the document changed status while the task was in the queue + logger.info("Document is no longer published. Stop here."); + return; + } + + const promises = []; + + // Copy document data to public collection + promises.push(publicDocumentRef.set(documentData)); + + // Copy associated files to public directory + const [files] = await storage.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))); + } + + await Promise.all(promises); + }, +); + +// Task to unpublish a document +// This task is responsible for removing the document from the public collection +// and deleting associated files from the cloud storage public directory. +export const taskUnpublishDocument = onTaskDispatched( + { + retryConfig: { + maxAttempts: 3, + minBackoffSeconds: 60, + }, + rateLimits: { + // Adjust for concurrency of documents in firestore + maxDispatchesPerSecond: 1, + }, + }, + async (req) => { + const documentId = req.data.documentId; + const publicDocumentRef = db.collection("tanam-public").doc(documentId); + const documentRef = db.collection("tanam-documents").doc(documentId); + const snap = await documentRef.get(); + + const documentData = snap.data(); + const document = new TanamDocumentAdmin(documentId, documentData as ITanamDocument); + + if (document.status === "published") { + // This could happen if the document changed status while the task was in the queue + logger.info("Document is in status published. Stop here."); + return; + } + + // Remove document from public collection + const promises = [publicDocumentRef.delete()]; + + // Delete associated files from public directory + const [files] = await storage.getFiles({prefix: `tanam-public/${documentId}/`}); + for (const file of files) { + promises.push(storage.file(file.name).delete().then()); + } + + await Promise.all(promises); + }, +); diff --git a/functions/src/index.ts b/functions/src/index.ts index 3cd59b25..0a4f8157 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -4,3 +4,4 @@ const app = admin.initializeApp(); app.firestore().settings({ignoreUndefinedProperties: true}); export * from "./genkit"; +export * from "./document-publish"; From ce43061d1dff00629cf45d720c3b228dddabc79c Mon Sep 17 00:00:00 2001 From: Dennis Alund Date: Mon, 1 Jul 2024 22:44:24 +0700 Subject: [PATCH 07/21] Updating `publishedAt` type --- functions/src/models/TanamDocument.ts | 15 +++------------ functions/src/models/TanamDocumentAdmin.ts | 12 +++++++++++- hosting/src/models/TanamDocumentClient.ts | 14 ++++++++++++-- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/functions/src/models/TanamDocument.ts b/functions/src/models/TanamDocument.ts index 67b04439..f24f2e5c 100644 --- a/functions/src/models/TanamDocument.ts +++ b/functions/src/models/TanamDocument.ts @@ -8,7 +8,7 @@ export interface ITanamDocument { data: DocumentData; documentType: string; revision?: number; - publishedAt?: Date; + publishedAt?: TimestampType; createdAt: TimestampType; updatedAt: TimestampType; } @@ -27,21 +27,12 @@ export abstract class TanamDocument { public readonly id: string; public data: DocumentData; public documentType: string; - public publishedAt?: Date; + public publishedAt?: TimestampType; public revision: number; public readonly createdAt: TimestampType; public readonly updatedAt: TimestampType; - get status(): TanamPublishStatus { - if (!this.publishedAt) { - return "unpublished"; - } else if (this.publishedAt > new Date()) { - return "scheduled"; - } else { - return "published"; - } - } - + abstract get status(): TanamPublishStatus; protected abstract getServerTimestamp(): FieldValueType; toJson(): object { diff --git a/functions/src/models/TanamDocumentAdmin.ts b/functions/src/models/TanamDocumentAdmin.ts index fa8aec7d..38cb866a 100644 --- a/functions/src/models/TanamDocumentAdmin.ts +++ b/functions/src/models/TanamDocumentAdmin.ts @@ -1,5 +1,5 @@ import {FieldValue, Timestamp} from "firebase-admin/firestore"; -import {ITanamDocument, TanamDocument} from "./TanamDocument"; +import {ITanamDocument, TanamDocument, TanamPublishStatus} from "./TanamDocument"; import {DocumentSnapshot} from "firebase-functions/v2/firestore"; export class TanamDocumentAdmin extends TanamDocument { @@ -7,6 +7,16 @@ export class TanamDocumentAdmin extends TanamDocument { super(id, json); } + get status(): TanamPublishStatus { + if (!this.publishedAt) { + return "unpublished"; + } else if (this.publishedAt.toMillis() > Timestamp.now().toMillis()) { + return "scheduled"; + } else { + return "published"; + } + } + getServerTimestamp(): FieldValue { return FieldValue.serverTimestamp(); } diff --git a/hosting/src/models/TanamDocumentClient.ts b/hosting/src/models/TanamDocumentClient.ts index 46a6b780..e0a7bd7e 100644 --- a/hosting/src/models/TanamDocumentClient.ts +++ b/hosting/src/models/TanamDocumentClient.ts @@ -1,11 +1,21 @@ -import {TanamDocument, ITanamDocument} from "@functions/models/TanamDocument"; -import {Timestamp, FieldValue, serverTimestamp, DocumentSnapshot} from "firebase/firestore"; +import {ITanamDocument, TanamDocument, TanamPublishStatus} from "@functions/models/TanamDocument"; +import {DocumentSnapshot, FieldValue, serverTimestamp, Timestamp} from "firebase/firestore"; export class TanamDocumentClient extends TanamDocument { constructor(id: string, json: ITanamDocument) { super(id, json); } + get status(): TanamPublishStatus { + if (!this.publishedAt) { + return "unpublished"; + } else if (this.publishedAt.toMillis() > Timestamp.now().toMillis()) { + return "scheduled"; + } else { + return "published"; + } + } + getServerTimestamp(): FieldValue { return serverTimestamp(); } From e4be536c88a25f8dba6efd52506e2f7693a835c1 Mon Sep 17 00:00:00 2001 From: Dennis Alund Date: Mon, 1 Jul 2024 22:44:54 +0700 Subject: [PATCH 08/21] Implementing support for scheduling --- functions/src/document-publish.ts | 37 +++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/functions/src/document-publish.ts b/functions/src/document-publish.ts index 2407d77d..4f9083fe 100644 --- a/functions/src/document-publish.ts +++ b/functions/src/document-publish.ts @@ -37,9 +37,28 @@ export const onPublishChange = onDocumentWritten("tanam-documents/{documentId}", if (documentAfter.status === "published") { logger.info("Document was published."); return publishQueue.enqueue({documentId}); - } else if (documentBefore.status === "published") { + } + + if (documentBefore.status === "published") { logger.info("Document was unpublished", documentAfter.toJson()); - return unpublishQueue.enqueue({documentId}); + await unpublishQueue.enqueue({documentId}); + } + + if (documentAfter.status === "scheduled") { + logger.info("Document has been scheduled", documentAfter.toJson()); + const publishedAt = documentAfter.publishedAt!.toDate(); + if (publishedAt !== normalizeDateToMaxOffset(publishedAt)) { + logger.error("Scheduled date is too far in the future", documentAfter.toJson()); + throw new Error("Scheduled date is too far in the future"); + } + + logger.info(`Enqueueing document for publishing at ${publishedAt}`, documentAfter.toJson()); + await publishQueue.enqueue( + {documentId}, + { + scheduleTime: publishedAt, + }, + ); } }); @@ -138,3 +157,17 @@ export const taskUnpublishDocument = onTaskDispatched( await Promise.all(promises); }, ); + +/** + * Normalize a date to a maximum offset from now + * + * @param {Date} date A date to normalize + * @param {number} hours Optional. Default is 720 hours (30 days) + * @returns {Date} The normalized date + */ +function normalizeDateToMaxOffset(date: Date, hours: number = 720): Date { + const now = new Date(); + const maxDate = new Date(now.getTime() + hours * 60 * 60 * 1000); + + return date > maxDate ? maxDate : date; +} From 6a58de291bab9581182e4570265f27e23ac594fe Mon Sep 17 00:00:00 2001 From: Dennis Alund Date: Mon, 1 Jul 2024 22:50:23 +0700 Subject: [PATCH 09/21] Fixing lint issues --- functions/src/document-publish.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/functions/src/document-publish.ts b/functions/src/document-publish.ts index 4f9083fe..f1afbd4c 100644 --- a/functions/src/document-publish.ts +++ b/functions/src/document-publish.ts @@ -44,9 +44,9 @@ export const onPublishChange = onDocumentWritten("tanam-documents/{documentId}", await unpublishQueue.enqueue({documentId}); } - if (documentAfter.status === "scheduled") { + const publishedAt = documentAfter.publishedAt?.toDate(); + if (documentAfter.status === "scheduled" && !!publishedAt) { logger.info("Document has been scheduled", documentAfter.toJson()); - const publishedAt = documentAfter.publishedAt!.toDate(); if (publishedAt !== normalizeDateToMaxOffset(publishedAt)) { logger.error("Scheduled date is too far in the future", documentAfter.toJson()); throw new Error("Scheduled date is too far in the future"); @@ -163,9 +163,9 @@ export const taskUnpublishDocument = onTaskDispatched( * * @param {Date} date A date to normalize * @param {number} hours Optional. Default is 720 hours (30 days) - * @returns {Date} The normalized date + * @return {Date} The normalized date */ -function normalizeDateToMaxOffset(date: Date, hours: number = 720): Date { +function normalizeDateToMaxOffset(date: Date, hours = 720): Date { const now = new Date(); const maxDate = new Date(now.getTime() + hours * 60 * 60 * 1000); From 6c8527b8f9a1d3f5c44c83149dfcde08e7200c05 Mon Sep 17 00:00:00 2001 From: WidaSuryani Date: Tue, 16 Jul 2024 09:16:39 +0800 Subject: [PATCH 10/21] fix : update model --- functions/src/models/TanamDocument.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/functions/src/models/TanamDocument.ts b/functions/src/models/TanamDocument.ts index ba6a05c1..51cd9d12 100644 --- a/functions/src/models/TanamDocument.ts +++ b/functions/src/models/TanamDocument.ts @@ -9,8 +9,8 @@ export interface ITanamDocument { documentType: string; revision?: number; publishedAt?: TimestampType; - createdAt: TimestampType; - updatedAt: TimestampType; + createdAt?: TimestampType; + updatedAt?: TimestampType; } export abstract class TanamDocument { From 06343b9bae8c85bf5d20526df3f4e4716b1e5a29 Mon Sep 17 00:00:00 2001 From: WidaSuryani Date: Tue, 16 Jul 2024 09:17:02 +0800 Subject: [PATCH 11/21] fix : use button component --- .../content/[documentTypeId]/page.tsx | 18 +++++++++------ .../app/(protected)/content/article/page.tsx | 22 +++++++++---------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/hosting/src/app/(protected)/content/[documentTypeId]/page.tsx b/hosting/src/app/(protected)/content/[documentTypeId]/page.tsx index 8ec5557c..e7c4851c 100644 --- a/hosting/src/app/(protected)/content/[documentTypeId]/page.tsx +++ b/hosting/src/app/(protected)/content/[documentTypeId]/page.tsx @@ -8,6 +8,7 @@ import {useTanamDocuments} from "@/hooks/useTanamDocuments"; import {Suspense, useEffect, useState} from "react"; import {useParams} from "next/navigation"; import {UserNotification} from "@/models/UserNotification"; +import {Button} from "@/components/Button"; export default function DocumentTypeDocumentsPage() { const {documentTypeId} = useParams<{documentTypeId: string}>() ?? {}; @@ -19,18 +20,21 @@ export default function DocumentTypeDocumentsPage() { setNotification(docsError); }, [docsError]); + const addNewDocument = async () => {}; + return ( <> }> {documentType ? ( -
+
{" "} - +
+
) : ( diff --git a/hosting/src/app/(protected)/content/article/page.tsx b/hosting/src/app/(protected)/content/article/page.tsx index 182c4488..56fd8a32 100644 --- a/hosting/src/app/(protected)/content/article/page.tsx +++ b/hosting/src/app/(protected)/content/article/page.tsx @@ -1,4 +1,5 @@ "use client"; +import {Button} from "@/components/Button"; import {DocumentTypeGenericList} from "@/components/DocumentType/DocumentTypeGenericList"; import Loader from "@/components/common/Loader"; import Notification from "@/components/common/Notification"; @@ -11,7 +12,7 @@ import {Suspense, useEffect, useState} from "react"; export default function DocumentTypeDocumentsPage() { const {data: documentType} = useTanamDocumentType("article"); - const {create, error: writeError, isLoading} = useCreateTanamDocument(documentType?.id); + const {create, error: writeError} = useCreateTanamDocument(documentType?.id); const {data: documents, error: docsError} = useTanamDocuments("article"); const [notification, setNotification] = useState(null); const router = useRouter(); @@ -30,18 +31,15 @@ export default function DocumentTypeDocumentsPage() { <> }> {documentType ? ( -
+
- +
+
) : ( From 2eaf964b8cc7e1578a8d088708b0a76e3d9dd5a1 Mon Sep 17 00:00:00 2001 From: WidaSuryani Date: Tue, 16 Jul 2024 09:17:15 +0800 Subject: [PATCH 12/21] fix : delete unuse code --- hosting/src/hooks/useTanamDocuments.tsx | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/hosting/src/hooks/useTanamDocuments.tsx b/hosting/src/hooks/useTanamDocuments.tsx index f72bf24b..f610a7cf 100644 --- a/hosting/src/hooks/useTanamDocuments.tsx +++ b/hosting/src/hooks/useTanamDocuments.tsx @@ -43,7 +43,6 @@ export function useTanamDocuments(documentTypeId?: string): UseTanamDocumentsRes q, (snapshot) => { const documents = snapshot.docs.map((doc) => TanamDocumentClient.fromFirestore(doc)); - console.log(documents); setData(documents); }, (err) => { @@ -137,15 +136,15 @@ export function useCreateTanamDocument(documentType?: string) { return; } const typeRef = collection(firestore, "tanam-documents"); - const result = await addDoc(typeRef, { - createdAt: serverTimestamp(), - data: {content: ""}, - documentType, - publishedAt: null, - revision: 0, - status: "", - updatedAt: serverTimestamp(), - }); + + const result = await addDoc( + typeRef, + new TanamDocumentClient("", { + data: {content: ""}, + documentType, + revision: 0, + }).toJson(), + ); return result; } catch (err) { setError( From 20fbf73147a94c0ba22854b4a36b5ea025377456 Mon Sep 17 00:00:00 2001 From: WidaSuryani Date: Tue, 16 Jul 2024 09:23:10 +0800 Subject: [PATCH 13/21] fix : refactor --- hosting/src/hooks/useTanamDocuments.tsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/hosting/src/hooks/useTanamDocuments.tsx b/hosting/src/hooks/useTanamDocuments.tsx index f610a7cf..590fe9d4 100644 --- a/hosting/src/hooks/useTanamDocuments.tsx +++ b/hosting/src/hooks/useTanamDocuments.tsx @@ -136,15 +136,13 @@ export function useCreateTanamDocument(documentType?: string) { return; } const typeRef = collection(firestore, "tanam-documents"); + const tanamDocument = new TanamDocumentClient("", { + data: {content: ""}, + documentType, + revision: 0, + }).toJson(); - const result = await addDoc( - typeRef, - new TanamDocumentClient("", { - data: {content: ""}, - documentType, - revision: 0, - }).toJson(), - ); + const result = await addDoc(typeRef, tanamDocument); return result; } catch (err) { setError( From 5fe9fa41a690c37f9b04a389c21108e1a4e12fab Mon Sep 17 00:00:00 2001 From: WidaSuryani Date: Wed, 17 Jul 2024 13:01:16 +0800 Subject: [PATCH 14/21] fix : refactoring create article --- .../app/(protected)/content/article/page.tsx | 4 ++-- hosting/src/hooks/useTanamDocuments.tsx | 21 ++++++++----------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/hosting/src/app/(protected)/content/article/page.tsx b/hosting/src/app/(protected)/content/article/page.tsx index 56fd8a32..aa954489 100644 --- a/hosting/src/app/(protected)/content/article/page.tsx +++ b/hosting/src/app/(protected)/content/article/page.tsx @@ -22,9 +22,9 @@ export default function DocumentTypeDocumentsPage() { }, [docsError, writeError]); const addNewArticle = async () => { - const result = await create(); + const id = await create(); - router.push(`article/${result?.id}`); + router.push(`article/${id}`); }; return ( diff --git a/hosting/src/hooks/useTanamDocuments.tsx b/hosting/src/hooks/useTanamDocuments.tsx index 590fe9d4..21f7f717 100644 --- a/hosting/src/hooks/useTanamDocuments.tsx +++ b/hosting/src/hooks/useTanamDocuments.tsx @@ -3,12 +3,12 @@ import {firestore} from "@/plugins/firebase"; import {ITanamDocument} from "@functions/models/TanamDocument"; import { Timestamp, - addDoc, collection, doc, onSnapshot, query, serverTimestamp, + setDoc, updateDoc, where, } from "firebase/firestore"; @@ -135,21 +135,18 @@ export function useCreateTanamDocument(documentType?: string) { setError(new UserNotification("error", "Missing parameter", "Document id parameter is missing")); return; } - const typeRef = collection(firestore, "tanam-documents"); - const tanamDocument = new TanamDocumentClient("", { - data: {content: ""}, - documentType, - revision: 0, - }).toJson(); - - const result = await addDoc(typeRef, tanamDocument); - return result; + const docRef = doc(collection(firestore, "tanam-documents")); + const docId = docRef.id; + + const tanamDocument = new TanamDocumentClient(docId, {data: {}, documentType}).toJson(); + await setDoc(docRef, tanamDocument); + return docId; } catch (err) { setError( new UserNotification( "error", - "UserNotification updating document", - "An error occurred while updating the document", + "UserNotification creating document", + "An error occurred while creating the document", ), ); } finally { From d6e317a070125b6bca3ef28d62eb9cf2bc339b76 Mon Sep 17 00:00:00 2001 From: Nurfirliana Muzanella Date: Tue, 30 Jul 2024 14:25:06 +0700 Subject: [PATCH 15/21] add publish button --- hosting/src/components/Button.tsx | 10 +++++----- hosting/src/components/Header/index.tsx | 9 ++++++++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/hosting/src/components/Button.tsx b/hosting/src/components/Button.tsx index 40e73142..4c1e1c18 100644 --- a/hosting/src/components/Button.tsx +++ b/hosting/src/components/Button.tsx @@ -1,4 +1,4 @@ -import React, {useState} from "react"; +import React, { useState } from "react"; interface ButtonProps { title: string; @@ -36,16 +36,16 @@ export function Button({title, onClick, style = "normal", color = "primary", chi switch (color) { case "primary": - styles.push("bg-primary", "text-white"); + styles.push("bg-primary", !style ? "text-white" : ""); break; case "meta-3": - styles.push("bg-meta-3", "text-white"); + styles.push("bg-meta-3", !style ? "text-white" : ""); break; case "black": - styles.push("bg-black", "text-white"); + styles.push("bg-black", !style ? "text-white" : ""); break; default: - styles.push("bg-primary", "text-white"); + styles.push("bg-primary", !style ? "text-white" : ""); } switch (style) { diff --git a/hosting/src/components/Header/index.tsx b/hosting/src/components/Header/index.tsx index 37b5e5d6..4e77536b 100644 --- a/hosting/src/components/Header/index.tsx +++ b/hosting/src/components/Header/index.tsx @@ -1,6 +1,7 @@ +import { Button } from "@/components/Button"; import DarkModeSwitcher from "@/components/Header/DarkModeSwitcher"; import DropdownUser from "@/components/Header/DropdownUser"; -import {useAuthentication} from "@/hooks/useAuthentication"; +import { useAuthentication } from "@/hooks/useAuthentication"; import Image from "next/image"; import Link from "next/link"; @@ -9,6 +10,12 @@ const Header = (props: {sidebarOpen: string | boolean | undefined; setSidebarOpe return (
+