commit 65f113a90f86b3531d51cb8d2033c096d7c54fcb Author: Arkaprabha Chakraborty Date: Tue Aug 6 01:16:01 2024 +0530 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6eb88b9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,50 @@ +# Node modules +frontend/node_modules/ + +# Build output +frontend/.next/ +frontend/out/ +frontend/public/static/ +frontend/.cache/ + +# Environment files +.env +.env.local +.env.*.local + +# Docker +docker-compose.override.yml +*.dockerignore + +# Logs +*.log +frontend/*.log +backend/*.log + +# OS-specific files +.DS_Store +Thumbs.db + +# Editor-specific files +*.sublime-project +*.sublime-workspace +.vscode/ +.idea/ +*.swp +*.swo + +# Go build artifacts +backend/*.exe +backend/*.test +backend/*.out + +# Database files +database/*.sql + +# Bun lock file +frontend/bun.lockb + +# Miscellaneous +*.tgz +*.gz +*.zip diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..2c795a3 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,23 @@ +# Use the official Golang image +FROM golang:1.20-alpine + +# Set the Current Working Directory inside the container +WORKDIR /app + +# Copy go mod and sum files +COPY go.mod go.sum ./ + +# Download all dependencies. Dependencies will be cached if the go.mod and go.sum files are not changed +RUN go mod download + +# Copy the source code into the container +COPY . . + +# Build the Go app +RUN go build -o main . + +# Expose port 8080 to the outside world +EXPOSE 8080 + +# Command to run the executable +CMD ["./main"] diff --git a/backend/go.mod b/backend/go.mod new file mode 100644 index 0000000..99dd1d8 --- /dev/null +++ b/backend/go.mod @@ -0,0 +1,25 @@ +module url-shortener-backend + +go 1.20 + +require ( + github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect + github.com/golang-jwt/jwt v3.2.2+incompatible // indirect + github.com/gorilla/mux v1.8.1 // indirect + github.com/jinzhu/gorm v1.9.16 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/labstack/echo v3.3.10+incompatible // indirect + github.com/labstack/echo/v4 v4.12.0 // indirect + github.com/labstack/gommon v0.4.2 // indirect + github.com/lib/pq v1.10.9 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/rs/xid v1.5.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect + golang.org/x/crypto v0.22.0 // indirect + golang.org/x/net v0.24.0 // indirect + golang.org/x/sys v0.19.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.5.0 // indirect +) diff --git a/backend/go.sum b/backend/go.sum new file mode 100644 index 0000000..eced652 --- /dev/null +++ b/backend/go.sum @@ -0,0 +1,61 @@ +github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= +github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o= +github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= +github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= +github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0= +github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM= +github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= +github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= +github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= diff --git a/backend/main.go b/backend/main.go new file mode 100644 index 0000000..b7e8257 --- /dev/null +++ b/backend/main.go @@ -0,0 +1,97 @@ +package main + +import ( + "log" + "math/rand" + "net/http" + "os" + "time" + + "github.com/jinzhu/gorm" + _ "github.com/jinzhu/gorm/dialects/postgres" + "github.com/labstack/echo" + "github.com/labstack/echo/middleware" +) + +var db *gorm.DB + +type URL struct { + ID string `gorm:"primary_key" json:"id"` + LongURL string `json:"long_url"` + ShortURL string `json:"short_url"` + CreatedAt time.Time `json:"created_at"` +} + +func init() { + var err error + dbHost := os.Getenv("DB_HOST") + dbPort := os.Getenv("DB_PORT") + dbUser := os.Getenv("DB_USER") + dbName := os.Getenv("DB_NAME") + dbPassword := os.Getenv("DB_PASSWORD") + + dsn := "host=" + dbHost + " port=" + dbPort + " user=" + dbUser + " dbname=" + dbName + " password=" + dbPassword + " sslmode=disable" + db, err = gorm.Open("postgres", dsn) + if err != nil { + panic(err) + } + + // Auto-migrate database + db.AutoMigrate(&URL{}) +} + +func generateRandomString(length int) string { + const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + bytes := make([]byte, length) + for i := range bytes { + bytes[i] = charset[rand.Intn(len(charset))] + } + return string(bytes) +} + +func shortenURL(c echo.Context) error { + url := new(URL) + if err := c.Bind(url); err != nil { + return err + } + url.CreatedAt = time.Now() + url.ID = generateRandomString(6) + url.ShortURL = "http://localhost:3000/" + url.ID + db.Create(url) + return c.JSON(http.StatusCreated, url) +} + +func fetchLongURL(c echo.Context) error { + id := c.Param("id") + var url URL + if err := db.Where("id = ?", id).First(&url).Error; err != nil { + log.Println("Error retrieving URL:", err) + return echo.NewHTTPError(http.StatusNotFound, "URL not found") + } + return c.JSON(http.StatusOK, map[string]string{"long_url": url.LongURL}) +} + + +func main() { + defer db.Close() + + e := echo.New() + + // Middleware + e.Use(middleware.Logger()) + e.Use(middleware.Recover()) + e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ + AllowOrigins: []string{"*"}, + AllowMethods: []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete}, + })) + + // Routes + e.GET("/", func(c echo.Context) error { + return c.String(http.StatusOK, "Backend is running alright.\n") + }) + + e.POST("/shorten", shortenURL) + e.GET("/:id", fetchLongURL) + + e.Logger.Fatal(e.Start(":8080")) +} diff --git a/database/Dockerfile b/database/Dockerfile new file mode 100644 index 0000000..89cd218 --- /dev/null +++ b/database/Dockerfile @@ -0,0 +1,4 @@ +# Use the official PostgreSQL image +FROM postgres:latest + +EXPOSE 5432 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..15d2ebf --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,42 @@ +services: + backend: + build: ./backend + environment: + DB_HOST: postgres + DB_PORT: 5432 + DB_USER: user + DB_NAME: urlshortener + DB_PASSWORD: password + ports: + - "8080:8080" + depends_on: + - postgres + networks: + - docker + + postgres: + image: postgres:latest + build: ./database + environment: + POSTGRES_DB: urlshortener + POSTGRES_USER: user + POSTGRES_PASSWORD: password + ports: + - "5432:5432" + networks: + - docker + + frontend: + build: ./frontend + environment: + NEXT_PUBLIC_BACKEND_URL: http://backend:8080 + ports: + - "3000:3000" + depends_on: + - backend + networks: + - docker + +networks: + docker: + driver: bridge diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json new file mode 100644 index 0000000..bffb357 --- /dev/null +++ b/frontend/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..fd3dbb5 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,36 @@ +# 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 diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..d886264 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,26 @@ +# Use a Node.js image with at least version 18.17.0 +FROM node:18.17.0 + +# Set the working directory +WORKDIR /app + +# Install bun +RUN curl -fsSL https://bun.sh/install | bash + +# Set bun's binary path +ENV PATH="/root/.bun/bin:${PATH}" + +# Copy the package.json and bun.lockb if available +COPY package*.json bun.lockb* ./ + +# Install dependencies using bun +RUN bun install + +# Copy the rest of the application files +COPY . . + +# Expose the port the app runs on +EXPOSE 3000 + +# Command to run the application +CMD ["bun", "run", "dev"] diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..0dc9ea2 --- /dev/null +++ b/frontend/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.js`. 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/frontend/app/api/redirect/[id]/route.js b/frontend/app/api/redirect/[id]/route.js new file mode 100644 index 0000000..fdf0c46 --- /dev/null +++ b/frontend/app/api/redirect/[id]/route.js @@ -0,0 +1,22 @@ +import { NextResponse } from "next/server"; +import axios from "axios"; +import dotenv from "dotenv"; + +dotenv.config(); + +export async function GET(request, { params }) { + const { id } = params; + + try { + const response = await axios.get( + `${process.env.NEXT_PUBLIC_BACKEND_URL}/${id}`, + ); + if (response.status === 200) { + return NextResponse.redirect(response.data.long_url); + } else { + return new NextResponse("URL not found", { status: 404 }); + } + } catch (error) { + return new NextResponse("Server error", { status: 500 }); + } +} diff --git a/frontend/app/favicon.ico b/frontend/app/favicon.ico new file mode 100644 index 0000000..718d6fe Binary files /dev/null and b/frontend/app/favicon.ico differ diff --git a/frontend/app/globals.css b/frontend/app/globals.css new file mode 100644 index 0000000..875c01e --- /dev/null +++ b/frontend/app/globals.css @@ -0,0 +1,33 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --foreground-rgb: 0, 0, 0; + --background-start-rgb: 214, 219, 220; + --background-end-rgb: 255, 255, 255; +} + +@media (prefers-color-scheme: dark) { + :root { + --foreground-rgb: 255, 255, 255; + --background-start-rgb: 0, 0, 0; + --background-end-rgb: 0, 0, 0; + } +} + +body { + color: rgb(var(--foreground-rgb)); + background: linear-gradient( + to bottom, + transparent, + rgb(var(--background-end-rgb)) + ) + rgb(var(--background-start-rgb)); +} + +@layer utilities { + .text-balance { + text-wrap: balance; + } +} diff --git a/frontend/app/layout.js b/frontend/app/layout.js new file mode 100644 index 0000000..10ba240 --- /dev/null +++ b/frontend/app/layout.js @@ -0,0 +1,20 @@ +import { Inter } from "next/font/google"; +import "./globals.css"; +import dotenv from "dotenv"; + +dotenv.config(); + +const inter = Inter({ subsets: ["latin"] }); + +export const metadata = { + title: "URL Shortener", + description: "A simple URL shortener application", +}; + +export default function RootLayout({ children }) { + return ( + + {children} + + ); +} diff --git a/frontend/app/page.js b/frontend/app/page.js new file mode 100644 index 0000000..0db1c59 --- /dev/null +++ b/frontend/app/page.js @@ -0,0 +1,61 @@ +"use client"; + +import { useState } from "react"; +import axios from "axios"; +import dotenv from "dotenv"; + +dotenv.config({ path: "./.env.local" }); + +export default function Home() { + const [longUrl, setLongUrl] = useState(""); + const [shortUrl, setShortUrl] = useState(""); + + const handleSubmit = async (e) => { + e.preventDefault(); + const response = await axios.post( + `${process.env.NEXT_PUBLIC_BACKEND_URL}/shorten`, + { + long_url: longUrl, + }, + ); + setShortUrl(response.data.short_url); + }; + + return ( +
+
+

