Compare commits

..

No commits in common. "e966158d374ca3a1762978adcc1115f027c1141f" and "8ed231c5f2269b2f3eb8bea70418d67a8c98a5fd" have entirely different histories.

25 changed files with 106 additions and 1370 deletions

View File

@ -1,3 +0,0 @@
const { PrismaClient } = require("@prisma/client")
const prisma = new PrismaClient()
module.exports = prisma

View File

@ -1,28 +0,0 @@
const prisma = require("./client")
async function findComments(where, options = {}) {
return prisma.comment.findMany({
where,
include: options.include || undefined,
select: options.select || undefined,
orderBy: options.orderBy || { createdAt: "desc" },
})
}
async function createComment(data, options = {}) {
return prisma.comment.create({
data,
include: options.include || undefined,
select: options.select || undefined,
})
}
async function deleteComment(where) {
return prisma.comment.delete({ where })
}
module.exports = {
findComments,
createComment,
deleteComment,
}

View File

@ -1,82 +0,0 @@
const prisma = require("./client")
async function findDeals(where = {}, options = {}) {
return prisma.deal.findMany({
where,
include: options.include || undefined,
select: options.select || undefined,
orderBy: options.orderBy || { createdAt: "desc" },
skip: options.skip || 0,
take: options.take || undefined,
})
}
async function findDeal(where, options = {}) {
return prisma.deal.findUnique({
where,
include: options.include || undefined,
select: options.select || undefined,
})
}
async function createDeal(data, options = {}) {
return prisma.deal.create({
data,
include: options.include || undefined,
select: options.select || undefined,
})
}
async function updateDeal(where, data, options = {}) {
return prisma.deal.update({
where,
data,
include: options.include || undefined,
select: options.select || undefined,
})
}
async function countDeals(where = {}) {
return prisma.deal.count({ where })
}
async function findVotes(where = {}, options = {}) {
return prisma.dealVote.findMany({
where,
include: options.include || undefined,
select: options.select || undefined,
})
}
async function createVote(data, options = {}) {
return prisma.dealVote.create({
data,
include: options.include || undefined,
select: options.select || undefined,
})
}
async function updateVote(where, data, options = {}) {
return prisma.dealVote.update({
where,
data,
include: options.include || undefined,
select: options.select || undefined,
})
}
async function countVotes(where = {}) {
return prisma.dealVote.count({ where })
}
module.exports = {
findDeals,
findDeal,
createDeal,
updateDeal,
countDeals,
findVotes,
createVote,
updateVote,
countVotes,
}

View File

@ -1,24 +0,0 @@
const { PrismaClient } = require("@prisma/client")
const prisma = new PrismaClient()
async function findUser(where, options = {}) {
return prisma.user.findUnique({
where,
include: options.include || undefined,
select: options.select || undefined,
})
}
async function updateUser(where, data, options = {}) {
return prisma.user.update({
where,
data,
include: options.include || undefined,
select: options.select || undefined,
})
}
module.exports = {
findUser,
updateUser,
}

