Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Temp #1

Open
wants to merge 44 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
03b405b
✨ Feat: 구현 피팅, 사이즈 예측
S686 Jul 1, 2023
a1cca1e
💄 Design : 마이페이지/피팅페이지 UI/UX
Titorima Jul 1, 2023
02288c0
Merge branch 'develop' of 2github.com:AIVLE-School-Third-Big-Project/…
Titorima Jul 1, 2023
60c0f4a
🔀 Merge : develop
Titorima Jul 1, 2023
b6e9eb4
Merge pull request #12 from AIVLE-School-Third-Big-Project/mergeTemp
Titosvarius Jul 1, 2023
04ae33d
🐛 Fix : 수정 app.js - dispatch
Titorima Jul 1, 2023
e33e0b2
Merge pull request #13 from AIVLE-School-Third-Big-Project/mergeTemp
Titosvarius Jul 1, 2023
e8709d7
✨ Feat: 구현 피팅페이지 조회
S686 Jul 1, 2023
f98a544
✨ Feat : 추가 마이페이지 기능
Titorima Jul 2, 2023
e11a600
Merge pull request #14 from AIVLE-School-Third-Big-Project/mergeTemp
Titosvarius Jul 2, 2023
eccf161
✨ Feat : 추가 개인정보 수정 에러 시 상세 알림
Titorima Jul 2, 2023
264b8e5
🔀 Merge!
Titorima Jul 2, 2023
0e86467
Merge branch 'develop' of 2github.com:AIVLE-School-Third-Big-Project/…
Titorima Jul 2, 2023
29499cf
Merge pull request #16 from AIVLE-School-Third-Big-Project/mergeTemp
Titosvarius Jul 2, 2023
7cce166
✨ Feat : 추가 피팅, 사이즈예측 기능
S686 Jul 2, 2023
88ee909
✨ Feat: 추가 의류 업로드(크롤링)
Titorima Jul 2, 2023
6ab01e4
🔀 Merge!
Titorima Jul 2, 2023
a2ac520
Merge pull request #17 from AIVLE-School-Third-Big-Project/mergeTemp
Titosvarius Jul 2, 2023
b552a54
🐛 Fix: 수정 피팅 페이지
Titorima Jul 2, 2023
402c2ea
Merge pull request #18 from AIVLE-School-Third-Big-Project/mergeTemp
Titosvarius Jul 2, 2023
4f192d3
✨ Feat: 의류 업로드 기능 구현
S686 Jul 3, 2023
d4d144f
💄 Design : 피팅 페이지 수정
Titorima Jul 3, 2023
f432cef
🐛 Fix: allowedOrigins
Titorima Jul 3, 2023
28f6302
🔀 Merge!
Titorima Jul 3, 2023
ff893b2
Merge pull request #19 from AIVLE-School-Third-Big-Project/mergeTemp
Titosvarius Jul 3, 2023
f6a4bc6
🐛 Fix: 수정 회원가입 (RegisterUser.js)
Titorima Jul 3, 2023
1c994f5
Merge branch 'develop' of 2github.com:AIVLE-School-Third-Big-Project/…
Titorima Jul 3, 2023
6fe4847
🔀 Merge!
Titorima Jul 3, 2023
801f9c8
Merge pull request #20 from AIVLE-School-Third-Big-Project/mergeTemp
Titosvarius Jul 3, 2023
3c7c679
✨ Feat: 추가 마이페이지 기능
Titorima Jul 3, 2023
050b3ab
🐛 Fix: 수정 마이페이지
Titorima Jul 3, 2023
46e5d9e
Merge pull request #21 from AIVLE-School-Third-Big-Project/mergeTemp
Titosvarius Jul 3, 2023
aafd9d6
✨ Feat: 피팅 및 의류 삭제 기능 구현
S686 Jul 3, 2023
b5cac30
Merge branch 'develop' of https://github.com/AIVLE-School-Third-Big-P…
S686 Jul 3, 2023
381e626
🐛 Fix: 수정 자잘한 오류
Titorima Jul 3, 2023
027332d
Merge pull request #22 from AIVLE-School-Third-Big-Project/mergeTemp
Titosvarius Jul 3, 2023
233eee1
🐛 Fix: 수정 크롤링
Titorima Jul 3, 2023
01dea77
✨ Feat: 구현 회원가입 검증 기능
S686 Jul 3, 2023
2010c4f
✨ Feat: 이메일인증 API 추가
Titorima Jul 3, 2023
d70ed53
Merge pull request #23 from AIVLE-School-Third-Big-Project/mergeTemp
Titosvarius Jul 3, 2023
f1f5c08
🐛 Fix: 수정 피팅 및 푸터
S686 Jul 3, 2023
abe327d
💄 Design: 수정 마이페이지
Titorima Jul 3, 2023
f56f8da
Merge pull request #24 from AIVLE-School-Third-Big-Project/mergeTemp
Titosvarius Jul 3, 2023
4dea947
:sparkles:: 추가 피팅 페이지
Oneul-hyeon Jul 4, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"axios": "^1.4.0",
"bcrypt": "^5.1.0",
"bull": "^4.10.4",
"cheerio": "^1.0.0-rc.12",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2",
Expand All @@ -31,6 +32,7 @@
"mongoose": "^7.3.0",
"multer": "^1.4.5-lts.1",
"multer-s3": "^3.0.1",
"nodemailer": "^6.9.3",
"passport": "^0.6.0",
"passport-local": "^1.0.0"
}
Expand Down
26 changes: 22 additions & 4 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ const aiAPI = process.env.AI_API

