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

Add events editing portal and increase API ability #270

Open
wants to merge 41 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
1bda1ef
Add helloworld page for displaying events in database
CharlieCrisp Jul 12, 2018
7913e95
Add basic functionality to iterate through events in database
CharlieCrisp Jul 12, 2018
09c3dad
Display more information about individual events
CharlieCrisp Jul 12, 2018
c5f039a
Add credentials test to events portal
CharlieCrisp Jul 12, 2018
c90cc9f
Remove eventDescription reference in docs
CharlieCrisp Jul 12, 2018
8513bba
Display event description in events portal
CharlieCrisp Jul 12, 2018
ce7ce41
Display all remaining fields in event portal
CharlieCrisp Jul 12, 2018
1388210
Change event schema to include eventId
CharlieCrisp Jul 12, 2018
3ad3fe8
Add individual event view route
CharlieCrisp Jul 12, 2018
623ce07
Use variable fields in events to allow future editing
CharlieCrisp Jul 12, 2018
6b9fc95
Rename add event handler
CharlieCrisp Jul 12, 2018
3306e3d
Add empty event edit handler to api
CharlieCrisp Jul 12, 2018
26f4766
Display event titles more clearly
CharlieCrisp Jul 12, 2018
f8eb9f1
Use vars not lets to allow Location properties to change
CharlieCrisp Jul 12, 2018
e5e8271
Add new api route for updating events
CharlieCrisp Jul 12, 2018
ddd9381
Update documentation to include edit_event api
CharlieCrisp Jul 12, 2018
c66149f
Check database for events with matching id before adding
CharlieCrisp Jul 12, 2018
19a480a
Check for location in edit event api better
CharlieCrisp Jul 12, 2018
15cd58e
Add api for deleting events
CharlieCrisp Jul 12, 2018
e6158f4
Update documentation to include delete events api
CharlieCrisp Jul 12, 2018
cb9bbd5
Add more attributes
CharlieCrisp Jul 13, 2018
f39a23a
Add gui form for adding events
CharlieCrisp Jul 13, 2018
a300e0e
Make api and form location fields compatible
CharlieCrisp Jul 13, 2018
9f7d46c
Add more instructions to AddEventForm
CharlieCrisp Jul 13, 2018
d977369
Add DeleteEvent form
CharlieCrisp Jul 13, 2018
f8dcb40
Add delete link to event listings
CharlieCrisp Jul 13, 2018
1c56c4b
Refactor codebase with Events folder
CharlieCrisp Jul 13, 2018
04f2077
Add GUI for editing events
CharlieCrisp Jul 13, 2018
e26805d
Add feedback to GUI when api call is made
CharlieCrisp Jul 13, 2018
920bfdb
Display required strings for dates
CharlieCrisp Jul 13, 2018
076f1a1
Remove incorrect http status
CharlieCrisp Jul 13, 2018
7bad59d
Fix multiple wrong messages appearing when edit/deleting events
CharlieCrisp Jul 13, 2018
20a12c7
Allow input of multiple tags
CharlieCrisp Jul 16, 2018
d97cf0a
Display all tags in EditEventPage
CharlieCrisp Jul 16, 2018
6ac7fae
Fix plurality on events portal
CharlieCrisp Jul 16, 2018
b5fc6d9
Minor refactor
CharlieCrisp Jul 16, 2018
84736b0
Add step to HaCTML attributes
CharlieCrisp Jul 16, 2018
faccebc
Update how api udpates dates
CharlieCrisp Jul 16, 2018
1c37a98
Implement UI for entering dates
CharlieCrisp Jul 16, 2018
dcf3736
Refactor edit event api
CharlieCrisp Jul 17, 2018
477a91a
Refactor Commit
CharlieCrisp Jul 17, 2018
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
1 change: 0 additions & 1 deletion Docs/Api/addEventSample.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ curl -X POST -H "Content-Type: application/json" -d @customEvent.json http://cha
"hypeStartDate":"2017-11-15T12:01:12.123",
"hypeEndDate":"2017-11-25T12:01:12.123",
"tags": ["tag1", "tag2"],
"eventDescription": "This is a plain description",
"websiteURL":"www.website.com",
"imageURL":"www.website.com",
"markdownDescription":"plain markdown",
Expand Down
13 changes: 13 additions & 0 deletions Docs/Api/deleteEventSample.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
### Example curl command to edit an event from a JSON file in the same directory

