mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-04 07:55:33 +00:00
Allow projects to be restore after being deleted
This commit is contained in:
parent
bc6d1cb5be
commit
015fd6873f
11 changed files with 154 additions and 83 deletions
|
@ -16,10 +16,19 @@ SecurityManager = require("../../managers/SecurityManager")
|
|||
|
||||
module.exports =
|
||||
|
||||
deleteProject: (req, res)->
|
||||
deleteProject: (req, res) ->
|
||||
project_id = req.params.Project_id
|
||||
logger.log project_id:project_id, "recived request to delete project"
|
||||
projectDeleter.deleteProject project_id, (err)->
|
||||
logger.log project_id:project_id, "received request to delete project"
|
||||
projectDeleter.archiveProject project_id, (err)->
|
||||
if err?
|
||||
res.send 500
|
||||
else
|
||||
res.send 200
|
||||
|
||||
restoreProject: (req, res) ->
|
||||
project_id = req.params.Project_id
|
||||
logger.log project_id:project_id, "received request to restore project"
|
||||
projectDeleter.restoreProject project_id, (err)->
|
||||
if err?
|
||||
res.send 500
|
||||
else
|
||||
|
@ -73,8 +82,6 @@ module.exports =
|
|||
timer = new metrics.Timer("project-list")
|
||||
user_id = req.session.user._id
|
||||
async.parallel {
|
||||
subscription: (cb)->
|
||||
SubscriptionLocator.getUsersSubscription user_id, cb
|
||||
tags: (cb)->
|
||||
TagsHandler.getAllTags user_id, cb
|
||||
projects: (cb)->
|
||||
|
@ -84,10 +91,17 @@ module.exports =
|
|||
logger.err err:err, "error getting data for project list page"
|
||||
return res.send 500
|
||||
logger.log results:results, user_id:user_id, "rendering project list"
|
||||
viewModel = _buildListViewModel results.projects[0], results.projects[1], results.projects[2], results.tags[0], results.tags[1], results.subscription?[0]
|
||||
viewModel = _buildListViewModel results.projects[0], results.projects[1], results.projects[2], results.tags[0], results.tags[1]
|
||||
res.render 'project/list', viewModel
|
||||
timer.done()
|
||||
|
||||
archivedProjects: (req, res, next)->
|
||||
user_id = req.session.user._id
|
||||
projectDeleter.findArchivedProjects user_id, 'name lastUpdated publicAccesLevel', (error, projects) ->
|
||||
return next(error) if error?
|
||||
logger.log projects: projects, user_id:user_id, "rendering archived project list"
|
||||
viewModel = _buildListViewModel projects, [], [], [], {}
|
||||
res.render 'project/archived', viewModel
|
||||
|
||||
loadEditor: (req, res, next)->
|
||||
timer = new metrics.Timer("load-editor")
|
||||
|
@ -166,7 +180,6 @@ module.exports =
|
|||
languages: Settings.languages
|
||||
timer.done()
|
||||
|
||||
|
||||
defaultSettingsForAnonymousUser = (user_id)->
|
||||
id : user_id
|
||||
ace:
|
||||
|
@ -183,15 +196,7 @@ defaultSettingsForAnonymousUser = (user_id)->
|
|||
dropbox: false
|
||||
trackChanges: false
|
||||
|
||||
|
||||
|
||||
_buildListViewModel = (projects, collabertions, readOnlyProjects, tags, tagsGroupedByProject, subscription)->
|
||||
# TODO: Remove this one month after the ability to start free trials was removed
|
||||
if subscription? and subscription.freeTrial? and subscription.freeTrial.expiresAt?
|
||||
freeTrial =
|
||||
expired: !!subscription.freeTrial.downgraded
|
||||
expiresAt: SubscriptionFormatters.formatDate(subscription.freeTrial.expiresAt)
|
||||
|
||||
_buildListViewModel = (projects, collabertions, readOnlyProjects, tags, tagsGroupedByProject)->
|
||||
for project in projects
|
||||
project.accessLevel = "owner"
|
||||
for project in collabertions
|
||||
|
@ -211,7 +216,6 @@ _buildListViewModel = (projects, collabertions, readOnlyProjects, tags, tagsGrou
|
|||
title:'Your Projects'
|
||||
priority_title: true
|
||||
projects: sortedProjects
|
||||
freeTrial: freeTrial
|
||||
tags:tags
|
||||
projectTabActive: true
|
||||
}
|
|
@ -20,8 +20,7 @@ module.exports =
|
|||
logger.log owner_id:owner_id, "deleting users projects"
|
||||
Project.remove owner_ref:owner_id, callback
|
||||
|
||||
|
||||
deleteProject: (project_id, callback = (error) ->)->
|
||||
archiveProject: (project_id, callback = (error) ->)->
|
||||
logger.log project_id:project_id, "deleting project"
|
||||
Project.findById project_id, (err, project)=>
|
||||
if err? or !project?
|
||||
|
@ -48,3 +47,12 @@ module.exports =
|
|||
if err?
|
||||
logger.err err:err, "problem deleting project"
|
||||
callback(err)
|
||||
|
||||
restoreProject: (project_id, callback = (error) ->) ->
|
||||
Project.update {_id:project_id}, { $unset: { archived: true }}, callback
|
||||
|
||||
findArchivedProjects: (owner_id, fields, callback = (error, projects) ->) ->
|
||||
Project.find {
|
||||
owner_ref: owner_id
|
||||
archived: true
|
||||
}, fields, callback
|
||||
|
|
|
@ -92,6 +92,7 @@ module.exports = class Router
|
|||
app.get '/user/:user_id/personal_info', httpAuth, UserInfoController.getPersonalInfo
|
||||
|
||||
app.get '/project', AuthenticationController.requireLogin(), ProjectController.projectListPage
|
||||
app.get '/project/archived', AuthenticationController.requireLogin(), ProjectController.archivedProjects
|
||||
app.post '/project/new', AuthenticationController.requireLogin(), ProjectController.newProject
|
||||
|
||||
app.get '/project/new/template', TemplatesMiddlewear.saveTemplateDataInSession, AuthenticationController.requireLogin(), TemplatesController.createProjectFromZipTemplate
|
||||
|
@ -115,7 +116,8 @@ module.exports = class Router
|
|||
|
||||
|
||||
app.del '/Project/:Project_id', SecurityManager.requestIsOwner, ProjectController.deleteProject
|
||||
app.post '/Project/:Project_id/clone', SecurityManager.requestCanAccessProject, ProjectController.cloneProject
|
||||
app.post '/Project/:Project_id/restore', SecurityManager.requestIsOwner, ProjectController.restoreProject
|
||||
app.post '/Project/:Project_id/clone', SecurityManager.requestCanAccessProject, ProjectController.cloneProject
|
||||
|
||||
app.post '/project/:Project_id/rename', SecurityManager.requestIsOwner, ProjectController.renameProject
|
||||
|
||||
|
|
46
services/web/app/views/project/archived.jade
Normal file
46
services/web/app/views/project/archived.jade
Normal file
|
@ -0,0 +1,46 @@
|
|||
extends ../layout
|
||||
|
||||
block content
|
||||
mixin projectList(projects)
|
||||
-each project in projects
|
||||
- project_id = project._id.toString()
|
||||
.project_entry(id=project_id)
|
||||
.btn-group.project-actions
|
||||
a.btn(href='/project/'+project_id+'/restore', data-csrf=csrfToken, data-id=project_id).restoreProject Restore
|
||||
.projectName #{project.name}
|
||||
|
||||
include ../general/sidebar
|
||||
|
||||
.content-with-navigation-sidebar
|
||||
.box#projectListArea
|
||||
.row-fluid
|
||||
.span12
|
||||
.page-header
|
||||
h1 Archived Projects
|
||||
- if (projects.length > 0)
|
||||
.row-fluid
|
||||
.span9
|
||||
ul#projectList
|
||||
mixin projectList(projects)
|
||||
|
||||
|
||||
- else
|
||||
.row-fluid
|
||||
.span12 You don't have any archived projects
|
||||
|
||||
include ../general/small-footer
|
||||
|
||||
- locals.supressDefaultJs = true
|
||||
|
||||
script
|
||||
window.requirejs = {
|
||||
"paths" : {
|
||||
"moment": "libs/moment"
|
||||
}
|
||||
};
|
||||
script(
|
||||
data-main=jsPath+'list.js?fingerprint='+fingerprint(jsPath + 'list.js'),
|
||||
baseurl=jsPath,
|
||||
src=jsPath+'libs/require.js?fingerprint='+fingerprint(jsPath + 'libs/require.js')
|
||||
)
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
extends ../layout
|
||||
|
||||
block content
|
||||
.container
|
||||
.row
|
||||
.span12.span-box
|
||||
.page-header
|
||||
h1 ShareLaTeX - Online LaTeX Editor : #{title}
|
||||
//- .btn-group#newProject
|
||||
//- a.btn.btn-success(href='/project/#{project_id}') go to editor mode
|
||||
-each doc in resources
|
||||
h2 #{doc.path}
|
||||
if (doc.url != null)
|
||||
img(src='/project/#{project_id}/file/#{doc.id}')
|
||||
else
|
||||
pre.prettyprint.lang-tex.linenums
|
||||
-each line in doc.content
|
||||
//leave the line below #{line}
|
||||
span.
|
||||
#{line}
|
||||
|
||||
hr
|
||||
|
||||
|
||||
- locals.supressDefaultJs = true
|
||||
script(data-main=jsPath+'codeprettifyer.js', src=jsPath+'libs/require.js', baseurl=jsPath)
|
||||
|
|
@ -124,23 +124,6 @@ block content
|
|||
a(href="/learn") help guides
|
||||
| .
|
||||
|
||||
|
||||
|
||||
if freeTrial && freeTrial.expired == false
|
||||
.row-fluid
|
||||
.span12
|
||||
.alert.alert-info.alert-free-trial
|
||||
p You are currently using a free trial which expires on #{freeTrial.expiresAt}.
|
||||
p
|
||||
a(href="/user/subscription").btn.btn-primary Upgrade now
|
||||
if freeTrial && freeTrial.expired == true
|
||||
.row-fluid
|
||||
.span12
|
||||
.alert.alert-danger.alert-free-trial
|
||||
p Your free trial has expired! Upgrade now to continue using ShareLaTeX uninterrupted.
|
||||
p
|
||||
a(href="/user/subscription").btn.btn-danger Upgrade now
|
||||
|
||||
include ../general/small-footer
|
||||
script(type="text/template")#tagTemplate
|
||||
mixin tag('{{ project_id }}', '{{ tagName }}', true)
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
.container
|
||||
table.table.table-striped.table-bordered#revisionList(cellpadding='0', cellspacing='0', border='0')
|
||||
thead
|
||||
tr
|
||||
th Date
|
||||
th Files Changed
|
||||
tbody
|
||||
|
||||
|
||||
|
|
@ -169,6 +169,18 @@ require [
|
|||
$modal.find('.cancel').click (e)->
|
||||
$modal.modal('hide')
|
||||
|
||||
$('.restoreProject').click (event) ->
|
||||
event.preventDefault()
|
||||
|
||||
id = $(@).data("id")
|
||||
$.ajax
|
||||
url: @href
|
||||
type: 'POST'
|
||||
data:
|
||||
_csrf: $(@).data("csrf")
|
||||
success: (data)->
|
||||
$("##{id}").fadeOut(1000)
|
||||
|
||||
newProject = (template, fileToOpen) ->
|
||||
$modal = $('#newProjectModal')
|
||||
$confirm = $('#confirmNewProject')
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
&:hover {
|
||||
background-color: #eaeaea;
|
||||
}
|
||||
min-height: 40px;
|
||||
|
||||
padding-left: 10px;
|
||||
padding-top: 10px;
|
||||
|
|
|
@ -18,7 +18,9 @@ describe "ProjectController", ->
|
|||
url:"chat.com"
|
||||
siteUrl: "mysite.com"
|
||||
@ProjectDeleter =
|
||||
deleteProject: sinon.stub().callsArgWith(1)
|
||||
archiveProject: sinon.stub().callsArgWith(1)
|
||||
restoreProject: sinon.stub().callsArgWith(1)
|
||||
findArchivedProjects: sinon.stub()
|
||||
@ProjectDuplicator =
|
||||
duplicate: sinon.stub().callsArgWith(3, null, {_id:@project_id})
|
||||
@ProjectCreationHandler =
|
||||
|
@ -68,18 +70,22 @@ describe "ProjectController", ->
|
|||
jsPath:"js path here"
|
||||
|
||||
describe "deleteProject", ->
|
||||
|
||||
it "should tell the project deleter", (done)->
|
||||
|
||||
@res.send = (code)=>
|
||||
@ProjectDeleter.deleteProject.calledWith(@project_id).should.equal true
|
||||
@ProjectDeleter.archiveProject.calledWith(@project_id).should.equal true
|
||||
code.should.equal 200
|
||||
done()
|
||||
@ProjectController.deleteProject @req, @res
|
||||
|
||||
describe "restoreProject", ->
|
||||
it "should tell the project deleter", (done)->
|
||||
@res.send = (code)=>
|
||||
@ProjectDeleter.restoreProject.calledWith(@project_id).should.equal true
|
||||
code.should.equal 200
|
||||
done()
|
||||
@ProjectController.restoreProject @req, @res
|
||||
|
||||
describe "cloneProject", ->
|
||||
|
||||
it "should call the project duplicator", (done)->
|
||||
@res.send = (json)=>
|
||||
@ProjectDuplicator.duplicate.calledWith(@user, @project_id, @projectName).should.equal true
|
||||
|
@ -135,6 +141,23 @@ describe "ProjectController", ->
|
|||
done()
|
||||
@ProjectController.projectListPage @req, @res
|
||||
|
||||
describe "archivedProjects", ->
|
||||
beforeEach ->
|
||||
@projects = [{lastUpdated:1, _id:1}, {lastUpdated:2, _id:2}]
|
||||
@ProjectDeleter.findArchivedProjects.callsArgWith(2, null, @projects)
|
||||
|
||||
it "should render the project/archived page", (done)->
|
||||
@res.render = (pageName, opts)=>
|
||||
pageName.should.equal "project/archived"
|
||||
done()
|
||||
@ProjectController.archivedProjects @req, @res
|
||||
|
||||
it "should send the projects", (done)->
|
||||
@res.render = (pageName, opts)=>
|
||||
opts.projects.length.should.equal (@projects.length)
|
||||
done()
|
||||
@ProjectController.archivedProjects @req, @res
|
||||
|
||||
describe "renameProject", ->
|
||||
beforeEach ->
|
||||
@newProjectName = "my supper great new project"
|
||||
|
|
|
@ -56,17 +56,17 @@ describe 'Project deleter', ->
|
|||
done()
|
||||
|
||||
|
||||
describe "deleteProject", ->
|
||||
describe "archiveProject", ->
|
||||
beforeEach ->
|
||||
@Project.update.callsArgWith(2)
|
||||
|
||||
it "should flushProjectToMongoAndDelete in doc updater", (done)->
|
||||
@deleter.deleteProject @project_id, =>
|
||||
@deleter.archiveProject @project_id, =>
|
||||
@documentUpdaterHandler.flushProjectToMongoAndDelete.calledWith(@project_id).should.equal true
|
||||
done()
|
||||
|
||||
it "should remove the project", (done)->
|
||||
@deleter.deleteProject @project_id, =>
|
||||
@deleter.archiveProject @project_id, =>
|
||||
@Project.update.calledWith({
|
||||
_id:@project_id
|
||||
}, {
|
||||
|
@ -75,7 +75,7 @@ describe 'Project deleter', ->
|
|||
done()
|
||||
|
||||
it "should removeProjectFromAllTags", (done)->
|
||||
@deleter.deleteProject @project_id, =>
|
||||
@deleter.archiveProject @project_id, =>
|
||||
@TagsHandler.removeProjectFromAllTags.calledWith(@project.owner_ref, @project_id).should.equal true
|
||||
@TagsHandler.removeProjectFromAllTags.calledWith(@project.collaberator_refs[0], @project_id).should.equal true
|
||||
@TagsHandler.removeProjectFromAllTags.calledWith(@project.collaberator_refs[1], @project_id).should.equal true
|
||||
|
@ -84,3 +84,32 @@ describe 'Project deleter', ->
|
|||
|
||||
done()
|
||||
|
||||
describe "restoreProject", ->
|
||||
beforeEach ->
|
||||
@Project.update.callsArgWith(2)
|
||||
|
||||
it "should unset the archive attribute", (done)->
|
||||
@deleter.restoreProject @project_id, =>
|
||||
@Project.update.calledWith({
|
||||
_id: @project_id
|
||||
}, {
|
||||
$unset: { archived: true }
|
||||
}).should.equal true
|
||||
done()
|
||||
|
||||
describe "findArchivedProjects", ->
|
||||
beforeEach ->
|
||||
@projects = ["mock-project"]
|
||||
@owner_id = "mock-owner-id"
|
||||
@callback = sinon.stub()
|
||||
@Project.find = sinon.stub().callsArgWith(2, null, @projects)
|
||||
@deleter.findArchivedProjects @owner_id, @fields = "name lastModified", @callback
|
||||
|
||||
it "should find the archived projects for the owner", ->
|
||||
@Project.find
|
||||
.calledWith(owner_ref: @owner_id, archived: true, @fields)
|
||||
.should.equal true
|
||||
|
||||
it "should return the projects", ->
|
||||
@callback.calledWith(null, @projects).should.equal true
|
||||
|
||||
|
|
Loading…
Reference in a new issue