const allowedOrigins = [
'http://localhost:3000',
'http://www.model-fit.kro.kr',
'http://localhost:3001',
process.env.WEB_API,
process.env.WEB_IP,
aiAPI,
]

Expand Down Expand Up @@ -60,13 +62,29 @@ app.use(express.static(path.join(__dirname, '../style/build')))
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, '../style/build/index.html'))
})
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, '../style/build/index.html'))
})

const usersRouter = require('./routes/RegisterUser')
const loginRouter = require('./routes/LoginUser')
const logoutRouter = require('./routes/LogoutUser')
app.use('/users', usersRouter)
app.use('/users', loginRouter)
app.use('/users', logoutRouter)

const authRouter = require('./routes/MailAuthRouter')
app.use('/auth', authRouter)

// userInfo 조회
const infoRouter = require('./routes/UserInfo')
app.use('/userInfo', infoRouter)

const fittingRouter = require('./routes/FittingImage')
const clothUploadRouter = require('./routes/UploadClothImage')
app.use('/api', fittingRouter)
app.use('/api', clothUploadRouter)

const clothRouter = require('./routes/Clothes')
app.use('/cloth', clothRouter)

app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, '../style/build/index.html'))
})
11 changes: 11 additions & 0 deletions src/models/Closet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const mongoose = require('mongoose')
const { Schema } = mongoose

const closetSchema = new Schema({
userId: { type: Schema.Types.ObjectId, ref: 'User', required: true },
clothesUrl: { type: String, required: true },
clothesImageLink: { type: String, required: true },
fittingImageLink: { type: String },
})

module.exports = mongoose.model('Closet', closetSchema)
8 changes: 4 additions & 4 deletions src/models/SizeProfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ const mongoose = require('mongoose')
const { Schema } = mongoose

const sizeProfileSchema = new Schema({
userId: { type: Schema.Types.ObjectId, ref: 'User' },
length: { type: Number },
shoulderWidth: { type: Number },
chestWidth: { type: Number },
userId: { type: Schema.Types.ObjectId, ref: 'User', required: true },
length: { type: Number, required: true },
shoulderWidth: { type: Number, required: true },
chestWidth: { type: Number, required: true },
})

module.exports = mongoose.model('SizeProfile', sizeProfileSchema)
3 changes: 2 additions & 1 deletion src/models/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ const userSchema = new Schema({
gender: { type: String, required: true },
height: { type: Number, required: true },
weight: { type: Number, required: true },
file: { type: String },
file: { type: String, required: true },
favoriteStyle: {
style: { type: String, default: '' },
color: { type: String, default: '' },
fit: { type: String, default: '정핏' },
},
sizeProfile: { type: Schema.Types.ObjectId, ref: 'SizeProfile' },
clothes: [{ type: Schema.Types.ObjectId, ref: 'Clothes' }],
})

