- Uploading Avatars - Created Image array

This commit is contained in:
cureb 2025-11-03 20:56:14 +00:00
parent 8ed231c5f2
commit d9ca95470f
18 changed files with 1041 additions and 25 deletions

875
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@ -31,7 +31,6 @@ model Deal {
title String
description String?
url String?
imageUrl String?
price Float?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@ -40,9 +39,18 @@ model Deal {
user User @relation(fields: [userId], references: [id])
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 {
id Int @id @default(autoincrement())

View 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

View File

@ -50,7 +50,7 @@ router.post("/login", async (req, res) => {
res.json({
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) {
console.error(err);

View File

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

View File

@ -9,8 +9,15 @@ async function getAllDeals(page = 1, limit = 10) {
prisma.deal.findMany({
skip,
take: limit,
include: { user: { select: { username: true } } },
orderBy: { createdAt: "desc" },
include: {
user: { select: { username: true } },
images: {
orderBy: { order: "asc" },
take: 1, // sadece kapak fotoğrafı
select: { imageUrl: true },
},
},
}),
prisma.deal.count(),
])
@ -26,10 +33,18 @@ async function getAllDeals(page = 1, limit = 10) {
async function getDealById(id) {
return prisma.deal.findUnique({
where: { id: Number(id) },
include: { user: { select: { username: true } } },
include: {
user: { select: { username: true } },
images: {
orderBy: { order: "asc" }, // tüm fotoğrafları sırayla getir
select: { id: true, imageUrl: true, order: true },
},
},
})
}
async function createDeal(data, userId) {
return prisma.deal.create({
data: {

View File

@ -0,0 +1,28 @@
const { PrismaClient } = require("@prisma/client")
const prisma = new PrismaClient()
async function updateAvatarUrl(userId, url) {
return await prisma.user.update({
where: { id: userId },
data: { avatarUrl: url },
select: { id: true, username: true, avatarUrl: true },
})
}
async function getUserProfile(userId) {
return await prisma.user.findUnique({
where: { id: userId },
select: {
id: true,
username: true,
email: true,
avatarUrl: true,
createdAt: true,
},
})
}
module.exports = {
updateAvatarUrl,
getUserProfile,
}

View 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("avatars")
.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.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

8
utils/validateImage.js Normal file
View 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 }