├── .gitignore ├── Package.swift ├── README.md ├── Sources ├── Database.swift ├── Handlers.swift ├── URL.swift └── main.swift └── webroot ├── index.mustache ├── perfect-2.0.png └── style.mustache /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata 19 | 20 | ## Other 21 | *.xccheckout 22 | *.moved-aside 23 | *.xcuserstate 24 | *.xcscmblueprint 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | *.ipa 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | 40 | # CocoaPods 41 | # 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 45 | # 46 | # Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | # Carthage/Checkouts 52 | 53 | Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/screenshots 64 | 65 | Packages/ 66 | *.xcodeproj 67 | url-database -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Package.swift 3 | // Perfect Cookie Demo 4 | // 5 | // Created by Jonathan Guthrie on 2016-09-28. 6 | // Copyright (C) 2015 PerfectlySoft, Inc. 7 | // 8 | //===----------------------------------------------------------------------===// 9 | // 10 | // This source file is part of the Perfect.org open source project 11 | // 12 | // Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors 13 | // Licensed under Apache License v2.0 14 | // 15 | // See http://perfect.org/licensing.html for license information 16 | // 17 | //===----------------------------------------------------------------------===// 18 | // 19 | 20 | import PackageDescription 21 | 22 | // Note that the following Swift Package Manager dependancy inclusion will also import other required modules. 23 | let package = Package( 24 | name: "PerfectURLShortener", 25 | targets: [], 26 | dependencies: [ 27 | .Package(url: "https://github.com/PerfectlySoft/Perfect-SQLite.git", majorVersion: 2, minor: 0), 28 | .Package(url: "https://github.com/PerfectlySoft/Perfect-HTTPServer.git", majorVersion: 2, minor: 0), 29 | .Package(url: "https://github.com/PerfectlySoft/Perfect-Mustache.git", majorVersion: 2, minor: 0), 30 | .Package(url: "https://github.com/iamjono/SwiftString.git",majorVersion: 1, minor: 0), 31 | .Package(url: "https://github.com/iamjono/SwiftRandom.git",majorVersion: 0, minor: 2) 32 | ] 33 | ) 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Perfect URL Shortener Example 2 | 3 |

4 | 5 | Get Involed with Perfect! 6 | 7 |

8 | 9 |

10 | 11 | Star Perfect On Github 12 | 13 | 14 | Stack Overflow 15 | 16 | 17 | Follow Perfect on Twitter 18 | 19 | 20 | Join the Perfect Slack 21 | 22 |

23 | 24 |

25 | 26 | Swift 3.0 27 | 28 | 29 | Platforms OS X | Linux 30 | 31 | 32 | License Apache 33 | 34 | 35 | PerfectlySoft Twitter 36 | 37 | 38 | Slack Status 39 | 40 |

