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 (
+