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 ( + + + + + +
+
Notifications
+ {unreadCount > 0 && ( + + )} +
+
+ {notifications.map((notification) => ( +
+
+ {/* {notification.user} */} +
+ +
{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