41 | 42 | An Example URL Shortener System for Perfect 43 | 44 | This project demonstrates the following: 45 | 46 | * Mustache templating 47 | * URL routing and handlers 48 | * URL Variables 49 | * Static file serving 50 | * SQLite3 database setup, and search. 51 | 52 | This package builds with Swift Package Manager and is part of the [Perfect](https://github.com/PerfectlySoft/Perfect) project. 53 | 54 | Ensure you have installed Xcode 8.0 or later. 55 | 56 | ## Setup - Xcode 8 57 | 58 | 59 | * Check out or download the project; 60 | * In terminal, navigate to the directory and execute 61 | 62 | ``` 63 | swift package generate-xcodeproj 64 | ``` 65 | 66 | * Open `PerfectURLShortener.xcodeproj` 67 | 68 | Due to the complexity of running static file serving from Xcode, we suggest running this project from Terminal. 69 | 70 | Optionally, to run from within Xcode, edit the Scheme, Under "Options" for "run", check "Use custom working directory" and choose the project's working directory. After doing this, the project can be run from within Xcode. 71 | 72 | NOTE: Due to a late-breaking bug in Xcode 8, if you wish to run directly within Xcode, we recommend [installing swiftenv](https://swiftenv.fuller.li/en/latest/) and installing the Swift 3.0.1 preview toolchain. 73 | 74 | ``` 75 | # after installing swiftenv from https://swiftenv.fuller.li/en/latest/ 76 | swiftenv install https://swift.org/builds/swift-3.0.1-preview-1/xcode/swift-3.0.1-PREVIEW-1/swift-3.0.1-PREVIEW-1-osx.pkg 77 | ``` 78 | 79 | 80 | ## Setup - Terminal 81 | 82 | * Check out or download the project; 83 | * In terminal, navigate to the directory 84 | * Execute `swift build` 85 | * Once the project has compiled, execute `./.build/debug/PerfectURLShortener` 86 | 87 | ``` 88 | [INFO] Starting HTTP server on 0.0.0.0:8181 with document root ./webroot 89 | ``` 90 | 91 | ## Routes 92 | 93 | * [http://localhost:8181](http://localhost:8181) - Form input and display of existing entries. 94 | * http://localhost:8181/to/{shortcut} - Will redirect to the allocated URL. 95 | 96 | ## Issues 97 | 98 | We are transitioning to using JIRA for all bugs and support related issues, therefore the GitHub issues has been disabled. 99 | 100 | If you find a mistake, bug, or any other helpful suggestion you'd like to make on the docs please head over to [http://jira.perfect.org:8080/servicedesk/customer/portal/1](http://jira.perfect.org:8080/servicedesk/customer/portal/1) and raise it. 101 | 102 | A comprehensive list of open issues can be found at [http://jira.perfect.org:8080/projects/ISS/issues](http://jira.perfect.org:8080/projects/ISS/issues) 103 | 104 | 105 | ## Further Information 106 | For more information on the Perfect project, please visit [perfect.org](http://perfect.org). 107 | -------------------------------------------------------------------------------- /Sources/Database.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Database.swift 3 | // Perfect-URL-Shortener 4 | // 5 | // Created by Jonathan Guthrie on 2016-09-28. 6 | // 7 | // 8 | 9 | import PerfectLib 10 | import SQLite 11 | import PerfectHTTP 12 | import PerfectMustache 13 | import SwiftString 14 | 15 | // bandles database interactions 16 | class DB { 17 | // The path to the SQLite database file 18 | let dbPath = "./url-database" 19 | 20 | // Create the database, and table. 21 | func create() { 22 | do { 23 | let sqlite = try SQLite(dbPath) 24 | defer { 25 | sqlite.close() // This makes sure we close our connection. 26 | } 27 | 28 | try sqlite.execute(statement: "CREATE TABLE IF NOT EXISTS urls (id TEXT PRIMARY KEY NOT NULL, url TEXT NOT NULL, sanity TEXT NOT NULL)") 29 | 30 | } catch { 31 | //Handle Errors 32 | print(error) 33 | } 34 | } 35 | 36 | 37 | 38 | // The function that retrieves the list of blog posts 39 | func getList() -> [URLify] { 40 | var data = [URLify]() 41 | 42 | do { 43 | let sqlite = try SQLite(dbPath) 44 | defer { 45 | sqlite.close() // This makes sure we close our connection. 46 | } 47 | 48 | let demoStatement = "SELECT id,url,sanity FROM urls" 49 | 50 | try sqlite.forEachRow(statement: demoStatement, handleRow: {(statement: SQLiteStmt, i:Int) -> () in 51 | var this = URLify() 52 | this.id = String(statement.columnText(position: 0)) 53 | this.url = String(statement.columnText(position: 1)) 54 | this.sanity = String(statement.columnText(position: 2)) 55 | data.append(this) 56 | }) 57 | 58 | } catch { 59 | //Handle Errors 60 | print(error) 61 | } 62 | return data 63 | 64 | } 65 | 66 | // Getting the url 67 | func getURL(_ opt: String) -> String { 68 | var url = "" 69 | 70 | do { 71 | let sqlite = try SQLite(dbPath) 72 | defer { 73 | sqlite.close() // This makes sure we close our connection. 74 | } 75 | 76 | try sqlite.forEachRow(statement: "SELECT url FROM urls WHERE sanity = ? LIMIT 1", doBindings: { 77 | 78 | (statement: SQLiteStmt) -> () in 79 | try statement.bind(position: 1, opt) 80 | 81 | }, handleRow: {(statement: SQLiteStmt, i:Int) -> () in 82 | url = String(statement.columnText(position: 0)) 83 | }) 84 | 85 | } catch { 86 | //Handle Errors 87 | print(error) 88 | } 89 | return url 90 | } 91 | 92 | 93 | // Saving the url 94 | func saveURL(_ this: URLify) -> URLify { 95 | var data = URLify() 96 | do { 97 | let sqlite = try SQLite(dbPath) 98 | defer { 99 | sqlite.close() // This makes sure we close our connection. 100 | } 101 | 102 | let demoStatement = "INSERT INTO urls (id,url,sanity) VALUES(:1,:2,:3)" 103 | 104 | try sqlite.forEachRow(statement: demoStatement, doBindings: { 105 | 106 | (statement: SQLiteStmt) -> () in 107 | 108 | try statement.bind(position: 1, this.id) 109 | try statement.bind(position: 2, this.url) 110 | try statement.bind(position: 3, this.sanity) 111 | 112 | }, handleRow: {(statement: SQLiteStmt, i:Int) -> () in 113 | data.id = String(statement.columnText(position: 0)) 114 | data.url = String(statement.columnText(position: 1)) 115 | data.sanity = String(statement.columnText(position: 2)) 116 | }) 117 | 118 | } catch { 119 | //Handle Errors 120 | print(error) 121 | } 122 | return data 123 | } 124 | } 125 | 126 | -------------------------------------------------------------------------------- /Sources/Handlers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Handlers.swift 3 | // Perfect-URL-Shortener 4 | // 5 | // Created by Jonathan Guthrie on 2016-09-29. 6 | // 7 | // 8 | 9 | import PerfectLib 10 | import SQLite 11 | import PerfectHTTP 12 | import PerfectMustache 13 | import SwiftString 14 | 15 | /* 16 | These are the main Mustache handlers. 17 | They are called as the handlers from the routes in main.swift 18 | */ 19 | 20 | 21 | struct IndexHandler: MustachePageHandler { // all template handlers must inherit from PageHandler 22 | // This is the function which all handlers must impliment. 23 | // It is called by the system to allow the handler to return the set of values which will be used when populating the template. 24 | // - parameter context: The MustacheWebEvaluationContext which provides access to the HTTPRequest containing all the information pertaining to the request 25 | // - parameter collector: The MustacheEvaluationOutputCollector which can be used to adjust the template output. For example a `defaultEncodingFunc` could be installed to change how outgoing values are encoded. 26 | 27 | func extendValuesForResponse(context contxt: MustacheWebEvaluationContext, collector: MustacheEvaluationOutputCollector) { 28 | 29 | var values = MustacheEvaluationContext.MapType() 30 | let dbHandler = DB() 31 | let data = dbHandler.getList() 32 | var ary = [Any]() 33 | 34 | for i in 0.. 0 && urlinput != "http://" { 72 | let newURL = URLify(urlinput) 73 | print("New URL: \(newURL.id), \(newURL.url), \(newURL.sanity)") 74 | let _ = dbHandler.saveURL(newURL) 75 | } 76 | 77 | let data = dbHandler.getList() 78 | var ary = [Any]() 79 | 80 | for i in 0.. String { 27 | let letters : String = "abcdefghijklmnopqrstuvwxyzZ0123456789" 28 | var randomString = "" 29 | for _ in 0.. 2 | 3 | 4 | 5 | Perfect URL Shortener Demo 6 | 7 | {{> style}} 8 | 9 | 10 | 11 | 17 |
18 |
19 | 20 | 21 |
22 |
23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | {{#urls}} 31 | 32 | 33 | 34 | 35 | 36 | {{/urls}} 37 | 38 |
IDURLNew Link
{{id}}{{url}}{{sanity}}
39 |
40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /webroot/perfect-2.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PerfectExamples/Perfect-URL-Shortener/96e7896a33217655a17eeb33c16b487d44e4888e/webroot/perfect-2.0.png -------------------------------------------------------------------------------- /webroot/style.mustache: -------------------------------------------------------------------------------- 1 | 24 | --------------------------------------------------------------------------------