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