From 083ea5bd19172381bb53e95aaf98eaabef839c54 Mon Sep 17 00:00:00 2001 From: MohamedBassem Date: Tue, 6 Feb 2024 12:26:29 +0000 Subject: Move the web app into a subdir --- .env.sample | 8 -- .eslintrc.json | 3 - .gitignore | 39 -------- Dockerfile | 57 ----------- README.md | 37 +------- app/api/auth/[...nextauth]/route.tsx | 3 - app/api/v1/links/route.ts | 60 ------------ app/favicon.ico | Bin 25931 -> 0 bytes app/globals.css | 76 --------------- app/layout.tsx | 22 ----- app/page.tsx | 15 --- bun.lockb | Bin 152334 -> 0 bytes components.json | 17 ---- components/auth/login.tsx | 17 ---- components/auth/logout.tsx | 12 --- lib/auth.ts | 25 ----- lib/config.ts | 20 ---- lib/prisma.ts | 5 - lib/types/api/links.ts | 26 ----- lib/types/next-auth.d.ts | 12 --- lib/utils.ts | 6 -- next.config.mjs | 4 - package.json | 36 ------- postcss.config.js | 6 -- .../20240205153748_add_users/migration.sql | 56 ----------- .../20240206000813_add_links/migration.sql | 43 --------- prisma/migrations/migration_lock.toml | 3 - prisma/schema.prisma | 105 --------------------- public/next.svg | 1 - public/vercel.svg | 1 - tailwind.config.ts | 80 ---------------- tsconfig.json | 26 ----- web/.env.sample | 8 ++ web/.eslintrc.json | 3 + web/.gitignore | 39 ++++++++ web/Dockerfile | 57 +++++++++++ web/README.md | 36 +++++++ web/app/api/auth/[...nextauth]/route.tsx | 3 + web/app/api/v1/links/route.ts | 60 ++++++++++++ web/app/favicon.ico | Bin 0 -> 25931 bytes web/app/globals.css | 76 +++++++++++++++ web/app/layout.tsx | 22 +++++ web/app/page.tsx | 15 +++ web/bun.lockb | Bin 0 -> 152334 bytes web/components.json | 17 ++++ web/components/auth/login.tsx | 17 ++++ web/components/auth/logout.tsx | 12 +++ web/lib/auth.ts | 25 +++++ web/lib/config.ts | 20 ++++ web/lib/prisma.ts | 5 + web/lib/types/api/links.ts | 26 +++++ web/lib/types/next-auth.d.ts | 12 +++ web/lib/utils.ts | 6 ++ web/next.config.mjs | 4 + web/package.json | 36 +++++++ web/postcss.config.js | 6 ++ .../20240205153748_add_users/migration.sql | 56 +++++++++++ .../20240206000813_add_links/migration.sql | 43 +++++++++ web/prisma/migrations/migration_lock.toml | 3 + web/prisma/schema.prisma | 105 +++++++++++++++++++++ web/public/next.svg | 1 + web/public/vercel.svg | 1 + web/tailwind.config.ts | 80 ++++++++++++++++ web/tsconfig.json | 26 +++++ 64 files changed, 821 insertions(+), 820 deletions(-) delete mode 100644 .env.sample delete mode 100644 .eslintrc.json delete mode 100644 .gitignore delete mode 100644 Dockerfile delete mode 100644 app/api/auth/[...nextauth]/route.tsx delete mode 100644 app/api/v1/links/route.ts delete mode 100644 app/favicon.ico delete mode 100644 app/globals.css delete mode 100644 app/layout.tsx delete mode 100644 app/page.tsx delete mode 100755 bun.lockb delete mode 100644 components.json delete mode 100644 components/auth/login.tsx delete mode 100644 components/auth/logout.tsx delete mode 100644 lib/auth.ts delete mode 100644 lib/config.ts delete mode 100644 lib/prisma.ts delete mode 100644 lib/types/api/links.ts delete mode 100644 lib/types/next-auth.d.ts delete mode 100644 lib/utils.ts delete mode 100644 next.config.mjs delete mode 100644 package.json delete mode 100644 postcss.config.js delete mode 100644 prisma/migrations/20240205153748_add_users/migration.sql delete mode 100644 prisma/migrations/20240206000813_add_links/migration.sql delete mode 100644 prisma/migrations/migration_lock.toml delete mode 100644 prisma/schema.prisma delete mode 100644 public/next.svg delete mode 100644 public/vercel.svg delete mode 100644 tailwind.config.ts delete mode 100644 tsconfig.json create mode 100644 web/.env.sample create mode 100644 web/.eslintrc.json create mode 100644 web/.gitignore create mode 100644 web/Dockerfile create mode 100644 web/README.md create mode 100644 web/app/api/auth/[...nextauth]/route.tsx create mode 100644 web/app/api/v1/links/route.ts create mode 100644 web/app/favicon.ico create mode 100644 web/app/globals.css create mode 100644 web/app/layout.tsx create mode 100644 web/app/page.tsx create mode 100755 web/bun.lockb create mode 100644 web/components.json create mode 100644 web/components/auth/login.tsx create mode 100644 web/components/auth/logout.tsx create mode 100644 web/lib/auth.ts create mode 100644 web/lib/config.ts create mode 100644 web/lib/prisma.ts create mode 100644 web/lib/types/api/links.ts create mode 100644 web/lib/types/next-auth.d.ts create mode 100644 web/lib/utils.ts create mode 100644 web/next.config.mjs create mode 100644 web/package.json create mode 100644 web/postcss.config.js create mode 100644 web/prisma/migrations/20240205153748_add_users/migration.sql create mode 100644 web/prisma/migrations/20240206000813_add_links/migration.sql create mode 100644 web/prisma/migrations/migration_lock.toml create mode 100644 web/prisma/schema.prisma create mode 100644 web/public/next.svg create mode 100644 web/public/vercel.svg create mode 100644 web/tailwind.config.ts create mode 100644 web/tsconfig.json diff --git a/.env.sample b/.env.sample deleted file mode 100644 index a48054f0..00000000 --- a/.env.sample +++ /dev/null @@ -1,8 +0,0 @@ -DATABASE_URL="file:./dev.db" -NEXTAUTH_URL= -NEXTAUTH_SECRET= - -# Oauth -AUTHENTIK_ID= -AUTHENTIK_SECRET= -AUTHENTIK_ISSUER= diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index bffb357a..00000000 --- a/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "next/core-web-vitals" -} diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 5bcde103..00000000 --- a/.gitignore +++ /dev/null @@ -1,39 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js -.yarn/install-state.gz - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# local env files -.env*.local - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts - -# The sqlite database -prisma/*dev.db* diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 30a46bd3..00000000 --- a/Dockerfile +++ /dev/null @@ -1,57 +0,0 @@ -FROM oven/bun:1.0-alpine AS base - -# Install dependencies only when needed -FROM base AS deps -# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. -# RUN apk add --no-cache libc6-compat -WORKDIR /app - -# Install dependencies based on the preferred package manager -COPY package.json bun.lockb ./ -RUN bun install --frozen-lockfile - -# Rebuild the source code only when needed -FROM base AS builder -WORKDIR /app -COPY --from=deps /app/node_modules ./node_modules -COPY . . - -ENV NODE_ENV production - -ENV NEXT_TELEMETRY_DISABLED 1 - -RUN bun run build - -# Production image, copy all the files and run next -FROM base AS runner -WORKDIR /app - -ENV NODE_ENV production - -ENV NEXT_TELEMETRY_DISABLED 1 - -RUN addgroup --system --gid 1001 nodejs -RUN adduser --system --uid 1001 nextjs - -COPY --from=builder /app/public ./public - -# Set the correct permission for prerender cache -RUN mkdir .next -RUN chown nextjs:nodejs .next - -# Automatically leverage output traces to reduce image size -# https://nextjs.org/docs/advanced-features/output-file-tracing -COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ -COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static - -USER nextjs - -EXPOSE 3000 - -ENV PORT 3000 -# set hostname to localhost -ENV HOSTNAME "0.0.0.0" - -# server.js is created by next build from the standalone output -# https://nextjs.org/docs/pages/api-reference/next-config-js/output -CMD ["node", "server.js"] diff --git a/README.md b/README.md index c4033664..71b85fc9 100644 --- a/README.md +++ b/README.md @@ -1,36 +1 @@ -This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). - -## Getting Started - -First, run the development server: - -```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev -``` - -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. - -This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. - -## Learn More - -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. +# Remember! diff --git a/app/api/auth/[...nextauth]/route.tsx b/app/api/auth/[...nextauth]/route.tsx deleted file mode 100644 index bfcda516..00000000 --- a/app/api/auth/[...nextauth]/route.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import { authHandler } from "@/lib/auth"; - -export { authHandler as GET, authHandler as POST } diff --git a/app/api/v1/links/route.ts b/app/api/v1/links/route.ts deleted file mode 100644 index 5be1018e..00000000 --- a/app/api/v1/links/route.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { authOptions } from "@/lib/auth"; -import prisma from "@/lib/prisma"; -import { ZNewBookmarkedLinkRequest, ZGetLinksResponse, ZBookmarkedLink } from "@/lib/types/api/links"; -import { getServerSession } from "next-auth"; -import { NextRequest, NextResponse } from "next/server"; - -export async function POST(request: NextRequest) { - // TODO: We probably should be using an API key here instead of the session; - const session = await getServerSession(authOptions); - if (!session) { - return new Response(null, { status: 401 }); - } - - const linkRequest = ZNewBookmarkedLinkRequest.safeParse(await request.json()); - - if (!linkRequest.success) { - return NextResponse.json({ - error: linkRequest.error.toString(), - }, { status: 400 }); - } - - const link = await prisma.bookmarkedLink.create({ - data: { - url: linkRequest.data.url, - userId: session.user.id, - } - }); - - let response: ZBookmarkedLink = { ...link }; - - return NextResponse.json(response, { status: 201 }); -} - -export async function GET() { - // TODO: We probably should be using an API key here instead of the session; - const session = await getServerSession(authOptions); - if (!session) { - return new Response(null, { status: 401 }); - } - const links = await prisma.bookmarkedLink.findMany({ - where: { - userId: session.user.id, - }, - select: { - id: true, - url: true, - createdAt: true, - details: { - select: { - title: true, - description: true, - imageUrl: true, - } - }, - } - }); - - let response: ZGetLinksResponse = { links }; - return NextResponse.json(response); -} diff --git a/app/favicon.ico b/app/favicon.ico deleted file mode 100644 index 718d6fea..00000000 Binary files a/app/favicon.ico and /dev/null differ diff --git a/app/globals.css b/app/globals.css deleted file mode 100644 index 6a757250..00000000 --- a/app/globals.css +++ /dev/null @@ -1,76 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - -@layer base { - :root { - --background: 0 0% 100%; - --foreground: 222.2 84% 4.9%; - - --card: 0 0% 100%; - --card-foreground: 222.2 84% 4.9%; - - --popover: 0 0% 100%; - --popover-foreground: 222.2 84% 4.9%; - - --primary: 222.2 47.4% 11.2%; - --primary-foreground: 210 40% 98%; - - --secondary: 210 40% 96.1%; - --secondary-foreground: 222.2 47.4% 11.2%; - - --muted: 210 40% 96.1%; - --muted-foreground: 215.4 16.3% 46.9%; - - --accent: 210 40% 96.1%; - --accent-foreground: 222.2 47.4% 11.2%; - - --destructive: 0 84.2% 60.2%; - --destructive-foreground: 210 40% 98%; - - --border: 214.3 31.8% 91.4%; - --input: 214.3 31.8% 91.4%; - --ring: 222.2 84% 4.9%; - - --radius: 0.5rem; - } - - .dark { - --background: 222.2 84% 4.9%; - --foreground: 210 40% 98%; - - --card: 222.2 84% 4.9%; - --card-foreground: 210 40% 98%; - - --popover: 222.2 84% 4.9%; - --popover-foreground: 210 40% 98%; - - --primary: 210 40% 98%; - --primary-foreground: 222.2 47.4% 11.2%; - - --secondary: 217.2 32.6% 17.5%; - --secondary-foreground: 210 40% 98%; - - --muted: 217.2 32.6% 17.5%; - --muted-foreground: 215 20.2% 65.1%; - - --accent: 217.2 32.6% 17.5%; - --accent-foreground: 210 40% 98%; - - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 210 40% 98%; - - --border: 217.2 32.6% 17.5%; - --input: 217.2 32.6% 17.5%; - --ring: 212.7 26.8% 83.9%; - } -} - -@layer base { - * { - @apply border-border; - } - body { - @apply bg-background text-foreground; - } -} \ No newline at end of file diff --git a/app/layout.tsx b/app/layout.tsx deleted file mode 100644 index 3314e478..00000000 --- a/app/layout.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { Metadata } from "next"; -import { Inter } from "next/font/google"; -import "./globals.css"; - -const inter = Inter({ subsets: ["latin"] }); - -export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", -}; - -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - return ( - - {children} - - ); -} diff --git a/app/page.tsx b/app/page.tsx deleted file mode 100644 index 2df40508..00000000 --- a/app/page.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { LoginButton } from "../components/auth/login"; -import { LogoutButton } from "../components/auth/logout"; - -export default function Home() { - return ( -
-
- -
-
- -
-
- ); -} diff --git a/bun.lockb b/bun.lockb deleted file mode 100755 index bfd618dc..00000000 Binary files a/bun.lockb and /dev/null differ diff --git a/components.json b/components.json deleted file mode 100644 index 15f2b025..00000000 --- a/components.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "$schema": "https://ui.shadcn.com/schema.json", - "style": "default", - "rsc": true, - "tsx": true, - "tailwind": { - "config": "tailwind.config.ts", - "css": "app/globals.css", - "baseColor": "slate", - "cssVariables": true, - "prefix": "" - }, - "aliases": { - "components": "@/components", - "utils": "@/lib/utils" - } -} \ No newline at end of file diff --git a/components/auth/login.tsx b/components/auth/login.tsx deleted file mode 100644 index 4cd55546..00000000 --- a/components/auth/login.tsx +++ /dev/null @@ -1,17 +0,0 @@ -"use client"; -import { signIn } from "next-auth/react"; - -export const LoginButton = () => { - return ( - - ); -}; diff --git a/components/auth/logout.tsx b/components/auth/logout.tsx deleted file mode 100644 index 87391c84..00000000 --- a/components/auth/logout.tsx +++ /dev/null @@ -1,12 +0,0 @@ -"use client"; -import { signOut } from "next-auth/react"; - -export const LogoutButton = () => { - return ( - - ); -}; diff --git a/lib/auth.ts b/lib/auth.ts deleted file mode 100644 index 9b21e605..00000000 --- a/lib/auth.ts +++ /dev/null @@ -1,25 +0,0 @@ -import NextAuth, { NextAuthOptions } from "next-auth" -import { PrismaAdapter } from "@next-auth/prisma-adapter" -import AuthentikProvider from "next-auth/providers/authentik"; -import serverConfig from "@/lib/config"; -import prisma from "@/lib/prisma"; - -let providers = []; - -if (serverConfig.auth.authentik) { - providers.push(AuthentikProvider(serverConfig.auth.authentik)); -} - -export const authOptions: NextAuthOptions = { - // Configure one or more authentication providers - adapter: PrismaAdapter(prisma), - providers: providers, - callbacks: { - session({ session, token, user }) { - session.user = { ...user }; - return session; - } - } -}; - -export const authHandler = NextAuth(authOptions); diff --git a/lib/config.ts b/lib/config.ts deleted file mode 100644 index ef86cb5a..00000000 --- a/lib/config.ts +++ /dev/null @@ -1,20 +0,0 @@ -function buildAuthentikConfig() { - let {id, secret, issuer} = process.env; - if (!id || !secret || !issuer) { - return undefined; - } - - return { - clientId: id, - clientSecret: secret, - issuer: issuer, - }; -} - -const serverConfig = { - auth: { - authentik: buildAuthentikConfig(), - } -}; - -export default serverConfig; diff --git a/lib/prisma.ts b/lib/prisma.ts deleted file mode 100644 index d73ba5f2..00000000 --- a/lib/prisma.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { PrismaClient } from '@prisma/client' - -const prisma = new PrismaClient(); - -export default prisma; diff --git a/lib/types/api/links.ts b/lib/types/api/links.ts deleted file mode 100644 index 81cde053..00000000 --- a/lib/types/api/links.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { z } from "zod"; - -export const ZBookmarkedLink = z.object({ - id: z.string(), - url: z.string().url(), - createdAt: z.coerce.date(), - - details: z.object({ - title: z.string(), - description: z.string(), - imageUrl: z.string().url(), - }).nullish(), - -}); -export type ZBookmarkedLink = z.infer; - - -// POST /v1/links -export const ZNewBookmarkedLinkRequest = ZBookmarkedLink.pick({ url: true }); - - -// GET /v1/links -export const ZGetLinksResponse = z.object({ - links: z.array(ZBookmarkedLink), -}); -export type ZGetLinksResponse = z.infer; diff --git a/lib/types/next-auth.d.ts b/lib/types/next-auth.d.ts deleted file mode 100644 index bdd3bd03..00000000 --- a/lib/types/next-auth.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -import NextAuth, { DefaultSession } from "next-auth" - -declare module "next-auth" { - /** - * Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context - */ - interface Session { - user: { - id: string; - } & DefaultSession["user"]; - } -} diff --git a/lib/utils.ts b/lib/utils.ts deleted file mode 100644 index d084ccad..00000000 --- a/lib/utils.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { type ClassValue, clsx } from "clsx" -import { twMerge } from "tailwind-merge" - -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)) -} diff --git a/next.config.mjs b/next.config.mjs deleted file mode 100644 index 4678774e..00000000 --- a/next.config.mjs +++ /dev/null @@ -1,4 +0,0 @@ -/** @type {import('next').NextConfig} */ -const nextConfig = {}; - -export default nextConfig; diff --git a/package.json b/package.json deleted file mode 100644 index 1323e456..00000000 --- a/package.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "remember", - "version": "0.1.0", - "private": true, - "scripts": { - "dev": "next dev", - "build": "next build", - "start": "next start", - "lint": "next lint" - }, - "dependencies": { - "@next-auth/prisma-adapter": "^1.0.7", - "class-variance-authority": "^0.7.0", - "clsx": "^2.1.0", - "lucide-react": "^0.322.0", - "next": "14.1.0", - "next-auth": "^4.24.5", - "prisma": "^5.9.1", - "react": "^18", - "react-dom": "^18", - "tailwind-merge": "^2.2.1", - "tailwindcss-animate": "^1.0.7", - "zod": "^3.22.4" - }, - "devDependencies": { - "typescript": "^5", - "@types/node": "^20", - "@types/react": "^18", - "@types/react-dom": "^18", - "autoprefixer": "^10.0.1", - "postcss": "^8", - "tailwindcss": "^3.3.0", - "eslint": "^8", - "eslint-config-next": "14.1.0" - } -} diff --git a/postcss.config.js b/postcss.config.js deleted file mode 100644 index 12a703d9..00000000 --- a/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; diff --git a/prisma/migrations/20240205153748_add_users/migration.sql b/prisma/migrations/20240205153748_add_users/migration.sql deleted file mode 100644 index cbf47073..00000000 --- a/prisma/migrations/20240205153748_add_users/migration.sql +++ /dev/null @@ -1,56 +0,0 @@ --- CreateTable -CREATE TABLE "Account" ( - "id" TEXT NOT NULL PRIMARY KEY, - "userId" TEXT NOT NULL, - "type" TEXT NOT NULL, - "provider" TEXT NOT NULL, - "providerAccountId" TEXT NOT NULL, - "refresh_token" TEXT, - "access_token" TEXT, - "expires_at" INTEGER, - "token_type" TEXT, - "scope" TEXT, - "id_token" TEXT, - "session_state" TEXT, - CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "Session" ( - "id" TEXT NOT NULL PRIMARY KEY, - "sessionToken" TEXT NOT NULL, - "userId" TEXT NOT NULL, - "expires" DATETIME NOT NULL, - CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "User" ( - "id" TEXT NOT NULL PRIMARY KEY, - "name" TEXT, - "email" TEXT, - "emailVerified" DATETIME, - "image" TEXT -); - --- CreateTable -CREATE TABLE "VerificationToken" ( - "identifier" TEXT NOT NULL, - "token" TEXT NOT NULL, - "expires" DATETIME NOT NULL -); - --- CreateIndex -CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId"); - --- CreateIndex -CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken"); - --- CreateIndex -CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); - --- CreateIndex -CREATE UNIQUE INDEX "VerificationToken_token_key" ON "VerificationToken"("token"); - --- CreateIndex -CREATE UNIQUE INDEX "VerificationToken_identifier_token_key" ON "VerificationToken"("identifier", "token"); diff --git a/prisma/migrations/20240206000813_add_links/migration.sql b/prisma/migrations/20240206000813_add_links/migration.sql deleted file mode 100644 index 38c8d938..00000000 --- a/prisma/migrations/20240206000813_add_links/migration.sql +++ /dev/null @@ -1,43 +0,0 @@ --- CreateTable -CREATE TABLE "BookmarkedLink" ( - "id" TEXT NOT NULL PRIMARY KEY, - "url" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "userId" TEXT NOT NULL, - CONSTRAINT "BookmarkedLink_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "BookmarkedLinkDetails" ( - "id" TEXT NOT NULL PRIMARY KEY, - "title" TEXT NOT NULL, - "description" TEXT NOT NULL, - "imageUrl" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "BookmarkedLinkDetails_id_fkey" FOREIGN KEY ("id") REFERENCES "BookmarkedLink" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "BookmarkTags" ( - "id" TEXT NOT NULL PRIMARY KEY, - "name" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "userId" TEXT NOT NULL, - CONSTRAINT "BookmarkTags_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "TagsOnLinks" ( - "linkId" TEXT NOT NULL, - "tagId" TEXT NOT NULL, - "attachedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "bookmarkTagsId" TEXT NOT NULL, - CONSTRAINT "TagsOnLinks_linkId_fkey" FOREIGN KEY ("linkId") REFERENCES "BookmarkedLink" ("id") ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT "TagsOnLinks_tagId_fkey" FOREIGN KEY ("tagId") REFERENCES "BookmarkTags" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateIndex -CREATE UNIQUE INDEX "BookmarkTags_name_key" ON "BookmarkTags"("name"); - --- CreateIndex -CREATE UNIQUE INDEX "TagsOnLinks_linkId_tagId_key" ON "TagsOnLinks"("linkId", "tagId"); diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml deleted file mode 100644 index e5e5c470..00000000 --- a/prisma/migrations/migration_lock.toml +++ /dev/null @@ -1,3 +0,0 @@ -# Please do not edit this file manually -# It should be added in your version-control system (i.e. Git) -provider = "sqlite" \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma deleted file mode 100644 index 54be3eae..00000000 --- a/prisma/schema.prisma +++ /dev/null @@ -1,105 +0,0 @@ -// This is your Prisma schema file, -// learn more about it in the docs: https://pris.ly/d/prisma-schema - -generator client { - provider = "prisma-client-js" -} - -datasource db { - provider = "sqlite" - url = env("DATABASE_URL") -} - -model Account { - id String @id @default(cuid()) - userId String - type String - provider String - providerAccountId String - refresh_token String? - access_token String? - expires_at Int? - token_type String? - scope String? - id_token String? - session_state String? - - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - - @@unique([provider, providerAccountId]) -} - -model Session { - id String @id @default(cuid()) - sessionToken String @unique - userId String - expires DateTime - user User @relation(fields: [userId], references: [id], onDelete: Cascade) -} - -model User { - id String @id @default(cuid()) - name String? - email String? @unique - emailVerified DateTime? - image String? - accounts Account[] - sessions Session[] - links BookmarkedLink[] - tags BookmarkTags[] -} - -model VerificationToken { - identifier String - token String @unique - expires DateTime - - @@unique([identifier, token]) -} - -model BookmarkedLink { - id String @id @default(cuid()) - url String - createdAt DateTime @default(now()) - - userId String - - details BookmarkedLinkDetails? - - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - tags TagsOnLinks[] -} - -model BookmarkedLinkDetails { - id String @id - title String - description String - imageUrl String - createdAt DateTime @default(now()) - - link BookmarkedLink @relation(fields: [id], references: [id], onDelete: Cascade) -} - -model BookmarkTags { - id String @id @default(cuid()) - name String @unique - createdAt DateTime @default(now()) - - userId String - - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - attachedLinks TagsOnLinks[] -} - -model TagsOnLinks { - link BookmarkedLink @relation(fields: [linkId], references: [id], onDelete: Cascade) - linkId String - - tag BookmarkTags @relation(fields: [tagId], references: [id], onDelete: Cascade) - tagId String - - attachedAt DateTime @default(now()) - bookmarkTagsId String - - @@unique([linkId, tagId]) -} diff --git a/public/next.svg b/public/next.svg deleted file mode 100644 index 5174b28c..00000000 --- a/public/next.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/vercel.svg b/public/vercel.svg deleted file mode 100644 index d2f84222..00000000 --- a/public/vercel.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tailwind.config.ts b/tailwind.config.ts deleted file mode 100644 index 84287e82..00000000 --- a/tailwind.config.ts +++ /dev/null @@ -1,80 +0,0 @@ -import type { Config } from "tailwindcss" - -const config = { - darkMode: ["class"], - content: [ - './pages/**/*.{ts,tsx}', - './components/**/*.{ts,tsx}', - './app/**/*.{ts,tsx}', - './src/**/*.{ts,tsx}', - ], - prefix: "", - theme: { - container: { - center: true, - padding: "2rem", - screens: { - "2xl": "1400px", - }, - }, - extend: { - colors: { - border: "hsl(var(--border))", - input: "hsl(var(--input))", - ring: "hsl(var(--ring))", - background: "hsl(var(--background))", - foreground: "hsl(var(--foreground))", - primary: { - DEFAULT: "hsl(var(--primary))", - foreground: "hsl(var(--primary-foreground))", - }, - secondary: { - DEFAULT: "hsl(var(--secondary))", - foreground: "hsl(var(--secondary-foreground))", - }, - destructive: { - DEFAULT: "hsl(var(--destructive))", - foreground: "hsl(var(--destructive-foreground))", - }, - muted: { - DEFAULT: "hsl(var(--muted))", - foreground: "hsl(var(--muted-foreground))", - }, - accent: { - DEFAULT: "hsl(var(--accent))", - foreground: "hsl(var(--accent-foreground))", - }, - popover: { - DEFAULT: "hsl(var(--popover))", - foreground: "hsl(var(--popover-foreground))", - }, - card: { - DEFAULT: "hsl(var(--card))", - foreground: "hsl(var(--card-foreground))", - }, - }, - borderRadius: { - lg: "var(--radius)", - md: "calc(var(--radius) - 2px)", - sm: "calc(var(--radius) - 4px)", - }, - keyframes: { - "accordion-down": { - from: { height: "0" }, - to: { height: "var(--radix-accordion-content-height)" }, - }, - "accordion-up": { - from: { height: "var(--radix-accordion-content-height)" }, - to: { height: "0" }, - }, - }, - animation: { - "accordion-down": "accordion-down 0.2s ease-out", - "accordion-up": "accordion-up 0.2s ease-out", - }, - }, - }, - plugins: [require("tailwindcss-animate")], -} satisfies Config - -export default config \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index e7ff90fd..00000000 --- a/tsconfig.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "compilerOptions": { - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "strict": true, - "noEmit": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "bundler", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve", - "incremental": true, - "plugins": [ - { - "name": "next" - } - ], - "paths": { - "@/*": ["./*"] - } - }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] -} diff --git a/web/.env.sample b/web/.env.sample new file mode 100644 index 00000000..a48054f0 --- /dev/null +++ b/web/.env.sample @@ -0,0 +1,8 @@ +DATABASE_URL="file:./dev.db" +NEXTAUTH_URL= +NEXTAUTH_SECRET= + +# Oauth +AUTHENTIK_ID= +AUTHENTIK_SECRET= +AUTHENTIK_ISSUER= diff --git a/web/.eslintrc.json b/web/.eslintrc.json new file mode 100644 index 00000000..bffb357a --- /dev/null +++ b/web/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/web/.gitignore b/web/.gitignore new file mode 100644 index 00000000..5bcde103 --- /dev/null +++ b/web/.gitignore @@ -0,0 +1,39 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +# The sqlite database +prisma/*dev.db* diff --git a/web/Dockerfile b/web/Dockerfile new file mode 100644 index 00000000..30a46bd3 --- /dev/null +++ b/web/Dockerfile @@ -0,0 +1,57 @@ +FROM oven/bun:1.0-alpine AS base + +# Install dependencies only when needed +FROM base AS deps +# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. +# RUN apk add --no-cache libc6-compat +WORKDIR /app + +# Install dependencies based on the preferred package manager +COPY package.json bun.lockb ./ +RUN bun install --frozen-lockfile + +# Rebuild the source code only when needed +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +ENV NODE_ENV production + +ENV NEXT_TELEMETRY_DISABLED 1 + +RUN bun run build + +# Production image, copy all the files and run next +FROM base AS runner +WORKDIR /app + +ENV NODE_ENV production + +ENV NEXT_TELEMETRY_DISABLED 1 + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +COPY --from=builder /app/public ./public + +# Set the correct permission for prerender cache +RUN mkdir .next +RUN chown nextjs:nodejs .next + +# Automatically leverage output traces to reduce image size +# https://nextjs.org/docs/advanced-features/output-file-tracing +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 + +ENV PORT 3000 +# set hostname to localhost +ENV HOSTNAME "0.0.0.0" + +# server.js is created by next build from the standalone output +# https://nextjs.org/docs/pages/api-reference/next-config-js/output +CMD ["node", "server.js"] diff --git a/web/README.md b/web/README.md new file mode 100644 index 00000000..c4033664 --- /dev/null +++ b/web/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. diff --git a/web/app/api/auth/[...nextauth]/route.tsx b/web/app/api/auth/[...nextauth]/route.tsx new file mode 100644 index 00000000..bfcda516 --- /dev/null +++ b/web/app/api/auth/[...nextauth]/route.tsx @@ -0,0 +1,3 @@ +import { authHandler } from "@/lib/auth"; + +export { authHandler as GET, authHandler as POST } diff --git a/web/app/api/v1/links/route.ts b/web/app/api/v1/links/route.ts new file mode 100644 index 00000000..5be1018e --- /dev/null +++ b/web/app/api/v1/links/route.ts @@ -0,0 +1,60 @@ +import { authOptions } from "@/lib/auth"; +import prisma from "@/lib/prisma"; +import { ZNewBookmarkedLinkRequest, ZGetLinksResponse, ZBookmarkedLink } from "@/lib/types/api/links"; +import { getServerSession } from "next-auth"; +import { NextRequest, NextResponse } from "next/server"; + +export async function POST(request: NextRequest) { + // TODO: We probably should be using an API key here instead of the session; + const session = await getServerSession(authOptions); + if (!session) { + return new Response(null, { status: 401 }); + } + + const linkRequest = ZNewBookmarkedLinkRequest.safeParse(await request.json()); + + if (!linkRequest.success) { + return NextResponse.json({ + error: linkRequest.error.toString(), + }, { status: 400 }); + } + + const link = await prisma.bookmarkedLink.create({ + data: { + url: linkRequest.data.url, + userId: session.user.id, + } + }); + + let response: ZBookmarkedLink = { ...link }; + + return NextResponse.json(response, { status: 201 }); +} + +export async function GET() { + // TODO: We probably should be using an API key here instead of the session; + const session = await getServerSession(authOptions); + if (!session) { + return new Response(null, { status: 401 }); + } + const links = await prisma.bookmarkedLink.findMany({ + where: { + userId: session.user.id, + }, + select: { + id: true, + url: true, + createdAt: true, + details: { + select: { + title: true, + description: true, + imageUrl: true, + } + }, + } + }); + + let response: ZGetLinksResponse = { links }; + return NextResponse.json(response); +} diff --git a/web/app/favicon.ico b/web/app/favicon.ico new file mode 100644 index 00000000..718d6fea Binary files /dev/null and b/web/app/favicon.ico differ diff --git a/web/app/globals.css b/web/app/globals.css new file mode 100644 index 00000000..6a757250 --- /dev/null +++ b/web/app/globals.css @@ -0,0 +1,76 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 222.2 84% 4.9%; + + --card: 0 0% 100%; + --card-foreground: 222.2 84% 4.9%; + + --popover: 0 0% 100%; + --popover-foreground: 222.2 84% 4.9%; + + --primary: 222.2 47.4% 11.2%; + --primary-foreground: 210 40% 98%; + + --secondary: 210 40% 96.1%; + --secondary-foreground: 222.2 47.4% 11.2%; + + --muted: 210 40% 96.1%; + --muted-foreground: 215.4 16.3% 46.9%; + + --accent: 210 40% 96.1%; + --accent-foreground: 222.2 47.4% 11.2%; + + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 40% 98%; + + --border: 214.3 31.8% 91.4%; + --input: 214.3 31.8% 91.4%; + --ring: 222.2 84% 4.9%; + + --radius: 0.5rem; + } + + .dark { + --background: 222.2 84% 4.9%; + --foreground: 210 40% 98%; + + --card: 222.2 84% 4.9%; + --card-foreground: 210 40% 98%; + + --popover: 222.2 84% 4.9%; + --popover-foreground: 210 40% 98%; + + --primary: 210 40% 98%; + --primary-foreground: 222.2 47.4% 11.2%; + + --secondary: 217.2 32.6% 17.5%; + --secondary-foreground: 210 40% 98%; + + --muted: 217.2 32.6% 17.5%; + --muted-foreground: 215 20.2% 65.1%; + + --accent: 217.2 32.6% 17.5%; + --accent-foreground: 210 40% 98%; + + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 210 40% 98%; + + --border: 217.2 32.6% 17.5%; + --input: 217.2 32.6% 17.5%; + --ring: 212.7 26.8% 83.9%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} \ No newline at end of file diff --git a/web/app/layout.tsx b/web/app/layout.tsx new file mode 100644 index 00000000..3314e478 --- /dev/null +++ b/web/app/layout.tsx @@ -0,0 +1,22 @@ +import type { Metadata } from "next"; +import { Inter } from "next/font/google"; +import "./globals.css"; + +const inter = Inter({ subsets: ["latin"] }); + +export const metadata: Metadata = { + title: "Create Next App", + description: "Generated by create next app", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + {children} + + ); +} diff --git a/web/app/page.tsx b/web/app/page.tsx new file mode 100644 index 00000000..2df40508 --- /dev/null +++ b/web/app/page.tsx @@ -0,0 +1,15 @@ +import { LoginButton } from "../components/auth/login"; +import { LogoutButton } from "../components/auth/logout"; + +export default function Home() { + return ( +
+
+ +
+
+ +
+
+ ); +} diff --git a/web/bun.lockb b/web/bun.lockb new file mode 100755 index 00000000..bfd618dc Binary files /dev/null and b/web/bun.lockb differ diff --git a/web/components.json b/web/components.json new file mode 100644 index 00000000..15f2b025 --- /dev/null +++ b/web/components.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "app/globals.css", + "baseColor": "slate", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils" + } +} \ No newline at end of file diff --git a/web/components/auth/login.tsx b/web/components/auth/login.tsx new file mode 100644 index 00000000..4cd55546 --- /dev/null +++ b/web/components/auth/login.tsx @@ -0,0 +1,17 @@ +"use client"; +import { signIn } from "next-auth/react"; + +export const LoginButton = () => { + return ( + + ); +}; diff --git a/web/components/auth/logout.tsx b/web/components/auth/logout.tsx new file mode 100644 index 00000000..87391c84 --- /dev/null +++ b/web/components/auth/logout.tsx @@ -0,0 +1,12 @@ +"use client"; +import { signOut } from "next-auth/react"; + +export const LogoutButton = () => { + return ( + + ); +}; diff --git a/web/lib/auth.ts b/web/lib/auth.ts new file mode 100644 index 00000000..9b21e605 --- /dev/null +++ b/web/lib/auth.ts @@ -0,0 +1,25 @@ +import NextAuth, { NextAuthOptions } from "next-auth" +import { PrismaAdapter } from "@next-auth/prisma-adapter" +import AuthentikProvider from "next-auth/providers/authentik"; +import serverConfig from "@/lib/config"; +import prisma from "@/lib/prisma"; + +let providers = []; + +if (serverConfig.auth.authentik) { + providers.push(AuthentikProvider(serverConfig.auth.authentik)); +} + +export const authOptions: NextAuthOptions = { + // Configure one or more authentication providers + adapter: PrismaAdapter(prisma), + providers: providers, + callbacks: { + session({ session, token, user }) { + session.user = { ...user }; + return session; + } + } +}; + +export const authHandler = NextAuth(authOptions); diff --git a/web/lib/config.ts b/web/lib/config.ts new file mode 100644 index 00000000..ef86cb5a --- /dev/null +++ b/web/lib/config.ts @@ -0,0 +1,20 @@ +function buildAuthentikConfig() { + let {id, secret, issuer} = process.env; + if (!id || !secret || !issuer) { + return undefined; + } + + return { + clientId: id, + clientSecret: secret, + issuer: issuer, + }; +} + +const serverConfig = { + auth: { + authentik: buildAuthentikConfig(), + } +}; + +export default serverConfig; diff --git a/web/lib/prisma.ts b/web/lib/prisma.ts new file mode 100644 index 00000000..d73ba5f2 --- /dev/null +++ b/web/lib/prisma.ts @@ -0,0 +1,5 @@ +import { PrismaClient } from '@prisma/client' + +const prisma = new PrismaClient(); + +export default prisma; diff --git a/web/lib/types/api/links.ts b/web/lib/types/api/links.ts new file mode 100644 index 00000000..81cde053 --- /dev/null +++ b/web/lib/types/api/links.ts @@ -0,0 +1,26 @@ +import { z } from "zod"; + +export const ZBookmarkedLink = z.object({ + id: z.string(), + url: z.string().url(), + createdAt: z.coerce.date(), + + details: z.object({ + title: z.string(), + description: z.string(), + imageUrl: z.string().url(), + }).nullish(), + +}); +export type ZBookmarkedLink = z.infer; + + +// POST /v1/links +export const ZNewBookmarkedLinkRequest = ZBookmarkedLink.pick({ url: true }); + + +// GET /v1/links +export const ZGetLinksResponse = z.object({ + links: z.array(ZBookmarkedLink), +}); +export type ZGetLinksResponse = z.infer; diff --git a/web/lib/types/next-auth.d.ts b/web/lib/types/next-auth.d.ts new file mode 100644 index 00000000..bdd3bd03 --- /dev/null +++ b/web/lib/types/next-auth.d.ts @@ -0,0 +1,12 @@ +import NextAuth, { DefaultSession } from "next-auth" + +declare module "next-auth" { + /** + * Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context + */ + interface Session { + user: { + id: string; + } & DefaultSession["user"]; + } +} diff --git a/web/lib/utils.ts b/web/lib/utils.ts new file mode 100644 index 00000000..d084ccad --- /dev/null +++ b/web/lib/utils.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/web/next.config.mjs b/web/next.config.mjs new file mode 100644 index 00000000..4678774e --- /dev/null +++ b/web/next.config.mjs @@ -0,0 +1,4 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = {}; + +export default nextConfig; diff --git a/web/package.json b/web/package.json new file mode 100644 index 00000000..1323e456 --- /dev/null +++ b/web/package.json @@ -0,0 +1,36 @@ +{ + "name": "remember", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@next-auth/prisma-adapter": "^1.0.7", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.0", + "lucide-react": "^0.322.0", + "next": "14.1.0", + "next-auth": "^4.24.5", + "prisma": "^5.9.1", + "react": "^18", + "react-dom": "^18", + "tailwind-merge": "^2.2.1", + "tailwindcss-animate": "^1.0.7", + "zod": "^3.22.4" + }, + "devDependencies": { + "typescript": "^5", + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "autoprefixer": "^10.0.1", + "postcss": "^8", + "tailwindcss": "^3.3.0", + "eslint": "^8", + "eslint-config-next": "14.1.0" + } +} diff --git a/web/postcss.config.js b/web/postcss.config.js new file mode 100644 index 00000000..12a703d9 --- /dev/null +++ b/web/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/web/prisma/migrations/20240205153748_add_users/migration.sql b/web/prisma/migrations/20240205153748_add_users/migration.sql new file mode 100644 index 00000000..cbf47073 --- /dev/null +++ b/web/prisma/migrations/20240205153748_add_users/migration.sql @@ -0,0 +1,56 @@ +-- CreateTable +CREATE TABLE "Account" ( + "id" TEXT NOT NULL PRIMARY KEY, + "userId" TEXT NOT NULL, + "type" TEXT NOT NULL, + "provider" TEXT NOT NULL, + "providerAccountId" TEXT NOT NULL, + "refresh_token" TEXT, + "access_token" TEXT, + "expires_at" INTEGER, + "token_type" TEXT, + "scope" TEXT, + "id_token" TEXT, + "session_state" TEXT, + CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "Session" ( + "id" TEXT NOT NULL PRIMARY KEY, + "sessionToken" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "expires" DATETIME NOT NULL, + CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "User" ( + "id" TEXT NOT NULL PRIMARY KEY, + "name" TEXT, + "email" TEXT, + "emailVerified" DATETIME, + "image" TEXT +); + +-- CreateTable +CREATE TABLE "VerificationToken" ( + "identifier" TEXT NOT NULL, + "token" TEXT NOT NULL, + "expires" DATETIME NOT NULL +); + +-- CreateIndex +CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken"); + +-- CreateIndex +CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); + +-- CreateIndex +CREATE UNIQUE INDEX "VerificationToken_token_key" ON "VerificationToken"("token"); + +-- CreateIndex +CREATE UNIQUE INDEX "VerificationToken_identifier_token_key" ON "VerificationToken"("identifier", "token"); diff --git a/web/prisma/migrations/20240206000813_add_links/migration.sql b/web/prisma/migrations/20240206000813_add_links/migration.sql new file mode 100644 index 00000000..38c8d938 --- /dev/null +++ b/web/prisma/migrations/20240206000813_add_links/migration.sql @@ -0,0 +1,43 @@ +-- CreateTable +CREATE TABLE "BookmarkedLink" ( + "id" TEXT NOT NULL PRIMARY KEY, + "url" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "userId" TEXT NOT NULL, + CONSTRAINT "BookmarkedLink_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "BookmarkedLinkDetails" ( + "id" TEXT NOT NULL PRIMARY KEY, + "title" TEXT NOT NULL, + "description" TEXT NOT NULL, + "imageUrl" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT "BookmarkedLinkDetails_id_fkey" FOREIGN KEY ("id") REFERENCES "BookmarkedLink" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "BookmarkTags" ( + "id" TEXT NOT NULL PRIMARY KEY, + "name" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "userId" TEXT NOT NULL, + CONSTRAINT "BookmarkTags_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "TagsOnLinks" ( + "linkId" TEXT NOT NULL, + "tagId" TEXT NOT NULL, + "attachedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "bookmarkTagsId" TEXT NOT NULL, + CONSTRAINT "TagsOnLinks_linkId_fkey" FOREIGN KEY ("linkId") REFERENCES "BookmarkedLink" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "TagsOnLinks_tagId_fkey" FOREIGN KEY ("tagId") REFERENCES "BookmarkTags" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateIndex +CREATE UNIQUE INDEX "BookmarkTags_name_key" ON "BookmarkTags"("name"); + +-- CreateIndex +CREATE UNIQUE INDEX "TagsOnLinks_linkId_tagId_key" ON "TagsOnLinks"("linkId", "tagId"); diff --git a/web/prisma/migrations/migration_lock.toml b/web/prisma/migrations/migration_lock.toml new file mode 100644 index 00000000..e5e5c470 --- /dev/null +++ b/web/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "sqlite" \ No newline at end of file diff --git a/web/prisma/schema.prisma b/web/prisma/schema.prisma new file mode 100644 index 00000000..54be3eae --- /dev/null +++ b/web/prisma/schema.prisma @@ -0,0 +1,105 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "sqlite" + url = env("DATABASE_URL") +} + +model Account { + id String @id @default(cuid()) + userId String + type String + provider String + providerAccountId String + refresh_token String? + access_token String? + expires_at Int? + token_type String? + scope String? + id_token String? + session_state String? + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@unique([provider, providerAccountId]) +} + +model Session { + id String @id @default(cuid()) + sessionToken String @unique + userId String + expires DateTime + user User @relation(fields: [userId], references: [id], onDelete: Cascade) +} + +model User { + id String @id @default(cuid()) + name String? + email String? @unique + emailVerified DateTime? + image String? + accounts Account[] + sessions Session[] + links BookmarkedLink[] + tags BookmarkTags[] +} + +model VerificationToken { + identifier String + token String @unique + expires DateTime + + @@unique([identifier, token]) +} + +model BookmarkedLink { + id String @id @default(cuid()) + url String + createdAt DateTime @default(now()) + + userId String + + details BookmarkedLinkDetails? + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + tags TagsOnLinks[] +} + +model BookmarkedLinkDetails { + id String @id + title String + description String + imageUrl String + createdAt DateTime @default(now()) + + link BookmarkedLink @relation(fields: [id], references: [id], onDelete: Cascade) +} + +model BookmarkTags { + id String @id @default(cuid()) + name String @unique + createdAt DateTime @default(now()) + + userId String + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + attachedLinks TagsOnLinks[] +} + +model TagsOnLinks { + link BookmarkedLink @relation(fields: [linkId], references: [id], onDelete: Cascade) + linkId String + + tag BookmarkTags @relation(fields: [tagId], references: [id], onDelete: Cascade) + tagId String + + attachedAt DateTime @default(now()) + bookmarkTagsId String + + @@unique([linkId, tagId]) +} diff --git a/web/public/next.svg b/web/public/next.svg new file mode 100644 index 00000000..5174b28c --- /dev/null +++ b/web/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/public/vercel.svg b/web/public/vercel.svg new file mode 100644 index 00000000..d2f84222 --- /dev/null +++ b/web/public/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/tailwind.config.ts b/web/tailwind.config.ts new file mode 100644 index 00000000..84287e82 --- /dev/null +++ b/web/tailwind.config.ts @@ -0,0 +1,80 @@ +import type { Config } from "tailwindcss" + +const config = { + darkMode: ["class"], + content: [ + './pages/**/*.{ts,tsx}', + './components/**/*.{ts,tsx}', + './app/**/*.{ts,tsx}', + './src/**/*.{ts,tsx}', + ], + prefix: "", + theme: { + container: { + center: true, + padding: "2rem", + screens: { + "2xl": "1400px", + }, + }, + extend: { + colors: { + border: "hsl(var(--border))", + input: "hsl(var(--input))", + ring: "hsl(var(--ring))", + background: "hsl(var(--background))", + foreground: "hsl(var(--foreground))", + primary: { + DEFAULT: "hsl(var(--primary))", + foreground: "hsl(var(--primary-foreground))", + }, + secondary: { + DEFAULT: "hsl(var(--secondary))", + foreground: "hsl(var(--secondary-foreground))", + }, + destructive: { + DEFAULT: "hsl(var(--destructive))", + foreground: "hsl(var(--destructive-foreground))", + }, + muted: { + DEFAULT: "hsl(var(--muted))", + foreground: "hsl(var(--muted-foreground))", + }, + accent: { + DEFAULT: "hsl(var(--accent))", + foreground: "hsl(var(--accent-foreground))", + }, + popover: { + DEFAULT: "hsl(var(--popover))", + foreground: "hsl(var(--popover-foreground))", + }, + card: { + DEFAULT: "hsl(var(--card))", + foreground: "hsl(var(--card-foreground))", + }, + }, + borderRadius: { + lg: "var(--radius)", + md: "calc(var(--radius) - 2px)", + sm: "calc(var(--radius) - 4px)", + }, + keyframes: { + "accordion-down": { + from: { height: "0" }, + to: { height: "var(--radix-accordion-content-height)" }, + }, + "accordion-up": { + from: { height: "var(--radix-accordion-content-height)" }, + to: { height: "0" }, + }, + }, + animation: { + "accordion-down": "accordion-down 0.2s ease-out", + "accordion-up": "accordion-up 0.2s ease-out", + }, + }, + }, + plugins: [require("tailwindcss-animate")], +} satisfies Config + +export default config \ No newline at end of file diff --git a/web/tsconfig.json b/web/tsconfig.json new file mode 100644 index 00000000..e7ff90fd --- /dev/null +++ b/web/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} -- cgit v1.2.3-70-g09d2