diff --git a/app/(dashboard)/[tenant]/settings/actions.ts b/app/(dashboard)/[tenant]/settings/actions.ts
index d7a7b43..13e88ee 100644
--- a/app/(dashboard)/[tenant]/settings/actions.ts
+++ b/app/(dashboard)/[tenant]/settings/actions.ts
@@ -1,7 +1,7 @@
"use server";
import { logtoConfig } from "@/app/logto";
-import { user } from "@/drizzle/schema";
+import { notification, user } from "@/drizzle/schema";
import { updateUser } from "@/lib/ops/auth";
import { database } from "@/lib/utils/useDatabase";
import { getOwner } from "@/lib/utils/useOwner";
@@ -43,6 +43,20 @@ export async function updateUserData(payload: FormData) {
return { success: true };
}
+export async function getUserNotifications() {
+ const { userId } = await getOwner();
+
+ const db = await database();
+ const notifications = await db.query.notification.findMany({
+ where: eq(notification.userId, userId),
+ with: {
+ user: true,
+ },
+ });
+
+ return notifications;
+}
+
export async function logout() {
await signOut(logtoConfig);
}
diff --git a/components/console/navbar.tsx b/components/console/navbar.tsx
index 9ddca8c..84bddca 100644
--- a/components/console/navbar.tsx
+++ b/components/console/navbar.tsx
@@ -3,6 +3,7 @@ import Image from "next/image";
import Link from "next/link";
import logo from "../../public/images/logo.png";
import { OrgSwitcher, ProjectSwitcher, UserButton } from "../core/auth";
+import { Notifications } from "../ui/popover-with-notificaition";
import NavBarLinks from "./navbar-links";
export default function NavBar({
@@ -52,7 +53,9 @@ export default function NavBar({
-
diff --git a/components/core/auth.tsx b/components/core/auth.tsx
index 94d0c01..a8d7bb5 100644
--- a/components/core/auth.tsx
+++ b/components/core/auth.tsx
@@ -137,11 +137,7 @@ export const UserButton = ({ orgSlug }: { orgSlug: string }) => {
return (
-
diff --git a/components/ui/popover-with-notificaition.tsx b/components/ui/popover-with-notificaition.tsx
new file mode 100644
index 0000000..e383654
--- /dev/null
+++ b/components/ui/popover-with-notificaition.tsx
@@ -0,0 +1,122 @@
+"use client";
+
+import { getUserNotifications } from "@/app/(dashboard)/[tenant]/settings/actions";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
+import { NotificationWithUser } from "@/drizzle/types";
+import { Bell } from "lucide-react";
+import { useCallback, useState } from "react";
+
+function Dot({ className }: { className?: string }) {
+ return (
+
+ );
+}
+
+function Notifications() {
+ const [notifications, setNotifications] = useState([]);
+ const unreadCount = notifications.filter((n) => !n.read).length;
+
+ const handleMarkAllAsRead = () => {
+ setNotifications(
+ notifications.map((notification) => ({
+ ...notification,
+ unread: false,
+ })),
+ );
+ };
+
+ const handleNotificationClick = (id: number) => {
+ setNotifications(
+ notifications.map((notification) =>
+ notification.id === id ? { ...notification, unread: false } : notification,
+ ),
+ );
+ };
+
+ const fetchNotifications = useCallback(async () => {
+ getUserNotifications().then(setNotifications);
+ }, []);
+
+ return (
+
+
+
+
+ {unreadCount > 0 && (
+
+ {unreadCount > 99 ? "99+" : unreadCount}
+
+ )}
+
+
+
+
+
Notifications
+ {unreadCount > 0 && (
+
+ Mark all as read
+
+ )}
+
+
+ {notifications.map((notification) => (
+
+
+ {/*

*/}
+
+
handleNotificationClick(notification.id)}
+ >
+
+ {notification.user.firstName}
+ {" "}
+ {notification.target}{" "}
+
+ {notification.target}
+
+ .
+
+
{notification.createdAt.toLocaleDateString()}
+
+ {!notification.read ? (
+
+
+
+ ) : null}
+
+
+ ))}
+
+
+ );
+}
+
+export { Notifications }
\ No newline at end of file
diff --git a/drizzle.config.ts b/drizzle.config.ts
new file mode 100644
index 0000000..101fbdc
--- /dev/null
+++ b/drizzle.config.ts
@@ -0,0 +1,7 @@
+import { defineConfig } from "drizzle-kit";
+
+export default defineConfig({
+ dialect: "sqlite",
+ schema: "./drizzle/schema.ts",
+ out: "./drizzle",
+});
diff --git a/drizzle/0008_optimal_obadiah_stane.sql b/drizzle/0008_optimal_obadiah_stane.sql
new file mode 100644
index 0000000..1cee5b0
--- /dev/null
+++ b/drizzle/0008_optimal_obadiah_stane.sql
@@ -0,0 +1,10 @@
+CREATE TABLE `Notification` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `type` text,
+ `message` text NOT NULL,
+ `target` text NOT NULL,
+ `read` integer DEFAULT false NOT NULL,
+ `createdAt` integer NOT NULL,
+ `userId` text NOT NULL,
+ FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON UPDATE cascade ON DELETE cascade
+);
diff --git a/drizzle/meta/0008_snapshot.json b/drizzle/meta/0008_snapshot.json
new file mode 100644
index 0000000..0a4e01b
--- /dev/null
+++ b/drizzle/meta/0008_snapshot.json
@@ -0,0 +1,1127 @@
+{
+ "version": "6",
+ "dialect": "sqlite",
+ "id": "b75e98f6-b4e8-4500-84b3-bcb6e739268a",
+ "prevId": "2920c062-f4b0-426d-99e8-81dcac77022f",
+ "tables": {
+ "Activity": {
+ "name": "Activity",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "message": {
+ "name": "message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "projectId": {
+ "name": "projectId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "Activity_projectId_Project_id_fk": {
+ "name": "Activity_projectId_Project_id_fk",
+ "tableFrom": "Activity",
+ "tableTo": "Project",
+ "columnsFrom": [
+ "projectId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ },
+ "Activity_userId_User_id_fk": {
+ "name": "Activity_userId_User_id_fk",
+ "tableFrom": "Activity",
+ "tableTo": "User",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "Blob": {
+ "name": "Blob",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "contentType": {
+ "name": "contentType",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "contentSize": {
+ "name": "contentSize",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdByUser": {
+ "name": "createdByUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "documentFolderId": {
+ "name": "documentFolderId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "Blob_key_unique": {
+ "name": "Blob_key_unique",
+ "columns": [
+ "key"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "Blob_createdByUser_User_id_fk": {
+ "name": "Blob_createdByUser_User_id_fk",
+ "tableFrom": "Blob",
+ "tableTo": "User",
+ "columnsFrom": [
+ "createdByUser"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ },
+ "Blob_documentFolderId_DocumentFolder_id_fk": {
+ "name": "Blob_documentFolderId_DocumentFolder_id_fk",
+ "tableFrom": "Blob",
+ "tableTo": "DocumentFolder",
+ "columnsFrom": [
+ "documentFolderId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "Event": {
+ "name": "Event",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "start": {
+ "name": "start",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "end": {
+ "name": "end",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "allDay": {
+ "name": "allDay",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "repeatRule": {
+ "name": "repeatRule",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "projectId": {
+ "name": "projectId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdByUser": {
+ "name": "createdByUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "Event_projectId_Project_id_fk": {
+ "name": "Event_projectId_Project_id_fk",
+ "tableFrom": "Event",
+ "tableTo": "Project",
+ "columnsFrom": [
+ "projectId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ },
+ "Event_createdByUser_User_id_fk": {
+ "name": "Event_createdByUser_User_id_fk",
+ "tableFrom": "Event",
+ "tableTo": "User",
+ "columnsFrom": [
+ "createdByUser"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "Comment": {
+ "name": "Comment",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdByUser": {
+ "name": "createdByUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "parentId": {
+ "name": "parentId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "Comment_createdByUser_User_id_fk": {
+ "name": "Comment_createdByUser_User_id_fk",
+ "tableFrom": "Comment",
+ "tableTo": "User",
+ "columnsFrom": [
+ "createdByUser"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "Document": {
+ "name": "Document",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "markdownContent": {
+ "name": "markdownContent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "projectId": {
+ "name": "projectId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "folderId": {
+ "name": "folderId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdByUser": {
+ "name": "createdByUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "Document_projectId_Project_id_fk": {
+ "name": "Document_projectId_Project_id_fk",
+ "tableFrom": "Document",
+ "tableTo": "Project",
+ "columnsFrom": [
+ "projectId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ },
+ "Document_folderId_DocumentFolder_id_fk": {
+ "name": "Document_folderId_DocumentFolder_id_fk",
+ "tableFrom": "Document",
+ "tableTo": "DocumentFolder",
+ "columnsFrom": [
+ "folderId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ },
+ "Document_createdByUser_User_id_fk": {
+ "name": "Document_createdByUser_User_id_fk",
+ "tableFrom": "Document",
+ "tableTo": "User",
+ "columnsFrom": [
+ "createdByUser"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "DocumentFolder": {
+ "name": "DocumentFolder",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "projectId": {
+ "name": "projectId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdByUser": {
+ "name": "createdByUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "DocumentFolder_projectId_Project_id_fk": {
+ "name": "DocumentFolder_projectId_Project_id_fk",
+ "tableFrom": "DocumentFolder",
+ "tableTo": "Project",
+ "columnsFrom": [
+ "projectId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ },
+ "DocumentFolder_createdByUser_User_id_fk": {
+ "name": "DocumentFolder_createdByUser_User_id_fk",
+ "tableFrom": "DocumentFolder",
+ "tableTo": "User",
+ "columnsFrom": [
+ "createdByUser"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "CalendarEventInvite": {
+ "name": "CalendarEventInvite",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "eventId": {
+ "name": "eventId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(unixepoch())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "CalendarEventInvite_eventId_Event_id_fk": {
+ "name": "CalendarEventInvite_eventId_Event_id_fk",
+ "tableFrom": "CalendarEventInvite",
+ "tableTo": "Event",
+ "columnsFrom": [
+ "eventId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ },
+ "CalendarEventInvite_userId_User_id_fk": {
+ "name": "CalendarEventInvite_userId_User_id_fk",
+ "tableFrom": "CalendarEventInvite",
+ "tableTo": "User",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "Notification": {
+ "name": "Notification",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "message": {
+ "name": "message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "target": {
+ "name": "target",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "read": {
+ "name": "read",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "Notification_userId_User_id_fk": {
+ "name": "Notification_userId_User_id_fk",
+ "tableFrom": "Notification",
+ "tableTo": "User",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "Project": {
+ "name": "Project",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "dueDate": {
+ "name": "dueDate",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdByUser": {
+ "name": "createdByUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "Project_createdByUser_User_id_fk": {
+ "name": "Project_createdByUser_User_id_fk",
+ "tableFrom": "Project",
+ "tableTo": "User",
+ "columnsFrom": [
+ "createdByUser"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "Task": {
+ "name": "Task",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "taskListId": {
+ "name": "taskListId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "dueDate": {
+ "name": "dueDate",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "position": {
+ "name": "position",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "assignedToUser": {
+ "name": "assignedToUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdByUser": {
+ "name": "createdByUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "Task_taskListId_TaskList_id_fk": {
+ "name": "Task_taskListId_TaskList_id_fk",
+ "tableFrom": "Task",
+ "tableTo": "TaskList",
+ "columnsFrom": [
+ "taskListId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ },
+ "Task_assignedToUser_User_id_fk": {
+ "name": "Task_assignedToUser_User_id_fk",
+ "tableFrom": "Task",
+ "tableTo": "User",
+ "columnsFrom": [
+ "assignedToUser"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ },
+ "Task_createdByUser_User_id_fk": {
+ "name": "Task_createdByUser_User_id_fk",
+ "tableFrom": "Task",
+ "tableTo": "User",
+ "columnsFrom": [
+ "createdByUser"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "TaskList": {
+ "name": "TaskList",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "dueDate": {
+ "name": "dueDate",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "projectId": {
+ "name": "projectId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdByUser": {
+ "name": "createdByUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "TaskList_projectId_Project_id_fk": {
+ "name": "TaskList_projectId_Project_id_fk",
+ "tableFrom": "TaskList",
+ "tableTo": "Project",
+ "columnsFrom": [
+ "projectId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ },
+ "TaskList_createdByUser_User_id_fk": {
+ "name": "TaskList_createdByUser_User_id_fk",
+ "tableFrom": "TaskList",
+ "tableTo": "User",
+ "columnsFrom": [
+ "createdByUser"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "User": {
+ "name": "User",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "firstName": {
+ "name": "firstName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "lastName": {
+ "name": "lastName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "imageUrl": {
+ "name": "imageUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "rawData": {
+ "name": "rawData",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "User_email_unique": {
+ "name": "User_email_unique",
+ "columns": [
+ "email"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ }
+ },
+ "views": {},
+ "enums": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "indexes": {}
+ }
+}
\ No newline at end of file
diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json
index 9e56123..311c050 100644
--- a/drizzle/meta/_journal.json
+++ b/drizzle/meta/_journal.json
@@ -57,6 +57,13 @@
"when": 1722984048795,
"tag": "0007_happy_callisto",
"breakpoints": true
+ },
+ {
+ "idx": 8,
+ "version": "6",
+ "when": 1736973604947,
+ "tag": "0008_optimal_obadiah_stane",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/drizzle/schema.ts b/drizzle/schema.ts
index abfb25e..a188602 100644
--- a/drizzle/schema.ts
+++ b/drizzle/schema.ts
@@ -237,6 +237,18 @@ export const activity = sqliteTable("Activity", {
createdAt: integer("createdAt", { mode: "timestamp" }).notNull(),
});
+export const notification = sqliteTable("Notification", {
+ id: integer("id", { mode: "number" }).primaryKey({ autoIncrement: true }),
+ type: text("type"),
+ message: text("message").notNull(),
+ target: text("target").notNull(),
+ read: integer("read", { mode: "boolean" }).notNull().default(false),
+ createdAt: integer("createdAt", { mode: "timestamp" }).notNull(),
+ userId: text("userId")
+ .notNull()
+ .references(() => user.id, { onDelete: "cascade", onUpdate: "cascade" }),
+});
+
export const userRelations = relations(user, ({ many }) => ({
projects: many(project),
documents: many(document),
@@ -328,3 +340,10 @@ export const activityRelations = relations(activity, ({ one }) => ({
references: [project.id],
}),
}));
+
+export const notificationRelations = relations(notification, ({ one }) => ({
+ user: one(user, {
+ fields: [notification.userId],
+ references: [user.id],
+ }),
+}));
diff --git a/drizzle/types.ts b/drizzle/types.ts
index 759cb4e..cd2e2a0 100644
--- a/drizzle/types.ts
+++ b/drizzle/types.ts
@@ -6,6 +6,7 @@ import type {
document,
documentFolder,
eventInvite,
+ notification,
project,
task,
taskList,
@@ -22,6 +23,7 @@ export type Blob = InferSelectModel;
export type CalendarEvent = InferSelectModel;
export type EventInvite = InferSelectModel;
export type Activity = InferSelectModel;
+export type Notification = InferSelectModel;
export type ProjectWithCreator = Project & { creator: User };
@@ -81,3 +83,7 @@ export type EventWithInvites = EventWithCreator & {
export type ActivityWithActor = Activity & {
actor: Pick;
};
+
+export type NotificationWithUser = Notification & {
+ user: Pick;
+};
diff --git a/package.json b/package.json
index eafe3a8..0436e8e 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,6 @@
"build": "next build",
"start": "next start",
"lint": "biome lint",
- "migrate": "drizzle-kit push",
"generate:migrations": "drizzle-kit generate"
},
"dependencies": {
@@ -37,7 +36,7 @@
"clsx": "^1.2.1",
"cmdk": "0.2.0",
"date-fns": "^2.30.0",
- "drizzle-orm": "^0.38.2",
+ "drizzle-orm": "^0.38.3",
"easymde": "^2.18.0",
"eslint-config-next": "15.1.0",
"ical-generator": "^8.0.1",
@@ -78,7 +77,7 @@
"@types/react-dom": "19.0.2",
"@types/uuid": "^9.0.2",
"dotenv": "^16.3.1",
- "drizzle-kit": "^0.22.8",
+ "drizzle-kit": "^0.30.1",
"encoding": "^0.1.13",
"typescript": "5.7.2"
},
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ca00dc8..683c2d0 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -88,8 +88,8 @@ importers:
specifier: ^2.30.0
version: 2.30.0
drizzle-orm:
- specifier: ^0.38.2
- version: 0.38.2(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.12)(@types/react@19.0.1)(better-sqlite3@11.7.0)(react@19.0.0)
+ specifier: ^0.38.3
+ version: 0.38.3(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.12)(@types/react@19.0.1)(better-sqlite3@11.7.0)(react@19.0.0)
easymde:
specifier: ^2.18.0
version: 2.18.0
@@ -206,8 +206,8 @@ importers:
specifier: ^16.3.1
version: 16.4.5
drizzle-kit:
- specifier: ^0.22.8
- version: 0.22.8
+ specifier: ^0.30.1
+ version: 0.30.1
encoding:
specifier: ^0.1.13
version: 0.1.13
@@ -482,6 +482,9 @@ packages:
peerDependencies:
react: '>=16.8.0'
+ '@drizzle-team/brocli@0.10.2':
+ resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==}
+
'@edge-runtime/cookies@5.0.2':
resolution: {integrity: sha512-Sd8LcWpZk/SWEeKGE8LT6gMm5MGfX/wm+GPnh1eBEtCpya3vYqn37wYknwAHw92ONoyyREl1hJwxV/Qx2DWNOg==}
engines: {node: '>=16'}
@@ -491,9 +494,11 @@ packages:
'@esbuild-kit/core-utils@3.3.2':
resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==}
+ deprecated: 'Merged into tsx: https://tsx.is'
'@esbuild-kit/esm-loader@2.6.5':
resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==}
+ deprecated: 'Merged into tsx: https://tsx.is'
'@esbuild/aix-ppc64@0.19.12':
resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==}
@@ -2696,12 +2701,12 @@ packages:
resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==}
engines: {node: '>=12'}
- drizzle-kit@0.22.8:
- resolution: {integrity: sha512-VjI4wsJjk3hSqHSa3TwBf+uvH6M6pRHyxyoVbt935GUzP9tUR/BRZ+MhEJNgryqbzN2Za1KP0eJMTgKEPsalYQ==}
+ drizzle-kit@0.30.1:
+ resolution: {integrity: sha512-HmA/NeewvHywhJ2ENXD3KvOuM/+K2dGLJfxVfIHsGwaqKICJnS+Ke2L6UcSrSrtMJLJaT0Im1Qv4TFXfaZShyw==}
hasBin: true
- drizzle-orm@0.38.2:
- resolution: {integrity: sha512-eCE3yPRAskLo1WpM9OHpFaM70tBEDsWhwR/0M3CKyztAXKR9Qs3asZlcJOEliIcUSg8GuwrlY0dmYDgmm6y5GQ==}
+ drizzle-orm@0.38.3:
+ resolution: {integrity: sha512-w41Y+PquMpSff/QDRGdItG0/aWca+/J3Sda9PPGkTxBtjWQvgU1jxlFBXdjog5tYvTu58uvi3PwR1NuCx0KeZg==}
peerDependencies:
'@aws-sdk/client-rds-data': '>=3'
'@cloudflare/workers-types': '>=4'
@@ -5218,11 +5223,13 @@ snapshots:
react: 19.0.0
tslib: 2.6.3
+ '@drizzle-team/brocli@0.10.2': {}
+
'@edge-runtime/cookies@5.0.2': {}
'@emnapi/runtime@1.2.0':
dependencies:
- tslib: 2.6.3
+ tslib: 2.8.1
optional: true
'@esbuild-kit/core-utils@3.3.2':
@@ -7428,15 +7435,16 @@ snapshots:
dotenv@16.4.5: {}
- drizzle-kit@0.22.8:
+ drizzle-kit@0.30.1:
dependencies:
+ '@drizzle-team/brocli': 0.10.2
'@esbuild-kit/esm-loader': 2.6.5
esbuild: 0.19.12
esbuild-register: 3.6.0(esbuild@0.19.12)
transitivePeerDependencies:
- supports-color
- drizzle-orm@0.38.2(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.12)(@types/react@19.0.1)(better-sqlite3@11.7.0)(react@19.0.0):
+ drizzle-orm@0.38.3(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.12)(@types/react@19.0.1)(better-sqlite3@11.7.0)(react@19.0.0):
optionalDependencies:
'@opentelemetry/api': 1.9.0
'@types/better-sqlite3': 7.6.12