+ URL Shortener +

+
+ setLongUrl(e.target.value)} + className="border border-gray-300 rounded-lg p-3 text-gray-800 focus:outline-none focus:ring-2 focus:ring-blue-500" + /> + +
+ {shortUrl && ( +

+ Short URL:{" "} + + {shortUrl} + +

+ )} +
+
+ ); +} diff --git a/frontend/jsconfig.json b/frontend/jsconfig.json new file mode 100644 index 0000000..2a2e4b3 --- /dev/null +++ b/frontend/jsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "paths": { + "@/*": ["./*"] + } + } +} diff --git a/frontend/next.config.mjs b/frontend/next.config.mjs new file mode 100644 index 0000000..222d953 --- /dev/null +++ b/frontend/next.config.mjs @@ -0,0 +1,14 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + async redirects() { + return [ + { + source: "/:id", + destination: `/api/redirect/:id`, + permanent: false, + }, + ]; + }, +}; + +export default nextConfig; diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..2eaeb2b --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,24 @@ +{ + "name": "my-nextjs13-app", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "axios": "^1.7.3", + "dotenv": "^16.4.5", + "next": "14.2.5", + "react": "^18", + "react-dom": "^18" + }, + "devDependencies": { + "postcss": "^8", + "tailwindcss": "^3.4.1", + "eslint": "^8", + "eslint-config-next": "14.2.5" + } +} diff --git a/frontend/postcss.config.mjs b/frontend/postcss.config.mjs new file mode 100644 index 0000000..1a69fd2 --- /dev/null +++ b/frontend/postcss.config.mjs @@ -0,0 +1,8 @@ +/** @type {import('postcss-load-config').Config} */ +const config = { + plugins: { + tailwindcss: {}, + }, +}; + +export default config; diff --git a/frontend/public/next.svg b/frontend/public/next.svg new file mode 100644 index 0000000..5174b28 --- /dev/null +++ b/frontend/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/vercel.svg b/frontend/public/vercel.svg new file mode 100644 index 0000000..d2f8422 --- /dev/null +++ b/frontend/public/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js new file mode 100644 index 0000000..78ebc4e --- /dev/null +++ b/frontend/tailwind.config.js @@ -0,0 +1,18 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + "./pages/**/*.{js,ts,jsx,tsx,mdx}", + "./components/**/*.{js,ts,jsx,tsx,mdx}", + "./app/**/*.{js,ts,jsx,tsx,mdx}", + ], + theme: { + extend: { + backgroundImage: { + "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", + "gradient-conic": + "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))", + }, + }, + }, + plugins: [], +};