```
curl -X POST -H "Content-Type: application/json" -d @deleteEvent.json http://charlie:secret@localhost:3000/api/delete_event
```

### Example JSON with required field to identify target event and a new value for the title field

```
{
"eventId": "workshop-id-2"
}
```
16 changes: 16 additions & 0 deletions Docs/Api/editEventSample.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
### Example curl command to edit an event from a JSON file in the same directory

```
curl -X POST -H "Content-Type: application/json" -d @editEvent.json http://charlie:secret@localhost:3000/api/edit_event
```

### Example JSON with required field to identify target event and a new value for the title field

```
{
"eventId": "workshop-id-2",
"title":"Better Workshop Title"
}
```

Note - The parameters that can be used to trigger updates are identical to the parameters which can be included when adding events via the add_events api.
6 changes: 6 additions & 0 deletions Sources/HaCTML/Attributes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,26 @@ public enum Attributes {
// We are filling this list in incrementally,
// implementing attributes as we need them so that we can type them correctly
static public let alt = stringAttribute("alt")
static public let action = stringAttribute("action")
static public let charset = stringAttribute("charset")
static public let className = stringAttribute("class")
static public let content = stringAttribute("content")
static public let encType = stringAttribute("enctype")
static public let height = numberAttribute("height")
static public let href = stringAttribute("href")
static public let id = stringAttribute("id")
static public let lang = stringAttribute("lang")
static public let method = stringAttribute("method")
static public let name = stringAttribute("name")
static public let onClick = stringAttribute("onclick")
static public let rel = stringAttribute("rel")
static public let src = stringAttribute("src")
static public let srcset = stringAttribute("srcset")
static public let step = stringAttribute("step")
static public let style = CSSAttribute("style")
static public let target = stringAttribute("target")
static public let type = stringAttribute("type")
static public let value = stringAttribute("value")
static public let placeholder = stringAttribute("placeholder")
static public let tabIndex = numberAttribute("tab-index")
static public let width = numberAttribute("width")
Expand Down
237 changes: 210 additions & 27 deletions Sources/HaCWebsiteLib/Controllers/EventApiController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import DotEnv
import LoggerAPI

struct EventApiController {
static var handler: RouterHandler = { request, response, next in
static var addEventHandler: RouterHandler = { request, response, next in
response.headers["Content-Type"] = "text/plain; charset=utf-8"
guard let parsedBody = request.body else {
next()
Expand All @@ -17,34 +17,123 @@ struct EventApiController {
}
if case .json(let json) = parsedBody {
do {
try saveEvent(json: json)
if let eventId = json["eventId"] as? String {
if EventServer.doesEventWithIdExist(eventId: eventId) {
Log.info("Database event adding failed - Event with that ID exists")
response.statusCode = HTTPStatusCode.badRequest
try response.send("Database event adding failed - Event with that ID exists\n").end()
return
}
}
try getEventFromJSON(src: json).save()
Log.info("Succesfully added event to the database")
try response.send("Successfully added your event to the database\n").end()
}
catch EventParsingError.missingParameters {
response.send("Successfully added your event to the database\n")
} catch EventParsingError.missingParameters {
Log.info("Database event adding failed - missing parameters")
response.statusCode = HTTPStatusCode.badRequest
try response.send("Sorry - looks like you didn't include all the necessary fields\n").end()
response.send("Sorry - looks like you didn't include all the necessary fields\n")
} catch EventParsingError.invalidHypePeriod {
Log.info("Database event adding failed - invalid hype period")
response.statusCode = HTTPStatusCode.badRequest
try response.send("Sorry - looks like the hype period was invalid\n").end()
response.send("Sorry - looks like the hype period was invalid\n")
}
} else {
Log.info("Adding event to database failed for unkown reason")
response.statusCode = HTTPStatusCode.badRequest
try response.send("Please use JSON for post data\n").end()
response.send("Please use JSON for post data\n")
}
next()
}

private static func saveEvent(json: [String : Any]) throws {
let event = try parseEvent(json: json)
try event.save()
static var editEventHandler: RouterHandler = { request, response, next in
response.headers["Content-Type"] = "text/plain; charset=utf-8"
guard let parsedBody = request.body else {
next()
response.statusCode = HTTPStatusCode.internalServerError
Log.info("Unable to parse the body of the request")
try response.send("Sorry - we weren't able to parse the body of the request\n").end()
return
}
if case .json(let json) = parsedBody {
do {
guard let id = request.parameters["eventId"] else {
throw EventParsingError.missingParameters
}
guard let event = EventServer.getEvent(eventId: id) else {
throw EventParsingError.noSuchEvent
}
let updatedEvent = try updateNecessaryFields(event: event, updates: json)
try updatedEvent.save()
Log.info("Event updated in database sucessfully")
response.send("The event has been updated sucessfully.\n")
} catch EventParsingError.missingParameters {
Log.info("Updating event in database failed due to missing parameters in api call")
response.statusCode = HTTPStatusCode.badRequest
response.send("There were missing parameters in your api call. Please consult the API documentation. \n")
} catch EventParsingError.noSuchEvent {
Log.info("Updating event in database failed due to the event not existing")
response.statusCode = HTTPStatusCode.badRequest
response.send("It appears there is no such event with that id.\n")
} catch {
Log.info("Updating event in database failed due to unknown reason")
response.statusCode = HTTPStatusCode.badRequest
response.send("Could not update event for unknown reason. \n")
}
} else {
Log.info("Editing event in database failed for unkown reason")
response.statusCode = HTTPStatusCode.badRequest
response.send("Please use JSON for post data\n")
}
next()
}

public static func parseEvent(json : [String : Any]) throws -> GeneralEvent {
guard let title = json["title"] as? String,
static var deleteEventHandler: RouterHandler = { request, response, next in
response.headers["Content-Type"] = "text/plain; charset=utf-8"
guard let parsedBody = request.body else {
next()
response.statusCode = HTTPStatusCode.internalServerError
Log.info("Unable to parse the body of the request")
try response.send("Sorry - we weren't able to parse the body of the request\n").end()
return
}
if case .json(let json) = parsedBody {
do {
guard let id = json["eventId"] as? String else {
throw EventParsingError.missingParameters
}
guard let event = EventServer.getEvent(eventId: id) else {
throw EventParsingError.noSuchEvent
}
try event.delete()
Log.info("Event deleted from database sucessfully")
response.send("The event has been deleted sucessfully.\n")
} catch EventParsingError.missingParameters {
Log.info("Deleting event in database failed due to missing eventId parameter")
response.statusCode = HTTPStatusCode.badRequest
response.send("Deleting event in database failed due to missing eventId parameter.\n")
} catch EventParsingError.noSuchEvent {
Log.info("Deleting event in database failed due to the event not existing")
response.statusCode = HTTPStatusCode.badRequest
response.send("It appears there is no such event with that id.\n")
} catch {
Log.info("Deleting event in database failed due to unknown reason")
response.statusCode = HTTPStatusCode.badRequest
response.send("Could not delete event for unknown reason. \n")
}
} else {
Log.info("Deleting event in database failed for unkown reason")
response.statusCode = HTTPStatusCode.badRequest
response.send("Please use JSON for post data\n")
}
next()
}

/*
Generate a GeneralEvent object from a JSON object containing ALL the necessary fields.
*/
public static func getEventFromJSON(src json: [String : Any]) throws -> GeneralEvent {
guard let eventId = json["eventId"] as? String,
let title = json["title"] as? String,
let startDate = Date.from(string: json["startDate"] as? String),
let endDate = Date.from(string: json["endDate"] as? String),
let tagLine = json["tagLine"] as? String,
Expand All @@ -58,11 +147,8 @@ struct EventApiController {

// Make sure the event itself falls within the hype period
// If it doesn't, we throw an invalidHypePeriod exception
if !((hypeStartDate <= startDate) &&
(startDate <= hypeEndDate) &&
(hypeStartDate <= endDate) &&
(endDate <= hypeEndDate)) {
throw EventParsingError.invalidHypePeriod
if !((hypeStartDate <= startDate) && (startDate <= endDate) && (endDate <= hypeEndDate)) {
throw EventParsingError.invalidHypePeriod
}

let location = getOptionalLocation(json: json)
Expand All @@ -75,20 +161,37 @@ struct EventApiController {
let imageURL = json["imageURL"] as? String
let facebookEventID = json["facebookEventID"] as? String

return GeneralEvent(title: title, time: time, tagLine: tagLine, color: color, hypePeriod: hypePeriod,
tags: tags, description: eventDescription, websiteURL: websiteURL, imageURL: imageURL, location: location,
facebookEventID: facebookEventID)
return GeneralEvent(
eventId: eventId,
title: title,
time: time,
tagLine: tagLine,
color: color,
hypePeriod: hypePeriod,
tags: tags,
description: eventDescription,
websiteURL: websiteURL,
imageURL: imageURL,
location: location,
facebookEventID: facebookEventID
)
}

private static func getOptionalLocation(json: [String : Any]) -> Location? {
guard let latitude = json["latitude"] as? Int,
let longitude = json["longitude"] as? Int else {
return nil
}
var long : Double? = nil
var lat : Double? = nil
if let latInt = json["latitude"] as? Int { lat = Double(latInt) }
if let longInt = json["longitude"] as? Int { long = Double(longInt) }
if let latDouble = json["latitude"] as? Double { lat = latDouble }
if let longDouble = json["longitude"] as? Double { long = longDouble }

let venue = json["venue"] as? String
let address = json["address"] as? String
return Location(latitude: Double(latitude), longitude: Double(longitude),
address: address, venue: venue)

if let longitude = long, let latitude = lat {
return Location(latitude: latitude, longitude: longitude, address: address, venue: venue)
}
return nil
}

private static func getOptionalTags(json: [String : Any]) -> [String]? {
Expand All @@ -101,9 +204,89 @@ struct EventApiController {
}
return tagsArray
}

private static func updateNecessaryFields(event: GeneralEvent, updates json: [String : Any]) throws -> GeneralEvent {
event.eventId = try getLatestFieldString(originalValue: event.eventId, json: json, fieldName: "eventId")
event.title = try getLatestFieldString(originalValue: event.title, json: json, fieldName: "title")
event.tagLine = try getLatestFieldString(originalValue: event.tagLine, json: json, fieldName: "tagLine")
event.color = try getLatestFieldString(originalValue: event.color, json: json, fieldName: "color")
event.websiteURL = try getLatestFieldStringOpt(originalValue: event.websiteURL, json: json, fieldName: "websiteURL")
event.imageURL = try getLatestFieldStringOpt(originalValue: event.imageURL, json: json, fieldName: "imageURL")
event.eventDescription = Markdown(try getLatestFieldString(originalValue: event.eventDescription.raw, json: json, fieldName: "markdownDescription"))
event.facebookEventID = try getLatestFieldStringOpt(originalValue: event.facebookEventID, json: json, fieldName: "facebookEventID")

//Check for new times in json

let startDate = getLatestFieldDate(originalValue: event.time.start, json: json, fieldName: "startDate")
let endDate = getLatestFieldDate(originalValue: event.time.end, json: json, fieldName: "endDate")
let hypeStartDate = getLatestFieldDate(originalValue: event.hypePeriod.start, json: json, fieldName: "hypeStartDate")
let hypeEndDate = getLatestFieldDate(originalValue: event.hypePeriod.end, json: json, fieldName: "hypeEndDate")

if !((hypeStartDate <= startDate) &&
(startDate <= endDate) &&
(endDate <= hypeEndDate)) {
throw EventParsingError.invalidUpdateValue
}
//Update DateIntervals in event
event.time = DateInterval(start: startDate, end: endDate)
event.hypePeriod = DateInterval(start: hypeStartDate, end: hypeEndDate)

if let newTags = getOptionalTags(json: json) {
event.tags = newTags
}
if let newLocation = getOptionalLocation(json: json) {
event.location = newLocation
}
return event
}

/*
Scan through a json and return either
- the String for a given key if it exists in the json, or
- the old String of the field if the key doesn't exist in the json
*/
private static func getLatestFieldString(originalValue: String, json: [String: Any], fieldName: String) throws -> String {
if json.keys.contains(fieldName) {
if let newValue = json[fieldName] as? String {
return newValue
} else {
throw EventParsingError.invalidUpdateValue
}
} else {
return originalValue
}
}

private static func getLatestFieldStringOpt(originalValue: String?, json: [String: Any], fieldName: String) throws -> String? {
if json.keys.contains(fieldName) {
if let newValue = json[fieldName] as? String {
return newValue
} else {
throw EventParsingError.invalidUpdateValue
}
} else {
return originalValue
}
}

/*
Scan through a json and return either
- the Date for a given key if it exists in the json, or
- the old Date of the field if the key doesn't exist in the json
*/
private static func getLatestFieldDate(originalValue: Date, json: [String: Any], fieldName: String) -> Date {
if json.keys.contains(fieldName) {
if let newValue = Date.from(string: json[fieldName] as? String) {
return newValue
}
}
return originalValue
}
}

enum EventParsingError: Swift.Error {
case missingParameters
case invalidHypePeriod
case noSuchEvent
case invalidUpdateValue
}
Loading