Skip to content
Snippets Groups Projects
Commit 8f843188 authored by MinJae Kwon's avatar MinJae Kwon
Browse files

feat: implement user APIs

parent 01e5bbc0
No related branches found
No related tags found
No related merge requests found
Pipeline #10456 failed
...@@ -34,6 +34,10 @@ services: ...@@ -34,6 +34,10 @@ services:
MYSQL_PASSWORD: localadmin MYSQL_PASSWORD: localadmin
ports: ports:
- "8180:8080" - "8180:8080"
volumes:
- ./webapp/backend:/app
depends_on: depends_on:
- mysql - mysql
command: sh -c "until nc -z mysql 3306; do echo Waiting for MySQL; sleep 2; done && node apiserver/app.js" command: sh -c "until nc -z mysql 3306; do echo Waiting for MySQL; sleep 2; done && npx nodemon -L apiserver/app.js"
\ No newline at end of file
# TODO(minjae): add Redis, Object Storage, etc.
{
"name": "crewup",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"argon2": "^0.41.1",
"jsonwebtoken": "^9.0.2",
"sequelize": "^6.37.5"
}
},
"node_modules/@phc/format": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@phc/format/-/format-1.0.0.tgz",
"integrity": "sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/@types/debug": {
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
"integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
"license": "MIT",
"dependencies": {
"@types/ms": "*"
}
},
"node_modules/@types/ms": {
"version": "0.7.34",
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz",
"integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==",
"license": "MIT"
},
"node_modules/@types/node": {
"version": "22.10.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz",
"integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.20.0"
}
},
"node_modules/@types/validator": {
"version": "13.12.2",
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.12.2.tgz",
"integrity": "sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==",
"license": "MIT"
},
"node_modules/argon2": {
"version": "0.41.1",
"resolved": "https://registry.npmjs.org/argon2/-/argon2-0.41.1.tgz",
"integrity": "sha512-dqCW8kJXke8Ik+McUcMDltrbuAWETPyU6iq+4AhxqKphWi7pChB/Zgd/Tp/o8xRLbg8ksMj46F/vph9wnxpTzQ==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@phc/format": "^1.0.0",
"node-addon-api": "^8.1.0",
"node-gyp-build": "^4.8.1"
},
"engines": {
"node": ">=16.17.0"
}
},
"node_modules/buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
"license": "BSD-3-Clause"
},
"node_modules/debug": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/dottie": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.6.tgz",
"integrity": "sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA==",
"license": "MIT"
},
"node_modules/ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"license": "Apache-2.0",
"dependencies": {
"safe-buffer": "^5.0.1"
}
},
"node_modules/inflection": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/inflection/-/inflection-1.13.4.tgz",
"integrity": "sha512-6I/HUDeYFfuNCVS3td055BaXBwKYuzw7K3ExVMStBowKo9oOAMJIXIHvdyR3iboTCp1b+1i5DSkIZTcwIktuDw==",
"engines": [
"node >= 0.4.0"
],
"license": "MIT"
},
"node_modules/jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
"license": "MIT",
"dependencies": {
"jws": "^3.2.2",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
"lodash.isnumber": "^3.0.3",
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.once": "^4.0.0",
"ms": "^2.1.1",
"semver": "^7.5.4"
},
"engines": {
"node": ">=12",
"npm": ">=6"
}
},
"node_modules/jwa": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
"integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
"license": "MIT",
"dependencies": {
"buffer-equal-constant-time": "1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"node_modules/jws": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
"license": "MIT",
"dependencies": {
"jwa": "^1.4.1",
"safe-buffer": "^5.0.1"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"license": "MIT"
},
"node_modules/lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
"license": "MIT"
},
"node_modules/lodash.isboolean": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
"license": "MIT"
},
"node_modules/lodash.isinteger": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
"license": "MIT"
},
"node_modules/lodash.isnumber": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
"license": "MIT"
},
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
"license": "MIT"
},
"node_modules/lodash.isstring": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
"license": "MIT"
},
"node_modules/lodash.once": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
"license": "MIT"
},
"node_modules/moment": {
"version": "2.30.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/moment-timezone": {
"version": "0.5.46",
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.46.tgz",
"integrity": "sha512-ZXm9b36esbe7OmdABqIWJuBBiLLwAjrN7CE+7sYdCCx82Nabt1wHDj8TVseS59QIlfFPbOoiBPm6ca9BioG4hw==",
"license": "MIT",
"dependencies": {
"moment": "^2.29.4"
},
"engines": {
"node": "*"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/node-addon-api": {
"version": "8.2.2",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.2.2.tgz",
"integrity": "sha512-9emqXAKhVoNrQ792nLI/wpzPpJ/bj/YXxW0CvAau1+RdGBcCRF1Dmz7719zgVsQNrzHl9Tzn3ImZ4qWFarWL0A==",
"license": "MIT",
"engines": {
"node": "^18 || ^20 || >= 21"
}
},
"node_modules/node-gyp-build": {
"version": "4.8.4",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
"integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
"license": "MIT",
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
"node-gyp-build-test": "build-test.js"
}
},
"node_modules/pg-connection-string": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.7.0.tgz",
"integrity": "sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==",
"license": "MIT"
},
"node_modules/retry-as-promised": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-7.0.4.tgz",
"integrity": "sha512-XgmCoxKWkDofwH8WddD0w85ZfqYz+ZHlr5yo+3YUCfycWawU56T5ckWXsScsj5B8tqUcIG67DxXByo3VUgiAdA==",
"license": "MIT"
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/semver": {
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/sequelize": {
"version": "6.37.5",
"resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.37.5.tgz",
"integrity": "sha512-10WA4poUb3XWnUROThqL2Apq9C2NhyV1xHPMZuybNMCucDsbbFuKg51jhmyvvAUyUqCiimwTZamc3AHhMoBr2Q==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/sequelize"
}
],
"license": "MIT",
"dependencies": {
"@types/debug": "^4.1.8",
"@types/validator": "^13.7.17",
"debug": "^4.3.4",
"dottie": "^2.0.6",
"inflection": "^1.13.4",
"lodash": "^4.17.21",
"moment": "^2.29.4",
"moment-timezone": "^0.5.43",
"pg-connection-string": "^2.6.1",
"retry-as-promised": "^7.0.4",
"semver": "^7.5.4",
"sequelize-pool": "^7.1.0",
"toposort-class": "^1.0.1",
"uuid": "^8.3.2",
"validator": "^13.9.0",
"wkx": "^0.5.0"
},
"engines": {
"node": ">=10.0.0"
},
"peerDependenciesMeta": {
"ibm_db": {
"optional": true
},
"mariadb": {
"optional": true
},
"mysql2": {
"optional": true
},
"oracledb": {
"optional": true
},
"pg": {
"optional": true
},
"pg-hstore": {
"optional": true
},
"snowflake-sdk": {
"optional": true
},
"sqlite3": {
"optional": true
},
"tedious": {
"optional": true
}
}
},
"node_modules/sequelize-pool": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-7.1.0.tgz",
"integrity": "sha512-G9c0qlIWQSK29pR/5U2JF5dDQeqqHRragoyahj/Nx4KOOQ3CPPfzxnfqFPCSB7x5UgjOgnZ61nSxz+fjDpRlJg==",
"license": "MIT",
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/toposort-class": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz",
"integrity": "sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg==",
"license": "MIT"
},
"node_modules/undici-types": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"license": "MIT"
},
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/validator": {
"version": "13.12.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz",
"integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==",
"license": "MIT",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/wkx": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz",
"integrity": "sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
}
}
}
{
"dependencies": {
"argon2": "^0.41.1",
"jsonwebtoken": "^9.0.2",
"sequelize": "^6.37.5"
}
}
...@@ -13,6 +13,7 @@ const indexRouter = require('./routes/index'); ...@@ -13,6 +13,7 @@ const indexRouter = require('./routes/index');
const crewRouter = require('./routes/crew'); const crewRouter = require('./routes/crew');
const eventRouter = require('./routes/event'); const eventRouter = require('./routes/event');
const regionRouter = require('./routes/region'); const regionRouter = require('./routes/region');
const userRouter = require('./routes/user');
const app = express(); const app = express();
...@@ -48,6 +49,7 @@ app.use('/', indexRouter); // 기본 라우터 ...@@ -48,6 +49,7 @@ app.use('/', indexRouter); // 기본 라우터
app.use('/api/crews', crewRouter); // 크루 관련 API app.use('/api/crews', crewRouter); // 크루 관련 API
app.use('/api/events', eventRouter); // 이벤트 관련 API app.use('/api/events', eventRouter); // 이벤트 관련 API
app.use('/api/regions', regionRouter); // 지역 관련 API app.use('/api/regions', regionRouter); // 지역 관련 API
app.use('/api', userRouter); // 사용자 관련 API
// **에러 핸들링** // **에러 핸들링**
app.use((err, req, res, next) => { app.use((err, req, res, next) => {
......
const argon2 = require('argon2');
const jwt = require('jsonwebtoken');
const AccountConfig = require('../../config/account');
const sequelize = require('../../config/database');
const User = require('../models/User');
const Profile = require('../models/Profile');
const authTokenExpiryTime = '1h';
async function hashPassword(password) {
return await argon2.hash(password, {
type: argon2.argon2id,
});
}
async function verifyPassword(hash, plainPassword) {
return await argon2.verify(hash, plainPassword);
}
// JWT 생성
function generateToken(payload) {
return jwt.sign(
payload,
AccountConfig.JWT_SECRET_KEY,
{
expiresIn: authTokenExpiryTime,
},
);
}
exports.signUp = async (req, res) => {
const {name, email, password} = req.body;
if (!name) {
return res.status(400).json({error: '이름을 입력하세요.'});
}
if (!email) {
return res.status(400).json({error: '이메일을 입력하세요.'});
}
if (!password) {
return res.status(400).json({error: '비밀번호를 입력하세요.'});
}
// Check if the user already exists
const user = await User.findOne({
where: {email: email},
});
if (user) {
return res.status(409).json({error: '이미 등록된 사용자입니다.'});
}
const profile = req.body.profile || {};
if (!profile.regionID) {
return res.status(400).json({error: '지역을 입력하세요.'});
}
if (!profile.sportTypeID) {
return res.status(400).json({error: '운동 종목을 입력하세요.'});
}
try {
const tx = await sequelize.transaction();
try {
const user = await User.create({
name,
email,
password: await hashPassword(password),
}, {transaction: tx});
const userProfile = await Profile.create({
// TODO: Save the image to storage and save the URL to the database
// profileImage: profile.profileImage,
regionID: profile.regionID,
job: profile.job || null,
birthDate: profile.birthDate || null,
experience: profile.experience || null,
introduction: profile.introduction || null,
sportTypeID: profile.sportTypeID,
userID: user.userID,
}, {transaction: tx});
await tx.commit();
// 생성된 사용자 정보 반환
res.status(201).json({
userID: user.userID,
name: user.name,
email: user.email,
createdAt: user.createdAt,
updatedAt: user.updatedAt,
profile: userProfile,
});
} catch (error) {
await tx.rollback();
console.error('사용자 생성 중 오류:', error);
res.status(500).json({error: '사용자 생성 중 오류가 발생생했습니다.'});
}
} catch (error) {
console.error('사용자 생성 중 오류:', error);
res.status(500).json({error: '사용자 생성 중 오류가 발생했습니다.'});
}
}
exports.verifyEmail = async (req, res) => {
const {email} = req.body;
if (!email) {
return res.status(400).json({error: '이메일을 입력하세요.'});
}
const user = await User.findOne({
where: {email: email},
});
if (user) {
return res.status(409).json({
error: '이미 등록된 사용자입니다.'
});
}
res.status(200).json({
message: '사용 가능한 이메일입니다.'
});
}
exports.login = async (req, res) => {
try {
const {email, password} = req.body;
if (!email) {
return res.status(400).json({error: '이메일을 입력하세요.'});
}
if (!password) {
return res.status(400).json({error: '비밀번호를 입력하세요.'});
}
// 이메일로 사용자 조회
const user = await User.findOne({
where: {email: email},
});
// 사용자가 없을 경우
if (!user) {
return res.status(404).json({error: '사용자를 찾을 수 없습니다.'});
}
// 비밀번호 검증
const isPasswordMatch = await verifyPassword(user.password, password);
// 비밀번호가 일치하지 않을 경우
if (!isPasswordMatch) {
return res.status(401).json({error: '비밀번호가 일치하지 않습니다.'});
}
// JWT 토큰 생성
const token = await generateToken({
userID: user.userID,
email: user.email,
});
// 토큰 반환
res.status(200).json({
"auth_token": token,
});
} catch (error) {
console.error('로그인 중 오류:', error);
res.status(500).json({error: '로그인 중 오류가 발생했습니다.'});
}
}
exports.getUserById = async (req, res) => {
// TODO: verify the user by auth token with middleware. #14
try {
const {id} = req.params;
const user = await User.findByPk(id);
if (!user) {
return res.status(404).json({error: '사용자를 찾을 수 없습니다.'});
}
const userProfile = await user.getProfile();
res.status(200).json({
userID: user.userID,
name: user.name,
email: user.email,
createdAt: user.createdAt,
updatedAt: user.updatedAt,
profile: userProfile,
});
} catch (error) {
console.error('사용자 조회 중 오류:', error);
res.status(500).json({error: '사용자 조회 중 오류가 발생했습니다.'});
}
}
exports.updateUser = async (req, res) => {
const tx = await sequelize.transaction();
const {id} = req.params;
const {password, profile} = req.body;
try {
const user = await User.findByPk(id, {transaction: tx});
if (!user) {
await tx.rollback();
return res.status(404).json({error: '사용자를 찾을 수 없습니다.'});
}
if (password) user.password = await hashPassword(password);
await user.save({transaction: tx});
let userProfile;
if (profile) {
userProfile = await user.getProfile({transaction: tx});
if (userProfile) {
// TODO: Save the image to storage and save the URL to the database
// if (profile.profileImage) userProfile.profileImage = profile.profileImage;
if (profile.regionID) userProfile.regionID = profile.regionID;
if (profile.job) userProfile.job = profile.job;
if (profile.birthDate) userProfile.birthDate = profile.birthDate;
if (profile.experience) userProfile.experience = profile.experience;
if (profile.introduction) userProfile.introduction = profile.introduction;
if (profile.sportTypeID) userProfile.sportTypeID = profile.sportTypeID;
await userProfile.save({transaction: tx});
}
}
await tx.commit();
res.status(200).json({
userID: user.userID,
name: user.name,
email: user.email,
createdAt: user.createdAt,
updatedAt: user.updatedAt,
profile: userProfile,
});
} catch (error) {
await tx.rollback();
console.error('사용자 업데이트 중 오류:', error);
res.status(500).json({error: '사용자 업데이트 중 오류가 발생했습니다.'});
}
}
exports.deleteUser = async (req, res) => {
const {id} = req.params;
const tx = await sequelize.transaction();
try {
const user = await User.findByPk(id, {transaction: tx});
if (!user) {
await tx.rollback();
return res.status(404).json({error: '사용자를 찾을 수 없습니다.'});
}
const userProfile = await user.getProfile({transaction: tx});
if (userProfile) {
await userProfile.destroy({transaction: tx});
}
await user.destroy({transaction: tx});
await tx.commit();
res.status(204).json({message: '사용자가 삭제되었습니다.'});
} catch (error) {
await tx.rollback();
console.error('사용자 삭제 중 오류:', error);
res.status(500).json({error: '사용자 삭제 중 오류가 발생했습니다.'});
}
}
const {DataTypes} = require('sequelize');
const sequelize = require('../../config/database');
const User = require('./User');
const Profile = sequelize.define('Profile', {
profileID: {
type: DataTypes.INTEGER,
primaryKey: true,
allowNull: false,
autoIncrement: true, // 프로필 ID 자동 증가
},
userID: {
type: DataTypes.INTEGER,
allowNull: false,
},
profileImage: {
type: DataTypes.STRING(255),
allowNull: true,
},
regionID: {
type: DataTypes.INTEGER,
allowNull: false,
},
job: {
type: DataTypes.STRING(50),
allowNull: true,
},
birthDate: {
type: DataTypes.DATE,
allowNull: true,
},
experience: {
type: DataTypes.STRING(100),
allowNull: true,
},
introduction: {
type: DataTypes.STRING(70),
allowNull: true,
},
sportTypeID: {
type: DataTypes.INTEGER,
allowNull: false,
},
createdAt: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
},
updatedAt: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
},
}, {
tableName: 'Profile', // MySQL 테이블 이름 매핑
});
Profile.belongsTo(User, {foreignKey: 'userID'});
User.hasOne(Profile, {foreignKey: 'userID'});
module.exports = Profile;
const {DataTypes} = require('sequelize'); const {DataTypes} = require('sequelize');
const sequelize = require('../../config/database'); // config 폴더는 apiserver의 상위 폴더에 위치 const sequelize = require('../../config/database');
const User = sequelize.define('User', { const User = sequelize.define('User', {
userID: { userID: {
...@@ -24,9 +24,18 @@ const User = sequelize.define('User', { ...@@ -24,9 +24,18 @@ const User = sequelize.define('User', {
type: DataTypes.STRING(100), type: DataTypes.STRING(100),
allowNull: false, allowNull: false,
}, },
createdAt: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
},
updatedAt: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
},
}, { }, {
tableName: 'User', // MySQL 테이블 이름 매핑 tableName: 'User', // MySQL 테이블 이름 매핑
timestamps: false, // createdAt, updatedAt 비활성화
indexes: [ indexes: [
{ {
name: 'idx_user_email', // 인덱스 이름 name: 'idx_user_email', // 인덱스 이름
......
...@@ -8,11 +8,13 @@ ...@@ -8,11 +8,13 @@
"name": "backend", "name": "backend",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"argon2": "^0.41.1",
"cookie-parser": "~1.4.4", "cookie-parser": "~1.4.4",
"cors": "^2.8.5", "cors": "^2.8.5",
"debug": "~2.6.9", "debug": "~2.6.9",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"express": "~4.16.1", "express": "~4.16.1",
"jsonwebtoken": "^9.0.2",
"moment": "^2.30.1", "moment": "^2.30.1",
"morgan": "^1.10.0", "morgan": "^1.10.0",
"mysql2": "^3.11.4", "mysql2": "^3.11.4",
...@@ -22,6 +24,15 @@ ...@@ -22,6 +24,15 @@
"nodemon": "^3.1.7" "nodemon": "^3.1.7"
} }
}, },
"node_modules/@phc/format": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@phc/format/-/format-1.0.0.tgz",
"integrity": "sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/@types/debug": { "node_modules/@types/debug": {
"version": "4.1.12", "version": "4.1.12",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
...@@ -79,6 +90,21 @@ ...@@ -79,6 +90,21 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/argon2": {
"version": "0.41.1",
"resolved": "https://registry.npmjs.org/argon2/-/argon2-0.41.1.tgz",
"integrity": "sha512-dqCW8kJXke8Ik+McUcMDltrbuAWETPyU6iq+4AhxqKphWi7pChB/Zgd/Tp/o8xRLbg8ksMj46F/vph9wnxpTzQ==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@phc/format": "^1.0.0",
"node-addon-api": "^8.1.0",
"node-gyp-build": "^4.8.1"
},
"engines": {
"node": ">=16.17.0"
}
},
"node_modules/array-flatten": { "node_modules/array-flatten": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
...@@ -171,6 +197,12 @@ ...@@ -171,6 +197,12 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
"license": "BSD-3-Clause"
},
"node_modules/bytes": { "node_modules/bytes": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
...@@ -322,6 +354,15 @@ ...@@ -322,6 +354,15 @@
"integrity": "sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA==", "integrity": "sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"license": "Apache-2.0",
"dependencies": {
"safe-buffer": "^5.0.1"
}
},
"node_modules/ee-first": { "node_modules/ee-first": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
...@@ -608,12 +649,103 @@ ...@@ -608,12 +649,103 @@
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
"license": "MIT",
"dependencies": {
"jws": "^3.2.2",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
"lodash.isnumber": "^3.0.3",
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.once": "^4.0.0",
"ms": "^2.1.1",
"semver": "^7.5.4"
},
"engines": {
"node": ">=12",
"npm": ">=6"
}
},
"node_modules/jsonwebtoken/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/jwa": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
"integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
"license": "MIT",
"dependencies": {
"buffer-equal-constant-time": "1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"node_modules/jws": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
"license": "MIT",
"dependencies": {
"jwa": "^1.4.1",
"safe-buffer": "^5.0.1"
}
},
"node_modules/lodash": { "node_modules/lodash": {
"version": "4.17.21", "version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
"license": "MIT"
},
"node_modules/lodash.isboolean": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
"license": "MIT"
},
"node_modules/lodash.isinteger": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
"license": "MIT"
},
"node_modules/lodash.isnumber": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
"license": "MIT"
},
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
"license": "MIT"
},
"node_modules/lodash.isstring": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
"license": "MIT"
},
"node_modules/lodash.once": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
"license": "MIT"
},
"node_modules/long": { "node_modules/long": {
"version": "5.2.3", "version": "5.2.3",
"resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz",
...@@ -816,6 +948,26 @@ ...@@ -816,6 +948,26 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/node-addon-api": {
"version": "8.2.2",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.2.2.tgz",
"integrity": "sha512-9emqXAKhVoNrQ792nLI/wpzPpJ/bj/YXxW0CvAau1+RdGBcCRF1Dmz7719zgVsQNrzHl9Tzn3ImZ4qWFarWL0A==",
"license": "MIT",
"engines": {
"node": "^18 || ^20 || >= 21"
}
},
"node_modules/node-gyp-build": {
"version": "4.8.4",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
"integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
"license": "MIT",
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
"node-gyp-build-test": "build-test.js"
}
},
"node_modules/nodemon": { "node_modules/nodemon": {
"version": "3.1.7", "version": "3.1.7",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz",
......
...@@ -6,11 +6,13 @@ ...@@ -6,11 +6,13 @@
"start": "node ./app.js" "start": "node ./app.js"
}, },
"dependencies": { "dependencies": {
"argon2": "^0.41.1",
"cookie-parser": "~1.4.4", "cookie-parser": "~1.4.4",
"cors": "^2.8.5", "cors": "^2.8.5",
"debug": "~2.6.9", "debug": "~2.6.9",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"express": "~4.16.1", "express": "~4.16.1",
"jsonwebtoken": "^9.0.2",
"moment": "^2.30.1", "moment": "^2.30.1",
"morgan": "^1.10.0", "morgan": "^1.10.0",
"mysql2": "^3.11.4", "mysql2": "^3.11.4",
......
var express = require('express');
var router = express.Router();
const userController = require('../controllers/userController');
router.post('/users', userController.signUp);
router.post('/login', userController.login);
router.post('/verify_email', userController.verifyEmail);
router.get('/users/:id', userController.getUserById);
router.put('/users/:id', userController.updateUser);
router.delete('/users/:id', userController.deleteUser);
module.exports = router;
const AccountConfig = {
JWT_SECRET_KEY: process.env.JWT_SECRET_KEY || "my_secret_key",
}
module.exports = AccountConfig;
-- Region 테이블 (자기 참조 구조) -- Region 테이블 (자기 참조 구조)
DROP TABLE IF EXISTS `Region`; DROP TABLE IF EXISTS `Region`;
CREATE TABLE `Region` ( CREATE TABLE `Region`
(
`regionID` INT NOT NULL, `regionID` INT NOT NULL,
`regionName` VARCHAR(100) NOT NULL, `regionName` VARCHAR(100) NOT NULL,
`parentRegionID` INT NULL, `parentRegionID` INT NULL,
...@@ -10,7 +11,8 @@ CREATE TABLE `Region` ( ...@@ -10,7 +11,8 @@ CREATE TABLE `Region` (
-- SportType 테이블 -- SportType 테이블
DROP TABLE IF EXISTS `SportType`; DROP TABLE IF EXISTS `SportType`;
CREATE TABLE `SportType` ( CREATE TABLE `SportType`
(
`sportTypeId` INT NOT NULL, `sportTypeId` INT NOT NULL,
`sportName` VARCHAR(100) NOT NULL COMMENT '(예: 러닝, 헬스, 클라이밍)', `sportName` VARCHAR(100) NOT NULL COMMENT '(예: 러닝, 헬스, 클라이밍)',
`metadata` JSON NULL COMMENT '(카테고리와 옵션을 JSON으로 저장)', `metadata` JSON NULL COMMENT '(카테고리와 옵션을 JSON으로 저장)',
...@@ -19,27 +21,33 @@ CREATE TABLE `SportType` ( ...@@ -19,27 +21,33 @@ CREATE TABLE `SportType` (
-- User 테이블에 email에 대한 unique 인덱스 추가 -- User 테이블에 email에 대한 unique 인덱스 추가
DROP TABLE IF EXISTS `User`; DROP TABLE IF EXISTS `User`;
CREATE TABLE `User` ( CREATE TABLE `User`
`userID` INT NOT NULL, (
`userID` INT NOT NULL AUTO_INCREMENT, # NOTE: Use UUID instead.
`name` VARCHAR(100) NOT NULL, `name` VARCHAR(100) NOT NULL,
`email` VARCHAR(100) NOT NULL, `email` VARCHAR(100) NOT NULL,
`password` VARCHAR(100) NOT NULL, `password` VARCHAR(100) NOT NULL,
`createdAt` DATETIME NOT NULL,
`updatedAt` DATETIME NOT NULL,
PRIMARY KEY (`userID`), PRIMARY KEY (`userID`),
UNIQUE INDEX `idx_user_email` (`email`) UNIQUE INDEX `idx_user_email` (`email`)
); );
-- Profile 테이블 -- Profile 테이블
DROP TABLE IF EXISTS `Profile`; DROP TABLE IF EXISTS `Profile`;
CREATE TABLE `Profile` ( CREATE TABLE `Profile`
`profileID` INT NOT NULL, (
`profileID` INT NOT NULL AUTO_INCREMENT, # NOTE: Use UUID instead.
`userID` INT NOT NULL, `userID` INT NOT NULL,
`profileImage` VARCHAR(255) NULL, `profileImage` VARCHAR(255) NULL,
`regionID` INT NOT NULL, `regionID` INT NOT NULL,
`job` VARCHAR(50) NULL COMMENT 'ex. 학생, 직장인', `job` VARCHAR(50) NULL COMMENT 'ex. 학생, 직장인',
`birthdate` DATE NULL, `birthDate` DATE NULL,
`experience` VARCHAR(100) NULL COMMENT '직접 입력', `experience` VARCHAR(100) NULL COMMENT '직접 입력',
`introduction` VARCHAR(70) NULL COMMENT '직접 입력', `introduction` VARCHAR(70) NULL COMMENT '직접 입력',
`sportTypeId` INT NOT NULL, `sportTypeId` INT NOT NULL,
`createdAt` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updatedAt` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`profileID`), PRIMARY KEY (`profileID`),
FOREIGN KEY (`userID`) REFERENCES `User` (`userID`), FOREIGN KEY (`userID`) REFERENCES `User` (`userID`),
FOREIGN KEY (`regionID`) REFERENCES `Region` (`regionID`), FOREIGN KEY (`regionID`) REFERENCES `Region` (`regionID`),
...@@ -48,7 +56,8 @@ CREATE TABLE `Profile` ( ...@@ -48,7 +56,8 @@ CREATE TABLE `Profile` (
-- Crew 테이블, (regionID, sportTypeId, createdDate) 복합 인덱스 추가 -- Crew 테이블, (regionID, sportTypeId, createdDate) 복합 인덱스 추가
DROP TABLE IF EXISTS `Crew`; DROP TABLE IF EXISTS `Crew`;
CREATE TABLE `Crew` ( CREATE TABLE `Crew`
(
`crewID` INT NOT NULL, `crewID` INT NOT NULL,
`regionID` INT NOT NULL, `regionID` INT NOT NULL,
`name` VARCHAR(100) NOT NULL, `name` VARCHAR(100) NOT NULL,
...@@ -66,7 +75,8 @@ CREATE TABLE `Crew` ( ...@@ -66,7 +75,8 @@ CREATE TABLE `Crew` (
-- Event 테이블에 (crewID, eventDate) 복합 인덱스 추가 -- Event 테이블에 (crewID, eventDate) 복합 인덱스 추가
DROP TABLE IF EXISTS `Event`; DROP TABLE IF EXISTS `Event`;
CREATE TABLE `Event` ( CREATE TABLE `Event`
(
`eventID` INT NOT NULL, `eventID` INT NOT NULL,
`crewID` INT NULL COMMENT '크루 ID (선택 항목으로 변경)', `crewID` INT NULL COMMENT '크루 ID (선택 항목으로 변경)',
`regionID` INT NOT NULL, `regionID` INT NOT NULL,
...@@ -88,13 +98,15 @@ CREATE TABLE `Event` ( ...@@ -88,13 +98,15 @@ CREATE TABLE `Event` (
-- Post 테이블에 date, crewID, userID 인덱스 추가 -- Post 테이블에 date, crewID, userID 인덱스 추가
DROP TABLE IF EXISTS `Post`; DROP TABLE IF EXISTS `Post`;
CREATE TABLE `Post` ( CREATE TABLE `Post`
(
`postID` INT NOT NULL, `postID` INT NOT NULL,
`userID` INT NOT NULL, `userID` INT NOT NULL,
`crewID` INT NOT NULL, `crewID` INT NOT NULL,
`title` VARCHAR(100) NULL, `title` VARCHAR(100) NULL,
`content` TEXT NULL, `content` TEXT NULL,
`postDate` DATE NULL, `postDate` DATE NULL,
`isNotice` BOOLEAN NULL,
PRIMARY KEY (`postID`), PRIMARY KEY (`postID`),
FOREIGN KEY (`userID`) REFERENCES `User` (`userID`), FOREIGN KEY (`userID`) REFERENCES `User` (`userID`),
FOREIGN KEY (`crewID`) REFERENCES `Crew` (`crewID`), FOREIGN KEY (`crewID`) REFERENCES `Crew` (`crewID`),
...@@ -107,7 +119,8 @@ CREATE TABLE `Post` ( ...@@ -107,7 +119,8 @@ CREATE TABLE `Post` (
-- Comment 테이블에 date 인덱스 추가 -- Comment 테이블에 date 인덱스 추가
DROP TABLE IF EXISTS `Comment`; DROP TABLE IF EXISTS `Comment`;
CREATE TABLE `Comment` ( CREATE TABLE `Comment`
(
`commentID` INT NOT NULL, `commentID` INT NOT NULL,
`userID` INT NOT NULL, `userID` INT NOT NULL,
`postID` INT NOT NULL, `postID` INT NOT NULL,
...@@ -121,7 +134,8 @@ CREATE TABLE `Comment` ( ...@@ -121,7 +134,8 @@ CREATE TABLE `Comment` (
-- UserCrew 테이블 -- UserCrew 테이블
DROP TABLE IF EXISTS `UserCrew`; DROP TABLE IF EXISTS `UserCrew`;
CREATE TABLE `UserCrew` ( CREATE TABLE `UserCrew`
(
`userID` INT NOT NULL, `userID` INT NOT NULL,
`crewID` INT NOT NULL, `crewID` INT NOT NULL,
`role` ENUM ('Leader', 'Staff', 'General') NULL COMMENT '크루장, 운영진, 일반인', `role` ENUM ('Leader', 'Staff', 'General') NULL COMMENT '크루장, 운영진, 일반인',
...@@ -133,7 +147,8 @@ CREATE TABLE `UserCrew` ( ...@@ -133,7 +147,8 @@ CREATE TABLE `UserCrew` (
-- EventParticipants 테이블 -- EventParticipants 테이블
DROP TABLE IF EXISTS `EventParticipants`; DROP TABLE IF EXISTS `EventParticipants`;
CREATE TABLE `EventParticipants` ( CREATE TABLE `EventParticipants`
(
`userID` INT NOT NULL, `userID` INT NOT NULL,
`eventID` INT NOT NULL, `eventID` INT NOT NULL,
`participationDate` DATE NULL, `participationDate` DATE NULL,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment