-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy path11-notifications.md.erb
280 lines (219 loc) · 11 KB
/
11-notifications.md.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
---
title: 알림(Notification)
slug: notifications
date: 0011/01/01
number: 11
contents: 사용자에게 다른 사용자들의 행위를 알리는 알림 컬렉션을 추가한다.|특정 사용자와 연관있는 알림들만을 공유하는 방법을 배운다.|미티어 발행과 구독에 대하여 더 상세하게 알아본다.
paragraphs: 25
---
이제 사용자들은 서로의 post에 댓글을 달 수 있게 되었으므로, 대화가 시작되었음을 각 사용자들에게 알려주면 좋을 것이다.
그렇게 하기 위해서, post의 작성자에게 댓글이 추가되었음을 알리고 댓글을 보기 위한 링크를 제공할 것이다.
이것이야말로 미티어가 진짜로 빛나는 기능이다: 왜냐면 미티어는 기본적으로 실시간이기 때문에 이런 알림을 _즉시_ 보여줄 것이다. 우리는 사용자가 페이지를 새로고침하거나 어떤 식으로는 확인하느라 기다리게 하지 않는다. 단지 어떤 특별한 코드를 작성하지 않고도 새 알림 메시지가 간단하게 나타나게 할 수 있다.
### 알림 생성하기
우리는 작성한 post에 누군가 댓글을 달면 알림을 주게 하려고 한다. 나중에, 알림은 많은 다른 상황에도 확장될 수 있다. 그러나 지금은 사용자에게 일어나고 있는 것을 알려주는 것으로 충분할 것이다.
`Notifications` 컬렉션을 만들고, 작성한 post에 새로운 댓글이 등록될 때마다 이에 대응하는 알림을 삽입하는 `createCommentNotification` 함수를 함께 만든다.
우리가 클라이언트에서 알림을 갱신하기 때문에, `allow` 호출을 하는 것을 반드시 확인해야 한다. 그래서 우리는 다음 사항을 체크한다:
- `update` 호출을 하는 사용자가 수정될 알림에 대한 소유권을 가지고 있는가
- 그 사용자가 단일 필드만을 수정하려고 하는가
- 그 단일 필드가 알림의 `read` 속성인가
~~~js
Notifications = new Mongo.Collection('notifications');
Notifications.allow({
update: function(userId, doc, fieldNames) {
return ownsDocument(userId, doc) &&
fieldNames.length === 1 && fieldNames[0] === 'read';
}
});
createCommentNotification = function(comment) {
var post = Posts.findOne(comment.postId);
if (comment.userId !== post.userId) {
Notifications.insert({
userId: post.userId,
postId: post._id,
commentId: comment._id,
commenterName: comment.author,
read: false
});
}
};
~~~
<%= caption "lib/collections/notifications.js" %>
Posts나 Comments와 마찬가지로, 이 `Notifications` 컬렉션은 클라이언트와 서버에 의해 공유될 것이다. 사용자가 알림을 열람했으면 notification을 갱신해야 하므로, 또한 수정을 가능하게 하되, 사용자가 소유한 데이터로 수정 권한을 한정한다.
또한, 사용자가 댓글을 추가한 post를 바라보는 간단한 함수를 만들어 그곳에서 알림을 받아야 할 사람들을 찾아서 새로운 알림을 등록할 것이다.
우리는 이미 서버쪽 메서드로 comment를 생성하고 있다. 그래서 이 함수를 호출하는 메서드를 추가하면 된다. 우리는 `return Comments.insert(comment);` 코드를 `comment._id = Comments.insert(comment)`로 바꾸어 새로 생성된 comment의 `_id`를 변수에 저장한다. 그리고 `createCommentNotification` 함수를 호출한다:
~~~js
Comments = new Mongo.Collection('comments');
Meteor.methods({
comment: function(commentAttributes) {
//...
comment = _.extend(commentAttributes, {
userId: user._id,
author: user.username,
submitted: new Date()
});
// update the post with the number of comments
Posts.update(comment.postId, {$inc: {commentsCount: 1}});
// create the comment, save the id
comment._id = Comments.insert(comment);
// now create a notification, informing the user that there's been a comment
createCommentNotification(comment);
return comment._id;
}
});
~~~
<%= caption "lib/collections/comments.js" %>
<%= highlight "17~123" %>
또한 notification을 발행하고 클라이언트에서 구독한다:
~~~js
Meteor.publish('posts', function() {
return Posts.find();
});
Meteor.publish('comments', function(postId) {
check(postId, String);
return Comments.find({postId: postId});
});
Meteor.publish('notifications', function() {
return Notifications.find();
});
~~~
<%= caption "server/publications.js" %>
<%= highlight "10~12" %>
그리고 클라이언트에서 구독한다:
~~~js
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
notFoundTemplate: 'notFound',
waitOn: function() {
return [Meteor.subscribe('posts'), Meteor.subscribe('notifications')]
}
});
~~~
<%= caption "lib/router.js" %>
<%= highlight "6" %>
<%= commit "11-1", "기본적인 Notifications 컬렉션을 추가했다." %>
### 알림 보이기
이제 계속해서 header에 알림 목록을 추가한다.
~~~html
<template name="header">
<nav class="navbar navbar-default" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navigation">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="{{pathFor 'postsList'}}">Microscope</a>
</div>
<div class="collapse navbar-collapse" id="navigation">
<ul class="nav navbar-nav">
{{#if currentUser}}
<li>
<a href="{{pathFor 'postSubmit'}}">Submit Post</a>
</li>
<li class="dropdown">
{{> notifications}}
</li>
{{/if}}
</ul>
<ul class="nav navbar-nav navbar-right">
{{> loginButtons}}
</ul>
</div>
</div>
</nav>
</template>
~~~
<%= caption "client/templates/includes/header.html" %>
<%= highlight "15~22" %>
그리고 `notifications`와 `notification` 템플릿(이들은 하나의 `notifications.html`파일에 작성한다)을 만든다:
~~~html
<template name="notifications">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
Notifications
{{#if notificationCount}}
<span class="badge badge-inverse">{{notificationCount}}</span>
{{/if}}
<b class="caret"></b>
</a>
<ul class="notification dropdown-menu">
{{#if notificationCount}}
{{#each notifications}}
{{> notificationItem}}
{{/each}}
{{else}}
<li><span>No Notifications</span></li>
{{/if}}
</ul>
</template>
<template name="notificationItem">
<li>
<a href="{{notificationPostPath}}">
<strong>{{commenterName}}</strong> commented on your post
</a>
</li>
</template>
~~~
<%= caption "client/templates/notifications/notifications.html" %>
계획은 각 알림은 댓글이 등록된 post에 대한 링크 정보를 담고, 댓글을 한 사용자의 이름을 담는 것이다.
다음, 매니저에서 올바른 알림 목록을 추출하고, 사용자가 링크를 클릭할 때, notification 정보를 "read"로 갱신한다.
~~~js
Template.notifications.helpers({
notifications: function() {
return Notifications.find({userId: Meteor.userId(), read: false});
},
notificationCount: function(){
return Notifications.find({userId: Meteor.userId(), read: false}).count();
}
});
Template.notificationItem.helpers({
notificationPostPath: function() {
return Router.routes.postPage.path({_id: this.postId});
}
});
Template.notificationItem.events({
'click a': function() {
Notifications.update(this._id, {$set: {read: true}});
}
});
~~~
<%= caption "client/templates/notifications/notifications.js" %>
<%= commit "11-2", "헤더에서 알림을 보여준다." %>
알림 기능이 오류 기능과 크게 다르지 않다고 생각할 지도 모르겠다. 사실이다. 그 구조에서 유사한 점이 있다. 그렇지만 핵심 차이는: 클라이언트-서버 동기화 상태로 컬렉션을 만들었다는 점이다. 이 의미는 알림 기능이 *영구적(persistent)*이라는 것인데, 동일한 사용자 계정이면 다른 브라우저나 다른 단말기로 접속해도 유지된다는 것이다.
직접 해보자: 두 번째 브라우저(이를테면 파이어폭스)을 열고, 새 계정을 만든다음, 기존 계정(이미 크롬으로 열고 있는)으로 작성한 post에 comment를 등록한다. 그러면 다음과 같은 화면을 보게 될 것이다:
<%= screenshot "11-1", "알림 보이기." %>
### 알림에 대한 접근제어
알림은 잘 작동한다. 그런데, 한 가지 작은 문제가 있다: 알림은 공개되어 있다.
만약 두 번째 브라우저가 열려 있다면, 아래 코드를 브라우저 콘솔에서 실행해보라:
~~~js
❯ Notifications.find().count();
1
~~~
<%= caption "브라우저 콘솔" %>
이 (*댓글을 등록한*) 새로운 사용자는 어떤 알림도 받지 않은 상태이어야 한다. 여기서 보는 알림은 (post를 등록한) *원래* 사용자에 속하는 `Notifications` 컬렉션의 정보이다.
잠재적인 프라이버시 이슈는 제쳐 놓더라도, 모든 다른 사용자의 브라우저에 모든 사용자의 알림이 로드되어 공개되도록 할 수는 없다. 충분히 큰 사이트에서라면, 이것은 브라우저의 메모리 문제를 일으킬 수 있고, 심각한 성능 문제를 일으킬 수 있다.
이 이슈는 **발행**으로 해결한다. 각각의 브라우저와 공유할 컬렉션의 정확한 부분을 지정하는 발행을 사용한다.
이를 처리하기 위해서, 발행에서 `Notifications.find()`와는 다른 커서를 리턴할 필요가 있다. 말하자면, 현재 사용자의 알림에 대응하는 커서를 리턴하려고 한다.
이렇게 하는 방법은 `publish` 함수가 `this.userId`에서 이용가능한 현재 사용자의 `_id`를 가지고 있기 때문에, 그대로 하면 된다:
~~~js
Meteor.publish('notifications', function() {
return Notifications.find({userId: this.userId, read: false});
});
~~~
<%= caption "server/publications.js" %>
<%= commit "11-3", "사용자에게 해당하는 알림만을 동기화한다." %>
이제 두 개의 브라우저 창에서 확인해보면, 두 개의 다른 notification 컬렉션을 보게 될 것이다:
~~~js
❯ Notifications.find().count();
1
~~~
<%= caption "브라우저 콘솔 (사용자 1)" %>
~~~js
❯ Notifications.find().count();
0
~~~
<%= caption "브라우저 콘솔 (사용자 2)" %>
사실, 알림 목록은 로그인 상태에 따라서 변할 수 있다. 그것은 사용자 계정이 변할 때마다 발행이 갱신되기 때문이다.
우리의 앱은 점점 기능이 많아져 간다. 그리고 더 많은 사용자가 가입하고, post를 등록하게 되면 끝없는 홈페이지가 될 위험에 빠진다. 이를 조정하려고 다음장에서 페이징 기법을 구현할 것이다.