- Uploading Avatars - Created Image array
This commit is contained in:
parent
8ed231c5f2
commit
d9ca95470f
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);
|
||||||
|
|
|
||||||
34
server.js
34
server.js
|
|
@ -1,22 +1,24 @@
|
||||||
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 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 app = express();
|
const app = express()
|
||||||
|
app.use(cors())
|
||||||
|
app.use(express.json())
|
||||||
|
app.use(express.urlencoded({ extended: true }))
|
||||||
|
|
||||||
app.use(cors());
|
app.use("/api/users", userRoutes)
|
||||||
app.use(express.json());
|
app.use("/api/deals", dealRoutes)
|
||||||
|
app.use("/api/auth", authRoutes)
|
||||||
app.use("/api/users", userRoutes);
|
app.use("/api/deal-votes", dealVoteRoutes)
|
||||||
app.use("/api/deals", dealRoutes);
|
|
||||||
app.use("/api/auth", authRoutes);
|
|
||||||
app.use("/api/deal-votes", dealVoteRoutes);
|
|
||||||
app.use("/api/comments", commentRoutes)
|
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"))
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,15 @@ async function getAllDeals(page = 1, limit = 10) {
|
||||||
prisma.deal.findMany({
|
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, // sadece kapak fotoğrafı
|
||||||
|
select: { imageUrl: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
prisma.deal.count(),
|
prisma.deal.count(),
|
||||||
])
|
])
|
||||||
|
|
@ -26,10 +33,18 @@ async function getAllDeals(page = 1, limit = 10) {
|
||||||
async function getDealById(id) {
|
async function getDealById(id) {
|
||||||
return prisma.deal.findUnique({
|
return prisma.deal.findUnique({
|
||||||
where: { id: Number(id) },
|
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) {
|
async function createDeal(data, userId) {
|
||||||
return prisma.deal.create({
|
return prisma.deal.create({
|
||||||
data: {
|
data: {
|
||||||
|
|
|
||||||
28
services/profile/myProfileService.js
Normal file
28
services/profile/myProfileService.js
Normal 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,
|
||||||
|
}
|
||||||
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("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 }
|
||||||
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