From e0d1ed01acf318f71e21845f3b74287292021cd0 Mon Sep 17 00:00:00 2001 From: JeetJani-11 <121782735+JeetJani-11@users.noreply.github.com> Date: Fri, 26 Jul 2024 14:08:29 +0530 Subject: [PATCH] Api Integration (#61) * Added API endpoints --- package-lock.json | 292 +++++++++++++++++++++++++++ package.json | 3 +- src/app/api/v1/create/event/route.ts | 74 +++++++ src/app/api/v1/delete/event/route.ts | 64 ++++++ src/app/api/v1/get/blog/route.ts | 63 ++++++ src/app/api/v1/get/blogs/route.ts | 70 +++++++ src/app/api/v1/get/event/route.ts | 43 ++++ src/app/api/v1/get/events/route.ts | 38 ++++ src/app/api/v1/submit/blog/route.ts | 113 +++++++++++ src/app/test_api/page.tsx | 234 +++++++++++++++++++++ src/types/supabase.ts | Bin 0 -> 14746 bytes src/utils/supabase/middleware.ts | 2 + 12 files changed, 995 insertions(+), 1 deletion(-) create mode 100644 src/app/api/v1/create/event/route.ts create mode 100644 src/app/api/v1/delete/event/route.ts create mode 100644 src/app/api/v1/get/blog/route.ts create mode 100644 src/app/api/v1/get/blogs/route.ts create mode 100644 src/app/api/v1/get/event/route.ts create mode 100644 src/app/api/v1/get/events/route.ts create mode 100644 src/app/api/v1/submit/blog/route.ts create mode 100644 src/app/test_api/page.tsx create mode 100644 src/types/supabase.ts diff --git a/package-lock.json b/package-lock.json index 9cd5076..9a68993 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,7 @@ "@types/react-dom": "^18", "autoprefixer": "^10.4.19", "postcss": "^8", + "supabase": "^1.187.3", "tailwindcss": "^3.4.1", "typescript": "^5" } @@ -415,6 +416,18 @@ "node": ">=12" } }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", @@ -1231,6 +1244,18 @@ } } }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", @@ -1339,6 +1364,21 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/bin-links": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-4.0.4.tgz", + "integrity": "sha512-cMtq4W5ZsEwcutJrVId+a/tjt8GSbS+h0oNkdl6+6rBuEv8Ot33Bevj5KPm40t309zuhVic8NjpuL42QCiJWWA==", + "dev": true, + "dependencies": { + "cmd-shim": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "read-cmd-shim": "^4.0.0", + "write-file-atomic": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -1540,6 +1580,15 @@ "node": ">= 6" } }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "engines": { + "node": ">=18" + } + }, "node_modules/class-variance-authority": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.0.tgz", @@ -1576,6 +1625,15 @@ "node": ">=6" } }, + "node_modules/cmd-shim": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-6.0.3.tgz", + "integrity": "sha512-FMabTRlc5t5zjdenF6mS0MBeFZm0XqHqeOkcskKFb/LYCcRQ5fVgLOHVc4Lq9CqABd9zhjwPjMBCJvMCziSVtA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1675,6 +1733,15 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, "node_modules/debug": { "version": "4.3.5", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", @@ -1801,6 +1868,29 @@ "reusify": "^1.0.4" } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -1835,6 +1925,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -1955,6 +2057,19 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/immer": { "version": "10.1.1", "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", @@ -1987,6 +2102,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -2210,6 +2334,34 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/minizlib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.1.tgz", + "integrity": "sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==", + "dev": true, + "dependencies": { + "minipass": "^7.0.4", + "rimraf": "^5.0.5" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -2336,6 +2488,43 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, "node_modules/node-releases": { "version": "2.0.17", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.17.tgz", @@ -2362,6 +2551,15 @@ "node": ">=0.10.0" } }, + "node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -2774,6 +2972,15 @@ "pify": "^2.3.0" } }, + "node_modules/read-cmd-shim": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-4.0.0.tgz", + "integrity": "sha512-yILWifhaSEEytfXI76kB9xEEiG1AiozaCJZ83A87ytjRiN+jVibXjedjCRNjoZviinhG+4UkalO3mWTd8u5O0Q==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -2849,6 +3056,24 @@ "node": ">=0.10.0" } }, + "node_modules/rimraf": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.9.tgz", + "integrity": "sha512-3i7b8OcswU6CpU8Ej89quJD4O98id7TtVM5U4Mybh84zQXdrFmDLouWBEEaD/QfO3gDDfH+AGFCGsR7kngzQnA==", + "dev": true, + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "14 >=14.20 || 16 >=16.20 || >=18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -3121,6 +3346,25 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/supabase": { + "version": "1.187.3", + "resolved": "https://registry.npmjs.org/supabase/-/supabase-1.187.3.tgz", + "integrity": "sha512-44yzHpxMrd88cUKWw3GVPPIUx6oCqgfqmN4Mlxp7a7GeNNZSTe433vmKBcj+ue3E7K08XcIyEX6G1TjhVyto+g==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "bin-links": "^4.0.3", + "https-proxy-agent": "^7.0.2", + "node-fetch": "^3.3.2", + "tar": "7.4.0" + }, + "bin": { + "supabase": "bin/supabase" + }, + "engines": { + "npm": ">=8" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -3201,6 +3445,23 @@ "tailwindcss": ">=3.0.0 || insiders" } }, + "node_modules/tar": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.0.tgz", + "integrity": "sha512-XQs0S8fuAkQWuqhDeCdMlJXDX80D7EOVLDPVFkna9yQfzS+PHKgfxcei0jf6/+QAWcjqrnC8uM3fSAnrQl+XYg==", + "dev": true, + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -3328,6 +3589,15 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -3452,6 +3722,19 @@ "node": ">=8" } }, + "node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/ws": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", @@ -3474,6 +3757,15 @@ } } }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "engines": { + "node": ">=18" + } + }, "node_modules/yaml": { "version": "2.4.5", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz", diff --git a/package.json b/package.json index 2d0778d..4251344 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,8 @@ "@types/react-dom": "^18", "autoprefixer": "^10.4.19", "postcss": "^8", + "supabase": "^1.187.3", "tailwindcss": "^3.4.1", "typescript": "^5" } -} +} \ No newline at end of file diff --git a/src/app/api/v1/create/event/route.ts b/src/app/api/v1/create/event/route.ts new file mode 100644 index 0000000..60ebbe5 --- /dev/null +++ b/src/app/api/v1/create/event/route.ts @@ -0,0 +1,74 @@ +import { NextRequest, NextResponse } from "next/server"; +import { createClient } from "@/utils/supabase/server"; +import { Tables } from "@/types/supabase"; + +function validateEvent(event: Tables<"events">): { + valid: boolean; + message?: string; +} { + if (!event.name) { + return { valid: false, message: "Event name is required." }; + } + if (!event.date || isNaN(Date.parse(event.date))) { + return { + valid: false, + message: "Event date is required and must be a valid date", + }; + } + // Add more validations as needed + return { valid: true }; +} + +export async function POST(request: NextRequest): Promise { + try { + const supabase = createClient(); + const { + data: { user }, + error: userError, + } = await supabase.auth.getUser(); + if (userError || !user) { + return NextResponse.json( + { success: false, message: "Unauthorized" }, + { status: 401 } + ); + } + const isAdmin = await supabase.from("users").select("admin").eq("id", user.id); + if (isAdmin.error || !isAdmin.data || isAdmin.data.length === 0 || !isAdmin.data[0].admin) { + return NextResponse.json( + { success: false, message: "Unauthorized" }, + { status: 401 } + ); + } + const body = await request.json(); + const { event }: { event: Tables<"events"> } = body; + event.creator = user.id; + const validation = validateEvent(event); + if (!validation.valid) { + return NextResponse.json( + { success: false, message: validation.message }, + { status: 400 } + ); + } + + const { data, error } = await supabase + .from("events") + .insert([event]) + .select(); + if (error) { + throw new Error(error.message); + } + if (!data || data.length === 0) { + throw new Error("Failed to create event"); + } + return NextResponse.json({ + success: true, + message: "Event created successfully", + id: data[0].id, + }); + } catch (error: any) { + return NextResponse.json( + { success: false, message: error.message }, + { status: 500 } + ); + } +} diff --git a/src/app/api/v1/delete/event/route.ts b/src/app/api/v1/delete/event/route.ts new file mode 100644 index 0000000..68bb873 --- /dev/null +++ b/src/app/api/v1/delete/event/route.ts @@ -0,0 +1,64 @@ +import { NextRequest, NextResponse } from "next/server"; +import { createClient } from "@/utils/supabase/server"; +import { Tables } from "@/types/supabase"; + +export async function DELETE(request: NextRequest): Promise { + try { + const supabase = createClient(); + const { + data: { user }, + error: userError, + } = await supabase.auth.getUser(); + if (userError || !user) { + return NextResponse.json( + { success: false, message: "Unauthorized" }, + { status: 401 } + ); + } + const isAdmin = await supabase.from("users").select("admin").eq("id", user.id); + if (isAdmin.error || !isAdmin.data || isAdmin.data.length === 0 || !isAdmin.data[0].admin) { + return NextResponse.json( + { success: false, message: "Unauthorized" }, + { status: 401 } + ); + } + const url = new URL(request.url); + const id = url.searchParams.get("id"); + + if (!id) { + return NextResponse.json( + { success: false, message: "Invalid or missing event ID" }, + { status: 400 } + ); + } + + const { data: event, error: eventError } = await supabase + .from("events") + .select("id") + .eq("id", id) + .single(); + + if (eventError) { + return NextResponse.json( + { success: false, message: "Event not found" }, + { status: 404 } + ); + } + + const { error } = await supabase.from("events").delete().eq("id", id); + + if (error) { + throw new Error(error.message); + } + + return NextResponse.json({ + success: true, + message: "Event deleted successfully", + }); + } catch (error: any) { + return NextResponse.json( + { success: false, message: error.message }, + { status: 500 } + ); + } +} diff --git a/src/app/api/v1/get/blog/route.ts b/src/app/api/v1/get/blog/route.ts new file mode 100644 index 0000000..09ad635 --- /dev/null +++ b/src/app/api/v1/get/blog/route.ts @@ -0,0 +1,63 @@ +import { NextRequest, NextResponse } from "next/server"; +import { createClient } from "@/utils/supabase/server"; + +export async function GET(request: NextRequest): Promise { + try { + const supabase = createClient(); + const url = new URL(request.url); + const id = url.searchParams.get('id'); + if (!id) { + return NextResponse.json( + { success: false, message: 'Missing blog ID' }, + { status: 400 } + ); + } + + const { data: blog, error: blogError } = await supabase + .from('blogs') + .select('*') + .eq('id', id) + .single(); + + if (blogError || !blog) { + return NextResponse.json( + { success: false, message: 'Blog not found' }, + { status: 404 } + ); + } + + const posterUrl = `${process.env.SUPABASE_STORAGE_URL}/web_data/images/${id}/poster`; + const blogFileUrl = `${process.env.SUPABASE_STORAGE_URL}/web_data/blogs/${id}/blog`; + + const { data: images, error: imagesError } = await supabase + .storage + .from('web_data') + .list(`images/${id}/`); + + if (imagesError) { + return NextResponse.json( + { success: false, message: 'Error fetching images' }, + { status: 500 } + ); + } + + const imageUrls = images.map(image => `${process.env.SUPABASE_STORAGE_URL}/web_data/blogs/${id}/images/${image.name}`); + + + return NextResponse.json({ + success: true, + message: 'Blog retrieved successfully', + blog: { + ...blog, + posterUrl, + blogFileUrl, + images: imageUrls + } + }); + } catch (error: any) { + return NextResponse.json( + { success: false, message: error.message }, + { status: 500 } + ); + } +} diff --git a/src/app/api/v1/get/blogs/route.ts b/src/app/api/v1/get/blogs/route.ts new file mode 100644 index 0000000..540bddd --- /dev/null +++ b/src/app/api/v1/get/blogs/route.ts @@ -0,0 +1,70 @@ +import { NextRequest, NextResponse } from "next/server"; +import { createClient } from "@/utils/supabase/server"; + +export async function GET(request: NextRequest): Promise { + try { + const supabase = createClient(); + + const url = new URL(request.url); + const limit = url.searchParams.get('limit'); + const page = url.searchParams.get('page'); + + const limitNumber = limit ? parseInt(limit) : 5; + const pageNumber = page ? parseInt(page) : 1; + + if (pageNumber < 1 || limitNumber < 1) { + return NextResponse.json( + { success: false, message: 'Page and limit must be greater than 0' }, + { status: 400 } + ); + } + + const offset = (pageNumber - 1) * limitNumber; + + const { data: blogs, error: blogsError } = await supabase + .from('blogs') + .select('*') + .range(offset, offset + limitNumber - 1); + + if (blogsError) { + throw new Error(blogsError.message); + } + + + const blogPromises = blogs.map(async (blog) => { + const posterUrl = `${process.env.SUPABASE_STORAGE_URL}/web_data/images/${blog.id}/poster`; + const blogFileUrl = `${process.env.SUPABASE_STORAGE_URL}/web_data/blogs/${blog.id}/blog`; + + const { data: images, error: imagesError } = await supabase + .storage + .from('web_data') + .list(`images/${blog.id}`); + + if (imagesError) { + throw new Error(imagesError.message); + } + + const imageUrls = images.map(image => `${process.env.SUPABASE_STORAGE_URL}/web_data/images/${blog.id}/${image.name}`); + + return { + ...blog, + posterUrl, + blogFileUrl, + images: imageUrls + }; + }); + + const blogsWithAssets = await Promise.all(blogPromises); + + return NextResponse.json({ + success: true, + message: 'Blogs retrieved successfully', + blogs: blogsWithAssets, + }); + } catch (error: any) { + return NextResponse.json( + { success: false, message: error.message }, + { status: 500 } + ); + } +} diff --git a/src/app/api/v1/get/event/route.ts b/src/app/api/v1/get/event/route.ts new file mode 100644 index 0000000..d610442 --- /dev/null +++ b/src/app/api/v1/get/event/route.ts @@ -0,0 +1,43 @@ + +import { NextRequest, NextResponse } from "next/server"; +import { createClient } from "@/utils/supabase/server"; + +export async function GET(request: NextRequest): Promise { + try { + const supabase = createClient(); + + const url = new URL(request.url); + const id = url.searchParams.get('id'); + + if (!id) { + return NextResponse.json( + { success: false, message: 'Event ID is required' }, + { status: 400 } + ); + } + + const { data: event, error: eventError } = await supabase + .from('events') + .select('*') + .eq('id', id) + .single(); + + if (eventError) { + return NextResponse.json( + { success: false, message: 'Event not found' }, + { status: 404 } + ); + } + + return NextResponse.json({ + success: true, + message: 'Event retrieved successfully', + event: event, + }); + } catch (error: any) { + return NextResponse.json( + { success: false, message: error.message }, + { status: 500 } + ); + } +} diff --git a/src/app/api/v1/get/events/route.ts b/src/app/api/v1/get/events/route.ts new file mode 100644 index 0000000..897ce27 --- /dev/null +++ b/src/app/api/v1/get/events/route.ts @@ -0,0 +1,38 @@ +// /api/v1/get/events/?category=upcoming&limit=10&page=1 +import { NextRequest, NextResponse } from "next/server"; +import { createClient } from "@/utils/supabase/server"; + +export async function GET(request: NextRequest) { + try { + const supabase = createClient(); + const { searchParams } = new URL(request.url); + const category = searchParams.get("category") || ""; + const limit = parseInt(searchParams.get("limit") || "5", 10); + const page = parseInt(searchParams.get("page") || "1", 10); + + const offset = (page - 1) * limit; + + let query = supabase + .from("events") + .select("*") + .range(offset, offset + limit - 1); + + if (category.toLocaleLowerCase() === "upcoming") { + query = query.gt("date", new Date().toISOString()); + }else if (category.toLocaleLowerCase() === "past") { + query = query.lt("date", new Date().toISOString()); + }else if (category.toLocaleLowerCase() === "ongoing") { + query = query.eq("date", new Date().toISOString()); + } + + const { data, error } = await query; + + if (error) { + throw new Error(error.message); + } + + return NextResponse.json(data); + } catch (error) { + return NextResponse.error(); + } +} diff --git a/src/app/api/v1/submit/blog/route.ts b/src/app/api/v1/submit/blog/route.ts new file mode 100644 index 0000000..758d8e0 --- /dev/null +++ b/src/app/api/v1/submit/blog/route.ts @@ -0,0 +1,113 @@ +import { NextRequest, NextResponse } from "next/server"; +import { createClient } from "@/utils/supabase/server"; +import { Tables } from "@/types/supabase"; + +function validateRequestBody(body: { + blogTable: Tables<"blogs">; + poster?: File; + blog?: File; + images?: File[]; +}): { valid: boolean; message?: string } { + if (!body.blogTable.title) { + return { valid: false, message: "Title is required" }; + } + if (!body.poster || !body.blog) { + return { valid: false, message: "Poster and blog files are required" }; + } + return { valid: true }; +} + +export async function POST(request: NextRequest): Promise { + const supabase = createClient(); + + try { + const { + data: { user }, + error: userError, + } = await supabase.auth.getUser(); + if (userError || !user) { + return NextResponse.json( + { success: false, message: "Unauthorized" }, + { status: 401 } + ); + } + + // Use request.formData() to handle file uploads + const formData = await request.formData(); + const blogData = JSON.parse(formData.get('blogData') as string); + + const poster = formData.get('poster') as File; + const blogFile = formData.get('blog') as File; + const images = formData.getAll('images') as File[]; + + const validation = validateRequestBody({ + blogTable: blogData, + poster, + blog: blogFile, + images + }); + if (!validation.valid) { + return NextResponse.json( + { success: false, message: validation.message }, + { status: 400 } + ); + } + + blogData.writer = user.id; + + const { data: blogs, error: blogsError } = await supabase + .from("blogs") + .insert([blogData]) + .select(); + if (blogsError || !blogs || blogs.length === 0) { + throw new Error(blogsError?.message || "Failed to create blog"); + } + console.log("Here is the blog data", blogs); + const blogId = blogs[0].id; + + const uploadPromises = [ + supabase.storage + .from("web_data") + .upload(`blogs/${blogId}/blog`, blogFile), + supabase.storage + .from("web_data") + .upload(`images/${blogId}/poster`, poster), + ]; + if (images.length > 0) { + images.forEach((image: File) => { + console.log("Uploading image", image); + uploadPromises.push( + supabase.storage + .from("web_data") + .upload(`images/${blogId}/${image.name}`, image) + ); + }); + } + + const uploadResults = await Promise.all(uploadPromises); + + const [blogUploadResult, posterUploadResult, ...imageUploadResults] = + uploadResults; + if (blogUploadResult.error || posterUploadResult.error) { + throw new Error( + blogUploadResult.error?.message || posterUploadResult.error?.message + ); + } + for (const result of imageUploadResults) { + if (result.error) { + throw new Error(result.error.message); + } + } + + return NextResponse.json({ + success: true, + message: "Blog created successfully", + id: blogId, + }); + } catch (error: any) { + return NextResponse.json( + { success: false, message: error.message }, + { status: 500 } + ); + } +} diff --git a/src/app/test_api/page.tsx b/src/app/test_api/page.tsx new file mode 100644 index 0000000..3a59582 --- /dev/null +++ b/src/app/test_api/page.tsx @@ -0,0 +1,234 @@ +"use client"; +import React, { useState } from "react"; +import { supabase } from "@/utils/supabase/client"; +import { Button } from "@/components/ui/button"; +import { Button as MUIButton } from "@mui/material"; + +export default function Page() { + const [selectedPosterFile, setSelectedPosterFile] = useState(null); + const [selectedBlogFile, setSelectedBlogFile] = useState(null); + const [selectedImages, setSelectedImages] = useState([]); + const [eventId, setEventId] = useState("7"); + const [blogId, setBlogId] = useState("24"); + const [events, setEvents] = useState([]); + const [blogs, setBlogs] = useState([]); + const createEvent = async () => { + const response = await fetch("/api/v1/create/event/", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + event: { + name: "CodeStrike v6.0", + description: "A competitive coding event", + date: "2024-07-30", + duration: 180, + mode: true, + host_link: "https://www.example.com", + requirements: ["Laptop", "Notebook"], + hosted_registration: true, + register_until: "2024-05-28", + registration_link: "https://www.example.com", + }, + }), + }); + const data = await response.json(); + console.log(data); + }; + + const submitBlog = async () => { + if (!selectedPosterFile || !selectedBlogFile) { + console.error("Missing required files for blog submission"); + return; + } + + const formData = new FormData(); + formData.append("poster", selectedPosterFile); + formData.append("blog", selectedBlogFile); + selectedImages.forEach((image) => { + formData.append("images", image); + }); + formData.append("blogData", JSON.stringify({ + title: "Seventh Blog", + intro: "This is the seventh blog", + })); + + const response = await fetch("/api/v1/submit/blog/", { + method: "POST", + body: formData, + }); + + const data = await response.json(); + console.log(data); + }; + + const deleteEvent = async () => { + const response = await fetch(`/api/v1/delete/event/?id=${eventId}`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + }); + const data = await response.json(); + console.log(data); + }; + + const getBlog = async () => { + const response = await fetch(`/api/v1/get/blog/?id=${blogId}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + const data = await response.json(); + console.log(data); + }; + + const getEvent = async () => { + const response = await fetch(`/api/v1/get/event/?id=${eventId}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + const data = await response.json(); + console.log(data); + }; + + const getEvents = async () => { + const response = await fetch("/api/v1/get/events/?category=past", { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + const data = await response.json(); + setEvents(data); + console.log(data); + }; + + const getBlogs = async () => { + const response = await fetch("/api/v1/get/blogs/?page=1&limit=5", { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + const data = await response.json(); + setBlogs(data.blogs); + console.log(data); + }; + + const handlePosterFileChange = (event: React.ChangeEvent) => { + if (event.target.files) { + const file = event.target.files[0]; + setSelectedPosterFile(file); + } + }; + + const handleBlogFileChange = (event: React.ChangeEvent) => { + if (event.target.files) { + const file = event.target.files[0]; + setSelectedBlogFile(file); + } + }; + + const handleImageChange = (event: React.ChangeEvent) => { + if (event.target.files) { + setSelectedImages(Array.from(event.target.files)); + } + }; + + return ( +
+

Test APIs

+
+ + + + + + + +
+ + + + {selectedPosterFile && ( +
+

Selected poster file: {selectedPosterFile.name}

+
+ )} + {selectedBlogFile && ( +
+

Selected blog file: {selectedBlogFile.name}

+
+ )} + {selectedImages.length > 0 && ( +
+

Selected images:

+
    + {selectedImages.map((image) => ( +
  • {image.name}
  • + ))} +
+
+ )} + {events?.length > 0 && ( +
+

Events:

+
    + {events.map((event) => ( +
  • {event.name}
  • + ))} +
+
+ )} + {blogs.length > 0 && ( +
+

Blogs:

+
    + {blogs.map((blog) => ( +
  • {blog.title}
  • + ))} +
+
+ )} +
+ ); +} \ No newline at end of file diff --git a/src/types/supabase.ts b/src/types/supabase.ts new file mode 100644 index 0000000000000000000000000000000000000000..84d5ff4c1aeb54937af3d84c27b7eeaee34567f8 GIT binary patch literal 14746 zcmeHO+in{-5an}${)6F%A_el&*CI_&v?-7lNDDXaiy#QGypGlAR!MGRr1|x>=PWhm z@UpvH%95SH5aeAg$>Chj49WF>|5oO`S(=4enT=VuHZQ=c3BJvXEJF4UH2RmRA5f)VVS zvX}IcE3HgxFX|E6^41qZ3(7dTZlI|RWUiv|P29b!eT#Cfvc@=D%bdKv0N=D9eVVfR zcG5jlntYZuGpSIQws${m#xvY+eVzgn&bR*ddFnH^NREf*jY|9I3H)vV$nqQ73k zpO#x}7~Eiv_Ez3E`!ZWEK#SNg0m{tHFSxrlFEPsuceLEG!k`gwhDdIsZG^EFcfUce zCh0lH7;{W(v?tf3I=~1`>v)T`A@A~gf^olszY3VS*xr%O1vs6et!ka0@?GG|>m1w= z14C_ES+8nZEBtMjC`Ivyq?C;KHt9Z^$4TC8?h*NUjlH5Wf7cRiz%ThvTe@FIl#4&rC-IrQZ_k>Br%0dB}DSe_E+%AQg?Nc~maJ0XZMRikK;UhK9UfuFix-eEPo!f}kQjYTC z)lx5sqJDLr#BBL|w;8piI_-|zT_~p_*U~GE-4brn(OXjOGJ0bUx-xB_5vg1okAH|@ zyHlYnE#BW%1jiO?@(CSpVdjmgVbO)RmxUKUpSOZeu({0W)Hh1EEBoyWjyyAwc5O+|Iunq3i* zqC_u9S3qpxr~4$~w60yayFRMdSG(1>jD*svmS>dQziX0|@^CHmxt$vL{_qE^P~jY= z>GWU#J8>nl28)Bma73uav>Cst|nWKGL`V|xNCXf?uY?=g}X zkRC@XeGiV>&&Sy-q)mE#u*Lf8t}(X(A1i3i>f1*~twnO|YtsMq95ndu#J=ptldKsk zdY+Bg)4o^Rw>1)})KguNf**)e2vGaQ?z8E<^(cQ&5f;D_ea^>|sY9V?tyuJCmyUTe%v-{$I) zZ=Jf=o>>kt_A7kbM!2YF|F5ID`9=*-=|tO%70pkm zN&2cg!*u+J^r5UicX9u2Z7~z|=TS%4vp}Xtk$((49p-RJYiPYuyTkgV+l_WM(%jJM z{DhjlO&;2;P76Ccx6$Q(TiZTGAHLh9JuAoxRRuG0Nyhoq zv*JE+u1(**x83)AbVx0gX9FE=)UVD7FFJC9-|kySaF|bZd^!6{*2<|3bWYIJLNs^O zEglOOf?$2d?h}XewNB2{XzXi0jk$c9ZplSsz4!Hi#<$_iB^e|1V;v8N#2L+pAMX!n zZ2w<_H9|QW>u3wn__PC!{dV6vVz&o07EK5;@|v%8_D-X*ul+RU%AB!n$W>Eq>~{6i z_$%%C`Bdv5+*NAJkTO%8TX(O7g|?0Zf=<_bm8 z?!NWK)na>I>gFg{KiuE7``#km@CGSEybF8g=t1XN?So