Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GSoC19] Add channel validation and Anouncement #2

Open
wants to merge 10 commits into
base: rc_articles
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 133 additions & 1 deletion app/components/gh-post-settings-menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default Component.extend(SettingsMenuMixin, {
session: service(),
settings: service(),
ui: service(),
rcServices: service('rc-services'),

post: null,

Expand All @@ -36,14 +37,24 @@ export default Component.extend(SettingsMenuMixin, {
ogTitleScratch: alias('post.ogTitleScratch'),
twitterDescriptionScratch: alias('post.twitterDescriptionScratch'),
twitterTitleScratch: alias('post.twitterTitleScratch'),
rcDescriptionScratch: alias('post.rcDescriptionScratch'),
rcTitleScratch: alias('post.rcTitleScratch'),
roomNameScratch: alias('post.roomNameScratch'),
slugValue: boundOneWay('post.slug'),

allowAnnouncements: boundOneWay('settings.isAnnounced'),
allowAuthorRooms: boundOneWay('settings.isAuthorsRooms'),
announce: boundOneWay('allowAnnouncements'),

roomName: or('roomNameScratch', 'settings.roomName'),
facebookDescription: or('ogDescriptionScratch', 'customExcerptScratch', 'seoDescription'),
facebookImage: or('post.ogImage', 'post.featureImage'),
facebookTitle: or('ogTitleScratch', 'seoTitle'),
twitterDescription: or('twitterDescriptionScratch', 'customExcerptScratch', 'seoDescription'),
twitterImage: or('post.twitterImage', 'post.featureImage'),
twitterTitle: or('twitterTitleScratch', 'seoTitle'),
rcDescription: or('rcDescriptionScratch', 'customExcerptScratch', 'seoDescription'),
rcImage: or('post.rcImage', 'post.featureImage'),
rcTitle: or('rcTitleScratch', 'seoTitle'),

seoTitle: computed('metaTitleScratch', 'post.titleScratch', function () {
return this.metaTitleScratch || this.post.titleScratch || '(Untitled)';
Expand Down Expand Up @@ -116,6 +127,11 @@ export default Component.extend(SettingsMenuMixin, {
post.validate({attribute: 'publishedAtBlog'});
}

if (errors.has('roomName')) {
post.set('roomNameScratch', post.get('roomName'));
post.validate({attribute: 'roomName'});
}

// remove throbbers
this.set('_showThrobbers', false);
}
Expand Down Expand Up @@ -163,6 +179,50 @@ export default Component.extend(SettingsMenuMixin, {
});
},

/**
* triggered by user manually changing announce-setting
*/
toggleAnnounce() {
let post = this.post;
let announce = this.announce;
this.toggleProperty('announce');
post.set('announce', !announce);
post.set('announceChanged', true);
},

/**
* triggered by user manually changing room-name
*/
validateRoom(newRoom) {
let oldRoom = this.roomName;
let post = this.post;
let errMessage = 'Room does not exist';

// reset errors and validation
post.get('errors').remove('roomName');

if (!newRoom) {
newRoom = oldRoom;
}
this.rcServices.getRoom(newRoom)
.then((room) => {
const existingRCRoom = room.data[0].exist && room.data[0].roomname === newRoom;

if (!existingRCRoom) {
throw errMessage;
}
post.set('roomName', newRoom);
post.set('roomId', room.data[0].rid);
})
.catch((e) => {
if (e === errMessage){
post.get('errors').add('roomName', errMessage);
return;
}
throw e;
});
},

/**
* triggered by user manually changing slug
*/
Expand Down Expand Up @@ -402,6 +462,52 @@ export default Component.extend(SettingsMenuMixin, {
});
},

setRcTitle(rcTitle) {
// Grab the post and current stored twitter title
let post = this.post;
let currentTitle = post.get('rcTitle');

// If the title entered matches the stored twitter title, do nothing
if (currentTitle === rcTitle) {
return;
}

// If the title entered is different, set it as the new twitter title
post.set('rcTitle', rcTitle);

// Make sure the twitter title is valid and if so, save it into the post
return post.validate({property: 'rcTitle'}).then(() => {
if (post.get('isNew')) {
return;
}

return this.savePost.perform();
});
},

setRcDescription(rcDescription) {
// Grab the post and current stored twitter description
let post = this.post;
let currentDescription = post.get('rcDescription');

// If the description entered matches the stored twitter description, do nothing
if (currentDescription === rcDescription) {
return;
}

// If the description entered is different, set it as the new twitter description
post.set('rcDescription', rcDescription);

// Make sure the twitter description is valid and if so, save it into the post
return post.validate({property: 'rcDescription'}).then(() => {
if (post.get('isNew')) {
return;
}

return this.savePost.perform();
});
},

setCoverImage(image) {
this.set('post.featureImage', image);

Expand Down Expand Up @@ -480,6 +586,32 @@ export default Component.extend(SettingsMenuMixin, {
});
},

setRcImage(image) {
this.set('post.rcImage', image);

if (this.get('post.isNew')) {
return;
}

this.savePost.perform().catch((error) => {
this.showError(error);
this.post.rollbackAttributes();
});
},

clearRcImage() {
this.set('post.rcImage', '');

if (this.get('post.isNew')) {
return;
}

this.savePost.perform().catch((error) => {
this.showError(error);
this.post.rollbackAttributes();
});
},

changeAuthors(newAuthors) {
let post = this.post;

Expand Down
47 changes: 20 additions & 27 deletions app/components/modal-invite-new-user.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const {Promise} = RSVP;
export default ModalComponent.extend(ValidationEngine, {
notifications: service(),
store: service(),
rcService: service('rc-services'),

classNames: 'modal-content invite-new-user',

Expand Down Expand Up @@ -48,36 +49,36 @@ export default ModalComponent.extend(ValidationEngine, {
},

validate() {
let email = this.email;
let username = this.username;

// TODO: either the validator should check the email's existence or
// TODO: either the validator should check the username's existence or
// the API should return an appropriate error when attempting to save
return new Promise((resolve, reject) => this._super().then(() => RSVP.hash({
users: this.store.findAll('user', {reload: true}),
invites: this.store.findAll('invite', {reload: true})
rc_users: this.rcService.getUser(username),
users: this.store.findAll('user', {reload: true})
}).then((data) => {
let existingUser = data.users.findBy('email', email);
let existingInvite = data.invites.findBy('email', email);
let existingRCUser = data.rc_users.data[0].exist;
let existingUser = data.users.findBy('rc_username', username);

if (existingUser || existingInvite) {
this.errors.clear('email');
if (existingUser || !existingRCUser) {
this.errors.clear('username');
if (existingUser) {
this.errors.add('email', 'A user with that email address already exists.');
this.errors.add('username', 'A user with that username already exists.');
} else {
this.errors.add('email', 'A user with that email address was already invited.');
this.errors.add('username', 'Username doesnot exist');
}

// TODO: this shouldn't be needed, ValidationEngine doesn't mark
// properties as validated when validating an entire object
this.hasValidated.addObject('email');
this.hasValidated.addObject('username');
reject();
} else {
resolve();
}
}), () => {
// TODO: this shouldn't be needed, ValidationEngine doesn't mark
// properties as validated when validating an entire object
this.hasValidated.addObject('email');
this.hasValidated.addObject('username');
reject();
}));
},
Expand All @@ -95,35 +96,27 @@ export default ModalComponent.extend(ValidationEngine, {
}),

sendInvitation: task(function* () {
let email = this.email;
let username = this.username;
let role = this.role;
let notifications = this.notifications;
let notificationText = `Invitation sent! (${email})`;
let invite;

try {
yield this.validate();

invite = this.store.createRecord('invite', {
email,
role
});
const user = yield this.rcService.addUser(username, role);

yield invite.save();

// If sending the invitation email fails, the API will still return a status of 201
// but the invite's status in the response object will be 'invited-pending'.
if (invite.get('status') === 'pending') {
notifications.showAlert('Invitation email was not sent. Please try resending.', {type: 'error', key: 'invite.send.failed'});
// Check and notify if user is added
if (user.invitation && user.invitation[0].message === 'User Added') {
notifications.showNotification(user.invitation[0].message, {key: 'invite.send.success'});
} else {
notifications.showNotification(notificationText, {key: 'invite.send.success'});
notifications.showAlert('Unable to add user', {type: 'error', key: 'invite.send.failed'});
}

this.send('closeModal');
} catch (error) {
// validation will reject and cause this to be called with no error
if (error) {
invite.deleteRecord();
// invite.deleteRecord();
notifications.showAPIError(error, {key: 'invite.send'});
this.send('closeModal');
}
Expand Down
68 changes: 68 additions & 0 deletions app/controllers/settings/general.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,22 @@ export default Controller.extend({
session: service(),
settings: service(),
ui: service(),
rcServices: service('rc_services'),

availableTimezones: null,
iconExtensions: null,
iconMimeTypes: 'image/png,image/x-icon',
imageExtensions: IMAGE_EXTENSIONS,
imageMimeTypes: IMAGE_MIME_TYPES,

_scratchRoom: null,
_scratchFacebook: null,
_scratchTwitter: null,

init() {
this._super(...arguments);
this.iconExtensions = ICON_EXTENSIONS;
this._scratchRoom = this.get('settings.roomName');
},

privateRSSUrl: computed('config.blogUrl', 'settings.publicHash', function () {
Expand Down Expand Up @@ -102,6 +105,35 @@ export default Controller.extend({
}
},

toggleIsAnnounced(isAnnounced) {
let settings = this.settings;

settings.set('isAnnounced', isAnnounced);

// Change isAuthorsRooms to false when isAnnounce is disabled
if (!isAnnounced) {
settings.set('isAuthorsRooms', false);
}
},

toggleIsAuthorsRooms(isAuthorsRooms) {
let settings = this.settings;

settings.set('isAuthorsRooms', isAuthorsRooms);
},

toggleIsComments(isComments) {
let settings = this.settings;

settings.set('isComments', isComments);
},

toggleInviteOnly(inviteOnly) {
let settings = this.settings;

settings.set('inviteOnly', inviteOnly);
},

toggleLeaveSettingsModal(transition) {
let leaveTransition = this.leaveSettingsTransition;

Expand Down Expand Up @@ -141,6 +173,42 @@ export default Controller.extend({
return transition.retry();
},

validateRoom() {
let newRoom = this._scratchRoom;
let oldRoom = this.get('settings.roomName');
let errMessage = 'Room does not exist';

// reset errors and validation
this.get('settings.errors').remove('room');
this.get('settings.hasValidated').removeObject('room');

if (!newRoom) {
newRoom = oldRoom;
}
this.rcServices.getRoom(newRoom)
.then((room) => {
const existingRCRoom = room.data[0].exist && room.data[0].roomname === newRoom;

if (!existingRCRoom) {
throw errMessage;
}
run.schedule('afterRender', this, function () {
this.set('settings.roomName', newRoom);
this.set('settings.roomId', room.data[0].rid);
});
})
.catch((e) => {
if (e === errMessage){
this.get('settings.errors').add('room', errMessage);
return;
}
throw e;
})
.finally(() => {
this.get('settings.hasValidated').pushObject('room');
});
},

validateFacebookUrl() {
let newUrl = this._scratchFacebook;
let oldUrl = this.get('settings.facebook');
Expand Down
Loading