Merge pull request #229 from tellform/speed_improvements
Speed up rendering of form submission page
This commit is contained in:
commit
d24ba0a439
@ -46,17 +46,16 @@ exports.deleteSubmissions = function(req, res) {
|
||||
* Submit a form entry
|
||||
*/
|
||||
exports.createSubmission = function(req, res) {
|
||||
var form = req.form;
|
||||
|
||||
var timeElapsed = 0;
|
||||
|
||||
|
||||
console.log(req.body);
|
||||
if(typeof req.body.timeElapsed === 'number'){
|
||||
timeElapsed = req.body.timeElapsed;
|
||||
}
|
||||
var submission = new FormSubmission({
|
||||
admin: form.admin._id,
|
||||
form: form._id,
|
||||
title: form.title,
|
||||
form: req.body._id,
|
||||
title: req.body.title,
|
||||
form_fields: req.body.form_fields,
|
||||
timeElapsed: timeElapsed,
|
||||
percentageComplete: req.body.percentageComplete,
|
||||
@ -73,8 +72,8 @@ exports.createSubmission = function(req, res) {
|
||||
message: errorHandler.getErrorMessage(err)
|
||||
});
|
||||
}
|
||||
|
||||
form.submissions.push(submission);
|
||||
res.status(200).send('Form submission successfully saved');
|
||||
/*form.submissions.push(submission);
|
||||
|
||||
form.save(function (err) {
|
||||
if (err) {
|
||||
@ -84,7 +83,7 @@ exports.createSubmission = function(req, res) {
|
||||
});
|
||||
}
|
||||
res.status(200).send('Form submission successfully saved');
|
||||
});
|
||||
});*/
|
||||
});
|
||||
};
|
||||
|
||||
@ -94,10 +93,10 @@ exports.createSubmission = function(req, res) {
|
||||
exports.listSubmissions = function(req, res) {
|
||||
var _form = req.form;
|
||||
|
||||
FormSubmission.find({ form: _form._id }).exec(function(err, _submissions) {
|
||||
FormSubmission.find({ form: _form._id }).sort('-created').lean().exec(function(err, _submissions) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
res.status(400).send({
|
||||
res.status(500).send({
|
||||
message: errorHandler.getErrorMessage(err)
|
||||
});
|
||||
}
|
||||
@ -172,9 +171,6 @@ var readForRender = exports.readForRender = function(req, res) {
|
||||
});
|
||||
}
|
||||
|
||||
delete newForm.submissions;
|
||||
//delete newForm.analytics;
|
||||
delete newForm.admin;
|
||||
delete newForm.lastModified;
|
||||
delete newForm.__v;
|
||||
delete newForm.created;
|
||||
@ -190,7 +186,7 @@ var readForRender = exports.readForRender = function(req, res) {
|
||||
* Update a form
|
||||
*/
|
||||
exports.update = function(req, res) {
|
||||
var form = req.form;
|
||||
var form = req.form;
|
||||
var updatedForm = req.body.form;
|
||||
|
||||
delete updatedForm.__v;
|
||||
@ -326,10 +322,9 @@ exports.formByIDFast = function(req, res, next, id) {
|
||||
});
|
||||
}
|
||||
Form.findById(id)
|
||||
.select('title language form_fields startPage endPage hideFooter isLive design admin analytics.gaCode')
|
||||
.populate('admin')
|
||||
.cache()
|
||||
.lean()
|
||||
.cache()
|
||||
.select('title language form_fields startPage endPage hideFooter isLive design analytics.gaCode')
|
||||
.exec(function(err, form) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
|
||||
@ -16,12 +16,6 @@ var FormSubmissionSchema = new Schema({
|
||||
type: String
|
||||
},
|
||||
|
||||
admin: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: true
|
||||
},
|
||||
|
||||
form_fields: [FieldSchema],
|
||||
|
||||
form: {
|
||||
@ -59,20 +53,19 @@ var FormSubmissionSchema = new Schema({
|
||||
});
|
||||
|
||||
FormSubmissionSchema.pre('save', function (next) {
|
||||
//Iterate through form fields and format data
|
||||
//Iterate through form fields and format data
|
||||
for(var i = 0; i < this.form_fields.length; i++){
|
||||
if(this.form_fields[i].fieldType === 'dropdown'){
|
||||
this.form_fields[i].fieldValue = this.form_fields[i].fieldValue.option_value;
|
||||
}
|
||||
}
|
||||
next();
|
||||
next();
|
||||
});
|
||||
|
||||
FormSubmissionSchema.path('form_fields', {
|
||||
set: function(form_fields){
|
||||
for (var i = 0; i < form_fields.length; i++) {
|
||||
form_fields[i].isSubmission = true;
|
||||
form_fields[i].submissionId = form_fields[i]._id;
|
||||
form_fields[i]._id = new mongoose.mongo.ObjectID();
|
||||
}
|
||||
return form_fields;
|
||||
|
||||
@ -14,19 +14,22 @@ module.exports = function(app) {
|
||||
app.route('/subdomain/:userSubdomain((?!api$)[A-Za-z0-9]+)/')
|
||||
.get(core.form);
|
||||
|
||||
app.route('/subdomain/:userSubdomain((?!api$)[A-Za-z0-9]+)/forms/:formId([a-zA-Z0-9]+)')
|
||||
app.route('/subdomain/:userSubdomain((?!api$)[A-Za-z0-9]+)/forms/([a-zA-Z0-9]+)')
|
||||
.post(forms.createSubmission);
|
||||
|
||||
app.route('/subdomain/:userSubdomain((?!api$)[A-Za-z0-9]+)/forms/:formIdFast([a-zA-Z0-9]+)/render')
|
||||
.get(forms.readForRender);
|
||||
|
||||
app.route('/forms/:formId([a-zA-Z0-9]+)/render')
|
||||
.put(auth.isAuthenticatedOrApiKey, forms.hasAuthorization, forms.readForRender)
|
||||
.get(auth.isAuthenticatedOrApiKey, forms.hasAuthorization, forms.readForRender);
|
||||
} else {
|
||||
app.route('/forms/:formIdFast([a-zA-Z0-9]+)/render')
|
||||
.get(forms.readForRender);
|
||||
}
|
||||
|
||||
app.route('/forms/:formIdFast([a-zA-Z0-9]+)')
|
||||
.post(forms.createSubmission)
|
||||
|
||||
app.route('/forms')
|
||||
.get(auth.isAuthenticatedOrApiKey, forms.list)
|
||||
.post(auth.isAuthenticatedOrApiKey, forms.create);
|
||||
@ -46,4 +49,5 @@ module.exports = function(app) {
|
||||
|
||||
// Fast formId middleware
|
||||
app.param('formIdFast', forms.formByIDFast);
|
||||
|
||||
};
|
||||
|
||||
@ -43,32 +43,9 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="/static/lib/jquery/dist/jquery.slim.min.js" type="text/javascript"></script>
|
||||
<script type="text/javascript">
|
||||
$(window).on("load", function() {
|
||||
$(".loader").fadeOut("slow");
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Fav Icon -->
|
||||
<link href="/static/modules/core/img/brand/favicon.ico" rel="shortcut icon" type="image/x-icon">
|
||||
<link rel="stylesheet" href="/static/lib/font-awesome/css/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="/static/lib/bootstrap/dist/css/bootstrap.min.css">
|
||||
<!--Bower CSS dependencies-->
|
||||
{% for bowerCssFile in bowerCssFiles %}
|
||||
<link rel="stylesheet" href="{{bowerCssFile}}">
|
||||
{% endfor %}
|
||||
<link rel="stylesheet" href="/static/lib/angular-input-stars/angular-input-stars.css">
|
||||
<link rel="stylesheet" href="/static/lib/jquery-ui/themes/flick/jquery-ui.min.css"/>
|
||||
|
||||
<!-- end Bower CSS dependencies-->
|
||||
|
||||
<!--Application CSS Files-->
|
||||
{% for cssFile in cssFiles %}
|
||||
<link rel="stylesheet" href="{{cssFile}}">
|
||||
{% endfor %}
|
||||
<!-- end Application CSS Files-->
|
||||
|
||||
|
||||
<!-- HTML5 Shim -->
|
||||
<!--[if lt IE 9]>
|
||||
@ -84,15 +61,6 @@
|
||||
<section ui-view></section>
|
||||
</section>
|
||||
|
||||
<!-- [if lt IE 9]>
|
||||
<section class="browsehappy jumbotron hide">
|
||||
<h1>Hello there!</h1>
|
||||
<p>You are using an old browser which we unfortunately do not support.</p>
|
||||
<p>Please <a href="http://browsehappy.com/">click here</a> to update your browser before using the website.</p>
|
||||
<p><a href="http://browsehappy.com" class="btn btn-primary btn-lg" role="button">Yes, upgrade my browser!</a></p>
|
||||
</section>
|
||||
<![endif] -->
|
||||
|
||||
|
||||
<!--Embedding The User Object signupDisabled, socketPort and socketUrl Boolean -->
|
||||
<script type="text/javascript">
|
||||
@ -106,13 +74,37 @@
|
||||
var subdomainsDisabled = {{subdomainsDisabled | safe}};
|
||||
</script>
|
||||
|
||||
<!--Socket.io Client Dependency-->
|
||||
<script src="/static/lib/socket.io-client/dist/socket.io.min.js" async></script>
|
||||
|
||||
<script src="/static/lib/jquery/dist/jquery.min.js" type="text/javascript"></script>
|
||||
<script type="text/javascript">
|
||||
$(window).on("load", function() {
|
||||
$(".loader").fadeOut("slow");
|
||||
});
|
||||
</script>
|
||||
|
||||
<link rel="stylesheet" href="/static/lib/font-awesome/css/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="/static/lib/bootstrap/dist/css/bootstrap.min.css">
|
||||
<!--Bower CSS dependencies-->
|
||||
{% for bowerCssFile in bowerCssFiles %}
|
||||
<link rel="stylesheet" href="{{bowerCssFile}}">
|
||||
{% endfor %}
|
||||
<link rel="stylesheet" href="/static/lib/angular-input-stars/angular-input-stars.css">
|
||||
<link rel="stylesheet" href="/static/lib/jquery-ui/themes/flick/jquery-ui.min.css"/>
|
||||
|
||||
<!-- end Bower CSS dependencies-->
|
||||
|
||||
<!--Application CSS Files-->
|
||||
{% for cssFile in cssFiles %}
|
||||
<link rel="stylesheet" href="{{cssFile}}">
|
||||
{% endfor %}
|
||||
<!-- end Application CSS Files-->
|
||||
|
||||
<!--Socket.io Client Dependency-->
|
||||
<script src="/static/lib/socket.io-client/dist/socket.io.min.js"></script>
|
||||
<script src="/static/lib/jquery-ui/jquery-ui.js" type="text/javascript"></script>
|
||||
|
||||
<script src="/static/dist/vendor.min.js"></script>
|
||||
<script src="/static/lib/angular-ui-date/src/date.js" type="text/javascript"></script>
|
||||
<script src="/static/lib/angular-ui-date/src/date.js" type="text/javascript"></script>
|
||||
<!--Application JavaScript Files-->
|
||||
{% for jsFile in formJSFiles %}
|
||||
<script type="text/javascript" src="{{jsFile}}"></script>
|
||||
@ -124,11 +116,12 @@
|
||||
<script async type="text/javascript" src="http://{{request.hostname}}:35729/livereload.js"></script>
|
||||
{% endif %}
|
||||
|
||||
<script async>
|
||||
<script>
|
||||
Raven.config('https://825fefd6b4ed4a4da199c1b832ca845c@sentry.tellform.com/2').install();
|
||||
</script>
|
||||
|
||||
<script async>
|
||||
{% if google_analytics_id %}
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
@ -137,7 +130,13 @@
|
||||
ga('create', '{{google_analytics_id}}', 'auto');
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
<script type="text/javascript">
|
||||
window.heap=window.heap||[],heap.load=function(e,t){window.heap.appid=e,window.heap.config=t=t||{};var r=t.forceSSL||"https:"===document.location.protocol,a=document.createElement("script");a.type="text/javascript",a.async=!0,a.src=(r?"https:":"http:")+"//cdn.heapanalytics.com/js/heap-"+e+".js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(a,n);for(var o=function(e){return function(){heap.push([e].concat(Array.prototype.slice.call(arguments,0)))}},p=["addEventProperties","addUserProperties","clearEventProperties","identify","removeEventProperty","setEventProperties","track","unsetEventProperty"],c=0;c<p.length;c++)heap[p[c]]=o(p[c])};
|
||||
heap.load("2213510609");
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@ -126,6 +126,11 @@
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
window.heap=window.heap||[],heap.load=function(e,t){window.heap.appid=e,window.heap.config=t=t||{};var r=t.forceSSL||"https:"===document.location.protocol,a=document.createElement("script");a.type="text/javascript",a.async=!0,a.src=(r?"https:":"http:")+"//cdn.heapanalytics.com/js/heap-"+e+".js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(a,n);for(var o=function(e){return function(){heap.push([e].concat(Array.prototype.slice.call(arguments,0)))}},p=["addEventProperties","addUserProperties","clearEventProperties","identify","removeEventProperty","setEventProperties","track","unsetEventProperty"],c=0;c<p.length;c++)heap[p[c]]=o(p[c])};
|
||||
heap.load("2213510609");
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
12
config/env/all.js
vendored
12
config/env/all.js
vendored
@ -14,11 +14,6 @@ module.exports = {
|
||||
pass: ''
|
||||
}
|
||||
},
|
||||
aws: {
|
||||
'accessKeyId': process.env.AWS_ACCESS_ID,
|
||||
'secretAccessKey': process.env.AWS_SECRET_KEY,
|
||||
'region': process.env.AWS_REGION
|
||||
},
|
||||
|
||||
port: process.env.PORT || 3000,
|
||||
socketPort: process.env.SOCKET_PORT || 20523,
|
||||
@ -32,11 +27,6 @@ module.exports = {
|
||||
baseUrl: '',
|
||||
tempUserCollection: 'temporary_users',
|
||||
|
||||
mailosaur: {
|
||||
key: process.env.MAILOSAUR_KEY || '',
|
||||
mailbox_id: process.env.MAILOSAUR_MAILBOX || ''
|
||||
},
|
||||
|
||||
subdomainsDisabled: (process.env.SUBDOMAINS_DISABLED === 'TRUE'),
|
||||
|
||||
//Sentry DSN Client Key
|
||||
@ -44,7 +34,7 @@ module.exports = {
|
||||
|
||||
// The secret should be set to a non-guessable string that
|
||||
// is used to compute a session hash
|
||||
sessionSecret: 'MEAN',
|
||||
sessionSecret: process.env.SESSION_SECRET || 'CHANGE_ME_PLEASE',
|
||||
// The name of the MongoDB collection to store sessions in
|
||||
sessionCollection: 'sessions',
|
||||
// The session cookie settings
|
||||
|
||||
@ -27,7 +27,7 @@ var fs = require('fs-extra'),
|
||||
var mongoose = require('mongoose');
|
||||
|
||||
var cacheOpts = {
|
||||
max:1000,
|
||||
max:100000,
|
||||
maxAge:1000*60
|
||||
};
|
||||
|
||||
@ -201,6 +201,7 @@ module.exports = function(db) {
|
||||
app.set('view cache', false);
|
||||
} else if (process.env.NODE_ENV === 'production') {
|
||||
app.locals.cache = 'memory';
|
||||
app.set('view cache', true);
|
||||
}
|
||||
|
||||
// Request body parsing middleware should be above methodOverride
|
||||
@ -294,16 +295,16 @@ module.exports = function(db) {
|
||||
// Log it
|
||||
client.captureError(err);
|
||||
|
||||
if(process.env.NODE_ENV === 'production'){
|
||||
/*if(process.env.NODE_ENV === 'production'){
|
||||
res.status(500).render('500', {
|
||||
error: 'Internal Server Error'
|
||||
});
|
||||
} else {
|
||||
} else {*/
|
||||
// Error page
|
||||
res.status(500).render('500', {
|
||||
error: err.stack
|
||||
});
|
||||
}
|
||||
//}
|
||||
});
|
||||
|
||||
// Assume 404 since no middleware responded
|
||||
|
||||
@ -120,10 +120,10 @@ module.exports = function(grunt) {
|
||||
productionForms: {
|
||||
options: {
|
||||
mangled: true,
|
||||
beautify: true
|
||||
compress: true
|
||||
},
|
||||
files: {
|
||||
'public/dist/vendor_forms_uglified.js': bowerArray
|
||||
'public/dist/vendor.min.js': bowerArray
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -367,7 +367,7 @@ module.exports = function(grunt) {
|
||||
grunt.registerTask('lint:tests', ['jshint:allTests']);
|
||||
|
||||
// Build task(s).
|
||||
grunt.registerTask('build', ['lint', 'loadConfig', 'cssmin', 'ngAnnotate', 'uglify', 'closure-compiler', 'html2js:main', 'html2js:forms']);
|
||||
grunt.registerTask('build', ['lint', 'loadConfig', 'cssmin', 'ngAnnotate', 'uglify', 'html2js:main', 'html2js:forms']);
|
||||
|
||||
//Setup task(s).
|
||||
grunt.registerTask('setup', ['execute']);
|
||||
|
||||
20
public/dist/application.js
vendored
20
public/dist/application.js
vendored
File diff suppressed because one or more lines are too long
2
public/dist/application.min.css
vendored
2
public/dist/application.min.css
vendored
File diff suppressed because one or more lines are too long
6
public/dist/application.min.js
vendored
6
public/dist/application.min.js
vendored
File diff suppressed because one or more lines are too long
18
public/dist/form-application.js
vendored
18
public/dist/form-application.js
vendored
File diff suppressed because one or more lines are too long
4
public/dist/form-application.min.js
vendored
4
public/dist/form-application.min.js
vendored
File diff suppressed because one or more lines are too long
2
public/dist/form_populate_template_cache.js
vendored
2
public/dist/form_populate_template_cache.js
vendored
File diff suppressed because one or more lines are too long
1170
public/dist/vendor.min.js
vendored
1170
public/dist/vendor.min.js
vendored
File diff suppressed because one or more lines are too long
@ -338,14 +338,30 @@ angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCoun
|
||||
|
||||
form.timeElapsed = _timeElapsed;
|
||||
form.percentageComplete = $filter('formValidity')($scope.myform) / $scope.myform.visible_form_fields.length * 100;
|
||||
delete form.endPage
|
||||
delete form.isLive
|
||||
delete form.provider
|
||||
delete form.startPage
|
||||
delete form.visible_form_fields;
|
||||
delete form.analytics;
|
||||
delete form.design;
|
||||
delete form.submissions;
|
||||
delete form.submitted;
|
||||
for(var i=0; i < $scope.myform.form_fields.length; i++){
|
||||
if($scope.myform.form_fields[i].fieldType === 'dropdown' && !$scope.myform.form_fields[i].deletePreserved){
|
||||
$scope.myform.form_fields[i].fieldValue = $scope.myform.form_fields[i].fieldValue.option_value;
|
||||
}
|
||||
|
||||
//Get rid of unnessecary attributes for each form field
|
||||
delete form.form_fields[i].submissionId;
|
||||
delete form.form_fields[i].disabled;
|
||||
delete form.form_fields[i].ratingOptions;
|
||||
delete form.form_fields[i].fieldOptions;
|
||||
delete form.form_fields[i].logicJump;
|
||||
delete form.form_fields[i].description;
|
||||
delete form.form_fields[i].validFieldTypes;
|
||||
delete form.form_fields[i].fieldType;
|
||||
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
|
||||
@ -5,12 +5,12 @@
|
||||
<div ng-show="!myform.submitted && myform.startPage.showStart" class="form-submitted" style="padding-top: 35vh;">
|
||||
<div class="row">
|
||||
<div class="col-xs-12 text-center" style="overflow-wrap: break-word;">
|
||||
<h1 style="font-weight: 400; nont-size: 25px;">
|
||||
<h1 style="font-weight: 400; nont-size: 25px;" ng-style="{'color': form.design.colors.questionColor}">
|
||||
{{myform.startPage.introTitle}}
|
||||
</h1>
|
||||
</div>
|
||||
<div class="col-xs-10 col-xs-offset-1 text-center" style="overflow-wrap: break-word;">
|
||||
<p style="color: grey; font-weight: 100; font-size: 16px;">
|
||||
<p style="font-weight: 100; font-size: 16px;" ng-style="{'color': form.design.colors.questionColor}">
|
||||
{{myform.startPage.introParagraph}}
|
||||
</p>
|
||||
</div>
|
||||
@ -102,7 +102,7 @@
|
||||
<!-- Default End Page View -->
|
||||
<div ng-if="myform.submitted && !loading && !myform.endPage.showEnd" class="form-submitted" ng-style="{'color':myform.design.colors.buttonTextColor}" style="padding-top: 5vh;">
|
||||
|
||||
<div class="field row text-center">
|
||||
<div class="field row text-center" ng-style="{'color': myform.design.colors.questionColor}">
|
||||
<div class="col-xs-12 col-sm-12 col-md-6 col-md-offset-3 text-center">{{ 'FORM_SUCCESS' | translate }}</div>
|
||||
</div>
|
||||
<div class="row form-actions">
|
||||
@ -118,12 +118,12 @@
|
||||
<div ng-if="myform.submitted && !loading && myform.endPage.showEnd" class="form-submitted" ng-style="{'color':myform.design.colors.buttonTextColor}" style="padding-top: 5vh;">
|
||||
<div class="row">
|
||||
<div class="col-xs-12 text-center" style="overflow-wrap: break-word;">
|
||||
<h1 style="font-weight: 400; nont-size: 25px;">
|
||||
<h1 style="font-weight: 400; font-size: 25px;" ng-style="{'color': myform.design.colors.questionColor}">
|
||||
{{myform.endPage.title}}
|
||||
</h1>
|
||||
</div>
|
||||
<div class="col-xs-10 col-xs-offset-1 text-center" style="overflow-wrap: break-word;">
|
||||
<p style="color: grey; font-weight: 100; font-size: 16px;">
|
||||
<p style="font-weight: 100; font-size: 16px;" ng-style="{'color': myform.design.colors.questionColor}">
|
||||
{{myform.endPage.paragraph}}
|
||||
</p>
|
||||
</div>
|
||||
@ -147,4 +147,4 @@
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -16,7 +16,7 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
|
||||
rows: []
|
||||
};
|
||||
|
||||
var submissions = $scope.myform.submissions || [];
|
||||
var submissions = $scope.myform.submissions || [];
|
||||
|
||||
//Iterate through form's submissions
|
||||
for(var i = 0; i < submissions.length; i++){
|
||||
|
||||
@ -247,7 +247,7 @@ section.public-form {
|
||||
padding: 0 10% 0 10%;
|
||||
}
|
||||
section.public-form .form-submitted {
|
||||
height: 100%;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
section.public-form .btn {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user