875
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,19 +12,16 @@
"type": "commonjs", "type": "commonjs",
"dependencies": { "dependencies": {
"@prisma/client": "^6.18.0", "@prisma/client": "^6.18.0",
"@supabase/supabase-js": "^2.78.0",
"bcryptjs": "^3.0.2", "bcryptjs": "^3.0.2",
"cors": "^2.8.5", "cors": "^2.8.5",
"express": "^5.1.0", "express": "^5.1.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"multer": "^2.0.2",
"zod": "^4.1.12" "zod": "^4.1.12"
}, },
"devDependencies": { "devDependencies": {
"@types/cors": "^2.8.19", "@types/cors": "^2.8.19",
"@types/express": "^5.0.5", "@types/express": "^5.0.5",
"@types/node": "^24.9.2", "@types/node": "^24.9.2",
"dependency-cruiser": "^17.2.0",
"prisma": "^6.18.0", "prisma": "^6.18.0",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.9.3" "typescript": "^5.9.3"

View File

@ -1,21 +0,0 @@
/*
Warnings:
- You are about to drop the column `imageUrl` on the `Deal` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "Deal" DROP COLUMN "imageUrl";
-- CreateTable
CREATE TABLE "DealImage" (
"id" SERIAL NOT NULL,
"imageUrl" VARCHAR(512) NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"dealId" INTEGER NOT NULL,
CONSTRAINT "DealImage_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "DealImage" ADD CONSTRAINT "DealImage_dealId_fkey" FOREIGN KEY ("dealId") REFERENCES "Deal"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -1,2 +0,0 @@
-- AlterTable
ALTER TABLE "DealImage" ADD COLUMN "order" INTEGER NOT NULL DEFAULT 0;

View File

@ -31,6 +31,7 @@ model Deal {
title String title String
description String? description String?
url String? url String?
imageUrl String?
price Float? price Float?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@ -39,18 +40,9 @@ model Deal {
user User @relation(fields: [userId], references: [id]) user User @relation(fields: [userId], references: [id])
votes DealVote[] votes DealVote[]
comments Comment[] comments Comment[] // ← burası eklendi
images DealImage[] // ← yeni ilişki
} }
model DealImage {
id Int @id @default(autoincrement())
imageUrl String @db.VarChar(512)
order Int @default(0)
createdAt DateTime @default(now())
dealId Int
deal Deal @relation(fields: [dealId], references: [id], onDelete: Cascade)
}
model DealVote { model DealVote {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())

View File

@ -1,40 +0,0 @@
const express = require("express")
const multer = require("multer")
const fs = require("fs")
const { uploadProfileImage } = require("../../services/supabase/supabaseUploadService")
const { validateImage } = require("../../utils/validateImage")
const authMiddleware = require("../../middleware/authMiddleware")
const { updateAvatarUrl, getUserProfile } = require("../../services/profile/myProfileService")
const router = express.Router()
const upload = multer({ dest: "uploads/" })
router.post("/avatar", authMiddleware, upload.single("file"), async (req, res) => {
try {
const file = req.file
if (!file) return res.status(400).json({ error: "No file uploaded" })
validateImage({ mimetype: file.mimetype, size: file.size })
const data = fs.readFileSync(file.path)
const url = await uploadProfileImage(req.user.userId, { data, mimetype: file.mimetype })
fs.unlinkSync(file.path)
const updated = await updateAvatarUrl(req.user.userId, url)
res.json({ message: "Avatar updated", user: updated })
} catch (err) {
console.error(err)
res.status(400).json({ error: err.message })
}
})
router.get("/me", authMiddleware, async (req, res) => {
try {
const user = await getUserProfile(req.user.id)
res.json(user)
} catch (err) {
res.status(400).json({ error: err.message })
}
})
module.exports = router

View File

@ -50,7 +50,7 @@ router.post("/login", async (req, res) => {
res.json({ res.json({
token, token,
user: { id: user.id, username: user.username, email: user.email,avatarUrl:user.avatarUrl }, user: { id: user.id, username: user.username, email: user.email },
}); });
} catch (err) { } catch (err) {
console.error(err); console.error(err);
@ -65,7 +65,7 @@ router.get("/me", authMiddleware, async (req, res) => {
try { try {
const user = await prisma.user.findUnique({ const user = await prisma.user.findUnique({
where: { id: req.user.userId }, where: { id: req.user.userId },
select: { id: true, username: true, email: true,avatarUrl:true }, select: { id: true, username: true, email: true },
}) })
if (!user) return res.status(404).json({ error: "Kullanıcı bulunamadı" }) if (!user) return res.status(404).json({ error: "Kullanıcı bulunamadı" })

View File

@ -1,6 +1,6 @@
const express = require("express") const express = require("express")
const router = express.Router() const router = express.Router()
const { getAllDeals, getDealById, createDeal,searchDeals } = require("../../services/deal/dealService") const { getAllDeals, getDealById, createDeal } = require("../../services/deal/dealService")
const authMiddleware = require("../../middleware/authMiddleware") const authMiddleware = require("../../middleware/authMiddleware")
router.get("/", async (req, res) => { router.get("/", async (req, res) => {
@ -15,25 +15,6 @@ router.get("/", async (req, res) => {
} }
}) })
router.get("/search", async (req, res) => {
try {
const query = req.query.q || ""
const page = Number(req.query.page) || 1
const limit = 10
if (!query.trim()) {
return res.json({ results: [], total: 0, totalPages: 0, page })
}
const data = await searchDeals(query, page, limit)
res.json(data)
} catch (e) {
console.error(e)
res.status(500).json({ error: "Sunucu hatası" })
}
})
router.get("/:id", async (req, res) => { router.get("/:id", async (req, res) => {
try { try {
const deal = await getDealById(req.params.id) const deal = await getDealById(req.params.id)

View File

@ -1,63 +0,0 @@
// routes/profileRoutes.js
const express = require("express")
const { PrismaClient } = require("@prisma/client")
const prisma = new PrismaClient()
const router = express.Router()
// Belirli bir kullanıcının profil detayları
router.get("/:userName", async (req, res) => {
console.log("İstek geldi:", req.params.userName)
try {
const username = req.params.userName
const user = await prisma.user.findUnique({
where: { username: username },
select: {
id: true,
username: true,
avatarUrl: true,
createdAt: true,
},
})
if (!user) return res.status(404).json({ message: "Kullanıcı bulunamadı." })
// Kullanıcının paylaştığı fırsatlar
const deals = await prisma.deal.findMany({
where: { userId: user.id },
orderBy: { createdAt: "desc" },
select: {
id: true,
title: true,
price: true,
createdAt: true,
score: true,
images: {
orderBy: { order: "asc" }, // küçük order en önde
take: 1, // sadece ilk görsel
select: { imageUrl: true },
},
},
})
// Kullanıcının yaptığı yorumlar
const comments = await prisma.comment.findMany({
where: { userId:user.id },
orderBy: { createdAt: "desc" },
select: {
id: true,
text: true,
dealId: true,
createdAt: true,
deal: { select: { title: true } },
},
})
res.json({ user, deals, comments })
} catch (err) {
console.error(err)
res.status(500).json({ message: "Profil bilgileri alınamadı.", error: err.message })
}
})
module.exports = router

View File

@ -1,27 +1,22 @@
const express = require("express") const express = require("express");
const cors = require("cors") const cors = require("cors");
require("dotenv").config() require("dotenv").config();
const userRoutesneedRefactor = require("./routes/userRoutes") const userRoutes = require("./routes/userRoutes");
const dealRoutes = require("./routes/deal/dealRoutes") const dealRoutes = require("./routes/deal/dealRoutes");
const authRoutes = require("./routes/authRoutes") const authRoutes = require("./routes/authRoutes");
const dealVoteRoutes = require("./routes/deal/voteRoutes") const dealVoteRoutes = require("./routes/deal/voteRoutes");
const commentRoutes = require("./routes/deal/commentRoutes") const commentRoutes = require("./routes/deal/commentRoutes")
const accountSettingsRoutes = require("./routes/account/accountSettingsRoutes")
const userRoutes = require("./routes/user/userRoutes")
const app = express();
const app = express() app.use(cors());
app.use(cors()) app.use(express.json());
app.use(express.json())
app.use(express.urlencoded({ extended: true }))
app.use("/api/users", userRoutesneedRefactor) app.use("/api/users", userRoutes);
app.use("/api/deals", dealRoutes) app.use("/api/deals", dealRoutes);
app.use("/api/auth", authRoutes) app.use("/api/auth", authRoutes);
app.use("/api/deal-votes", dealVoteRoutes) app.use("/api/deal-votes", dealVoteRoutes);
app.use("/api/comments", commentRoutes) app.use("/api/comments", commentRoutes)
app.use("/api/account", accountSettingsRoutes)
app.use("/api/user", userRoutes)
app.listen(3000, () => console.log("Server running on http://localhost:3000")) app.listen(3000, () => console.log("Server running on http://localhost:3000"));

View File

@ -1,5 +1,6 @@
const dealDB = require("../../db/deal.db") // services/deal/commentService.js
const commentDB = require("../../db/comment.db") const { PrismaClient } = require("@prisma/client")
const prisma = new PrismaClient()
function assertPositiveInt(v, name = "id") { function assertPositiveInt(v, name = "id") {
const n = Number(v) const n = Number(v)
@ -10,51 +11,56 @@ function assertPositiveInt(v, name = "id") {
async function getCommentsByDealId(dealId) { async function getCommentsByDealId(dealId) {
const id = assertPositiveInt(dealId, "dealId") const id = assertPositiveInt(dealId, "dealId")
const deal = await dealDB.findDeal({ id }) // Deal mevcut mu kontrol et
const deal = await prisma.deal.findUnique({ where: { id } })
if (!deal) throw new Error("Deal bulunamadı.") if (!deal) throw new Error("Deal bulunamadı.")
const include = { user: { select: { username: true, avatarUrl: true } } } return prisma.comment.findMany({
return commentDB.findComments({ dealId: id }, { include }) where: { dealId: id },
include: { user: { select: { username: true } } },
orderBy: { createdAt: "desc" },
})
} }
async function createComment({ dealId, userId, text }) { async function createComment({ dealId, userId, text }) {
// Basit doğrulamalar
const dId = assertPositiveInt(dealId, "dealId") const dId = assertPositiveInt(dealId, "dealId")
const uId = assertPositiveInt(userId, "userId") const uId = assertPositiveInt(userId, "userId")
if (!text || typeof text !== "string" || !text.trim()) if (!text || typeof text !== "string" || !text.trim())
throw new Error("Yorum boş olamaz.") throw new Error("Yorum boş olamaz.")
const deal = await dealDB.findDeal({ id: dId }) // Deal var mı kontrol et
const deal = await prisma.deal.findUnique({ where: { id: dId } })
if (!deal) throw new Error("Deal bulunamadı.") if (!deal) throw new Error("Deal bulunamadı.")
const include = { user: { select: { username: true, avatarUrl: true } } } // (Opsiyonel) Kullanıcı var mı kontrolü (ek güvenlik)
const data = { const user = await prisma.user.findUnique({ where: { id: uId } })
if (!user) throw new Error("Kullanıcı bulunamadı.")
const comment = await prisma.comment.create({
data: {
text: text.trim(), text: text.trim(),
userId: uId, userId: uId,
dealId: dId, dealId: dId,
} },
include: {
user: { select: { username: true } },
},
})
return commentDB.createComment(data, { include }) return comment
} }
async function deleteComment(commentId, userId) { async function deleteComment(commentId, userId) {
const cId = assertPositiveInt(commentId, "commentId") const cId = assertPositiveInt(commentId, "commentId")
const uId = assertPositiveInt(userId, "userId") const uId = assertPositiveInt(userId, "userId")
const comments = await commentDB.findComments( const comment = await prisma.comment.findUnique({ where: { id: cId } })
{ id: cId }, if (!comment) throw new Error("Yorum bulunamadı.")
{ select: { userId: true } } if (comment.userId !== uId) throw new Error("Bu yorumu silme yetkin yok.")
)
if (!comments || comments.length === 0) throw new Error("Yorum bulunamadı.") await prisma.comment.delete({ where: { id: cId } })
if (comments[0].userId !== uId) throw new Error("Bu yorumu silme yetkin yok.")
await commentDB.deleteComment({ id: cId })
return { message: "Yorum silindi." } return { message: "Yorum silindi." }
} }
module.exports = { module.exports = { getCommentsByDealId, createComment, deleteComment }
getCommentsByDealId,
createComment,
deleteComment,
}

View File

@ -1,57 +1,18 @@
const dealDB = require("../../db/deal.db") const { PrismaClient } = require("@prisma/client")
const prisma = new PrismaClient()
async function searchDeals(query, page = 1, limit = 10) {
const skip = (page - 1) * limit
const where = {
OR: [
{ title: { contains: query, mode: "insensitive" } },
{ description: { contains: query, mode: "insensitive" } },
],
}
const [deals, total] = await Promise.all([
dealDB.findDeals(where, {
skip,
take: limit,
orderBy: { createdAt: "desc" },
include: {
user: { select: { username: true } },
images: {
orderBy: { order: "asc" },
take: 1,
select: { imageUrl: true },
},
},
}),
dealDB.countDeals(where),
])
return {
page,
total,
totalPages: Math.ceil(total / limit),
results: deals,
}
}
async function getAllDeals(page = 1, limit = 10) { async function getAllDeals(page = 1, limit = 10) {
const skip = (page - 1) * limit const skip = (page - 1) * limit
const [deals, total] = await Promise.all([ const [deals, total] = await Promise.all([
dealDB.findDeals({}, { prisma.deal.findMany({
skip, skip,
take: limit, take: limit,
include: { user: { select: { username: true } } },
orderBy: { createdAt: "desc" }, orderBy: { createdAt: "desc" },
include: {
user: { select: { username: true } },
images: {
orderBy: { order: "asc" },
take: 1,
select: { imageUrl: true },
},
},
}), }),
dealDB.countDeals(), prisma.deal.count(),
]) ])
return { return {
@ -63,71 +24,68 @@ async function getAllDeals(page = 1, limit = 10) {
} }
async function getDealById(id) { async function getDealById(id) {
return dealDB.findDeal( return prisma.deal.findUnique({
{ id: Number(id) }, where: { id: Number(id) },
{ include: { user: { select: { username: true } } },
include: { })
user: { select: { username: true } },
images: {
orderBy: { order: "asc" },
select: { id: true, imageUrl: true, order: true },
},
},
}
)
} }
async function createDeal(data, userId) { async function createDeal(data, userId) {
const payload = { return prisma.deal.create({
data: {
title: data.title, title: data.title,
description: data.description, description: data.description,
url: data.url, url: data.url,
imageUrl: data.imageUrl,
price: data.price, price: data.price,
user: { connect: { id: userId } }, user: { connect: { id: userId } }, // JWTden gelen userId burada bağlanır
images: data.images?.length },
? { })
create: data.images.map((imgUrl, index) => ({
imageUrl: imgUrl,
order: index,
})),
}
: undefined,
}
return dealDB.createDeal(payload, { include: { images: true } })
} }
async function voteDeal(dealId, userId, voteType) { async function voteDeal(dealId, userId, voteType) {
if (!dealId || !userId || !voteType) throw new Error("Eksik veri") if (!dealId || !userId || !voteType) throw new Error("Eksik veri")
const existingVote = await dealDB.findVotes({ dealId, userId }) const existingVote = await prisma.dealVote.findFirst({
const vote = existingVote[0] where: { dealId, userId },
})
if (vote) { if (existingVote) {
await dealDB.updateVote({ id: vote.id }, { voteType }) await prisma.dealVote.update({
where: { id: existingVote.id },
data: { voteType },
})
} else { } else {
await dealDB.createVote({ dealId, userId, voteType }) await prisma.dealVote.create({
data: { dealId, userId, voteType },
})
} }
const upvotes = await dealDB.countVotes({ dealId, voteType: "UP" }) const upvotes = await prisma.dealVote.count({
const downvotes = await dealDB.countVotes({ dealId, voteType: "DOWN" }) where: { dealId, voteType: "UP" },
})
const downvotes = await prisma.dealVote.count({
where: { dealId, voteType: "DOWN" },
})
const score = upvotes - downvotes const score = upvotes - downvotes
await dealDB.updateDeal({ id: dealId }, { score }) await prisma.deal.update({
where: { id: dealId },
data: { score },
})
return score return score
} }
async function getVotes(dealId) { async function getVotes(dealId) {
const upvotes = await dealDB.countVotes({ dealId: Number(dealId), voteType: "UP" }) const upvotes = await prisma.dealVote.count({
const downvotes = await dealDB.countVotes({ dealId: Number(dealId), voteType: "DOWN" }) where: { dealId: Number(dealId), voteType: "UP" },
})
const downvotes = await prisma.dealVote.count({
where: { dealId: Number(dealId), voteType: "DOWN" },
})
return { upvotes, downvotes, score: upvotes - downvotes } return { upvotes, downvotes, score: upvotes - downvotes }
} }
module.exports = { module.exports = { getAllDeals, getDealById, createDeal, voteDeal, getVotes }
getAllDeals,
getDealById,
createDeal,
voteDeal,
getVotes,
searchDeals,
}

View File

@ -1,33 +0,0 @@
const userDb = require("../../db/user.db")
function assertPositiveInt(v, name = "id") {
const n = Number(v)
if (!Number.isInteger(n) || n <= 0) throw new Error(`Geçersiz ${name}.`)
return n
}
async function updateAvatarUrl(userId, url) {
const id = assertPositiveInt(userId, "userId")
if (!url || typeof url !== "string" || !url.trim())
throw new Error("Geçersiz URL.")
const select = { id: true, username: true, avatarUrl: true }
return userDb.updateUser({ id }, { avatarUrl: url.trim() }, { select })
}
async function getUserProfile(userId) {
const id = assertPositiveInt(userId, "userId")
const select = {
id: true,
username: true,
email: true,
avatarUrl: true,
createdAt: true,
}
return userDb.findUser({ id }, { select })
}
module.exports = {
updateAvatarUrl,
getUserProfile,
}

View File

@ -1,18 +0,0 @@
const { createClient } = require("@supabase/supabase-js")
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY)
async function uploadProfileImage(userId, file) {
const path = `avatars/${userId}_${Date.now()}.jpg`
const { data, error } = await supabase.storage
.from("deal")
.upload(path, file.data, {
contentType: "image/jpeg",
upsert: true,
})
if (error) throw new Error(error.message)
const { data: publicUrl } = supabase.storage.from("avatars").getPublicUrl(path)
return publicUrl.publicUrl
}
module.exports = { uploadProfileImage }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 181 KiB

View File

@ -1,8 +0,0 @@
function validateImage(file) {
if (!file) throw new Error("No file uploaded")
if (!["image/jpeg", "image/jpg"].includes(file.mimetype))
throw new Error("Only JPEG allowed")
if (file.size > 2 * 1024 * 1024)
throw new Error("File too large")
}
module.exports = { validateImage }