Skip to content

Commit

Permalink
Can create + update log entries, sort of
Browse files Browse the repository at this point in the history
  • Loading branch information
etousley committed Aug 8, 2017
1 parent 7dd6a16 commit e1c2202
Show file tree
Hide file tree
Showing 7 changed files with 222 additions and 91 deletions.
6 changes: 3 additions & 3 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,13 +143,13 @@ app.get('/api/log', passportConfig.isAuthenticated, logEntryController.getLogEnt
app.get('/api/log/:id', passportConfig.isAuthenticated, logEntryController.getLogEntry);

// Update a single log entry
app.put('/api/log/:id', passportConfig.isAuthenticatedOwner, logEntryController.updateLogEntry);
app.put('/api/log/:id', passportConfig.isAuthenticated, logEntryController.updateLogEntry);

// Create a new log entry
app.post('/api/log', passportConfig.isAuthenticatedOwner, logEntryController.createLogEntry);
app.post('/api/log', passportConfig.isAuthenticated, logEntryController.createLogEntry);

// Delete a single log entry
app.delete('/api/log/:id', passportConfig.isAuthenticatedOwner, logEntryController.deleteLogEntry);
app.delete('/api/log/:id', passportConfig.isAuthenticated, logEntryController.deleteLogEntry);

// Get all activity definitions
app.get('/api/activity', passportConfig.isAuthenticated, logEntryController.getActivityDefinitions);
Expand Down
11 changes: 0 additions & 11 deletions config/passport.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,3 @@ exports.isAuthorized = (req, res, next) => {
res.redirect(`/auth/${provider}`);
}
};

/**
* Login Required middleware - user making request must be same user whose email is in the request
*/
exports.isAuthenticatedOwner = (req, res, next) => {
const ownerEmail = req.path.split('/').slice(-1)[0];
if (req.isAuthenticated() && req.user.email === ownerEmail) {
return next();
}
res.status(401).send("401 Forbidden: You don't have permission to make this request");
};
45 changes: 34 additions & 11 deletions controllers/logEntry.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ exports.getActivityDefinitions = (req, res) => {
*/
exports.getLog = (req, res) => {
res.render('log/index', {
title: 'My Log'
title: 'My Log',
user: req.user,
logOwner: {email: req.url.split("/").slice(-1)[0]},
activities: lookups.activityDefinitions
});
};

Expand Down Expand Up @@ -50,7 +53,7 @@ exports.getLogEntries = (req, res) => {
}
}

console.log('filter: ' + JSON.stringify(filter));
// console.log('filter: ' + JSON.stringify(filter));

