임태민

Merge branch 'release'

1 +# 📒 Mapmory
2 +
3 +#### Mapmory provides location-based recording sevices utilizing Google Maps API.
4 +
5 +
6 +### Mapmory functions
7 +-----------------
8 +- [x] Register
9 +- [x] Log in/out
10 +- [x] Member information edit
11 +- [x] Create your memories(With Title, Date, Author, Address)
12 +- [x] Delete your memories
13 +- [x] Save your memory with map
14 +- [x] Show your memory with map
15 +- [x] Edit your memory with map
16 +- [x] Search your memories
17 +
18 +
19 +### Stack
20 +-----------------
21 ++ Front end : EJS template engine
22 ++ Back end : Express/NodeJS
23 ++ Database : Mongo DB
24 ++ Server : AWS EC2
25 +
26 +
27 +## ✏️ Quick Start (build, install, setup manual)
28 +
29 + $ git clone http://khuhub.khu.ac.kr/2017101294/Mapmory.git
30 + $ npm install
31 + $ node index.js
32 +
33 +If it does not work well
34 +
35 + $ npm install nodemon-g
36 +
37 +At package.json, add ``` "start" : "nodemon index.js" ```
38 +
39 + "scripts": {
40 + "test": "echo \"Error: no test specified\" && exit 1",
41 + "start" : "nodemon index.js"
42 + }
43 +
44 + $ npm start
45 +
46 +
47 +### Dependency
48 +-----------------
49 ++ bcryptjs : 2.4.3,
50 ++ body-parser : 1.19.0,
51 ++ connect-flash : 0.1.1,
52 ++ ejs : 3.1.6,
53 ++ express : 4.17.1,
54 ++ express-session : 1.17.1,
55 ++ method-override : 3.0.0,
56 ++ mongoose : 5.12.8,
57 ++ passport : 0.4.1,
58 ++ passport-local : 1.0.0
59 +
60 +
61 +### 👬 Team members
62 +-----------------
63 ++ Im Taemin (@devTaemin)
64 ++ Hong Jiyoon (@fheldgktpdy)
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -16,7 +16,7 @@ mongoose.set('useCreateIndex', true); ...@@ -16,7 +16,7 @@ mongoose.set('useCreateIndex', true);
16 mongoose.set('useUnifiedTopology', true); 16 mongoose.set('useUnifiedTopology', true);
17 17
18 // Connect DB environment variable 18 // Connect DB environment variable
19 -mongoose.connect('mongodb+srv://Mapmory_admin:admin@cluster0.ncnjj.mongodb.net/Project-Mapmory?retryWrites=true&w=majority'); 19 +mongoose.connect('mongodbkey입력하는곳');
20 20
21 // Store DB in the variable 'db' 21 // Store DB in the variable 'db'
22 var db = mongoose.connection; 22 var db = mongoose.connection;
......
...@@ -7,7 +7,7 @@ var postSchema = mongoose.Schema({ ...@@ -7,7 +7,7 @@ var postSchema = mongoose.Schema({
7 address:{type:String, required:[true, 'address is required!']}, 7 address:{type:String, required:[true, 'address is required!']},
8 body:{type:String, required:[true, 'Content is required!']}, 8 body:{type:String, required:[true, 'Content is required!']},
9 author:{type:mongoose.Schema.Types.ObjectId, ref:'user', required:true}, 9 author:{type:mongoose.Schema.Types.ObjectId, ref:'user', required:true},
10 - createdAt:{type:Date, default:Date.now()}, 10 + createdAt:{type:Date, default:Date.now},
11 updatedAt:{type:Date} 11 updatedAt:{type:Date}
12 }); 12 });
13 13
......
...@@ -6,13 +6,31 @@ var util = require('../util'); ...@@ -6,13 +6,31 @@ var util = require('../util');
6 6
7 7
8 // Post home 8 // Post home
9 -router.get('/', function(req, res){ 9 +router.get('/', async function(req, res){
10 - Post.find({}) 10 + // Paging 기능 추가
11 - .populate('author') 11 + var page = Math.max(1, parseInt(req.query.page));
12 - .sort('-createdAt') 12 + var limit = Math.max(1, parseInt(req.query.limit));
13 - .exec(function(err, posts){ 13 + page = !isNaN(page)?page:1;
14 - if(err){return res.json(err)}; 14 + limit = !isNaN(limit)?limit:10;
15 - res.render('posts/index', {posts:posts}); 15 +
16 + var searchQuery = createSearchQuery(req.query);
17 + var skip = (page-1)*limit;
18 + var count = await Post.countDocuments(searchQuery);
19 + var maxPage = Math.ceil(count/limit);
20 + var posts = await Post.find(searchQuery)
21 + .populate('author')
22 + .sort('-createdAt')
23 + .skip(skip)
24 + .limit(limit)
25 + .exec();
26 +
27 + res.render('posts/index', {
28 + posts:posts,
29 + currentPage:page,
30 + maxPage:maxPage,
31 + limit:limit,
32 + searchType:req.query.searchType,
33 + searchText:req.query.searchText
16 }); 34 });
17 }); 35 });
18 36
...@@ -97,5 +115,23 @@ function checkPermission(req, res, next){ ...@@ -97,5 +115,23 @@ function checkPermission(req, res, next){
97 }); 115 });
98 } 116 }
99 117
118 +
119 +// Search function
120 +function createSearchQuery(queries){
121 + var searchQuery = {};
122 + if(queries.searchType && queries.searchText && queries.searchText.length >= 3){
123 + var searchTypes = queries.searchType.toLowerCase().split(',');
124 + var postQueries = [];
125 + if(searchTypes.indexOf('title')>=0){
126 + postQueries.push({ title: { $regex: new RegExp(queries.searchText, 'i') } });
127 + }
128 + if(searchTypes.indexOf('body')>=0){
129 + postQueries.push({ body: { $regex: new RegExp(queries.searchText, 'i') } });
130 + }
131 + if(postQueries.length > 0) searchQuery = {$or:postQueries};
132 + }
133 + return searchQuery;
134 +}
135 +
100 // Export module 136 // Export module
101 module.exports = router; 137 module.exports = router;
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -33,6 +33,26 @@ util.noPermission = function(req, res){ ...@@ -33,6 +33,26 @@ util.noPermission = function(req, res){
33 res.redirect('/login'); 33 res.redirect('/login');
34 } 34 }
35 35
36 +// 검색 기능 추가
37 +util.getPostQueryString = function(req, res, next){
38 + res.locals.getPostQueryString = function(isAppended=false, overwrites={}){
39 + var queryString = '';
40 + var queryArray = [];
41 + var page = overwrites.page?overwrites.page:(req.query.page?req.query.page:'');
42 + var limit = overwrites.limit?overwrites.limit:(req.query.limit?req.query.limit:'');
43 + var searchType = overwrites.searchType?overwrites.searchType:(req.query.searchType?req.query.searchType:'');
44 + var searchText = overwrites.searchText?overwrites.searchText:(req.query.searchText?req.query.searchText:'');
36 45
46 + if(page) queryArray.push('page='+page);
47 + if(limit) queryArray.push('limit='+limit);
48 + if(searchType) queryArray.push('searchType='+searchType);
49 + if(searchText) queryArray.push('searchText='+searchText);
50 +
51 + if(queryArray.length>0) queryString = (isAppended?'&':'?') + queryArray.join('&');
52 +
53 + return queryString;
54 + }
55 + next();
56 +}
37 57
38 module.exports = util; 58 module.exports = util;
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -135,7 +135,7 @@ ...@@ -135,7 +135,7 @@
135 135
136 <!-- Async script executes immediately and must be after any DOM elements used in callback. --> 136 <!-- Async script executes immediately and must be after any DOM elements used in callback. -->
137 <script 137 <script
138 - src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDK6K4iDdo9cKQdrNoOJaaYg29nEG0BIjw&callback=initMap&libraries=&v=weekly" 138 + src="https://maps.googleapis.com/maps/api/js?key=googlekey값을 입력하세요&callback=initMap&libraries=&v=weekly"
139 async 139 async
140 ></script> 140 ></script>
141 </body> 141 </body>
......
...@@ -142,7 +142,7 @@ ...@@ -142,7 +142,7 @@
142 142
143 <!-- Async script executes immediately and must be after any DOM elements used in callback. --> 143 <!-- Async script executes immediately and must be after any DOM elements used in callback. -->
144 <script 144 <script
145 - src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDK6K4iDdo9cKQdrNoOJaaYg29nEG0BIjw&callback=initMap&libraries=&v=weekly" 145 + src="https://maps.googleapis.com/maps/api/js?key=googlekey값을 입력하세요&callback=initMap&libraries=&v=weekly"
146 async 146 async
147 ></script> 147 ></script>
148 148
......
...@@ -47,15 +47,49 @@ ...@@ -47,15 +47,49 @@
47 </tr> 47 </tr>
48 <% }) %> 48 <% }) %>
49 </tbody> 49 </tbody>
50 -
51 </table> 50 </table>
52 51
53 - </div> 52 + <nav class="nav justify-content-center bg-light">
54 - 53 + <%
55 - 54 + var offset = 2;
56 - 55 + var previousBtnEnabled = currentPage>1;
56 + var nextBtnEnabled = currentPage<maxPage;
57 + %>
58 + <ul class="pagination pagination-sm justify-content-center align-items-center h-100 mb-0">
59 + <li class="page-item <%= previousBtnEnabled?'':'disabled' %>">
60 + <a class="page-link" href="/posts?page=<%= currentPage-1 %>&limit=<%= limit %>"<%= previousBtnEnabled?'':'tabindex=-1' %></a>
61 + </li>
62 + <% for(i=1;i<=maxPage;i++){ %>
63 + <% if(i==1 || i==maxPage || (i>=currentPage-offset && i<=currentPage+offset)){ %>
64 + <li class="page-item <%= currentPage==i?'active':'' %>"><a class="page-link" href="/posts?page=<%= i %>&limit=<%= limit %>"><%= i %></a></li>
65 + <% } else if(i==2 || i==maxPage-1){ %>
66 + <li><a class="page-link">...</a></li>
67 + <% } %>
68 + <% } %>
69 + <li class="page-item <%= nextBtnEnabled?'':'disabled' %>">
70 + <a class="page-link" href="/posts?page=<%= currentPage+1 %>&limit=<%= limit %>"<%= nextBtnEnabled?'':'tabindex=-1' %></a>
71 + </li>
72 + </ul>
73 + </nav>
57 74
75 +
76 + <form action="/posts" method="get" style="float:right">
77 + <div class="form-row">
78 + <div class="form-group">
79 + <div class="input-group">
80 + <select name="searchType" class="custom-select">
81 + <option value="title" <%= searchType=='title'?'selected':'' %>>Title</option>
82 + </select>
83 + <input minLength="3" type="text" name="searchText" value="<%= searchText %>">
84 + <div class="input-group-append">
85 + <button class="btn btn-outline-primary" type="submit">Search</button>
86 + </div>
87 + </div>
88 + </div>
89 + </div>
90 + </form>
58 91
92 + </div>
59 </div> 93 </div>
60 </body> 94 </body>
61 </html> 95 </html>
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -13,12 +13,13 @@ ...@@ -13,12 +13,13 @@
13 <ol class="breadcrumb p-1 pl-2 pr-2"> 13 <ol class="breadcrumb p-1 pl-2 pr-2">
14 <li class="breadcrumb-item"><a href="/">Home</a></li> 14 <li class="breadcrumb-item"><a href="/">Home</a></li>
15 <li class="breadcrumb-item"><a href="/posts">Board</a></li> 15 <li class="breadcrumb-item"><a href="/posts">Board</a></li>
16 - <li class="breadcrumb-item active" aria-current="page"><%= post.title %></li> 16 + <li class="breadcrumb-item active" aria-current="page"><%= post.address %></li>
17 </ol> 17 </ol>
18 </nav> 18 </nav>
19 19
20 <div class="card"> 20 <div class="card">
21 - <h5 class="card-header p-2" style="font-weight: bold; font-family: 'Archivo', sans-serif;"><%= post.title %></h5> 21 + <h5 class="card-header p-2" style="font-weight: bold; font-family: 'Archivo', sans-serif; background-color:goldenrod;"><%= post.title %></h5>
22 + <!-- <h5 class="card-header p-1" style="font-family: 'Archivo', sans-serif;"><%= post.address %></h5> -->
22 <div class="row"> <!-- 1 --> 23 <div class="row"> <!-- 1 -->
23 24
24 <div class="col-md-7 col-lg-8 col-xl-9 order-sm-2 order-md-1"> <!-- 1 --> 25 <div class="col-md-7 col-lg-8 col-xl-9 order-sm-2 order-md-1"> <!-- 1 -->
...@@ -45,7 +46,7 @@ ...@@ -45,7 +46,7 @@
45 <% if(isAuthenticated && post.author && currentUser.id == post.author.id){ %> <!-- 1 --> 46 <% if(isAuthenticated && post.author && currentUser.id == post.author.id){ %> <!-- 1 -->
46 <a class="btn btn-outline-primary" href="/posts/<%= post._id %>/edit">Edit</a> 47 <a class="btn btn-outline-primary" href="/posts/<%= post._id %>/edit">Edit</a>
47 <form action="/posts/<%= post._id %>?_method=delete" method="post" class="d-inline"> 48 <form action="/posts/<%= post._id %>?_method=delete" method="post" class="d-inline">
48 - <a class="btn btn-outline-primary" href="javascript:void(0)" onclick="confirm('Do you want to delete this?')?this.parentElement.submit():null;">Delete</a> 49 + <a class="btn btn-outline-primary" href="javascript:void(0)" onclick="confirm('기록을 삭제하시겠습니까?')?this.parentElement.submit():null;">Delete</a>
49 </form> 50 </form>
50 <% } %> 51 <% } %>
51 </div> 52 </div>
......