Compare commits
2 Commits
8ed231c5f2
...
e966158d37
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e966158d37 | ||
|
|
d9ca95470f |
3
db/client.js
Normal file
3
db/client.js
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
const { PrismaClient } = require("@prisma/client")
|
||||||
|
const prisma = new PrismaClient()
|
||||||
|
module.exports = prisma
|
||||||
28
db/comment.db.js
Normal file
28
db/comment.db.js
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
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,
|
||||||
|
}
|
||||||
82
db/deal.db.js
Normal file
82
db/deal.db.js
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
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,
|
||||||
|
}
|
||||||
24
db/user.db.js
Normal file
24
db/user.db.js
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
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
875
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
|
@ -12,16 +12,19 @@
|
||||||
"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"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
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;
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "DealImage" ADD COLUMN "order" INTEGER NOT NULL DEFAULT 0;
|
||||||
|
|
@ -31,18 +31,26 @@ 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
|
||||||
userId Int
|
userId Int
|
||||||
score Int @default(0)
|
score Int @default(0)
|
||||||
|
|
||||||
user User @relation(fields: [userId], references: [id])
|
user User @relation(fields: [userId], references: [id])
|
||||||
votes DealVote[]
|
votes DealVote[]
|
||||||
comments Comment[] // ← burası eklendi
|
comments Comment[]
|
||||||
|
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())
|
||||||
|
|
|
||||||
40
routes/account/accountSettingsRoutes.js
Normal file
40
routes/account/accountSettingsRoutes.js
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
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
|
||||||
|
|
@ -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 },
|
user: { id: user.id, username: user.username, email: user.email,avatarUrl:user.avatarUrl },
|
||||||
});
|
});
|
||||||
} 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 },
|
select: { id: true, username: true, email: true,avatarUrl:true },
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!user) return res.status(404).json({ error: "Kullanıcı bulunamadı" })
|
if (!user) return res.status(404).json({ error: "Kullanıcı bulunamadı" })
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
const express = require("express")
|
const express = require("express")
|
||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
const { getAllDeals, getDealById, createDeal } = require("../../services/deal/dealService")
|
const { getAllDeals, getDealById, createDeal,searchDeals } = 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,6 +15,25 @@ 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)
|
||||||
|
|
|
||||||
63
routes/user/userRoutes.js
Normal file
63
routes/user/userRoutes.js
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
// 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
|
||||||
35
server.js
35
server.js
|
|
@ -1,22 +1,27 @@
|
||||||
const express = require("express");
|
const express = require("express")
|
||||||
const cors = require("cors");
|
const cors = require("cors")
|
||||||
require("dotenv").config();
|
require("dotenv").config()
|
||||||
|
|
||||||
const userRoutes = require("./routes/userRoutes");
|
const userRoutesneedRefactor = 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();
|
|
||||||
|
|
||||||
app.use(cors());
|
const app = express()
|
||||||
app.use(express.json());
|
app.use(cors())
|
||||||
|
app.use(express.json())
|
||||||
|
app.use(express.urlencoded({ extended: true }))
|
||||||
|
|
||||||
app.use("/api/users", userRoutes);
|
app.use("/api/users", userRoutesneedRefactor)
|
||||||
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"))
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
// services/deal/commentService.js
|
const dealDB = require("../../db/deal.db")
|
||||||
const { PrismaClient } = require("@prisma/client")
|
const commentDB = require("../../db/comment.db")
|
||||||
const prisma = new PrismaClient()
|
|
||||||
|
|
||||||
function assertPositiveInt(v, name = "id") {
|
function assertPositiveInt(v, name = "id") {
|
||||||
const n = Number(v)
|
const n = Number(v)
|
||||||
|
|
@ -11,56 +10,51 @@ function assertPositiveInt(v, name = "id") {
|
||||||
async function getCommentsByDealId(dealId) {
|
async function getCommentsByDealId(dealId) {
|
||||||
const id = assertPositiveInt(dealId, "dealId")
|
const id = assertPositiveInt(dealId, "dealId")
|
||||||
|
|
||||||
// Deal mevcut mu kontrol et
|
const deal = await dealDB.findDeal({ id })
|
||||||
const deal = await prisma.deal.findUnique({ where: { id } })
|
|
||||||
if (!deal) throw new Error("Deal bulunamadı.")
|
if (!deal) throw new Error("Deal bulunamadı.")
|
||||||
|
|
||||||
return prisma.comment.findMany({
|
const include = { user: { select: { username: true, avatarUrl: true } } }
|
||||||
where: { dealId: id },
|
return commentDB.findComments({ dealId: id }, { include })
|
||||||
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.")
|
||||||
|
|
||||||
// Deal var mı kontrol et
|
const deal = await dealDB.findDeal({ id: dId })
|
||||||
const deal = await prisma.deal.findUnique({ where: { id: dId } })
|
|
||||||
if (!deal) throw new Error("Deal bulunamadı.")
|
if (!deal) throw new Error("Deal bulunamadı.")
|
||||||
|
|
||||||
// (Opsiyonel) Kullanıcı var mı kontrolü (ek güvenlik)
|
const include = { user: { select: { username: true, avatarUrl: true } } }
|
||||||
const user = await prisma.user.findUnique({ where: { id: uId } })
|
const data = {
|
||||||
if (!user) throw new Error("Kullanıcı bulunamadı.")
|
text: text.trim(),
|
||||||
|
userId: uId,
|
||||||
|
dealId: dId,
|
||||||
|
}
|
||||||
|
|
||||||
const comment = await prisma.comment.create({
|
return commentDB.createComment(data, { include })
|
||||||
data: {
|
|
||||||
text: text.trim(),
|
|
||||||
userId: uId,
|
|
||||||
dealId: dId,
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
user: { select: { username: true } },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
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 comment = await prisma.comment.findUnique({ where: { id: cId } })
|
const comments = await commentDB.findComments(
|
||||||
if (!comment) throw new Error("Yorum bulunamadı.")
|
{ id: cId },
|
||||||
if (comment.userId !== uId) throw new Error("Bu yorumu silme yetkin yok.")
|
{ select: { userId: true } }
|
||||||
|
)
|
||||||
|
|
||||||
await prisma.comment.delete({ where: { id: cId } })
|
if (!comments || comments.length === 0) throw new Error("Yorum bulunamadı.")
|
||||||
|
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 = { getCommentsByDealId, createComment, deleteComment }
|
module.exports = {
|
||||||
|
getCommentsByDealId,
|
||||||
|
createComment,
|
||||||
|
deleteComment,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,57 @@
|
||||||
const { PrismaClient } = require("@prisma/client")
|
const dealDB = require("../../db/deal.db")
|
||||||
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([
|
||||||
prisma.deal.findMany({
|
dealDB.findDeals({}, {
|
||||||
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 },
|
||||||
|
},
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
prisma.deal.count(),
|
dealDB.countDeals(),
|
||||||
])
|
])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -24,68 +63,71 @@ async function getAllDeals(page = 1, limit = 10) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getDealById(id) {
|
async function getDealById(id) {
|
||||||
return prisma.deal.findUnique({
|
return dealDB.findDeal(
|
||||||
where: { id: Number(id) },
|
{ 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) {
|
||||||
return prisma.deal.create({
|
const payload = {
|
||||||
data: {
|
title: data.title,
|
||||||
title: data.title,
|
description: data.description,
|
||||||
description: data.description,
|
url: data.url,
|
||||||
url: data.url,
|
price: data.price,
|
||||||
imageUrl: data.imageUrl,
|
user: { connect: { id: userId } },
|
||||||
price: data.price,
|
images: data.images?.length
|
||||||
user: { connect: { id: userId } }, // JWT’den gelen userId burada bağlanır
|
? {
|
||||||
},
|
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 prisma.dealVote.findFirst({
|
const existingVote = await dealDB.findVotes({ dealId, userId })
|
||||||
where: { dealId, userId },
|
const vote = existingVote[0]
|
||||||
})
|
|
||||||
|
|
||||||
if (existingVote) {
|
if (vote) {
|
||||||
await prisma.dealVote.update({
|
await dealDB.updateVote({ id: vote.id }, { voteType })
|
||||||
where: { id: existingVote.id },
|
|
||||||
data: { voteType },
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
await prisma.dealVote.create({
|
await dealDB.createVote({ dealId, userId, voteType })
|
||||||
data: { dealId, userId, voteType },
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const upvotes = await prisma.dealVote.count({
|
const upvotes = await dealDB.countVotes({ dealId, voteType: "UP" })
|
||||||
where: { dealId, voteType: "UP" },
|
const downvotes = await dealDB.countVotes({ dealId, voteType: "DOWN" })
|
||||||
})
|
|
||||||
const downvotes = await prisma.dealVote.count({
|
|
||||||
where: { dealId, voteType: "DOWN" },
|
|
||||||
})
|
|
||||||
|
|
||||||
const score = upvotes - downvotes
|
const score = upvotes - downvotes
|
||||||
|
|
||||||
await prisma.deal.update({
|
await dealDB.updateDeal({ id: dealId }, { score })
|
||||||
where: { id: dealId },
|
|
||||||
data: { score },
|
|
||||||
})
|
|
||||||
|
|
||||||
return score
|
return score
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getVotes(dealId) {
|
async function getVotes(dealId) {
|
||||||
const upvotes = await prisma.dealVote.count({
|
const upvotes = await dealDB.countVotes({ dealId: Number(dealId), voteType: "UP" })
|
||||||
where: { dealId: Number(dealId), voteType: "UP" },
|
const downvotes = await dealDB.countVotes({ dealId: Number(dealId), voteType: "DOWN" })
|
||||||
})
|
|
||||||
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 = { getAllDeals, getDealById, createDeal, voteDeal, getVotes }
|
module.exports = {
|
||||||
|
getAllDeals,
|
||||||
|
getDealById,
|
||||||
|
createDeal,
|
||||||
|
voteDeal,
|
||||||
|
getVotes,
|
||||||
|
searchDeals,
|
||||||
|
}
|
||||||
|
|
|
||||||
33
services/profile/myProfileService.js
Normal file
33
services/profile/myProfileService.js
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
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,
|
||||||
|
}
|
||||||
18
services/supabase/supabaseUploadService.js
Normal file
18
services/supabase/supabaseUploadService.js
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
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 }
|
||||||
BIN
tmp/tmp-1-395921762140416222
Normal file
BIN
tmp/tmp-1-395921762140416222
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
BIN
uploads/5c08faec103653f19a646741f3bfea38
Normal file
BIN
uploads/5c08faec103653f19a646741f3bfea38
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
BIN
uploads/a3dee70f3e136c85aa2488e1ae88e6fb
Normal file
BIN
uploads/a3dee70f3e136c85aa2488e1ae88e6fb
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
BIN
uploads/b92a8821204d94524d33dafbb8097a6c
Normal file
BIN
uploads/b92a8821204d94524d33dafbb8097a6c
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
BIN
uploads/bf18a45730e75fdd52513afadbaa26f8
Normal file
BIN
uploads/bf18a45730e75fdd52513afadbaa26f8
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
BIN
uploads/df9e3eabfe7cbe5b7f1c426af0007a80
Normal file
BIN
uploads/df9e3eabfe7cbe5b7f1c426af0007a80
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 181 KiB |
8
utils/validateImage.js
Normal file
8
utils/validateImage.js
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
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 }
|
||||||
Loading…
Reference in New Issue
Block a user