First commit
This commit is contained in:
commit
56f362d189
|
|
@ -0,0 +1,5 @@
|
||||||
|
node_modules
|
||||||
|
# Keep environment variables out of version control
|
||||||
|
.env
|
||||||
|
|
||||||
|
/src/generated/prisma
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
const { createDefaultPreset } = require("ts-jest");
|
||||||
|
|
||||||
|
const tsJestTransformCfg = createDefaultPreset().transform;
|
||||||
|
|
||||||
|
/** @type {import("jest").Config} **/
|
||||||
|
export default {
|
||||||
|
testEnvironment: "node",
|
||||||
|
transform: {
|
||||||
|
...tsJestTransformCfg,
|
||||||
|
},
|
||||||
|
};
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,41 @@
|
||||||
|
{
|
||||||
|
"name": "server",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"main": "index.ts",
|
||||||
|
"scripts": {
|
||||||
|
"start": "tsx src/index.ts",
|
||||||
|
"dev": "nodemon --exec tsx src/index.ts",
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": "",
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/client": "^6.15.0",
|
||||||
|
"@types/jest": "^30.0.0",
|
||||||
|
"@types/supertest": "^6.0.3",
|
||||||
|
"bcrypt": "^6.0.0",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"dotenv": "^17.2.1",
|
||||||
|
"express": "^5.1.0",
|
||||||
|
"express-validator": "^7.2.1",
|
||||||
|
"jest": "^30.1.3",
|
||||||
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"lodash.merge": "^4.6.2",
|
||||||
|
"morgan": "^1.10.1",
|
||||||
|
"supertest": "^7.1.4",
|
||||||
|
"ts-jest": "^29.4.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/lodash.merge": "^4.6.9",
|
||||||
|
"@types/node": "^24.3.0",
|
||||||
|
"nodemon": "^3.1.10",
|
||||||
|
"prisma": "^6.15.0",
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
|
"tsx": "^4.20.5",
|
||||||
|
"typescript": "^5.9.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "public"."UPDATE_STATUS" AS ENUM ('IN_PROGRESS', 'LIVE', 'DEPRECATED', 'ARCHIVED');
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "public"."Post" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"title" VARCHAR(255) NOT NULL,
|
||||||
|
"content" TEXT,
|
||||||
|
"published" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"authorId" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Post_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "public"."User" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"username" TEXT NOT NULL,
|
||||||
|
"password" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "public"."Product" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"belongsToId" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Product_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "public"."Update" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"title" VARCHAR(255) NOT NULL,
|
||||||
|
"body" TEXT NOT NULL,
|
||||||
|
"status" "public"."UPDATE_STATUS" NOT NULL DEFAULT 'IN_PROGRESS',
|
||||||
|
"version" TEXT,
|
||||||
|
"asset" TEXT NOT NULL,
|
||||||
|
"productId" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Update_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "public"."UpdatePoint" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"name" VARCHAR(255) NOT NULL,
|
||||||
|
"description" TEXT NOT NULL,
|
||||||
|
"updateId" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "UpdatePoint_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "public"."_UpdateToUser" (
|
||||||
|
"A" TEXT NOT NULL,
|
||||||
|
"B" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "_UpdateToUser_AB_pkey" PRIMARY KEY ("A","B")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "User_username_key" ON "public"."User"("username");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "_UpdateToUser_B_index" ON "public"."_UpdateToUser"("B");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "public"."Post" ADD CONSTRAINT "Post_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "public"."User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "public"."Product" ADD CONSTRAINT "Product_belongsToId_fkey" FOREIGN KEY ("belongsToId") REFERENCES "public"."User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "public"."Update" ADD CONSTRAINT "Update_productId_fkey" FOREIGN KEY ("productId") REFERENCES "public"."Product"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "public"."UpdatePoint" ADD CONSTRAINT "UpdatePoint_updateId_fkey" FOREIGN KEY ("updateId") REFERENCES "public"."Update"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "public"."_UpdateToUser" ADD CONSTRAINT "_UpdateToUser_A_fkey" FOREIGN KEY ("A") REFERENCES "public"."Update"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "public"."_UpdateToUser" ADD CONSTRAINT "_UpdateToUser_B_fkey" FOREIGN KEY ("B") REFERENCES "public"."User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Please do not edit this file manually
|
||||||
|
# It should be added in your version-control system (e.g., Git)
|
||||||
|
provider = "postgresql"
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
// This is your Prisma schema file,
|
||||||
|
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||||
|
|
||||||
|
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
|
||||||
|
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
|
||||||
|
|
||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
output = "../src/generated/prisma"
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "postgresql"
|
||||||
|
url = env("DATABASE_URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
model Post {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
title String @db.VarChar(255)
|
||||||
|
content String?
|
||||||
|
published Boolean @default(false)
|
||||||
|
author User @relation(fields: [authorId], references: [id])
|
||||||
|
authorId String
|
||||||
|
}
|
||||||
|
|
||||||
|
model User {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
username String @unique
|
||||||
|
password String
|
||||||
|
updates Update[]
|
||||||
|
|
||||||
|
Post Post[]
|
||||||
|
|
||||||
|
Product Product[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model Product {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
name String
|
||||||
|
belongsTo User @relation(fields: [belongsToId], references: [id])
|
||||||
|
belongsToId String
|
||||||
|
updates Update[]
|
||||||
|
}
|
||||||
|
|
||||||
|
enum UPDATE_STATUS {
|
||||||
|
IN_PROGRESS
|
||||||
|
LIVE
|
||||||
|
DEPRECATED
|
||||||
|
ARCHIVED
|
||||||
|
}
|
||||||
|
|
||||||
|
model Update {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime
|
||||||
|
title String @db.VarChar(255)
|
||||||
|
body String
|
||||||
|
status UPDATE_STATUS @default(IN_PROGRESS)
|
||||||
|
version String?
|
||||||
|
asset String
|
||||||
|
|
||||||
|
productId String
|
||||||
|
product Product @relation(fields: [productId], references: [id])
|
||||||
|
updatePoints UpdatePoint[]
|
||||||
|
|
||||||
|
User User[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model UpdatePoint {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime
|
||||||
|
name String @db.VarChar(255)
|
||||||
|
description String
|
||||||
|
updateId String
|
||||||
|
update Update @relation(fields: [updateId], references: [id])
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
POST http://localhost:3000/user
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"username": "Piddo",
|
||||||
|
"password": "123456789"
|
||||||
|
}
|
||||||
|
|
||||||
|
POST http://localhost:3000/signin
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"username": "Piddo",
|
||||||
|
"password": "123456789"
|
||||||
|
}
|
||||||
|
|
||||||
|
GET http://localhost:3000/health
|
||||||
|
|
||||||
|
GET http://localhost:3000/api/product
|
||||||
|
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEwZGQ1MmUzLWY2NjEtNGRkNy1hNjIzLTFkNzY0YjQwMmY0YSIsInVzZXJuYW1lIjoiUGlkZG8iLCJpYXQiOjE3NTY5MTA5NTd9.rtktVNT5uofPuSJ5nkz6J19iCRnC_qQ35oCuN5VgfBI
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
GET http://localhost:3000/api/product
|
||||||
|
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjAyNDAzNGRjLTVjZmEtNDk2Mi1iOTA5LTU2MmUwY2M0OGIyZCIsInVzZXJuYW1lIjoiRXBocmFpbSIsImlhdCI6MTc1NjkwODI5NH0.gLosNyt2gISTlHLO2jRtE1a07lgHJukJ34i7jHOwkAM
|
||||||
|
|
||||||
|
POST http://localhost:3000/user
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"username": "Y",
|
||||||
|
"password": "t@no2500"
|
||||||
|
}
|
||||||
|
|
||||||
|
POST http://localhost:3000/user
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"username": "Paschal",
|
||||||
|
"password": "M@no2500pasc"
|
||||||
|
}
|
||||||
|
|
||||||
|
POST http://localhost:3000/user
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"username": "Ephraim",
|
||||||
|
"password": "M@no474pasc"
|
||||||
|
}
|
||||||
|
|
||||||
|
POST http://localhost:3000/signin
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"username": "kiddoh",
|
||||||
|
"password": "M@no2500"
|
||||||
|
}
|
||||||
|
|
||||||
|
POST http://localhost:3000/signin
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"username": "Paschal",
|
||||||
|
"password": "M@no2500pasc"
|
||||||
|
}
|
||||||
|
|
||||||
|
POST http://localhost:3000/signin
|
||||||
|
Content-Type: application/json
|
||||||
|
{
|
||||||
|
"username": "Ephraim",
|
||||||
|
"password": "M@no474pasc"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import merge from "lodash.merge"
|
||||||
|
|
||||||
|
const stage = process.env.STAGE || "local";
|
||||||
|
|
||||||
|
let envConfig;
|
||||||
|
|
||||||
|
if(stage=== "production"){
|
||||||
|
envConfig = require("./prod").default;
|
||||||
|
}
|
||||||
|
else if( stage==="staging") {
|
||||||
|
envConfig = require("./local").default;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
envConfig = require("./local").default;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultConfig = {
|
||||||
|
stage,
|
||||||
|
dbUrl: process.env.DATABASE_URL,
|
||||||
|
jwtSecret:process.env.JWT_SECRET,
|
||||||
|
logging:false,
|
||||||
|
|
||||||
|
}
|
||||||
|
export default merge(defaultConfig, envConfig);
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { PrismaClient } from "./generated/prisma";
|
||||||
|
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
export default prisma;
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
import prisma from "../db";
|
||||||
|
import { Request, Response } from "express";
|
||||||
|
|
||||||
|
export const getPosts = async (req:Request, res:Response) => {
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: { id: req.user.id },
|
||||||
|
include: { Post: true },
|
||||||
|
});
|
||||||
|
res.json({data: user.Post});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getPostById = async (req:Request, res:Response) => {
|
||||||
|
const post = await prisma.post.findUnique({
|
||||||
|
where: {id: req.params.id, authorId: req.user.id},
|
||||||
|
});
|
||||||
|
res.json({data: post});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createPost = async (req:Request, res:Response) => {
|
||||||
|
const post = await prisma.post.create({
|
||||||
|
data: {
|
||||||
|
title: req.body.title,
|
||||||
|
content: req.body.content,
|
||||||
|
authorId: req.user.id,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
res.json({data: post});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updatePost = async (req:Request, res:Response) => {
|
||||||
|
const post = await prisma.post.update({
|
||||||
|
where: {id: req.params.id, authorId: req.user.id},
|
||||||
|
data: {
|
||||||
|
title: req.body.title,
|
||||||
|
content: req.body.content,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
res.json({data: post});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deletePost = async (req:Request, res:Response) => {
|
||||||
|
const post = await prisma.post.delete({
|
||||||
|
where: {id: req.params.id, authorId: req.user.id},
|
||||||
|
});
|
||||||
|
res.json({data: post});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
import prisma from "../db";
|
||||||
|
import { Request, Response } from "express";
|
||||||
|
|
||||||
|
export const getProducts = async (req:Request, res:Response) => {
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: { id: req.user.id },
|
||||||
|
include: { Product: true },
|
||||||
|
});
|
||||||
|
res.json({data: user.Product});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getProductById = async (req:Request, res:Response) => {
|
||||||
|
const product = await prisma.product.findUnique({
|
||||||
|
where: {id: req.params.id,belongsToId: req.user.id},
|
||||||
|
});
|
||||||
|
res.json({data: product});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createProduct = async (req:Request, res:Response) => {
|
||||||
|
const product = await prisma.product.create({
|
||||||
|
data: {
|
||||||
|
name: req.body.name,
|
||||||
|
belongsToId: req.user.id,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
res.json({data: product});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateProduct = async (req:Request, res:Response) => {
|
||||||
|
const product = await prisma.product.update({
|
||||||
|
where: {id: req.params.id, belongsToId: req.user.id},
|
||||||
|
data: {
|
||||||
|
name: req.body.name,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
res.json({data: product});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deleteProduct = async (req:Request, res:Response) => {
|
||||||
|
const product = await prisma.product.delete({
|
||||||
|
where: {id: req.params.id, belongsToId: req.user.id},
|
||||||
|
});
|
||||||
|
res.json({data: product});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
import prisma from "../db";
|
||||||
|
import { Request, Response } from "express";
|
||||||
|
|
||||||
|
export const getUpdates = async (req:Request, res:Response) => {
|
||||||
|
const products = await prisma.product.findMany({
|
||||||
|
where: {belongsToId: req.user.id},
|
||||||
|
include: {updates: true}
|
||||||
|
});
|
||||||
|
const updates = products.reduce((allUpdates, product) => {
|
||||||
|
return [...allUpdates, ...product.updates]
|
||||||
|
}, []);
|
||||||
|
res.json({data: updates});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getUpdateById = async (req:Request, res:Response) => {
|
||||||
|
const update = await prisma.update.findUnique({
|
||||||
|
where: {id: req.params.id}
|
||||||
|
});
|
||||||
|
res.json({data: update});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createUpdate = async (req:Request, res:Response) => {
|
||||||
|
const product = await prisma.product.findUnique({
|
||||||
|
where: {id: req.body.productId}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!product || product.belongsToId !== req.user.id) {
|
||||||
|
return res.status(401).json({message: "Unauthorized"});
|
||||||
|
}
|
||||||
|
|
||||||
|
const update = await prisma.update.create({
|
||||||
|
data: req.body
|
||||||
|
});
|
||||||
|
res.json({data: update});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateUpdate = async (req:Request, res:Response) => {
|
||||||
|
const products = await prisma.product.findMany({
|
||||||
|
where: {belongsToId: req.user.id},
|
||||||
|
include: {updates: true}
|
||||||
|
});
|
||||||
|
|
||||||
|
const updates = products.reduce((allUpdates, product) => {
|
||||||
|
return [...allUpdates, ...product.updates]
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const match = updates.find(update => update.id === req.params.id);
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
return res.status(401).json({message: "Unauthorized"});
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedUpdate = await prisma.update.update({
|
||||||
|
where: {id: req.params.id},
|
||||||
|
data: req.body
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json({data: updatedUpdate});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deleteUpdate = async (req:Request, res:Response) => {
|
||||||
|
const products = await prisma.product.findMany({
|
||||||
|
where: {belongsToId: req.user.id},
|
||||||
|
include: {updates: true}
|
||||||
|
});
|
||||||
|
|
||||||
|
const updates = products.reduce((allUpdates, product) => {
|
||||||
|
return [...allUpdates, ...product.updates]
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const match = updates.find(update => update.id === req.params.id);
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
return res.status(401).json({message: "Unauthorized"});
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleted = await prisma.update.delete({
|
||||||
|
where: {id: req.params.id}
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json({data: deleted});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
import prisma from "../db";
|
||||||
|
import { Request, Response } from "express";
|
||||||
|
|
||||||
|
export const getUpdatePoints = async (req:Request, res:Response) => {
|
||||||
|
const update = await prisma.update.findUnique({
|
||||||
|
where: {id: req.body.updateId},
|
||||||
|
include: {updatePoints: true}
|
||||||
|
});
|
||||||
|
res.json({data: update.updatePoints});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getUpdatePointById = async (req:Request, res:Response) => {
|
||||||
|
const updatePoint = await prisma.updatePoint.findUnique({
|
||||||
|
where: {id: req.params.id}
|
||||||
|
});
|
||||||
|
res.json({data: updatePoint});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createUpdatePoint = async (req:Request, res:Response) => {
|
||||||
|
const update = await prisma.update.findUnique({
|
||||||
|
where: {id: req.body.updateId},
|
||||||
|
include: {product: true}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!update || update.product.belongsToId !== req.user.id) {
|
||||||
|
return res.status(401).json({message: "Unauthorized"});
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatePoint = await prisma.updatePoint.create({
|
||||||
|
data: req.body
|
||||||
|
});
|
||||||
|
res.json({data: updatePoint});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateUpdatePoint = async (req:Request, res:Response) => {
|
||||||
|
const updatePoint = await prisma.updatePoint.findUnique({
|
||||||
|
where: {id: req.params.id},
|
||||||
|
include: {update: {include: {product: true}}}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!updatePoint || updatePoint.update.product.belongsToId !== req.user.id) {
|
||||||
|
return res.status(401).json({message: "Unauthorized"});
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedUpdatePoint = await prisma.updatePoint.update({
|
||||||
|
where: {id: req.params.id},
|
||||||
|
data: req.body
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json({data: updatedUpdatePoint});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deleteUpdatePoint = async (req:Request, res:Response) => {
|
||||||
|
const updatePoint = await prisma.updatePoint.findUnique({
|
||||||
|
where: {id: req.params.id},
|
||||||
|
include: {update: {include: {product: true}}}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!updatePoint || updatePoint.update.product.belongsToId !== req.user.id) {
|
||||||
|
return res.status(401).json({message: "Unauthorized"});
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleted = await prisma.updatePoint.delete({
|
||||||
|
where: {id: req.params.id}
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json({data: deleted});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
import prisma from "../db";
|
||||||
|
import { comparePasswords, createJWT, hashPassword } from "../module/auth";
|
||||||
|
|
||||||
|
export const createNewUser = async (req, res) => {
|
||||||
|
const hash = await hashPassword(req.body.password);
|
||||||
|
|
||||||
|
const user = await prisma.user.create({
|
||||||
|
data: {
|
||||||
|
username: req.body.username,
|
||||||
|
password: hash,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const token = createJWT(user);
|
||||||
|
res.json({ user, token });
|
||||||
|
}
|
||||||
|
|
||||||
|
export const signIn = async (req, res) => {
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: {
|
||||||
|
username: req.body.username,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return res.status(401).json({ error: 'User not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const isValid = await comparePasswords(req.body.password, user.password);
|
||||||
|
|
||||||
|
if (!isValid) {
|
||||||
|
return res.status(401).json({ error: 'Invalid password' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = createJWT(user);
|
||||||
|
res.status(200).json({ token });
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
import app from './server';
|
||||||
|
import dotenv from 'dotenv';
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
app.listen(process.env.PORT || 3000, () => {
|
||||||
|
console.log(`Server is running on port ${process.env.PORT || 3000}`);
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
import * as bcrypt from 'bcrypt';
|
||||||
|
|
||||||
|
export const createJWT = (user) => {
|
||||||
|
const token = jwt.sign(
|
||||||
|
{ id: user.id, username: user.username },
|
||||||
|
process.env.JWT_SECRET,
|
||||||
|
);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const comparePasswords = (password: string, hash: string) => {
|
||||||
|
return bcrypt.compare(password, hash);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const hashPassword = (password: string) => {
|
||||||
|
return bcrypt.hash(password, 10);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { NextFunction, Request, Response } from 'express';
|
||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
|
||||||
|
export const protect = (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const bearer = req.headers.authorization;
|
||||||
|
if(!bearer){
|
||||||
|
return res.status(401).json({message: 'Unauthorized'});
|
||||||
|
}
|
||||||
|
|
||||||
|
const [, token] = bearer.split(" ");
|
||||||
|
if(!token){
|
||||||
|
console.log("No token found");
|
||||||
|
return res.status(401).json({message: 'Unauthorized'});
|
||||||
|
}
|
||||||
|
|
||||||
|
const jwtSecret = process.env.JWT_SECRET;
|
||||||
|
if(!jwtSecret){
|
||||||
|
console.log("JWT_SECRET not defined in environment variables");
|
||||||
|
return res.status(500).json({message: 'Internal server error'});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const payload = jwt.verify(token, jwtSecret);
|
||||||
|
req.user = payload;
|
||||||
|
console.log(payload);
|
||||||
|
next();
|
||||||
|
} catch (error) {
|
||||||
|
console.log("JWT verification failed:", error);
|
||||||
|
return res.status(401).json({message: 'Unauthorized'});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { NextFunction, Request, Response } from 'express';
|
||||||
|
|
||||||
|
export const healthCheck = (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
// res.status(200).json({status: 'OK', message: 'Server is healthy'});
|
||||||
|
console.log(new Date(), 'Health check endpoint hit');
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { NextFunction, Request, Response} from 'express';
|
||||||
|
import { body, validationResult } from "express-validator";
|
||||||
|
|
||||||
|
export const validateRequestUpdate =[
|
||||||
|
body("title").isString().isLength({min: 2, max: 255}),
|
||||||
|
body("body").isString().isLength({min: 2}),
|
||||||
|
body("version").optional().isString(),
|
||||||
|
body("asset").isString().isLength({min:1, max:5}),
|
||||||
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const errors = validationResult(req);
|
||||||
|
if (!errors.isEmpty()){
|
||||||
|
return res.status(400).json({errors: errors.array()});
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
}];
|
||||||
|
|
||||||
|
export const validateRequestProduct =[
|
||||||
|
body("name").isString().isLength({min: 2}),
|
||||||
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const errors = validationResult(req);
|
||||||
|
if (!errors.isEmpty()){
|
||||||
|
return res.status(400).json({errors: errors.array()});
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
}];
|
||||||
|
|
||||||
|
export const validateRequestPost = [
|
||||||
|
body("title").isString().isLength({min: 2, max: 255}),
|
||||||
|
body("content").optional().isString(),
|
||||||
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const errors = validationResult(req);
|
||||||
|
if (!errors.isEmpty()){
|
||||||
|
return res.status(400).json({errors: errors.array()});
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export const validateRequestUpdatePoint =[
|
||||||
|
body("name").isString().isLength({min: 2, max: 255}),
|
||||||
|
body("description").isString().isLength({min: 2}),
|
||||||
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const errors = validationResult(req);
|
||||||
|
if (!errors.isEmpty()){
|
||||||
|
return res.status(400).json({errors: errors.array()});
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
}];
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { Router } from "express";
|
||||||
|
import { body, validationResult } from "express-validator";
|
||||||
|
import { getProducts, getProductById, createProduct, updateProduct, deleteProduct } from "./handlers/product";
|
||||||
|
import { getPosts, getPostById, createPost, updatePost, deletePost } from "./handlers/post";
|
||||||
|
import { getUpdates, getUpdateById, createUpdate, updateUpdate, deleteUpdate } from "./handlers/update";
|
||||||
|
import { getUpdatePoints, getUpdatePointById, createUpdatePoint, updateUpdatePoint, deleteUpdatePoint } from "./handlers/updatepoint";
|
||||||
|
import { validateRequestProduct, validateRequestUpdate, validateRequestUpdatePoint, validateRequestPost } from "./module/validateMiddleware";
|
||||||
|
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
|
||||||
|
router.get("/product", getProducts);
|
||||||
|
|
||||||
|
|
||||||
|
router.get("/product/:id", getProductById);
|
||||||
|
|
||||||
|
router.post("/product",validateRequestProduct, createProduct);
|
||||||
|
router.put("/product/:id",validateRequestProduct,updateProduct);
|
||||||
|
router.delete("/product/:id", deleteProduct);
|
||||||
|
|
||||||
|
router.get("/update", getUpdates);
|
||||||
|
router.get("/update/:id", getUpdateById);
|
||||||
|
router.post("/update",validateRequestUpdate, createUpdate);
|
||||||
|
router.put("/update/:id",validateRequestUpdate, updateUpdate);
|
||||||
|
router.delete("/update/:id", deleteUpdate);
|
||||||
|
|
||||||
|
router.get("/updatepoint", getUpdatePoints);
|
||||||
|
router.get("/updatepoint/:id", getUpdatePointById);
|
||||||
|
router.post("/updatepoint", validateRequestUpdatePoint, createUpdatePoint);
|
||||||
|
router.put("/updatepoint/:id", validateRequestUpdatePoint, updateUpdatePoint);
|
||||||
|
router.delete("/updatepoint/:id", deleteUpdatePoint);
|
||||||
|
|
||||||
|
router.get("/post", getPosts);
|
||||||
|
router.get("/post/:id", getPostById);
|
||||||
|
router.post("/post", validateRequestPost, createPost);
|
||||||
|
router.put("/post/:id", validateRequestPost, updatePost);
|
||||||
|
router.delete("/post/:id", deletePost);
|
||||||
|
|
||||||
|
export default router;
|
||||||
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
import cors from 'cors';
|
||||||
|
import express from 'express';
|
||||||
|
import morgan from 'morgan';
|
||||||
|
import { createNewUser, signIn } from './handlers/user';
|
||||||
|
import { protect } from './module/authMiddleware';
|
||||||
|
import { healthCheck } from './module/healthMiddleware';
|
||||||
|
import router from './router';
|
||||||
|
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
// app.get("/", (req, res) => {
|
||||||
|
// console.log('Request received');
|
||||||
|
// res.status(200);
|
||||||
|
// res.json({ message: 'Hello, Dropping Zone' });
|
||||||
|
// });
|
||||||
|
|
||||||
|
app.use(cors());
|
||||||
|
app.use(morgan('dev'));
|
||||||
|
app.use(express.json());
|
||||||
|
app.use(express.urlencoded({ extended: true }));
|
||||||
|
|
||||||
|
app.get('/health',healthCheck, (req, res) => {
|
||||||
|
res.json({"ping": "pong"});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.use('/api', protect, router);
|
||||||
|
app.post('/user', createNewUser)
|
||||||
|
app.post('/signin', signIn)
|
||||||
|
|
||||||
|
|
||||||
|
export default app;
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"sourceMap": true,
|
||||||
|
"outDir": "dist",
|
||||||
|
"strict": false,
|
||||||
|
"lib":["esnext"],
|
||||||
|
"esModuleInterop": true,
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue