Jeongmin Seo

Merge branch 'master'

1 +# Logs
2 +logs
3 +*.log
4 +npm-debug.log*
5 +yarn-debug.log*
6 +yarn-error.log*
7 +lerna-debug.log*
8 +.pnpm-debug.log*
9 +app/log/*
10 +# Diagnostic reports (https://nodejs.org/api/report.html)
11 +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
12 +
13 +# Runtime data
14 +pids
15 +*.pid
16 +*.seed
17 +*.pid.lock
18 +
19 +# Directory for instrumented libs generated by jscoverage/JSCover
20 +lib-cov
21 +
22 +# Coverage directory used by tools like istanbul
23 +coverage
24 +*.lcov
25 +
26 +# nyc test coverage
27 +.nyc_output
28 +
29 +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
30 +.grunt
31 +
32 +# Bower dependency directory (https://bower.io/)
33 +bower_components
34 +
35 +# node-waf configuration
36 +.lock-wscript
37 +
38 +# Compiled binary addons (https://nodejs.org/api/addons.html)
39 +build/Release
40 +
41 +# Dependency directories
42 +**node_modules/
43 +jspm_packages/
44 +
45 +# Snowpack dependency directory (https://snowpack.dev/)
46 +web_modules/
47 +
48 +# TypeScript cache
49 +*.tsbuildinfo
50 +
51 +# Optional npm cache directory
52 +.npm
53 +
54 +# Optional eslint cache
55 +.eslintcache
56 +
57 +# Microbundle cache
58 +.rpt2_cache/
59 +.rts2_cache_cjs/
60 +.rts2_cache_es/
61 +.rts2_cache_umd/
62 +
63 +# Optional REPL history
64 +.node_repl_history
65 +
66 +# Output of 'npm pack'
67 +*.tgz
68 +
69 +# Yarn Integrity file
70 +.yarn-integrity
71 +
72 +# dotenv environment variables file
73 +.env
74 +.env.test
75 +.env.production
76 +
77 +# parcel-bundler cache (https://parceljs.org/)
78 +.cache
79 +.parcel-cache
80 +
81 +# Next.js build output
82 +.next
83 +out
84 +
85 +# Nuxt.js build / generate output
86 +.nuxt
87 +dist
88 +
89 +# Gatsby files
90 +.cache/
91 +# Comment in the public line in if your project uses Gatsby and not Next.js
92 +# https://nextjs.org/blog/next-9-1#public-directory-support
93 +# public
94 +
95 +# vuepress build output
96 +.vuepress/dist
97 +
98 +# Serverless directories
99 +.serverless/
100 +
101 +# FuseBox cache
102 +.fusebox/
103 +
104 +# DynamoDB Local files
105 +.dynamodb/
106 +
107 +# TernJS port file
108 +.tern-port
109 +
110 +# Stores VSCode versions used for testing VSCode extensions
111 +.vscode-test
112 +
113 +# yarn v2
114 +.yarn/cache
115 +.yarn/unplugged
116 +.yarn/build-state.yml
117 +.yarn/install-state.gz
118 +.pnp.*
119 +
120 +.package-lock.json
...\ No newline at end of file ...\ No newline at end of file
1 +"use strict";
2 +//모듈
3 +const express = require("express");
4 +const bodyParser = require("body-parser");
5 +//환경변수 (운영체제간 gap 없애고자)
6 +const dotenv = require("dotenv");
7 +dotenv.config();
8 +const compression = require("compression");
9 +const methodOverride = require("method-override");
10 +const path = require("path")
11 +const socketIO = require("socket.io")
12 +const moment = require("moment")
13 +const http = require("http");
14 +const app = express();
15 +const server = http.createServer(app);
16 +
17 +var cors = require("cors");
18 +const { logger } = require("./src/config/winston");
19 +//app이라는 express 객체 생성
20 +//라우팅
21 +const home = require("./src/routes/home");
22 +const port = 3000;
23 +const jwtMiddleware = require("./src/config/jwtMiddleware");
24 +const io = socketIO(server);
25 +
26 +// 앱 세팅
27 +app.set("views", "./src/views");
28 +app.set("view engine", "ejs");
29 +app.use(express.static(`${__dirname}/src/public`));
30 +app.use(express.static(path.join(__dirname, "src")))
31 +
32 +app.use(bodyParser.json());
33 +//url통해 전달되는 데이터에 한글, 공백 등의 문자 오류 해결
34 +app.use(bodyParser.urlencoded({extended: true}));
35 +
36 +app.use(compression()); // HTTP 요청을 압축 및 해제
37 +app.use(express.json());
38 +app.use(express.urlencoded({ extended: true }));
39 +app.use(methodOverride());
40 +app.use(cors());
41 +// app.use("/restaurants", require("../app/src/routes/home/restaurant.route"));
42 +// require("../app/src/routes/home/restaurant.route")(app);
43 +
44 +app.use("/", home); //미들웨어 등록해주는 method
45 +
46 +io.on("connection", (socket) => {
47 + socket.on("chatting", (data) => {
48 + const { name, msg } = data;
49 + io.emit("chatting", {
50 + name,
51 + msg,
52 + time: moment(new Date()).format("h:ss A"),
53 + });
54 + });
55 + });
56 +
57 +logger.info(`${process.env.NODE_ENV} - API Server Start At Port ${port}`);
58 +
59 +module.exports = server;
...\ No newline at end of file ...\ No newline at end of file
1 +"use strict";
2 +
3 +const server = require("../app");
4 +const PORT = process.env.PORT || 3000;
5 +
6 +server.listen(PORT, () => {
7 + console.log("서버 가동");
8 +});
...\ No newline at end of file ...\ No newline at end of file
This diff is collapsed. Click to expand it.
1 +{
2 + "name": "login",
3 + "version": "1.0.0",
4 + "main": "app.js",
5 + "bin": {
6 + "login": "www.js"
7 + },
8 + "dependencies": {
9 + "body-parser": "^1.20.0",
10 + "compression": "^1.7.4",
11 + "cors": "^2.8.5",
12 + "crypto": "^1.0.1",
13 + "dateformat": "^4.3.1",
14 + "dotenv": "^16.0.1",
15 + "ejs": "^3.1.8",
16 + "express": "^4.18.1",
17 + "jsonwebtoken": "^8.5.1",
18 + "method-override": "^3.0.0",
19 + "moment": "^2.29.3",
20 + "mysql": "^2.18.1",
21 + "mysql2": "^2.2.0",
22 + "regex-email": "^1.0.2",
23 + "request": "^2.88.2",
24 + "socket.io": "^4.5.1",
25 + "winston": "^3.2.1",
26 + "winston-daily-rotate-file": "^4.2.1"
27 + },
28 + "devDependencies": {},
29 + "scripts": {
30 + "start": "nodemon ./bin/www.js",
31 + "test": "echo \"Error: no test specified\" && exit 1",
32 + "dev": "NODE_ENV=development node index.js",
33 + "prod": "NODE_ENV=production node index.js"
34 + },
35 + "author": "Jeongmin Seo, Jumi Yang",
36 + "license": "MIT",
37 + "keywords": [],
38 + "description": "Node.js API Server"
39 +}
1 +// const mysql = require("mysql");
2 +const { logger } = require("./winston");
3 +const mysql2 = require("mysql2/promise");
4 +
5 +// const db = mysql.createConnection({
6 +// host: process.env.DB_HOST,
7 +// user: process.env.DB_USER,
8 +// password: process.env.DB_PASSWORD,
9 +// database: process.env.DB_DATABASE, //schema
10 +// });
11 +
12 +const pool = mysql2.createPool({
13 + host: process.env.DB_HOST,
14 + user: process.env.DB_USER,
15 + password: process.env.DB_PASSWORD,
16 + database: process.env.DB_DATABASE, //schema
17 + connectionLimit: 10000,
18 + multipleStatements: true,
19 +});
20 +
21 +// db.connect();
22 +
23 +module.exports = {
24 + pool: pool,
25 +};
26 +
27 +
...\ No newline at end of file ...\ No newline at end of file
1 +const jwt = require("jsonwebtoken");
2 +const secret_config = require("./db");
3 +const jwtMiddleware = function (req, res, next) {
4 + // read the token from header or url
5 + const token = req.headers["x-access-token"] || req.query.token;
6 + // token does not exist
7 + if (!token) {
8 + return res.status(403).json({
9 + isSuccess: false,
10 + code: 403,
11 + message: "로그인이 되어 있지 않습니다.",
12 + });
13 + }
14 +
15 + try {
16 + const verifiedToken = jwt.verify(token, secret_config.jwtsecret);
17 + req.verifiedToken = verifiedToken;
18 + next();
19 + } catch {
20 + res.status(403).json({
21 + isSuccess: false,
22 + code: 403,
23 + message: "검증 실패",
24 + });
25 + }
26 +};
27 +
28 +module.exports = jwtMiddleware;
1 +const { createLogger, format, transports } = require('winston');
2 +require('winston-daily-rotate-file');
3 +const fs = require('fs');
4 +
5 +const env = process.env.NODE_ENV || 'development';
6 +const logDir = 'log';
7 +
8 +// https://lovemewithoutall.github.io/it/winston-example/
9 +// Create the log directory if it does not exist
10 +if (!fs.existsSync(logDir)) {
11 + fs.mkdirSync(logDir)
12 +}
13 +
14 +const dailyRotateFileTransport = new transports.DailyRotateFile({
15 + level: 'debug',
16 + filename: `${logDir}/%DATE%-smart-push.log`,
17 + datePattern: 'YYYY-MM-DD',
18 + zippedArchive: true,
19 + maxSize: '20m',
20 + maxFiles: '14d'
21 +});
22 +
23 +const logger = createLogger({
24 + level: env === 'development' ? 'debug' : 'info',
25 + format: format.combine(
26 + format.timestamp({
27 + format: 'YYYY-MM-DD HH:mm:ss'
28 + }),
29 + format.json()
30 + ),
31 + transports: [
32 + new transports.Console({
33 + level: 'info',
34 + format: format.combine(
35 + format.colorize(),
36 + format.printf(
37 + info => `${info.timestamp} ${info.level}: ${info.message}`
38 + )
39 + )
40 + }),
41 + dailyRotateFileTransport
42 + ]
43 +});
44 +
45 +module.exports = {
46 + logger: logger
47 +};
...\ No newline at end of file ...\ No newline at end of file
1 +'use strict';
2 +//for DB manipulate
3 +const RestaurantStorage = require("./RestaurantStorage");
4 +
5 +const {pool} = require("../config/db");
6 +const { logger } = require("../config/winston");
7 +const jwt = require("jsonwebtoken");
8 +
9 +exports.readRestaurants = async function (req,res) {
10 + const {category} = req.query;
11 +
12 + if (category) {
13 + const validCategory = ["한식", "중식", "일식", "양식", "분식", "구이", "회/초밥", "기타",];
14 +
15 + if (!validCategory.includes(category)) {
16 + return res.send({
17 + isSuccess: false,
18 + code: 400,
19 + message: "유효한 카테고리가 아닙니다.",
20 + });
21 + }
22 + }
23 +
24 + try {
25 + const connection = await pool.getConnection(async (conn) => conn);
26 + try {
27 + //mysql접속 관련 부분 정의하는 함수
28 + //es6 비구조할당
29 + const [rows] = await RestaurantStorage.selectRestaurants(connection, category);
30 +
31 + return res.send({
32 + result: rows,
33 + isSuccess: true,
34 + code: 200, // 요청 성공시 200번대 코드를 뿌려주고, 실패시 400번대 코드
35 + message: "식당 목록 요청 성공",
36 + });
37 + } catch (err) {
38 + logger.error(`readRestaurants Query error\n: ${JSON.stringify(err)}`);
39 + return false;
40 + } finally {
41 + connection.release();
42 + }
43 + } catch (err) {
44 + logger.error(`readRestaurants DB Connection error\n: ${JSON.stringify(err)}`);
45 + return false;
46 + }
47 +}
...\ No newline at end of file ...\ No newline at end of file
1 +'use strict';
2 +//for DB CRUD
3 +// const db = require("../config/db");
4 +const { pool } = require("../config/db");
5 +
6 +exports.selectRestaurants = async function (connection, category) {
7 +
8 + const selectAllRestaurantsQuery = `select title, address, category from restaurants where status='A';`;
9 + const selectCategorizedRestaurantsQuery = `select title, address, category from restaurants where status='A' and category=?;`;
10 +
11 + const Params = [category];
12 +
13 + const Query = category ? selectCategorizedRestaurantsQuery : selectAllRestaurantsQuery;
14 +
15 + const rows = await connection.query(Query, Params);
16 +
17 + return rows;
18 +
19 +}
...\ No newline at end of file ...\ No newline at end of file
1 +'use strict';
2 +//for DB manipulate
3 +const UserStorage = require("./UserStorage");
4 +const {pool} = require("../config/db");
5 +const { logger } = require("../config/winston");
6 +const jwt = require("jsonwebtoken");
7 +
8 +class User {
9 + constructor(body) {
10 + this.body = body;
11 + }
12 +
13 + async login() {
14 + const client = this.body;
15 +
16 + try {
17 + const connection = await pool.getConnection(async (conn) => conn);
18 + try {
19 + const { id, password } = await UserStorage.getUserInfo(
20 + connection,
21 + client.id
22 + );
23 + if (id) {
24 + if (id === client.id && password === client.password) {
25 + return { success: true };
26 + }
27 + return { success: false, msg: "비밀번호가 틀렸습니다." };
28 + }
29 + return { success: false, msg: "존재하지 않는 아이디입니다." };
30 + } catch (err) {
31 + return { success: false, msg: err };
32 + } finally {
33 + connection.release();
34 + }
35 + } catch (err) {
36 + logger.error(`login DB Connection error\n: ${JSON.stringify(err)}`);
37 + return false;
38 + }
39 + }
40 +
41 + async register() {
42 + const client = this.body;
43 + try {
44 + const connection = await pool.getConnection(async (conn) => conn);
45 + // console.log(client);
46 + try {
47 + const response = await UserStorage.save(connection, client);
48 + // console.log("테스트2 : ", response);
49 + return response;
50 + } catch (err) {
51 + console.log(err);
52 + return {success: false, msg : err};
53 + } finally {
54 + connection.release();
55 + }
56 + } catch (err) {
57 + logger.error(`usersaving DB Connection error\n: ${JSON.stringify(err)}`);
58 + return false;
59 + }
60 + }
61 +}
62 +module.exports = User;
1 +'use strict';
2 +
3 +const { pool } = require("../config/db");
4 +
5 + //for DB CRUD
6 +
7 +
8 +class UserStorage {
9 + constructor(body) {
10 + this.body = body;
11 + // this.connection = await pool.getConnection(async (conn) => conn);
12 + }
13 +
14 + // static getUsers(isAll, ...fields) {}
15 + static async getUserInfo(connection, id) {
16 + const query = `SELECT * FROM users WHERE id = '${id}';`;
17 + console.log(query);
18 + let [row] = await connection.query(query);
19 + console.log(row[0]);
20 + return row[0];
21 + // , [id], (err, data) => {
22 + // console.log("44444444");
23 + // if (err) reject(`${err}`);
24 + // resolve(data[0]);
25 + // pool.releaseConnection(conn);
26 + // });
27 + }
28 +
29 + static async save (connection, userInfo) {
30 + const query = "INSERT INTO users(id, name, password) VALUES(?, ?, ?);";
31 + try {
32 + const [rows] = await connection.query({
33 + sql: query,
34 + timeout: 30000,
35 + values: [userInfo.id, userInfo.name, userInfo.password]
36 + });
37 + // console.log(fields);
38 + if (rows.affectedRows) {
39 + return {success: true};
40 + } else {
41 + return {success: false};
42 + }
43 + } catch (error) {
44 + console.log(error);
45 + }
46 + }
47 +
48 +}
49 +// static getUserInfo(id) {
50 +// return new Promise((resolve, reject) => {
51 +// const query = "SELECT * FROM users WHERE id = ?;";
52 +// pool.query(query, [id], (err, data) => {
53 +// if (err) reject(`${err}`);
54 +// // console.log(data[0]);
55 +// resolve(data[0]);
56 +// });
57 +// });
58 +// }
59 +
60 +module.exports = UserStorage;
...\ No newline at end of file ...\ No newline at end of file
1 +* {
2 + margin: 0;
3 + padding: 0;
4 +}
5 +
6 +html, body {
7 + height : 100%;
8 +}
9 +
10 +.wrapper {
11 + height : 100%;
12 + width: 100%;
13 + display: flex;
14 + flex-direction: column;
15 + overflow: hidden;
16 +}
17 +
18 +.user-container {
19 + background: rebeccapurple;
20 + flex: 1;
21 + display: flex;
22 + justify-content: flex-start;
23 + align-items: center;
24 + padding: 0.5rem;
25 +}
26 +
27 +.user-container .nickname {
28 + font-size : 14px;
29 + margin-right : 1.5rem;
30 + margin-left : 1rem;
31 + color:#fff;
32 +}
33 +
34 +.user-container input {
35 + border-radius: 3px;
36 + border: none;
37 + height: 80%;
38 +}
39 +
40 +.display-container {
41 + background: #D2D2FF;
42 + flex : 12;
43 + overflow-y:scroll;
44 +}
45 +
46 +.input-container {
47 + flex:1;
48 + display:flex;
49 + justify-content: stretch;
50 + align-items: stretch;
51 +}
52 +
53 +.input-container span {
54 + display: flex;
55 + justify-content: flex-start;
56 + align-items:center;
57 + padding: 0.3rem;
58 + width: 100%;
59 +}
60 +
61 +.chatting-input {
62 + font-size:12px;
63 + height:100%;
64 + flex:8;
65 + border:none;
66 +}
67 +
68 +.send-button {
69 + flex:1;
70 + background: rebeccapurple;
71 + color:#fff;
72 + border:none;
73 + height:100%;
74 + border-radius:3px;
75 +}
76 +
77 +.chatting-list li {
78 + width:50%;
79 + padding:0.3rem;
80 + display:flex;
81 + justify-content: flex-start;
82 + align-items:flex-end;
83 + margin-top:0.5rem;
84 +}
85 +
86 +.profile {
87 + display: flex;
88 + flex-direction: column;
89 + align-items: center;
90 + justify-content: center;
91 + flex: 1;
92 +}
93 +
94 +.profile .user {
95 + font-size: 10px;
96 + margin-bottom: 0.3rem;
97 +}
98 +
99 +.profile .image {
100 + border-radius: 50%;
101 + object-fit: cover;
102 + width: 50px;
103 + height: 50px;
104 +}
105 +
106 +.message {
107 + border-radius: 5px;
108 + padding: 0.5rem;
109 + font-size: 12px;
110 + margin: 0 5px;
111 + flex: 10;
112 +}
113 +
114 +.time {
115 + font-size: 10px;
116 + margin: 0 5px;
117 +}
118 +
119 +.sent {
120 + flex-direction: row-reverse;
121 + float: right;
122 +}
123 +
124 +.sent .message {
125 + background: #9986EE;
126 + color: #fff;
127 +}
128 +
129 +.received .message {
130 + background: #fff;
131 +}
...\ No newline at end of file ...\ No newline at end of file
1 +@font-face {
2 + font-family: 'Noto Sans KR', sans-serif;
3 + src: url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@700&display=swap');
4 + font-weight: normal;
5 + font-style: normal;
6 + }
7 +
8 + * {
9 + padding: 0;
10 + margin: 0;
11 + box-sizing: border-box;
12 + }
13 +
14 + html {
15 + font-size: 10px;
16 + font-family: 'Noto Sans KR', sans-serif;
17 + }
18 +
19 + nav {
20 + /* background-color: #e69a06; */
21 + }
22 +
23 + .nav-container {
24 + padding: 1rem 0;
25 + display: flex;
26 + flex-direction: row;
27 + justify-content: space-between;
28 + align-items: center;
29 + }
30 +
31 + .nav-title {
32 + font-size: 2.5rem;
33 + color :rebeccapurple;
34 + }
35 +
36 + .nav-contact {
37 + font-size: 1.5rem;
38 + border: 0;
39 + background: none;
40 + cursor: pointer;
41 + font-family: inherit;
42 + color :lightslategray;
43 + margin-right: 0px;
44 + }
45 +
46 + .category-title {
47 + font-size: 2rem;
48 + padding : 0 30%;
49 + }
50 +
51 + .category-list {
52 + padding: 15px 1rem;
53 + }
54 +
55 + .category-item {
56 + width: 24%;
57 + height: 5rem;
58 + background: none;
59 + border: none;
60 + font-family: inherit;
61 + font-size: 1.6rem;
62 + }
63 +
64 + .category-item:hover {
65 + color: #e69a06;
66 + cursor: pointer;
67 + }
68 +
69 + .inner {
70 + padding: 0 1.5rem;
71 + }
72 +
73 + @media all and (min-width: 1024px) {
74 + .inner {
75 + max-width: 1024px;
76 + margin: 0 auto;
77 + }
78 + }
79 +
80 + /* 카카오맵 CSS */
81 +
82 + body {
83 + height: 100vh;
84 + }
85 +
86 + nav {
87 + height: 59px;
88 + }
89 +
90 + main {
91 + padding-top: 1.5rem;
92 + height: calc(100% - 59px);
93 + display: flex;
94 + flex-direction: column;
95 + }
96 +
97 + #map {
98 + flex-grow: 1;
99 + width: 100%;
100 + height: 100%;
101 + }
...\ No newline at end of file ...\ No newline at end of file
1 +@import url(https://fonts.googleapis.com/css?family=Roboto:300);
2 +
3 +.login-page {
4 + width: 360px;
5 + padding: 12% 0 0;
6 + margin: auto;
7 +}
8 +.form {
9 + position: relative;
10 + z-index: 1;
11 + background: #FFFFFF;
12 + max-width: 360px;
13 + margin: 0 auto 100px;
14 + padding: 45px;
15 + text-align: center;
16 + box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2), 0 5px 5px 0 rgba(0, 0, 0, 0.24);
17 +}
18 +.form input {
19 + font-family: "Roboto", sans-serif;
20 + outline: 0;
21 + background: #f2f2f2;
22 + width: 100%;
23 + border: 0;
24 + margin: 0 0 15px;
25 + padding: 15px;
26 + box-sizing: border-box;
27 + font-size: 14px;
28 +}
29 +.form #button {
30 + font-family: "Roboto", sans-serif;
31 + text-transform: uppercase;
32 + outline: 0;
33 + background: rebeccapurple;
34 + width: 89%;
35 + border: 0;
36 + margin: 0 auto;
37 + padding: 15px;
38 + color: #FFFFFF;
39 + font-size: 14px;
40 + -webkit-transition: all 0.3 ease;
41 + transition: all 0.3 ease;
42 + cursor: pointer;
43 +}
44 +.form #button:hover,.form #button:active,.form #button:focus {
45 + background: rebeccapurple;
46 +}
47 +.form .message {
48 + margin: 15px 0 0;
49 + color: #b3b3b3;
50 + font-size: 12px;
51 +}
52 +.form .message a {
53 + color: rebeccapurple;
54 + text-decoration: none;
55 +}
56 +.form .register-form {
57 + display: none;
58 +}
59 +.container {
60 + position: relative;
61 + z-index: 1;
62 + max-width: 300px;
63 + margin: 0 auto;
64 +}
65 +.container:before, .container:after {
66 + content: "";
67 + display: block;
68 + clear: both;
69 +}
70 +.container .info {
71 + margin: 50px auto;
72 + text-align: center;
73 +}
74 +.container .info h1 {
75 + margin: 0 0 15px;
76 + padding: 0;
77 + font-size: 36px;
78 + font-weight: 300;
79 + color: #1a1a1a;
80 +}
81 +.container .info span {
82 + color: #4d4d4d;
83 + font-size: 12px;
84 +}
85 +.container .info span a {
86 + color: #000000;
87 + text-decoration: none;
88 +}
89 +.container .info span .fa {
90 + color: #EF3B3A;
91 +}
92 +
93 +/* #id::placeholder #password::placeholder {
94 + color: black;
95 + font-style: italic;
96 + font-weight: bold;
97 +} */
98 +
99 +body {
100 + background: rebeccapurple; /* fallback for old browsers */
101 + /* background: rebeccapurple; */
102 + background: linear-gradient(90deg, rebeccapurple 0%, rebeccapurple 0%);
103 + font-family: "Roboto", sans-serif;
104 + -webkit-font-smoothing: antialiased;
105 + -moz-osx-font-smoothing: grayscale;
106 +}
107 +
108 +/* Copyright (c) 2022 by Aigars Silkalns (https://codepen.io/colorlib/pen/rxddKy) */
...\ No newline at end of file ...\ No newline at end of file
1 +/*인포윈도우 설정*/
2 +.infowindow {
3 + width : 25rem;
4 + border : 1px solid black;
5 + border-radius: 5px;
6 + background-color : white;
7 +}
8 +
9 +.infowindow-title {
10 + font-size: 15px;
11 + color: rebeccapurple;
12 + font-weight: 600;
13 +}
14 +
15 +.infowindow-address {
16 + font-size: 8px;
17 +}
18 +
19 +.infowindow-btn {
20 + font-size: 8px;
21 +}
...\ No newline at end of file ...\ No newline at end of file
1 +@import url(https://fonts.googleapis.com/css?family=Roboto:300);
2 +
3 +.login-page {
4 + width: 360px;
5 + padding: 8% 0 0;
6 + margin: auto;
7 +}
8 +.form {
9 + position: relative;
10 + z-index: 1;
11 + background: #FFFFFF;
12 + max-width: 360px;
13 + margin: 0 auto 100px;
14 + padding: 45px;
15 + text-align: center;
16 + box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2), 0 5px 5px 0 rgba(0, 0, 0, 0.24);
17 +}
18 +.form input {
19 + font-family: "Roboto", sans-serif;
20 + outline: 0;
21 + background: #f2f2f2;
22 + width: 100%;
23 + border: 0;
24 + margin: 0 0 15px;
25 + padding: 15px;
26 + box-sizing: border-box;
27 + font-size: 14px;
28 +}
29 +.form #button {
30 + font-family: "Roboto", sans-serif;
31 + text-transform: uppercase;
32 + outline: 0;
33 + background: rebeccapurple;
34 + width: 89%;
35 + border: 0;
36 + margin: 0 auto;
37 + padding: 15px;
38 + color: #FFFFFF;
39 + font-size: 14px;
40 + -webkit-transition: all 0.3 ease;
41 + transition: all 0.3 ease;
42 + cursor: pointer;
43 +}
44 +.form #button:hover,.form #button:active,.form #button:focus {
45 + background: rebeccapurple;
46 +}
47 +.form .message {
48 + margin: 15px 0 0;
49 + color: #b3b3b3;
50 + font-size: 12px;
51 +}
52 +.form .message a {
53 + color: rebeccapurple;
54 + text-decoration: none;
55 +}
56 +.form .register-form {
57 + display: none;
58 +}
59 +.container {
60 + position: relative;
61 + z-index: 1;
62 + max-width: 300px;
63 + margin: 0 auto;
64 +}
65 +.container:before, .container:after {
66 + content: "";
67 + display: block;
68 + clear: both;
69 +}
70 +.container .info {
71 + margin: 50px auto;
72 + text-align: center;
73 +}
74 +.container .info h1 {
75 + margin: 0 0 15px;
76 + padding: 0;
77 + font-size: 36px;
78 + font-weight: 300;
79 + color: #1a1a1a;
80 +}
81 +.container .info span {
82 + color: #4d4d4d;
83 + font-size: 12px;
84 +}
85 +.container .info span a {
86 + color: #000000;
87 + text-decoration: none;
88 +}
89 +.container .info span .fa {
90 + color: #EF3B3A;
91 +}
92 +
93 +/* #id::placeholder #password::placeholder {
94 + color: black;
95 + font-style: italic;
96 + font-weight: bold;
97 +} */
98 +
99 +body {
100 + background: rebeccapurple; /* fallback for old browsers */
101 + /* background: rebeccapurple; */
102 + background: linear-gradient(90deg, rebeccapurple 0%, rebeccapurple 0%);
103 + font-family: "Roboto", sans-serif;
104 + -webkit-font-smoothing: antialiased;
105 + -moz-osx-font-smoothing: grayscale;
106 +}
107 +
108 +/* Copyright (c) 2022 by Aigars Silkalns (https://codepen.io/colorlib/pen/rxddKy) */
...\ No newline at end of file ...\ No newline at end of file
1 +/*********************************************************************************
2 + * 1. 지도 생성 및 확대 축소 컨트롤러
3 + */
4 +
5 + var container = document.getElementById('map'); //지도를 담을 영역의 DOM 레퍼런스
6 + var options = { //지도를 생성할 때 필요한 기본 옵션
7 + center: new kakao.maps.LatLng(37.248, 127.08), //지도의 중심좌표.
8 + level: 4 //지도의 레벨(확대, 축소 정도)
9 + };
10 +
11 + var map = new kakao.maps.Map(container, options); //지도 생성 및 객체 리턴
12 +
13 + // 확대 축소 컨트롤러
14 + var zoomControl = new kakao.maps.ZoomControl();
15 + map.addControl(zoomControl, kakao.maps.ControlPosition.RIGHT);
16 +
17 + /******************************************************************************
18 + * 2. 데이터 준비하기(제목, 주소, 카테고리)
19 + */
20 +
21 +// const dataSet = [
22 +// {
23 +// title: "희락돈까스",
24 +// address: "서울 영등포구 양산로 210",
25 +// category: "양식",
26 +// },
27 +// {
28 +// title: "즉석우동짜장",
29 +// address: "서울 영등포구 대방천로 260",
30 +// category: "한식",
31 +// },
32 +// {
33 +// title: "아카사카",
34 +// address: "서울 서초구 서초대로74길 23",
35 +// category: "일식",
36 +// }
37 +// ];
38 +
39 + async function getDataSet(category) {
40 + let qs = category;
41 + if(!qs) {
42 + qs = "";
43 + }
44 +
45 + const dataSet = await axios({
46 + method: "get", // http method
47 + url: `http://52.54.201.217:3000/restaurants?category=${qs}`,
48 + headers: {},
49 + data: {},
50 + });
51 + // console.log(dataSet);
52 +
53 + return dataSet.data.result;
54 +}
55 +
56 +// }
57 +
58 + getDataSet();
59 +
60 + /******************************************************************************
61 + * 3. 여러개 마커찍기
62 + */
63 +
64 + // 주소-좌표 변환 객체를 생성합니다
65 + var geocoder = new kakao.maps.services.Geocoder();
66 +
67 + function getCoordsByAddress(address) {
68 + return new Promise((resolve, reject) => {
69 + // 주소로 좌표를 검색합니다
70 + geocoder.addressSearch(address, function (result, status) {
71 + // 정상적으로 검색이 완료됐으면
72 + if (status === kakao.maps.services.Status.OK) {
73 + var coords = new kakao.maps.LatLng(result[0].y, result[0].x);
74 + return resolve(coords);
75 + }
76 + reject(new Error("getCoordsByAddress Error: not valid Address"));
77 + });
78 + });
79 + }
80 +
81 +// setMap(dataSet);
82 +
83 + /*
84 + *************************************************************
85 + 4. 마커에 인포윈도우 붙이기
86 + */
87 +
88 + function getContent(data) {
89 + // 인포윈도우 가공하기
90 + return `
91 + <div class="infowindow">
92 + <div class="infowindow-body">
93 + <h5 class="infowindow-title">${data.title}</h5>
94 + <p class="infowindow-address">${data.address}</p>
95 + <a href='/chat' class="infowindow-btn" target="_blank">채팅방이동</a>
96 + </div>
97 + </div>
98 + `;
99 + }
100 +
101 + async function setMap(dataSet) {
102 + for (var i = 0; i < dataSet.length; i++) {
103 + // 마커를 생성합니다
104 + let coords = await getCoordsByAddress(dataSet[i].address);
105 + var marker = new kakao.maps.Marker({
106 + map: map, // 마커를 표시할 지도
107 + position: coords,
108 + });
109 +
110 + markerArray.push(marker);
111 +
112 + // 마커에 표시할 인포윈도우를 생성합니다
113 + var infowindow = new kakao.maps.InfoWindow({
114 + content: getContent(dataSet[i]),// 인포윈도우에 표시할 내용
115 + });
116 +
117 + infowindowArray.push(infowindow);
118 +
119 + // 마커에 mouseover 이벤트와 mouseout 이벤트를 등록합니다
120 + // 이벤트 리스너로는 클로저를 만들어 등록합니다
121 + // for문에서 클로저를 만들어 주지 않으면 마지막 마커에만 이벤트가 등록됩니다
122 + kakao.maps.event.addListener(marker, 'click', makeOverListener(map, marker, infowindow, coords));
123 + kakao.maps.event.addListener(map, 'click', makeOutListener(infowindow));
124 + }
125 + }
126 +
127 + // 인포윈도우를 표시하는 클로저를 만드는 함수입니다
128 + function makeOverListener(map, marker, infowindow, coords) {
129 + return function() {
130 + // 1. 클릭시 다른 인포윈도우 닫기
131 + closeInfoWindow();
132 + infowindow.open(map, marker);
133 + // 2. 클릭한 곳으로 지도 중심 옮기기
134 + map.panTo(coords);
135 + };
136 + }
137 +
138 + let infowindowArray = [];
139 + function closeInfoWindow() {
140 + for (let infowindow of infowindowArray) {
141 + infowindow.close();
142 + }
143 + }
144 +
145 + // 인포윈도우를 닫는 클로저를 만드는 함수입니다
146 + function makeOutListener(infowindow) {
147 + return function() {
148 + infowindow.close();
149 + };
150 + }
151 +
152 + /*
153 + **********************************************
154 + 5. 카테고리 분류
155 + */
156 +
157 + // 카테고리
158 + const categoryMap = {
159 + korea: "한식",
160 + china: "중식",
161 + japan: "일식",
162 + america: "양식",
163 + wheat: "분식",
164 + meat: "구이",
165 + sushi: "회/초밥",
166 + etc: "기타",
167 + };
168 +
169 + const categoryList = document.querySelector(".category-list");
170 + categoryList.addEventListener("click", categoryHandler);
171 +
172 + async function categoryHandler(event) {
173 + const categoryId = event.target.id;
174 + const category = categoryMap[categoryId];
175 +
176 +
177 + try {
178 + // 데이터 분류
179 + let categorizedDataSet = await getDataSet(category);
180 +
181 + // 기존 마커 삭제
182 + closeMarker();
183 +
184 + // 기존 인포윈도우 닫기
185 + closeInfoWindow();
186 +
187 + setMap(categorizedDataSet);
188 +
189 + } catch (error) {
190 + console.error(error);
191 + }
192 + }
193 +
194 + let markerArray = [];
195 + function closeMarker() {
196 + for (marker of markerArray) {
197 + marker.setMap(null);
198 + }
199 + }
200 +
201 + async function setting() {
202 + try {
203 + const dataSet = await getDataSet();
204 + setMap(dataSet);
205 + } catch (error) {
206 + console.error(error);
207 + }
208 + }
209 +
210 + setting();
...\ No newline at end of file ...\ No newline at end of file
1 +"use strict";
2 +
3 +const socket = io.connect("http://52.54.201.217:3000/", {
4 + transports: ["websocket"],
5 +});
6 +const nickname = document.querySelector("#nickname")
7 +const chatlist = document.querySelector(".chatting-list")
8 +const chatInput = document.querySelector(".chatting-input")
9 +const sendButton = document.querySelector(".send-button")
10 +const displayContainer = document.querySelector(".display-container")
11 +
12 +chatInput.addEventListener("keypress", (event)=> {
13 + if(event.keyCode === 13) {
14 + send()
15 + }
16 +})
17 +
18 +function send() {
19 + const param = {
20 + name: nickname.value,
21 + msg: chatInput.value
22 + }
23 + socket.emit("chatting", param)
24 +}
25 +
26 +sendButton.addEventListener("click", send)
27 +
28 +socket.on("chatting", (data)=>{
29 + console.log(data)
30 + const {name, msg, time} = data;
31 + const item = new LiModel(name, msg, time);
32 + item.makeLi()
33 + displayContainer.scrollTo(0, displayContainer.scrollHeight)
34 +})
35 +
36 +//console.log(socket);
37 +
38 +function LiModel(name, msg, time) {
39 + this.name = name;
40 + this.msg = msg;
41 + this.time = time;
42 +
43 + this.makeLi = ()=>{
44 + const li = document.createElement("li");
45 + li.classList.add(nickname.value === this.name ? "sent":"received")
46 + const dom = `<span class="profile">
47 + <span class="user">${this.name}</span>
48 + <img class="image" src="https://placeimg.com/50/50/any" alt="any">
49 + </span>
50 + <span class="message">${this.msg}</span>
51 + <span class="time">${this.time}</span>`;
52 +
53 + li.innerHTML = dom;
54 + chatlist.appendChild(li)
55 + }
56 +}
...\ No newline at end of file ...\ No newline at end of file
1 +'use strict';
2 +
3 +const id = document.querySelector("#id"),
4 + password = document.querySelector("#password"),
5 + loginBtn = document.querySelector("#button");
6 +
7 +loginBtn.addEventListener("click", login);
8 +
9 +function login() {
10 + const req = {
11 + id : id.value,
12 + password : password.value,
13 + };
14 +
15 + // console.log("login value : ", id.value);
16 + fetch("/login", {
17 + method: "POST",
18 + headers: {
19 + "Content-Type": "application/json"
20 + },
21 + body: JSON.stringify(req),
22 + })
23 + .then((res) => res.json())
24 + .then((res) => {
25 + if (res.success) {
26 + //성공하면 이동
27 + location.href = "/";
28 + } else {
29 + alert(res.msg);
30 + }
31 + })
32 + .catch((err) => {
33 + console.error("로그인 중 에러 발생");
34 + });
35 +}
...\ No newline at end of file ...\ No newline at end of file
1 +'use strict';
2 +
3 +const id = document.querySelector("#id"),
4 + name = document.querySelector("#name"),
5 + password = document.querySelector("#password"),
6 + confirmPassword = document.querySelector("#confirm-password"),
7 + registerBtn = document.querySelector("#button");
8 +
9 +registerBtn.addEventListener("click", register);
10 +
11 +async function register() {
12 + if(!id.value) {
13 + return alert("아이디를 입력해주세요.")
14 + }
15 + if(!name.value) {
16 + return alert("이름을 입력해주세요.")
17 + }
18 + if(!password.value) {
19 + return alert("비밀번호를 입력해주세요.")
20 + }
21 + if(!confirmPassword.value) {
22 + return alert("비밀번호를 확인해주세요.")
23 + }
24 + if (password.value !== confirmPassword.value) {
25 + return alert("비밀번호가 일치하지 않습니다.")
26 + }
27 +
28 + const req = {
29 + id: id.value,
30 + name: name.value,
31 + password: password.value,
32 + };
33 +
34 + fetch("/register", {
35 + method: "POST",
36 + headers: {
37 + "Content-Type": "application/json",
38 + },
39 + body: JSON.stringify(req),
40 + })
41 + .then((res) => res.json())
42 + .then((res) => {
43 + if (res.success) {
44 + location.href = "/login";
45 + } else {
46 + if (res.err) return alert(res.err);
47 + alert(res.msg);
48 + }
49 + })
50 + .catch((err) => {
51 + console.error("회원가입 중 에러 발생");
52 + });
53 +
54 + // try {
55 + // const response = await fetch("/register", {
56 + // method: "POST",
57 + // headers: {
58 + // "Content-Type": "application/json"
59 + // },
60 + // body: JSON.stringify(req),
61 + // });
62 + // console.log("테스트 : ", response);
63 + // } catch(e) {
64 + // console.log(e);
65 + // }
66 +
67 +}
...\ No newline at end of file ...\ No newline at end of file
1 +"use strict";
2 +
3 +const User = require("../../models/User");
4 +// const Restaurant = require("../../models/Restaurant");
5 +
6 +const output = {
7 + hello: (req, res) => {
8 + res.render("home/index");
9 + },
10 +
11 + login: (req, res) => {
12 + res.render("home/login");
13 + },
14 +
15 + register: (req, res) => {
16 + res.render("home/register");
17 + },
18 +
19 + chat: (req, res) => {
20 + res.render("home/chat");
21 + },
22 +
23 + // restaurants: (req, res) => {
24 + // res.render("home/restaurants");
25 + // }
26 +};
27 +
28 +const process = {
29 + login: async (req, res) => {
30 + const user = new User(req.body);
31 + const response = await user.login();
32 + return res.json(response);
33 + },
34 +
35 + register: async (req, res) => {
36 + const user = new User(req.body);
37 + const response = await user.register();
38 + // console.log("req.body", req.body);
39 + // console.log(res.json(response));
40 + // console.log(res.json(response).statusCode); => 이거도 잘 찍혔음.
41 + return res.json(response);
42 + },
43 +
44 + // restaurants: async (req, res) => {
45 + // const restaurant = new Restaurant(req.body);
46 + // const response = await restaurant.restaurants();
47 + // return res.json(response);
48 + // },
49 +};
50 +
51 +module.exports = {
52 + output,
53 + process,
54 +};
...\ No newline at end of file ...\ No newline at end of file
1 +"use strict";
2 +
3 +const express = require("express");
4 +const router = express.Router();
5 +const jwtMiddleware = require("../../config/jwtMiddleware");
6 +// const Restaurant = require("../../models/Restaurant");
7 +
8 +const ctrl = require("./home.ctrl");
9 +const index = require("../../models/Restaurant");
10 +
11 +router.get("/", ctrl.output.hello);
12 +router.get("/login", ctrl.output.login);
13 +router.get("/register", ctrl.output.register);
14 +router.get("/restaurants", index.readRestaurants);
15 +router.get("/chat", ctrl.output.chat);
16 +// router.get("/restaurants", Restaurant.restaurants);
17 +// router.get("/restaurants", ctrl.output.restaurants);
18 +
19 +router.post("/login", ctrl.process.login);
20 +router.post("/register", ctrl.process.register);
21 +
22 +module.exports = router;
...\ No newline at end of file ...\ No newline at end of file
1 +<!DOCTYPE html>
2 +<html lang="en">
3 + <head>
4 + <meta charset="UTF-8" />
5 + <meta http-equiv="X-UA-Compatible" content="IE=edge" />
6 + <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7 + <title>채팅방</title>
8 + <link rel="stylesheet" href="/css/home/chat.css" />
9 + </head>
10 + <body>
11 + <div class="wrapper">
12 + <div class="user-container">
13 + <lable class="nickname" for="nickname">닉네임설정</lable>
14 + <input type="text" id="nickname" />
15 + </div>
16 + <div class="display-container">
17 + <ul class="chatting-list"></ul>
18 + </div>
19 + <div class="input-container">
20 + <span>
21 + <input type="text" class="chatting-input" />
22 + <button class="send-button">전송</button>
23 + </span>
24 + </div>
25 + </div>
26 + <script
27 + src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.5.1/socket.io.js"
28 + integrity="sha512-9mpsATI0KClwt+xVZfbcf2lJ8IFBAwsubJ6mI3rtULwyM3fBmQFzj0It4tGqxLOGQwGfJdk/G+fANnxfq9/cew=="
29 + crossorigin="anonymous"
30 + referrerpolicy="no-referrer"
31 + ></script>
32 + <script src="/js/home/chat.js"></script>
33 + </body>
34 +</html>
1 +<!DOCTYPE html>
2 +<html lang="en">
3 + <head>
4 + <meta charset="UTF-8" />
5 + <meta http-equiv="X-UA-Compatible" content="IE=edge" />
6 + <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7 + <title>맛집지도</title>
8 + <meta name="author" content="양주미" />
9 + <meta name="description" content="맛집지도 서비스" />
10 + <meta name="keywords" content="맛집지도, 맛집추천, 실시간채팅" />
11 + <link rel="stylesheet" href="/css/home/index.css" />
12 + <link rel="stylesheet" href="/css/home/map.css" />
13 + </head>
14 + <body>
15 + <nav>
16 + <div class="inner">
17 + <div class="nav-container">
18 + <h1 class="nav-title">맛집지도</h1>
19 + <button class="nav-contact">Contact : balljm@naver.com</button>
20 + </div>
21 + </div>
22 + </nav>
23 +
24 + <main>
25 + <section id="category">
26 + <div class="inner">
27 + <div class="category-container">
28 + <h2 class="category-title">💜맛집지도 카테고리를 선택해보세요💜</h2>
29 + <div class="category-list">
30 + <button class="category-item" id="korea">한식🍚</button>
31 + <button class="category-item" id="china">중식🍜</button>
32 + <button class="category-item" id="japan">일식🍙</button>
33 + <button class="category-item" id="america">양식🍝</button>
34 + <button class="category-item" id="wheat">분식🍭</button>
35 + <button class="category-item" id="meat">구이🍖</button>
36 + <button class="category-item" id="sushi">회/초밥🍣</button>
37 + <button class="category-item" id="etc">기타🍴</button>
38 + </div>
39 + </div>
40 + </div>
41 + </section>
42 + <!-- 카테고리 -->
43 + <div id="map" class="inner"></div>
44 +
45 + <!-- 카카오지도 -->
46 + </main>
47 +
48 + <script
49 + type="text/javascript"
50 + src="//dapi.kakao.com/v2/maps/sdk.js?appkey=e55f753363b95e27b799aa6286a6c398&libraries=services"
51 + ></script>
52 + <script
53 + src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.0.0-alpha.1/axios.min.js"
54 + integrity="sha512-xIPqqrfvUAc/Cspuj7Bq0UtHNo/5qkdyngx6Vwt+tmbvTLDszzXM0G6c91LXmGrRx8KEPulT+AfOOez+TeVylg=="
55 + crossorigin="anonymous"
56 + referrerpolicy="no-referrer"
57 + ></script>
58 + <script src="/js/home/axios-index.js"></script>
59 + </body>
60 +</html>
1 +<!DOCTYPE html>
2 +<html lang="ko">
3 + <head>
4 + <meta charset="UTF-8" />
5 + <meta http-equiv="X-UA-Compatible" content="IE=edge" />
6 + <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7 + <link rel="stylesheet" href="/css/home/login.css" />
8 + <script src="/js/home/login.js" defer></script>
9 + <title>로그인</title>
10 + </head>
11 + <body>
12 + <div class="login-page">
13 + <div class="form">
14 + <!-- <form class="register-form">
15 + <input type="text" placeholder="name" />
16 + <input type="password" placeholder="password" />
17 + <input type="text" placeholder="email address" />
18 + <button>create</button>
19 + <p class="message">Already registered? <a href="#">Sign In</a></p>
20 + </form> -->
21 + <form class="login-form">
22 + <input id="id" type="text" placeholder="아이디" />
23 + <input id="password" type="password" placeholder="비밀번호" />
24 + <p id="button">LOGIN</p>
25 + <p class="message">
26 + 계정이 없으신가요? <a href="/register">회원가입</a>
27 + </p>
28 + </form>
29 + </div>
30 + </div>
31 + </body>
32 +</html>
33 +
34 +<!-- Copyright (c) 2022 by Aigars Silkalns (https://codepen.io/colorlib/pen/rxddKy) -->
1 +<!DOCTYPE html>
2 +<html lang="ko">
3 + <head>
4 + <meta charset="UTF-8" />
5 + <meta http-equiv="X-UA-Compatible" content="IE=edge" />
6 + <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7 + <link rel="stylesheet" href="/css/home/register.css" />
8 + <script src="/js/home/register.js" defer></script>
9 + <title>회원가입</title>
10 + </head>
11 + <body>
12 + <div class="login-page">
13 + <div class="form">
14 + <!-- <form class="register-form">
15 + <input type="text" placeholder="name" />
16 + <input type="password" placeholder="password" />
17 + <input type="text" placeholder="email address" />
18 + <button>create</button>
19 + <p class="message">Already registered? <a href="#">Sign In</a></p>
20 + </form> -->
21 + <form class="login-form">
22 + <input id="id" type="text" placeholder="아이디" />
23 + <input id="name" type="text" placeholder="이름" />
24 + <input id="password" type="password" placeholder="비밀번호" />
25 + <input
26 + id="confirm-password"
27 + type="password"
28 + placeholder="비밀번호 확인"
29 + />
30 + <p id="button">SIGN UP</p>
31 + <p class="message">계정이 있으신가요? <a href="/login">로그인</a></p>
32 + </form>
33 + </div>
34 + </div>
35 + </body>
36 +</html>
37 +
38 +<!-- Copyright (c) 2022 by Aigars Silkalns (https://codepen.io/colorlib/pen/rxddKy) -->
1 +{
2 + "lockfileVersion": 1
3 +}