mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge branch 'pr/221'
This commit is contained in:
commit
5c5888b5dc
16 changed files with 275 additions and 9 deletions
|
@ -0,0 +1,16 @@
|
|||
|
||||
NotificationsHandler = require("./NotificationsHandler")
|
||||
|
||||
module.exports =
|
||||
|
||||
groupPlan: (user, licence)->
|
||||
key : "join-sub-#{licence.subscription_id}"
|
||||
|
||||
create: (callback = ->)->
|
||||
messageOpts =
|
||||
groupName: licence.name
|
||||
subscription_id: licence.subscription_id
|
||||
NotificationsHandler.createNotification user._id, key, "joinSubscriptionInvite", messageOpts, callback
|
||||
|
||||
read: (callback = ->)->
|
||||
NotificationsHandler.markAsReadWithKey user._id, key, callback
|
|
@ -0,0 +1,19 @@
|
|||
NotificationsHandler = require("./NotificationsHandler")
|
||||
logger = require("logger-sharelatex")
|
||||
_ = require("underscore")
|
||||
|
||||
module.exports =
|
||||
|
||||
getAllUnreadNotifications: (req, res)->
|
||||
NotificationsHandler.getUserNotifications req.session.user._id, (err, unreadNotifications)->
|
||||
unreadNotifications = _.map unreadNotifications, (notification)->
|
||||
notification.html = req.i18n.translate(notification.templateKey, notification.messageOpts)
|
||||
return notification
|
||||
res.send(unreadNotifications)
|
||||
|
||||
markNotificationAsRead: (req, res)->
|
||||
user_id = req.session.user._id
|
||||
notification_id = req.params.notification_id
|
||||
NotificationsHandler.markAsRead user_id, notification_id, ->
|
||||
res.send()
|
||||
logger.log user_id:user_id, notification_id:notification_id, "mark notification as read"
|
|
@ -0,0 +1,50 @@
|
|||
settings = require("settings-sharelatex")
|
||||
request = require("request")
|
||||
logger = require("logger-sharelatex")
|
||||
|
||||
oneSecond = 1000
|
||||
module.exports =
|
||||
|
||||
getUserNotifications: (user_id, callback)->
|
||||
opts =
|
||||
uri: "#{settings.apis.notifications.url}/user/#{user_id}"
|
||||
json: true
|
||||
timeout: 2000
|
||||
request.get opts, (err, res, unreadNotifications)->
|
||||
statusCode = if res? then res.statusCode else 500
|
||||
if err? or statusCode != 200
|
||||
e = new Error("something went wrong getting notifications, #{err}, #{statusCode}")
|
||||
logger.err err:err, "something went wrong getting notifications"
|
||||
callback(null, [])
|
||||
else
|
||||
if !unreadNotifications?
|
||||
unreadNotifications = []
|
||||
callback(null, unreadNotifications)
|
||||
|
||||
createNotification: (user_id, key, templateKey, messageOpts, callback)->
|
||||
opts =
|
||||
uri: "#{settings.apis.notifications.url}/user/#{user_id}"
|
||||
timeout: 1000
|
||||
json: {
|
||||
key:key
|
||||
messageOpts:messageOpts
|
||||
templateKey:templateKey
|
||||
}
|
||||
request.post opts, callback
|
||||
|
||||
markAsReadWithKey: (user_id, key, callback)->
|
||||
opts =
|
||||
uri: "#{settings.apis.notifications.url}/user/#{user_id}"
|
||||
timeout: 1000
|
||||
json: {
|
||||
key:key
|
||||
}
|
||||
request.del opts, callback
|
||||
|
||||
|
||||
markAsRead: (user_id, notification_id, callback)->
|
||||
opts =
|
||||
uri: "#{settings.apis.notifications.url}/user/#{user_id}/notification/#{notification_id}"
|
||||
timeout:oneSecond
|
||||
logger.log user_id:user_id, notification_id:notification_id, "send mark notification to notifications api"
|
||||
request.del opts, callback
|
|
@ -9,6 +9,7 @@ Project = require('../../models/Project').Project
|
|||
User = require('../../models/User').User
|
||||
TagsHandler = require("../Tags/TagsHandler")
|
||||
SubscriptionLocator = require("../Subscription/SubscriptionLocator")
|
||||
NotificationsHandler = require("../Notifications/NotificationsHandler")
|
||||
LimitationsManager = require("../Subscription/LimitationsManager")
|
||||
_ = require("underscore")
|
||||
Settings = require("settings-sharelatex")
|
||||
|
@ -127,6 +128,8 @@ module.exports = ProjectController =
|
|||
async.parallel {
|
||||
tags: (cb)->
|
||||
TagsHandler.getAllTags user_id, cb
|
||||
notifications: (cb)->
|
||||
NotificationsHandler.getUserNotifications user_id, cb
|
||||
projects: (cb)->
|
||||
Project.findAllUsersProjects user_id, 'name lastUpdated publicAccesLevel archived owner_ref', cb
|
||||
hasSubscription: (cb)->
|
||||
|
@ -139,6 +142,9 @@ module.exports = ProjectController =
|
|||
return next(err)
|
||||
logger.log results:results, user_id:user_id, "rendering project list"
|
||||
tags = results.tags[0]
|
||||
notifications = require("underscore").map results.notifications, (notification)->
|
||||
notification.html = req.i18n.translate(notification.templateKey, notification.messageOpts)
|
||||
return notification
|
||||
projects = ProjectController._buildProjectList results.projects[0], results.projects[1], results.projects[2]
|
||||
user = results.user
|
||||
ProjectController._injectProjectOwners projects, (error, projects) ->
|
||||
|
@ -149,6 +155,7 @@ module.exports = ProjectController =
|
|||
priority_title: true
|
||||
projects: projects
|
||||
tags: tags
|
||||
notifications: notifications
|
||||
user: user
|
||||
hasSubscription: results.hasSubscription[0]
|
||||
}
|
||||
|
@ -317,4 +324,4 @@ do generateThemeList = () ->
|
|||
for file in files
|
||||
if file.slice(-2) == "js" and file.match(/^theme-/)
|
||||
cleanName = file.slice(0,-3).slice(6)
|
||||
THEME_LIST.push cleanName
|
||||
THEME_LIST.push cleanName
|
|
@ -1,3 +1,4 @@
|
|||
NotificationsBuilder = require("../Notifications/NotificationsBuilder")
|
||||
async = require("async")
|
||||
_ = require("underscore")
|
||||
settings = require("settings-sharelatex")
|
||||
|
@ -7,17 +8,18 @@ _s = require("underscore.string")
|
|||
module.exports = SubscriptionDomainHandler =
|
||||
|
||||
|
||||
getLicenceUserCanJoin: (user, callback)->
|
||||
getLicenceUserCanJoin: (user)->
|
||||
licence = SubscriptionDomainHandler._findDomainLicence(user.email)
|
||||
if licence?
|
||||
callback null, licence
|
||||
else
|
||||
callback()
|
||||
return licence
|
||||
|
||||
attemptToJoinGroup: (user, callback)->
|
||||
licence = SubscriptionDomainHandler._findDomainLicence(user.email)
|
||||
if licence? and user.emailVerified
|
||||
SubscriptionGroupHandler.addUserToGroup licence.adminUser_id, user.email, callback
|
||||
SubscriptionGroupHandler.addUserToGroup licence.adminUser_id, user.email, (err)->
|
||||
if err?
|
||||
logger.err err:err, "error adding user to group"
|
||||
return callback(err)
|
||||
NotificationsBuilder.groupPlan(user, licence).read()
|
||||
else
|
||||
callback "user not verified"
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ ReferalController = require('./Features/Referal/ReferalController')
|
|||
ReferalMiddleware = require('./Features/Referal/ReferalMiddleware')
|
||||
AuthenticationController = require('./Features/Authentication/AuthenticationController')
|
||||
TagsController = require("./Features/Tags/TagsController")
|
||||
NotificationsController = require("./Features/Notifications/NotificationsController")
|
||||
CollaboratorsRouter = require('./Features/Collaborators/CollaboratorsRouter')
|
||||
UserInfoController = require('./Features/User/UserInfoController')
|
||||
UserController = require("./Features/User/UserController")
|
||||
|
@ -138,6 +139,9 @@ module.exports = class Router
|
|||
webRouter.post '/tag/:tag_id/project/:project_id', AuthenticationController.requireLogin(), TagsController.addProjectToTag
|
||||
webRouter.delete '/tag/:tag_id/project/:project_id', AuthenticationController.requireLogin(), TagsController.removeProjectFromTag
|
||||
|
||||
webRouter.get '/notifications', AuthenticationController.requireLogin(), NotificationsController.getAllUnreadNotifications
|
||||
webRouter.delete '/notifications/:notification_id', AuthenticationController.requireLogin(), NotificationsController.markNotificationAsRead
|
||||
|
||||
# Deprecated in favour of /internal/project/:project_id but still used by versioning
|
||||
apiRouter.get '/project/:project_id/details', AuthenticationController.httpAuth, ProjectApiController.getProjectDetails
|
||||
|
||||
|
@ -232,4 +236,4 @@ module.exports = class Router
|
|||
logger.error err: req.body.error, meta: req.body.meta, "client side error"
|
||||
res.sendStatus(204)
|
||||
|
||||
webRouter.get '*', ErrorController.notFound
|
||||
webRouter.get '*', ErrorController.notFound
|
|
@ -9,7 +9,8 @@ block content
|
|||
script(type="text/javascript").
|
||||
window.data = {
|
||||
projects: !{JSON.stringify(projects).replace(/\//g, '\\/')},
|
||||
tags: !{JSON.stringify(tags).replace(/\//g, '\\/')}
|
||||
tags: !{JSON.stringify(tags).replace(/\//g, '\\/')},
|
||||
notifications: !{JSON.stringify(notifications).replace(/\//g, '\\/')}
|
||||
};
|
||||
window.algolia = {
|
||||
institutions: {
|
||||
|
@ -20,12 +21,14 @@ block content
|
|||
|
||||
.content.content-alt(ng-controller="ProjectPageController")
|
||||
.container
|
||||
|
||||
.row(ng-cloak)
|
||||
span(ng-show="first_sign_up == 'default' || projects.length > 0")
|
||||
aside.col-md-2.col-xs-3
|
||||
include ./list/side-bar
|
||||
|
||||
.col-md-10.col-xs-9
|
||||
include ./list/notifications
|
||||
include ./list/project-list
|
||||
|
||||
span(ng-if="first_sign_up == 'minimial' && projects.length == 0")
|
||||
|
|
15
services/web/app/views/project/list/notifications.jade
Normal file
15
services/web/app/views/project/list/notifications.jade
Normal file
|
@ -0,0 +1,15 @@
|
|||
span(ng-controller="NotificationsController").userNotifications
|
||||
ul.list-unstyled.notifications-list(
|
||||
ng-if="notifications.length > 0",
|
||||
ng-cloak
|
||||
)
|
||||
li.notification_entry(
|
||||
ng-repeat="unreadNotification in notifications",
|
||||
)
|
||||
.row(ng-hide="unreadNotification.hide")
|
||||
.col-xs-12
|
||||
.alert.alert-warning
|
||||
span(ng-bind-html="unreadNotification.html")
|
||||
button(ng-click="dismiss(unreadNotification)").close.pull-right
|
||||
span(aria-hidden="true") ×
|
||||
span.sr-only #{translate("close")}
|
|
@ -108,6 +108,8 @@ module.exports =
|
|||
url: ""
|
||||
references:
|
||||
url: "http://localhost:3040"
|
||||
notifications:
|
||||
url: "http://localhost:3042"
|
||||
|
||||
templates:
|
||||
user_id: process.env.TEMPLATES_USER_ID or "5395eb7aad1f29a88756c7f2"
|
||||
|
|
|
@ -2,6 +2,7 @@ define [
|
|||
"main/project-list/project-list"
|
||||
"main/project-list/modal-controllers"
|
||||
"main/project-list/tag-controllers"
|
||||
"main/project-list/notifications-controller"
|
||||
"main/project-list/queued-http"
|
||||
"main/project-list/left-hand-menu-promo-controller"
|
||||
], () ->
|
|
@ -0,0 +1,17 @@
|
|||
define [
|
||||
"base"
|
||||
], (App) ->
|
||||
|
||||
App.controller "NotificationsController", ($scope, $http) ->
|
||||
for notification in $scope.notifications
|
||||
notification.hide = false
|
||||
|
||||
$scope.dismiss = (notification) ->
|
||||
$http({
|
||||
url: "/notifications/#{notification._id}"
|
||||
method: "DELETE"
|
||||
headers:
|
||||
"X-Csrf-Token": window.csrfToken
|
||||
})
|
||||
.success (data) ->
|
||||
notification.hide = true
|
|
@ -5,6 +5,7 @@ define [
|
|||
App.controller "ProjectPageController", ($scope, $modal, $q, $window, queuedHttp, event_tracking, $timeout, sixpack) ->
|
||||
$scope.projects = window.data.projects
|
||||
$scope.tags = window.data.tags
|
||||
$scope.notifications = window.data.notifications
|
||||
$scope.allSelected = false
|
||||
$scope.selectedProjects = []
|
||||
$scope.filter = "all"
|
||||
|
|
|
@ -28,6 +28,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
.userNotifications {
|
||||
ul {
|
||||
margin-bottom:0px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ul.folders-menu {
|
||||
margin: 0;
|
||||
.subdued {
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
SandboxedModule = require('sandboxed-module')
|
||||
assert = require('assert')
|
||||
require('chai').should()
|
||||
sinon = require('sinon')
|
||||
modulePath = require('path').join __dirname, '../../../../app/js/Features/Notifications/NotificationsController.js'
|
||||
|
||||
|
||||
describe 'NotificationsController', ->
|
||||
user_id = "123nd3ijdks"
|
||||
notification_id = "123njdskj9jlk"
|
||||
|
||||
beforeEach ->
|
||||
@handler =
|
||||
getUserNotifications: sinon.stub().callsArgWith(1)
|
||||
markAsRead: sinon.stub().callsArgWith(2)
|
||||
@controller = SandboxedModule.require modulePath, requires:
|
||||
"./NotificationsHandler":@handler
|
||||
"underscore":@underscore =
|
||||
map:(arr)-> return arr
|
||||
'logger-sharelatex':
|
||||
log:->
|
||||
err:->
|
||||
@req =
|
||||
params:
|
||||
notification_id:notification_id
|
||||
session:
|
||||
user:
|
||||
_id:user_id
|
||||
i18n:
|
||||
translate:->
|
||||
|
||||
it 'should ask the handler for all unread notifications', (done)->
|
||||
allNotifications = [{_id: notification_id, user_id: user_id}]
|
||||
@handler.getUserNotifications = sinon.stub().callsArgWith(1, null, allNotifications)
|
||||
@controller.getAllUnreadNotifications @req, send:(body)=>
|
||||
body.should.equal allNotifications
|
||||
@handler.getUserNotifications.calledWith(user_id).should.equal true
|
||||
done()
|
||||
|
||||
it 'should send a delete request when a delete has been received to mark a notification', (done)->
|
||||
@controller.markNotificationAsRead @req, send:=>
|
||||
@handler.markAsRead.calledWith(user_id, notification_id).should.equal true
|
||||
done()
|
|
@ -0,0 +1,72 @@
|
|||
SandboxedModule = require('sandboxed-module')
|
||||
assert = require('chai').assert
|
||||
require('chai').should()
|
||||
sinon = require('sinon')
|
||||
modulePath = require('path').join __dirname, '../../../../app/js/Features/Notifications/NotificationsHandler.js'
|
||||
_ = require('underscore')
|
||||
|
||||
|
||||
describe 'NotificationsHandler', ->
|
||||
user_id = "123nd3ijdks"
|
||||
notification_id = "123njdskj9jlk"
|
||||
notificationUrl = "notification.sharelatex.testing"
|
||||
|
||||
beforeEach ->
|
||||
@request =
|
||||
post: sinon.stub().callsArgWith(1)
|
||||
del: sinon.stub().callsArgWith(1)
|
||||
get: sinon.stub()
|
||||
@handler = SandboxedModule.require modulePath, requires:
|
||||
"settings-sharelatex": apis:{notifications:{url:notificationUrl}}
|
||||
"request":@request
|
||||
'logger-sharelatex':
|
||||
log:->
|
||||
err:->
|
||||
|
||||
describe "getUserNotifications", ->
|
||||
it 'should get unread notifications', (done)->
|
||||
stubbedNotifications = [{_id: notification_id, user_id: user_id}]
|
||||
@request.get.callsArgWith(1, null, {statusCode:200}, stubbedNotifications)
|
||||
@handler.getUserNotifications user_id, (err, unreadNotifications)=>
|
||||
stubbedNotifications.should.deep.equal unreadNotifications
|
||||
getOpts =
|
||||
uri: "#{notificationUrl}/user/#{user_id}"
|
||||
json:true
|
||||
timeout:2000
|
||||
@request.get.calledWith(getOpts).should.equal true
|
||||
done()
|
||||
|
||||
it 'should return empty arrays if there are no notifications', ->
|
||||
@request.get.callsArgWith(1, null, {statusCode:200}, null)
|
||||
@handler.getUserNotifications user_id, (err, unreadNotifications)=>
|
||||
unreadNotifications.length.should.equal 0
|
||||
|
||||
describe "markAsRead", ->
|
||||
beforeEach ->
|
||||
@key = "some key here"
|
||||
|
||||
it 'should send a delete request when a delete has been received to mark a notification', (done)->
|
||||
@handler.markAsReadWithKey user_id, @key, =>
|
||||
opts =
|
||||
uri: "#{notificationUrl}/user/#{user_id}"
|
||||
json:
|
||||
key:@key
|
||||
timeout:1000
|
||||
@request.del.calledWith(opts).should.equal true
|
||||
done()
|
||||
|
||||
|
||||
describe "createNotification", ->
|
||||
beforeEach ->
|
||||
@key = "some key here"
|
||||
@messageOpts = {value:12344}
|
||||
@templateKey = "renderThisHtml"
|
||||
|
||||
it "should post the message over", (done)->
|
||||
@handler.createNotification user_id, @key, @templateKey, @messageOpts, =>
|
||||
args = @request.post.args[0][0]
|
||||
args.uri.should.equal "#{notificationUrl}/user/#{user_id}"
|
||||
args.timeout.should.equal 1000
|
||||
expectedJson = {key:@key, templateKey:@templateKey, messageOpts:@messageOpts}
|
||||
assert.deepEqual(args.json, expectedJson)
|
||||
done()
|
|
@ -33,6 +33,8 @@ describe "ProjectController", ->
|
|||
userHasSubscriptionOrIsGroupMember: sinon.stub()
|
||||
@TagsHandler =
|
||||
getAllTags: sinon.stub()
|
||||
@NotificationsHandler =
|
||||
getUserNotifications: sinon.stub()
|
||||
@ProjectModel =
|
||||
findAllUsersProjects: sinon.stub()
|
||||
findPopulatedById: sinon.stub()
|
||||
|
@ -60,6 +62,7 @@ describe "ProjectController", ->
|
|||
"../Subscription/SubscriptionLocator": @SubscriptionLocator
|
||||
"../Subscription/LimitationsManager": @LimitationsManager
|
||||
"../Tags/TagsHandler":@TagsHandler
|
||||
"../Notifications/NotificationsHandler":@NotificationsHandler
|
||||
'../../models/Project': Project:@ProjectModel
|
||||
"../../models/User":User:@UserModel
|
||||
"../../managers/SecurityManager":@SecurityManager
|
||||
|
@ -78,6 +81,8 @@ describe "ProjectController", ->
|
|||
user: @user
|
||||
body:
|
||||
projectName: @projectName
|
||||
i18n:
|
||||
translate:->
|
||||
@res =
|
||||
locals:
|
||||
jsPath:"js path here"
|
||||
|
@ -198,6 +203,7 @@ describe "ProjectController", ->
|
|||
|
||||
beforeEach ->
|
||||
@tags = [{name:1, project_ids:["1","2","3"]}, {name:2, project_ids:["a","1"]}, {name:3, project_ids:["a", "b", "c", "d"]}]
|
||||
@notifications = [{_id:'1',user_id:'2',templateKey:'3',messageOpts:'4',key:'5'}]
|
||||
@projects = [{lastUpdated:1, _id:1, owner_ref: "user-1"}, {lastUpdated:2, _id:2, owner_ref: "user-2"}]
|
||||
@collabertions = [{lastUpdated:5, _id:5, owner_ref: "user-1"}]
|
||||
@readOnly = [{lastUpdated:3, _id:3, owner_ref: "user-1"}]
|
||||
|
@ -213,6 +219,7 @@ describe "ProjectController", ->
|
|||
|
||||
@LimitationsManager.userHasSubscriptionOrIsGroupMember.callsArgWith(1, null, false)
|
||||
@TagsHandler.getAllTags.callsArgWith(1, null, @tags, {})
|
||||
@NotificationsHandler.getUserNotifications = sinon.stub().callsArgWith(1, null, @notifications, {})
|
||||
@ProjectModel.findAllUsersProjects.callsArgWith(2, null, @projects, @collabertions, @readOnly)
|
||||
|
||||
it "should render the project/list page", (done)->
|
||||
|
|
Loading…
Reference in a new issue