회원가입 로그인 기능 작성
This commit is contained in:
parent
7028fe5103
commit
ff9ed0d462
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# 디폴트 무시된 파일
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 에디터 기반 HTTP 클라이언트 요청
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
12
.idea/Degullmok-server.iml
generated
Normal file
12
.idea/Degullmok-server.iml
generated
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/Degullmok-server.iml" filepath="$PROJECT_DIR$/.idea/Degullmok-server.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
95
app.js
Normal file
95
app.js
Normal file
@ -0,0 +1,95 @@
|
||||
var createError = require('http-errors');
|
||||
var express = require('express');
|
||||
var path = require('path');
|
||||
var cookieParser = require('cookie-parser');
|
||||
var logger = require('morgan');
|
||||
var mongodb = require('mongodb');
|
||||
var MongoClient = mongodb.MongoClient;
|
||||
|
||||
var indexRouter = require('./routes/index');
|
||||
var usersRouter = require('./routes/users');
|
||||
var leaderboardRouter = require('./routes/leaderboard');
|
||||
const session = require('express-session');
|
||||
var fileStore = require('session-file-store')(session);
|
||||
|
||||
var app = express();
|
||||
|
||||
app.use(session({
|
||||
secret: process.env.SESSION_SECRET || "session-login",
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
store: new fileStore({
|
||||
path: './sessions/',
|
||||
ttl: 24 * 60 * 60,
|
||||
reapInterval: 60 * 60
|
||||
}),
|
||||
cookie: {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
maxAge: 24 * 60 * 60 * 1000,
|
||||
}
|
||||
}));
|
||||
|
||||
async function connectDB(){
|
||||
var databaseUrl = "mongodb://localhost:27017/DegullMok";
|
||||
|
||||
// MongoDB 연결
|
||||
try {
|
||||
const client = await MongoClient.connect(databaseUrl,{
|
||||
useNewUrlParser: true,
|
||||
useUnifiedTopology: true,
|
||||
});
|
||||
|
||||
const db = client.db('DegullMok'); // 사용할 데이터베이스 선택
|
||||
app.set('database', db); // DB를 app에 저장
|
||||
console.log("Database Connected : " + databaseUrl);
|
||||
|
||||
|
||||
// 연결 종료 처리
|
||||
process.on("SIGINT", async ()=> {
|
||||
await database.close();
|
||||
console.log("Database Connected");
|
||||
process.exit(0);
|
||||
})
|
||||
} catch (err){
|
||||
console.error("DB 연결 실패: " + err);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
connectDB().catch(err => {
|
||||
console.error("초기 DB 연결 실패: " + err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// view engine setup
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
app.set('view engine', 'pug');
|
||||
|
||||
app.use(logger('dev'));
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: false }));
|
||||
app.use(cookieParser());
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
|
||||
app.use('/', indexRouter);
|
||||
app.use('/users', usersRouter);
|
||||
app.use('/leaderboard', leaderboardRouter);
|
||||
|
||||
// catch 404 and forward to error handler
|
||||
app.use(function(req, res, next) {
|
||||
next(createError(404));
|
||||
});
|
||||
|
||||
// error handler
|
||||
app.use(function(err, req, res, next) {
|
||||
// set locals, only providing error in development
|
||||
res.locals.message = err.message;
|
||||
res.locals.error = req.app.get('env') === 'development' ? err : {};
|
||||
|
||||
// render the error page
|
||||
res.status(err.status || 500);
|
||||
res.render('error');
|
||||
});
|
||||
|
||||
module.exports = app;
|
96
bin/www
Normal file
96
bin/www
Normal file
@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var app = require('../app');
|
||||
var debug = require('debug')('tictactoe-server:server');
|
||||
var http = require('http');
|
||||
var game = require('../game');
|
||||
|
||||
/**
|
||||
* Get port from environment and store in Express.
|
||||
*/
|
||||
|
||||
var port = normalizePort(process.env.PORT || '3000');
|
||||
app.set('port', port);
|
||||
|
||||
/**
|
||||
* Create HTTP server.
|
||||
*/
|
||||
|
||||
var server = http.createServer(app);
|
||||
|
||||
/**
|
||||
* 게임 서버 실행
|
||||
*/
|
||||
game(server);
|
||||
|
||||
/**
|
||||
* Listen on provided port, on all network interfaces.
|
||||
*/
|
||||
|
||||
server.listen(port);
|
||||
server.on('error', onError);
|
||||
server.on('listening', onListening);
|
||||
|
||||
/**
|
||||
* Normalize a port into a number, string, or false.
|
||||
*/
|
||||
|
||||
function normalizePort(val) {
|
||||
var port = parseInt(val, 10);
|
||||
|
||||
if (isNaN(port)) {
|
||||
// named pipe
|
||||
return val;
|
||||
}
|
||||
|
||||
if (port >= 0) {
|
||||
// port number
|
||||
return port;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "error" event.
|
||||
*/
|
||||
|
||||
function onError(error) {
|
||||
if (error.syscall !== 'listen') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
var bind = typeof port === 'string'
|
||||
? 'Pipe ' + port
|
||||
: 'Port ' + port;
|
||||
|
||||
// handle specific listen errors with friendly messages
|
||||
switch (error.code) {
|
||||
case 'EACCES':
|
||||
console.error(bind + ' requires elevated privileges');
|
||||
process.exit(1);
|
||||
break;
|
||||
case 'EADDRINUSE':
|
||||
console.error(bind + ' is already in use');
|
||||
process.exit(1);
|
||||
break;
|
||||
default:
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "listening" event.
|
||||
*/
|
||||
|
||||
function onListening() {
|
||||
var addr = server.address();
|
||||
var bind = typeof addr === 'string'
|
||||
? 'pipe ' + addr
|
||||
: 'port ' + addr.port;
|
||||
debug('Listening on ' + bind);
|
||||
}
|
56
game.js
Normal file
56
game.js
Normal file
@ -0,0 +1,56 @@
|
||||
const {v4: uuidv4} = require('uuid');
|
||||
|
||||
module.exports = function(server) {
|
||||
|
||||
const io = require('socket.io')(server, {
|
||||
transports: ['websocket']
|
||||
});
|
||||
|
||||
// 방 정보
|
||||
var rooms = [];
|
||||
var socketRooms = new Map();
|
||||
|
||||
io.on('connection', function(socket) {
|
||||
console.log('Connected: ' + socket.id);
|
||||
if (rooms.length > 0) {
|
||||
var roomId = rooms.shift();
|
||||
socket.join(roomId)
|
||||
socket.emit('joinRoom', { roomId: roomId });
|
||||
socket.to(roomId).emit('startGame', { roomId: socket.id });
|
||||
socketRooms.set(socket.id, roomId);
|
||||
} else {
|
||||
var roomId = uuidv4();
|
||||
socket.join(roomId);
|
||||
socket.emit('createRoom', { room: roomId });
|
||||
rooms.push(roomId);
|
||||
socketRooms.set(socket.id, roomId);
|
||||
}
|
||||
|
||||
socket.on('leaveRoom', function(roomData) {
|
||||
socket.leave(roomData.roomId);
|
||||
socket.emit('exitRoom');
|
||||
socket.to(roomData.roomId).emit('endGame');
|
||||
|
||||
// 방 만든 후 혼자 들어갔다가 나갈 때 rooms에서 방 삭제
|
||||
var roomId = socketRooms.get(socket.id);
|
||||
const roomIdx = rooms.indexOf(roomId);
|
||||
if (roomIdx !== -1) {
|
||||
rooms.splice(roomIdx, 1);
|
||||
console.log('Room removed:', roomId);
|
||||
}
|
||||
|
||||
socketRooms.delete(socket.id);
|
||||
});
|
||||
|
||||
socket.on('doPlayer', function(moveData) {
|
||||
const roomId = moveData.roomId;
|
||||
const position = moveData.position;
|
||||
|
||||
socket.to(roomId).emit('doOpponent', { position: position });
|
||||
});
|
||||
|
||||
socket.on('disconnect', function(reason) {
|
||||
console.log('Disconnected: ' + socket.id + ', Reason: ' + reason);
|
||||
});
|
||||
});
|
||||
};
|
2811
package-lock.json
generated
Normal file
2811
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
package.json
Normal file
22
package.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "tictactoe-server",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node ./bin/www"
|
||||
},
|
||||
"dependencies": {
|
||||
"bcrypt": "^5.1.1",
|
||||
"cookie-parser": "~1.4.4",
|
||||
"debug": "~2.6.9",
|
||||
"express": "^4.21.2",
|
||||
"express-session": "^1.18.1",
|
||||
"http-errors": "~1.6.3",
|
||||
"mongodb": "^6.14.1",
|
||||
"morgan": "~1.9.1",
|
||||
"pug": "^3.0.3",
|
||||
"session-file-store": "^1.5.0",
|
||||
"socket.io": "^4.8.1",
|
||||
"uuid": "^11.1.0"
|
||||
}
|
||||
}
|
8
public/stylesheets/style.css
Normal file
8
public/stylesheets/style.css
Normal file
@ -0,0 +1,8 @@
|
||||
body {
|
||||
padding: 50px;
|
||||
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #00B7FF;
|
||||
}
|
21
readme.md
Normal file
21
readme.md
Normal file
@ -0,0 +1,21 @@
|
||||
|
||||
|
||||
TODO : 유저 컬렉션 항목 추가
|
||||
- int : 코인 수량
|
||||
- int : 급수
|
||||
- int : 점수
|
||||
- int : 승
|
||||
- int : 패
|
||||
- int : 무승부
|
||||
|
||||
|
||||
TODO : 리더보드 로직 수정
|
||||
- 급수별 별도의 리더보드 표시
|
||||
- 같은 급수일 경우 승률로 정렬
|
||||
|
||||
|
||||
TODO : 실시간 소켓 통신
|
||||
- 타이머 동기화
|
||||
- 타이머가 시작된 정보를 클라이언트로부터 받음
|
||||
- 혹은 서버가 타이머 정보를 두 플레이어에게 내려줌
|
||||
- 시간 초과시 처리 : 클라이언트가 처리 OR 서버에서 처리
|
9
routes/index.js
Normal file
9
routes/index.js
Normal file
@ -0,0 +1,9 @@
|
||||
var express = require('express');
|
||||
var router = express.Router();
|
||||
|
||||
/* GET home page. */
|
||||
router.get('/', function(req, res, next) {
|
||||
res.render('index', { title: 'Express' });
|
||||
});
|
||||
|
||||
module.exports = router;
|
34
routes/leaderboard.js
Normal file
34
routes/leaderboard.js
Normal file
@ -0,0 +1,34 @@
|
||||
var express = require('express');
|
||||
var router = express.Router();
|
||||
|
||||
// 랭킹 조회
|
||||
router.get("/", async function (req, res, next) {
|
||||
try {
|
||||
if(!req.session.isAuthenticated) {
|
||||
return res.status(403).send("로그인이 필요합니다.");
|
||||
}
|
||||
|
||||
var database = req.app.get('database');
|
||||
var users = database.collection('users');
|
||||
|
||||
var allUsers = await users
|
||||
.find({ }, { projection: { username:1, nickname: 1, score: 1 } })
|
||||
.sort({ score: -1 }) // 점수가 높은 순으로 정렬
|
||||
.toArray();
|
||||
|
||||
var result = allUsers.map((user) => (
|
||||
{
|
||||
username: user.username,
|
||||
nickname: user.nickname,
|
||||
score: user.score || 0,
|
||||
}
|
||||
))
|
||||
|
||||
res.json({ leaderboardDatas : result ?? [] });
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
res.status(500).send("서버 오류가 발생했습니다.");
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = router;
|
186
routes/users.js
Normal file
186
routes/users.js
Normal file
@ -0,0 +1,186 @@
|
||||
var express = require('express');
|
||||
var router = express.Router();
|
||||
var bcrypt = require('bcrypt');
|
||||
const {ObjectId} = require("mongodb");
|
||||
var saltRounds = 10;
|
||||
|
||||
var ResponseType = {
|
||||
INVALID_USERNAME: 0,
|
||||
INVALID_PASSWORD: 1,
|
||||
SUCCESS: 2
|
||||
}
|
||||
|
||||
/* GET users listing. */
|
||||
router.get('/', function(req, res, next) {
|
||||
res.send('respond with a resource');
|
||||
});
|
||||
|
||||
|
||||
// 회원 가입
|
||||
router.post('/signup', async function (req, res, next) {
|
||||
try {
|
||||
var username = req.body.username;
|
||||
var password = req.body.password;
|
||||
var nickname = req.body.nickname;
|
||||
var profileImageIndex = req.body.imageindex ?? 0;
|
||||
|
||||
// 입력값 검증
|
||||
if( !username || !password || !nickname ){
|
||||
return res.status(400).send("모든 필드를 입력하세요.")
|
||||
}
|
||||
|
||||
var database = req.app.get('database');
|
||||
var users = database.collection('users');
|
||||
const existingUser = await users.findOne({username: username})
|
||||
if(existingUser){
|
||||
return res.status(409).send("이미 존재하는 사용자입니다.")
|
||||
}
|
||||
|
||||
// 비밀번호 암호화
|
||||
var salt = bcrypt.genSaltSync(saltRounds);
|
||||
var hash = bcrypt.hashSync(password, salt);
|
||||
|
||||
// 신규 유저를 DB에 저장
|
||||
await users.insertOne({
|
||||
username: username,
|
||||
password: hash,
|
||||
nickname: nickname,
|
||||
profileImageIndex: profileImageIndex,
|
||||
rating: 18,
|
||||
score:0
|
||||
});
|
||||
|
||||
res.status(201).send("사용자가 성공적으로 생성되었습니다.");
|
||||
|
||||
} catch (err){
|
||||
console.log("사용자 추가중 오류 발생 : " + err);
|
||||
res.status(500).send("서버 오류가 발생했습니다.");
|
||||
}
|
||||
})
|
||||
|
||||
// 로그인
|
||||
router.post("/signin", async function (req, res, next) {
|
||||
try {
|
||||
var username = req.body.username;
|
||||
var password = req.body.password;
|
||||
var database = req.app.get('database');
|
||||
var users = database.collection('users');
|
||||
|
||||
// 입력값 검증
|
||||
if (!username || !password) {
|
||||
return res.status(400).send("모든 필드를 입력해주세요.");
|
||||
}
|
||||
|
||||
const existingUser = await users.findOne({username: username});
|
||||
if(existingUser){
|
||||
var compareResult = bcrypt.compareSync(password, existingUser.password);
|
||||
if(compareResult){
|
||||
req.session.isAuthenticated = true;
|
||||
req.session.userId = existingUser._id.toString();
|
||||
req.session.username = existingUser.username;
|
||||
req.session.nickname = existingUser.nickname;
|
||||
req.session.profileImageIndex = existingUser.profileImageIndex || 0;
|
||||
req.session.rating = existingUser.rating;
|
||||
req.session.score = existingUser.score;
|
||||
|
||||
res.json({
|
||||
result: ResponseType.SUCCESS,
|
||||
imageindex: existingUser.imageindex,
|
||||
rating: existingUser.rating,
|
||||
score: existingUser.score,
|
||||
});
|
||||
} else {
|
||||
res.json({result : ResponseType.INVALID_PASSWORD});
|
||||
}
|
||||
} else {
|
||||
res.json({result : ResponseType.INVALID_USERNAME});
|
||||
}
|
||||
}catch (err){
|
||||
console.log("로그인 중 오류 발생 : ", err);
|
||||
res.status(500).send("서버 오류가 발생했습니다.");
|
||||
}
|
||||
})
|
||||
|
||||
// 로그 아웃
|
||||
router.post("/signout", async function (req, res, next) {
|
||||
req.session.destroy((err) => {
|
||||
if(err) {
|
||||
console.log("로그아웃 중 오류 발생");
|
||||
return res.status(500).send("서버 오류가 발생했습니다.");
|
||||
}
|
||||
res.status(200).send("로그아웃 되었습니다.")
|
||||
});
|
||||
});
|
||||
|
||||
// 점수 추가
|
||||
router.post("/addscore", async function (req, res, next) {
|
||||
try {
|
||||
if(!req.session.isAuthenticated) {
|
||||
return res.status(400).send("로그인이 필요합니다.");
|
||||
}
|
||||
|
||||
var userId = req.session.userId;
|
||||
var score = req.body.score;
|
||||
|
||||
// 점수 유효성 검사
|
||||
if(!score || isNaN(score)) {
|
||||
return res.status(400).send("유효한 점수를 입력해주세요.");
|
||||
}
|
||||
|
||||
var database = req.app.get('database');
|
||||
var users = database.collection('users');
|
||||
|
||||
const result = await users.updateOne(
|
||||
{_id: ObjectId.createFromHexString(userId) },
|
||||
{
|
||||
$set: {
|
||||
score: Number(score),
|
||||
updatedAt: new Date()
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if(result.matchedCount === 0 ) {
|
||||
return res.status(400).send("사용자를 찾을 수 없습니다.");
|
||||
}
|
||||
|
||||
res.status(200).json({message: "점수가 성공적으로 업데이트되었습니다."});
|
||||
|
||||
} catch(err) {
|
||||
console.log("점수 추가 중 오류 발생 : ",err);
|
||||
res.status(500).send("서버 오류가 발생했습니다.");
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
// 점수 조회
|
||||
router.get("/score", async function (req, res, next) {
|
||||
try {
|
||||
if(!req.session.isAuthenticated) {
|
||||
return res.status(403).send("로그인이 필요합니다.");
|
||||
}
|
||||
|
||||
var userId = req.session.userId;
|
||||
var database = req.app.get('database');
|
||||
var users = database.collection('users');
|
||||
|
||||
const user = await users.findOne({_id: ObjectId.createFromHexString(userId) });
|
||||
|
||||
if(!user){
|
||||
return res.status(404).send("사용자를 찾을 수 없습니다.");
|
||||
}
|
||||
|
||||
res.json({
|
||||
id: user._id.toString(),
|
||||
username: user.username,
|
||||
nickname: user.nickname,
|
||||
score: Number(user.score) || 0,
|
||||
})
|
||||
|
||||
} catch (err) {
|
||||
console.error("점수 조회 중 오류 발생 : ", err);
|
||||
res.status(500).send("서버 오류가 발생했습니다.")
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = router;
|
1
sessions/uJF8ea2nXHdx18OP26Xvu2-IW02_1pHR.json
Normal file
1
sessions/uJF8ea2nXHdx18OP26Xvu2-IW02_1pHR.json
Normal file
@ -0,0 +1 @@
|
||||
{"cookie":{"originalMaxAge":86400000,"expires":"2025-03-12T08:52:52.709Z","secure":false,"httpOnly":true,"path":"/"},"isAuthenticated":true,"userId":"67cff9e05e33682110dd38cf","username":"test2","nickname":"testuser","__lastAccess":1741683172709,"profileImageIndex":1,"rating":18,"score":0}
|
6
views/error.pug
Normal file
6
views/error.pug
Normal file
@ -0,0 +1,6 @@
|
||||
extends layout
|
||||
|
||||
block content
|
||||
h1= message
|
||||
h2= error.status
|
||||
pre #{error.stack}
|
5
views/index.pug
Normal file
5
views/index.pug
Normal file
@ -0,0 +1,5 @@
|
||||
extends layout
|
||||
|
||||
block content
|
||||
h1= title
|
||||
p Welcome to #{title}
|
7
views/layout.pug
Normal file
7
views/layout.pug
Normal file
@ -0,0 +1,7 @@
|
||||
doctype html
|
||||
html
|
||||
head
|
||||
title= title
|
||||
link(rel='stylesheet', href='/stylesheets/style.css')
|
||||
body
|
||||
block content
|
Loading…
x
Reference in New Issue
Block a user