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

Solution in Swift 4, by Rocky Wei #171

Open
wants to merge 1 commit into
base: master
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
59 changes: 59 additions & 0 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,49 @@ There are many ways that this application could be built; we ask that you build
Please modify `README.md` to add:

1. Instructions on how to build/run your application

Before building/running this Swift, please read [Perfect: A Server Side Swift](perfect.org) for installation guide.

For Xcode 9.0+ (macOS), or Swift 4.0+ (Ubuntu 16.04)

```
$ cd csvsql
$ swift run
```

The server will run at [http://localhost:8181](http://localhost:8181).

If success, you can upload the sample CSV and try "summary" on the web page.
To stop the demo server, simply just click [Stop the demo server](http://localhost:8181/halt), as on the same html page.

⚠️**Alternatively**⚠️, the easiest way to build & run this demo is [docker](docker.com)

```
$ docker run -it -v $PWD/csvsql:/home -w /home -p 8181:8181 rockywei/swift:4.0 /bin/bash -c "swift run"
```

1. A paragraph or two about what you are particularly proud of in your implementation, and why.

Nothing specially, it is a very common demo for a typical Perfect backend, which doesn't include other powerful Perfect features such as AI / Machine Learning / Live Messaging / Big Data Mining:

``` swift
/// a Perfect Web Server prefers a pure json style scheme.
/// By such a design, the web server can apply such an architecture:
/// - model.swift, a pure data model file to serve data model
/// - main.swift, a http server route controller
/// - index.html, a static html page to view the data
```

The solution took about 3 hours:

- Document reading.
- Prototyping.
- Final implementation.
- Testing.
- API documentation, which was taken over 90 minutes.



## Submission Instructions

1. Fork this project on github. You will need to create an account if you don't already have one.
Expand All @@ -52,8 +93,26 @@ Please modify `README.md` to add:
Evaluation of your submission will be based on the following criteria.

1. Did you follow the instructions for submission?

- Yes


1. Did you document your build/deploy instructions and your explanation of what you did well?

- Yes.

1. Were models/entities and other components easily identifiable to the reviewer?

- Yes. All codes have been well documented in the source.

1. What design decisions did you make when designing your models/entities? Why (i.e. were they explained?)

- Yes, Perfect prefers a pure json backend style.

1. Did you separate any concerns in your application? Why or why not?

It is too small a demo so couldn't including other important/scaling features in hours, typically, such as file size control, malicious detection, ORM, google-protocol buffer for huge data trunk traffic, OAuth and JWT token control, legacy single sign-on such as SPNEGO, or distribution storage / indexing, full text searching, etc.

1. Does your solution use appropriate datatypes for the problem as described?

- Yes, however, SQLite is a simplified implementation of ANSI SQL, so it should be a bit complicated if production.
7 changes: 7 additions & 0 deletions csvsql/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
*.resolved
*.pins

22 changes: 22 additions & 0 deletions csvsql/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// swift-tools-version:4.0
/// a Perfect Web Server prefers a pure json style scheme.
/// By such a design, the web server can apply such an architecture:
/// - model.swift, a pure data model file to serve data model
/// - main.swift, a http server route controller
/// - index.html, a static html page to view the data

import PackageDescription

let package = Package(
name: "csvsql",
dependencies: [
.package(url: "https://github.com/yaslab/CSV.swift.git", from: "2.1.0"),
.package(url: "https://github.com/PerfectlySoft/Perfect-HTTPServer.git", from: "3.0.0"),
.package(url: "https://github.com/PerfectlySoft/Perfect-SQLite.git", from: "3.0.0")
],
targets: [
.target(
name: "csvsql",
dependencies: ["CSV", "PerfectHTTPServer", "PerfectSQLite"]),
]
)
135 changes: 135 additions & 0 deletions csvsql/Sources/csvsql/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/// main.swift
/// se-challenge-expenses solution in Swift
/// by Xiaoquan (Rockford) Wei, Feb 26, 2018

/// a Perfect Web Server prefers a pure json style scheme.
/// By such a design, the web server can apply such an architecture:
/// - model.swift, a pure data model file to serve data model
/// - main.swift, a http server route controller
/// - index.html, a static html page to view the data


/// Perfect is Server Side Swift
import PerfectLib

/// importing essential components from Perfect Web Server
import PerfectHTTP
import PerfectHTTPServer

/// other essential libraries from Apple Inc., such as json
import Foundation

/* This demo setup 5 different routes to handle the HTTP requests
- /upload: allow user to upload a CSV file
- /record: return database records in json format
- /summary: return an expense summary in months
- /halt: **only for demo purpose**, to stop the server after demo
- others: for static files
*/

/// uploader handler
/// - parameter request: upload request in method POST, **MUST BE** a CSV file
/// - parameter response: upload response, return a json with error message, if failed
func handlerUpload(request: HTTPRequest, response: HTTPResponse) {
let err: String

// look for uploaded files
if let uploads = request.postFileUploads,
let source = uploads.first {
do {

// this is only for a demo, clean the database to avoid duplicated record with the same testing data
let _ = unlink(ExpenseModel.databasePath)

// convert the uploaded file into database backbone
let _ = try ExpenseModel(csvSourcePath: source.tmpFileName, sqlitePath: ExpenseModel.databasePath)
err = ""
} catch {
err = "\(error)"
}
} else {
debugPrint(request.postBodyString ?? "")
err = "no uploads"
}
response.setHeader(.contentType, value: "text/json")
.setBody(string: "{\"error\": \"\(err)\"}\n")
.completed()
}

/// record handler
/// - parameter request: upload request, with no parameters required.
/// - parameter response: upload response in json data
func handlerRecord(request: HTTPRequest, response: HTTPResponse) {
var body = ""
do {
// load the imported data by some default settings.
let e = try ExpenseModel(sqlitePath: ExpenseModel.databasePath)
let rec = try e.fetch(limit: 100)

// turn the result into json
let json = JSONEncoder()
let data = try json.encode(rec)
body = String(bytes: data, encoding: .utf8) ?? "{\"error\": \"json failure\"}\n"
} catch {
body = "{\"error\": \"\(error)\"}\n"
}
response.setHeader(.contentType, value: "text/json")
.setBody(string: body)
.completed()
}

/// report summary handler
/// - parameter request: upload request, with no parameters required.
/// - parameter response: upload response in json data
func handlerSummary(request: HTTPRequest, response: HTTPResponse) {
var body = ""
do {

// load a summary report
let e = try ExpenseModel(sqlitePath: ExpenseModel.databasePath)
let report = try e.summary()

// turn the result into json
let json = JSONEncoder()
let data = try json.encode(report)
body = String(bytes: data, encoding: .utf8) ?? "{\"error\": \"json failure\"}\n"
} catch {
body = "{\"error\": \"\(error)\"}\n"
}
response.setHeader(.contentType, value: "text/json")
.setBody(string: body)
.completed()
}

/// easy route to stop the web server
/// - parameter request: upload request, with no parameters required.
func handlerHalt(request: HTTPRequest, response: HTTPResponse) {
exit(0)
}


/// configure the above routes to the server.
let confData = [
"servers": [
[
"name":"localhost",
"port":8181,
"routes":[
["method":"post", "uri":"/upload", "handler":handlerUpload],
["method":"get", "uri":"/record", "handler":handlerRecord],
["method":"get", "uri":"/summary", "handler":handlerSummary],
["method":"get", "uri":"/halt", "handler": handlerHalt],
["method":"get", "uri":"/**",
"handler": PerfectHTTPServer.HTTPHandler.staticFiles,
"documentRoot":"./webroot"],
]
]
]
]

/// start the web server.
do {
try HTTPServer.launch(configurationData: confData)
} catch {
fatalError("\(error)")
}
Loading