diff --git a/.env.development b/.env.development deleted file mode 100644 index 31bf9fd..0000000 --- a/.env.development +++ /dev/null @@ -1,5 +0,0 @@ -HOST=dev.seistart.com -PORT=3000 - -VITE_DOMAIN=dev.seistart.com -VITE_NAMESPACE=dev \ No newline at end of file diff --git a/.env.test b/.env.test deleted file mode 100644 index 129d6d0..0000000 --- a/.env.test +++ /dev/null @@ -1,5 +0,0 @@ -HOST=tst.seistart.com -PORT=3000 - -VITE_DOMAIN=tst.seistart.com -VITE_NAMESPACE=tst \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3226984..7c2e720 100644 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,7 @@ next-env.d.ts # open-next .open-next +#storybook *storybook.log + +database/migrations \ No newline at end of file diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 3bb5fcd..0000000 --- a/Dockerfile +++ /dev/null @@ -1,31 +0,0 @@ -FROM node:21 as base -# RUN apk add --no-cache g++ make py3-pip libc6-compat -WORKDIR /app -COPY package*.json ./ -RUN npm install -EXPOSE 3000 - -FROM base as builder -WORKDIR /app -COPY . . -RUN npm run build - - -FROM base as production -WORKDIR /app - -ENV NODE_ENV=production -RUN npm ci - -COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next -COPY --from=builder /app/node_modules ./node_modules -COPY --from=builder /app/package.json ./package.json -COPY --from=builder /app/public ./public - -CMD npm start - -FROM base as dev -ENV NODE_ENV=development -RUN npm install -COPY . . -CMD npm run dev \ No newline at end of file diff --git a/Dockerrun.aws.json b/Dockerrun.aws.json deleted file mode 100644 index 2ecb616..0000000 --- a/Dockerrun.aws.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "AWSEBDockerrunVersion": "1", - "Authentication": { - "bucket": "docker-login-bucket-seistart", - "key": "dockercfg" - }, - "Image": { - "Name": "siddharth9903/seistart:latest", - "Update": "true" - }, - "Ports": [ - { - "ContainerPort": 3000, - "HostPort": 80 - } - ], - "Logging": "/var/log/nginx" -} diff --git a/app/(app)/blog/[slug]/loading.tsx b/app/(app)/blog/[slug]/loading.tsx new file mode 100644 index 0000000..eb90f60 --- /dev/null +++ b/app/(app)/blog/[slug]/loading.tsx @@ -0,0 +1,25 @@ +export default function Loading() { + return ( +
+
+ + Loading... +
+
+ ) +} diff --git a/app/(app)/blog/loading.tsx b/app/(app)/blog/loading.tsx new file mode 100644 index 0000000..eb90f60 --- /dev/null +++ b/app/(app)/blog/loading.tsx @@ -0,0 +1,25 @@ +export default function Loading() { + return ( +
+
+ + Loading... +
+
+ ) +} diff --git a/app/(app)/dashboard/analytics/loading.tsx b/app/(app)/dashboard/analytics/loading.tsx new file mode 100644 index 0000000..eb90f60 --- /dev/null +++ b/app/(app)/dashboard/analytics/loading.tsx @@ -0,0 +1,25 @@ +export default function Loading() { + return ( +
+
+ + Loading... +
+
+ ) +} diff --git a/app/(app)/dashboard/analytics/page.tsx b/app/(app)/dashboard/analytics/page.tsx index 5625f9a..64a4a79 100644 --- a/app/(app)/dashboard/analytics/page.tsx +++ b/app/(app)/dashboard/analytics/page.tsx @@ -4,6 +4,6 @@ import { redirect } from "next/navigation" export default async function AnalyticsPage() { const { entitlements } = await getUser() - if (!entitlements || entitlements.role !== Role.Admin) redirect("/dashboard") + if (!entitlements || entitlements.role !== Role.Admin) redirect("/") return

Analytics

} diff --git a/app/(app)/dashboard/create-project/loading.tsx b/app/(app)/dashboard/create-project/loading.tsx new file mode 100644 index 0000000..eb90f60 --- /dev/null +++ b/app/(app)/dashboard/create-project/loading.tsx @@ -0,0 +1,25 @@ +export default function Loading() { + return ( +
+
+ + Loading... +
+
+ ) +} diff --git a/app/(app)/dashboard/create-project/page.tsx b/app/(app)/dashboard/create-project/page.tsx index a3131d6..2bca7d1 100644 --- a/app/(app)/dashboard/create-project/page.tsx +++ b/app/(app)/dashboard/create-project/page.tsx @@ -1,15 +1,19 @@ import { getUser } from "@/auth/auth-guard" import { CreateProject } from "@/components/pages/create-project" import { Permission } from "@/server-actions/entitlements/entitlements.models" +import { getAllStagesAction } from "@/server-actions/stages/stages.actions" import { getAllTagsAction } from "@/server-actions/tags/tags.actions" import { redirect } from "next/navigation" export default async function CreateProjectPage() { - const { entitlements } = await getUser() + const [{ tags }, { stages }, { entitlements }] = await Promise.all([ + getAllTagsAction(), + getAllStagesAction(), + getUser(), + ]) if (entitlements && entitlements.permissions[Permission.ProjectSelfWrite]) { - const { tags } = await getAllTagsAction() - return + return } else { - redirect("/dashboard") + redirect("/") } } diff --git a/app/(app)/dashboard/edit-projects/[slug]/page.tsx b/app/(app)/dashboard/edit-projects/[slug]/page.tsx index f7ae07c..c08ac74 100644 --- a/app/(app)/dashboard/edit-projects/[slug]/page.tsx +++ b/app/(app)/dashboard/edit-projects/[slug]/page.tsx @@ -2,6 +2,7 @@ import { getUser } from "@/auth/auth-guard" import { EditProject } from "@/components/pages/edit-project" import { Permission } from "@/server-actions/entitlements/entitlements.models" import { getProjectBySlugAction } from "@/server-actions/projects/projects.actions" +import { getAllStagesAction } from "@/server-actions/stages/stages.actions" import { getAllTagsAction } from "@/server-actions/tags/tags.actions" import { Metadata } from "next" import { redirect } from "next/navigation" @@ -15,17 +16,23 @@ export default async function EditProjectPage({ }: { params: { slug: string } }) { - const { entitlements, userId } = await getUser() - const { project } = await getProjectBySlugAction(params.slug) + const [{ tags }, { stages }, { entitlements, userId }, { project }] = + await Promise.all([ + getAllTagsAction(), + getAllStagesAction(), + getUser(), + getProjectBySlugAction(params.slug), + ]) if ( entitlements && ((entitlements.permissions[Permission.ProjectSelfEdit] && project.userId === userId) || entitlements.permissions[Permission.ProjectAllEdit]) ) { - const { tags } = await getAllTagsAction() - return + return ( + + ) } else { - redirect("/dashboard") + redirect("/") } } diff --git a/app/(app)/dashboard/edit-projects/loading.tsx b/app/(app)/dashboard/edit-projects/loading.tsx new file mode 100644 index 0000000..eb90f60 --- /dev/null +++ b/app/(app)/dashboard/edit-projects/loading.tsx @@ -0,0 +1,25 @@ +export default function Loading() { + return ( +
+
+ + Loading... +
+
+ ) +} diff --git a/app/(app)/dashboard/edit-projects/page.tsx b/app/(app)/dashboard/edit-projects/page.tsx index 4d778c6..5beaa27 100644 --- a/app/(app)/dashboard/edit-projects/page.tsx +++ b/app/(app)/dashboard/edit-projects/page.tsx @@ -29,6 +29,6 @@ export default async function EditProjectsPage() { ) } else { - redirect("/dashboard") + redirect("/") } } diff --git a/app/(app)/dashboard/layout.tsx b/app/(app)/dashboard/layout.tsx index c63e26c..ba49368 100644 --- a/app/(app)/dashboard/layout.tsx +++ b/app/(app)/dashboard/layout.tsx @@ -2,15 +2,15 @@ import { getUser } from "@/auth/auth-guard" import { DashboardSidebar } from "@/components/dashboard/dashboard-sidebar" import { redirect } from "next/navigation" -export const dynamic = "force-dynamic" - export default async function DashboardLayout({ children, }: { children: React.ReactNode }) { const { userId } = await getUser() - if (!userId) redirect("/") + if (!userId) { + redirect("/") + } return ( <>
diff --git a/app/(app)/dashboard/loading.tsx b/app/(app)/dashboard/loading.tsx new file mode 100644 index 0000000..eb90f60 --- /dev/null +++ b/app/(app)/dashboard/loading.tsx @@ -0,0 +1,25 @@ +export default function Loading() { + return ( +
+
+ + Loading... +
+
+ ) +} diff --git a/app/(app)/dashboard/page.tsx b/app/(app)/dashboard/page.tsx index caaa1a5..e37b2cb 100644 --- a/app/(app)/dashboard/page.tsx +++ b/app/(app)/dashboard/page.tsx @@ -1,10 +1,15 @@ -import SignOutBtn from "@/components/auth/sign-out-button" +import { getUser } from "@/auth/auth-guard" +import { redirect } from "next/navigation" export default async function DashboardPage() { + const { userId } = await getUser() + if (!userId) { + redirect("/") + } return ( <>
-

Home

+

Home

) diff --git a/app/(app)/dashboard/tags/loading.tsx b/app/(app)/dashboard/tags/loading.tsx new file mode 100644 index 0000000..eb90f60 --- /dev/null +++ b/app/(app)/dashboard/tags/loading.tsx @@ -0,0 +1,25 @@ +export default function Loading() { + return ( +
+
+ + Loading... +
+
+ ) +} diff --git a/app/(app)/dashboard/tags/page.tsx b/app/(app)/dashboard/tags/page.tsx index 1d7e6be..5065dd6 100644 --- a/app/(app)/dashboard/tags/page.tsx +++ b/app/(app)/dashboard/tags/page.tsx @@ -5,8 +5,11 @@ import { getAllTagsAction } from "@/server-actions/tags/tags.actions" import { redirect } from "next/navigation" export default async function ProjectsPage() { - const { entitlements } = await getUser() - if (!entitlements || entitlements.role !== Role.Admin) redirect("/dashboard") - const { tags } = await getAllTagsAction() + const [{ tags }, { entitlements }] = await Promise.all([ + getAllTagsAction(), + getUser(), + ]) + if (!entitlements || entitlements.role !== Role.Admin) redirect("/") + return } diff --git a/app/(app)/dashboard/wallets/loading.tsx b/app/(app)/dashboard/wallets/loading.tsx new file mode 100644 index 0000000..eb90f60 --- /dev/null +++ b/app/(app)/dashboard/wallets/loading.tsx @@ -0,0 +1,25 @@ +export default function Loading() { + return ( +
+
+ + Loading... +
+
+ ) +} diff --git a/app/(app)/dashboard/wallets/page.tsx b/app/(app)/dashboard/wallets/page.tsx index 02e55b0..2de05e8 100644 --- a/app/(app)/dashboard/wallets/page.tsx +++ b/app/(app)/dashboard/wallets/page.tsx @@ -1,7 +1,13 @@ +import { getUser } from "@/auth/auth-guard" import { Wallets } from "@/components/pages/wallets" import { getAllWalletsAction } from "@/server-actions/wallets/wallets.actions" +import { redirect } from "next/navigation" export default async function ProjectsPage() { + const { userId } = await getUser() + if (!userId) { + redirect("/") + } const { wallets } = await getAllWalletsAction() return } diff --git a/app/(app)/projects/TagFilter.tsx b/app/(app)/projects/TagFilter.tsx index 00c19d6..0085ef9 100644 --- a/app/(app)/projects/TagFilter.tsx +++ b/app/(app)/projects/TagFilter.tsx @@ -1,26 +1,26 @@ -import { Button } from "@/components/ui/button" -import { projectTagSchema } from "@/database/schemas/projects.schema" -import { useFilterStore } from "@/stores/project-filter-store" +// import { Button } from "@/components/ui/button" +// import { projectTagSchema } from "@/database/schemas/projects.schema" +// import { useFilterStore } from "@/stores/project-filter-store" -const TagFilter = () => { - const { filter, toggleMainTagFilter } = useFilterStore() +// const TagFilter = () => { +// const { filter, toggleMainTagFilter } = useFilterStore() - console.log("filter", filter) +// console.log("filter", filter) - return ( -
- {projectTagSchema.options.map((tag) => ( - - ))} -
- ) -} +// return ( +//
+// {projectTagSchema.options.map((tag) => ( +// +// ))} +//
+// ) +// } -export default TagFilter +// export default TagFilter diff --git a/app/(app)/projects/[slug]/loading.tsx b/app/(app)/projects/[slug]/loading.tsx new file mode 100644 index 0000000..eb90f60 --- /dev/null +++ b/app/(app)/projects/[slug]/loading.tsx @@ -0,0 +1,25 @@ +export default function Loading() { + return ( +
+
+ + Loading... +
+
+ ) +} diff --git a/app/(app)/projects/[slug]/page.tsx b/app/(app)/projects/[slug]/page.tsx index c01b213..815e6cc 100644 --- a/app/(app)/projects/[slug]/page.tsx +++ b/app/(app)/projects/[slug]/page.tsx @@ -1,5 +1,8 @@ import { AspectRatio } from "@/components/ui/aspect-ratio" -import { getProjectBySlugAction } from "@/server-actions/projects/projects.actions" +import { + getAllProjectsAction, + getProjectBySlugAction, +} from "@/server-actions/projects/projects.actions" import { TwitterIcon } from "lucide-react" import Image from "next/image" import Link from "next/link" @@ -8,8 +11,6 @@ import { GoDotFill } from "react-icons/go" import { IoBookOutline } from "react-icons/io5" import ProjectLinks from "./ProjectLinks" -export const dynamic = "force-dynamic" - export async function generateMetadata({ params, }: { @@ -23,6 +24,13 @@ export async function generateMetadata({ } } +export async function generateStaticParams() { + const { projects } = await getAllProjectsAction() + return projects.map((project) => ({ + slug: project.slug, + })) +} + const images = [ { url: "/images/noimage.webp", diff --git a/app/(app)/projects/loading.tsx b/app/(app)/projects/loading.tsx new file mode 100644 index 0000000..eb90f60 --- /dev/null +++ b/app/(app)/projects/loading.tsx @@ -0,0 +1,25 @@ +export default function Loading() { + return ( +
+
+ + Loading... +
+
+ ) +} diff --git a/app/layout.tsx b/app/layout.tsx index a7cc26a..f482e09 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -2,7 +2,6 @@ import { Footer } from "@/components/footer" import { Header } from "@/components/header" import { appMetadata } from "@/config/meteada.config" import AppProvider from "@/providers/app-provider" -import { getCompleteUserProfileAction } from "@/server-actions/user-profile/user-profile.actions" import { cn } from "@/utils/tailwind.utils" import type { Metadata } from "next" import { Space_Grotesk } from "next/font/google" @@ -12,17 +11,15 @@ const spaceGrotesk = Space_Grotesk({ subsets: ["latin"] }) export const metadata: Metadata = { ...appMetadata.home, } -const PRE_LAUNCH = true export default async function RootLayout({ children, }: Readonly<{ children: React.ReactNode }>) { - const { userProfile } = await getCompleteUserProfileAction() return ( - +
{children}
diff --git a/auth/auth-guard.ts b/auth/auth-guard.ts index c204234..f65f9e8 100644 --- a/auth/auth-guard.ts +++ b/auth/auth-guard.ts @@ -1,51 +1,42 @@ import type { Session, User } from "lucia" import { cookies } from "next/headers" -import { redirect } from "next/navigation" -import { cache } from "react" import { lucia } from "./lucia" -export const authGuard = async () => { - const { session } = await validateRequest() - if (!session) redirect("/sign-in") -} - export const getUser = async () => { const { session, user } = await validateRequest() if (!session) return { userId: undefined } return { userId: user.id, entitlements: session.entitlements } } -export const validateRequest = cache( - async (): Promise< - { user: User; session: Session } | { user: null; session: null } - > => { - const sessionId = cookies().get(lucia.sessionCookieName)?.value ?? null - if (!sessionId) { - return { - user: null, - session: null, - } +export const validateRequest = async (): Promise< + { user: User; session: Session } | { user: null; session: null } +> => { + const sessionId = cookies().get(lucia.sessionCookieName)?.value ?? null + if (!sessionId) { + return { + user: null, + session: null, } - - const result = await lucia.validateSession(sessionId) - try { - if (result.session && result.session.fresh) { - const sessionCookie = lucia.createSessionCookie(result.session.id) - cookies().set( - sessionCookie.name, - sessionCookie.value, - sessionCookie.attributes - ) - } - if (!result.session) { - const sessionCookie = lucia.createBlankSessionCookie() - cookies().set( - sessionCookie.name, - sessionCookie.value, - sessionCookie.attributes - ) - } - } catch {} - return result } -) + + const result = await lucia.validateSession(sessionId) + try { + if (result.session && result.session.fresh) { + const sessionCookie = lucia.createSessionCookie(result.session.id) + cookies().set( + sessionCookie.name, + sessionCookie.value, + sessionCookie.attributes + ) + } + if (!result.session) { + const sessionCookie = lucia.createBlankSessionCookie() + cookies().set( + sessionCookie.name, + sessionCookie.value, + sessionCookie.attributes + ) + } + } catch {} + return result +} diff --git a/auth/lucia.ts b/auth/lucia.ts index a05bda0..6142348 100644 --- a/auth/lucia.ts +++ b/auth/lucia.ts @@ -1,11 +1,18 @@ import { adapter } from "@/database/schemas/auth.schema" import { Entitlements } from "@/server-actions/entitlements/entitlements.models" -import { Lucia } from "lucia" +import { Lucia, TimeSpan } from "lucia" export const lucia = new Lucia(adapter, { + sessionExpiresIn: new TimeSpan(30, "d"), sessionCookie: { + expires: true, attributes: { secure: process.env.NODE_ENV === "production", + sameSite: process.env.NODE_ENV === "production" ? "strict" : undefined, + domain: + process.env.NODE_ENV === "production" + ? "d3qryrn2zooyrh.cloudfront.net" + : undefined, }, }, getSessionAttributes: (attributes) => { diff --git a/bun.lockb b/bun.lockb index 38edad1..5fbcdab 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/components/auth/sign-out-button.tsx b/components/auth/sign-out-button.tsx index f799ff2..002e7e5 100644 --- a/components/auth/sign-out-button.tsx +++ b/components/auth/sign-out-button.tsx @@ -1,14 +1,12 @@ "use client" import { signOutAction } from "@/server-actions/users/users.actions" -import { usePathname } from "next/navigation" import { useFormStatus } from "react-dom" import { Button } from "../ui/button" export default function SignOutBtn() { - const pathname = usePathname() return ( -
signOutAction(pathname)}> + signOutAction()}> ) diff --git a/components/d3/Chart/hooks/useRelationChartManager.ts b/components/d3/Chart/hooks/useRelationChartManager.ts index 38cfeef..c1c70bf 100644 --- a/components/d3/Chart/hooks/useRelationChartManager.ts +++ b/components/d3/Chart/hooks/useRelationChartManager.ts @@ -1,8 +1,8 @@ "use client" +import { debounce } from "@/utils/debounce" import { D3DragEvent } from "d3-drag" import { Timer, timer } from "d3-timer" -import debounce from "lodash/debounce" import { useCallback, useEffect, useMemo, useRef } from "react" import { diff --git a/components/d3/Chart/utils/storage.ts b/components/d3/Chart/utils/storage.ts index 6ab99e8..60d4ec4 100644 --- a/components/d3/Chart/utils/storage.ts +++ b/components/d3/Chart/utils/storage.ts @@ -1,5 +1,3 @@ -import CryptoJS from "crypto-js" - import { NodeDatumState } from "../types" export const clearD3LocalStorage = function (prefix: string) { @@ -37,8 +35,6 @@ export function getD3LocalStorage(key: string, type: "NODE") { // storage hash node en link ids export const generateStorageKey = function (ids: number[]) { - const key = ids.sort().join("") - const sha256Hash = CryptoJS.SHA256(key).toString() - - return sha256Hash + const storageKey = crypto.randomUUID() + return storageKey } diff --git a/components/dashboard/create-project-form.tsx b/components/dashboard/create-project-form.tsx index 4f45544..0c5bf78 100644 --- a/components/dashboard/create-project-form.tsx +++ b/components/dashboard/create-project-form.tsx @@ -15,6 +15,7 @@ import { NewProjectWithTagsParams, Project, } from "@/database/schemas/projects.schema" +import { Stage } from "@/database/schemas/stages.schema" import { Tag } from "@/database/schemas/tags.schema" import { createProjectWithTagsAction, @@ -23,7 +24,7 @@ import { import { cn } from "@/utils/tailwind.utils" import { zodResolver } from "@hookform/resolvers/zod" import { format } from "date-fns" -import { CalendarIcon } from "lucide-react" +import { CalendarIcon, Loader2 } from "lucide-react" import { useRouter } from "next/navigation" import { SubmitHandler, useForm } from "react-hook-form" import { z } from "zod" @@ -78,6 +79,7 @@ const steps = [ interface CreateProjectFormProps { tags: Tag[] + stages: Stage[] project?: Project } @@ -100,7 +102,7 @@ export const ProjectFormDataSchema = z.object({ tokenName: z.string().optional(), website: z.string().optional(), whitepaper: z.string().optional(), - stage: z.enum(["Mainnet", "Testnet", "Devnet", "Local/Private"]).optional(), + stageId: z.number(), releaseDate: z.date().optional().nullable(), isLive: z.boolean().optional(), twitter: z.string().optional(), @@ -135,10 +137,11 @@ export const mapProjectTagNamesToIds = ( export default function CreateProjectForm({ tags, + stages, project, }: CreateProjectFormProps) { const router = useRouter() - + const [isSubmitting, setIsSubmitting] = useState(false) const [_, setPreviousStep] = useState(0) const [currentStep, setCurrentStep] = useState(0) const form = useForm({ @@ -152,7 +155,7 @@ export default function CreateProjectForm({ releaseDate: project?.releaseDate || null, summary: project?.summary || "", isLive: project?.isLive || false, - stage: project?.stage || "Local/Private", + stageId: project?.stageId || stages[0].id, description: project?.description || "", communitySize: project?.communitySize || 0, website: project?.website || "", @@ -168,13 +171,18 @@ export default function CreateProjectForm({ }) const processForm: SubmitHandler = async (data) => { - let slug - if (project) { - slug = await updateProjectWithTagsAction(data, project.id) - } else { - slug = await createProjectWithTagsAction(data) + try { + setIsSubmitting(true) + let slug + if (project) { + slug = await updateProjectWithTagsAction(data, project.id) + } else { + slug = await createProjectWithTagsAction(data) + } + router.push(`/dashboard/edit-projects`) + } catch { + setIsSubmitting(false) } - router.push(`/dashboard/edit-projects`) } type FieldName = keyof NewProjectWithTagsParams @@ -414,13 +422,13 @@ export default function CreateProjectForm({ /> ( - Stage (optional) + Stage
@@ -695,7 +705,11 @@ export default function CreateProjectForm({
{currentStep > 0 ? ( -
)} -
diff --git a/components/dashboard/dashboard-sidebar-items.tsx b/components/dashboard/dashboard-sidebar-items.tsx index 299fc1c..363cdbb 100644 --- a/components/dashboard/dashboard-sidebar-items.tsx +++ b/components/dashboard/dashboard-sidebar-items.tsx @@ -5,20 +5,20 @@ import { defaultLinks, type SidebarLink, } from "@/config/navigation.config" -import { CompletUserProfile } from "@/database/schemas/profiles.schema" -import { useUserStore } from "@/providers/user-provider" +import { Entitlements } from "@/server-actions/entitlements/entitlements.models" +import { userStore } from "@/stores/user-store" import { cn } from "@/utils/tailwind.utils" import Link from "next/link" import { usePathname } from "next/navigation" export const DashboardSidebarItems = () => { - const { entitlements } = useUserStore( - (store) => store.userProfile - ) as CompletUserProfile - + const entitlements = userStore().userProfile?.entitlements + if (!entitlements) { + return <> + } return ( <> - + {additionalLinks.length > 0 ? additionalLinks .filter(({ permissions }) => @@ -30,6 +30,7 @@ export const DashboardSidebarItems = () => { ) .map((l) => ( { - const { entitlements } = useUserStore( - (store) => store.userProfile - ) as CompletUserProfile const fullPathname = usePathname() const pathname = "/" + fullPathname.split("/")[1] diff --git a/components/filter/FilterScreen.tsx b/components/filter/FilterScreen.tsx index d379a1b..f7cdbf1 100644 --- a/components/filter/FilterScreen.tsx +++ b/components/filter/FilterScreen.tsx @@ -1,7 +1,4 @@ -import { - projectTagSchema, - stageSchema, -} from "@/database/schemas/projects.schema" +import { projectTagSchema } from "@/database/schemas/projects.schema" import { useFilterStore } from "@/stores/project-filter-store" import { zodResolver } from "@hookform/resolvers/zod" import { DoubleArrowRightIcon } from "@radix-ui/react-icons" @@ -35,7 +32,7 @@ export const FilterScreen = ({ close }: Props) => { const tags = projectTagSchema.options // Stages - Derived from the stage schema - const stages = stageSchema.options + const stages = ["Local/Private", "Devnet", "Testnet", "Mainnet"] React.useEffect(() => { if (!ready) { diff --git a/components/header.tsx b/components/header.tsx index 11e9b3e..c2174e6 100644 --- a/components/header.tsx +++ b/components/header.tsx @@ -37,7 +37,7 @@ export const Header = async ({}: HeaderProps) => {
- +
) diff --git a/components/navigation/logged-in-dropdown.tsx b/components/navigation/logged-in-dropdown.tsx index b3842ae..687b1e4 100644 --- a/components/navigation/logged-in-dropdown.tsx +++ b/components/navigation/logged-in-dropdown.tsx @@ -3,6 +3,7 @@ import { CompletUserProfile } from "@/database/schemas/profiles.schema" import { signOutAction } from "@/server-actions/users/users.actions" import { addWalletAction } from "@/server-actions/wallets/wallets.actions" +import { userStore } from "@/stores/user-store" import { preventDefaultAction } from "@/utils/react-event-handlers.utils" import { SeiWallet } from "@sei-js/core" import { Loader2 } from "lucide-react" @@ -44,6 +45,7 @@ export const LoggedInDropdown = ({ const otherWalletLinked = userProfile.wallets.some( (wallet) => (wallet.walletAddress = walletAddress) ) + const { setUserProfile } = userStore() const [isPending, startTransition] = useTransition() const pathname = usePathname() return ( @@ -51,7 +53,7 @@ export const LoggedInDropdown = ({
{walletAddress ? ( -

+

{`${walletAddress.substring(0, 6)}...${walletAddress.substring(walletAddress.length - 3)}`} {mainWalletAddressConnected @@ -60,7 +62,7 @@ export const LoggedInDropdown = ({ ? "Linked" : "Unlinked"} -

+
) : (

{`${mainWalletAddress.substring(0, 6)}...${mainWalletAddress.substring(mainWalletAddress.length - 3)}`} @@ -72,7 +74,7 @@ export const LoggedInDropdown = ({ {walletAddress ? "Connected" : "Not Connected"} - {!otherWalletLinked && walletAddress && ( + {!otherWalletLinked && walletAddress && !mainWalletAddressConnected && ( { preventDefaultAction(event) @@ -139,8 +141,9 @@ export const LoggedInDropdown = ({ { event.preventDefault() - startTransition(() => { - signOutAction(pathname) + startTransition(async () => { + await signOutAction() + setUserProfile(null) }) }} > diff --git a/components/navigation/user-navigation.tsx b/components/navigation/user-navigation.tsx index 9d577b0..0033e43 100644 --- a/components/navigation/user-navigation.tsx +++ b/components/navigation/user-navigation.tsx @@ -1,9 +1,10 @@ "use client" +import { getCompleteUserProfileAction } from "@/server-actions/user-profile/user-profile.actions" +import { userStore } from "@/stores/user-store" import { Avatar, AvatarFallback, AvatarImage } from "@radix-ui/react-avatar" import { useSelectWallet, useWallet } from "@sei-js/react" -import { useState } from "react" -import { useUserStore } from "../../providers/user-provider" +import { useEffect, useState } from "react" import { Button } from "../ui/button" import { DropdownMenu, @@ -14,10 +15,25 @@ import { LoggedInDropdown } from "./logged-in-dropdown" import { WalletConnectedDropdown } from "./wallet-connected-dropdown" export const UserNavigation = () => { + const { userProfile, setUserProfile } = userStore() + const [isLoading, setIsLoading] = useState(true) + useEffect(() => { + async function getUserProfile() { + setIsLoading(true) + const { userProfile } = await getCompleteUserProfileAction() + setUserProfile(userProfile) + setIsLoading(false) + } + getUserProfile() + }, [setUserProfile]) + const [isDropdownOpen, setDropdownOpen] = useState(false) - const userProfile = useUserStore((store) => store.userProfile) const { connectedWallet, disconnect, accounts } = useWallet() const { openModal: connectWallet } = useSelectWallet() + + if (isLoading) { + return <>loading... + } return ( <> {connectedWallet || userProfile ? ( diff --git a/components/pages/create-project.tsx b/components/pages/create-project.tsx index 947d7f4..a03bb86 100644 --- a/components/pages/create-project.tsx +++ b/components/pages/create-project.tsx @@ -1,10 +1,12 @@ +import { Stage } from "@/database/schemas/stages.schema" import { Tag } from "@/database/schemas/tags.schema" import CreateProjectForm from "../dashboard/create-project-form" interface CreateProjectProps { tags: Tag[] + stages: Stage[] } -export const CreateProject = ({ tags }: CreateProjectProps) => { - return +export const CreateProject = ({ tags, stages }: CreateProjectProps) => { + return } diff --git a/components/pages/edit-project.tsx b/components/pages/edit-project.tsx index 91ae985..fd748c7 100644 --- a/components/pages/edit-project.tsx +++ b/components/pages/edit-project.tsx @@ -1,12 +1,20 @@ import { Project } from "@/database/schemas/projects.schema" +import { Stage } from "@/database/schemas/stages.schema" import { Tag } from "@/database/schemas/tags.schema" import CreateProjectForm from "../dashboard/create-project-form" interface EditProjectProps { tags: Tag[] project: Project + stages: Stage[] } -export const EditProject = ({ tags, project }: EditProjectProps) => { - return +export const EditProject = ({ tags, project, stages }: EditProjectProps) => { + return ( + + ) } diff --git a/database/create-stages.ts b/database/create-stages.ts new file mode 100644 index 0000000..0bdba3b --- /dev/null +++ b/database/create-stages.ts @@ -0,0 +1,32 @@ +import { db } from "./database" +import { StageTable } from "./schemas/stages.schema" + +enum Stage { + Local_Private = "Local/Private", + Devnet = "Devnet", + Testnet = "Testnet", + Mainnet = "Mainnet", +} + +async function createInitialStages() { + const stages = Object.values(Stage) + for (const stage of stages) { + console.log(stage) + await db.insert(StageTable).values({ + name: stage, + }) + } +} + +const initStages = async () => { + console.error("Creating Stages") + await createInitialStages() + console.error("Succesfully Created Stages") + process.exit(0) +} + +initStages().catch((err) => { + console.error("Failed Creating Stages") + console.error(err) + process.exit(1) +}) diff --git a/database/create-tags.ts b/database/create-tags.ts new file mode 100644 index 0000000..a26377d --- /dev/null +++ b/database/create-tags.ts @@ -0,0 +1,50 @@ +import { db } from "./database" +import { TagTable } from "./schemas/tags.schema" + +const tags = [ + "AI", + "Community", + "DeFi", + "Developer Tools", + "Education", + "Exchanges (DEX)", + "Gambling", + "Gaming", + "Governance", + "Identity", + "Infrastructure", + "Insurance", + "Launchpad", + "Lending & Borrowing", + "Marketplaces", + "Meme Coins", + "Metaverse", + "NFT", + "Payment", + "Social", + "Stablecoin", + "Tools", + "Wallets", +] + +async function createInitialTags() { + for (const tag of tags) { + console.log(tag) + await db.insert(TagTable).values({ + name: tag, + }) + } +} + +const initTags = async () => { + console.error("Creating Stages") + await createInitialTags() + console.error("Succesfully Created Tags") + process.exit(0) +} + +initTags().catch((err) => { + console.error("Failed Creating Tags") + console.error(err) + process.exit(1) +}) diff --git a/database/database.ts b/database/database.ts index 218a7ef..c4d1c21 100644 --- a/database/database.ts +++ b/database/database.ts @@ -1,6 +1,9 @@ -import { env } from "@/env.mjs" -import { drizzle } from "drizzle-orm/postgres-js" -import postgres from "postgres" +import { RDSDataClient } from "@aws-sdk/client-rds-data" +import { drizzle } from "drizzle-orm/aws-data-api/pg" +import { Resource } from "sst" -export const client = postgres(env.DATABASE_URL) -export const db = drizzle(client) +export const db = drizzle(new RDSDataClient({}), { + database: Resource.SeistartDatabase.database, + secretArn: Resource.SeistartDatabase.secretArn, + resourceArn: Resource.SeistartDatabase.clusterArn, +}) diff --git a/database/migrate.ts b/database/migrate.ts index 2a95334..f4843bf 100644 --- a/database/migrate.ts +++ b/database/migrate.ts @@ -1,17 +1,7 @@ -import { env } from "@/env.mjs" -import { drizzle } from "drizzle-orm/postgres-js" -import { migrate } from "drizzle-orm/postgres-js/migrator" -import postgres from "postgres" +import { migrate } from "drizzle-orm/aws-data-api/pg/migrator" +import { db } from "./database" const runMigrate = async () => { - if (!env.DATABASE_URL) { - throw new Error("DATABASE_URL is not defined") - } - - const connection = postgres(env.DATABASE_URL, { max: 1 }) - - const db = drizzle(connection) - console.log("⏳ Running migrations...") const start = Date.now() diff --git a/database/migrations/0000_moaning_hammerhead.sql b/database/migrations/0000_moaning_hammerhead.sql deleted file mode 100644 index 77460a8..0000000 --- a/database/migrations/0000_moaning_hammerhead.sql +++ /dev/null @@ -1,181 +0,0 @@ -DO $$ BEGIN - CREATE TYPE "stage" AS ENUM('Mainnet', 'Testnet', 'Devnet', 'Local/Private'); -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "session" ( - "id" text PRIMARY KEY NOT NULL, - "user_id" text NOT NULL, - "entitlements" text NOT NULL, - "expires_at" timestamp NOT NULL -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "user_profile" ( - "user_id" text PRIMARY KEY NOT NULL -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "project" ( - "id" serial PRIMARY KEY NOT NULL, - "name" varchar(255) NOT NULL, - "slug" varchar(255) NOT NULL, - "token_name" varchar(255), - "token_supply" integer, - "release_date" timestamp, - "summary" varchar(255) NOT NULL, - "is_live" boolean DEFAULT false NOT NULL, - "stage" "stage" NOT NULL, - "description" text NOT NULL, - "community_size" integer, - "website" varchar(255), - "whitepaper" varchar(255), - "twitter" varchar(255), - "discord" varchar(255), - "telegram" varchar(255), - "contact_name" varchar(255), - "contact_email" varchar(255), - "main_tag_id" integer, - "user_id" text NOT NULL, - "created_at" timestamp DEFAULT now() NOT NULL, - "updated_at" timestamp DEFAULT now() NOT NULL, - CONSTRAINT "project_slug_unique" UNIQUE("slug") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "permission" ( - "id" serial PRIMARY KEY NOT NULL, - "name" text NOT NULL, - CONSTRAINT "permission_name_unique" UNIQUE("name") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "role_permission" ( - "role_id" integer NOT NULL, - "permission_id" integer NOT NULL, - CONSTRAINT "role_permission_role_id_permission_id_pk" PRIMARY KEY("role_id","permission_id") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "role" ( - "id" serial PRIMARY KEY NOT NULL, - "name" text NOT NULL, - CONSTRAINT "role_name_unique" UNIQUE("name") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "user_role" ( - "user_id" text NOT NULL, - "role_id" integer NOT NULL, - CONSTRAINT "user_role_role_id_user_id_pk" PRIMARY KEY("role_id","user_id") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "project_tag" ( - "project_id" integer NOT NULL, - "tag_id" integer NOT NULL, - "user_id" text NOT NULL, - CONSTRAINT "project_tag_project_id_tag_id_user_id_pk" PRIMARY KEY("project_id","tag_id","user_id") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "tag" ( - "id" serial PRIMARY KEY NOT NULL, - "name" varchar(256) NOT NULL, - CONSTRAINT "tag_name_unique" UNIQUE("name") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "user" ( - "id" text PRIMARY KEY NOT NULL -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "main_wallet" ( - "user_id" text NOT NULL, - "wallet_id" integer NOT NULL, - CONSTRAINT "main_wallet_user_id_wallet_id_pk" PRIMARY KEY("user_id","wallet_id") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "wallet" ( - "id" serial PRIMARY KEY NOT NULL, - "user_id" text NOT NULL, - "wallet_address" text NOT NULL, - CONSTRAINT "wallet_user_id_unique" UNIQUE("user_id"), - CONSTRAINT "wallet_wallet_address_unique" UNIQUE("wallet_address") -); ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "user_profile" ADD CONSTRAINT "user_profile_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "project" ADD CONSTRAINT "project_main_tag_id_tag_id_fk" FOREIGN KEY ("main_tag_id") REFERENCES "tag"("id") ON DELETE no action ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "project" ADD CONSTRAINT "project_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "role_permission" ADD CONSTRAINT "role_permission_role_id_role_id_fk" FOREIGN KEY ("role_id") REFERENCES "role"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "role_permission" ADD CONSTRAINT "role_permission_permission_id_permission_id_fk" FOREIGN KEY ("permission_id") REFERENCES "permission"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "user_role" ADD CONSTRAINT "user_role_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "user_role" ADD CONSTRAINT "user_role_role_id_role_id_fk" FOREIGN KEY ("role_id") REFERENCES "role"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "project_tag" ADD CONSTRAINT "project_tag_project_id_project_id_fk" FOREIGN KEY ("project_id") REFERENCES "project"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "project_tag" ADD CONSTRAINT "project_tag_tag_id_tag_id_fk" FOREIGN KEY ("tag_id") REFERENCES "tag"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "project_tag" ADD CONSTRAINT "project_tag_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "main_wallet" ADD CONSTRAINT "main_wallet_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "main_wallet" ADD CONSTRAINT "main_wallet_wallet_id_wallet_id_fk" FOREIGN KEY ("wallet_id") REFERENCES "wallet"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "wallet" ADD CONSTRAINT "wallet_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; diff --git a/database/migrations/meta/0000_snapshot.json b/database/migrations/meta/0000_snapshot.json deleted file mode 100644 index e408532..0000000 --- a/database/migrations/meta/0000_snapshot.json +++ /dev/null @@ -1,613 +0,0 @@ -{ - "id": "84680cfe-2504-4df3-b72e-e7d0dd6216c0", - "prevId": "00000000-0000-0000-0000-000000000000", - "version": "5", - "dialect": "pg", - "tables": { - "session": { - "name": "session", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "entitlements": { - "name": "entitlements", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "session_user_id_user_id_fk": { - "name": "session_user_id_user_id_fk", - "tableFrom": "session", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "user_profile": { - "name": "user_profile", - "schema": "", - "columns": { - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": true, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "user_profile_user_id_user_id_fk": { - "name": "user_profile_user_id_user_id_fk", - "tableFrom": "user_profile", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "project": { - "name": "project", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "serial", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true - }, - "slug": { - "name": "slug", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true - }, - "token_name": { - "name": "token_name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false - }, - "token_supply": { - "name": "token_supply", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "release_date": { - "name": "release_date", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "summary": { - "name": "summary", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true - }, - "is_live": { - "name": "is_live", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "stage": { - "name": "stage", - "type": "stage", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "community_size": { - "name": "community_size", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "website": { - "name": "website", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false - }, - "whitepaper": { - "name": "whitepaper", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false - }, - "twitter": { - "name": "twitter", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false - }, - "discord": { - "name": "discord", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false - }, - "telegram": { - "name": "telegram", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false - }, - "contact_name": { - "name": "contact_name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false - }, - "contact_email": { - "name": "contact_email", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false - }, - "main_tag_id": { - "name": "main_tag_id", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "project_main_tag_id_tag_id_fk": { - "name": "project_main_tag_id_tag_id_fk", - "tableFrom": "project", - "tableTo": "tag", - "columnsFrom": ["main_tag_id"], - "columnsTo": ["id"], - "onDelete": "no action", - "onUpdate": "no action" - }, - "project_user_id_user_id_fk": { - "name": "project_user_id_user_id_fk", - "tableFrom": "project", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "project_slug_unique": { - "name": "project_slug_unique", - "nullsNotDistinct": false, - "columns": ["slug"] - } - } - }, - "permission": { - "name": "permission", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "serial", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "permission_name_unique": { - "name": "permission_name_unique", - "nullsNotDistinct": false, - "columns": ["name"] - } - } - }, - "role_permission": { - "name": "role_permission", - "schema": "", - "columns": { - "role_id": { - "name": "role_id", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "permission_id": { - "name": "permission_id", - "type": "integer", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "role_permission_role_id_role_id_fk": { - "name": "role_permission_role_id_role_id_fk", - "tableFrom": "role_permission", - "tableTo": "role", - "columnsFrom": ["role_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "role_permission_permission_id_permission_id_fk": { - "name": "role_permission_permission_id_permission_id_fk", - "tableFrom": "role_permission", - "tableTo": "permission", - "columnsFrom": ["permission_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "role_permission_role_id_permission_id_pk": { - "name": "role_permission_role_id_permission_id_pk", - "columns": ["role_id", "permission_id"] - } - }, - "uniqueConstraints": {} - }, - "role": { - "name": "role", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "serial", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "role_name_unique": { - "name": "role_name_unique", - "nullsNotDistinct": false, - "columns": ["name"] - } - } - }, - "user_role": { - "name": "user_role", - "schema": "", - "columns": { - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "role_id": { - "name": "role_id", - "type": "integer", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "user_role_user_id_user_id_fk": { - "name": "user_role_user_id_user_id_fk", - "tableFrom": "user_role", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "user_role_role_id_role_id_fk": { - "name": "user_role_role_id_role_id_fk", - "tableFrom": "user_role", - "tableTo": "role", - "columnsFrom": ["role_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "user_role_role_id_user_id_pk": { - "name": "user_role_role_id_user_id_pk", - "columns": ["role_id", "user_id"] - } - }, - "uniqueConstraints": {} - }, - "project_tag": { - "name": "project_tag", - "schema": "", - "columns": { - "project_id": { - "name": "project_id", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "tag_id": { - "name": "tag_id", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "project_tag_project_id_project_id_fk": { - "name": "project_tag_project_id_project_id_fk", - "tableFrom": "project_tag", - "tableTo": "project", - "columnsFrom": ["project_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "project_tag_tag_id_tag_id_fk": { - "name": "project_tag_tag_id_tag_id_fk", - "tableFrom": "project_tag", - "tableTo": "tag", - "columnsFrom": ["tag_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "project_tag_user_id_user_id_fk": { - "name": "project_tag_user_id_user_id_fk", - "tableFrom": "project_tag", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "project_tag_project_id_tag_id_user_id_pk": { - "name": "project_tag_project_id_tag_id_user_id_pk", - "columns": ["project_id", "tag_id", "user_id"] - } - }, - "uniqueConstraints": {} - }, - "tag": { - "name": "tag", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "serial", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "varchar(256)", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "tag_name_unique": { - "name": "tag_name_unique", - "nullsNotDistinct": false, - "columns": ["name"] - } - } - }, - "user": { - "name": "user", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "main_wallet": { - "name": "main_wallet", - "schema": "", - "columns": { - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "wallet_id": { - "name": "wallet_id", - "type": "integer", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "main_wallet_user_id_user_id_fk": { - "name": "main_wallet_user_id_user_id_fk", - "tableFrom": "main_wallet", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "main_wallet_wallet_id_wallet_id_fk": { - "name": "main_wallet_wallet_id_wallet_id_fk", - "tableFrom": "main_wallet", - "tableTo": "wallet", - "columnsFrom": ["wallet_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "main_wallet_user_id_wallet_id_pk": { - "name": "main_wallet_user_id_wallet_id_pk", - "columns": ["user_id", "wallet_id"] - } - }, - "uniqueConstraints": {} - }, - "wallet": { - "name": "wallet", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "serial", - "primaryKey": true, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "wallet_address": { - "name": "wallet_address", - "type": "text", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "wallet_user_id_user_id_fk": { - "name": "wallet_user_id_user_id_fk", - "tableFrom": "wallet", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "wallet_user_id_unique": { - "name": "wallet_user_id_unique", - "nullsNotDistinct": false, - "columns": ["user_id"] - }, - "wallet_wallet_address_unique": { - "name": "wallet_wallet_address_unique", - "nullsNotDistinct": false, - "columns": ["wallet_address"] - } - } - } - }, - "enums": { - "stage": { - "name": "stage", - "values": { - "Mainnet": "Mainnet", - "Testnet": "Testnet", - "Devnet": "Devnet", - "Local/Private": "Local/Private" - } - } - }, - "schemas": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} diff --git a/database/migrations/meta/_journal.json b/database/migrations/meta/_journal.json deleted file mode 100644 index 2847aed..0000000 --- a/database/migrations/meta/_journal.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "version": "5", - "dialect": "pg", - "entries": [ - { - "idx": 0, - "version": "5", - "when": 1711141268089, - "tag": "0000_moaning_hammerhead", - "breakpoints": true - } - ] -} diff --git a/database/schemas/profiles.schema.ts b/database/schemas/profiles.schema.ts index a747d46..1541aa5 100644 --- a/database/schemas/profiles.schema.ts +++ b/database/schemas/profiles.schema.ts @@ -16,7 +16,6 @@ export const UserProfileTable = pgTable("user_profile", { const UserProfileBaseSchema = createSelectSchema(UserProfileTable) export type UserProfile = z.infer export type CompletUserProfile = { - userProfile: UserProfile mainWallet: Wallet wallets: Wallet[] entitlements: Entitlements diff --git a/database/schemas/projects.schema.ts b/database/schemas/projects.schema.ts index 77a3cee..959752a 100644 --- a/database/schemas/projects.schema.ts +++ b/database/schemas/projects.schema.ts @@ -3,7 +3,6 @@ import { sql } from "drizzle-orm" import { boolean, integer, - pgEnum, pgTable, serial, text, @@ -12,23 +11,10 @@ import { } from "drizzle-orm/pg-core" import { createSelectSchema } from "drizzle-zod" import { z } from "zod" +import { StageTable } from "./stages.schema" import { TagTable } from "./tags.schema" import { UserTable } from "./users.schema" -export const stageSchema = z.enum([ - "Mainnet", - "Testnet", - "Devnet", - "Local/Private", -]) - -export const stage = pgEnum("stage", [ - "Mainnet", - "Testnet", - "Devnet", - "Local/Private", -]) - export const projectTagSchema = z.enum([ "AI", "Community", @@ -67,7 +53,9 @@ export const ProjectTable = pgTable("project", { releaseDate: timestamp("release_date"), summary: varchar("summary", { length: 255 }).notNull(), isLive: boolean("is_live").notNull().default(false), - stage: stage("stage").notNull(), + stageId: integer("stage_id") + .references(() => StageTable.id) + .notNull(), description: text("description").notNull(), communitySize: integer("community_size"), website: varchar("website", { length: 255 }), @@ -96,6 +84,7 @@ const BaseSchema = createSelectSchema(ProjectTable).omit(timestamps) export const ProjectWithTagsSchema = BaseSchema.extend({ tags: z.array(z.string()).max(3), mainTag: z.string(), + stage: z.string(), website: z.string().url(), whitepaper: z.string().url(), twitter: z.string().url(), diff --git a/database/schemas/stages.schema.ts b/database/schemas/stages.schema.ts new file mode 100644 index 0000000..5b3d5c4 --- /dev/null +++ b/database/schemas/stages.schema.ts @@ -0,0 +1,11 @@ +import { pgTable, serial, varchar } from "drizzle-orm/pg-core" +import { createSelectSchema } from "drizzle-zod" +import { z } from "zod" + +export const StageTable = pgTable("stage", { + id: serial("id").primaryKey(), + name: varchar("name", { length: 256 }).unique().notNull(), +}) + +const stageBaseSchema = createSelectSchema(StageTable) +export type Stage = z.infer diff --git a/database/schemas/tags.schema.ts b/database/schemas/tags.schema.ts index 656c0ca..9e9370b 100644 --- a/database/schemas/tags.schema.ts +++ b/database/schemas/tags.schema.ts @@ -3,13 +3,11 @@ import { pgTable, primaryKey, serial, - text, varchar, } from "drizzle-orm/pg-core" import { createSelectSchema } from "drizzle-zod" import { z } from "zod" import { ProjectTable } from "./projects.schema" -import { UserTable } from "./users.schema" export const TagTable = pgTable("tag", { id: serial("id").primaryKey(), @@ -29,15 +27,10 @@ export const ProjectTagTable = pgTable( onDelete: "cascade", }) .notNull(), - userId: text("user_id") - .references(() => UserTable.id, { - onDelete: "cascade", - }) - .notNull(), }, (table) => { return { - pk: primaryKey({ columns: [table.projectId, table.tagId, table.userId] }), + pk: primaryKey({ columns: [table.projectId, table.tagId] }), } } ) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml deleted file mode 100644 index 7af2066..0000000 --- a/docker-compose.dev.yml +++ /dev/null @@ -1,14 +0,0 @@ -version: "1" -services: - app: - image: seistart - build: - context: ./ - target: dev - dockerfile: Dockerfile - volumes: - - .:/app - - /app/node_modules - - /app/.next - ports: - - "3000:3000" diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index ed8ccfb..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,10 +0,0 @@ -version: "3" -services: - app: - image: seistart - build: - context: ./ - target: production - dockerfile: Dockerfile - ports: - - "80:3000" diff --git a/drizzle.config.ts b/drizzle.config.ts index 69dac91..3d7f23a 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,11 +1,7 @@ -import { env } from "@/env.mjs" import type { Config } from "drizzle-kit" export default { schema: "./database/schemas", out: "./database/migrations", driver: "pg", - dbCredentials: { - connectionString: env.DATABASE_URL, - }, } satisfies Config diff --git a/env.mjs b/env.mjs index 09b7ab2..2d86461 100644 --- a/env.mjs +++ b/env.mjs @@ -3,12 +3,10 @@ import { z } from "zod" export const env = createEnv({ server: { - DATABASE_URL: z.string().url(), NEXTAUTH_JWT_SECRET: z.string(), }, client: {}, runtimeEnv: { - DATABASE_URL: process.env.DATABASE_URL, NEXTAUTH_JWT_SECRET: process.env.NEXTAUTH_JWT_SECRET, }, }) diff --git a/hooks/use-sign-in.tsx b/hooks/use-sign-in.tsx index 3de7766..2dbf187 100644 --- a/hooks/use-sign-in.tsx +++ b/hooks/use-sign-in.tsx @@ -2,6 +2,7 @@ import { generateSignedMessageAction, singInSignUpAction, } from "@/server-actions/users/users.actions" +import { userStore } from "@/stores/user-store" import { SeiWallet } from "@sei-js/core" import { useState } from "react" @@ -10,6 +11,7 @@ function useSignIn( walletAddress: string, pathName: string ) { + const { setUserProfile } = userStore() const [isSigningIn, setIsSigningIn] = useState(false) const signIn = async () => { @@ -24,7 +26,8 @@ function useSignIn( JSON.stringify(message) ) if (signature) { - await singInSignUpAction(signature, jwt, pathName) + const userProfile = await singInSignUpAction(signature, jwt) + setUserProfile(userProfile) } } } catch (error) { diff --git a/middleware.ts b/middleware.ts deleted file mode 100644 index 01f583c..0000000 --- a/middleware.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { NextRequest, NextResponse } from "next/server" - -export default async function middleware(req: NextRequest) { - return NextResponse.next() -} - -export const matcher = { - matcher: ["/"], -} diff --git a/next.config.mjs b/next.config.mjs index 8f7d15f..1d61478 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,9 +1,4 @@ /** @type {import('next').NextConfig} */ -const nextConfig = { - webpack: (config) => { - config.externals.push("@node-rs/argon2", "@node-rs/bcrypt") - return config - }, -} +const nextConfig = {} export default nextConfig diff --git a/open-next.config.ts b/open-next.config.ts new file mode 100644 index 0000000..fccfe1f --- /dev/null +++ b/open-next.config.ts @@ -0,0 +1,10 @@ +const config = { + default: { + override: { + wrapper: "aws-lambda-streaming", + }, + }, +} + +export default config +export type Config = typeof config diff --git a/package.json b/package.json index c4049c3..2f206cb 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "private": true, "scripts": { "dev": "next dev", + "sst:dev": "sst dev next dev", "build": "next build", "start": "next start", "lint": "next lint", @@ -22,10 +23,10 @@ "build-storybook": "storybook build" }, "dependencies": { + "@aws-sdk/client-cloudfront": "^3.540.0", + "@aws-sdk/client-rds-data": "^3.540.0", "@hookform/resolvers": "^3.3.4", "@lucia-auth/adapter-drizzle": "^1.0.2", - "@node-rs/argon2": "^1.8.0", - "@node-rs/bcrypt": "^1.9.2", "@radix-ui/react-aspect-ratio": "^1.0.3", "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4", @@ -44,38 +45,36 @@ "@storybook/addon-themes": "^8.0.5", "@t3-oss/env-nextjs": "^0.9.2", "@tremor/react": "^3.14.0", - "@types/lodash": "^4.14.202", - "@upstash/redis": "^1.28.4", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "cmdk": "^0.2.1", - "crypto-js": "^4.2.0", "d3-force": "^3.0.0", "d3-selection": "^3.0.0", "d3-zoom": "^3.0.0", "date-fns": "^3.3.1", - "drizzle-orm": "^0.29.4", + "drizzle-orm": "^0.30.4", "drizzle-zod": "^0.5.1", "framer-motion": "^11.0.8", "gray-matter": "^4.0.3", "jsonwebtoken": "^9.0.2", - "lodash": "^4.17.21", "lucia": "^3.0.1", "lucide-react": "^0.341.0", "nanoid": "^5.0.6", "net": "^1.0.2", - "next": "14.1.0", + "next": "^14.1.4", "next-mdx-remote": "^4.4.1", "next-themes": "^0.2.1", - "oslo": "^1.1.3", + "open-next": "^2.3.8", "postgres": "^3.4.3", - "react": "^18", + "react": "^18.2.0", "react-country-flag": "^3.1.0", "react-day-picker": "^8.10.0", - "react-dom": "^18", + "react-dom": "^18.2.0", "react-hook-form": "^7.51.0", "react-icons": "^5.0.1", + "sharp": "^0.33.3", "sonner": "^1.4.2", + "sst": "^3.0.1", "tailwind-merge": "^2.2.1", "tailwindcss-animate": "^1.0.7", "tailwindcss-gradients": "^3.0.0", @@ -86,10 +85,11 @@ "devDependencies": { "@anatine/zod-mock": "^3.13.3", "@faker-js/faker": "^8.4.1", - "@types/crypto-js": "^4.2.2", "@types/node": "^20.11.29", "@types/react": "^18", "@types/react-dom": "^18", + "@types/d3": "^7.4.3", + "@types/jsonwebtoken": "^9.0.6", "autoprefixer": "^10.0.1", "@chromatic-com/storybook": "^1.2.25", "@storybook/addon-essentials": "^8.0.5", @@ -101,8 +101,6 @@ "@storybook/nextjs": "^8.0.5", "@storybook/react": "^8.0.5", "@storybook/test": "^8.0.5", - "@types/d3": "^7.4.3", - "@types/jsonwebtoken": "^9.0.6", "dotenv": "^16.4.5", "drizzle-kit": "^0.20.14", "eslint": "^8", diff --git a/providers/app-provider.tsx b/providers/app-provider.tsx index 7ba2e78..fd790b5 100644 --- a/providers/app-provider.tsx +++ b/providers/app-provider.tsx @@ -1,7 +1,5 @@ import { SeiProvider } from "@/components/sei/sei-provider" import { ThemeProvider } from "@/components/theme/theme-provider" -import { CompletUserProfile } from "@/database/schemas/profiles.schema" -import { UserStoreProvider } from "@/providers/user-provider" import { ReactNode } from "react" const chainConfiguration = { @@ -11,9 +9,8 @@ const chainConfiguration = { } interface AppProviderProps { children: ReactNode - initialUserProfile: CompletUserProfile | null } -const AppProvider = ({ children, initialUserProfile }: AppProviderProps) => { +const AppProvider = ({ children }: AppProviderProps) => { return ( { - - {children} - + {children} ) diff --git a/providers/user-provider.tsx b/providers/user-provider.tsx deleted file mode 100644 index 9e394e1..0000000 --- a/providers/user-provider.tsx +++ /dev/null @@ -1,52 +0,0 @@ -"use client" - -import { CompletUserProfile } from "@/database/schemas/profiles.schema" -import { createUserStore, type UserStore } from "@/stores/user-store" -import { - createContext, - useContext, - useEffect, - useRef, - type ReactNode, -} from "react" -import { useStore, type StoreApi } from "zustand" - -export const UserStoreContext = createContext | null>(null) - -export interface UserStoreProviderProps { - children: ReactNode -} - -export const UserStoreProvider = ({ - children, - initialUserProfile, -}: UserStoreProviderProps & { - initialUserProfile: CompletUserProfile | null -}) => { - const storeRef = useRef>() - if (!storeRef.current) { - storeRef.current = createUserStore({ userProfile: initialUserProfile }) - } - - useEffect(() => { - if (storeRef.current) { - storeRef.current.setState({ userProfile: initialUserProfile }) - } - }, [initialUserProfile]) - - return ( - - {children} - - ) -} - -export const useUserStore = (selector: (store: UserStore) => T): T => { - const userStoreContext = useContext(UserStoreContext) - - if (!userStoreContext) { - throw new Error(`useCounterStore must be use within CounterStoreProvider`) - } - - return useStore(userStoreContext, selector) -} diff --git a/server-actions/cache/invalidateCloudFrontPaths.tsx b/server-actions/cache/invalidateCloudFrontPaths.tsx new file mode 100644 index 0000000..835e27f --- /dev/null +++ b/server-actions/cache/invalidateCloudFrontPaths.tsx @@ -0,0 +1,22 @@ +import { + CloudFrontClient, + CreateInvalidationCommand, +} from "@aws-sdk/client-cloudfront" + +const cloudFront = new CloudFrontClient({}) + +export const invalidateCloudFrontPaths = async (paths: string[]) => { + await cloudFront.send( + new CreateInvalidationCommand({ + // Set CloudFront distribution ID here + DistributionId: "E2UEIZ39CY8D9Y", + InvalidationBatch: { + CallerReference: `${Date.now()}`, + Paths: { + Quantity: paths.length, + Items: paths, + }, + }, + }) + ) +} diff --git a/server-actions/projects/projects.actions.ts b/server-actions/projects/projects.actions.ts index b95b5fb..54315e1 100644 --- a/server-actions/projects/projects.actions.ts +++ b/server-actions/projects/projects.actions.ts @@ -5,7 +5,8 @@ import { NewProjectSchema, NewProjectWithTagsParams, } from "@/database/schemas/projects.schema" -import { revalidatePath } from "next/cache" +import { revalidatePath, revalidateTag, unstable_cache } from "next/cache" +import { invalidateCloudFrontPaths } from "../cache/invalidateCloudFrontPaths" import { Permission } from "../entitlements/entitlements.models" import { addTagsToProjectMutation, @@ -38,8 +39,10 @@ export const createProjectWithTagsAction = async ( const { tags, ...newProject } = newProjectWithTagsParams const payload = NewProjectSchema.parse(newProject) const { project } = await createProjectMutation(payload, userId) - await addTagsToProjectMutation(project.id, tags, userId) + await addTagsToProjectMutation(project.id, tags) + await invalidateCloudFrontPaths(["/*"]) revalidatePath("/") + revalidateTag("projects") return project.slug } @@ -59,8 +62,10 @@ export const updateProjectWithTagsAction = async ( const { tags, ...newProject } = newProjectWithTagsParams const payload = NewProjectSchema.parse(newProject) const { project } = await updateProjectMutation(projectId, payload, userId) - await addTagsToProjectMutation(project.id, tags, userId) + await addTagsToProjectMutation(project.id, tags) + await invalidateCloudFrontPaths(["/*"]) revalidatePath("/") + revalidateTag("projects") return project.slug } else { throw "Access Denied" @@ -85,7 +90,9 @@ export const deleteProjectAction = async (projectId: number) => { ) { await deleteProjectMutation(projectId) await new Promise((resolve) => setTimeout(resolve, 1000)) // Wait for 1 second (not recommended for production) + await invalidateCloudFrontPaths(["/*"]) revalidatePath("/") + revalidateTag("projects") } else { throw "Access Denied" } @@ -95,10 +102,14 @@ export const deleteProjectAction = async (projectId: number) => { } // Public -export const getAllProjectsAction = async () => { - const projects = await getAllProjectsQuery() - return projects -} +export const getAllProjectsAction = unstable_cache( + async () => { + const projects = await getAllProjectsQuery() + return projects + }, + ["projects"], + { tags: ["projects"] } +) // Public export const getAllProjectsByUserAction = async (userId: string) => { diff --git a/server-actions/projects/projects.mutations.ts b/server-actions/projects/projects.mutations.ts index f629401..91cfb96 100644 --- a/server-actions/projects/projects.mutations.ts +++ b/server-actions/projects/projects.mutations.ts @@ -60,8 +60,7 @@ export const updateProjectMutation = async ( export const addTagsToProjectMutation = async ( projectId: number, - newTags: number[], - userId: string + newTags: number[] ) => { try { if (newTags.length > 3) { @@ -76,8 +75,7 @@ export const addTagsToProjectMutation = async ( .where( and( eq(ProjectTagTable.projectId, projectId), - eq(ProjectTagTable.tagId, tagId), - eq(ProjectTagTable.userId, userId) + eq(ProjectTagTable.tagId, tagId) ) ) } @@ -85,7 +83,7 @@ export const addTagsToProjectMutation = async ( .filter((tag) => !existingTags.includes(tag)) .slice(0, 3 - (existingTags.length - tagsToRemove.length)) for (const tagId of tagsToAdd) { - await db.insert(ProjectTagTable).values({ projectId, tagId, userId }) + await db.insert(ProjectTagTable).values({ projectId, tagId }) } const updatedTags = await getProjectTagIdsQuery(projectId) return { updatedTags } diff --git a/server-actions/stages/stages.actions.ts b/server-actions/stages/stages.actions.ts new file mode 100644 index 0000000..126564a --- /dev/null +++ b/server-actions/stages/stages.actions.ts @@ -0,0 +1,7 @@ +import { unstable_cache } from "next/cache" +import { getAllStagesQuery } from "./stages.queries" + +export const getAllStagesAction = unstable_cache(async () => { + const stages = await getAllStagesQuery() + return stages +}, ["stages"]) diff --git a/server-actions/stages/stages.queries.ts b/server-actions/stages/stages.queries.ts new file mode 100644 index 0000000..4439769 --- /dev/null +++ b/server-actions/stages/stages.queries.ts @@ -0,0 +1,11 @@ +"use server" + +import { db } from "@/database/database" +import { StageTable } from "@/database/schemas/stages.schema" + +// TODO: Add validation schemas to all inputs + +export const getAllStagesQuery = async () => { + const rows = await db.select().from(StageTable).orderBy(StageTable.id) + return { stages: rows } +} diff --git a/server-actions/tags/tags.actions.ts b/server-actions/tags/tags.actions.ts index 2703f57..e9612f1 100644 --- a/server-actions/tags/tags.actions.ts +++ b/server-actions/tags/tags.actions.ts @@ -2,7 +2,7 @@ import { getUser } from "@/auth/auth-guard" import { Tag } from "@/database/schemas/tags.schema" -import { revalidatePath } from "next/cache" +import { revalidateTag, unstable_cache } from "next/cache" import { Role } from "../entitlements/entitlements.models" import { createTagMutation, @@ -20,7 +20,7 @@ export const updateTagAction = async (tag: Tag) => { throw "Access Denied" } await updateTagMutation(tag) - revalidatePath("/") + revalidateTag("tags") } // Private for Admins @@ -30,7 +30,7 @@ export const createTagAction = async (name: string) => { throw "Access Denied" } await createTagMutation(name) - revalidatePath("/") + revalidateTag("tags") } // Private for Admins @@ -40,11 +40,15 @@ export const deleteTagAction = async (tag: Tag) => { throw "Access Denied" } await deleteTagMutation(tag) - revalidatePath("/") + revalidateTag("tags") } // Public -export const getAllTagsAction = async () => { - const tags = await getAllTagsQuery() - return tags -} +export const getAllTagsAction = unstable_cache( + async () => { + const tags = await getAllTagsQuery() + return tags + }, + ["tags"], + { tags: ["tags"] } +) diff --git a/server-actions/tags/tags.mutations.ts b/server-actions/tags/tags.mutations.ts index 8455a1c..3468d2a 100644 --- a/server-actions/tags/tags.mutations.ts +++ b/server-actions/tags/tags.mutations.ts @@ -1,6 +1,5 @@ "use server" -import { authGuard } from "@/auth/auth-guard" import { db } from "@/database/database" import { Tag, TagTable } from "@/database/schemas/tags.schema" import { eq } from "drizzle-orm" @@ -17,7 +16,6 @@ export const updateTagMutation = async (tag: Tag) => { } export const deleteTagMutation = async (tag: Tag) => { - await authGuard() const [updatedTag] = await db .delete(TagTable) .where(eq(TagTable.id, tag.id)) @@ -26,6 +24,9 @@ export const deleteTagMutation = async (tag: Tag) => { } export const createTagMutation = async (name: string) => { - const [updatedTag] = await db.insert(TagTable).values({ name: name }) + const [updatedTag] = await db + .insert(TagTable) + .values({ name: name }) + .returning() return { tag: updatedTag } } diff --git a/server-actions/user-profile/user-profile.queries.ts b/server-actions/user-profile/user-profile.queries.ts index 1816291..1007ef2 100644 --- a/server-actions/user-profile/user-profile.queries.ts +++ b/server-actions/user-profile/user-profile.queries.ts @@ -25,12 +25,12 @@ export async function getUserProfileQuery() { } export async function getCompleteUserProfileQuery() { - const { userId } = await getUser() + const { userId, entitlements } = await getUser() if (!userId) return { userProfile: null } try { - const [userProfileRows, userWalletRows, mainWalletRows, { entitlements }] = - await Promise.all([ + const [userProfileRows, userWalletRows, mainWalletRows] = await Promise.all( + [ db .select() .from(UserProfileTable) @@ -41,8 +41,8 @@ export async function getCompleteUserProfileQuery() { .from(MainWalletTable) .leftJoin(WalletTable, eq(MainWalletTable.walletId, WalletTable.id)) .where(eq(MainWalletTable.userId, userId)), - getUser(), - ]) + ] + ) const mainWallet = mainWalletRows.length > 0 ? mainWalletRows[0].wallet : null diff --git a/server-actions/users/users.actions.ts b/server-actions/users/users.actions.ts index 01281e7..9002675 100644 --- a/server-actions/users/users.actions.ts +++ b/server-actions/users/users.actions.ts @@ -17,8 +17,9 @@ import { cookies } from "next/headers" import { redirect } from "next/navigation" import { Role } from "../entitlements/entitlements.models" import { getUserEntitlements } from "../entitlements/entitlements.queries" +import { getCompleteUserProfileAction } from "../user-profile/user-profile.actions" export const setAuthCookie = (cookie: Cookie) => { - cookies().set(cookie) + cookies().set(cookie.name, cookie.value, cookie.attributes) } interface ActionResult { @@ -75,7 +76,7 @@ export async function createUserAction( return redirect(pathName) } -export async function signOutAction(pathName: string) { +export async function signOutAction() { const { session } = await validateRequest() if (!session) { return { @@ -85,7 +86,7 @@ export async function signOutAction(pathName: string) { await lucia.invalidateSession(session.id) const sessionCookie = lucia.createBlankSessionCookie() setAuthCookie(sessionCookie) - redirect(pathName) + return redirect("/") } export async function generateSignedMessageAction(walletAddress: string) { @@ -108,8 +109,7 @@ export async function generateSignedMessageAction(walletAddress: string) { export async function singInSignUpAction( signedMessage: StdSignature, - signedJwtToken: string, - pathName: string + signedJwtToken: string ) { const isValidJwt = jwt.verify(signedJwtToken, env.NEXTAUTH_JWT_SECRET) if (!isValidJwt) { @@ -124,6 +124,32 @@ export async function singInSignUpAction( if (!verified) { throw "Invalid Signature" } else { - await createUserAction(message.walletAddress, pathName) + await signInUserAction(message.walletAddress) + const { userProfile } = await getCompleteUserProfileAction() + return userProfile + } +} + +export async function signInUserAction(walletAddress: string) { + let userId + try { + const existingWallets = await db + .select() + .from(WalletTable) + .where(and(eq(WalletTable.walletAddress, walletAddress))) + if (existingWallets[0]?.userId) { + userId = existingWallets[0].userId + + const entitlements = await getUserEntitlements(userId) + const session = await lucia.createSession(userId, { + entitlements: JSON.stringify(entitlements), + }) + const sessionCookie = lucia.createSessionCookie(session.id) + setAuthCookie(sessionCookie) + } else { + throw "No Access" + } + } catch (e) { + console.log(e) } } diff --git a/sst-env.d.ts b/sst-env.d.ts new file mode 100644 index 0000000..d11d29b --- /dev/null +++ b/sst-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/sst.config.ts b/sst.config.ts new file mode 100644 index 0000000..d5b7c13 --- /dev/null +++ b/sst.config.ts @@ -0,0 +1,33 @@ +/// +import { env } from "./env.mjs" +export default $config({ + app(input) { + return { + name: "gateway", + removal: input?.stage === "production" ? "retain" : "remove", + home: "aws", + } + }, + async run() { + const database = new sst.aws.Postgres("SeistartDatabase", { + version: "16.1", + databaseName: "seistart", + scaling: { + min: "0.5 ACU", + max: "1 ACU", + }, + }) + new sst.aws.Nextjs( + "SeistartWebApp", + { + link: [database], + openNextVersion: "3.0.0-rc.11", + warm: 20, + environment: env, + }, + { + version: "20", + } + ) + }, +}) diff --git a/stores/user-store.tsx b/stores/user-store.tsx index 072931b..b48bd61 100644 --- a/stores/user-store.tsx +++ b/stores/user-store.tsx @@ -11,14 +11,8 @@ export type UserActions = { export type UserStore = UserState & UserActions -export const defaultInitState: UserState = { +export const userStore = create((set) => ({ userProfile: null, -} - -export const createUserStore = (initState: UserState = defaultInitState) => { - return create((set) => ({ - ...initState, - setUserProfile: (userProfile: CompletUserProfile | null) => - set({ userProfile }), - })) -} + setUserProfile: (userProfile: CompletUserProfile | null) => + set({ userProfile }), +})) diff --git a/tsconfig.json b/tsconfig.json index e7ff90f..d286911 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,5 +22,5 @@ } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "exclude": ["node_modules", "sst.config.ts"] } diff --git a/utils/debounce.ts b/utils/debounce.ts new file mode 100644 index 0000000..2f598b3 --- /dev/null +++ b/utils/debounce.ts @@ -0,0 +1,14 @@ +export const debounce = any>( + func: T, + wait: number +): ((...funcArgs: Parameters) => void) => { + let timeoutId: ReturnType | null = null + + return (...args: Parameters) => { + if (timeoutId !== null) { + clearTimeout(timeoutId) + } + + timeoutId = setTimeout(() => func(...args), wait) + } +}