LogEntry.find(filter, function(err, activities) {
res.send({ data: activities });
Expand Down Expand Up @@ -78,10 +81,17 @@ exports.getLogEntry = (req, res) => {
*/
exports.updateLogEntry = (req, res) => {
const id = req.params.id;
const updateData = req.body.logEntry;
const requester = req.user.email;
const updatedEntry = req.body.data;

LogEntry.findByIdAndUpdate(id, updateData, function(err, logEntry) {
res.send({ data: logEntry });
LogEntry.findById(id, function(err, logEntry) {
if (logEntry.user === requester) {
LogEntry.findByIdAndUpdate(id, updatedEntry, function(err, logEntry) {
res.send({ data: logEntry });
});
} else {
res.status(401).send("401 Forbidden: You aren't the owner of this log entry");
}
});
};

Expand All @@ -93,10 +103,16 @@ exports.updateLogEntry = (req, res) => {
*/
exports.createLogEntry = (req, res) => {
const newEntry = req.body.logEntry;

LogEntry.save(newEntry, function(err, logEntry) {
res.send({ data: logEntry });
});
const requesterEmail = req.user.email;
const ownerEmail = newEntry.user;

if (requesterEmail === ownerEmail) {
LogEntry.save(newEntry, function(err, logEntry) {
res.send({ data: logEntry });
});
} else {
res.status(401).send("401 Forbidden: You aren't the owner of this log entry");
}
};


Expand All @@ -107,9 +123,16 @@ exports.createLogEntry = (req, res) => {
*/
exports.deleteLogEntry = (req, res) => {
const id = req.params.id;
const requesterEmail = req.user.email;

LogEntry.findById(id).remove(function(err) {
res.status(200).send("Success: Deleted logEntry document ID: " + id);
LogEntry.findById(id, function(err, logEntry) {
if (logEntry.user === requester) {
LogEntry.findById(id).remove(function(err) {
res.status(200).send("200 Success: Deleted log entry with id = " + id);
});
} else {
res.status(401).send("401 Forbidden: You aren't the owner of this log entry");
}
});
};

Expand Down
24 changes: 22 additions & 2 deletions public/css/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,26 @@ textarea {
cursor: pointer;
}

.fc-day.active {
.fc-day:hover {
background-color: #e6fff7;
}

.fc-day-top {
cursor: pointer;
}

.fc-row .fc-content-skeleton {
cursor: pointer;
}

.fc .log-entry-btn {
display: block;
margin-top: 2em;
margin-right: auto;
margin-bottom: 1em;
margin-left: auto;
}

// Log entry modal styles
// -------------------------
.modal-title {
Expand All @@ -93,5 +109,9 @@ textarea {

.modal-header > .close {
max-width: 10%;
float: rightt;
float: right;
}

// .entry-duration-value-input {
// max-width: 100px;
// }
147 changes: 106 additions & 41 deletions public/js/log.js
Original file line number Diff line number Diff line change
@@ -1,82 +1,140 @@

// TODO: view existing log entry
// TODO: create/edit log entry (in modal?)
// TODO: save log entry
// Global variables used by multiple functions
const CSRF_HEADER = 'X-CSRF-Token';

let modal = $('#log-entry-modal');
let modalDate = modal.find('.entry-date');
let modalTitle = modal.find('.entry-title-input');
let modalActivityButton = modal.find('#btn-activity');
let modalDescription = modal.find('.entry-description-input');
let modalDurationValue = modal.find('.entry-duration-value-input');
let activeEntryData = {};


/**
* Set AJAX prefilter so that HTTP requests get through Express.js CSRF protection
* Credit: https://stackoverflow.com/a/18041849
*/
setCSRFToken = (securityToken) => {
jQuery.ajaxPrefilter(function (options, _, xhr) {
if (!xhr.crossDomain) {
xhr.setRequestHeader(CSRF_HEADER, securityToken);
}
});
};


/**
* Get log data from REST API, then draw them to calendar
*/
fillLogEntries = () => {
const dayElems = $('.fc-day');
const email = window.location.href.split("/").slice(-1)[0];
const email = window.location.href.split('/')[-1];
const startDate = dayElems[0].dataset.date;
const endDate = dayElems[dayElems.length - 1].dataset.date;
const ignoreDataKeys = {'_id': 1, 'user': 1}
let entryTitle = undefined;
let entryElem = undefined;

// Build query parameters for GET request
const url = '/api/log?' + jQuery.param({
const getLogEntriesUrl = '/api/log?' + jQuery.param({
"email": email,
"from": startDate,
"to": endDate
});
let entryElem = undefined;


// Map data to calendar days
// Could probably do this more efficiently...
$.get(url, function(data) {
$.get(getLogEntriesUrl, function(data) {
for (entry of data.data) {
for (dayElem of dayElems) {
if (entry.date.slice(0, 10) === dayElem.dataset.date) {
entryTitle = entry.title || (entry.activity + " (" + entry.durationValue + " " + entry.durationUnit + "s)");
entryElem = document.createElement("div");
entryElem.className = "log-entry-title";
entryElem.textContent = entryTitle;
entryElem = document.createElement("button");
entryElem.className = "btn btn-sm btn-primary log-entry-btn";
entryElem.textContent = entry.title || (entry.activity + " (" + entry.durationValue + " " + entry.durationUnit + "s)");;

// Add data as data-foo attributes to the event element (probably a better way...)
// Add data as data-foo attributes to the event element (there's probably a better way...)
for (let [key, value] of Object.entries(entry)) {
if ( !(key in ignoreDataKeys) ) {
entryElem.dataset[key] = value;
}
entryElem.dataset[key] = value;
}

dayElem.appendChild(entryElem);
break;
}
}
}
})
});
}


/**
* Render and display modal to reflect data (date, user, existing activities)
*/
drawLogEntryModal = (clickedDayElem) => {
const entryElem = clickedDayElem.find('.log-entry-title')[0];
let modal = $('#log-entry-modal');
let modalDate = modal.find('.entry-date');
let modalTitle = modal.find('.entry-title-input');
let modalActivity = modal.find('.entry-activity-input');
let modalDurationValue = modal.find('.entry-duration-value-input');
let modalDescription = modal.find('.entry-description-input');

modalDate.html( entryElem.dataset.date.slice(0, 10) );
modalTitle.val(entryElem.dataset.title);
modalActivity.html(entryElem.dataset.activity); // TODO: this is wrong
modalDurationValue.val(entryElem.dataset.durationValue);
modalDescription.val(entryElem.dataset.description);
modalDate.html( activeEntryData.date.slice(0, 10) );
modalTitle.val(activeEntryData.title);
modalActivityButton.html(activeEntryData.activity);
modalActivityButton.val(activeEntryData.activity);
modalDescription.val(activeEntryData.description);
modalDurationValue.val(activeEntryData.durationValue);

modal.modal('show');
};


/**
* Create or update log entry
*/
saveLogEntry = () => {
const newEntryData = {
"date": activeEntryData.date,
"title": modalTitle.val(),
"activity": modalActivityButton.html(),
"description": modalDescription.val(),
"durationValue": modalDurationValue.val()
};
updateActiveEntryData(newEntryData);

if (activeEntryData._id) {
// If there's already an _id, do a PUT (update)
$.ajax({
url: '/api/log/' + activeEntryData._id,
type: 'PUT',
data: {"data": activeEntryData},
success: function(data) {
console.log('Updated entry: ' + JSON.stringify(activeEntryData));
}
});
} else {
// If there's no _id, do a POST (create)
$.ajax({
url: '/api/log/',
type: 'POST',
data: {"data": activeEntryData},
success: function(data) {
console.log('Created entry: ' + JSON.stringify(activeEntryData));
}
});
}
};


/**
* Update activeEntryData to reflect current state of log entry modal
*/
updateActiveEntryData = (newEntryData) => {
if (newEntryData === undefined || Object.keys(newEntryData).length === 0) {
console.log("Missing newEntryData: " + newEntryData);
return;
}
for ( let [key, val] of Object.entries(newEntryData) ) {
activeEntryData[key] = val;
};
};


/**
* Do stuff when page loads
*/
$(document).ready(function() {
let visibleDates = undefined;
setCSRFToken($('meta[name="csrf-token"]').attr('content'));

// Hide modal
$('#log-entry-modal').modal('hide');
Expand All @@ -90,14 +148,21 @@ $(document).ready(function() {
// TODO: this should also get called when month arrows are clicked
fillLogEntries();

$('.fc-day').on('click touch', function () {
const clickedDayElem = $(this);
// Show log entry when date is clicked
$('.fc-day, .fc-day-top, .log-entry-btn').on('click touch', function () {
event.stopPropagation(); // Need this to click on an entry button inside a clickable day cell
updateActiveEntryData(event.target.dataset);
drawLogEntryModal(event.target);
});

// Mark active day
$('.fc-day').removeClass('active');
clickedDayElem.addClass('active');
// Save log entry when Save button is clicked
$('.save-entry').on('click touch', function() {
saveLogEntry();
});

drawLogEntryModal(clickedDayElem);
// $('#log-entry-modal').modal('show');
// Update selected value (and html) when a dropdown option is clicked
$('#activity-dropdown li > a').on('click touch', function() {
$('#btn-activity').html(this.innerHTML);
$('#btn-activity').val(this.innerHTML);
});
});
9 changes: 6 additions & 3 deletions views/layout.pug
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ html
link(rel='shortcut icon', href='/favicon.png')
link(rel='stylesheet', href='/css/main.css')
link(rel='stylesheet', href='/css/lib/fullcalendar.min.css')
//- script(src='/js/lib/jquery-3.1.1.min.js')

//- link(rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous")
script(src='https://code.jquery.com/jquery-3.1.1.min.js')
//- script(src="https://code.jquery.com/jquery-3.1.1.slim.min.js" integrity="sha384-A7FZj7v+d/sdmMqp/nOQwliLvUsJfDHW+k9Omg/a/EheAdgtzNs3hpfag6Ed950n" crossorigin="anonymous")
//- script(src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js" integrity="sha384-DztdAPBWPRXSA/3eYEEUWrWCy7G5KFbe8fFjk5JAIxUYHKkDx6Qin1DkWx51bBrb" crossorigin="anonymous")
//- script(src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js" integrity="sha384-vBWWzlZJ8ea9aCX4pEW3rVHjgjt7zpkNpZk+02D9phzyeVkE+jo0ieGizqPLForn" crossorigin="anonymous")
script(src='https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.11.1/moment.min.js')

body
Expand All @@ -24,7 +27,7 @@ html

include partials/footer

// Note: fullcalendar 3.4.0 has bugs. Took CSS and JS files from 3.3.1
//- Note: Using fullcalendar 3.3.1.
script(src='/js/lib/fullcalendar.min.js')
script(src='/js/lib/bootstrap.min.js')
script(src='/js/main.js')
Expand Down
Loading

0 comments on commit e1c2202

Please sign in to comment.