This commit is contained in:
cureb 2025-10-30 22:49:11 +00:00
commit fcd683ed67
17 changed files with 2111 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
node_modules
# Keep environment variables out of version control
.env
/generated/prisma

View 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

File diff suppressed because it is too large Load Diff

28
package.json Normal file
View 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"
}
}

View 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");

View 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;

View File

@ -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;

View File

@ -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;

View File

@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "Deal" ADD COLUMN "score" INTEGER NOT NULL DEFAULT 0,
ALTER COLUMN "updatedAt" DROP DEFAULT;

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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;