Initial
This commit is contained in:
commit
fcd683ed67
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
node_modules
|
||||
# Keep environment variables out of version control
|
||||
.env
|
||||
|
||||
/generated/prisma
|
||||
19
middleware/authMiddleware.js
Normal file
19
middleware/authMiddleware.js
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
const jwt = require("jsonwebtoken");
|
||||
|
||||
module.exports = (req, res, next) => {
|
||||
const authHeader = req.headers.authorization;
|
||||
console.log("Authorization Header:", authHeader); // <---
|
||||
|
||||
if (!authHeader) return res.status(401).json({ error: "Token yok" });
|
||||
|
||||
const token = authHeader.split(" ")[1];
|
||||
try {
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||||
console.log("Decoded Token:", decoded); // <---
|
||||
req.user = decoded;
|
||||
next();
|
||||
} catch (err) {
|
||||
console.error("JWT verify error:", err.message);
|
||||
return res.status(401).json({ error: "Token geçersiz" });
|
||||
}
|
||||
};
|
||||
1747
package-lock.json
generated
Normal file
1747
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
28
package.json
Normal file
28
package.json
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"name": "backend",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"type": "commonjs",
|
||||
"dependencies": {
|
||||
"@prisma/client": "^6.18.0",
|
||||
"bcryptjs": "^3.0.2",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^5.1.0",
|
||||
"jsonwebtoken": "^9.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/express": "^5.0.5",
|
||||
"@types/node": "^24.9.2",
|
||||
"prisma": "^6.18.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
17
prisma/migrations/20251030135832_init_user/migration.sql
Normal file
17
prisma/migrations/20251030135832_init_user/migration.sql
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"username" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"passwordHash" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
||||
17
prisma/migrations/20251030140011_init_user/migration.sql
Normal file
17
prisma/migrations/20251030140011_init_user/migration.sql
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
-- CreateTable
|
||||
CREATE TABLE "Deal" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"url" TEXT,
|
||||
"imageUrl" TEXT,
|
||||
"price" DOUBLE PRECISION,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"userId" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "Deal_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Deal" ADD CONSTRAINT "Deal_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "Deal" ALTER COLUMN "updatedAt" SET DEFAULT CURRENT_TIMESTAMP;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "User" ALTER COLUMN "updatedAt" SET DEFAULT CURRENT_TIMESTAMP;
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
-- CreateTable
|
||||
CREATE TABLE "DealVote" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"dealId" INTEGER NOT NULL,
|
||||
"userId" INTEGER NOT NULL,
|
||||
"voteType" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "DealVote_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "DealVote" ADD CONSTRAINT "DealVote_dealId_fkey" FOREIGN KEY ("dealId") REFERENCES "Deal"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "DealVote" ADD CONSTRAINT "DealVote_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "Deal" ADD COLUMN "score" INTEGER NOT NULL DEFAULT 0,
|
||||
ALTER COLUMN "updatedAt" DROP DEFAULT;
|
||||
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal file
|
|
@ -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"
|
||||
54
prisma/schema.prisma
Normal file
54
prisma/schema.prisma
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
// 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"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
username String @unique
|
||||
email String @unique
|
||||
passwordHash String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
Deal Deal[]
|
||||
votes DealVote[]
|
||||
}
|
||||
|
||||
model Deal {
|
||||
id Int @id @default(autoincrement())
|
||||
title String
|
||||
description String?
|
||||
url String?
|
||||
imageUrl String?
|
||||
price Float?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
userId Int
|
||||
|
||||
// yeni alan:
|
||||
score Int @default(0)
|
||||
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
votes DealVote[]
|
||||
}
|
||||
|
||||
model DealVote {
|
||||
id Int @id @default(autoincrement())
|
||||
dealId Int
|
||||
userId Int
|
||||
voteType String // "UP" veya "DOWN"
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
deal Deal @relation(fields: [dealId], references: [id])
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
}
|
||||
63
routes/authRoutes.js
Normal file
63
routes/authRoutes.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
const express = require("express");
|
||||
const bcrypt = require("bcryptjs");
|
||||
const jwt = require("jsonwebtoken");
|
||||
const { PrismaClient } = require("@prisma/client");
|
||||
const generateToken = require("../utils/generateToken");
|
||||
|
||||
const router = express.Router();
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
|
||||
// Kayıt ol
|
||||
router.post("/register", async (req, res) => {
|
||||
try {
|
||||
const { username, email, password } = req.body;
|
||||
|
||||
const existingUser = await prisma.user.findUnique({ where: { email } });
|
||||
if (existingUser) return res.status(400).json({ message: "Bu e-posta zaten kayıtlı." });
|
||||
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
|
||||
const user = await prisma.user.create({
|
||||
data: { username, email, passwordHash: hashedPassword },
|
||||
});
|
||||
|
||||
const token = generateToken(user.id);
|
||||
|
||||
res.json({ token, user: { id: user.id, username: user.username, email: user.email } });
|
||||
} catch (err) {
|
||||
res.status(500).json({ message: "Kayıt işlemi başarısız.", error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Giriş yap
|
||||
router.post("/login", async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await prisma.user.findUnique({ where: { email } });
|
||||
if (!user)
|
||||
return res.status(400).json({ message: "Kullanıcı bulunamadı." });
|
||||
|
||||
const isMatch = await bcrypt.compare(password, user.passwordHash);
|
||||
if (!isMatch)
|
||||
return res.status(401).json({ message: "Şifre hatalı." });
|
||||
|
||||
// userId olarak imzala
|
||||
const token = generateToken(user.id);
|
||||
|
||||
res.json({
|
||||
token,
|
||||
user: { id: user.id, username: user.username, email: user.email },
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res
|
||||
.status(500)
|
||||
.json({ message: "Giriş işlemi başarısız.", error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = router;
|
||||
21
routes/dealRoutes.js
Normal file
21
routes/dealRoutes.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
const express = require("express");
|
||||
const { PrismaClient } = require("@prisma/client");
|
||||
const router = express.Router();
|
||||
const prisma = new PrismaClient();
|
||||
const authMiddleware = require("../middleware/authMiddleware");
|
||||
|
||||
|
||||
router.get("/", async (req, res) => {
|
||||
const deals = await prisma.deal.findMany({ include: { user: true } });
|
||||
res.json(deals);
|
||||
});
|
||||
|
||||
router.post("/",authMiddleware, async (req, res) => {
|
||||
const { title, description, url, imageUrl, price, userId } = req.body;
|
||||
const deal = await prisma.deal.create({
|
||||
data: { title, description, url, imageUrl, price, userId },
|
||||
});
|
||||
res.json(deal);
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
73
routes/dealVoteRoutes.js
Normal file
73
routes/dealVoteRoutes.js
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
const express = require("express");
|
||||
const { PrismaClient } = require("@prisma/client");
|
||||
const authMiddleware = require("../middleware/authMiddleware");
|
||||
|
||||
const router = express.Router();
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Oy verme
|
||||
router.post("/", authMiddleware, async (req, res) => {
|
||||
console.log("body:", req.body);
|
||||
console.log("user:", req.user);
|
||||
try {
|
||||
const { dealId, voteType } = req.body;
|
||||
const userId = req.user.userId;
|
||||
if (!dealId || !userId || !voteType)
|
||||
return res.status(400).json({ error: "Eksik veri." });
|
||||
|
||||
const existingVote = await prisma.dealVote.findFirst({
|
||||
where: { dealId, userId },
|
||||
});
|
||||
|
||||
let vote;
|
||||
if (existingVote) {
|
||||
// Aynı kullanıcı aynı ilana yeniden oy verirse güncelle
|
||||
vote = await prisma.dealVote.update({
|
||||
where: { id: existingVote.id },
|
||||
data: { voteType },
|
||||
});
|
||||
} else {
|
||||
vote = await prisma.dealVote.create({
|
||||
data: { dealId, userId, voteType },
|
||||
});
|
||||
}
|
||||
|
||||
// Toplam oy sayısını güncelle
|
||||
const upvotes = await prisma.dealVote.count({
|
||||
where: { dealId, voteType: "UP" },
|
||||
});
|
||||
const downvotes = await prisma.dealVote.count({
|
||||
where: { dealId, voteType: "DOWN" },
|
||||
});
|
||||
|
||||
await prisma.deal.update({
|
||||
where: { id: dealId },
|
||||
data: { score: upvotes - downvotes },
|
||||
});
|
||||
|
||||
res.json({ vote, score: upvotes - downvotes });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: "Sunucu hatası." });
|
||||
}
|
||||
});
|
||||
|
||||
// Belirli bir deal için oyları çek
|
||||
router.get("/:dealId", async (req, res) => {
|
||||
try {
|
||||
const { dealId } = req.params;
|
||||
|
||||
const upvotes = await prisma.dealVote.count({
|
||||
where: { dealId: parseInt(dealId), voteType: "UP" },
|
||||
});
|
||||
const downvotes = await prisma.dealVote.count({
|
||||
where: { dealId: parseInt(dealId), voteType: "DOWN" },
|
||||
});
|
||||
|
||||
res.json({ upvotes, downvotes, score: upvotes - downvotes });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: "Sunucu hatası." });
|
||||
}
|
||||
});
|
||||
module.exports = router;
|
||||
11
routes/userRoutes.js
Normal file
11
routes/userRoutes.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
const express = require("express");
|
||||
const { PrismaClient } = require("@prisma/client");
|
||||
const router = express.Router();
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
router.get("/", async (req, res) => {
|
||||
const users = await prisma.user.findMany();
|
||||
res.json(users);
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
20
server.js
Normal file
20
server.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
const express = require("express");
|
||||
const cors = require("cors");
|
||||
require("dotenv").config();
|
||||
|
||||
const userRoutes = require("./routes/userRoutes");
|
||||
const dealRoutes = require("./routes/dealRoutes");
|
||||
const authRoutes = require("./routes/authRoutes");
|
||||
const dealVoteRoutes = require("./routes/dealVoteRoutes");
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
app.use("/api/users", userRoutes);
|
||||
app.use("/api/deals", dealRoutes);
|
||||
app.use("/api/auth", authRoutes);
|
||||
app.use("/api/deal-votes", dealVoteRoutes);
|
||||
|
||||
app.listen(3000, () => console.log("Server running on http://localhost:3000"));
|
||||
9
utils/generateToken.js
Normal file
9
utils/generateToken.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
const jwt = require("jsonwebtoken");
|
||||
|
||||
function generateToken(userId) {
|
||||
return jwt.sign({ userId }, process.env.JWT_SECRET || "secretkey", {
|
||||
expiresIn: "7d",
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = generateToken;
|
||||
Loading…
Reference in New Issue
Block a user