├── .gitignore ├── .sourcery.yml ├── .swiftlint.yml ├── .travis.yml ├── Config ├── app.json ├── crypto.json ├── development │ └── app.json ├── droplet.json ├── facebook.json ├── fluent.json ├── github.json ├── mysql.json ├── server.json ├── test │ ├── app.json │ ├── facebook.json │ ├── github.json │ ├── mysql.json │ └── vkontakte.json └── vkontakte.json ├── Devops ├── Jenkinsfile ├── deploy-build.sh └── rollback.sh ├── Package.resolved ├── Package.swift ├── Package.swift.orig ├── Public └── .gitkeep ├── README.md ├── Scripts └── SourceryGenerator.command ├── Sources ├── App │ ├── Common │ │ ├── Extensions │ │ │ ├── Crypto │ │ │ │ └── CryptoHasher+String.swift │ │ │ ├── Foundation │ │ │ │ ├── Array │ │ │ │ │ ├── Array+RandomValue.swift │ │ │ │ │ └── Array+Shuffled.swift │ │ │ │ ├── Bool │ │ │ │ │ └── Bool+RandomValue.swift │ │ │ │ ├── Date │ │ │ │ │ ├── Date+Format.swift │ │ │ │ │ └── Date+Random.swift │ │ │ │ ├── Double │ │ │ │ │ └── Double+RandomValue.swift │ │ │ │ ├── FileManager │ │ │ │ │ └── FileManger+RemoveFiles.swift │ │ │ │ ├── Int │ │ │ │ │ └── Int+RandomValue.swift │ │ │ │ ├── Optional │ │ │ │ │ └── Optional+Empty.swift │ │ │ │ └── String │ │ │ │ │ ├── String+RandomValue.swift │ │ │ │ │ └── String+isNotEmpty.swift │ │ │ ├── HTTP │ │ │ │ ├── Headers+CustomHeaderKeys.swift │ │ │ │ ├── Request+UserAuthenticated.swift │ │ │ │ └── Response+Message.swift │ │ │ └── Node │ │ │ │ └── StructuredDataWrapper+stringValue.swift │ │ └── Helpers │ │ │ ├── Constants │ │ │ └── Constants.swift │ │ │ └── Photo │ │ │ └── PhotoController.swift │ ├── Controllers │ │ ├── Event │ │ │ ├── EventController.swift │ │ │ ├── EventSpeechController.swift │ │ │ └── Registration │ │ │ │ ├── AutoapproveController.swift │ │ │ │ ├── RegistrationController.swift │ │ │ │ ├── RegistrationControllerHelper.swift │ │ │ │ └── RegistrationFormController.swift │ │ ├── Heartbeat │ │ │ └── HeartbeatController.swift │ │ ├── Stage │ │ │ ├── Samples │ │ │ │ ├── ClearDatabase.swift │ │ │ │ ├── CreatorSample.swift │ │ │ │ ├── EventSample.swift │ │ │ │ ├── RegFormSample.swift │ │ │ │ ├── Samples.swift │ │ │ │ ├── SpeakerSample.swift │ │ │ │ └── UserSample.swift │ │ │ └── StageController.swift │ │ └── User │ │ │ ├── Auth │ │ │ ├── FacebookController.swift │ │ │ ├── GithubController.swift │ │ │ ├── SocialHelper.swift │ │ │ └── VkontakteController.swift │ │ │ ├── CreatorsController.swift │ │ │ ├── GiveSpeechController.swift │ │ │ ├── PushNotificationController.swift │ │ │ ├── UserAuthorizationController.swift │ │ │ └── UserController.swift │ ├── Middleware │ │ ├── ClientMiddleware.swift │ │ ├── MiddlewareError.swift │ │ └── PhotoURLMiddleware.swift │ ├── Models │ │ ├── Approval │ │ │ ├── Approval.generated.swift │ │ │ └── Approval.swift │ │ ├── City │ │ │ ├── City.generated.swift │ │ │ └── City.swift │ │ ├── Client │ │ │ ├── Client.generated.swift │ │ │ └── Client.swift │ │ ├── Content │ │ │ ├── Content.generated.swift │ │ │ └── Content.swift │ │ ├── Creator │ │ │ ├── Creator.generated.swift │ │ │ └── Creator.swift │ │ ├── Event │ │ │ ├── Event.generated.swift │ │ │ └── Event.swift │ │ ├── EventReg │ │ │ ├── EventReg.generated.swift │ │ │ └── EventReg.swift │ │ ├── EventRegAnswer │ │ │ ├── EventRegAnswer.generated.swift │ │ │ └── EventRegAnswer.swift │ │ ├── GiveSpeech │ │ │ ├── GiveSpeech.generated.swift │ │ │ └── GiveSpeech.swift │ │ ├── Heartbeat │ │ │ ├── Heartbeat.generated.swift │ │ │ └── Heartbeat.swift │ │ ├── Place │ │ │ ├── Place.generated.swift │ │ │ └── Place.swift │ │ ├── RegField │ │ │ ├── RegField.generated.swift │ │ │ └── RegField.swift │ │ ├── RegFieldAnswer │ │ │ ├── RegFieldAnswer.generated.swift │ │ │ └── RegFieldAnswer.swift │ │ ├── RegForm │ │ │ ├── RegForm.generated.swift │ │ │ └── RegForm.swift │ │ ├── Rule │ │ │ ├── Rule.generated.swift │ │ │ └── Rule.swift │ │ ├── Session │ │ │ ├── Session.generated.swift │ │ │ └── Session.swift │ │ ├── Social │ │ │ ├── Config │ │ │ │ ├── FacebookConfig.swift │ │ │ │ ├── GithubConfig.swift │ │ │ │ └── VKontakteConfig.swift │ │ │ ├── Social+Net.swift │ │ │ ├── Social.generated.swift │ │ │ └── Social.swift │ │ ├── SocialAccount │ │ │ ├── SocialAccount.generated.swift │ │ │ └── SocialAccount.swift │ │ ├── Speaker │ │ │ ├── Speaker.generated.swift │ │ │ └── Speaker.swift │ │ ├── Speech │ │ │ ├── Speech.generated.swift │ │ │ └── Speech.swift │ │ └── User │ │ │ ├── User.generated.swift │ │ │ └── User.swift │ ├── Routes │ │ ├── FrontendAPICollection.swift │ │ ├── Routes.swift │ │ └── TestAPICollection.swift │ └── Setup │ │ ├── Config+Setup.swift │ │ ├── Droplet+Setup.swift │ │ └── Social+Data.swift └── Run │ └── main.swift ├── Templates ├── AutoModelGeneratable.stencil └── LinuxMainAllTestsGenerate.stencil ├── Tests ├── AppTests │ ├── Common │ │ ├── Extensions │ │ │ ├── Crypto │ │ │ │ └── CryptoHasher+Compare.swift │ │ │ ├── Foundation │ │ │ │ └── String │ │ │ │ │ └── String+RandomToken.swift │ │ │ ├── HTTP │ │ │ │ └── Responder │ │ │ │ │ └── Responder+Authorization.swift │ │ │ └── Models │ │ │ │ ├── City+Random.swift │ │ │ │ ├── Content+Random.swift │ │ │ │ ├── Creators+Random.swift │ │ │ │ ├── Event+Random.swift │ │ │ │ ├── Place+Random.swift │ │ │ │ ├── RegField+Random.swift │ │ │ │ ├── RegForm+Random.swift │ │ │ │ ├── Rule+Initialization.swift │ │ │ │ ├── Session+Random.swift │ │ │ │ ├── Speech+Random.swift │ │ │ │ └── User+Random.swift │ │ └── Helpers │ │ │ ├── Constants │ │ │ └── Constants.swift │ │ │ └── Controllers │ │ │ ├── Auth │ │ │ ├── FacebookAuthControllerTestHelper.swift │ │ │ ├── GithubAuthControllerTestHelper.swift │ │ │ ├── SocialAuthControllerTestHelper.swift │ │ │ └── VkontakteAuthControllerTestHelper.swift │ │ │ ├── Event │ │ │ ├── EventHelper.swift │ │ │ └── EventSpeechHelper.swift │ │ │ └── Registration │ │ │ ├── ApproveHelper.swift │ │ │ ├── EventRegAnswerHelper.swift │ │ │ ├── EventRegHelper.swift │ │ │ ├── RegFieldAnswerHelper.swift │ │ │ ├── RegFieldHelper.swift │ │ │ ├── RegFieldRuleHelper.swift │ │ │ ├── RegFormHelper.swift │ │ │ └── UserSessionHelper.swift │ ├── Controllers │ │ ├── Event │ │ │ ├── EventControllerTests.swift │ │ │ ├── EventSpeechControllerTests.swift │ │ │ └── Registration │ │ │ │ ├── AutoapproveTest.swift │ │ │ │ ├── CancelRegistrationTest.swift │ │ │ │ ├── GetRegFormTest.swift │ │ │ │ └── PostUserAnswersTests.swift │ │ ├── Heartbeat │ │ │ └── HeartbeatControllerTests.swift │ │ └── User │ │ │ ├── Auth │ │ │ ├── FacebookAuthControllerTest.swift │ │ │ ├── GithubAuthControllerTest.swift │ │ │ ├── UserAuthByTokenTest.swift │ │ │ └── VkontakteAuthControllerTest.swift │ │ │ ├── CreatorsControllerTest.swift │ │ │ ├── GiveSpeechControllerTest.swift │ │ │ ├── PushNotificationControllerTest.swift │ │ │ ├── UserControllerTests.swift │ │ │ └── UserPhotoTest.swift │ ├── Middleware │ │ └── ClientMiddlewareTests.swift │ ├── RouteTests.swift │ ├── Stubs │ │ └── ResponderStub.swift │ └── Utilities.swift ├── LinuxMain.swift └── Resources │ ├── 1716396_original2.jpg │ ├── 2017-11-27 11.39.37.jpg │ ├── Image 2017-10-25 00-02-39.png │ ├── VKcocoaheadsdev.jpg │ ├── github.png │ ├── robot-1469114466kSY.jpg │ └── robot.jpg ├── circle.yml ├── cloud.yml └── license /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/vapor 3 | 4 | ### Vapor ### 5 | Config/secrets 6 | 7 | ### Vapor Patch ### 8 | Packages 9 | .build 10 | xcuserdata 11 | *.xcodeproj 12 | DerivedData/ 13 | .DS_Store 14 | 15 | # End of https://www.gitignore.io/api/vapor 16 | 17 | # Production files 18 | Public/user_photos/ 19 | Public/event_photos/ 20 | 21 | -------------------------------------------------------------------------------- /.sourcery.yml: -------------------------------------------------------------------------------- 1 | sources: 2 | - Sources/App/Models 3 | - Tests/AppTests 4 | templates: 5 | - Templates/ 6 | output: 7 | . 8 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | excluded: # paths to ignore during linting. 2 | - Devops 3 | - Public 4 | - .build 5 | identifier_name: 6 | min_length: 2 7 | max_length: 50 8 | 9 | force_try: 10 | severity: warning 11 | 12 | line_length: 180 13 | 14 | trailing_whitespace: 15 | ignores_empty_lines: true 16 | 17 | colon: 18 | apply_to_dictionaries: false 19 | 20 | disabled_rules: 21 | - nesting 22 | - unused_optional_binding 23 | 24 | opt_in_rules: 25 | - literal_expression_end_indentation 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | - osx 4 | language: generic 5 | sudo: required 6 | dist: trusty 7 | 8 | osx_image: xcode8.3 9 | before_install: 10 | - if [ $TRAVIS_OS_NAME == "osx" ]; then 11 | brew tap vapor/tap; 12 | brew update; 13 | brew install vapor; 14 | else 15 | eval "$(curl -sL https://apt.vapor.sh)"; 16 | sudo apt-get install vapor; 17 | sudo chmod -R a+rx /usr/; 18 | fi 19 | 20 | script: 21 | - swift build 22 | - swift build -c release 23 | - swift test 24 | 25 | after_success: 26 | - eval "$(curl -sL https://swift.vapor.sh/codecov)" 27 | -------------------------------------------------------------------------------- /Config/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "$CH_APP_DOMAIN", 3 | "client-token": "$CH_APP_CLIENT_TOKEN" 4 | } 5 | -------------------------------------------------------------------------------- /Config/crypto.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash": { 3 | "method": "sha256", 4 | "encoding": "hex", 5 | "key": "5e5c7f415d05beb07f71de03c35f63b0" 6 | }, 7 | 8 | "cipher": { 9 | "method": "aes256", 10 | "encoding": "base64", 11 | "key": "fXlmT5Y6irfYqsmFO1/H6JgNi4YcvXX52FUt3b84sWc=" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Config/development/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "http://localhost:8080", 3 | "client-token": "test" 4 | } 5 | -------------------------------------------------------------------------------- /Config/droplet.json: -------------------------------------------------------------------------------- 1 | { 2 | "//": "The type of server to use for handling requests.", 3 | "//": "engine: Vapor's blazing fast Engine HTTP server.", 4 | "server": "engine", 5 | 6 | "//": "The type of client to use for requesting data from other services.", 7 | "//": "engine: Vapor's blazing fast Engine HTTP client.", 8 | "//": "foundation: A wrapper around Foundation's URLSession.", 9 | "client": "engine", 10 | 11 | "//": "The type of console to use for displaying information and prompting input.", 12 | "//": "terminal: Vapor's default terminal console.", 13 | "console": "terminal", 14 | 15 | "//": "The type of logger to use for recording logs, warnings, errors, etc.", 16 | "//": "console: Vapor's default logger sends logs directly to the chosen console.", 17 | "log": "console", 18 | 19 | "//": "The type of hasher to use for hashing messages.", 20 | "//": "crypto: Vapor's default hasher powered by OpenSSL (configure in crypto.json)", 21 | "//": "bcrypt: Performant BCrypt hashing implementation (configure in bcrypt.json)", 22 | "hash": "crypto", 23 | 24 | "//": "The type of cipher to use for encrypting and decrypting messages.", 25 | "//": "crypto: Vapor's default cipher powered by OpenSSL (configure in crypto.json)", 26 | "cipher": "crypto", 27 | 28 | "//": "Choose which middleware are enabled (and in which order).", 29 | "//": "error: Catches errors thrown in your application and returns a nice response.", 30 | "//": "date: Adds the 'Date' header to HTTP requests.", 31 | "//": "file: Catches 404 errors and checks for files in the Public/ folder", 32 | "middleware": [ 33 | "error", 34 | "date", 35 | "file" 36 | ], 37 | 38 | "//": "Choose which commands this application can run", 39 | "//": "prepare: Supplied by the Fluent provider. Prepares the database (configure in fluent.json)", 40 | "commands": [ 41 | "prepare" 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /Config/facebook.json: -------------------------------------------------------------------------------- 1 | { 2 | "//": "User auth", 3 | "token_request_url": "https://graph.facebook.com/v2.12/oauth/access_token", 4 | "client_id": "$CH_FB_CLIENT_ID", 5 | "redirect_uri": "$CH_FB_REDIRECT", 6 | "scope": "email", 7 | "client_secret": "$CH_FB_CLIENT_SECRET", 8 | "user_info_url": "https://graph.facebook.com/me", 9 | "fields": "id,email,first_name,last_name,picture.height(961)" 10 | } 11 | 12 | -------------------------------------------------------------------------------- /Config/fluent.json: -------------------------------------------------------------------------------- 1 | { 2 | "//": "The underlying database technology to use.", 3 | "//": "memory: SQLite in-memory DB.", 4 | "//": "sqlite: Persisted SQLite DB (configure with sqlite.json)", 5 | "//": "Other drivers are available through Vapor providers", 6 | "//": "https://github.com/search?q=topic:vapor-provider+topic:database", 7 | "driver": "mysql", 8 | 9 | "//": "Naming convention to use for creating foreign id keys,", 10 | "//": "e.g., `user_id`", 11 | "//": "`camelCase` option is also available.", 12 | "keyNamingConvention": "snake_case", 13 | 14 | "//": "Name of the table Fluent uses to track migrations.", 15 | "//": "If null, preparations will not be run.", 16 | "migrationEntityName": "fluent", 17 | 18 | "//": "Character used to join pivot tables, e.g., `user_pet`", 19 | "pivotNameConnector": "_", 20 | 21 | "//": "If true, foreign keys will automatically be added", 22 | "//": "to any `builder.foreignId(...)` calls.", 23 | "autoForeignKeys": true, 24 | 25 | "//": "Key to specify page number for paginated responses", 26 | "//": "e.g., `?page=2` ", 27 | "defaultPageKey": "page", 28 | 29 | "//": "Default page size if not otherwise specified on models", 30 | "defaultPageSize": 10, 31 | 32 | "//": "If true, all queries made to the database will be logged.", 33 | "log": false 34 | } 35 | -------------------------------------------------------------------------------- /Config/github.json: -------------------------------------------------------------------------------- 1 | { 2 | "token_request_url": "https://github.com/login/oauth/access_token", 3 | "client_id": "$CH_GIT_CLIENT_ID", 4 | "client_secret": "$CH_GIT_CLIENT_SECRET", 5 | "user_info_url": "https://api.github.com/user" 6 | } 7 | 8 | -------------------------------------------------------------------------------- /Config/mysql.json: -------------------------------------------------------------------------------- 1 | { 2 | "hostname": "$CH_DB_HOST:localhost", 3 | "user": "$CH_DB_USER:root", 4 | "password": "$CH_DB_PASSWORD:", 5 | "database": "$CH_DB_DATABASE:cocoaheads" 6 | } 7 | -------------------------------------------------------------------------------- /Config/server.json: -------------------------------------------------------------------------------- 1 | { 2 | "//": "The $PORT:8080 call tells the json file to see if there", 3 | "//": "is any value at the 'PORT' environment variable.", 4 | "//": "If there is no value there, it will fallback to '8080'", 5 | "port": "$PORT:8080", 6 | 7 | "host": "0.0.0.0", 8 | 9 | "//": "It's very rare that a server manages its own TLS.", 10 | "//": "More commonly, vapor is served behind a proxy like nginx.", 11 | "securityLayer": "none", 12 | } 13 | -------------------------------------------------------------------------------- /Config/test/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "http://localhost:8080", 3 | "client-token": "test" 4 | } 5 | -------------------------------------------------------------------------------- /Config/test/facebook.json: -------------------------------------------------------------------------------- 1 | { 2 | "//": "User auth", 3 | "token_request_url": "https://graph.facebook.com/v2.12/oauth/access_token", 4 | "client_id": "412364922532866", 5 | "redirect_uri": "https://www.facebook.com/connect/login_success.html", 6 | "scope": "email", 7 | "client_secret": "c79f80d3ada9fdd483d274378eb632b6", 8 | "//": "User info", 9 | "user_info_url": "https://graph.facebook.com/me", 10 | "fields": "id,email,first_name,last_name,picture.height(961)", 11 | "//": "For tests", 12 | "//": "ATTENTION Only fresh and not used authorization code is work. You may copy new code from to go this URL: ", 13 | "//": "https://www.facebook.com/v2.8/dialog/oauth?client_id=412364922532866&redirect_uri=https://www.facebook.com/connect/login_success.html&scope=email", 14 | "//": "login: cocoaheads_gublhtf_test@tfbnw.net password: vfreyby", 15 | "code": "", 16 | "access_token": "EAAF3CzC5tAIBAJpnoj53aqMqBugvQKPY8hhzS4UzKH0p4z6isUbQY4jlv3hipICI9g1UB4wT2BUFJwNzzXcaUp5RTRwhZA3FTpQDuW1QfPbkd45DNNTmFQP0hgNoouZBs5CkUmEay0l6TIZBkL9DsTiIsR9ic0XdwlWY5sDh8ZBBuhG8wkIf" 17 | } 18 | -------------------------------------------------------------------------------- /Config/test/github.json: -------------------------------------------------------------------------------- 1 | { 2 | "token_request_url": "https://github.com/login/oauth/access_token", 3 | "client_id": "4f93f0cfa7e6bdd5e881", 4 | "client_secret": "93af327e3302d217d3efe5e0820a7ce68e047e21", 5 | "user_info_url": "https://api.github.com/user", 6 | "//": "For tests", 7 | "//": "https://github.com/login/oauth/authorize?client_id=4f93f0cfa7e6bdd5e881&scope=user:email&state=asdf34rrad451bsdfweww32dsd343", 8 | "//": "Code for one-time use!!! Use link above to get a new one!", 9 | "code": "", 10 | "state": "asdf34rrad451bsdfweww32dsd343", 11 | "test_user_info_url": "https://api.github.com/users/JenkinsBotCH", 12 | "access_token": "5d8dc9a6e1cbebd935d080c9236f77abcc19ee53" 13 | } 14 | -------------------------------------------------------------------------------- /Config/test/mysql.json: -------------------------------------------------------------------------------- 1 | { 2 | "hostname": "localhost", 3 | "user": "test", 4 | "password": "test", 5 | "database": "cocoaheads_test" 6 | } 7 | -------------------------------------------------------------------------------- /Config/test/vkontakte.json: -------------------------------------------------------------------------------- 1 | { 2 | "api_url": "https://api.vk.com/", 3 | "method": "/method/users.get", 4 | "fields": "photo_max,city,verified", 5 | "//": "For tests", 6 | "secret": "7a24164e7bdb225b5f", 7 | "access_token": "0dfb80d566999b0aa844cb15ce87909115d431a294bf5d835f2c8d78e5544e5822ed5d5359af812491355" 8 | } 9 | -------------------------------------------------------------------------------- /Config/vkontakte.json: -------------------------------------------------------------------------------- 1 | { 2 | "api_url": "https://api.vk.com/", 3 | "method": "/method/users.get", 4 | "fields": "photo_max,city,verified" 5 | } 6 | 7 | -------------------------------------------------------------------------------- /Devops/deploy-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # CocoaHeadsRu (c) 3 | # Description: this is the shell script for the cocoaheadsru server deploy and start 4 | # Author: Dmitry, ditansu@gmail.com 5 | 6 | 7 | ##SETINGS 8 | #Get from Jenkins pipeline 9 | 10 | PROJECT=$CH_BUILD 11 | WEBROOT=$CH_WEBROOT 12 | 13 | #Permisions 14 | USER="www-data" 15 | GROUP="www-data" 16 | 17 | #Commands 18 | SUPERVISOR=/usr/bin/supervisorctl 19 | SERVICE="cocoaheadsru" 20 | 21 | ##WORK 22 | echo "Stop $SERVICE..." 23 | $SUPERVISOR stop $SERVICE 24 | 25 | rm -rf $WEBROOT/server.bak 26 | mv -f $WEBROOT/server $WEBROOT/server.bak 27 | 28 | echo "Copy $PROJECT to $WEBROOT" 29 | cp -r $PROJECT $WEBROOT/server 30 | echo "Chown $USER:$GROUP for $WEBROOT" 31 | chown -fR $USER:$GROUP $WEBROOT 32 | 33 | echo "Start $SERVICE..." 34 | 35 | RESULT=$($SUPERVISOR start $SERVICE) 36 | echo "" 37 | echo "Result is $RESULT" 38 | echo "" 39 | 40 | if [[ $RESULT == *"ERROR"* ]]; then 41 | echo "Something was wrong, let try start to rollback deploy..." 42 | exit 1 43 | fi 44 | exit 0 45 | 46 | -------------------------------------------------------------------------------- /Devops/rollback.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # CocoaHeads Ru 3 | # Description: this is the shell script for the cocoaheadsru server deploy and start 4 | # Author: Dmitry, ditansu@gmail.com 5 | 6 | 7 | ##SETINGS 8 | 9 | #Paths 10 | 11 | #get from Jankins pipeline 12 | WEBROOT=$CH_WEBROOT 13 | 14 | #Commands 15 | SUPERVISOR=/usr/bin/supervisorctl 16 | SERVICE="cocoaheadsru" 17 | 18 | ##WORK 19 | 20 | if [ ! -d "$WEBROOT/server.bak" ]; then 21 | echo "$WEBROOT/server.bak is not exist!" 22 | echo "Unfortenately I can't rollback" 23 | exit 1 24 | fi 25 | 26 | echo "Recovering the backup from ${WEBROOT}/server.bak" 27 | rm -rf $WEBROOT/server.error 28 | mv -f $WEBROOT/server $WEBROOT/server.error 29 | rm -rf $WEBROOT/server 30 | mv -f $WEBROOT/server.bak $WEBROOT/server 31 | echo "Restart supervisor" 32 | 33 | echo "Try to start previos release $SERVICE..." 34 | RESULT=$($SUPERVISOR restart $SERVICE) 35 | 36 | if [[ $RESULT == *"ERROR"* ]]; then 37 | echo "Unfortenately I've can't rollback" 38 | exit 1 39 | fi 40 | 41 | echo "Fine I could rollback and start previos build" 42 | exit 0 43 | 44 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.0 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "server", 7 | products: [ 8 | .library(name: "App", targets: ["App"]), 9 | .executable(name: "Run", targets: ["Run"]) 10 | ], 11 | dependencies: [ 12 | .package(url: "https://github.com/vapor/vapor.git", .upToNextMajor(from: "2.1.0")), 13 | .package(url: "https://github.com/vapor/fluent-provider.git", .upToNextMajor(from: "1.2.0")), 14 | .package(url: "https://github.com/vapor/mysql-provider.git", .upToNextMajor(from: "2.0.0")), 15 | .package(url: "https://github.com/vapor/validation-provider.git", .upToNextMajor(from: "1.1.1")), 16 | .package(url: "https://github.com/vapor/auth-provider.git", .upToNextMajor(from: "1.2.0")) 17 | ], 18 | targets: [ 19 | .target(name: "App", dependencies: ["Vapor", "FluentProvider", "MySQLProvider", "AuthProvider"], 20 | exclude: [ 21 | "Config", 22 | "Public", 23 | "Resources" 24 | ]), 25 | .target(name: "Run", dependencies: ["App"]), 26 | .testTarget(name: "AppTests", dependencies: ["App", "Testing"]) 27 | ] 28 | ) 29 | -------------------------------------------------------------------------------- /Package.swift.orig: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.0 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "server", 7 | products: [ 8 | .library(name: "App", targets: ["App"]), 9 | .executable(name: "Run", targets: ["Run"]) 10 | ], 11 | dependencies: [ 12 | .package(url: "https://github.com/vapor/vapor.git", .upToNextMinor(from: "2.1.0")), 13 | .package(url: "https://github.com/vapor/fluent-provider.git", .upToNextMinor(from: "1.2.0")), 14 | .package(url: "https://github.com/vapor/mysql-provider.git", .upToNextMinor(from: "2.0.0")) 15 | ], 16 | targets: [ 17 | .target(name: "App", 18 | dependencies: ["Vapor", "FluentProvider", "MySQLProvider"], 19 | exclude: [ 20 | "Config", 21 | "Public", 22 | "Resources" 23 | ]), 24 | .target(name: "Run", dependencies: ["App"]), 25 | .testTarget(name: "AppTests", dependencies: ["App", "Testing"]) 26 | ] 27 | ) 28 | -------------------------------------------------------------------------------- /Public/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cocoaheadsru/server/3367dbfe3fec62056acd2a6cd604835356dc6243/Public/.gitkeep -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # CocoaHeads Server 3 | 4 | This a server side api for CocoaHeads Russia app. It was built with Swift and backed by Vapor. 5 | 6 | ## Getting Started 7 | 8 | These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. 9 | 10 | ### Prerequisites 11 | 12 | What things you need to install the software and how to install them. At first verify swift installation: 13 | 14 | ``` 15 | eval "$(curl -sL check.vapor.sh)" 16 | ``` 17 | 18 | ### Installing 19 | 20 | A step by step series of examples that tell you have to get a development env running 21 | 22 | 23 | #### Add Homebrew Tap 24 | 25 | ``` 26 | brew tap vapor/homebrew-tap 27 | brew update 28 | ``` 29 | 30 | #### Install Vapor 31 | 32 | ``` 33 | brew install vapor 34 | ``` 35 | 36 | #### Install MySQL 37 | 38 | ``` 39 | brew install mysql 40 | ``` 41 | Start mysql server 42 | 43 | ``` 44 | mysql.server start 45 | 46 | ``` 47 | Create database for development and tests 48 | 49 | ``` 50 | mysql -u root 51 | CREATE DATABASE cocoaheads; 52 | CREATE DATABASE cocoaheads_test; 53 | CREATE USER 'test'@'localhost' IDENTIFIED BY 'test'; 54 | GRANT ALL PRIVILEGES ON cocoaheads_test.* TO 'test'@'localhost' WITH GRANT OPTION; 55 | FLUSH PRIVILEGES; 56 | ``` 57 | 58 | 59 | 60 | #### Create project 61 | 62 | ``` 63 | vapor new server --template=cocoaheadsru/server 64 | ``` 65 | 66 | #### Build project 67 | 68 | Change directory 69 | 70 | ``` 71 | cd server 72 | ``` 73 | Create xcode project 74 | 75 | ``` 76 | vapor xcode 77 | ``` 78 | -------------------------------------------------------------------------------- /Scripts/SourceryGenerator.command: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ### Make sure Sourcery is installed, if not ‘brew install sourcery’ ### 4 | 5 | cd "$(dirname "$0")" 6 | cd .. 7 | sourcery --config .sourcery.yml -------------------------------------------------------------------------------- /Sources/App/Common/Extensions/Crypto/CryptoHasher+String.swift: -------------------------------------------------------------------------------- 1 | import Crypto 2 | 3 | extension CryptoHasher { 4 | static func makeMD5(from string: String) throws -> String { 5 | let md5 = CryptoHasher(hash: .md5, encoding: .hex) 6 | return try md5.make(string.makeBytes()).makeString() 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Sources/App/Common/Extensions/Foundation/Array/Array+RandomValue.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Array { 4 | var randomValue: Element { 5 | let randomIndex = Int.random(min: 0, max: self.count - 1) 6 | return self[randomIndex] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Sources/App/Common/Extensions/Foundation/Array/Array+Shuffled.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Array { 4 | func shuffled() -> [Element] { 5 | guard count > 1 else { 6 | return self 7 | } 8 | var result = self 9 | for i in 0..<(result.count - 1) { 10 | let j = Int.random(min: 0, max: result.count - 1 - i) + i 11 | result.swapAt(i, j) 12 | } 13 | return result 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/App/Common/Extensions/Foundation/Bool/Bool+RandomValue.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Bool { 4 | static var randomValue: Bool { 5 | return Int.random(min: 0, max: 1) == 0 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Sources/App/Common/Extensions/Foundation/Date/Date+Format.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Date { 4 | var mysqlString: String { 5 | return DateFormatter.mysql.string(from: self) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Sources/App/Common/Extensions/Foundation/Date/Date+Random.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Date { 4 | static var randomValue: Date { 5 | let randomTimelane: Double = Bool.randomValue ? 1 : -1 6 | let positiveInterval = randomTimelane * Double.random(min: 30000, max: 1000000) 7 | return Date().addingTimeInterval(positiveInterval) 8 | } 9 | 10 | static var randomValueInFuture: Date { 11 | let positiveInterval = Double.random(min: 30000, max: 1000000) 12 | return Date().addingTimeInterval(positiveInterval) 13 | } 14 | 15 | static var randomValueInPast: Date { 16 | let negativeInterval = -1 * Double.random(min: 30000, max: 1000000) 17 | return Date().addingTimeInterval(negativeInterval) 18 | } 19 | 20 | var fiveHoursAgo: Date { 21 | let negativeFiveHourseInterval: Double = -18000 22 | return self.addingTimeInterval(negativeFiveHourseInterval) 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Sources/App/Common/Extensions/Foundation/Double/Double+RandomValue.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | #if os(Linux) 3 | import libc 4 | #endif 5 | extension Double { 6 | /// Random double value, 7 | /// rounded to 4 places, for ex: 1.212233244 -> 1.2122 8 | static var randomValue: Double { 9 | let r = random(min: 1.111, max: 333.333) 10 | let divisor = pow(10.0, 4.0) 11 | #if os(Linux) 12 | return libc.round(r * divisor) / divisor 13 | #else 14 | return Darwin.round(r * divisor) / divisor 15 | #endif 16 | } 17 | 18 | static func random(min: Double, max: Double) -> Double { 19 | let bigRand = Int.random(min: 0, max: Int(Int32.max)-1) 20 | // Random number from 0 to 1.0, inclusive 21 | let random = Double(bigRand) / 0xFFFFFFFF 22 | return random * (max - min) + min 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/App/Common/Extensions/Foundation/FileManager/FileManger+RemoveFiles.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension FileManager { 4 | 5 | func removeAllFiles(atPath: String) throws { 6 | 7 | guard fileExists(atPath: atPath) else { 8 | return 9 | } 10 | 11 | try contentsOfDirectory(atPath: atPath).forEach { filename in 12 | let filePathToRemove = atPath + filename 13 | try removeItem(atPath: filePathToRemove) 14 | } 15 | 16 | } 17 | 18 | func removeAllFiles(at dir: URL) throws { 19 | 20 | guard fileExists(atPath: dir.path) else { 21 | return 22 | } 23 | 24 | try contentsOfDirectory(atPath: dir.path).forEach { filename in 25 | let filePathToRemove = dir.appendingPathComponent(filename) 26 | try removeItem(at: filePathToRemove) 27 | } 28 | 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Sources/App/Common/Extensions/Foundation/Int/Int+RandomValue.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Int { 4 | static var randomValue: Int { 5 | return Int.random(min: 0, max: 1000) 6 | } 7 | 8 | static func randomValue(min: Int, max: Int) -> Int { 9 | return Int.random(min: min, max: max) 10 | } 11 | 12 | static var randomTimestamp: Int { 13 | return Int.random(min: 50000, max: 500000) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/App/Common/Extensions/Foundation/Optional/Optional+Empty.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Optional where Wrapped == String { 4 | var ifNotEmpty: String? { 5 | if let value = self, !value.isEmpty { 6 | return value 7 | } 8 | return nil 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/App/Common/Extensions/Foundation/String/String+RandomValue.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension String { 4 | static var randomValue: String { 5 | let uuid = UUID().uuidString 6 | let upperBound = uuid.count - 1 7 | let randomStringLength = Int.random(min: 1, max: upperBound) 8 | let randomStringIndex = String.Index(encodedOffset: randomStringLength) 9 | let randString = String(uuid[.. User { 6 | return try auth.assertAuthenticated() 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Sources/App/Common/Extensions/HTTP/Response+Message.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import HTTP 3 | 4 | extension Response { 5 | 6 | convenience init(_ status: Status, message: String) throws { 7 | var json = JSON() 8 | try json.set("message", message) 9 | try self.init(status: status, json: json) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Sources/App/Common/Extensions/Node/StructuredDataWrapper+stringValue.swift: -------------------------------------------------------------------------------- 1 | import Node 2 | 3 | extension StructuredDataWrapper { 4 | 5 | public var stringValue: String { 6 | return self.string ?? "" 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /Sources/App/Common/Helpers/Constants/Constants.swift: -------------------------------------------------------------------------------- 1 | import HTTP 2 | 3 | struct Constants { 4 | 5 | struct Config { 6 | static let app = "app" 7 | static let clientToken = "client-token" 8 | static let domain = "domain" 9 | } 10 | 11 | struct Middleware { 12 | static let client = "client-middleware" 13 | static let photoURL = "photoURL-middleware" 14 | } 15 | 16 | struct Path { 17 | static let userPhotos = "user_photos" 18 | static let eventPhotos = "event_photos" 19 | } 20 | 21 | struct Status { 22 | struct Registration { 23 | static let closed = "registration_closed" 24 | static let canRegister = "can_register" 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Sources/App/Common/Helpers/Photo/PhotoController.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import HTTP 3 | import Vapor 4 | import FluentProvider 5 | 6 | struct PhotoController { 7 | 8 | private let drop: Droplet 9 | 10 | init(drop: Droplet) { 11 | self.drop = drop 12 | } 13 | 14 | func downloadAndSavePhoto(for event: Event, with url: String?) throws -> String { 15 | 16 | guard 17 | let photoURL = url, 18 | photoURL.isNotEmpty, 19 | let eventId = event.id?.string 20 | else { 21 | throw Abort(.badRequest, reason: "Can't get photo from url: '\(url ?? "empty URL")'") 22 | } 23 | return try downloadAndSavePhoto(for: eventId, with: photoURL, folder: Constants.Path.eventPhotos) 24 | } 25 | 26 | func downloadAndSavePhoto(for user: User, with url: String?) throws -> String { 27 | 28 | guard 29 | let photoURL = url, 30 | photoURL.isNotEmpty, 31 | let userId = user.id?.string 32 | else { 33 | throw Abort(.badRequest, reason: "Can't get photo from url: '\(url ?? "empty URL")'") 34 | } 35 | return try downloadAndSavePhoto(for: userId, with: photoURL) 36 | } 37 | 38 | func downloadAndSavePhoto(for objectId: String, with url: String, folder: String = Constants.Path.userPhotos) throws -> String { 39 | 40 | let request = try drop.client.get(url) 41 | guard let photoBytes = request.body.bytes else { 42 | throw Abort(.badRequest, reason: "Can't get photo from url: '\(url)'") 43 | } 44 | 45 | guard let photoURL = URL(string: url) else { 46 | throw Abort(.badRequest, reason: "Can't cast String to URL for: '\(url)'") 47 | } 48 | 49 | let filename = photoURL.lastPathComponent 50 | try savePhoto(for: objectId, photoBytes: photoBytes, filename: filename, folder: folder) 51 | 52 | return filename 53 | } 54 | 55 | func savePhoto(for objectId: String, photoAsString: String, folder: String = Constants.Path.userPhotos) throws -> String { 56 | 57 | let filename = UUID().uuidString + ".png" 58 | 59 | guard let photo = Data(base64Encoded: photoAsString) else { 60 | throw Abort(.badRequest, reason: "Can't cast photoAsString to Data") 61 | } 62 | 63 | let photoBytes = photo.makeBytes() 64 | try savePhoto(for: objectId, photoBytes: photoBytes, filename: filename, folder: folder) 65 | 66 | return filename 67 | } 68 | 69 | func savePhoto(for objectId: String, photoBytes: Bytes, filename: String, folder: String = Constants.Path.userPhotos) throws { 70 | 71 | let userDir = URL(fileURLWithPath: drop.config.publicDir) 72 | .appendingPathComponent(folder) 73 | .appendingPathComponent(objectId) 74 | 75 | let fileManager = FileManager.default 76 | 77 | if fileManager.fileExists(atPath: userDir.path) { 78 | try fileManager.removeAllFiles(at: userDir) 79 | } else { 80 | try fileManager.createDirectory(at: userDir, withIntermediateDirectories: true, attributes: nil) 81 | } 82 | 83 | let userDirWithImage = userDir.appendingPathComponent(filename) 84 | let data = Data(bytes: photoBytes) 85 | fileManager.createFile(atPath: userDirWithImage.path, contents: data, attributes: nil) 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /Sources/App/Controllers/Event/EventController.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Fluent 3 | 4 | final class EventController { 5 | 6 | enum Timeline { 7 | case beforeDate(Date) 8 | case afterDate(Date) 9 | } 10 | 11 | func index(_ request: Request) throws -> ResponseRepresentable { 12 | guard let query = request.query else { 13 | throw Abort(.badRequest, reason: "Query parameters is missing in URL request") 14 | } 15 | 16 | if let date = query["before"]?.date { 17 | return try sendEvents(.beforeDate(date), request: request) 18 | } else if let date = query["after"]?.date { 19 | return try sendEvents(.afterDate(date), request: request) 20 | } else { 21 | throw Abort(.badRequest, reason: "Query parameter is not correct in URL request") 22 | } 23 | } 24 | 25 | private func sendEvents(_ eventsTimeline: Timeline, request: Request) throws -> Response { 26 | let events = try fetchEvents(using: eventsTimeline) 27 | return try prepareJSON(for: events, with: request).makeResponse() 28 | } 29 | 30 | private func fetchEvents(using eventsTimeline: Timeline) throws -> [Event] { 31 | switch eventsTimeline { 32 | case .beforeDate(let date): 33 | return try fetchEvents(that: .lessThanOrEquals, date: date) 34 | case .afterDate(let date): 35 | return try fetchEvents(that: .greaterThanOrEquals, date: date) 36 | } 37 | } 38 | 39 | private func fetchEvents( 40 | that comparison: Filter.Comparison, 41 | date: Date 42 | ) throws -> [Event] { 43 | return try Event 44 | .makeQuery() 45 | .filter(Event.Keys.endDate, comparison, date) 46 | .sort(Event.Keys.startDate, .descending) 47 | .all() 48 | } 49 | 50 | private func prepareJSON( 51 | for events: [Event], 52 | with request: Request 53 | ) throws -> JSON { 54 | let jsonArray: [StructuredData] = try events.map { (event) -> StructuredData in 55 | try event.makeJSON(with: request).wrapped 56 | } 57 | return JSON(.array(jsonArray)) 58 | } 59 | } 60 | 61 | extension EventController: ResourceRepresentable { 62 | 63 | func makeResource() -> Resource { 64 | return Resource( 65 | index: index 66 | ) 67 | } 68 | } 69 | 70 | extension EventController: EmptyInitializable { } 71 | -------------------------------------------------------------------------------- /Sources/App/Controllers/Event/EventSpeechController.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | final class EventSpeechController { 4 | 5 | func index(request: Request) throws -> ResponseRepresentable { 6 | guard let id = request.parameters["id"]?.int else { 7 | throw Abort(.badRequest, reason: "Id parameter is missing.") 8 | } 9 | guard let event = try Event.find(id) else { 10 | throw Abort(.notFound, reason: "Event with given id not found.") 11 | } 12 | 13 | let speeches = try event.speeches() 14 | return try speeches.makeJSON() 15 | } 16 | } 17 | 18 | extension EventSpeechController: ResourceRepresentable { 19 | 20 | func makeResource() -> Resource { 21 | return Resource( 22 | index: index 23 | ) 24 | } 25 | } 26 | 27 | extension EventSpeechController: EmptyInitializable { } 28 | -------------------------------------------------------------------------------- /Sources/App/Controllers/Event/Registration/AutoapproveController.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Vapor 3 | import Fluent 4 | 5 | final class AutoapproveController { 6 | 7 | private let autoapprove: Approval 8 | 9 | init() throws { 10 | guard let count = try? Approval.count() else { 11 | fatalError("There are problems with call Approve.count()") 12 | } 13 | 14 | if count < 1 { 15 | autoapprove = Approval( 16 | visitedEvents: 2, 17 | skippedEvents: 2, 18 | periodInMonths: 6) 19 | try autoapprove.save() 20 | } else { 21 | autoapprove = try Approval.all().first! 22 | } 23 | } 24 | 25 | func grandApprove(to user: User, on event: Event) throws -> Bool? { 26 | 27 | guard let userId = user.id else { 28 | return nil 29 | } 30 | 31 | let visitedEvents = try EventReg 32 | .makeQuery() 33 | .filter(EventReg.Keys.userId, userId) 34 | .filter(EventReg.Keys.status, EventReg.RegistrationStatus.approved.string) 35 | .count() 36 | 37 | guard visitedEvents >= autoapprove.visitedEvents else { 38 | return false 39 | } 40 | 41 | guard 42 | let date = Calendar.current.date(byAdding: .month, value: -autoapprove.periodInMonths, to: Date()), 43 | let eventId = event.id 44 | else { 45 | return nil 46 | } 47 | 48 | let events = try Event.makeQuery() 49 | .filter(Event.Keys.id != eventId) 50 | .filter(Event.Keys.endDate >= date) 51 | .filter(Event.Keys.endDate < Date()) 52 | .all() 53 | 54 | let regForms = try RegForm.makeQuery() 55 | .filter(RegForm.Keys.eventId, in: events.array.map { $0.id! }) 56 | .all() 57 | 58 | guard regForms.count >= autoapprove.skippedEvents else { 59 | return true 60 | } 61 | 62 | let skippedEventsCount = try EventReg 63 | .makeQuery() 64 | .filter(EventReg.Keys.regFormId, in: regForms.array.map { $0.id!.int }) 65 | .filter(EventReg.Keys.userId, user.id!) 66 | .filter(EventReg.Keys.status, EventReg.RegistrationStatus.skipped.string) 67 | .count() 68 | 69 | guard skippedEventsCount >= autoapprove.skippedEvents else { 70 | return true 71 | } 72 | 73 | return false 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /Sources/App/Controllers/Event/Registration/RegistrationController.swift: -------------------------------------------------------------------------------- 1 | import HTTP 2 | import Vapor 3 | import Fluent 4 | 5 | final class RegistrationController { 6 | 7 | let autoapprove = try? AutoapproveController() 8 | let drop: Droplet 9 | 10 | init(drop: Droplet) { 11 | self.drop = drop 12 | } 13 | 14 | func store(_ request: Request) throws -> ResponseRepresentable { 15 | 16 | let user = try request.user() 17 | guard let userId = user.id else { 18 | throw Abort(.internalServerError, reason: "Can't get user.id") 19 | } 20 | 21 | guard 22 | let json = request.json, 23 | let regFormIdInt = json[Keys.regFormId]?.int 24 | else { 25 | throw Abort(.internalServerError, reason: "Can't get 'reg_form_Id' from request") 26 | } 27 | 28 | let regFormId = Identifier(.int(regFormIdInt)) 29 | 30 | guard try EventReg.duplicationCheck(regFormId: regFormId, userId: userId) else { 31 | throw Abort( 32 | .internalServerError, 33 | reason: "User with token '\(user.token ?? "")' has alredy registered to this event") 34 | } 35 | 36 | guard 37 | let event = try RegForm.find(regFormId)?.event, 38 | let grandApprove = try autoapprove?.grandApprove(to: user, on: event) 39 | else { 40 | throw Abort(.internalServerError, reason: "Can't check autoapprove status") 41 | } 42 | 43 | let eventReg = EventReg( 44 | regFormId: regFormId, 45 | userId: userId, 46 | status: grandApprove ? EventReg.RegistrationStatus.approved : EventReg.RegistrationStatus.waiting) 47 | 48 | try drop.mysql().transaction { (connection) in 49 | try eventReg.makeQuery(connection).save() 50 | try storeEventRegAnswers(request, eventReg: eventReg, connection: connection) 51 | } 52 | 53 | return eventReg 54 | } 55 | 56 | func cancel(_ request: Request, eventReg: EventReg) throws -> ResponseRepresentable { 57 | 58 | let user = try request.user() 59 | 60 | guard 61 | let userId = user.id, 62 | eventReg.status == EventReg.RegistrationStatus.approved, 63 | eventReg.userId == userId 64 | else { 65 | throw Abort(.internalServerError, reason: "Can't find approved User's registraion for cancel") 66 | } 67 | 68 | try eventReg.delete() 69 | return try Response( .ok, message: "Registration is canceled") 70 | } 71 | 72 | } 73 | 74 | extension RegistrationController: ResourceRepresentable { 75 | 76 | func makeResource() -> Resource { 77 | return Resource( 78 | store: store, 79 | destroy: cancel 80 | ) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Sources/App/Controllers/Event/Registration/RegistrationControllerHelper.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Fluent 3 | 4 | extension RegistrationController { 5 | struct Keys { 6 | static let regFields = "reg_fields" 7 | static let regFormId = "reg_form_id" 8 | static let fields = "fields" 9 | static let userAnswers = "user_answers" 10 | } 11 | 12 | func checkRadio(fieldId: Identifier, answerCount: Int) throws -> Bool { 13 | let fieldType = try RegField.find(fieldId)?.type 14 | if fieldType == RegField.FieldType.radio { 15 | return answerCount <= 1 16 | } else { 17 | return true 18 | } 19 | } 20 | 21 | func checkRequired(fieldId: Identifier, answerCount: Int) throws -> Bool { 22 | guard let fieldRequired = try RegField.find(fieldId)?.required else { 23 | throw Abort(.internalServerError, reason: "Can't get fieldId") 24 | } 25 | 26 | if fieldRequired { 27 | return answerCount > 0 28 | } else { 29 | return true 30 | } 31 | } 32 | 33 | func storeEventRegAnswers(_ request: Request, eventReg: EventReg, connection: Connection? = nil) throws { 34 | 35 | guard let eventRegId = eventReg.id else { 36 | throw Abort(.internalServerError, reason: "Can't get eventRegId") 37 | } 38 | 39 | guard let fields = request.json?[Keys.fields]?.array else { 40 | throw Abort(.internalServerError, reason: "Can't get 'fields' and 'reg_form_Id' from request") 41 | } 42 | 43 | for field in fields { 44 | 45 | let fieldId = try field.get(EventRegAnswer.Keys.regFieldId) as Identifier 46 | let userAnswers = try field.get(Keys.userAnswers) as [JSON] 47 | 48 | guard try checkRequired(fieldId: fieldId, answerCount: userAnswers.count) else { 49 | throw Abort(.internalServerError, reason: "The field must have at least one answer. Field id is '\(fieldId.int ?? -1)'") 50 | } 51 | 52 | guard try checkRadio(fieldId: fieldId, answerCount: userAnswers.count) else { 53 | throw Abort(.internalServerError, reason: "The answer to field with type radio should be only one. Field id is '\(fieldId.int ?? -1)'") 54 | } 55 | 56 | for userAnswer in userAnswers { 57 | let answerId = try userAnswer.get("id") as Identifier 58 | let answerValue = try userAnswer.get("value") as String 59 | 60 | let eventRegAnswer = EventRegAnswer( 61 | eventRegId: eventRegId, 62 | regFieldId: fieldId, 63 | regFieldAnswerId: answerId, 64 | answerValue: answerValue) 65 | if let conn = connection { 66 | try eventRegAnswer.makeQuery(conn).save() 67 | } else { 68 | try eventRegAnswer.save() 69 | } 70 | } 71 | 72 | } 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /Sources/App/Controllers/Event/Registration/RegistrationFormController.swift: -------------------------------------------------------------------------------- 1 | import HTTP 2 | import Vapor 3 | import Fluent 4 | 5 | final class RegistrationFormController { 6 | 7 | func index(_ request: Request) throws -> ResponseRepresentable { 8 | 9 | guard let eventId = request.parameters["id"]?.int else { 10 | throw Abort(.badRequest, reason: "EventId parameters is missing in URL request") 11 | } 12 | 13 | guard let regForm = try RegForm.getRegForm(by: eventId) else { 14 | throw Abort(.internalServerError, reason: "Can't find RegForm by event_id: \(eventId)") 15 | } 16 | 17 | return regForm 18 | } 19 | } 20 | 21 | extension RegistrationFormController: ResourceRepresentable { 22 | 23 | func makeResource() -> Resource { 24 | return Resource( 25 | index: index 26 | ) 27 | } 28 | } 29 | 30 | extension RegistrationFormController: EmptyInitializable { } 31 | -------------------------------------------------------------------------------- /Sources/App/Controllers/Heartbeat/HeartbeatController.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import HTTP 3 | 4 | final class HeartbeatController { 5 | 6 | func index(request: Request) throws -> ResponseRepresentable { 7 | guard let beat = try Heartbeat.all().first else { 8 | return Response(status: .noContent) 9 | } 10 | let result = try beat.makeJSON() 11 | return result 12 | } 13 | 14 | func store(request: Request) throws -> ResponseRepresentable { 15 | let count = try Heartbeat.count() 16 | if count > 0 { 17 | try Heartbeat.makeQuery().delete() 18 | } 19 | guard let json = request.json else { 20 | return try Response(.badRequest, message: "JSON is missing") 21 | } 22 | let beat = try Heartbeat(json: json) 23 | try beat.save() 24 | return try beat.makeJSON() 25 | } 26 | } 27 | 28 | extension HeartbeatController: ResourceRepresentable { 29 | 30 | func makeResource() -> Resource { 31 | return Resource( 32 | index: index, 33 | store: store 34 | ) 35 | } 36 | } 37 | 38 | extension HeartbeatController: EmptyInitializable {} 39 | -------------------------------------------------------------------------------- /Sources/App/Controllers/Stage/Samples/ClearDatabase.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | //swiftlint:disable superfluous_disable_command 4 | //swiftlint:disable force_try 5 | extension Samples { 6 | 7 | func truncateTables() throws { 8 | 9 | var tableList: [String]? 10 | 11 | var tableForTruncate: [String] { 12 | 13 | if tableList != nil { 14 | return tableList! 15 | } 16 | 17 | let db = try! drop.assertDatabase() 18 | // swiftlint:disable force_try 19 | guard let nodes = try! db.driver.makeConnection(.read).raw("SHOW TABLES;").array else { 20 | return [] 21 | } 22 | 23 | let jsons = nodes.map { node -> JSON in 24 | return JSON(node: node) 25 | } 26 | 27 | let dbName = drop.config["mysql", "database"]?.string ?? "" 28 | tableList = jsons 29 | .map { json -> String in 30 | json["Tables_in_\(dbName)"]?.string ?? "" 31 | } 32 | .filter { table -> Bool in 33 | table != "social" && 34 | table != "fluent" 35 | } 36 | 37 | return tableList! 38 | } 39 | 40 | guard drop.config.environment == .development else { 41 | return 42 | } 43 | 44 | let db = try! drop.assertDatabase() 45 | 46 | defer { 47 | // swiftlint:disable force_try 48 | try! db.raw("SET FOREIGN_KEY_CHECKS = 1;") 49 | print("Foreign key checks ON\n") 50 | } 51 | 52 | try! db.raw("SET FOREIGN_KEY_CHECKS = 0;") 53 | print("\nForeign key checks OFF") 54 | 55 | var truncatedTableCount = 0 56 | 57 | tableForTruncate.forEach { (table) in 58 | try! db.raw("TRUNCATE \(table)") 59 | truncatedTableCount += 1 60 | } 61 | 62 | print("Truncate table(s): \(truncatedTableCount)") 63 | 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Sources/App/Controllers/Stage/Samples/CreatorSample.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | //swiftlint:disable superfluous_disable_command 4 | //swiftlint:disable force_try 5 | extension Samples { 6 | 7 | func createCreatorSample() throws { 8 | 9 | func createCreator(user: User) throws { 10 | 11 | let creator = Creator( 12 | userId: user.id!, 13 | position: user.id!.int!, 14 | info: String.randomValue, 15 | url: String.randomURL, 16 | active: Bool.randomValue 17 | ) 18 | 19 | try creator.save() 20 | } 21 | 22 | try createUserSample(count: 10).forEach { user in 23 | try createCreator(user: user) 24 | } 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /Sources/App/Controllers/Stage/Samples/EventSample.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | //swiftlint:disable superfluous_disable_command 4 | //swiftlint:disable force_try 5 | extension Samples { 6 | 7 | @discardableResult 8 | func createEventSample(pastCount: Int = 30, comingCount: Int = 1) throws -> [Event] { 9 | 10 | let photoController = PhotoController(drop: drop) 11 | let randomPhotoURL = "https://picsum.photos/200/300?image=" 12 | 13 | func storePastEvent() throws -> Event { 14 | return try! storeEvent(date: Date.randomValueInPast, isRegistrationOpen: false) 15 | } 16 | 17 | func storeComingEvent() throws -> Event { 18 | return try! storeEvent(date: Date.randomValueInFuture, isRegistrationOpen: true) 19 | } 20 | 21 | func storeEvent(date: Date = Date.randomValue, isRegistrationOpen: Bool) throws -> Event { 22 | let city = City(cityName: String.randomValue) 23 | try! city.save() 24 | 25 | let place = Place( 26 | title: String.randomValue, 27 | address: String.randomValue, 28 | description: String.randomValue, 29 | latitude: Double.randomValue, 30 | longitude: Double.randomValue, 31 | cityId: city.id! 32 | ) 33 | let photoURL = "\(randomPhotoURL)\(Int.random(min: 0, max: 200))" 34 | 35 | try! place.save() 36 | 37 | let event = Event( 38 | title: String.randomValue, 39 | description: String.randomValue, 40 | photo: String.randomValue, 41 | placeId: place.id!, 42 | isRegistrationOpen: isRegistrationOpen, 43 | startDate: date, 44 | endDate: date) 45 | 46 | try! event.save() 47 | event.photo = try! photoController.downloadAndSavePhoto(for: event, with: photoURL) 48 | try! event.save() 49 | return event 50 | } 51 | 52 | var events: [Event] = [] 53 | for _ in 1...Int.random(min: 2, max: pastCount) { 54 | let event = try! storePastEvent() 55 | events.append(event) 56 | } 57 | 58 | for _ in 1...Int.random(min: 2, max: comingCount) { 59 | let event = try! storeComingEvent() 60 | events.append(event) 61 | } 62 | 63 | return events 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /Sources/App/Controllers/Stage/Samples/RegFormSample.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Foundation 3 | //swiftlint:disable superfluous_disable_command 4 | //swiftlint:disable force_try 5 | extension Samples { 6 | 7 | func createRegFormSample() throws { 8 | 9 | try! Event 10 | .makeQuery() 11 | .filter(Event.Keys.endDate, .greaterThanOrEquals, Date()) 12 | .all() 13 | .forEach { (event) in 14 | 15 | let regForm = RegForm( 16 | eventId: event.id!, 17 | formName: String.randomValue, 18 | description: String.randomValue) 19 | 20 | try! regForm.save() 21 | 22 | for _ in 1...Int.random(min: 2, max: 3) { 23 | 24 | let type = ["checkbox", "radio", "string"].randomValue 25 | 26 | let regField = RegField( 27 | regFormId: regForm.id!, 28 | required: Bool.randomValue, 29 | name: String.randomValue, 30 | type: RegField.FieldType(type), 31 | placeholder: String.randomValue, 32 | defaultValue: String.randomValue) 33 | 34 | try! regField.save() 35 | 36 | for _ in 1...Int.random(min: 2, max: 3) { 37 | let regFiedlAnswers = RegFieldAnswer( 38 | value: String.randomValue, 39 | regFieldId: regField.id!) 40 | try! regFiedlAnswers.save() 41 | } 42 | 43 | } 44 | 45 | } 46 | 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Sources/App/Controllers/Stage/Samples/Samples.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import AuthProvider 3 | 4 | //swiftlint:disable superfluous_disable_command 5 | //swiftlint:disable force_try 6 | 7 | final class Samples { 8 | let drop: Droplet 9 | 10 | init (drop: Droplet) { 11 | self.drop = drop 12 | } 13 | 14 | } 15 | 16 | extension Samples { 17 | 18 | func create() throws { 19 | 20 | guard drop.config.environment == .development else { 21 | return 22 | } 23 | 24 | try truncateTables() 25 | try createUserSample() 26 | try createCreatorSample() 27 | try createEventSample() 28 | try createSpeakerSample() 29 | try createRegFormSample() 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Sources/App/Controllers/Stage/Samples/SpeakerSample.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | //swiftlint:disable superfluous_disable_command 4 | //swiftlint:disable force_try 5 | extension Samples { 6 | 7 | func createSpeakerSample() throws { 8 | 9 | func createSpeech(for eventId: Identifier) -> Speech { 10 | let speech = Speech( 11 | eventId: eventId, 12 | title: String.randomValue, 13 | description: String.randomValue) 14 | try! speech.save() 15 | return speech 16 | } 17 | 18 | @discardableResult 19 | func createContent(for speecchId: Identifier, type: Content.ContentType = Content.ContentType.video) -> Content { 20 | let content = Content( 21 | speechId: speecchId, 22 | title: String.randomValue, 23 | description: String.randomValue, 24 | link: String.randomURL, 25 | type: Content.ContentType.video) 26 | try! content.save() 27 | return content 28 | } 29 | 30 | func createSpeeaker(with users: [User], for speechId: Identifier) { 31 | 32 | let usersShuffled = users.shuffled() 33 | let range = 0...Int.random(min: 1, max: usersShuffled.count - 1 < 2 ? usersShuffled.count - 1 : 2) 34 | for index in range { 35 | 36 | let speaker = Speaker( 37 | userId: usersShuffled[index].id!, 38 | speechId: speechId) 39 | try! speaker.save() 40 | 41 | if Bool.randomValue && Bool.randomValue { // second speaker 42 | let speaker = Speaker( 43 | userId: usersShuffled[usersShuffled.count - 1 - index].id!, 44 | speechId: speechId) 45 | try! speaker.save() 46 | } 47 | 48 | } 49 | 50 | } 51 | 52 | let users: [User] = try! User.all() 53 | let events: [Event] = try! Event.all() 54 | 55 | events.forEach { event in 56 | 57 | for _ in 1...Int.random(min: 2, max:5) { 58 | let speech = createSpeech(for: event.id!) 59 | createContent(for: speech.id!) 60 | if Bool.randomValue { 61 | createContent(for: speech.id!, type: Content.ContentType.slide) 62 | } 63 | createSpeeaker(with: users, for: speech.id!) 64 | } 65 | } 66 | 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /Sources/App/Controllers/Stage/Samples/UserSample.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | //swiftlint:disable superfluous_disable_command 4 | //swiftlint:disable force_try 5 | extension Samples { 6 | 7 | @discardableResult 8 | func createUserSample(count: Int = 5) throws -> [User] { 9 | 10 | let photoController = PhotoController(drop: drop) 11 | let randomPhotoURL = "https://picsum.photos/200/300?image=" 12 | 13 | func createUser(index: Int) throws -> User { 14 | 15 | let user = User( 16 | name: "TestNameUser\(index)" , 17 | lastname: "TestLastNameUser\(index)", 18 | company: "TestCompany\(index)", 19 | position: "TestPosition\(index)", 20 | photo: "", 21 | email: String.randomEmail, 22 | phone: String.randomPhone 23 | ) 24 | 25 | let photoURL = "\(randomPhotoURL)\(Int.random(min: 0, max: 200))" 26 | try! user.save() 27 | user.createSession() 28 | user.token = "test\(index)" 29 | user.photo = try! photoController.downloadAndSavePhoto(for: user, with: photoURL) 30 | try! user.save() 31 | return user 32 | } 33 | 34 | var users: [User] = [] 35 | for i in 1...Int.random(min: 2, max: count) { 36 | let user = try createUser(index: i) 37 | users.append(user) 38 | } 39 | return users 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /Sources/App/Controllers/Stage/StageController.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import HTTP 3 | 4 | final class StageController { 5 | 6 | private let sample: Samples 7 | 8 | init(drop: Droplet) { 9 | self.sample = Samples(drop: drop) 10 | } 11 | 12 | func store(request: Request) throws -> ResponseRepresentable { 13 | try sample.create() 14 | return try Response( .ok, message: "Stage was recreated") 15 | } 16 | } 17 | 18 | extension StageController: ResourceRepresentable { 19 | 20 | func makeResource() -> Resource { 21 | return Resource( 22 | store: store 23 | ) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/App/Controllers/User/Auth/FacebookController.swift: -------------------------------------------------------------------------------- 1 | import HTTP 2 | import Vapor 3 | import Fluent 4 | 5 | final class FacebookController { 6 | 7 | private let drop: Droplet 8 | private let photoController: PhotoController 9 | private let fb = Social.FB.self 10 | private let config: FacebookConfig 11 | 12 | init(drop: Droplet) { 13 | self.drop = drop 14 | self.config = FacebookConfig(drop.config) 15 | self.photoController = PhotoController(drop: self.drop) 16 | } 17 | 18 | func createOrUpdateUserProfile(with authorizationCode: String) throws -> User { 19 | 20 | let accessToken = try authorize(by: authorizationCode) 21 | let (profile, socialUserId) = try getUserProfile(with: accessToken) 22 | 23 | if let user = try SocialAccount.find(by: socialUserId) { 24 | user.name = profile.name 25 | user.lastname = profile.lastname 26 | user.email = profile.email 27 | user.photo = try photoController.downloadAndSavePhoto(for: user, with: profile.photo) 28 | try user.save() 29 | return user 30 | } 31 | 32 | try profile.save() 33 | 34 | if let url = profile.photo, !url.isEmpty { 35 | profile.photo = try photoController.downloadAndSavePhoto(for: profile, with: profile.photo) 36 | try profile.save() 37 | } 38 | 39 | try SocialHelper.storeSocialProfile(for: profile, socialUserId: socialUserId, social: Social.Nets.fb) 40 | 41 | return profile 42 | } 43 | 44 | fileprivate func authorize(by authorizationCode: String) throws -> String { 45 | 46 | if 47 | drop.config.environment == .test, 48 | authorizationCode.isEmpty, 49 | let accessTokenFromConfig = config.accessToken, 50 | accessTokenFromConfig.isNotEmpty { 51 | print("\n\nUSING TOKEN FROM CONFIG FILE\n\n") 52 | return accessTokenFromConfig 53 | } 54 | 55 | let code = authorizationCode + "#_=_" 56 | 57 | let authRequest = try drop.client.get(config.tokenRequestURL, query: [ 58 | fb.clientId: config.clientId, 59 | fb.redirectURI: config.redirectURI, 60 | fb.scope: config.scope, 61 | fb.clientSecret: config.clientSecret, 62 | fb.code: code 63 | ]) 64 | 65 | guard let accessToken = authRequest.json?[fb.accessToken]?.string else { 66 | throw Abort(.badRequest, reason: "Can't get 'access_token' from Facebook") 67 | } 68 | 69 | return accessToken 70 | } 71 | 72 | fileprivate func getUserProfile(with token: String) throws -> (user: User, socialUserId: String) { 73 | 74 | let userInfoUrl = config.userInfoURL 75 | let fields = config.fields 76 | let scope = config.scope 77 | 78 | let userInfo = try drop.client.get(userInfoUrl, query: [ 79 | fb.fields: fields, 80 | fb.scope: scope, 81 | fb.accessToken: token 82 | ]) 83 | 84 | let profile = fb.Profile.self 85 | guard 86 | let socialUserId = userInfo.json?[profile.socialUserId]?.string, 87 | let name = userInfo.json?[profile.name]?.string, 88 | let lastname = userInfo.json?[profile.lastname]?.string, 89 | let email = userInfo.json?[profile.email]?.string, 90 | let photo = userInfo.json?[profile.photo]?.string 91 | else { 92 | throw Abort(.badRequest, reason: "Can't get user profile from Facebook") 93 | } 94 | 95 | let user = User( 96 | name: name, 97 | lastname: lastname, 98 | photo: photo, 99 | email: email) 100 | 101 | return (user, socialUserId) 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /Sources/App/Controllers/User/Auth/GithubController.swift: -------------------------------------------------------------------------------- 1 | import HTTP 2 | import Vapor 3 | import Fluent 4 | 5 | final class GithubController { 6 | 7 | private let drop: Droplet 8 | private let photoController: PhotoController 9 | private let git = Social.Github.self 10 | private let config: GithubConfig 11 | 12 | init(drop: Droplet) { 13 | self.drop = drop 14 | self.config = GithubConfig(drop.config) 15 | self.photoController = PhotoController(drop: self.drop) 16 | } 17 | 18 | func createOrUpdateUserProfile(with authorizationCode: String, secret: String) throws -> User { 19 | 20 | let accessToken = try authorize(by: authorizationCode, secret: secret) 21 | let (profile, socialUserId) = try getUserProfile(with: accessToken) 22 | 23 | if let user = try SocialAccount.find(by: socialUserId) { 24 | user.name = profile.name 25 | user.lastname = profile.lastname 26 | user.photo = try photoController.downloadAndSavePhoto(for: user, with: profile.photo) 27 | try user.save() 28 | return user 29 | } 30 | 31 | try profile.save() 32 | 33 | if let url = profile.photo, url.isNotEmpty { 34 | profile.photo = try photoController.downloadAndSavePhoto(for: profile, with: profile.photo) 35 | try profile.save() 36 | } 37 | 38 | try SocialHelper.storeSocialProfile(for: profile, socialUserId: socialUserId, social: Social.Nets.fb) 39 | 40 | return profile 41 | } 42 | 43 | fileprivate func authorize(by authorizationCode: String, secret: String) throws -> String { 44 | 45 | if 46 | drop.config.environment == .test, 47 | authorizationCode.isEmpty, 48 | let accessTokenFromConfig = config.accessToken, 49 | accessTokenFromConfig.isNotEmpty { 50 | print("\n\nUSING TOKEN FROM CONFIG FILE\n\n") 51 | return accessTokenFromConfig 52 | } 53 | 54 | let code = authorizationCode 55 | let state = secret 56 | 57 | let authRequest = try drop.client.get(config.tokenRequestURL, query: [ 58 | git.clientId: config.clientId, 59 | git.state: state, 60 | git.clientSecret: config.clientSecret, 61 | git.code: code 62 | ]) 63 | 64 | guard let node = authRequest.formURLEncoded else { 65 | throw Abort(.badRequest, reason: "Can't encode formURL from Github") 66 | } 67 | 68 | let response = JSON(node: node) 69 | 70 | guard let accessToken = response[git.accessToken]?.string else { 71 | throw Abort(.badRequest, reason: "Can't get 'access_token' from Github") 72 | } 73 | 74 | return accessToken 75 | } 76 | 77 | fileprivate func getUserProfile(with token: String) throws -> (user: User, socialUserId: String) { 78 | 79 | let userInfo = try drop.client.get( config.userInfoURL, query: [ 80 | git.accessToken: token 81 | ]) 82 | 83 | let profile = git.Profile.self 84 | guard let response = userInfo.json else { 85 | throw Abort(.internalServerError, reason: "Can't get json from github answer") 86 | } 87 | 88 | guard 89 | let login = response[profile.login]?.string, 90 | let photo = response[profile.photo]?.string 91 | else { 92 | throw Abort(.badRequest, reason: "Can't get user profile from Github \n \(try response.serialize(prettyPrint: true).makeString())") 93 | } 94 | 95 | let fullName = response[profile.name]?.string ?? login 96 | let nameComponents = fullName.components(separatedBy: " ") 97 | let name = nameComponents[0] 98 | let lastname = nameComponents[safe: 1] 99 | 100 | let user = User( 101 | name: name, 102 | lastname: lastname, 103 | photo: photo) 104 | 105 | let socialUserId = profile.socialUserId + login 106 | 107 | return (user, socialUserId) 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /Sources/App/Controllers/User/Auth/SocialHelper.swift: -------------------------------------------------------------------------------- 1 | import HTTP 2 | import Vapor 3 | import Fluent 4 | 5 | final class SocialHelper { 6 | 7 | static func storeSocialProfile(for user: User, socialUserId: String, social: String) throws { 8 | 9 | guard let userId = user.id else { 10 | throw Abort(.internalServerError, reason: "Can't get user.id") 11 | } 12 | 13 | guard let socialid = try Social.find(by: social)?.id else { 14 | throw Abort(.internalServerError, reason: "Can't get social.id") 15 | } 16 | 17 | let socialAccount = SocialAccount( 18 | userId: userId, 19 | socialId: socialid, 20 | socialUserId: socialUserId) 21 | 22 | try socialAccount.save() 23 | 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /Sources/App/Controllers/User/Auth/VkontakteController.swift: -------------------------------------------------------------------------------- 1 | import HTTP 2 | import Vapor 3 | import Fluent 4 | import Crypto 5 | 6 | final class VkontakteController { 7 | 8 | private let drop: Droplet 9 | private let photoController: PhotoController 10 | private let vk = Social.VK.self 11 | private let config: VkontakteConfig 12 | 13 | init(drop: Droplet) { 14 | self.drop = drop 15 | self.photoController = PhotoController(drop: self.drop) 16 | self.config = VkontakteConfig(drop.config) 17 | } 18 | 19 | func createOrUpdateUserProfile(use token: String, secret: String) throws -> User { 20 | 21 | let (profile, socialUserId) = try getUserProfile(with: token, secret: secret) 22 | 23 | if let user = try SocialAccount.find(by: socialUserId) { 24 | user.name = profile.name 25 | user.lastname = profile.lastname 26 | user.photo = try photoController.downloadAndSavePhoto(for: user, with: profile.photo) 27 | try user.save() 28 | return user 29 | } 30 | 31 | try profile.save() 32 | 33 | if let url = profile.photo, url.isNotEmpty { 34 | profile.photo = try photoController.downloadAndSavePhoto(for: profile, with: profile.photo) 35 | try profile.save() 36 | } 37 | 38 | try SocialHelper.storeSocialProfile(for: profile, socialUserId: socialUserId, social: Social.Nets.vk) 39 | 40 | return profile 41 | } 42 | 43 | fileprivate func getUserProfile(with token: String, secret: String) throws -> (user: User, socialUserId: String) { 44 | 45 | let signature = try config.getSignatureBased(on: token, and: secret) 46 | let userInfo = try drop.client.get(config.userInfoURL, query: [ 47 | vk.fields: config.fields, 48 | vk.accessToken: token, 49 | vk.sig: signature, 50 | vk.version: vk.versionValue 51 | ]) 52 | 53 | let profile = vk.Profile.self 54 | guard 55 | let json = userInfo.json, 56 | let response = json[profile.response]?.array?.first, 57 | let socialUserId = response[profile.socialUserId]?.string, 58 | let name = response[profile.name]?.string, 59 | let lastname = response[profile.lastname]?.string, 60 | let photo = response[profile.photo]?.string 61 | else { 62 | throw Abort(.badRequest, reason: "Can't get user profile from Vkontakte") 63 | } 64 | 65 | let user = User( 66 | name: name, 67 | lastname: lastname, 68 | photo: photo) 69 | 70 | return (user, socialUserId) 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /Sources/App/Controllers/User/CreatorsController.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import FluentProvider 3 | 4 | final class CreatorsController { 5 | 6 | func getAllCreators(_ request: Request) throws -> ResponseRepresentable { 7 | return try Creator.all().makeJSON() 8 | } 9 | 10 | } 11 | 12 | extension CreatorsController: ResourceRepresentable { 13 | 14 | func makeResource() -> Resource { 15 | return Resource( 16 | index: getAllCreators 17 | ) 18 | } 19 | } 20 | 21 | extension CreatorsController: EmptyInitializable { } 22 | -------------------------------------------------------------------------------- /Sources/App/Controllers/User/GiveSpeechController.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import FluentProvider 3 | import Foundation 4 | import Multipart 5 | 6 | final class GiveSpeechController { 7 | 8 | func storeRequest(_ request: Request) throws -> ResponseRepresentable { 9 | 10 | guard 11 | let json = request.json, 12 | let title = json[GiveSpeech.Keys.title]?.string, 13 | let desciption = json[GiveSpeech.Keys.description]?.string 14 | else { 15 | throw Abort(.badRequest, reason: "Request's body no have `title` or `desciption` fields to give speech") 16 | } 17 | 18 | let user = try request.user() 19 | let giveSpeech = GiveSpeech( 20 | title: title, 21 | description: desciption, 22 | userId: user.id!) 23 | try giveSpeech.save() 24 | 25 | return giveSpeech 26 | } 27 | 28 | } 29 | 30 | extension GiveSpeechController: ResourceRepresentable { 31 | 32 | func makeResource() -> Resource { 33 | return Resource( 34 | store: storeRequest 35 | ) 36 | } 37 | } 38 | 39 | extension GiveSpeechController: EmptyInitializable { } 40 | -------------------------------------------------------------------------------- /Sources/App/Controllers/User/PushNotificationController.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import FluentProvider 3 | import Foundation 4 | import Multipart 5 | 6 | final class PushNotificationController { 7 | 8 | func subscribe(_ request: Request) throws -> ResponseRepresentable { 9 | 10 | guard 11 | let json = request.json, 12 | let pushToken = json[Client.Keys.pushToken]?.string 13 | else { 14 | throw Abort(.badRequest, reason: "Request's body no have pushToken to the push-notification registration") 15 | } 16 | 17 | if let client = try Client.returnIfExists(request: request) { 18 | client.pushToken = pushToken 19 | try client.save() 20 | return client 21 | } 22 | 23 | let client = try Client(request: request) 24 | try client.save() 25 | 26 | return client 27 | } 28 | 29 | func cancel(_ request: Request, user: User) throws -> ResponseRepresentable { 30 | 31 | guard let client = try Client.returnIfExists(request: request) else { 32 | throw Abort( 33 | .internalServerError, 34 | reason: "Not found push-notifications registration for user.id: '\(user.id?.int ?? -1)'" 35 | ) 36 | } 37 | 38 | try client.delete() 39 | return try Response(.ok, message: "Push-notifications registration is canceled") 40 | } 41 | } 42 | 43 | extension PushNotificationController: ResourceRepresentable { 44 | 45 | func makeResource() -> Resource { 46 | return Resource( 47 | store: subscribe, 48 | destroy: cancel 49 | ) 50 | } 51 | } 52 | 53 | extension PushNotificationController: EmptyInitializable { } 54 | -------------------------------------------------------------------------------- /Sources/App/Controllers/User/UserAuthorizationController.swift: -------------------------------------------------------------------------------- 1 | import HTTP 2 | import Vapor 3 | import Fluent 4 | 5 | final class UserAuthorizationController { 6 | 7 | private let fb: FacebookController 8 | private let vk: VkontakteController 9 | private let git: GithubController 10 | 11 | init(drop: Droplet) { 12 | fb = FacebookController(drop: drop) 13 | vk = VkontakteController(drop: drop) 14 | git = GithubController(drop: drop) 15 | } 16 | 17 | func store(_ request: Request) throws -> ResponseRepresentable { 18 | 19 | guard let token = request.json?[RequestKeys.token]?.string else { 20 | throw Abort(.badRequest, reason: "Can't get 'token' from request") 21 | } 22 | 23 | guard let social = request.json?[RequestKeys.social]?.string else { 24 | throw Abort(.badRequest, reason: "Can't get 'social' from request") 25 | } 26 | 27 | var user: User 28 | 29 | switch social { 30 | case Social.Nets.fb: 31 | user = try fb.createOrUpdateUserProfile(with: token) 32 | case Social.Nets.vk: 33 | guard let secret = request.json?[RequestKeys.secret]?.string else { 34 | throw Abort(.badRequest, reason: "Can't get 'secret' from request") 35 | } 36 | user = try vk.createOrUpdateUserProfile(use: token, secret: secret) 37 | case Social.Nets.github: 38 | guard let secret = request.json?[RequestKeys.secret]?.string else { 39 | throw Abort(.badRequest, reason: "Can't get 'secret' from request") 40 | } 41 | user = try git.createOrUpdateUserProfile(with: token, secret: secret) 42 | default: 43 | throw Abort(.badRequest, reason: "Wrong social id: \(social)") 44 | } 45 | 46 | if user.token == nil { 47 | user.createSession() 48 | } else { 49 | try user.updateSessionToken() 50 | } 51 | 52 | return user 53 | } 54 | 55 | } 56 | 57 | extension UserAuthorizationController: ResourceRepresentable { 58 | 59 | func makeResource() -> Resource { 60 | return Resource( 61 | store: store 62 | ) 63 | } 64 | } 65 | 66 | extension UserAuthorizationController { 67 | 68 | struct RequestKeys { 69 | static let token = "token" 70 | static let social = "social" 71 | static let secret = "secret" 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /Sources/App/Controllers/User/UserController.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import FluentProvider 3 | import Foundation 4 | import Multipart 5 | 6 | final class UserController { 7 | 8 | let photoController: PhotoController 9 | 10 | init(drop: Droplet) { 11 | photoController = PhotoController(drop: drop) 12 | } 13 | 14 | func show(_ request: Request, user: User) throws -> ResponseRepresentable { 15 | try updateSessionToken(for: user) 16 | return user 17 | } 18 | 19 | func update(_ request: Request, user: User) throws -> ResponseRepresentable { 20 | 21 | let photo = try updatePhoto(by: request, for: user) 22 | try user.update(for: request) 23 | if photo != nil { 24 | user.photo = photo 25 | } 26 | try user.save() 27 | try updateSessionToken(for: user) 28 | return user 29 | } 30 | 31 | func updateSessionToken(for user: User) throws { 32 | do { 33 | try user.updateSessionToken() 34 | } catch { 35 | throw Abort.badRequest 36 | } 37 | } 38 | } 39 | 40 | extension UserController: ResourceRepresentable { 41 | 42 | func makeResource() -> Resource { 43 | return Resource( 44 | show: show, 45 | update: update 46 | ) 47 | } 48 | } 49 | 50 | extension UserController: EmptyInitializable { 51 | convenience init() throws { 52 | try self.init() 53 | } 54 | } 55 | 56 | extension UserController { 57 | 58 | func updatePhoto(by request: Request, for user: User) throws -> String? { 59 | 60 | guard let userId = user.id?.string else { 61 | throw Abort.badRequest 62 | } 63 | 64 | // get photo from formData 65 | if 66 | let bytes = request.formData?[RequestKeys.photo]?.bytes, 67 | let filename = request.formData?[RequestKeys.photo]?.filename { 68 | try photoController.savePhoto(for: userId, photoBytes: bytes, filename: filename) 69 | return filename 70 | } 71 | 72 | // get photo from body as base64EncodedString 73 | if let photoString = request.json?[RequestKeys.photo]?.string { 74 | return try photoController.savePhoto(for: userId, photoAsString: photoString) 75 | } 76 | 77 | // get photo by url download 78 | if let photoURL = request.json?[RequestKeys.photoURL]?.string { 79 | return try photoController.downloadAndSavePhoto(for: userId, with: photoURL) 80 | } 81 | 82 | return nil 83 | } 84 | 85 | } 86 | 87 | extension UserController { 88 | 89 | struct RequestKeys { 90 | static let photo = "photo" 91 | static let photoURL = "photoURL" 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /Sources/App/Middleware/ClientMiddleware.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import HTTP 3 | 4 | final class ClientMiddleware: Middleware { 5 | let token: String 6 | init(_ token: String) { 7 | self.token = token 8 | } 9 | 10 | func respond(to request: Request, chainingTo next: Responder) throws -> Response { 11 | guard request.headers.clientToken == token else { 12 | return Response(status: .unauthorized) 13 | } 14 | 15 | return try next.respond(to: request) 16 | } 17 | } 18 | 19 | extension ClientMiddleware: ConfigInitializable { 20 | convenience init(config: Config) throws { 21 | let constants = Constants.Config.self 22 | if let token = config[constants.app, constants.clientToken]?.string.ifNotEmpty { 23 | self.init(token) 24 | } else { 25 | throw MiddlewareError.missingClientToken 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/App/Middleware/MiddlewareError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum MiddlewareError: Error { 4 | case missingClientToken 5 | case missingDomain 6 | } 7 | -------------------------------------------------------------------------------- /Sources/App/Middleware/PhotoURLMiddleware.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import HTTP 3 | 4 | final class PhotoURLMiddleware: Middleware { 5 | 6 | let domain: String 7 | 8 | init(_ domain: String) { 9 | self.domain = domain 10 | } 11 | 12 | func respond(to request: Request, chainingTo next: Responder) throws -> Response { 13 | let response = try next.respond(to: request) 14 | 15 | if let array = response.json?.array { 16 | // this is Event 17 | 18 | let arrayWithPhotoURL = try array.map { element -> StructuredData in 19 | var result = element 20 | 21 | if let photoWithPath = element[User.Keys.photoURL]?.string { 22 | let photoURL = "\(domain)/\(photoWithPath)" 23 | try result.set(User.Keys.photoURL, photoURL) 24 | } 25 | 26 | if let speakersPhoto = element[Event.Keys.speakersPhotos]?.array { 27 | 28 | let speakersPhotoURL = speakersPhoto.map { speackerPhoto -> String? in 29 | guard let photo = speackerPhoto.string else { 30 | return nil 31 | } 32 | 33 | let photoURL = "\(domain)/\(photo)" 34 | return photoURL 35 | } 36 | 37 | try result.set(Event.Keys.speakersPhotos, speakersPhotoURL) 38 | } 39 | 40 | return result.wrapped 41 | } 42 | 43 | response.json = JSON(.array(arrayWithPhotoURL)) 44 | return response 45 | } 46 | 47 | if let photoWithPath = response.json?[User.Keys.photoURL]?.string { 48 | let photoURL = "\(domain)/\(photoWithPath)" 49 | try response.json?.set(User.Keys.photoURL, photoURL) 50 | } 51 | 52 | return response 53 | } 54 | 55 | } 56 | 57 | extension PhotoURLMiddleware: ConfigInitializable { 58 | 59 | convenience init(config: Config) throws { 60 | let constants = Constants.Config.self 61 | if let domain = config[constants.app, constants.domain]?.string.ifNotEmpty { 62 | self.init(domain) 63 | } else { 64 | throw MiddlewareError.missingDomain 65 | } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /Sources/App/Models/Approval/Approval.generated.swift: -------------------------------------------------------------------------------- 1 | // Generated using Sourcery 0.10.1 — https://github.com/krzysztofzablocki/Sourcery 2 | // DO NOT EDIT 3 | 4 | import Vapor 5 | import FluentProvider 6 | 7 | extension Approval { 8 | static var entity: String = "approval" 9 | } 10 | 11 | extension Approval { 12 | 13 | struct Keys { 14 | static let id = "id" 15 | static let visitedEvents = "visited_events" 16 | static let skippedEvents = "skipped_events" 17 | static let periodInMonths = "period_in_months" 18 | } 19 | } 20 | 21 | extension Approval: Updateable { 22 | 23 | public static var updateableKeys: [UpdateableKey] { 24 | return [ 25 | UpdateableKey(Keys.visitedEvents, Int.self) { $0.visitedEvents = $1 }, 26 | UpdateableKey(Keys.skippedEvents, Int.self) { $0.skippedEvents = $1 }, 27 | UpdateableKey(Keys.periodInMonths, Int.self) { $0.periodInMonths = $1 } 28 | ] 29 | } 30 | } 31 | 32 | extension Approval: JSONInitializable { 33 | 34 | convenience init(json: JSON) throws { 35 | self.init( 36 | visitedEvents: try json.get(Keys.visitedEvents), 37 | skippedEvents: try json.get(Keys.skippedEvents), 38 | periodInMonths: try json.get(Keys.periodInMonths) 39 | ) 40 | } 41 | } 42 | 43 | extension Approval: Preparation { 44 | 45 | static func prepare(_ database: Database) throws { 46 | try database.create(self) { builder in 47 | builder.id() 48 | builder.int(Keys.visitedEvents) 49 | builder.int(Keys.skippedEvents) 50 | builder.int(Keys.periodInMonths) 51 | } 52 | } 53 | 54 | static func revert(_ database: Database) throws { 55 | try database.delete(self) 56 | } 57 | } 58 | 59 | extension Approval: JSONRepresentable { 60 | 61 | func makeJSON() throws -> JSON { 62 | var json = JSON() 63 | try json.set(Keys.id, id) 64 | try json.set(Keys.visitedEvents, visitedEvents) 65 | try json.set(Keys.skippedEvents, skippedEvents) 66 | try json.set(Keys.periodInMonths, periodInMonths) 67 | return json 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Sources/App/Models/Approval/Approval.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import FluentProvider 3 | 4 | // sourcery: AutoModelGeneratable 5 | // sourcery: toJSON, Preparation, fromJSON, Updateable 6 | final class Approval: Model { 7 | 8 | let storage = Storage() 9 | 10 | var visitedEvents: Int 11 | var skippedEvents: Int 12 | var periodInMonths: Int 13 | 14 | init(visitedEvents: Int = 2, skippedEvents: Int = 2, periodInMonths: Int = 6) { 15 | self.visitedEvents = visitedEvents 16 | self.skippedEvents = skippedEvents 17 | self.periodInMonths = periodInMonths 18 | } 19 | 20 | // sourcery:inline:auto:Approval.AutoModelGeneratable 21 | init(row: Row) throws { 22 | visitedEvents = try row.get(Keys.visitedEvents) 23 | skippedEvents = try row.get(Keys.skippedEvents) 24 | periodInMonths = try row.get(Keys.periodInMonths) 25 | } 26 | 27 | func makeRow() throws -> Row { 28 | var row = Row() 29 | try row.set(Keys.visitedEvents, visitedEvents) 30 | try row.set(Keys.skippedEvents, skippedEvents) 31 | try row.set(Keys.periodInMonths, periodInMonths) 32 | return row 33 | } 34 | // sourcery:end 35 | } 36 | -------------------------------------------------------------------------------- /Sources/App/Models/City/City.generated.swift: -------------------------------------------------------------------------------- 1 | // Generated using Sourcery 0.10.1 — https://github.com/krzysztofzablocki/Sourcery 2 | // DO NOT EDIT 3 | 4 | import Vapor 5 | import FluentProvider 6 | 7 | extension City { 8 | static var entity: String = "city" 9 | } 10 | 11 | extension City { 12 | 13 | struct Keys { 14 | static let id = "id" 15 | static let cityName = "city_name" 16 | } 17 | } 18 | 19 | extension City: Preparation { 20 | 21 | static func prepare(_ database: Database) throws { 22 | try database.create(self) { builder in 23 | builder.id() 24 | builder.string(Keys.cityName) 25 | } 26 | } 27 | 28 | static func revert(_ database: Database) throws { 29 | try database.delete(self) 30 | } 31 | } 32 | 33 | extension City: JSONRepresentable { 34 | 35 | func makeJSON() throws -> JSON { 36 | var json = JSON() 37 | try json.set(Keys.id, id) 38 | try json.set(Keys.cityName, cityName) 39 | return json 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/App/Models/City/City.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import FluentProvider 3 | 4 | // sourcery: AutoModelGeneratable 5 | // sourcery: toJSON, Preparation 6 | final class City: Model { 7 | 8 | let storage = Storage() 9 | 10 | var cityName: String 11 | 12 | init(cityName: String) { 13 | self.cityName = cityName 14 | } 15 | 16 | // sourcery:inline:auto:City.AutoModelGeneratable 17 | init(row: Row) throws { 18 | cityName = try row.get(Keys.cityName) 19 | } 20 | 21 | func makeRow() throws -> Row { 22 | var row = Row() 23 | try row.set(Keys.cityName, cityName) 24 | return row 25 | } 26 | // sourcery:end 27 | } 28 | -------------------------------------------------------------------------------- /Sources/App/Models/Client/Client.generated.swift: -------------------------------------------------------------------------------- 1 | // Generated using Sourcery 0.10.1 — https://github.com/krzysztofzablocki/Sourcery 2 | // DO NOT EDIT 3 | 4 | import Vapor 5 | import FluentProvider 6 | 7 | extension Client { 8 | static var entity: String = "client" 9 | } 10 | 11 | extension Client { 12 | 13 | struct Keys { 14 | static let id = "id" 15 | static let userId = "user_id" 16 | static let pushToken = "push_token" 17 | } 18 | } 19 | 20 | extension Client: ResponseRepresentable { } 21 | 22 | extension Client: JSONInitializable { 23 | 24 | convenience init(json: JSON) throws { 25 | self.init( 26 | pushToken: try json.get(Keys.pushToken), 27 | userId: try? json.get(Keys.userId) 28 | ) 29 | } 30 | } 31 | 32 | extension Client: Timestampable { } 33 | 34 | extension Client: Updateable { 35 | 36 | public static var updateableKeys: [UpdateableKey] { 37 | return [ 38 | UpdateableKey(Keys.userId, Identifier.self) { $0.userId = $1 }, 39 | UpdateableKey(Keys.pushToken, String.self) { $0.pushToken = $1 } 40 | ] 41 | } 42 | } 43 | 44 | extension Client: Preparation { 45 | 46 | static func prepare(_ database: Database) throws { 47 | try database.create(self) { builder in 48 | builder.id() 49 | builder.foreignId(for: User.self, optional: true, unique: false, foreignIdKey: Keys.userId, foreignKeyName: self.entity + "_" + Keys.userId) 50 | builder.string(Keys.pushToken) 51 | } 52 | } 53 | 54 | static func revert(_ database: Database) throws { 55 | try database.delete(self) 56 | } 57 | } 58 | 59 | extension Client: JSONRepresentable { 60 | 61 | func makeJSON() throws -> JSON { 62 | var json = JSON() 63 | try json.set(Keys.id, id) 64 | try? json.set(Keys.userId, userId) 65 | try json.set(Keys.pushToken, pushToken) 66 | return json 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Sources/App/Models/Client/Client.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import FluentProvider 3 | import HTTP 4 | 5 | // sourcery: AutoModelGeneratable 6 | // sourcery: fromJSON, toJSON, Preparation, Updateable, ResponseRepresentable, Timestampable 7 | final class Client: Model { 8 | 9 | let storage = Storage() 10 | 11 | var userId: Identifier? 12 | var pushToken: String 13 | 14 | init(pushToken: String, userId: Identifier?) { 15 | self.pushToken = pushToken 16 | self.userId = userId 17 | } 18 | 19 | // sourcery:inline:auto:Client.AutoModelGeneratable 20 | init(row: Row) throws { 21 | userId = try? row.get(Keys.userId) 22 | pushToken = try row.get(Keys.pushToken) 23 | } 24 | 25 | func makeRow() throws -> Row { 26 | var row = Row() 27 | try? row.set(Keys.userId, userId) 28 | try row.set(Keys.pushToken, pushToken) 29 | return row 30 | } 31 | // sourcery:end 32 | } 33 | 34 | extension Client { 35 | 36 | convenience init(request: Request) throws { 37 | self.init( 38 | pushToken: try request.json!.get(Keys.pushToken), 39 | userId: try request.user().id 40 | ) 41 | } 42 | 43 | static func returnIfExists(request: Request) throws -> Client? { 44 | return try Client.makeQuery() 45 | .filter(Keys.userId, try request.user().id) 46 | .first() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/App/Models/Content/Content.generated.swift: -------------------------------------------------------------------------------- 1 | // Generated using Sourcery 0.10.1 — https://github.com/krzysztofzablocki/Sourcery 2 | // DO NOT EDIT 3 | 4 | import Vapor 5 | import FluentProvider 6 | 7 | extension Content { 8 | static var entity: String = "content" 9 | } 10 | 11 | extension Content { 12 | 13 | struct Keys { 14 | static let id = "id" 15 | static let speechId = "speech_id" 16 | static let type = "type" 17 | static let title = "title" 18 | static let description = "description" 19 | static let link = "link" 20 | } 21 | } 22 | 23 | extension Content { 24 | 25 | enum ContentType: String { 26 | case video 27 | case slide 28 | 29 | var string: String { 30 | return String(describing: self) 31 | } 32 | 33 | init(_ string: String) { 34 | self = ContentType(rawValue: string) ?? .slide 35 | } 36 | } 37 | } 38 | 39 | extension Content: Preparation { 40 | 41 | static func prepare(_ database: Database) throws { 42 | try database.create(self) { builder in 43 | builder.id() 44 | builder.foreignId(for: Speech.self, optional: false, unique: false, foreignIdKey: Keys.speechId, foreignKeyName: self.entity + "_" + Keys.speechId) 45 | builder.enum(Keys.type, options: ["video", "slide"]) 46 | builder.string(Keys.title) 47 | builder.string(Keys.description) 48 | builder.string(Keys.link) 49 | } 50 | } 51 | 52 | static func revert(_ database: Database) throws { 53 | try database.delete(self) 54 | } 55 | } 56 | 57 | extension Content: JSONRepresentable { 58 | 59 | func makeJSON() throws -> JSON { 60 | var json = JSON() 61 | try json.set(Keys.id, id) 62 | try json.set(Keys.type, type.string) 63 | try json.set(Keys.title, title) 64 | try json.set(Keys.description, description) 65 | try json.set(Keys.link, link) 66 | return json 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Sources/App/Models/Content/Content.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import FluentProvider 3 | 4 | // sourcery: AutoModelGeneratable 5 | // sourcery: toJSON, Preparation 6 | final class Content: Model { 7 | 8 | let storage = Storage() 9 | 10 | // sourcery: ignoreInJSON 11 | var speechId: Identifier 12 | // sourcery: enum, video, slide 13 | var type: ContentType 14 | var title: String 15 | var description: String 16 | var link: String 17 | 18 | init(speechId: Identifier, 19 | title: String, 20 | description: String, 21 | link: String, 22 | type: ContentType) { 23 | self.speechId = speechId 24 | self.title = title 25 | self.description = description 26 | self.link = link 27 | self.type = type 28 | } 29 | 30 | // sourcery:inline:auto:Content.AutoModelGeneratable 31 | init(row: Row) throws { 32 | speechId = try row.get(Keys.speechId) 33 | type = ContentType(try row.get(Keys.type)) 34 | title = try row.get(Keys.title) 35 | description = try row.get(Keys.description) 36 | link = try row.get(Keys.link) 37 | } 38 | 39 | func makeRow() throws -> Row { 40 | var row = Row() 41 | try row.set(Keys.speechId, speechId) 42 | try row.set(Keys.type, type.string) 43 | try row.set(Keys.title, title) 44 | try row.set(Keys.description, description) 45 | try row.set(Keys.link, link) 46 | return row 47 | } 48 | // sourcery:end 49 | } 50 | 51 | extension Content { 52 | 53 | func speech() throws -> Speech? { 54 | return try parent(id: speechId).get() 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/App/Models/Creator/Creator.generated.swift: -------------------------------------------------------------------------------- 1 | // Generated using Sourcery 0.10.1 — https://github.com/krzysztofzablocki/Sourcery 2 | // DO NOT EDIT 3 | 4 | import Vapor 5 | import FluentProvider 6 | 7 | extension Creator { 8 | static var entity: String = "creator" 9 | } 10 | 11 | extension Creator { 12 | 13 | struct Keys { 14 | static let id = "id" 15 | static let userId = "user_id" 16 | static let position = "position" 17 | static let info = "info" 18 | static let url = "url" 19 | static let active = "active" 20 | static let photoURL = "photo_url" 21 | } 22 | } 23 | 24 | extension Creator: ResponseRepresentable { } 25 | 26 | extension Creator: Preparation { 27 | 28 | static func prepare(_ database: Database) throws { 29 | try database.create(self) { builder in 30 | builder.id() 31 | builder.foreignId(for: User.self, optional: false, unique: false, foreignIdKey: Keys.userId, foreignKeyName: self.entity + "_" + Keys.userId) 32 | builder.int(Keys.position) 33 | builder.string(Keys.info) 34 | builder.string(Keys.url) 35 | builder.bool(Keys.active) 36 | } 37 | } 38 | 39 | static func revert(_ database: Database) throws { 40 | try database.delete(self) 41 | } 42 | } 43 | 44 | extension Creator: JSONRepresentable { 45 | 46 | func makeJSON() throws -> JSON { 47 | var json = JSON() 48 | try json.set(Keys.id, id) 49 | try json.set(Keys.userId, userId) 50 | try json.set(Keys.position, position) 51 | try json.set(Keys.info, info) 52 | try json.set(Keys.url, url) 53 | try json.set(Keys.active, active) 54 | try json.set(Keys.photoURL, photoURL()!) 55 | return json 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sources/App/Models/Creator/Creator.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import FluentProvider 3 | 4 | // sourcery: AutoModelGeneratable 5 | // sourcery: toJSON, Preparation, ResponseRepresentable 6 | final class Creator: Model { 7 | 8 | let storage = Storage() 9 | 10 | var userId: Identifier 11 | var position: Int 12 | var info: String 13 | var url: String 14 | var active: Bool 15 | 16 | init(userId: Identifier, 17 | position: Int, 18 | info: String, 19 | url: String, 20 | active: Bool) { 21 | self.userId = userId 22 | self.position = position 23 | self.info = info 24 | self.url = url 25 | self.active = active 26 | } 27 | 28 | // sourcery:inline:auto:Creator.AutoModelGeneratable 29 | init(row: Row) throws { 30 | userId = try row.get(Keys.userId) 31 | position = try row.get(Keys.position) 32 | info = try row.get(Keys.info) 33 | url = try row.get(Keys.url) 34 | active = try row.get(Keys.active) 35 | } 36 | 37 | func makeRow() throws -> Row { 38 | var row = Row() 39 | try row.set(Keys.userId, userId) 40 | try row.set(Keys.position, position) 41 | try row.set(Keys.info, info) 42 | try row.set(Keys.url, url) 43 | try row.set(Keys.active, active) 44 | return row 45 | } 46 | // sourcery:end 47 | } 48 | 49 | extension Creator { 50 | 51 | func user() throws -> User? { 52 | return try parent(id: userId).get() 53 | } 54 | 55 | // sourcery: nestedJSONField 56 | func photoURL() -> String? { 57 | guard let user = try? user() else { 58 | return nil 59 | } 60 | return user?.photoURL() 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /Sources/App/Models/Event/Event.generated.swift: -------------------------------------------------------------------------------- 1 | // Generated using Sourcery 0.10.1 — https://github.com/krzysztofzablocki/Sourcery 2 | // DO NOT EDIT 3 | 4 | import Vapor 5 | import FluentProvider 6 | 7 | extension Event { 8 | static var entity: String = "event" 9 | } 10 | 11 | extension Event { 12 | 13 | struct Keys { 14 | static let id = "id" 15 | static let placeId = "place_id" 16 | static let title = "title" 17 | static let description = "description" 18 | static let photo = "photo" 19 | static let isRegistrationOpen = "is_registration_open" 20 | static let startDate = "start_date" 21 | static let endDate = "end_date" 22 | static let hide = "hide" 23 | static let photoURL = "photo_url" 24 | static let place = "place" 25 | static let status = "status" 26 | static let speakersPhotos = "speakers_photos" 27 | } 28 | } 29 | 30 | extension Event: Updateable { 31 | 32 | public static var updateableKeys: [UpdateableKey] { 33 | return [ 34 | UpdateableKey(Keys.placeId, Identifier.self) { $0.placeId = $1 }, 35 | UpdateableKey(Keys.title, String.self) { $0.title = $1 }, 36 | UpdateableKey(Keys.description, String.self) { $0.description = $1 }, 37 | UpdateableKey(Keys.photo, String.self) { $0.photo = $1 }, 38 | UpdateableKey(Keys.isRegistrationOpen, Bool.self) { $0.isRegistrationOpen = $1 }, 39 | UpdateableKey(Keys.startDate, Date.self) { $0.startDate = $1 }, 40 | UpdateableKey(Keys.endDate, Date.self) { $0.endDate = $1 }, 41 | UpdateableKey(Keys.hide, Bool.self) { $0.hide = $1 } 42 | ] 43 | } 44 | } 45 | 46 | extension Event: Preparation { 47 | 48 | static func prepare(_ database: Database) throws { 49 | try database.create(self) { builder in 50 | builder.id() 51 | builder.foreignId(for: Place.self, optional: false, unique: false, foreignIdKey: Keys.placeId, foreignKeyName: self.entity + "_" + Keys.placeId) 52 | builder.string(Keys.title) 53 | builder.string(Keys.description) 54 | builder.string(Keys.photo, optional: true) 55 | builder.bool(Keys.isRegistrationOpen) 56 | builder.date(Keys.startDate) 57 | builder.date(Keys.endDate) 58 | builder.bool(Keys.hide) 59 | } 60 | } 61 | 62 | static func revert(_ database: Database) throws { 63 | try database.delete(self) 64 | } 65 | } 66 | 67 | extension Event: Timestampable { } 68 | -------------------------------------------------------------------------------- /Sources/App/Models/EventReg/EventReg.generated.swift: -------------------------------------------------------------------------------- 1 | // Generated using Sourcery 0.10.1 — https://github.com/krzysztofzablocki/Sourcery 2 | // DO NOT EDIT 3 | 4 | import Vapor 5 | import FluentProvider 6 | 7 | extension EventReg { 8 | static var entity: String = "event_reg" 9 | } 10 | 11 | extension EventReg { 12 | 13 | struct Keys { 14 | static let id = "id" 15 | static let regFormId = "reg_form_id" 16 | static let userId = "user_id" 17 | static let status = "status" 18 | } 19 | } 20 | 21 | extension EventReg { 22 | 23 | enum RegistrationStatus: String { 24 | case approved 25 | case rejected 26 | case waiting 27 | case canceled 28 | case skipped 29 | 30 | var string: String { 31 | return String(describing: self) 32 | } 33 | 34 | init(_ string: String) { 35 | self = RegistrationStatus(rawValue: string) ?? .skipped 36 | } 37 | } 38 | } 39 | 40 | extension EventReg: ResponseRepresentable { } 41 | 42 | extension EventReg: Updateable { 43 | 44 | public static var updateableKeys: [UpdateableKey] { 45 | return [ 46 | UpdateableKey(Keys.regFormId, Identifier.self) { $0.regFormId = $1 }, 47 | UpdateableKey(Keys.userId, Identifier.self) { $0.userId = $1 }, 48 | UpdateableKey(Keys.status, String.self) { $0.status = RegistrationStatus($1) } 49 | ] 50 | } 51 | } 52 | 53 | extension EventReg: Preparation { 54 | 55 | static func prepare(_ database: Database) throws { 56 | try database.create(self) { builder in 57 | builder.id() 58 | builder.foreignId(for: RegForm.self, optional: false, unique: false, foreignIdKey: Keys.regFormId, foreignKeyName: self.entity + "_" + Keys.regFormId) 59 | builder.foreignId(for: User.self, optional: false, unique: false, foreignIdKey: Keys.userId, foreignKeyName: self.entity + "_" + Keys.userId) 60 | builder.enum(Keys.status, options: ["approved", "rejected", "waiting", "canceled", "skipped"]) 61 | } 62 | } 63 | 64 | static func revert(_ database: Database) throws { 65 | try database.delete(self) 66 | } 67 | } 68 | 69 | extension EventReg: JSONRepresentable { 70 | 71 | func makeJSON() throws -> JSON { 72 | var json = JSON() 73 | try json.set(Keys.id, id) 74 | try json.set(Keys.regFormId, regFormId) 75 | try json.set(Keys.userId, userId) 76 | try json.set(Keys.status, status.string) 77 | return json 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Sources/App/Models/EventReg/EventReg.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import FluentProvider 3 | 4 | // sourcery: AutoModelGeneratable 5 | // sourcery: toJSON, Preparation, Updateable, ResponseRepresentable 6 | final class EventReg: Model { 7 | 8 | let storage = Storage() 9 | 10 | var regFormId: Identifier 11 | var userId: Identifier 12 | // sourcery: enum, waiting, rejected, approved, canceled, skipped 13 | var status: RegistrationStatus = .waiting 14 | 15 | init(regFormId: Identifier, 16 | userId: Identifier, 17 | status: RegistrationStatus = .waiting) { 18 | self.regFormId = regFormId 19 | self.userId = userId 20 | self.status = status 21 | } 22 | 23 | // sourcery:inline:auto:EventReg.AutoModelGeneratable 24 | init(row: Row) throws { 25 | regFormId = try row.get(Keys.regFormId) 26 | userId = try row.get(Keys.userId) 27 | status = RegistrationStatus(try row.get(Keys.status)) 28 | } 29 | 30 | func makeRow() throws -> Row { 31 | var row = Row() 32 | try row.set(Keys.regFormId, regFormId) 33 | try row.set(Keys.userId, userId) 34 | try row.set(Keys.status, status.string) 35 | return row 36 | } 37 | // sourcery:end 38 | } 39 | 40 | extension EventReg { 41 | 42 | func regForm() throws -> RegForm? { 43 | return try children().first() 44 | } 45 | 46 | func eventRegAnswers() throws -> [EventRegAnswer] { 47 | return try children().all() 48 | } 49 | 50 | static func duplicationCheck(regFormId: Identifier, userId: Identifier) throws -> Bool { 51 | return try EventReg.makeQuery() 52 | .filter(EventReg.Keys.regFormId, regFormId) 53 | .filter(EventReg.Keys.userId, userId) 54 | .filter(EventReg.Keys.status, in: [ 55 | EventReg.RegistrationStatus.waiting.string, 56 | EventReg.RegistrationStatus.approved.string]) 57 | .all().isEmpty 58 | } 59 | 60 | func willDelete() throws { 61 | try eventRegAnswers() 62 | .forEach { try $0.delete() } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /Sources/App/Models/EventRegAnswer/EventRegAnswer.generated.swift: -------------------------------------------------------------------------------- 1 | // Generated using Sourcery 0.10.1 — https://github.com/krzysztofzablocki/Sourcery 2 | // DO NOT EDIT 3 | 4 | import Vapor 5 | import FluentProvider 6 | 7 | extension EventRegAnswer { 8 | static var entity: String = "event_reg_answer" 9 | } 10 | 11 | extension EventRegAnswer { 12 | 13 | struct Keys { 14 | static let id = "id" 15 | static let eventRegId = "event_reg_id" 16 | static let regFieldId = "reg_field_id" 17 | static let regFieldAnswerId = "reg_field_answer_id" 18 | static let answerValue = "answer_value" 19 | } 20 | } 21 | 22 | extension EventRegAnswer: JSONInitializable { 23 | 24 | convenience init(json: JSON) throws { 25 | self.init( 26 | eventRegId: try json.get(Keys.eventRegId), 27 | regFieldId: try json.get(Keys.regFieldId), 28 | regFieldAnswerId: try json.get(Keys.regFieldAnswerId), 29 | answerValue: try json.get(Keys.answerValue) 30 | ) 31 | } 32 | } 33 | 34 | extension EventRegAnswer: Preparation { 35 | 36 | static func prepare(_ database: Database) throws { 37 | try database.create(self) { builder in 38 | builder.id() 39 | builder.foreignId(for: EventReg.self, optional: false, unique: false, foreignIdKey: Keys.eventRegId, foreignKeyName: self.entity + "_" + Keys.eventRegId) 40 | builder.foreignId(for: RegField.self, optional: false, unique: false, foreignIdKey: Keys.regFieldId, foreignKeyName: self.entity + "_" + Keys.regFieldId) 41 | builder.foreignId(for: RegFieldAnswer.self, optional: false, unique: false, foreignIdKey: Keys.regFieldAnswerId, foreignKeyName: self.entity + "_" + Keys.regFieldAnswerId) 42 | builder.string(Keys.answerValue) 43 | } 44 | } 45 | 46 | static func revert(_ database: Database) throws { 47 | try database.delete(self) 48 | } 49 | } 50 | 51 | extension EventRegAnswer: JSONRepresentable { 52 | 53 | func makeJSON() throws -> JSON { 54 | var json = JSON() 55 | try json.set(Keys.id, id) 56 | try json.set(Keys.eventRegId, eventRegId) 57 | try json.set(Keys.regFieldId, regFieldId) 58 | try json.set(Keys.regFieldAnswerId, regFieldAnswerId) 59 | try json.set(Keys.answerValue, answerValue) 60 | return json 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Sources/App/Models/EventRegAnswer/EventRegAnswer.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import FluentProvider 3 | 4 | // sourcery: AutoModelGeneratable 5 | // sourcery: fromJSON, toJSON, Preparation 6 | final class EventRegAnswer: Model { 7 | 8 | let storage = Storage() 9 | var eventRegId: Identifier 10 | var regFieldId: Identifier 11 | var regFieldAnswerId: Identifier 12 | var answerValue: String 13 | 14 | init(eventRegId: Identifier, 15 | regFieldId: Identifier, 16 | regFieldAnswerId: Identifier, 17 | answerValue: String) { 18 | self.eventRegId = eventRegId 19 | self.regFieldId = regFieldId 20 | self.regFieldAnswerId = regFieldAnswerId 21 | self.answerValue = answerValue 22 | } 23 | 24 | // sourcery:inline:auto:EventRegAnswer.AutoModelGeneratable 25 | init(row: Row) throws { 26 | eventRegId = try row.get(Keys.eventRegId) 27 | regFieldId = try row.get(Keys.regFieldId) 28 | regFieldAnswerId = try row.get(Keys.regFieldAnswerId) 29 | answerValue = try row.get(Keys.answerValue) 30 | } 31 | 32 | func makeRow() throws -> Row { 33 | var row = Row() 34 | try row.set(Keys.eventRegId, eventRegId) 35 | try row.set(Keys.regFieldId, regFieldId) 36 | try row.set(Keys.regFieldAnswerId, regFieldAnswerId) 37 | try row.set(Keys.answerValue, answerValue) 38 | return row 39 | } 40 | // sourcery:end 41 | } 42 | 43 | extension EventRegAnswer { 44 | 45 | func eventRegistration() throws -> EventReg? { 46 | return try parent(id: eventRegId).get() 47 | } 48 | 49 | func answer() throws -> RegFieldAnswer? { 50 | return try children().first() 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /Sources/App/Models/GiveSpeech/GiveSpeech.generated.swift: -------------------------------------------------------------------------------- 1 | // Generated using Sourcery 0.10.1 — https://github.com/krzysztofzablocki/Sourcery 2 | // DO NOT EDIT 3 | 4 | import Vapor 5 | import FluentProvider 6 | 7 | extension GiveSpeech { 8 | static var entity: String = "give_speech" 9 | } 10 | 11 | extension GiveSpeech { 12 | 13 | struct Keys { 14 | static let id = "id" 15 | static let userId = "user_id" 16 | static let title = "title" 17 | static let description = "description" 18 | } 19 | } 20 | 21 | extension GiveSpeech: ResponseRepresentable { } 22 | 23 | extension GiveSpeech: JSONInitializable { 24 | 25 | convenience init(json: JSON) throws { 26 | self.init( 27 | title: try json.get(Keys.title), 28 | description: try json.get(Keys.description), 29 | userId: try json.get(Keys.userId) 30 | ) 31 | } 32 | } 33 | 34 | extension GiveSpeech: Preparation { 35 | 36 | static func prepare(_ database: Database) throws { 37 | try database.create(self) { builder in 38 | builder.id() 39 | builder.foreignId(for: User.self, optional: false, unique: false, foreignIdKey: Keys.userId, foreignKeyName: self.entity + "_" + Keys.userId) 40 | builder.string(Keys.title) 41 | builder.string(Keys.description) 42 | } 43 | } 44 | 45 | static func revert(_ database: Database) throws { 46 | try database.delete(self) 47 | } 48 | } 49 | 50 | extension GiveSpeech: JSONRepresentable { 51 | 52 | func makeJSON() throws -> JSON { 53 | var json = JSON() 54 | try json.set(Keys.id, id) 55 | try json.set(Keys.userId, userId) 56 | try json.set(Keys.title, title) 57 | try json.set(Keys.description, description) 58 | return json 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Sources/App/Models/GiveSpeech/GiveSpeech.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import FluentProvider 3 | 4 | // sourcery: AutoModelGeneratable 5 | // sourcery: toJSON, fromJSON, Preparation, ResponseRepresentable 6 | final class GiveSpeech: Model { 7 | 8 | let storage = Storage() 9 | 10 | var userId: Identifier 11 | var title: String 12 | var description: String 13 | 14 | init(title: String, 15 | description: String, 16 | userId: Identifier) { 17 | self.title = title 18 | self.description = description 19 | self.userId = userId 20 | } 21 | 22 | // sourcery:inline:auto:GiveSpeech.AutoModelGeneratable 23 | init(row: Row) throws { 24 | userId = try row.get(Keys.userId) 25 | title = try row.get(Keys.title) 26 | description = try row.get(Keys.description) 27 | } 28 | 29 | func makeRow() throws -> Row { 30 | var row = Row() 31 | try row.set(Keys.userId, userId) 32 | try row.set(Keys.title, title) 33 | try row.set(Keys.description, description) 34 | return row 35 | } 36 | // sourcery:end 37 | } 38 | 39 | extension GiveSpeech { 40 | 41 | func user() throws -> User? { 42 | return try parent(id: userId).get() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/App/Models/Heartbeat/Heartbeat.generated.swift: -------------------------------------------------------------------------------- 1 | // Generated using Sourcery 0.10.1 — https://github.com/krzysztofzablocki/Sourcery 2 | // DO NOT EDIT 3 | 4 | import Vapor 5 | import FluentProvider 6 | 7 | extension Heartbeat { 8 | static var entity: String = "heartbeat" 9 | } 10 | 11 | extension Heartbeat { 12 | 13 | struct Keys { 14 | static let id = "id" 15 | static let beat = "beat" 16 | } 17 | } 18 | 19 | extension Heartbeat: JSONInitializable { 20 | 21 | convenience init(json: JSON) throws { 22 | self.init( 23 | beat: try json.get(Keys.beat) 24 | ) 25 | } 26 | } 27 | 28 | extension Heartbeat: Preparation { 29 | 30 | static func prepare(_ database: Database) throws { 31 | try database.create(self) { builder in 32 | builder.id() 33 | builder.int(Keys.beat) 34 | } 35 | } 36 | 37 | static func revert(_ database: Database) throws { 38 | try database.delete(self) 39 | } 40 | } 41 | 42 | extension Heartbeat: JSONRepresentable { 43 | 44 | func makeJSON() throws -> JSON { 45 | var json = JSON() 46 | try json.set(Keys.id, id) 47 | try json.set(Keys.beat, beat) 48 | return json 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/App/Models/Heartbeat/Heartbeat.swift: -------------------------------------------------------------------------------- 1 | import FluentProvider 2 | 3 | // sourcery: AutoModelGeneratable 4 | // sourcery: fromJSON, toJSON, Preparation 5 | final class Heartbeat: Model { 6 | let storage = Storage() 7 | 8 | var beat: Int 9 | 10 | init(beat: Int) { 11 | self.beat = beat 12 | } 13 | 14 | // sourcery:inline:auto:Heartbeat.AutoModelGeneratable 15 | init(row: Row) throws { 16 | beat = try row.get(Keys.beat) 17 | } 18 | 19 | func makeRow() throws -> Row { 20 | var row = Row() 21 | try row.set(Keys.beat, beat) 22 | return row 23 | } 24 | // sourcery:end 25 | } 26 | -------------------------------------------------------------------------------- /Sources/App/Models/Place/Place.generated.swift: -------------------------------------------------------------------------------- 1 | // Generated using Sourcery 0.10.1 — https://github.com/krzysztofzablocki/Sourcery 2 | // DO NOT EDIT 3 | 4 | import Vapor 5 | import FluentProvider 6 | 7 | extension Place { 8 | static var entity: String = "place" 9 | } 10 | 11 | extension Place { 12 | 13 | struct Keys { 14 | static let id = "id" 15 | static let cityId = "city_id" 16 | static let title = "title" 17 | static let address = "address" 18 | static let description = "description" 19 | static let latitude = "latitude" 20 | static let longitude = "longitude" 21 | static let city = "city" 22 | } 23 | } 24 | 25 | extension Place: Preparation { 26 | 27 | static func prepare(_ database: Database) throws { 28 | try database.create(self) { builder in 29 | builder.id() 30 | builder.foreignId(for: City.self, optional: false, unique: false, foreignIdKey: Keys.cityId, foreignKeyName: self.entity + "_" + Keys.cityId) 31 | builder.string(Keys.title) 32 | builder.string(Keys.address) 33 | builder.string(Keys.description) 34 | builder.double(Keys.latitude) 35 | builder.double(Keys.longitude) 36 | } 37 | } 38 | 39 | static func revert(_ database: Database) throws { 40 | try database.delete(self) 41 | } 42 | } 43 | 44 | extension Place: JSONRepresentable { 45 | 46 | func makeJSON() throws -> JSON { 47 | var json = JSON() 48 | try json.set(Keys.id, id) 49 | try json.set(Keys.title, title) 50 | try json.set(Keys.address, address) 51 | try json.set(Keys.description, description) 52 | try json.set(Keys.latitude, latitude) 53 | try json.set(Keys.longitude, longitude) 54 | try json.set(Keys.city, city()?.makeJSON()) 55 | return json 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sources/App/Models/Place/Place.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import FluentProvider 3 | 4 | // sourcery: AutoModelGeneratable 5 | // sourcery: toJSON, Preparation 6 | final class Place: Model { 7 | 8 | let storage = Storage() 9 | 10 | // sourcery: ignoreInJSON 11 | var cityId: Identifier 12 | var title: String 13 | var address: String 14 | var description: String 15 | var latitude: Double 16 | var longitude: Double 17 | 18 | init(title: String, 19 | address: String, 20 | description: String, 21 | latitude: Double, 22 | longitude: Double, 23 | cityId: Identifier) { 24 | self.title = title 25 | self.address = address 26 | self.description = description 27 | self.latitude = latitude 28 | self.longitude = longitude 29 | self.cityId = cityId 30 | } 31 | 32 | // sourcery:inline:auto:Place.AutoModelGeneratable 33 | init(row: Row) throws { 34 | cityId = try row.get(Keys.cityId) 35 | title = try row.get(Keys.title) 36 | address = try row.get(Keys.address) 37 | description = try row.get(Keys.description) 38 | latitude = try row.get(Keys.latitude) 39 | longitude = try row.get(Keys.longitude) 40 | } 41 | 42 | func makeRow() throws -> Row { 43 | var row = Row() 44 | try row.set(Keys.cityId, cityId) 45 | try row.set(Keys.title, title) 46 | try row.set(Keys.address, address) 47 | try row.set(Keys.description, description) 48 | try row.set(Keys.latitude, latitude) 49 | try row.set(Keys.longitude, longitude) 50 | return row 51 | } 52 | // sourcery:end 53 | } 54 | 55 | extension Place { 56 | 57 | // sourcery: nestedJSONRepresentableField 58 | func city() throws -> City? { 59 | return try parent(id: cityId).get() 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Sources/App/Models/RegField/RegField.generated.swift: -------------------------------------------------------------------------------- 1 | // Generated using Sourcery 0.10.1 — https://github.com/krzysztofzablocki/Sourcery 2 | // DO NOT EDIT 3 | 4 | import Vapor 5 | import FluentProvider 6 | 7 | extension RegField { 8 | static var entity: String = "reg_field" 9 | } 10 | 11 | extension RegField { 12 | 13 | struct Keys { 14 | static let id = "id" 15 | static let regFormId = "reg_form_id" 16 | static let required = "required" 17 | static let type = "type" 18 | static let name = "name" 19 | static let placeholder = "placeholder" 20 | static let defaultValue = "default_value" 21 | static let fieldAnswers = "field_answers" 22 | } 23 | } 24 | 25 | extension RegField { 26 | 27 | enum FieldType: String { 28 | case checkbox 29 | case radio 30 | case string 31 | 32 | var string: String { 33 | return String(describing: self) 34 | } 35 | 36 | init(_ string: String) { 37 | self = FieldType(rawValue: string) ?? .string 38 | } 39 | } 40 | } 41 | 42 | extension RegField: Updateable { 43 | 44 | public static var updateableKeys: [UpdateableKey] { 45 | return [ 46 | UpdateableKey(Keys.regFormId, Identifier.self) { $0.regFormId = $1 }, 47 | UpdateableKey(Keys.required, Bool.self) { $0.required = $1 }, 48 | UpdateableKey(Keys.type, String.self) { $0.type = FieldType($1) }, 49 | UpdateableKey(Keys.name, String.self) { $0.name = $1 }, 50 | UpdateableKey(Keys.placeholder, String.self) { $0.placeholder = $1 }, 51 | UpdateableKey(Keys.defaultValue, String.self) { $0.defaultValue = $1 } 52 | ] 53 | } 54 | } 55 | 56 | extension RegField: JSONInitializable { 57 | 58 | convenience init(json: JSON) throws { 59 | self.init( 60 | regFormId: try json.get(Keys.regFormId), 61 | required: try json.get(Keys.required), 62 | name: try json.get(Keys.name), 63 | type: FieldType(try json.get(Keys.type)), 64 | placeholder: try json.get(Keys.placeholder), 65 | defaultValue: try json.get(Keys.defaultValue) 66 | ) 67 | } 68 | } 69 | 70 | extension RegField: Preparation { 71 | 72 | static func prepare(_ database: Database) throws { 73 | try database.create(self) { builder in 74 | builder.id() 75 | builder.foreignId(for: RegForm.self, optional: false, unique: false, foreignIdKey: Keys.regFormId, foreignKeyName: self.entity + "_" + Keys.regFormId) 76 | builder.bool(Keys.required) 77 | builder.enum(Keys.type, options: ["checkbox", "radio", "string"]) 78 | builder.string(Keys.name) 79 | builder.string(Keys.placeholder) 80 | builder.string(Keys.defaultValue) 81 | } 82 | } 83 | 84 | static func revert(_ database: Database) throws { 85 | try database.delete(self) 86 | } 87 | } 88 | 89 | extension RegField: JSONRepresentable { 90 | 91 | func makeJSON() throws -> JSON { 92 | var json = JSON() 93 | try json.set(Keys.id, id) 94 | try json.set(Keys.required, required) 95 | try json.set(Keys.type, type.string) 96 | try json.set(Keys.name, name) 97 | try json.set(Keys.placeholder, placeholder) 98 | try json.set(Keys.defaultValue, defaultValue) 99 | try json.set(Keys.fieldAnswers, fieldAnswers().makeJSON()) 100 | return json 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Sources/App/Models/RegField/RegField.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import FluentProvider 3 | 4 | // sourcery: AutoModelGeneratable 5 | // sourcery: toJSON, Preparation, fromJSON, Updateable 6 | final class RegField: Model { 7 | 8 | let storage = Storage() 9 | 10 | // sourcery: ignoreInJSON 11 | var regFormId: Identifier 12 | var required: Bool 13 | // sourcery: enum,string,radio,checkbox 14 | var type: FieldType 15 | var name: String 16 | var placeholder: String 17 | var defaultValue: String 18 | 19 | init(regFormId: Identifier, 20 | required: Bool, 21 | name: String, 22 | type: FieldType, 23 | placeholder: String, 24 | defaultValue: String) { 25 | self.regFormId = regFormId 26 | self.required = required 27 | self.type = type 28 | self.name = name 29 | self.placeholder = placeholder 30 | self.defaultValue = defaultValue 31 | } 32 | 33 | // sourcery:inline:auto:RegField.AutoModelGeneratable 34 | init(row: Row) throws { 35 | regFormId = try row.get(Keys.regFormId) 36 | required = try row.get(Keys.required) 37 | type = FieldType(try row.get(Keys.type)) 38 | name = try row.get(Keys.name) 39 | placeholder = try row.get(Keys.placeholder) 40 | defaultValue = try row.get(Keys.defaultValue) 41 | } 42 | 43 | func makeRow() throws -> Row { 44 | var row = Row() 45 | try row.set(Keys.regFormId, regFormId) 46 | try row.set(Keys.required, required) 47 | try row.set(Keys.type, type.string) 48 | try row.set(Keys.name, name) 49 | try row.set(Keys.placeholder, placeholder) 50 | try row.set(Keys.defaultValue, defaultValue) 51 | return row 52 | } 53 | 54 | // sourcery:end 55 | } 56 | 57 | extension RegField { 58 | 59 | // sourcery: nestedJSONRepresentableField 60 | func fieldAnswers() throws -> [RegFieldAnswer] { 61 | return try children().all() 62 | } 63 | 64 | var rules: Siblings> { 65 | return siblings() 66 | } 67 | 68 | func regForm() throws -> RegForm? { 69 | return try parent(id: regFormId).get() 70 | } 71 | 72 | func field() throws -> RegField? { 73 | return try children().first() 74 | } 75 | 76 | func fieldToJSON() throws -> JSON { 77 | 78 | func fieldWithAnswersJSON() throws -> JSON { 79 | var json = JSON() 80 | try json.set(Keys.name, name) 81 | try json.set(Keys.type, type.string) 82 | try json.set(Keys.placeholder, placeholder) 83 | try json.set(Keys.defaultValue, defaultValue) 84 | 85 | var fieldAnswers = try RegFieldAnswer.fieldAnswers(by: id!) 86 | fieldAnswers.removeKey(RegFieldAnswer.Keys.regFieldId) 87 | 88 | try json.set(AnswersKeys.fieldAnswers, fieldAnswers) 89 | return json 90 | } 91 | 92 | var json = JSON() 93 | try json.set(Keys.id, id) 94 | try json.set(Keys.required, required) 95 | try json.set(AnswersKeys.field, fieldWithAnswersJSON()) 96 | return json 97 | } 98 | } 99 | 100 | extension RegField { 101 | 102 | struct AnswersKeys { 103 | static let regFields = "reg_fields" 104 | static let field = "field" 105 | static let fieldAnswers = "field_answers" 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Sources/App/Models/RegFieldAnswer/RegFieldAnswer.generated.swift: -------------------------------------------------------------------------------- 1 | // Generated using Sourcery 0.10.1 — https://github.com/krzysztofzablocki/Sourcery 2 | // DO NOT EDIT 3 | 4 | import Vapor 5 | import FluentProvider 6 | 7 | extension RegFieldAnswer { 8 | static var entity: String = "reg_field_answer" 9 | } 10 | 11 | extension RegFieldAnswer { 12 | 13 | struct Keys { 14 | static let id = "id" 15 | static let regFieldId = "reg_field_id" 16 | static let value = "value" 17 | } 18 | } 19 | 20 | extension RegFieldAnswer: JSONInitializable { 21 | 22 | convenience init(json: JSON) throws { 23 | self.init( 24 | value: try json.get(Keys.value), 25 | regFieldId: try json.get(Keys.regFieldId) 26 | ) 27 | } 28 | } 29 | 30 | extension RegFieldAnswer: Preparation { 31 | 32 | static func prepare(_ database: Database) throws { 33 | try database.create(self) { builder in 34 | builder.id() 35 | builder.foreignId(for: RegField.self, optional: false, unique: false, foreignIdKey: Keys.regFieldId, foreignKeyName: self.entity + "_" + Keys.regFieldId) 36 | builder.string(Keys.value) 37 | } 38 | } 39 | 40 | static func revert(_ database: Database) throws { 41 | try database.delete(self) 42 | } 43 | } 44 | 45 | extension RegFieldAnswer: JSONRepresentable { 46 | 47 | func makeJSON() throws -> JSON { 48 | var json = JSON() 49 | try json.set(Keys.id, id) 50 | try json.set(Keys.value, value) 51 | return json 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sources/App/Models/RegFieldAnswer/RegFieldAnswer.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import FluentProvider 3 | 4 | // sourcery: AutoModelGeneratable 5 | // sourcery: fromJSON, toJSON, Preparation 6 | final class RegFieldAnswer: Model { 7 | 8 | let storage = Storage() 9 | 10 | // sourcery: ignoreInJSON 11 | var regFieldId: Identifier 12 | var value: String 13 | 14 | init(value: String, regFieldId: Identifier) { 15 | self.value = value 16 | self.regFieldId = regFieldId 17 | } 18 | 19 | // sourcery:inline:auto:RegFieldAnswer.AutoModelGeneratable 20 | init(row: Row) throws { 21 | regFieldId = try row.get(Keys.regFieldId) 22 | value = try row.get(Keys.value) 23 | } 24 | 25 | func makeRow() throws -> Row { 26 | var row = Row() 27 | try row.set(Keys.regFieldId, regFieldId) 28 | try row.set(Keys.value, value) 29 | return row 30 | } 31 | // sourcery:end 32 | } 33 | 34 | extension RegFieldAnswer { 35 | 36 | static func fieldAnswers(by fieldId: Identifier) throws -> JSON { 37 | return try RegFieldAnswer.makeQuery() 38 | .filter(RegFieldAnswer.Keys.regFieldId, fieldId) 39 | .all() 40 | .makeJSON() 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /Sources/App/Models/RegForm/RegForm.generated.swift: -------------------------------------------------------------------------------- 1 | // Generated using Sourcery 0.10.1 — https://github.com/krzysztofzablocki/Sourcery 2 | // DO NOT EDIT 3 | 4 | import Vapor 5 | import FluentProvider 6 | 7 | extension RegForm { 8 | static var entity: String = "reg_form" 9 | } 10 | 11 | extension RegForm { 12 | 13 | struct Keys { 14 | static let id = "id" 15 | static let eventId = "event_id" 16 | static let formName = "form_name" 17 | static let description = "description" 18 | static let regFields = "reg_fields" 19 | } 20 | } 21 | 22 | extension RegForm: ResponseRepresentable { } 23 | 24 | extension RegForm: Preparation { 25 | 26 | static func prepare(_ database: Database) throws { 27 | try database.create(self) { builder in 28 | builder.id() 29 | builder.foreignId(for: Event.self, optional: false, unique: false, foreignIdKey: Keys.eventId, foreignKeyName: self.entity + "_" + Keys.eventId) 30 | builder.string(Keys.formName) 31 | builder.string(Keys.description) 32 | } 33 | } 34 | 35 | static func revert(_ database: Database) throws { 36 | try database.delete(self) 37 | } 38 | } 39 | 40 | extension RegForm: JSONRepresentable { 41 | 42 | func makeJSON() throws -> JSON { 43 | var json = JSON() 44 | try json.set(Keys.id, id) 45 | try json.set(Keys.eventId, eventId) 46 | try json.set(Keys.formName, formName) 47 | try json.set(Keys.description, description) 48 | try json.set(Keys.regFields, regFields().makeJSON()) 49 | return json 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sources/App/Models/RegForm/RegForm.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import FluentProvider 3 | 4 | // sourcery: AutoModelGeneratable 5 | // sourcery: toJSON, Preparation, ResponseRepresentable 6 | final class RegForm: Model { 7 | 8 | let storage = Storage() 9 | 10 | var eventId: Identifier 11 | var formName: String 12 | var description: String 13 | 14 | init(eventId: Identifier, 15 | formName: String, 16 | description: String) { 17 | self.eventId = eventId 18 | self.formName = formName 19 | self.description = description 20 | } 21 | 22 | // sourcery:inline:auto:RegForm.AutoModelGeneratable 23 | init(row: Row) throws { 24 | eventId = try row.get(Keys.eventId) 25 | formName = try row.get(Keys.formName) 26 | description = try row.get(Keys.description) 27 | } 28 | 29 | func makeRow() throws -> Row { 30 | var row = Row() 31 | try row.set(Keys.eventId, eventId) 32 | try row.set(Keys.formName, formName) 33 | try row.set(Keys.description, description) 34 | return row 35 | } 36 | // sourcery:end 37 | } 38 | 39 | extension RegForm { 40 | // sourcery: nestedJSONRepresentableField 41 | func regFields() throws -> [RegField] { 42 | return try children().all() 43 | } 44 | 45 | func eventRegFields() throws -> [RegField] { 46 | return try RegField.makeQuery().filter(RegField.Keys.regFormId, id).all() 47 | } 48 | 49 | var event: Event? { 50 | return try? parent(id: eventId).get()! 51 | } 52 | 53 | static func getRegForm(by eventId: Int) throws -> RegForm? { 54 | return try RegForm.makeQuery().filter(Keys.eventId, eventId).first() 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /Sources/App/Models/Rule/Rule.generated.swift: -------------------------------------------------------------------------------- 1 | // Generated using Sourcery 0.10.1 — https://github.com/krzysztofzablocki/Sourcery 2 | // DO NOT EDIT 3 | 4 | import Vapor 5 | import FluentProvider 6 | 7 | extension Rule { 8 | static var entity: String = "rule" 9 | } 10 | 11 | extension Rule { 12 | 13 | struct Keys { 14 | static let id = "id" 15 | static let type = "type" 16 | } 17 | } 18 | 19 | extension Rule { 20 | 21 | enum ValidationRule: String { 22 | case phone 23 | case number 24 | case alphanumeric 25 | case email 26 | case string 27 | 28 | var string: String { 29 | return String(describing: self) 30 | } 31 | 32 | init(_ string: String) { 33 | self = ValidationRule(rawValue: string) ?? .string 34 | } 35 | } 36 | } 37 | 38 | extension Rule: Preparation { 39 | 40 | static func prepare(_ database: Database) throws { 41 | try database.create(self) { builder in 42 | builder.id() 43 | builder.enum(Keys.type, options: ["phone", "number", "alphanumeric", "email", "string"]) 44 | } 45 | } 46 | 47 | static func revert(_ database: Database) throws { 48 | try database.delete(self) 49 | } 50 | } 51 | 52 | extension Rule: JSONRepresentable { 53 | 54 | func makeJSON() throws -> JSON { 55 | var json = JSON() 56 | try json.set(Keys.id, id) 57 | try json.set(Keys.type, type.string) 58 | return json 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Sources/App/Models/Rule/Rule.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import FluentProvider 3 | 4 | // sourcery: AutoModelGeneratable 5 | // sourcery: toJSON, Preparation 6 | final class Rule: Model { 7 | 8 | let storage = Storage() 9 | 10 | // sourcery: enum, email, number, alphanumeric, string, phone 11 | var type: ValidationRule 12 | 13 | init(type: ValidationRule) { 14 | self.type = type 15 | } 16 | 17 | // sourcery:inline:auto:Rule.AutoModelGeneratable 18 | init(row: Row) throws { 19 | type = ValidationRule(try row.get(Keys.type)) 20 | } 21 | 22 | func makeRow() throws -> Row { 23 | var row = Row() 24 | try row.set(Keys.type, type.string) 25 | return row 26 | } 27 | // sourcery:end 28 | } 29 | -------------------------------------------------------------------------------- /Sources/App/Models/Session/Session.generated.swift: -------------------------------------------------------------------------------- 1 | // Generated using Sourcery 0.10.1 — https://github.com/krzysztofzablocki/Sourcery 2 | // DO NOT EDIT 3 | 4 | import Vapor 5 | import FluentProvider 6 | 7 | extension Session { 8 | static var entity: String = "session" 9 | } 10 | 11 | extension Session { 12 | 13 | struct Keys { 14 | static let id = "id" 15 | static let userId = "user_id" 16 | static let token = "token" 17 | static let actual = "actual" 18 | } 19 | } 20 | 21 | extension Session: Preparation { 22 | 23 | static func prepare(_ database: Database) throws { 24 | try database.create(self) { builder in 25 | builder.id() 26 | builder.foreignId(for: User.self, optional: false, unique: true, foreignIdKey: Keys.userId, foreignKeyName: self.entity + "_" + Keys.userId) 27 | builder.string(Keys.token) 28 | builder.bool(Keys.actual) 29 | } 30 | } 31 | 32 | static func revert(_ database: Database) throws { 33 | try database.delete(self) 34 | } 35 | } 36 | 37 | extension Session: Timestampable { } 38 | -------------------------------------------------------------------------------- /Sources/App/Models/Session/Session.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import FluentProvider 3 | import Foundation 4 | import Crypto 5 | 6 | // sourcery: AutoModelGeneratable 7 | // sourcery: Preparation, Timestampable 8 | final class Session: Model { 9 | 10 | let storage = Storage() 11 | 12 | // sourcery: unique = true 13 | var userId: Identifier 14 | var token: String 15 | var actual: Bool 16 | 17 | init(userId: Identifier, 18 | token: String, 19 | actual: Bool = true) { 20 | self.userId = userId 21 | self.token = token 22 | self.actual = actual 23 | } 24 | 25 | convenience init (user: User, actual: Bool = true) throws { 26 | self.init( 27 | userId: try user.assertExists(), 28 | token: try Session.generateToken(), 29 | actual: actual 30 | ) 31 | } 32 | 33 | // sourcery:inline:auto:Session.AutoModelGeneratable 34 | init(row: Row) throws { 35 | userId = try row.get(Keys.userId) 36 | token = try row.get(Keys.token) 37 | actual = try row.get(Keys.actual) 38 | } 39 | 40 | func makeRow() throws -> Row { 41 | var row = Row() 42 | try row.set(Keys.userId, userId) 43 | try row.set(Keys.token, token) 44 | try row.set(Keys.actual, actual) 45 | return row 46 | } 47 | // sourcery:end 48 | } 49 | 50 | extension Session: JSONRepresentable { 51 | 52 | func makeJSON() throws -> JSON { 53 | var json = JSON() 54 | try json.set(Keys.token, token) 55 | try json.set(Keys.actual, actual) 56 | return json 57 | } 58 | } 59 | 60 | extension Session { 61 | 62 | static func find(by token: String) throws -> Session? { 63 | return try Session.makeQuery().filter(Keys.token, token).first() 64 | } 65 | 66 | var user: User? { 67 | return try? parent(id: userId).get()! 68 | } 69 | 70 | static func find(by user: User) throws -> Session? { 71 | guard let userId = user.id else { 72 | throw Abort(.internalServerError, reason: "User not found") 73 | } 74 | return try Session.makeQuery().filter(Keys.userId, userId).first() 75 | } 76 | 77 | static func generateToken() throws -> String { 78 | let random = try Crypto.Random.bytes(count: 16) 79 | return random.base64Encoded.makeString() 80 | } 81 | 82 | func updateToken() throws { 83 | guard 84 | let date = updatedAt, 85 | let referenceDate = Calendar.current.date(byAdding: .month, value: 1, to: date) 86 | else { 87 | throw Abort.serverError 88 | } 89 | 90 | if referenceDate < Date() { 91 | self.token = try Session.generateToken() 92 | try self.save() 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Sources/App/Models/Social/Config/FacebookConfig.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct FacebookConfig { 4 | private let fb = Social.FB.self 5 | let tokenRequestURL: String 6 | let clientId: String 7 | let redirectURI: String 8 | let scope: String 9 | let clientSecret: String 10 | let userInfoURL: String 11 | let fields: String 12 | var code: String? 13 | var accessToken: String? 14 | 15 | init(_ config: Config) { 16 | guard 17 | let tokenRequestURL = config[fb.name, fb.tokenRequestURL]?.string, 18 | let clientId = config[fb.name, fb.clientId]?.string, 19 | let redirectURI = config[fb.name, fb.redirectURI]?.string, 20 | let scope = config[fb.name, fb.scope]?.string, 21 | let clientSecret = config[fb.name, fb.clientSecret]?.string, 22 | let userInfoURL = config[fb.name, fb.userInfoURL]?.string, 23 | let fields = config[fb.name, fb.fields]?.string 24 | else { 25 | fatalError("Can't read config from facebook.json") 26 | } 27 | 28 | self.tokenRequestURL = tokenRequestURL 29 | self.clientId = clientId 30 | self.redirectURI = redirectURI 31 | self.scope = scope 32 | self.clientSecret = clientSecret 33 | self.userInfoURL = userInfoURL 34 | self.fields = fields 35 | 36 | if config.environment == .test { 37 | code = config[fb.name, fb.code]?.string ?? "" 38 | accessToken = config[fb.name, fb.accessToken]?.string ?? "" 39 | 40 | guard code != nil || accessToken != nil else { 41 | fatalError("Can't read token or code from test facebook.json") 42 | } 43 | } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /Sources/App/Models/Social/Config/GithubConfig.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | struct GithubConfig { 4 | private let github = Social.Github.self 5 | let tokenRequestURL: String 6 | let clientId: String 7 | let clientSecret: String 8 | let userInfoURL: String 9 | var code: String? 10 | var state: String? 11 | var accessToken: String? 12 | var testUserInfoURL: String? 13 | 14 | init(_ config: Config) { 15 | guard 16 | let tokenRequestURL = config[github.name, github.tokenRequestURL]?.string, 17 | let clientId = config[github.name, github.clientId]?.string, 18 | let clientSecret = config[github.name, github.clientSecret]?.string, 19 | let userInfoURL = config[github.name, github.userInfoURL]?.string 20 | else { 21 | fatalError("Can't read config from github.json") 22 | } 23 | 24 | self.tokenRequestURL = tokenRequestURL 25 | self.clientId = clientId 26 | self.clientSecret = clientSecret 27 | self.userInfoURL = userInfoURL 28 | 29 | if config.environment == .test { 30 | code = config[github.name, github.code]?.string ?? "" 31 | accessToken = config[github.name, github.accessToken]?.string ?? "" 32 | state = config[github.name, github.state]?.string ?? "" 33 | guard (code != nil || accessToken != nil) && state != nil else { 34 | fatalError("Can't read token or code from test facebook.json") 35 | } 36 | testUserInfoURL = config[github.name, github.testUserInfoURL]?.string ?? "" 37 | } 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /Sources/App/Models/Social/Config/VKontakteConfig.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Crypto 3 | 4 | struct VkontakteConfig { 5 | private let vk = Social.VK.self 6 | let apiURL: String 7 | let fields: String 8 | let method: String 9 | var secret: String = "" 10 | var accessToken: String = "" 11 | var userInfoURL: String { 12 | return apiURL + method 13 | } 14 | 15 | init(_ config: Config) { 16 | guard 17 | let apiURL = config[vk.name, vk.apiURL]?.string, 18 | let fields = config[vk.name, vk.fields]?.string, 19 | let method = config[vk.name, vk.method]?.string 20 | else { 21 | fatalError("Can't read config from vkontakte.json") 22 | } 23 | 24 | self.apiURL = apiURL 25 | self.fields = fields 26 | self.method = method 27 | 28 | if config.environment == .test { 29 | guard 30 | let secret = config[vk.name, vk.secret]?.string, 31 | let accessToken = config[vk.name, vk.accessToken]?.string 32 | else { 33 | fatalError("Can't read token & secret from test vkontakte.json") 34 | } 35 | self.secret = secret 36 | self.accessToken = accessToken 37 | } 38 | 39 | } 40 | 41 | func getSignatureBased(on token: String, and secret: String)throws -> String { 42 | let urlForSignature = "\(method)?\(vk.fields)=\(fields)&\(vk.version)=\(vk.versionValue)&\(vk.accessToken)=\(token + secret)" 43 | print(urlForSignature) 44 | return try CryptoHasher.makeMD5(from: urlForSignature) 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /Sources/App/Models/Social/Social+Net.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Social { 4 | 5 | struct Nets { 6 | static let fb = "facebook" 7 | static let vk = "vkontakte" 8 | static let github = "github" 9 | } 10 | 11 | // swiftlint:disable type_name 12 | struct FB { 13 | static let name = Nets.fb 14 | static let tokenRequestURL = "token_request_url" 15 | static let clientId = "client_id" 16 | static let redirectURI = "redirect_uri" 17 | static let scope = "scope" 18 | static let clientSecret = "client_secret" 19 | static let userInfoURL = "user_info_url" 20 | static let fields = "fields" 21 | static let accessToken = "access_token" 22 | static let code = "code" 23 | 24 | struct Profile { 25 | static let socialUserId = "id" 26 | static let name = "first_name" 27 | static let lastname = "last_name" 28 | static let email = "email" 29 | static let photo = ["picture", "data", "url"] 30 | } 31 | 32 | } 33 | 34 | // swiftlint:disable type_name 35 | struct VK { 36 | static let name = Nets.vk 37 | static let apiURL = "api_url" 38 | static let method = "method" 39 | static let fields = "fields" 40 | static let accessToken = "access_token" 41 | static let sig = "sig" 42 | static let secret = "secret" 43 | static let version = "v" 44 | static let versionValue = "5.73" 45 | struct Profile { 46 | static let response = "response" 47 | static let socialUserId = "id" 48 | static let name = "first_name" 49 | static let lastname = "last_name" 50 | static let photo = "photo_max" 51 | } 52 | } 53 | 54 | struct Github { 55 | static let name = Nets.github 56 | static let tokenRequestURL = "token_request_url" 57 | static let clientId = "client_id" 58 | static let clientSecret = "client_secret" 59 | static let userInfoURL = "user_info_url" 60 | static let accessToken = "access_token" 61 | static let state = "state" 62 | static let code = "code" 63 | static let testUserInfoURL = "test_user_info_url" 64 | struct Profile { 65 | static let socialUserId = "github.com/" 66 | static let login = "login" 67 | static let photo = "avatar_url" 68 | static let name = "name" 69 | } 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /Sources/App/Models/Social/Social.generated.swift: -------------------------------------------------------------------------------- 1 | // Generated using Sourcery 0.10.1 — https://github.com/krzysztofzablocki/Sourcery 2 | // DO NOT EDIT 3 | 4 | import Vapor 5 | import FluentProvider 6 | 7 | extension Social { 8 | static var entity: String = "social" 9 | } 10 | 11 | extension Social { 12 | 13 | struct Keys { 14 | static let id = "id" 15 | static let name = "name" 16 | static let appId = "app_id" 17 | static let secureKey = "secure_key" 18 | static let serviceToken = "service_token" 19 | } 20 | } 21 | 22 | extension Social: JSONInitializable { 23 | 24 | convenience init(json: JSON) throws { 25 | self.init( 26 | name: try json.get(Keys.name), 27 | appId: try? json.get(Keys.appId), 28 | secureKey: try? json.get(Keys.secureKey), 29 | serviceToken: try? json.get(Keys.serviceToken) 30 | ) 31 | } 32 | } 33 | 34 | extension Social: Preparation { 35 | 36 | static func prepare(_ database: Database) throws { 37 | try database.create(self) { builder in 38 | builder.id() 39 | builder.string(Keys.name, unique: true) 40 | builder.string(Keys.appId, optional: true) 41 | builder.string(Keys.secureKey, optional: true) 42 | builder.string(Keys.serviceToken, optional: true) 43 | } 44 | } 45 | 46 | static func revert(_ database: Database) throws { 47 | try database.delete(self) 48 | } 49 | } 50 | 51 | extension Social: JSONRepresentable { 52 | 53 | func makeJSON() throws -> JSON { 54 | var json = JSON() 55 | try json.set(Keys.id, id) 56 | try json.set(Keys.name, name) 57 | try? json.set(Keys.appId, appId) 58 | try? json.set(Keys.secureKey, secureKey) 59 | try? json.set(Keys.serviceToken, serviceToken) 60 | return json 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Sources/App/Models/Social/Social.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import FluentProvider 3 | 4 | // sourcery: AutoModelGeneratable 5 | // sourcery: fromJSON, toJSON, Preparation 6 | final class Social: Model { 7 | 8 | let storage = Storage() 9 | // sourcery: unique 10 | var name: String 11 | var appId: String? 12 | var secureKey: String? 13 | var serviceToken: String? 14 | 15 | init(name: String, 16 | appId: String? = nil, 17 | secureKey: String? = nil, 18 | serviceToken: String? = nil) { 19 | self.name = name 20 | self.appId = appId 21 | self.secureKey = secureKey 22 | self.serviceToken = serviceToken 23 | } 24 | 25 | // sourcery:inline:auto:Social.AutoModelGeneratable 26 | init(row: Row) throws { 27 | name = try row.get(Keys.name) 28 | appId = try? row.get(Keys.appId) 29 | secureKey = try? row.get(Keys.secureKey) 30 | serviceToken = try? row.get(Keys.serviceToken) 31 | } 32 | 33 | func makeRow() throws -> Row { 34 | var row = Row() 35 | try row.set(Keys.name, name) 36 | try? row.set(Keys.appId, appId) 37 | try? row.set(Keys.secureKey, secureKey) 38 | try? row.set(Keys.serviceToken, serviceToken) 39 | return row 40 | } 41 | // sourcery:end 42 | } 43 | 44 | extension Social { 45 | 46 | static func find(by name: String) throws -> Social? { 47 | return try Social 48 | .makeQuery() 49 | .filter(Keys.name, name) 50 | .first() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/App/Models/SocialAccount/SocialAccount.generated.swift: -------------------------------------------------------------------------------- 1 | // Generated using Sourcery 0.10.1 — https://github.com/krzysztofzablocki/Sourcery 2 | // DO NOT EDIT 3 | 4 | import Vapor 5 | import FluentProvider 6 | 7 | extension SocialAccount { 8 | static var entity: String = "social_account" 9 | } 10 | 11 | extension SocialAccount { 12 | 13 | struct Keys { 14 | static let id = "id" 15 | static let userId = "user_id" 16 | static let socialId = "social_id" 17 | static let socialUserId = "social_user_id" 18 | } 19 | } 20 | 21 | extension SocialAccount: JSONInitializable { 22 | 23 | convenience init(json: JSON) throws { 24 | self.init( 25 | userId: try json.get(Keys.userId), 26 | socialId: try json.get(Keys.socialId), 27 | socialUserId: try json.get(Keys.socialUserId) 28 | ) 29 | } 30 | } 31 | 32 | extension SocialAccount: Preparation { 33 | 34 | static func prepare(_ database: Database) throws { 35 | try database.create(self) { builder in 36 | builder.id() 37 | builder.foreignId(for: User.self, optional: false, unique: false, foreignIdKey: Keys.userId, foreignKeyName: self.entity + "_" + Keys.userId) 38 | builder.foreignId(for: Social.self, optional: false, unique: false, foreignIdKey: Keys.socialId, foreignKeyName: self.entity + "_" + Keys.socialId) 39 | builder.string(Keys.socialUserId) 40 | } 41 | } 42 | 43 | static func revert(_ database: Database) throws { 44 | try database.delete(self) 45 | } 46 | } 47 | 48 | extension SocialAccount: JSONRepresentable { 49 | 50 | func makeJSON() throws -> JSON { 51 | var json = JSON() 52 | try json.set(Keys.id, id) 53 | try json.set(Keys.userId, userId) 54 | try json.set(Keys.socialId, socialId) 55 | try json.set(Keys.socialUserId, socialUserId) 56 | return json 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/App/Models/SocialAccount/SocialAccount.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import FluentProvider 3 | 4 | // sourcery: AutoModelGeneratable 5 | // sourcery: fromJSON, toJSON, Preparation 6 | final class SocialAccount: Model { 7 | 8 | let storage = Storage() 9 | 10 | var userId: Identifier 11 | var socialId: Identifier 12 | var socialUserId: String 13 | 14 | init(userId: Identifier, 15 | socialId: Identifier, 16 | socialUserId: String) { 17 | self.socialId = socialId 18 | self.socialUserId = socialUserId 19 | self.userId = userId 20 | } 21 | 22 | // sourcery:inline:auto:SocialAccount.AutoModelGeneratable 23 | init(row: Row) throws { 24 | userId = try row.get(Keys.userId) 25 | socialId = try row.get(Keys.socialId) 26 | socialUserId = try row.get(Keys.socialUserId) 27 | } 28 | 29 | func makeRow() throws -> Row { 30 | var row = Row() 31 | try row.set(Keys.userId, userId) 32 | try row.set(Keys.socialId, socialId) 33 | try row.set(Keys.socialUserId, socialUserId) 34 | return row 35 | } 36 | // sourcery:end 37 | } 38 | 39 | extension SocialAccount { 40 | 41 | func social() throws -> Social? { 42 | return try parent(id: socialId).get() 43 | } 44 | 45 | func user() throws -> User? { 46 | return try parent(id: userId).get() 47 | } 48 | 49 | static func find(by socialUserId: String) throws -> User? { 50 | return try SocialAccount 51 | .makeQuery() 52 | .filter(SocialAccount.Keys.socialUserId, socialUserId) 53 | .first()? 54 | .user() 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /Sources/App/Models/Speaker/Speaker.generated.swift: -------------------------------------------------------------------------------- 1 | // Generated using Sourcery 0.10.1 — https://github.com/krzysztofzablocki/Sourcery 2 | // DO NOT EDIT 3 | 4 | import Vapor 5 | import FluentProvider 6 | 7 | extension Speaker { 8 | static var entity: String = "speaker" 9 | } 10 | 11 | extension Speaker { 12 | 13 | struct Keys { 14 | static let id = "id" 15 | static let userId = "user_id" 16 | static let speechId = "speech_id" 17 | } 18 | } 19 | 20 | extension Speaker: Preparation { 21 | 22 | static func prepare(_ database: Database) throws { 23 | try database.create(self) { builder in 24 | builder.id() 25 | builder.foreignId(for: User.self, optional: false, unique: false, foreignIdKey: Keys.userId, foreignKeyName: self.entity + "_" + Keys.userId) 26 | builder.foreignId(for: Speech.self, optional: false, unique: false, foreignIdKey: Keys.speechId, foreignKeyName: self.entity + "_" + Keys.speechId) 27 | } 28 | } 29 | 30 | static func revert(_ database: Database) throws { 31 | try database.delete(self) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/App/Models/Speaker/Speaker.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import FluentProvider 3 | 4 | // sourcery: AutoModelGeneratable 5 | // sourcery: Preparation 6 | final class Speaker: Model { 7 | 8 | let storage = Storage() 9 | 10 | var userId: Identifier 11 | var speechId: Identifier 12 | 13 | init(userId: Identifier, speechId: Identifier) { 14 | self.userId = userId 15 | self.speechId = speechId 16 | } 17 | 18 | // sourcery:inline:auto:Speaker.AutoModelGeneratable 19 | init(row: Row) throws { 20 | userId = try row.get(Keys.userId) 21 | speechId = try row.get(Keys.speechId) 22 | } 23 | 24 | func makeRow() throws -> Row { 25 | var row = Row() 26 | try row.set(Keys.userId, userId) 27 | try row.set(Keys.speechId, speechId) 28 | return row 29 | } 30 | // sourcery:end 31 | } 32 | 33 | extension Speaker { 34 | 35 | func user() throws -> User? { 36 | return try parent(id: userId).get() 37 | } 38 | 39 | func speech() throws -> Speech? { 40 | return try parent(id: speechId).get() 41 | } 42 | } 43 | 44 | /// Custom implementation because of this exceptional case 45 | extension Speaker: JSONRepresentable { 46 | 47 | func makeJSON() throws -> JSON { 48 | guard 49 | let userJSON = try user()?.makeJSON(), 50 | let userId = userJSON[Keys.id]?.int 51 | else { 52 | throw Abort(.internalServerError, reason: "Speaker doesn't have associated User") 53 | } 54 | var json = JSON(json: userJSON) 55 | json.removeKey(Session.Keys.token) 56 | json.removeKey(Keys.id) 57 | try json.set(Keys.userId, userId) 58 | return json 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Sources/App/Models/Speech/Speech.generated.swift: -------------------------------------------------------------------------------- 1 | // Generated using Sourcery 0.10.1 — https://github.com/krzysztofzablocki/Sourcery 2 | // DO NOT EDIT 3 | 4 | import Vapor 5 | import FluentProvider 6 | 7 | extension Speech { 8 | static var entity: String = "speech" 9 | } 10 | 11 | extension Speech { 12 | 13 | struct Keys { 14 | static let id = "id" 15 | static let eventId = "event_id" 16 | static let title = "title" 17 | static let description = "description" 18 | static let speakers = "speakers" 19 | static let contents = "contents" 20 | } 21 | } 22 | 23 | extension Speech: Preparation { 24 | 25 | static func prepare(_ database: Database) throws { 26 | try database.create(self) { builder in 27 | builder.id() 28 | builder.foreignId(for: Event.self, optional: false, unique: false, foreignIdKey: Keys.eventId, foreignKeyName: self.entity + "_" + Keys.eventId) 29 | builder.string(Keys.title, optional: true) 30 | builder.string(Keys.description) 31 | } 32 | } 33 | 34 | static func revert(_ database: Database) throws { 35 | try database.delete(self) 36 | } 37 | } 38 | 39 | extension Speech: JSONRepresentable { 40 | 41 | func makeJSON() throws -> JSON { 42 | var json = JSON() 43 | try json.set(Keys.id, id) 44 | try? json.set(Keys.title, title) 45 | try json.set(Keys.description, description) 46 | try json.set(Keys.speakers, speakers().makeJSON()) 47 | try json.set(Keys.contents, contents().makeJSON()) 48 | return json 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/App/Models/Speech/Speech.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import FluentProvider 3 | 4 | // sourcery: AutoModelGeneratable 5 | // sourcery: toJSON, Preparation 6 | final class Speech: Model { 7 | 8 | let storage = Storage() 9 | 10 | // sourcery: ignoreInJSON 11 | var eventId: Identifier 12 | var title: String? 13 | var description: String 14 | 15 | init(eventId: Identifier, 16 | title: String?, 17 | description: String) { 18 | self.eventId = eventId 19 | self.title = title 20 | self.description = description 21 | } 22 | 23 | // sourcery:inline:auto:Speech.AutoModelGeneratable 24 | init(row: Row) throws { 25 | eventId = try row.get(Keys.eventId) 26 | title = try? row.get(Keys.title) 27 | description = try row.get(Keys.description) 28 | } 29 | 30 | func makeRow() throws -> Row { 31 | var row = Row() 32 | try row.set(Keys.eventId, eventId) 33 | try? row.set(Keys.title, title) 34 | try row.set(Keys.description, description) 35 | return row 36 | } 37 | // sourcery:end 38 | } 39 | 40 | extension Speech { 41 | 42 | func event() throws -> Event? { 43 | return try parent(id: eventId).get() 44 | } 45 | 46 | // sourcery: nestedJSONRepresentableField 47 | func speakers() throws -> [Speaker] { 48 | return try children().all() 49 | } 50 | 51 | // sourcery: nestedJSONRepresentableField 52 | func contents() throws -> [Content] { 53 | return try children().all() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/App/Models/User/User.generated.swift: -------------------------------------------------------------------------------- 1 | // Generated using Sourcery 0.10.1 — https://github.com/krzysztofzablocki/Sourcery 2 | // DO NOT EDIT 3 | 4 | import Vapor 5 | import FluentProvider 6 | 7 | extension User { 8 | static var entity: String = "user" 9 | } 10 | 11 | extension User { 12 | 13 | struct Keys { 14 | static let id = "id" 15 | static let name = "name" 16 | static let lastname = "lastname" 17 | static let company = "company" 18 | static let position = "position" 19 | static let photo = "photo" 20 | static let email = "email" 21 | static let phone = "phone" 22 | static let photoURL = "photo_url" 23 | } 24 | } 25 | 26 | extension User: ResponseRepresentable { } 27 | 28 | extension User: Updateable { 29 | 30 | public static var updateableKeys: [UpdateableKey] { 31 | return [ 32 | UpdateableKey(Keys.name, String.self) { $0.name = $1 }, 33 | UpdateableKey(Keys.lastname, String.self) { $0.lastname = $1 }, 34 | UpdateableKey(Keys.company, String.self) { $0.company = $1 }, 35 | UpdateableKey(Keys.position, String.self) { $0.position = $1 }, 36 | UpdateableKey(Keys.photo, String.self) { $0.photo = $1 }, 37 | UpdateableKey(Keys.email, String.self) { $0.email = $1 }, 38 | UpdateableKey(Keys.phone, String.self) { $0.phone = $1 } 39 | ] 40 | } 41 | } 42 | 43 | extension User: JSONInitializable { 44 | 45 | convenience init(json: JSON) throws { 46 | self.init( 47 | name: try json.get(Keys.name), 48 | lastname: try? json.get(Keys.lastname), 49 | company: try? json.get(Keys.company), 50 | position: try? json.get(Keys.position), 51 | photo: try? json.get(Keys.photo), 52 | email: try? json.get(Keys.email), 53 | phone: try? json.get(Keys.phone) 54 | ) 55 | } 56 | } 57 | 58 | extension User: Preparation { 59 | 60 | static func prepare(_ database: Database) throws { 61 | try database.create(self) { builder in 62 | builder.id() 63 | builder.string(Keys.name) 64 | builder.string(Keys.lastname, optional: true) 65 | builder.string(Keys.company, optional: true) 66 | builder.string(Keys.position, optional: true) 67 | builder.string(Keys.photo, optional: true) 68 | builder.string(Keys.email, optional: true) 69 | builder.string(Keys.phone, optional: true) 70 | } 71 | } 72 | 73 | static func revert(_ database: Database) throws { 74 | try database.delete(self) 75 | } 76 | } 77 | 78 | extension User: Timestampable { } 79 | -------------------------------------------------------------------------------- /Sources/App/Routes/FrontendAPICollection.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import HTTP 3 | import Routing 4 | import AuthProvider 5 | 6 | final class FrontendAPICollection: RouteCollection { 7 | 8 | let drop: Droplet 9 | let config: Config 10 | 11 | init(drop: Droplet) { 12 | self.drop = drop 13 | self.config = drop.config 14 | } 15 | 16 | func build(_ builder: RouteBuilder) throws { 17 | 18 | let frontendAPI = builder.grouped("api") 19 | 20 | let clientMiddlewareGroup = frontendAPI.grouped([ 21 | try ClientMiddleware(config: config), 22 | try PhotoURLMiddleware(config: config) 23 | ]) 24 | 25 | let userMiddlewareGroup = frontendAPI.grouped([ 26 | try ClientMiddleware(config: config), 27 | TokenAuthenticationMiddleware(User.self), 28 | try PhotoURLMiddleware(config: config) 29 | ]) 30 | 31 | // MARK: Event 32 | try clientMiddlewareGroup.resource("event", EventController.self) 33 | try clientMiddlewareGroup.resource("event/:id/speech", EventSpeechController.self) 34 | 35 | try userMiddlewareGroup.resource("event/:id/form", RegistrationFormController.self) 36 | 37 | let registrationController = RegistrationController(drop: drop) 38 | userMiddlewareGroup.resource("event/register", registrationController) 39 | 40 | // MARK: User 41 | let userAuthorizationController = UserAuthorizationController(drop: drop) 42 | clientMiddlewareGroup.resource("user/auth", userAuthorizationController) 43 | 44 | let userController = UserController(drop: drop) 45 | userMiddlewareGroup.resource("user", userController) 46 | 47 | try userMiddlewareGroup.resource("user/notification", PushNotificationController.self) 48 | try userMiddlewareGroup.resource("user/give-speech", GiveSpeechController.self) 49 | 50 | // MARK: Creators 51 | try clientMiddlewareGroup.resource("user/creators", CreatorsController.self) 52 | 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /Sources/App/Routes/Routes.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import AuthProvider 3 | 4 | extension Droplet { 5 | func setupRoutes() throws { 6 | 7 | let frontendAPICollection = FrontendAPICollection(drop: self) 8 | let testAPICollection = TestAPICollection(drop: self) 9 | 10 | try collection(frontendAPICollection) 11 | try collection(testAPICollection) 12 | 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/App/Routes/TestAPICollection.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import HTTP 3 | import Routing 4 | import AuthProvider 5 | 6 | final class TestAPICollection: RouteCollection { 7 | 8 | let drop: Droplet 9 | let config: Config 10 | 11 | init(drop: Droplet) { 12 | self.drop = drop 13 | self.config = drop.config 14 | } 15 | 16 | func build(_ builder: RouteBuilder) throws { 17 | 18 | let testAPI = builder.grouped("test") 19 | 20 | try testAPI.resource("heartbeat", HeartbeatController.self) 21 | 22 | let stageController = StageController(drop: drop) 23 | testAPI.resource("stage/recreate", stageController) 24 | 25 | let clientMiddlewareGroup = testAPI.grouped([ 26 | try ClientMiddleware(config: config), 27 | try PhotoURLMiddleware(config: config) 28 | ]) 29 | 30 | clientMiddlewareGroup.get("hello") { _ in 31 | var json = JSON() 32 | try json.set("hello", "world") 33 | return json 34 | } 35 | 36 | clientMiddlewareGroup.get("plaintext") { _ in 37 | return "Hello, world!" 38 | } 39 | 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /Sources/App/Setup/Config+Setup.swift: -------------------------------------------------------------------------------- 1 | import FluentProvider 2 | import MySQLProvider 3 | import AuthProvider 4 | 5 | extension Config { 6 | public func setup() throws { 7 | // allow fuzzy conversions for these types 8 | // (add your own types here) 9 | Node.fuzzy = [Row.self, JSON.self, Node.self] 10 | try setupProviders() 11 | try setupPreparations() 12 | try setupMiddleware() 13 | } 14 | 15 | /// Configure providers 16 | private func setupProviders() throws { 17 | try addProvider(FluentProvider.Provider.self) 18 | try addProvider(MySQLProvider.Provider.self) 19 | try addProvider(AuthProvider.Provider.self) 20 | } 21 | 22 | /// Add all models that should have their 23 | /// schemas prepared before the app boots 24 | private func setupPreparations() throws { 25 | 26 | let entities: [Preparation.Type] = [ 27 | Approval.self, 28 | Heartbeat.self, 29 | User.self, 30 | Client.self, 31 | Session.self, 32 | City.self, 33 | Place.self, 34 | Event.self, 35 | RegForm.self, 36 | EventReg.self, 37 | Rule.self, 38 | RegField.self, 39 | RegFieldAnswer.self, 40 | EventRegAnswer.self, 41 | Speech.self, 42 | Speaker.self, 43 | Content.self, 44 | Creator.self, 45 | GiveSpeech.self, 46 | Social.self, 47 | SocialAccount.self, 48 | Pivot.self 49 | ] 50 | 51 | entities 52 | .forEach { 53 | preparations.append($0) 54 | } 55 | 56 | } 57 | 58 | // Configure Middleware 59 | private func setupMiddleware() throws { 60 | self.addConfigurable(middleware: ClientMiddleware.init, name: Constants.Middleware.client) 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /Sources/App/Setup/Droplet+Setup.swift: -------------------------------------------------------------------------------- 1 | @_exported import Vapor 2 | 3 | extension Droplet { 4 | public func setup() throws { 5 | try setupRoutes() 6 | try setupSocial() 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Sources/App/Setup/Social+Data.swift: -------------------------------------------------------------------------------- 1 | import Fluent 2 | import Vapor 3 | 4 | extension Droplet { 5 | 6 | func setupSocial() throws { 7 | let socials = [ 8 | Social.Nets.fb, 9 | Social.Nets.vk, 10 | Social.Nets.github] 11 | 12 | try socials.forEach { socialName in 13 | if try Social.find(by: socialName) == nil { 14 | let social = Social(name: socialName) 15 | try social.save() 16 | } 17 | } 18 | 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Run/main.swift: -------------------------------------------------------------------------------- 1 | import App 2 | 3 | /// We have isolated all of our App's logic into 4 | /// the App module because it makes our app 5 | /// more testable. 6 | /// 7 | /// In general, the executable portion of our App 8 | /// shouldn't include much more code than is presented 9 | /// here. 10 | /// 11 | /// We simply initialize our Droplet, optionally 12 | /// passing in values if necessary 13 | /// Then, we pass it to our App's setup function 14 | /// this should setup all the routes and special 15 | /// features of our app 16 | /// 17 | /// .run() runs the Droplet's commands, 18 | /// if no command is given, it will default to "serve" 19 | let config = try Config() 20 | try config.setup() 21 | 22 | let drop = try Droplet(config) 23 | try drop.setup() 24 | 25 | try drop.run() 26 | -------------------------------------------------------------------------------- /Templates/LinuxMainAllTestsGenerate.stencil: -------------------------------------------------------------------------------- 1 | // sourcery:file:Tests/LinuxMain.swift 2 | #if os(Linux) 3 | import XCTest 4 | @testable import AppTests 5 | 6 | {% for type in types.classes|based:"TestCase" %} 7 | {% if not type.annotations.disableTests %}extension {{ type.name }} { 8 | static var allTests: [(String, ({{ type.name }}) -> () throws -> Void)] = [ 9 | {% for method in type.methods where method.parameters.count == 0 and method.shortName|hasPrefix:"test" %} 10 | ("{{ method.shortName }}", {{ method.shortName }}){% if not forloop.last %},{% endif %} 11 | {% endfor %} ] 12 | } 13 | {% endif %}{% endfor %} 14 | 15 | XCTMain([ 16 | {% for type in types.classes|based:"TestCase" %}{% if not type.annotations.disableTests %} testCase({{ type.name }}.allTests){% if not forloop.last %},{% endif %} 17 | {% endif %}{% endfor %}]) 18 | #endif 19 | // sourcery:end 20 | -------------------------------------------------------------------------------- /Tests/AppTests/Common/Extensions/Crypto/CryptoHasher+Compare.swift: -------------------------------------------------------------------------------- 1 | import Crypto 2 | @testable import Vapor 3 | 4 | // swiftlint:disable superfluous_disable_command 5 | // swiftlint:disable force_try 6 | extension CryptoHasher { 7 | 8 | static func compareFiles(filePath1: String, filePath2: String) throws -> Bool { 9 | let file1 = try! Vapor.FileManager.readBytesFromFile(filePath1) 10 | let file2 = try! Vapor.FileManager.readBytesFromFile(filePath2) 11 | 12 | let md5 = CryptoHasher(hash: .md5, encoding: .hex) 13 | 14 | let file1MD5 = try! md5.make(file1).makeString() 15 | let file2MD5 = try! md5.make(file2).makeString() 16 | 17 | return file1MD5 == file2MD5 18 | } 19 | 20 | static func compareFileAndBytes(filePath: String, bytes: Bytes) throws -> Bool { 21 | let file = try! Vapor.FileManager.readBytesFromFile(filePath) 22 | 23 | let md5 = CryptoHasher(hash: .md5, encoding: .hex) 24 | 25 | let fileMD5 = try! md5.make(file).makeString() 26 | let bytesMD5 = try! md5.make(bytes).makeString() 27 | 28 | return fileMD5 == bytesMD5 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Tests/AppTests/Common/Extensions/Foundation/String/String+RandomToken.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | @testable import App 3 | 4 | extension String { 5 | static var invalidRandomToken: String { 6 | let token = String.randomValue 7 | if ["test", "user"].contains(token) { 8 | return self.invalidRandomToken 9 | } 10 | return token 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Tests/AppTests/Common/Extensions/HTTP/Responder/Responder+Authorization.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import HTTP 3 | @testable import Vapor 4 | @testable import App 5 | 6 | //swiftlint:disable superfluous_disable_command 7 | //swiftlint:disable force_try 8 | //swiftlint:disable force_cast 9 | 10 | extension Responder { 11 | public func unauthorizedTestResponse( 12 | to method: HTTP.Method, 13 | at path: String, 14 | query: String? = nil, 15 | hostname: String = "0.0.0.0", 16 | headers: [HTTP.HeaderKey: String] = [:], 17 | body: BodyRepresentable? = nil, 18 | file: StaticString = #file, 19 | line: UInt = #line 20 | ) throws -> HTTP.Response { 21 | let request = Request.makeTest( 22 | method: method, 23 | headers: headers, 24 | body: body?.makeBody() ?? .data([]), 25 | hostname: hostname, 26 | path: path, 27 | query: query 28 | ) 29 | 30 | request.headers[.host] = hostname 31 | request.headers[.contentType] = TestConstants.Header.Value.applicationJson 32 | 33 | return try self.testResponse( 34 | to: request, 35 | file: file, 36 | line: line 37 | ) 38 | } 39 | 40 | public func clientAuthorizedTestResponse( 41 | to method: HTTP.Method, 42 | at path: String, 43 | query: String? = nil, 44 | hostname: String = "0.0.0.0", 45 | headers: [HTTP.HeaderKey: String] = [:], 46 | body: BodyRepresentable? = nil, 47 | file: StaticString = #file, 48 | line: UInt = #line 49 | ) throws -> HTTP.Response { 50 | 51 | let droplet = self as? Droplet 52 | let token = droplet?.config["app", "client-token"]?.string 53 | 54 | var appHeaders = headers 55 | appHeaders["client-token"] = token 56 | if appHeaders[TestConstants.Header.Key.contentType] == nil { 57 | appHeaders[TestConstants.Header.Key.contentType] = TestConstants.Header.Value.applicationJson 58 | } 59 | return try self.unauthorizedTestResponse( 60 | to: method, 61 | at: path, 62 | query: query, 63 | hostname: hostname, 64 | headers: appHeaders, 65 | body: body, 66 | file: file, 67 | line: line 68 | ) 69 | } 70 | 71 | public func userAuthorizedTestResponse( 72 | to method: HTTP.Method, 73 | at path: String, 74 | query: String? = nil, 75 | hostname: String = "0.0.0.0", 76 | headers: [HTTP.HeaderKey: String] = [:], 77 | body: BodyRepresentable? = nil, 78 | file: StaticString = #file, 79 | line: UInt = #line, 80 | bearer token: String? = nil 81 | ) throws -> HTTP.Response { 82 | 83 | var userHeaders = headers 84 | var bearerToken = "Bearer " 85 | 86 | if let token = token { 87 | bearerToken += token 88 | } else if body != nil { 89 | let json = body as! JSON 90 | let tokenFromBody = json["token"]?.string 91 | bearerToken += tokenFromBody! 92 | } else { 93 | let user = User() 94 | try! user.save() 95 | user.createSession() 96 | bearerToken += user.token! 97 | } 98 | 99 | userHeaders[HTTP.HeaderKey.authorization] = bearerToken 100 | return try self.clientAuthorizedTestResponse( 101 | to: method, 102 | at: path, 103 | query: query, 104 | hostname: hostname, 105 | headers: userHeaders, 106 | body: body, 107 | file: file, 108 | line: line 109 | ) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Tests/AppTests/Common/Extensions/Models/City+Random.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | @testable import App 3 | 4 | extension City { 5 | 6 | convenience init(_ randomlyInitialized: Bool = true) { 7 | if randomlyInitialized { 8 | self.init(cityName: String.randomValue) 9 | } else { 10 | self.init(cityName: "") 11 | } 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /Tests/AppTests/Common/Extensions/Models/Content+Random.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | @testable import App 3 | 4 | extension App.Content { 5 | 6 | convenience init(_ randomlyInitialized: Bool = true, speechId: Identifier) { 7 | if randomlyInitialized { 8 | self.init( 9 | speechId: speechId, 10 | title: String.randomValue, 11 | description: String.randomValue, 12 | link: String.randomValue, 13 | type: Bool.randomValue ? .video : .slide) 14 | } else { 15 | self.init( 16 | speechId: speechId, 17 | title: "", 18 | description: "", 19 | link: "", 20 | type: .video ) 21 | } 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /Tests/AppTests/Common/Extensions/Models/Creators+Random.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | @testable import App 3 | 4 | extension Creator { 5 | 6 | convenience init(_ randomlyInitialized: Bool = true, userId: Identifier) { 7 | if randomlyInitialized { 8 | self.init( 9 | userId: userId, 10 | position: Int.randomValue, 11 | info: String.randomValue, 12 | url: String.randomValue, 13 | active: Bool.randomValue 14 | ) 15 | } else { 16 | self.init( 17 | userId: userId, 18 | position: 0, 19 | info: "", 20 | url: "", 21 | active: true 22 | ) 23 | } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /Tests/AppTests/Common/Extensions/Models/Event+Random.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | @testable import App 3 | 4 | extension App.Event { 5 | 6 | convenience init(_ randomlyInitialized: Bool = true, endDate: Date? = nil, placeId: Identifier) { 7 | var date = Date() 8 | 9 | if randomlyInitialized { 10 | 11 | if endDate != nil { 12 | date = endDate! 13 | } 14 | 15 | self.init( 16 | title: String.randomValue, 17 | description: String.randomValue, 18 | photo: String.randomValue, 19 | placeId: placeId, 20 | isRegistrationOpen: Bool.randomValue, 21 | startDate: date.fiveHoursAgo, 22 | endDate: date, 23 | hide: Bool.randomValue 24 | ) 25 | } else { 26 | self.init( 27 | title: "", 28 | description: "", 29 | photo: "", 30 | placeId: placeId, 31 | startDate: date.fiveHoursAgo, 32 | endDate: date 33 | ) 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /Tests/AppTests/Common/Extensions/Models/Place+Random.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | @testable import App 3 | 4 | extension Place { 5 | 6 | convenience init(_ randomlyInitialized: Bool = true, cityId: Identifier) { 7 | if randomlyInitialized { 8 | self.init( 9 | title: String.randomValue, 10 | address: String.randomValue, 11 | description: String.randomValue, 12 | latitude: Double.randomValue, 13 | longitude: Double.randomValue, 14 | cityId: cityId 15 | ) 16 | } else { 17 | self.init( 18 | title: "", 19 | address: "", 20 | description: "", 21 | latitude: 0.0, 22 | longitude: 0.0, 23 | cityId: cityId 24 | ) 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Tests/AppTests/Common/Extensions/Models/RegField+Random.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | @testable import App 3 | 4 | extension RegField { 5 | 6 | convenience init(_ randomlyInitialized: Bool = true, 7 | fieldTypeNumber: Int = Int.random(min: 0, max: FieldType.count-1), 8 | regFormId: Identifier) { 9 | let regFieldType = ["checkbox", "radio", "string"] 10 | 11 | if randomlyInitialized { 12 | self.init( 13 | regFormId: regFormId, 14 | required: Bool.randomValue, 15 | name: String.randomValue, 16 | type: RegField.FieldType(regFieldType[fieldTypeNumber]), 17 | placeholder: String.randomValue, 18 | defaultValue: String.randomValue) 19 | } else { 20 | self.init( 21 | regFormId: regFormId, 22 | required: true, 23 | name: "", 24 | type: RegField.FieldType(regFieldType[0]), 25 | placeholder: "", 26 | defaultValue: "") 27 | } 28 | } 29 | 30 | } 31 | 32 | extension RegField.FieldType { 33 | static let count = 3 34 | } 35 | -------------------------------------------------------------------------------- /Tests/AppTests/Common/Extensions/Models/RegForm+Random.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | @testable import App 3 | 4 | extension RegForm { 5 | 6 | convenience init(_ randomlyInitialized: Bool = true, eventId: Identifier) { 7 | if randomlyInitialized { 8 | self.init( 9 | eventId: eventId, 10 | formName: String.randomValue, 11 | description: String.randomValue 12 | ) 13 | } else { 14 | self.init( 15 | eventId: eventId, 16 | formName: "", 17 | description: "" 18 | ) 19 | } 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /Tests/AppTests/Common/Extensions/Models/Rule+Initialization.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | @testable import App 3 | 4 | extension Rule { 5 | 6 | static var rules: [Rule]? { 7 | //swiftlint:disable force_try 8 | if try! Rule.count() < 1 { 9 | //swiftlint:enable force_try 10 | var result: [Rule]? = [] 11 | try? ["phone", "number", "alphanumeric", "email", "string"].forEach { 12 | let rule = Rule(type: Rule.ValidationRule($0)) 13 | try rule.save() 14 | result?.append(rule) 15 | } 16 | return result 17 | } 18 | 19 | return try? Rule.all() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Tests/AppTests/Common/Extensions/Models/Session+Random.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Foundation 3 | @testable import App 4 | 5 | extension Session { 6 | 7 | convenience init(_ randomlyInitialized: Bool = true, userId: Identifier) { 8 | if randomlyInitialized { 9 | self.init( 10 | userId: userId, 11 | token: UUID().uuidString 12 | ) 13 | } else { 14 | self.init( 15 | userId: userId, 16 | token: "" 17 | ) 18 | } 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Tests/AppTests/Common/Extensions/Models/Speech+Random.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | @testable import App 3 | 4 | extension Speech { 5 | 6 | convenience init(_ randomlyInitialized: Bool = true, eventId: Identifier) { 7 | if randomlyInitialized { 8 | self.init( 9 | eventId: eventId, 10 | title: String.randomValue, 11 | description: String.randomValue) 12 | } else { 13 | self.init( 14 | eventId: eventId, 15 | title: "", 16 | description: "") 17 | } 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /Tests/AppTests/Common/Extensions/Models/User+Random.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | @testable import App 3 | 4 | extension User { 5 | 6 | convenience init(_ randomlyInitialized: Bool = true) { 7 | if randomlyInitialized { 8 | self.init( 9 | name: String.randomValue, 10 | lastname: String.randomValue, 11 | company: String.randomValue, 12 | position: String.randomValue, 13 | photo: String.randomPhotoName, 14 | email: String.randomEmail, 15 | phone: String.randomPhone 16 | ) 17 | } else { 18 | self.init( 19 | name: "", 20 | lastname: "", 21 | company: "", 22 | position: "", 23 | photo: "", 24 | email: "", 25 | phone: "" 26 | ) 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Tests/AppTests/Common/Helpers/Constants/Constants.swift: -------------------------------------------------------------------------------- 1 | import HTTP 2 | 3 | struct TestConstants { 4 | 5 | struct Middleware { 6 | static let validToken = "test" 7 | } 8 | 9 | struct Header { 10 | 11 | struct Key { 12 | static let clientToken: HeaderKey = "client-token" 13 | static let contentType: HeaderKey = "Content-Type" 14 | static let userToken: HeaderKey = "user-token" 15 | } 16 | 17 | struct Value { 18 | static let applicationJson = "application/json" 19 | static let multipartFormData = "multipart/form-data" 20 | } 21 | } 22 | 23 | struct Path { 24 | static let userPhotosPath = "user_photos/" 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /Tests/AppTests/Common/Helpers/Controllers/Auth/FacebookAuthControllerTestHelper.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Foundation 3 | @testable import App 4 | 5 | //swiftlint:disable superfluous_disable_command 6 | //swiftlint:disable force_try 7 | final class FacebookAuthControllerTestHelper { 8 | 9 | static func getTestRequest(config: Config) throws -> JSON? { 10 | guard let token = config[Social.Nets.fb, "code"]?.string else { 11 | return nil 12 | } 13 | let result = try! JSON( 14 | node: [ 15 | "token": token, 16 | "social": Social.Nets.fb 17 | ]) 18 | return result 19 | } 20 | 21 | static func getUserInfoFromSocial(drop: Droplet) throws -> JSON? { 22 | 23 | let config = drop.config 24 | let userInfoUrl = config[Social.Nets.fb, "user_info_url"]?.string ?? "" 25 | let fields = config[Social.Nets.fb, "fields"]?.string ?? "" 26 | let scope = config[Social.Nets.fb, "scope"]?.string ?? "" 27 | let token = config[Social.Nets.fb, "access_token"]?.string ?? "" 28 | 29 | let userInfo = try drop.client.get(userInfoUrl, query: [ 30 | "fields": fields, 31 | "scope": scope, 32 | "access_token": token 33 | ]) 34 | 35 | guard 36 | let name = userInfo.json?["first_name"]?.string, 37 | let lastname = userInfo.json?["last_name"]?.string, 38 | let email = userInfo.json?["email"]?.string, 39 | let photo = userInfo.json?["picture", "data", "url"]?.string 40 | else { 41 | throw Abort(.badRequest, reason: "Can't get user profile from Facebook") 42 | } 43 | 44 | let Keys = User.Keys.self 45 | var json = JSON() 46 | try json.set(Keys.name, name) 47 | try json.set(Keys.lastname, lastname) 48 | try json.set(Keys.company, JSON.null) 49 | try json.set(Keys.position, JSON.null) 50 | if 51 | let url = URL(string: photo), 52 | let domen = drop.config["app", "domain"]?.string { 53 | try json.set(Keys.photoURL, "\(domen)/user_photos/1/\(url.lastPathComponent)") 54 | } else { 55 | try json.set(Keys.photoURL, photo) 56 | } 57 | try json.set(Keys.email, email) 58 | try json.set(Keys.phone, JSON.null) 59 | return json 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /Tests/AppTests/Common/Helpers/Controllers/Auth/GithubAuthControllerTestHelper.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Foundation 3 | @testable import App 4 | 5 | //swiftlint:disable superfluous_disable_command 6 | //swiftlint:disable force_try 7 | final class GithubAuthControllerTestHelper { 8 | 9 | static func getTestRequest() throws -> JSON? { 10 | let configFile = try! Config(arguments: ["vapor", "--env=test"]) 11 | let config = GithubConfig(configFile) 12 | guard 13 | let token = config.code, 14 | let secret = config.state 15 | else { 16 | return nil 17 | } 18 | let result = try! JSON( 19 | node: [ 20 | "token": token, 21 | "social": Social.Nets.github, 22 | "secret": secret 23 | ]) 24 | return result 25 | } 26 | 27 | static func getUserInfoFromSocial(drop: Droplet) throws -> JSON? { 28 | 29 | let config = GithubConfig(drop.config) 30 | let userInfo = try! drop.client.get(config.testUserInfoURL!) 31 | 32 | guard 33 | let response = userInfo.json, 34 | let login = response["login"]?.string, 35 | let photo = response["avatar_url"]?.string 36 | else { 37 | throw Abort(.badRequest, reason: "Can't get user profile from Github") 38 | } 39 | 40 | let fullName = response["name"]?.string ?? login 41 | let nameComponents = fullName.components(separatedBy: " ") 42 | let name = nameComponents[0] 43 | let lastname = nameComponents[safe: 1] 44 | 45 | let Keys = User.Keys.self 46 | var json = JSON() 47 | try json.set(Keys.name, name) 48 | try json.set(Keys.lastname, lastname) 49 | try json.set(Keys.company, JSON.null) 50 | try json.set(Keys.position, JSON.null) 51 | if 52 | let url = URL(string: photo), 53 | let domen = drop.config["app", "domain"]?.string { 54 | try json.set(Keys.photo, "\(domen)/user_photos/1/\(url.lastPathComponent)") 55 | } else { 56 | try json.set(Keys.photo, photo) 57 | } 58 | try json.set(Keys.email, JSON.null) 59 | try json.set(Keys.phone, JSON.null) 60 | return json 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /Tests/AppTests/Common/Helpers/Controllers/Auth/SocialAuthControllerTestHelper.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Foundation 3 | @testable import App 4 | 5 | //swiftlint:disable superfluous_disable_command 6 | //swiftlint:disable force_try 7 | final class SocialAuthControllerTestHelper { 8 | 9 | static func getUserSessionToken(by response: JSON) throws -> String? { 10 | guard 11 | let userId = response[User.Keys.id]?.int, 12 | let user = try! User.find(userId), 13 | let session = try! user.session() 14 | else { 15 | return nil 16 | } 17 | 18 | return session.token 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Tests/AppTests/Common/Helpers/Controllers/Auth/VkontakteAuthControllerTestHelper.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Foundation 3 | @testable import App 4 | 5 | //swiftlint:disable superfluous_disable_command 6 | //swiftlint:disable force_try 7 | final class VkontakteAuthControllerTestHelper { 8 | 9 | static func getTestRequest() throws -> JSON? { 10 | let configFile = try! Config(arguments: ["vapor", "--env=test"]) 11 | let config = VkontakteConfig(configFile) 12 | let token = config.accessToken 13 | let secret = config.secret 14 | 15 | let result = try! JSON( 16 | node: [ 17 | "token": token, 18 | "social": Social.Nets.vk, 19 | "secret": secret 20 | ]) 21 | return result 22 | } 23 | 24 | static func getUserInfoFromSocial(drop: Droplet) throws -> JSON? { 25 | 26 | let configFile = try! Config(arguments: ["vapor", "--env=test"]) 27 | let config = VkontakteConfig(configFile) 28 | 29 | let apiURL = config.apiURL 30 | let fields = config.fields 31 | let method = config.method 32 | let token = config.accessToken 33 | let secret = config.secret 34 | 35 | let signature = try! config.getSignatureBased(on: token, and: secret) 36 | let userInfoUrl = apiURL + method 37 | let userInfo = try! drop.client.get(userInfoUrl, query: [ 38 | "fields": fields, 39 | "access_token": token, 40 | "sig": signature, 41 | "v": "5.73" 42 | ]) 43 | 44 | guard 45 | let response = userInfo.json?["response"]?.array?.first, 46 | let name = response["first_name"]?.string, 47 | let lastname = response["last_name"]?.string, 48 | let photo = response["photo_max"]?.string 49 | else { 50 | throw Abort(.badRequest, reason: "Can't get user profile from Vkontakte") 51 | } 52 | 53 | let Keys = User.Keys.self 54 | var json = JSON() 55 | try json.set(Keys.name, name) 56 | try json.set(Keys.lastname, lastname) 57 | try json.set(Keys.company, JSON.null) 58 | try json.set(Keys.position, JSON.null) 59 | if let url = URL(string: photo) { 60 | try json.set(Keys.photoURL, "user_photos/1/\(url.lastPathComponent)") 61 | } else { 62 | try json.set(Keys.photoURL, photo) 63 | } 64 | try json.set(Keys.email, JSON.null) 65 | try json.set(Keys.phone, JSON.null) 66 | return json 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /Tests/AppTests/Common/Helpers/Controllers/Event/EventHelper.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Foundation 3 | @testable import App 4 | 5 | //swiftlint:disable superfluous_disable_command 6 | //swiftlint:disable force_try 7 | final class EventHelper { 8 | 9 | static var invalidQueryKeyParameter: String { 10 | let parameter = String.randomValue 11 | if ["before", "after"].contains(parameter) { 12 | return self.invalidQueryKeyParameter 13 | } 14 | return parameter 15 | } 16 | 17 | static var invalidQueryValueParameter: String { 18 | let parameter = String.randomValue 19 | if DateFormatter.mysql.date(from: parameter) != nil { 20 | return self.invalidQueryValueParameter 21 | } 22 | return parameter 23 | } 24 | 25 | static func storePastEvent() throws -> Identifier? { 26 | return try storeEvent(date: Date.randomValueInPast) 27 | } 28 | 29 | static func storeComingEvent() throws -> Identifier? { 30 | return try! storeEvent(date: Date.randomValueInFuture) 31 | } 32 | 33 | static func storeEvent(date: Date = Date.randomValue) throws -> Identifier? { 34 | let city = City() 35 | try! city.save() 36 | 37 | let place = Place(cityId: city.id!) 38 | try! place.save() 39 | 40 | let event = App.Event(endDate: date, placeId: place.id!) 41 | try! event.save() 42 | 43 | return event.id 44 | } 45 | 46 | static func findEvent(by id: Identifier?) throws -> App.Event? { 47 | return try! Event.find(id) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Tests/AppTests/Common/Helpers/Controllers/Event/EventSpeechHelper.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | @testable import App 3 | //swiftlint:disable superfluous_disable_command 4 | //swiftlint:disable force_try 5 | class EventSpeechHelper { 6 | 7 | static var invalidParameterKey: String { 8 | let parameter = String.randomValue 9 | if parameter == "id" { 10 | return self.invalidParameterKey 11 | } 12 | return parameter 13 | } 14 | 15 | static var invalidParameterValue: String { 16 | let parameter = String.randomValue 17 | if parameter.int != nil { 18 | return self.invalidParameterValue 19 | } 20 | return parameter 21 | } 22 | 23 | static func storeSpeech( 24 | for eventId: Identifier, 25 | speakersCount: Int = 2, 26 | contentCount: Int = 2 27 | ) throws { 28 | let speech = Speech(eventId: eventId) 29 | try! speech.save() 30 | 31 | for _ in 0.. [App.Event]? { 13 | 14 | var cityIds: [Identifier] = [] 15 | var placeIds: [Identifier] = [] 16 | var events: [App.Event] = [] 17 | let randomRange = 1...Int.random(min: 10, max: 20) 18 | let months = 24 19 | 20 | if cities.isEmpty { 21 | for _ in randomRange { 22 | let city = City() 23 | try! city.save() 24 | cityIds.append(city.id!) 25 | } 26 | } else { 27 | cityIds = cities.map { city in city.id! } 28 | } 29 | 30 | if places.isEmpty { 31 | for _ in randomRange { 32 | let place = Place(true, cityId: cityIds.randomValue) 33 | try! place.save() 34 | placeIds.append(place.id!) 35 | } 36 | } else { 37 | placeIds = places.map { place in place.id!} 38 | } 39 | 40 | for i in 0...months-1 { 41 | 42 | guard 43 | let date = Calendar.current.date(byAdding: .month, value: -i, to: Date()), 44 | let date1 = Calendar.current.date(byAdding: .day, value: -6, to: date), 45 | let date2 = Calendar.current.date(byAdding: .day, value: -12, to: date) 46 | else { 47 | return nil 48 | } 49 | 50 | let event1 = App.Event(endDate: date1, placeId: placeIds.randomValue) 51 | let event2 = App.Event(endDate: date2, placeId: placeIds.randomValue) 52 | 53 | try! event1.save() 54 | try! event2.save() 55 | 56 | events.append(event1) 57 | events.append(event2) 58 | } 59 | 60 | return events 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /Tests/AppTests/Common/Helpers/Controllers/Registration/RegFieldAnswerHelper.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Fluent 3 | @testable import App 4 | //swiftlint:disable superfluous_disable_command 5 | //swiftlint:disable force_try 6 | final class RegFieldAnswerHelper { 7 | 8 | static func assertRegFieldAnswerHasExpectedFields(_ regFieldAnswer: JSON) throws -> Bool { 9 | return regFieldAnswer["id"]?.int != nil && regFieldAnswer["value"]?.string != nil 10 | } 11 | 12 | @discardableResult 13 | static func store(for regFields: [RegField]) throws -> [RegFieldAnswer]? { 14 | 15 | var regFieldAnswers: [RegFieldAnswer] = [] 16 | 17 | for regField in regFields { 18 | 19 | guard let regField = regField.id else { 20 | return nil 21 | } 22 | 23 | for _ in 1...Int.random(min: 2, max: 5) { 24 | let regFieldAnswer = RegFieldAnswer( 25 | value: String.randomValue, 26 | regFieldId: regField) 27 | try! regFieldAnswer.save() 28 | regFieldAnswers.append(regFieldAnswer) 29 | } 30 | 31 | } 32 | 33 | return regFieldAnswers 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Tests/AppTests/Common/Helpers/Controllers/Registration/RegFieldHelper.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Fluent 3 | @testable import App 4 | 5 | // swiftlint:disable superfluous_disable_command 6 | // swiftlint:disable force_try 7 | final class RegFieldHelper { 8 | 9 | static func assertRegFieldHasExpectedFields(_ regFields: JSON) throws -> Bool { 10 | return 11 | regFields["id"]?.int != nil && 12 | regFields["name"]?.string != nil && 13 | regFields["default_value"]?.string != nil && 14 | regFields["required"]?.bool != nil && 15 | regFields["type"]?.string != nil && 16 | regFields["placeholder"]?.string != nil && 17 | regFields["field_answers"]?.makeJSON() != nil 18 | 19 | } 20 | 21 | static func store(for regForms: [RegForm]) throws -> [RegField]? { 22 | 23 | var regFields: [RegField]! = [] 24 | for regForm in regForms { 25 | guard let regFormId = regForm.id else { 26 | return nil 27 | } 28 | let count = RegField.FieldType.count 29 | for i in 0...Int.randomValue(min: count, max: 4) { 30 | // we use 'i % count' to cover during the test all field types 31 | let regField = RegField(fieldTypeNumber: i % count, regFormId: regFormId) 32 | try! regField.save() 33 | regFields.append(regField) 34 | } 35 | let regField = regFields.randomValue 36 | regField.required = true 37 | try! regField.save() 38 | } 39 | return regFields 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /Tests/AppTests/Common/Helpers/Controllers/Registration/RegFieldRuleHelper.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Fluent 3 | 4 | @testable import App 5 | 6 | final class RegFieldRuleHelper { 7 | 8 | static func assertRegFieldRuleHasExpectedFields(_ rules: JSON) throws -> Bool { 9 | return rules["id"]?.int != nil && rules["type"]?.string != nil 10 | } 11 | 12 | static func addRules(to regFields: [RegField]) throws -> [RegField]? { 13 | 14 | guard let rules = Rule.rules else { 15 | return nil 16 | } 17 | 18 | var result: [RegField]! = [] 19 | for regField in regFields { 20 | let rule1 = rules[Int.randomValue(min: 0, max: 2)] 21 | try regField.rules.add(rule1) 22 | if Bool.randomValue { 23 | let rule2 = rules[Int.randomValue(min: 3, max: 4)] 24 | try regField.rules.add(rule2) 25 | } 26 | result.append(regField) 27 | } 28 | 29 | return result 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Tests/AppTests/Common/Helpers/Controllers/Registration/RegFormHelper.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | @testable import App 3 | 4 | typealias EventId = Identifier 5 | 6 | //swiftlint:disable superfluous_disable_command 7 | //swiftlint:disable force_try 8 | final class RegFormHelper { 9 | 10 | static func assertRegFromHasExpectedFields(_ regForm: JSON) throws -> Bool { 11 | return 12 | regForm["id"]?.int != nil && 13 | regForm["form_name"]?.string != nil && 14 | regForm["description"]?.string != nil && 15 | regForm["reg_fields"]?.makeJSON() != nil 16 | } 17 | 18 | @discardableResult 19 | static func store(for events: [App.Event] = []) throws -> [RegForm]? { 20 | var regForms: [RegForm] = [] 21 | if events.isEmpty { 22 | var cityIds: [Identifier] = [] 23 | var placeIds: [Identifier] = [] 24 | var eventIds: [Identifier] = [] 25 | let randomRange = 1...Int.random(min: 10, max: 20) 26 | 27 | for _ in randomRange { 28 | let city = City() 29 | try city.save() 30 | cityIds.append(city.id!) 31 | } 32 | 33 | for _ in randomRange { 34 | let place = Place(true, cityId: cityIds.randomValue) 35 | try place.save() 36 | placeIds.append(place.id!) 37 | } 38 | 39 | for _ in randomRange { 40 | let event = Event(placeId: placeIds.randomValue) 41 | try! event.save() 42 | eventIds.append(event.id!) 43 | } 44 | 45 | let shuflleEventId = eventIds.shuffled() 46 | for i in randomRange { 47 | let regForm = RegForm(eventId: shuflleEventId[i-1]) 48 | try regForm.save() 49 | regForms.append(regForm) 50 | } 51 | } else { 52 | 53 | for event in events { 54 | let regForm = RegForm(eventId: event.id!) 55 | try regForm.save() 56 | regForms.append(regForm) 57 | } 58 | 59 | } 60 | return regForms 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /Tests/AppTests/Common/Helpers/Controllers/Registration/UserSessionHelper.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import Fluent 3 | @testable import App 4 | // swiftlint:disable superfluous_disable_command 5 | // swiftlint:disable force_try 6 | 7 | // swiftlint:disable superfluous_disable_command 8 | // swiftlint:disable force_try 9 | final class UserSessionHelper { 10 | 11 | static func store() throws { 12 | 13 | for _ in 1...Int.random(min: 2, max: 10) { 14 | 15 | let user = User() 16 | try! user.save() 17 | user.createSession() 18 | 19 | } 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /Tests/AppTests/Controllers/Event/Registration/GetRegFormTest.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Testing 3 | import HTTP 4 | import Sockets 5 | import Fluent 6 | @testable import Vapor 7 | @testable import App 8 | 9 | // swiftlint:disable superfluous_disable_command 10 | // swiftlint:disable force_try 11 | class GetRegFormTests: TestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | do { 16 | try drop.truncateTables() 17 | } catch { 18 | XCTFail("Droplet set raise exception: \(error.localizedDescription)") 19 | return 20 | } 21 | } 22 | 23 | func testThatRegFormGetNotFoundForWrongEventId() throws { 24 | let wrongEventId = -1 25 | try drop 26 | .userAuthorizedTestResponse(to: .get, at: "api/event/\(wrongEventId)/form") 27 | .assertStatus(is: .internalServerError) 28 | .assertJSON("reason", contains: "Can't find RegForm by event_id:") 29 | } 30 | 31 | func testThatRegFormGetBadReguestForBadEventId() throws { 32 | let wrongEventId = "1,3" 33 | try drop 34 | .userAuthorizedTestResponse(to: .get, at: "api/event/\(wrongEventId)/form") 35 | .assertStatus(is: .badRequest) 36 | .assertJSON("reason", contains: "EventId parameters is missing in URL request") 37 | } 38 | 39 | func testThatRegFormHasExpectedFields() throws { 40 | guard let regForm = try! RegFormHelper.store()?.first else { 41 | XCTFail("Can't get RegForms") 42 | return 43 | } 44 | XCTAssertTrue(try RegFormHelper.assertRegFromHasExpectedFields(regForm.makeJSON())) 45 | } 46 | 47 | func testThatRegFieldLinkedWithRegFormAndHasExpectedFields() throws { 48 | guard let regForms = try! RegFormHelper.store() else { 49 | XCTFail("Can't get RegForms") 50 | return 51 | } 52 | 53 | guard let regField = try! RegFieldHelper.store(for: regForms)?.first else { 54 | XCTFail("Can't get RegField") 55 | return 56 | } 57 | 58 | XCTAssertTrue(try RegFieldHelper.assertRegFieldHasExpectedFields(regField.makeJSON())) 59 | } 60 | 61 | func testThatRegFieldAnswerLinkedWithRegFieldAndHasExpectedFields() throws { 62 | guard let regForms = try! RegFormHelper.store() else { 63 | XCTFail("Can't get RegForms") 64 | return 65 | } 66 | 67 | guard let regFields = try! RegFieldHelper.store(for: regForms) else { 68 | XCTFail("Can't get RegFields") 69 | return 70 | } 71 | 72 | guard let regFiedAnswer = try! RegFieldAnswerHelper.store(for: regFields)?.first else { 73 | XCTFail("Can't get RegFiedAnswer") 74 | return 75 | } 76 | 77 | XCTAssertTrue(try RegFieldAnswerHelper.assertRegFieldAnswerHasExpectedFields(regFiedAnswer.makeJSON())) 78 | } 79 | 80 | func testThatRegFormFetchedByEventId() throws { 81 | //arrange 82 | 83 | guard let regForm = try! RegFormHelper.store()?.random else { 84 | XCTFail("Can't get RegForms") 85 | return 86 | } 87 | 88 | guard let regFields = try! RegFieldHelper.store(for: [regForm]) else { 89 | XCTFail("Can't get RegFields") 90 | return 91 | } 92 | 93 | guard try! RegFieldAnswerHelper.store(for: regFields) != nil else { 94 | XCTFail("Can't get RegFiedAnswer") 95 | return 96 | } 97 | 98 | guard let eventId = regForm.eventId.int else { 99 | XCTFail("Can't get eventId for RegForm with id \(regForm.id?.int ?? -1)") 100 | return 101 | } 102 | 103 | let expected = try regForm.makeResponse().json! 104 | 105 | try! drop 106 | //act 107 | .userAuthorizedTestResponse(to: .get, at: "api/event/\(eventId)/form") 108 | //assert 109 | .assertStatus(is: .ok) 110 | .assertJSON("", equals: expected) 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /Tests/AppTests/Controllers/User/Auth/UserAuthByTokenTest.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Testing 3 | import HTTP 4 | import Sockets 5 | import Fluent 6 | @testable import Vapor 7 | @testable import App 8 | 9 | // swiftlint:disable superfluous_disable_command 10 | // swiftlint:disable force_try 11 | class UserAuthByTokenTest: TestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | do { 16 | try drop.truncateTables() 17 | } catch { 18 | XCTFail("Droplet set raise exception: \(error.localizedDescription)") 19 | return 20 | } 21 | } 22 | 23 | func testThatGotUnauthorizedWithEmptyAccessToken() throws { 24 | try! createUser() 25 | let res = try! drop.clientAuthorizedTestResponse(to: .get, at: "api/user/1") 26 | res.assertStatus(is: .unauthorized) 27 | } 28 | 29 | func testThatGotUnauthorizedWithIncorrectAccessToken() throws { 30 | let user = try! createUser() 31 | 32 | let token = user.token! 33 | let bearer = "Bearer " + token 34 | 35 | let clientToken = drop.config["server", "client-token"]?.string ?? "" 36 | 37 | let headers: [HeaderKey: String] = [ 38 | "client-token": clientToken, 39 | HeaderKey.authorization: bearer, 40 | HeaderKey.contentType: TestConstants.Header.Value.applicationJson 41 | ] 42 | 43 | guard let session = try App.Session.find(by: user) else { 44 | XCTFail("Can't get session for User") 45 | return 46 | } 47 | session.token = try App.Session.generateToken() 48 | try session.save() 49 | 50 | try! drop.clientAuthorizedTestResponse( 51 | to: .get, 52 | at: "api/user/1", 53 | headers: headers) 54 | .assertStatus(is: .unauthorized) 55 | } 56 | 57 | func testThatGotAccessWithCorrectAccessToken() throws { 58 | let user = try! createUser() 59 | 60 | let token = user.token! 61 | let bearer = "Bearer " + token 62 | 63 | let clientToken = drop.config["server", "client-token"]?.string ?? "" 64 | 65 | let headers: [HeaderKey: String] = [ 66 | "client-token": clientToken, 67 | HeaderKey.authorization: bearer, 68 | HeaderKey.contentType: TestConstants.Header.Value.applicationJson 69 | ] 70 | 71 | let res = try! drop.clientAuthorizedTestResponse( 72 | to: .get, 73 | at: "api/user/1", 74 | headers: headers) 75 | 76 | res.assertStatus(is: .ok) 77 | 78 | guard let returnedJSON = res.json else { 79 | XCTFail("Can't get JSON for User") 80 | return 81 | } 82 | 83 | let json = try! user.makeJSON() 84 | guard 85 | let photoURL = json["photo_url"]?.string else { 86 | XCTFail("Can't get JSON for photo_url") 87 | return 88 | } 89 | 90 | var returned = returnedJSON 91 | try! returned.set("photo_url", photoURL) 92 | XCTAssertEqual(returned, try! user.makeJSON()) 93 | } 94 | 95 | } 96 | 97 | extension UserAuthByTokenTest { 98 | @discardableResult 99 | func createUser() throws -> User { 100 | let user = User() 101 | try user.save() 102 | user.createSession() 103 | return user 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Tests/AppTests/Controllers/User/CreatorsControllerTest.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Testing 3 | import HTTP 4 | import Sockets 5 | import Fluent 6 | @testable import Vapor 7 | @testable import App 8 | 9 | // swiftlint:disable superfluous_disable_command 10 | // swiftlint:disable force_try 11 | class CreatorsControllerTest: TestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | do { 16 | try drop.truncateTables() 17 | } catch { 18 | XCTFail("Droplet set raise exception: \(error.localizedDescription)") 19 | return 20 | } 21 | } 22 | 23 | func testThatGetCreatorsReturnsEqualStoredCreatorsCount() throws { 24 | 25 | guard let count = try! generateCreators() else { 26 | XCTFail("Can't generate creators") 27 | return 28 | } 29 | 30 | let response = try! getCreators() 31 | response.assertStatus(is: .ok) 32 | 33 | guard 34 | let json = response.json, 35 | let creators = json.array 36 | else { 37 | XCTFail("Can't get creators count from JSON") 38 | return 39 | } 40 | 41 | let storedCreatorsCount = creators.count 42 | XCTAssert(storedCreatorsCount == count, "Expected count \(count), recived count: '\(storedCreatorsCount)'") 43 | 44 | } 45 | 46 | } 47 | 48 | extension CreatorsControllerTest { 49 | 50 | func getCreators() throws -> Response { 51 | return try! drop.clientAuthorizedTestResponse( 52 | to: .get, 53 | at: "api/user/creators" 54 | ) 55 | } 56 | 57 | func generateCreators() throws -> Int? { 58 | 59 | let count = Int.random(min: 3, max: 15) 60 | 61 | for _ in 1...count { 62 | let user = User() 63 | try! user.save() 64 | let creator = Creator(userId: user.id!) 65 | try! creator.save() 66 | } 67 | 68 | return count 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /Tests/AppTests/Controllers/User/GiveSpeechControllerTest.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Testing 3 | import HTTP 4 | import Sockets 5 | import Fluent 6 | @testable import Vapor 7 | @testable import App 8 | 9 | // swiftlint:disable superfluous_disable_command 10 | // swiftlint:disable force_try 11 | class GiveSeechControllerTest: TestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | do { 16 | try drop.truncateTables() 17 | } catch { 18 | XCTFail("Droplet set raise exception: \(error.localizedDescription)") 19 | return 20 | } 21 | } 22 | 23 | func testThatGiveSpeechIsPosted() throws { 24 | 25 | let user = User() 26 | try! user.save() 27 | user.createSession() 28 | 29 | let title = String.randomValue 30 | let description = String.randomValue + "\n" + String.randomValue + "\n" + String.randomValue 31 | 32 | let body = try! JSON( node:[ 33 | "title": title, 34 | "description": description 35 | ]) 36 | 37 | try! postSpeech(for: user, with: body).assertStatus(is: .ok) 38 | 39 | let speeches = try! GiveSpeech.makeQuery() 40 | .filter(GiveSpeech.Keys.title, title) 41 | .filter(GiveSpeech.Keys.description, description) 42 | .all() 43 | 44 | XCTAssert(speeches.count == 1, "Expected count 1, recived count: '\(speeches.count)'") 45 | 46 | let speakerJSON = try! speeches.first?.user()?.makeJSON() 47 | 48 | XCTAssertEqual(speakerJSON!, try! user.makeJSON()) 49 | 50 | } 51 | 52 | } 53 | 54 | extension GiveSeechControllerTest { 55 | 56 | func postSpeech(for user: User, with body: JSON) throws -> Response { 57 | return try! drop.userAuthorizedTestResponse( 58 | to: .post, 59 | at: "api/user/give-speech", 60 | body: body, 61 | bearer: user.token!) 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /Tests/AppTests/Controllers/User/PushNotificationControllerTest.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Testing 3 | import HTTP 4 | import Sockets 5 | import Fluent 6 | @testable import Vapor 7 | @testable import App 8 | 9 | // swiftlint:disable superfluous_disable_command 10 | // swiftlint:disable force_try 11 | class PushNotificationControllerTest: TestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | do { 16 | try drop.truncateTables() 17 | } catch { 18 | XCTFail("Droplet set raise exception: \(error.localizedDescription)") 19 | return 20 | } 21 | } 22 | 23 | func testThatPushTokenIsRegistered() throws { 24 | 25 | let user = try! createUser() 26 | 27 | let pushToken = String.randomValue 28 | 29 | let body = try! JSON(node: ["push_token": pushToken]) 30 | 31 | try! subscribeNotification(for: user, with: body).assertStatus(is: .ok) 32 | 33 | let count = try! App.Client.makeQuery() 34 | .filter(App.Client.Keys.pushToken, pushToken) 35 | .filter(App.Client.Keys.userId, 1).count() 36 | 37 | XCTAssert(count == 1, "Expected count 1, recived count: '\(count)'") 38 | } 39 | 40 | func testThatPushTokenIsRegisteredOnlyOnceForCertainUser() throws { 41 | 42 | let user = try! createUser() 43 | 44 | // create 45 | let pushToken = String.randomValue 46 | let body = try! JSON(node: ["push_token": pushToken]) 47 | 48 | try! subscribeNotification(for: user, with: body).assertStatus(is: .ok) 49 | 50 | let count = try! App.Client.makeQuery() 51 | .filter(App.Client.Keys.pushToken, pushToken) 52 | .filter(App.Client.Keys.userId, 1).count() 53 | 54 | XCTAssert(count == 1, "Expected count 1, recived count: '\(count)'") 55 | 56 | // update 57 | let pushTokenUpdated = String.randomValue 58 | let bodyUpdated = try! JSON(node: ["push_token": pushTokenUpdated]) 59 | 60 | try! subscribeNotification(for: user, with: bodyUpdated).assertStatus(is: .ok) 61 | 62 | let countUpdated = try! App.Client.makeQuery() 63 | .filter(App.Client.Keys.pushToken, pushTokenUpdated) 64 | .filter(App.Client.Keys.userId, 1).count() 65 | 66 | XCTAssert(countUpdated == 1, "Expected count 1, recived count: '\(countUpdated)'") 67 | 68 | } 69 | 70 | func testThatPushNotificationIsCanceled() throws { 71 | 72 | let user = try! createUser() 73 | 74 | let pushToken = String.randomValue 75 | 76 | let body = try! JSON(node: ["push_token": pushToken]) 77 | 78 | try! subscribeNotification(for: user, with: body).assertStatus(is: .ok) 79 | 80 | let count = try! App.Client.makeQuery() 81 | .filter(App.Client.Keys.pushToken, pushToken) 82 | .filter(App.Client.Keys.userId, 1).count() 83 | 84 | XCTAssert(count == 1, "Expected count 1, recived count: '\(count)'") 85 | 86 | try! cancelNotification(for: user).assertStatus(is: .ok) 87 | 88 | let countAfterCancelNotification = try! App.Client.makeQuery() 89 | .filter(App.Client.Keys.pushToken, pushToken) 90 | .filter(App.Client.Keys.userId, 1).count() 91 | 92 | XCTAssert(countAfterCancelNotification == 0, "Expected count 0, recived count: '\(countAfterCancelNotification)'") 93 | } 94 | 95 | } 96 | 97 | extension PushNotificationControllerTest { 98 | 99 | func createUser() throws -> User { 100 | let user = User() 101 | try user.save() 102 | user.createSession() 103 | return user 104 | } 105 | 106 | func subscribeNotification(for user: User, with body: JSON) throws -> Response { 107 | return try! drop.userAuthorizedTestResponse( 108 | to: .post, 109 | at: "api/user/notification", 110 | body: body, 111 | bearer: user.token!) 112 | } 113 | 114 | func cancelNotification(for user: User) throws -> Response { 115 | 116 | guard let userId: Int = user.id?.int else { 117 | XCTFail("Can't got userId") 118 | return Response(status: .internalServerError) 119 | } 120 | 121 | return try! drop.userAuthorizedTestResponse( 122 | to: .delete, 123 | at: "api/user/notification/\(userId)", 124 | bearer: user.token!) 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /Tests/AppTests/Middleware/ClientMiddlewareTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Testing 3 | @testable import Vapor 4 | @testable import App 5 | 6 | class ClientMiddlewareTests: TestCase { 7 | //swiftlint:disable force_try 8 | let droplet = try! Droplet.testable() 9 | //swiftlint:enable force_try 10 | let validToken = TestConstants.Middleware.validToken 11 | let generatedToken = String.invalidRandomToken 12 | 13 | func testThatConfigInitializationFailWithoutToken() { 14 | var config = droplet.config 15 | config.removeKey("app.client-token") 16 | XCTAssertThrowsError(try ClientMiddleware(config: config)) 17 | } 18 | 19 | func testThatConfigInitializationFailWithEmptyToken() throws { 20 | var config = droplet.config 21 | try config.set("app.client-token", "") 22 | XCTAssertThrowsError(try ClientMiddleware(config: config)) 23 | } 24 | 25 | func testThatFailedConfigInitializationThrowsProperError() throws { 26 | var config = droplet.config 27 | try config.set("app.client-token", "") 28 | XCTAssertThrowsError(try ClientMiddleware(config: config)) { error in 29 | XCTAssertEqual(error as? MiddlewareError, MiddlewareError.missingClientToken) 30 | } 31 | } 32 | 33 | func testThatConfigInitializationPassWithAnyNonEmptyToken() throws { 34 | var config = droplet.config 35 | try config.set("app.client-token", generatedToken) 36 | XCTAssertNoThrow(try ClientMiddleware(config: config)) 37 | } 38 | 39 | func testThatTokenIsAssignedFromConfigInitialization() throws { 40 | var config = droplet.config 41 | try config.set("app.client-token", generatedToken) 42 | let middleware = try ClientMiddleware(config: config) 43 | XCTAssertEqual(middleware.token, generatedToken) 44 | } 45 | 46 | func testThatResponsePassWithCoincidentToken() throws { 47 | let middleware = ClientMiddleware(generatedToken) 48 | let request = Request(method: .get, uri: "hello", headers: ["client-token": generatedToken]) 49 | let responder = ResponderStub(.notFound) 50 | 51 | let response = try middleware.respond(to: request, chainingTo: responder) 52 | XCTAssertEqual(response.status, responder.status) 53 | } 54 | 55 | func testThatResponseFailWithIncoincidentToken() throws { 56 | let middleware = ClientMiddleware(generatedToken) 57 | let request = Request(method: .get, uri: "hello", headers: ["client-token": validToken]) 58 | let responder = ResponderStub() 59 | 60 | let response = try middleware.respond(to: request, chainingTo: responder) 61 | XCTAssertEqual(response.status, .unauthorized) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Tests/AppTests/RouteTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Foundation 3 | import Testing 4 | import HTTP 5 | @testable import Vapor 6 | @testable import App 7 | 8 | class RouteTests: TestCase { 9 | 10 | override func setUp() { 11 | super.setUp() 12 | do { 13 | try drop.truncateTables() 14 | } catch { 15 | XCTFail("Droplet set raise exception: \(error.localizedDescription)") 16 | return 17 | } 18 | } 19 | 20 | func testThatRequestWithNoClientTokenFails() throws { 21 | try drop 22 | .unauthorizedTestResponse(to: .get, at: "test/hello") 23 | .assertStatus(is: .unauthorized) 24 | } 25 | 26 | func testThatAuthorizedRequestPasses() throws { 27 | try drop 28 | .clientAuthorizedTestResponse(to: .get, at: "test/hello") 29 | .assertStatus(is: .ok) 30 | } 31 | 32 | func testThatRequestWithInvalidClientTokenFails() throws { 33 | let randomToken = String.invalidRandomToken 34 | try drop 35 | .unauthorizedTestResponse(to: .get, at: "test/hello", headers: ["client-token": randomToken]) 36 | .assertStatus(is: .unauthorized) 37 | } 38 | 39 | func testThatRequestToHelloReturnsProperData() throws { 40 | try drop 41 | .clientAuthorizedTestResponse(to: .get, at: "test/hello") 42 | .assertStatus(is: .ok) 43 | .assertJSON("hello", equals: "world") 44 | } 45 | 46 | func testThatRequestToPlainTextReturnsProperData() throws { 47 | try drop 48 | .clientAuthorizedTestResponse(to: .get, at: "test/plaintext") 49 | .assertStatus(is: .ok) 50 | .assertBody(contains: "Hello, world!") 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Tests/AppTests/Stubs/ResponderStub.swift: -------------------------------------------------------------------------------- 1 | import HTTP 2 | 3 | final class ResponderStub: Responder { 4 | let status: Status 5 | init(_ status: Status = .ok) { 6 | self.status = status 7 | } 8 | func respond(to request: Request) throws -> Response { 9 | return Response(status: status) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Tests/AppTests/Utilities.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | import Testing 4 | import FluentProvider 5 | @testable import App 6 | @testable import Vapor 7 | 8 | // swiftlint:disable superfluous_disable_command 9 | // swiftlint:disable force_try 10 | 11 | var drop: Droplet! = try? Droplet.testable() 12 | 13 | private var _tableList: [String]? 14 | 15 | private var tableForTruncate: [String] { 16 | 17 | if _tableList != nil { 18 | return _tableList! 19 | } 20 | 21 | let db = try! drop.assertDatabase() 22 | 23 | guard let nodes = try! db.driver.makeConnection(.read).raw("SHOW TABLES;").array else { 24 | XCTFail("Can't get tables list") 25 | return [] 26 | } 27 | 28 | let jsons = nodes.map { (node) -> JSON in 29 | return JSON(node: node) 30 | } 31 | 32 | let dbName = drop.config["mysql", "database"]?.string ?? "" 33 | _tableList = jsons 34 | .map { (json) -> String in 35 | json["Tables_in_\(dbName)"]?.string ?? "" 36 | } 37 | .filter({ (table) -> Bool in 38 | table != "social" 39 | }) 40 | 41 | return _tableList! 42 | } 43 | 44 | extension Droplet { 45 | static func testable() throws -> Droplet { 46 | 47 | func recreateDatabase(_ config: Config) throws { 48 | let dbName = config["mysql", "database"]?.string ?? "" 49 | let driver = try! config.resolveDriver() 50 | let connection = try! driver.makeConnection(.readWrite) 51 | try! connection.raw("DROP DATABASE IF EXISTS \(dbName)") 52 | print("Database '\(dbName)' successfully dropped") 53 | try! connection.raw("CREATE DATABASE \(dbName)") 54 | print("Database '\(dbName)' successfully created") 55 | } 56 | 57 | let config = try Config(arguments: ["vapor", "--env=test"]) 58 | try config.setup() 59 | try recreateDatabase(config) 60 | let drop = try Droplet(config) 61 | try drop.setup() 62 | return drop 63 | } 64 | 65 | func truncateTables() throws { 66 | let db = try! self.assertDatabase() 67 | 68 | defer { 69 | try! db.raw("SET FOREIGN_KEY_CHECKS = 1;") 70 | print("Foreign key checks ON\n") 71 | } 72 | 73 | try! db.raw("SET FOREIGN_KEY_CHECKS = 0;") 74 | print("\nForeign key checks OFF") 75 | 76 | var truncatedTableCount = 0 77 | tableForTruncate.forEach { (table) in 78 | try! db.raw("TRUNCATE \(table)") 79 | truncatedTableCount += 1 80 | } 81 | 82 | print("Truncate table(s): \(truncatedTableCount)") 83 | 84 | } 85 | 86 | func serveInBackground() throws { 87 | background { 88 | //swiftlint:disable force_try 89 | try! self.run() 90 | //swiftlint:enable force_try 91 | } 92 | console.wait(seconds: 0.5) 93 | } 94 | } 95 | 96 | class TestCase: XCTestCase { 97 | override func setUp() { 98 | Node.fuzzy = [Row.self, JSON.self, Node.self] 99 | Testing.onFail = XCTFail 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Tests/Resources/1716396_original2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cocoaheadsru/server/3367dbfe3fec62056acd2a6cd604835356dc6243/Tests/Resources/1716396_original2.jpg -------------------------------------------------------------------------------- /Tests/Resources/2017-11-27 11.39.37.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cocoaheadsru/server/3367dbfe3fec62056acd2a6cd604835356dc6243/Tests/Resources/2017-11-27 11.39.37.jpg -------------------------------------------------------------------------------- /Tests/Resources/Image 2017-10-25 00-02-39.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cocoaheadsru/server/3367dbfe3fec62056acd2a6cd604835356dc6243/Tests/Resources/Image 2017-10-25 00-02-39.png -------------------------------------------------------------------------------- /Tests/Resources/VKcocoaheadsdev.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cocoaheadsru/server/3367dbfe3fec62056acd2a6cd604835356dc6243/Tests/Resources/VKcocoaheadsdev.jpg -------------------------------------------------------------------------------- /Tests/Resources/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cocoaheadsru/server/3367dbfe3fec62056acd2a6cd604835356dc6243/Tests/Resources/github.png -------------------------------------------------------------------------------- /Tests/Resources/robot-1469114466kSY.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cocoaheadsru/server/3367dbfe3fec62056acd2a6cd604835356dc6243/Tests/Resources/robot-1469114466kSY.jpg -------------------------------------------------------------------------------- /Tests/Resources/robot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cocoaheadsru/server/3367dbfe3fec62056acd2a6cd604835356dc6243/Tests/Resources/robot.jpg -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | override: 3 | - eval "$(curl -sL https://apt.vapor.sh)" 4 | - sudo apt-get install vapor 5 | - sudo chmod -R a+rx /usr/ 6 | test: 7 | override: 8 | - swift build 9 | - swift build -c release 10 | - swift test 11 | -------------------------------------------------------------------------------- /cloud.yml: -------------------------------------------------------------------------------- 1 | swift_version: "4.0.0" 2 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Qutheory, LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | --------------------------------------------------------------------------------