module.exports = mongoose.model('User', userSchema)
43 changes: 43 additions & 0 deletions src/modules/AuthMail/AuthMail.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const nodemailer = require('nodemailer')
const generateAuthCode = require('./generateAuthCode')

/*
구글에서는 from 바꾸는 부분 동작하지 않음
참고 : https://nodemailer.com/usage/using-gmail/
*/
const noreply = '[email protected]'

async function sendAuthMail(email) {
try {
// SMTP 전송을 위한 transporter 생성
const transporter = nodemailer.createTransport({
service: 'Gmail',
auth: {
user: process.env.AUTH_BOT_MAIL,
pass: process.env.AUTH_BOT_PASS,
},
})

// 이메일 옵션 설정
const code = generateAuthCode()
const mailOptions = {
from: `AuthBot<${noreply}>`,
to: email,
subject: 'The authorization code for model.fit.',
text: `code : ${code}`,
}

// 이메일 전송
const info = await transporter.sendMail(mailOptions)
console.log(`Email sent successfully : ${email}`)
return {
authCode: code,
result: { success: true, code: 'SEND_DONE', errno: 0 },
}
} catch (error) {
console.error(`Email sent failed : ${email}`)
return { success: false, code: error.code, errno: -1 }
}
}

module.exports = sendAuthMail
13 changes: 13 additions & 0 deletions src/modules/AuthMail/generateAuthCode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const crypto = require('crypto')

// 6자리의 숫자로만 구성된 난수 생성
function generateAuthCode() {
const byteLength = 4

const buffer = crypto.randomBytes(byteLength)
const randomCode = parseInt(buffer.toString('hex'), 16).toString().slice(0, 6)

return randomCode
}

module.exports = generateAuthCode
Binary file added src/routes/.FittingImage.js.swp
Binary file not shown.
67 changes: 67 additions & 0 deletions src/routes/Clothes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
const express = require('express')
const router = express.Router()
const Clothes = require('../models/Closet')
const User = require('../models/User')
const { getClothesImageUrls } = require('./crawling')
const { deleteImageFromS3 } = require('./ImageUploader')

router.get('/api/clothes', async (req, res) => {
const { userId } = req.query
try {
const user = await User.findById(userId)
if (!user) {
return res
.status(404)
.json({ success: false, message: 'The user was not found.' })
}
const clothes = await Clothes.find({ userId: userId })
res.json(
clothes.map((cloth) => ({
clothesImageLink: cloth.clothesImageLink,
_id: cloth._id,
}))
)
} catch (err) {
console.error(err.message)
res.status(500).send('Server Error')
}
})

// 입력받은 url에서 이미지 링크 검색 후 반환
router.get('/api/clothesFromUrl', async (req, res) => {
const { url } = req.query

const result = await getClothesImageUrls(url)
if (result.success) {
console.log('success')
return res.status(200).json(result)
} else {
return res.status(500).json(result)
}
})

router.delete('/api/delete/:id', async (req, res) => {
const clothId = req.params.id
try {
const cloth = await Clothes.findById(clothId)
if (!cloth) {
return res.status(404).json({ error: 'No cloth found for given id' })
}
if (cloth.clothesImageLink && cloth.clothesImageLink.trim() !== '') {
console.log(cloth.clothesImageLink)
await deleteImageFromS3(cloth.clothesImageLink)
}

if (cloth.fittingImageLink && cloth.fittingImageLink.trim() !== '') {
console.log(cloth.fittingImageLink)
await deleteImageFromS3(cloth.fittingImageLink)
}

await Clothes.findByIdAndDelete(clothId)
res.status(200).json({ message: 'Cloth deleted successfully' })
} catch (error) {
res.status(400).json({ error: 'Error while deleting the cloth' })
}
})

module.exports = router
92 changes: 92 additions & 0 deletions src/routes/FittingImage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
const express = require('express')
const router = express.Router()
const axios = require('axios')
const https = require('https')
const User = require('../models/User')
const Clothes = require('../models/Closet')
const fittingApiUrl = process.env.AI_FITTING_API_URL

async function isImageValid(imageUrl) {
return new Promise((resolve, reject) => {
const options = {
method: 'HEAD',
url: imageUrl,
};

const req = https.request(imageUrl, options, (res) => {
if (res.statusCode === 200) {
resolve(true);
} else {
resolve(false);
}
});

req.on('error', (error) => {
console.error(`Error checking image URL: ${imageUrl}`, error);
resolve(false);
});

req.end();
});
}
router.post('/fitting', async (req, res) => {
try {
const { userId, clothID } = req.body

// 사용자 이미지 가져오기
const user = await User.findOne({ _id: userId })
if (!user) {
return res.status(404).json({ error: 'User not found' })
}
const userImageUrl = user.file
const cloth = await Clothes.findOne({ _id: clothID })
if (!cloth) {
return res.status(404).json({ error: 'Cloth not found' })
}
const clothImageUrl = cloth.clothesImageLink

// 이미 피팅이미지가 있으면
if (cloth && cloth.fittingImageLink) {
const isValid = await isImageValid(cloth.fittingImageLink)
if (isValid) {
return res
.status(200)
.json({ fittingImageLink: cloth.fittingImageLink })
} else {
console.warn(
'Stored fittingImageLink is not valid. Fetching new image.'
)
}
}
const response = await axios.post(fittingApiUrl, null, {
headers: {
'Content-Type': 'application/json',
},
params: {
ID: userId,
image_url: userImageUrl,
cloth_url: clothImageUrl,
},
})
if (response.data.success) {
console.log('Fitting request sent successfully.')

const imageUrl = response.data.file_name
await Clothes.updateOne(
{ userId, clothesImageLink: clothImageUrl },
{ $set: { fittingImageLink: imageUrl } }
)

res.status(200).json({ fittingImageLink: imageUrl })
} else {
console.log(error.response)
console.error('Error:', response.data.error)
res.status(500).json({ error: response.data.error })
}
} catch (error) {
console.error('Fitting Request Error:', error)
res.status(500).json({ error: 'Fitting Request Error' })
}
})

module.exports = router
47 changes: 42 additions & 5 deletions src/routes/ImageUploader.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ const aws = require('@aws-sdk/client-s3')
const multer = require('multer')
const multers3 = require('multer-s3')
const path = require('path')
const { ObjectId } = require('mongodb')
const url = require('url')

const { S3Client, PutObjectCommand } = aws
const { S3Client, PutObjectCommand, DeleteObjectCommand } = aws

const s3 = new S3Client({
region: 'us-east-2',
Expand All @@ -26,13 +28,32 @@ const ImageUploader = multer({
s3: s3,
bucket: 'bigprogect-bucket',
key: (req, file, callback) => {
const uploadDirectory = req.query.directory ?? 'images'
/*
회원가입 시 신체 이미지 업로드 하면 아직 DB에 컬럼이 없음.
=> userId 필드는 무조건 undefined가 됨.
=> s3/undefined/에 이미지가 저장 됨
=> id를 생성할 수 있게 [ ?? new ObjectId().toString() ] 추가
*/
const userId =
(req.body.userId || req.query.userId) ?? new ObjectId().toString()
const uploadDirectory = req.query.directory ?? userId
const extension = path.extname(file.originalname)
if (!allowedExtensions.includes(extension)) {
return callback(new Error('wrong extension'))
}
const userId = req.query.userId
callback(null, `${uploadDirectory}/${userId}/userimage${extension}`)

const imageType = req.query.type
let imageFilename

if (imageType === 'body') {
imageFilename = `userimage${extension}`
} else if (imageType === 'clothing') {
imageFilename = file.originalname
} else {
return callback(new Error('Unknown image type'))
}

callback(null, `${uploadDirectory}/${imageFilename}`)
},
acl: 'public-read-write',
}),
Expand All @@ -42,4 +63,20 @@ const ImageUploader = multer({
},
})

module.exports = ImageUploader
// 이미지 삭제
const deleteImageFromS3 = async (imageUrl) => {
const imageKey = decodeURIComponent(new URL(imageUrl).pathname.substring(1))
const params = {
Bucket: 'bigprogect-bucket',
Key: imageKey,
}

try {
const deleteObject = new DeleteObjectCommand(params)
await s3.send(deleteObject)
} catch (error) {
console.error('Error deleting image from S3:', error)
}
}

module.exports = { ImageUploader, deleteImageFromS3 }
Loading