├── README.md ├── dev ├── local.clj └── user.clj ├── profiles.clj ├── project.clj ├── resources ├── migrations │ ├── 20151001145313-users.down.sql │ └── 20151001145313-users.up.sql └── sql │ └── queries.sql ├── src └── swagger_service │ ├── component │ └── db.clj │ ├── config.clj │ ├── endpoint │ ├── example.clj │ └── service.clj │ ├── main.clj │ └── system.clj └── test └── swagger_service └── endpoint └── example_test.clj /README.md: -------------------------------------------------------------------------------- 1 | # swagger-service 2 | 3 | FIXME: description 4 | 5 | ## Developing 6 | 7 | ### Setup 8 | 9 | When you first clone this repository, run: 10 | 11 | ```sh 12 | lein setup 13 | ``` 14 | 15 | This will create files for local configuration, and prep your system 16 | for the project. 17 | 18 | ### Environment 19 | 20 | To begin developing, start with a REPL. 21 | 22 | ```sh 23 | lein repl 24 | ``` 25 | 26 | Run `go` to initiate and start the system. 27 | 28 | ```clojure 29 | user=> (go) 30 | :started 31 | ``` 32 | 33 | By default this creates a web server at . 34 | 35 | When you make changes to your source files, use `reset` to reload any 36 | modified files and reset the server. 37 | 38 | ```clojure 39 | user=> (reset) 40 | :reloading (...) 41 | :resumed 42 | ``` 43 | 44 | ### Testing 45 | 46 | Testing is fastest through the REPL, as you avoid environment startup 47 | time. 48 | 49 | ```clojure 50 | user=> (test) 51 | ... 52 | ``` 53 | 54 | But you can also run tests through Leiningen. 55 | 56 | ```sh 57 | lein test 58 | ``` 59 | 60 | ### Generators 61 | 62 | This project has several [generators][] to help you create files. 63 | 64 | * `lein gen endpoint ` to create a new endpoint 65 | * `lein gen component ` to create a new component 66 | 67 | [generators]: https://github.com/weavejester/lein-generate 68 | 69 | ## Deploying 70 | 71 | FIXME: steps to deploy 72 | 73 | ## Legal 74 | 75 | Copyright © 2015 FIXME 76 | -------------------------------------------------------------------------------- /dev/local.clj: -------------------------------------------------------------------------------- 1 | ;; Local REPL configuration 2 | 3 | (alter-var-root #'config meta-merge {}) 4 | -------------------------------------------------------------------------------- /dev/user.clj: -------------------------------------------------------------------------------- 1 | (ns user 2 | (:require [clojure.repl :refer :all] 3 | [clojure.pprint :refer [pprint]] 4 | [clojure.tools.namespace.repl :refer [refresh]] 5 | [clojure.java.io :as io] 6 | [com.stuartsierra.component :as component] 7 | [eftest.runner :as eftest] 8 | [meta-merge.core :refer [meta-merge]] 9 | [reloaded.repl :refer [system init start stop go reset]] 10 | [ring.middleware.stacktrace :refer [wrap-stacktrace]] 11 | [swagger-service.config :as config] 12 | [swagger-service.system :as system])) 13 | 14 | (def dev-config 15 | {:app {:middleware [wrap-stacktrace]}}) 16 | 17 | (def config 18 | (meta-merge config/defaults 19 | config/environ 20 | dev-config)) 21 | 22 | (defn new-system [] 23 | (into (system/new-system config) 24 | {})) 25 | 26 | (ns-unmap *ns* 'test) 27 | 28 | (defn test [] 29 | (eftest/run-tests (eftest/find-tests "test") {:multithread? false})) 30 | 31 | (when (io/resource "local.clj") 32 | (load "local")) 33 | 34 | (reloaded.repl/set-init! new-system) 35 | -------------------------------------------------------------------------------- /profiles.clj: -------------------------------------------------------------------------------- 1 | ;; Local profile overrides 2 | 3 | {:profiles/dev {:env {:connection-uri "jdbc:sqlite:service-store.db"}} 4 | :profiles/test {}} 5 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject swagger-service "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :min-lein-version "2.0.0" 5 | :dependencies [[org.clojure/clojure "1.7.0"] 6 | [com.stuartsierra/component "0.3.0"] 7 | [compojure "1.4.0"] 8 | [duct "0.4.2"] 9 | [environ "1.0.1"] 10 | [meta-merge "0.1.1"] 11 | [ring "1.4.0"] 12 | [ring/ring-defaults "0.1.5"] 13 | [ring-jetty-component "0.3.0"] 14 | [crypto-password "0.1.3"] 15 | [metosin/compojure-api "0.23.1"] 16 | [org.xerial/sqlite-jdbc "3.8.11.1"] 17 | [yesql "0.5.0"] 18 | [migratus "0.8.4"]] 19 | :plugins [[lein-environ "1.0.1"] 20 | [lein-gen "0.2.2"] 21 | [migratus-lein "0.1.7"]] 22 | 23 | :migratus {:store :database 24 | :db {:classname "org.sqlite.JDBC" 25 | :connection-uri "jdbc:sqlite:service-store.db"}} 26 | 27 | :generators [[duct/generators "0.4.2"]] 28 | :duct {:ns-prefix swagger-service} 29 | :main ^:skip-aot swagger-service.main 30 | :target-path "target/%s/" 31 | :aliases {"gen" ["generate"] 32 | "setup" ["do" ["generate" "locals"]]} 33 | :profiles 34 | {:dev [:project/dev :profiles/dev] 35 | :test [:project/test :profiles/test] 36 | :uberjar {:aot :all} 37 | :profiles/dev {} 38 | :profiles/test {} 39 | :project/dev {:source-paths ["dev"] 40 | :repl-options {:init-ns user} 41 | :dependencies [[reloaded.repl "0.2.0"] 42 | [org.clojure/tools.namespace "0.2.11"] 43 | [eftest "0.1.0"] 44 | [kerodon "0.7.0"]] 45 | :env {:port 3000}} 46 | :project/test {}}) 47 | -------------------------------------------------------------------------------- /resources/migrations/20151001145313-users.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE users; 2 | -------------------------------------------------------------------------------- /resources/migrations/20151001145313-users.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE users 2 | (id VARCHAR(20) PRIMARY KEY, 3 | first_name VARCHAR(30), 4 | last_name VARCHAR(30), 5 | email VARCHAR(30), 6 | admin BOOLEAN, 7 | last_login TIME, 8 | is_active BOOLEAN, 9 | pass VARCHAR(100)); 10 | -------------------------------------------------------------------------------- /resources/sql/queries.sql: -------------------------------------------------------------------------------- 1 | -- name: create-user! 2 | -- creates a new user record 3 | INSERT INTO users 4 | (id, first_name, last_name, email, pass) 5 | VALUES (:id, :first_name, :last_name, :email, :pass) 6 | 7 | -- name: get-user 8 | -- retrieve a user given the id. 9 | SELECT id, first_name, last_name, email FROM users 10 | WHERE id = :id 11 | 12 | -- name: get-users 13 | -- retrieve a user given the id. 14 | SELECT id, first_name, last_name, email FROM users 15 | 16 | -- name: delete-user! 17 | -- delete a user given the id 18 | DELETE FROM users 19 | WHERE id = :id 20 | -------------------------------------------------------------------------------- /src/swagger_service/component/db.clj: -------------------------------------------------------------------------------- 1 | (ns swagger-service.component.db 2 | (:require [yesql.core :refer [defqueries]] 3 | [com.stuartsierra.component :as component] 4 | [crypto.password.bcrypt :as password] 5 | [environ.core :refer [env]])) 6 | 7 | (defqueries "sql/queries.sql") 8 | 9 | (defn create-user-account! [user db] 10 | (create-user! (update user :pass password/encrypt) db)) 11 | 12 | (defn authenticate [user db] 13 | (boolean 14 | (when-let [db-user (-> user (get-user db) first)] 15 | (password/check (:pass user) (:pass db-user))))) 16 | 17 | (defrecord DbComponent [connection] 18 | component/Lifecycle 19 | (start [component] 20 | (assoc component :connection connection)) 21 | (stop [component] 22 | (dissoc component :connection))) 23 | 24 | (defn db-component [connection] 25 | (->DbComponent connection)) 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/swagger_service/config.clj: -------------------------------------------------------------------------------- 1 | (ns swagger-service.config 2 | (:require [environ.core :refer [env]])) 3 | 4 | (def defaults 5 | ^:displace {:http {:port 3000}}) 6 | 7 | (def environ 8 | {:http {:port (some-> env :port Integer.)} 9 | :db {:connection-uri (:connection-uri env)}}) 10 | -------------------------------------------------------------------------------- /src/swagger_service/endpoint/example.clj: -------------------------------------------------------------------------------- 1 | (ns swagger-service.endpoint.example 2 | (:require [compojure.core :refer :all])) 3 | 4 | (defn example-endpoint [config] 5 | (routes 6 | (GET "/" [] "Hello World"))) 7 | -------------------------------------------------------------------------------- /src/swagger_service/endpoint/service.clj: -------------------------------------------------------------------------------- 1 | (ns swagger-service.endpoint.service 2 | (:require [clojure.java.io :as io] 3 | [ring.util.http-response :refer :all] 4 | [compojure.api.sweet :refer :all] 5 | [schema.core :as s] 6 | [swagger-service.component.db :as db])) 7 | 8 | (s/defschema User 9 | {:id String 10 | (s/optional-key :first_name) String 11 | (s/optional-key :last_name) String 12 | (s/optional-key :email) String 13 | (s/optional-key :pass) String}) 14 | 15 | (defn service-endpoint [config] 16 | (api 17 | (ring.swagger.ui/swagger-ui 18 | "/swagger-ui") 19 | (swagger-docs 20 | {:info {:title "User API"}}) 21 | (context* "/api" [] 22 | :tags ["users"] 23 | 24 | (GET* "/users" [] 25 | :return [User] 26 | :summary "returns the list of users" 27 | (ok (db/get-users {} (:db config)))) 28 | 29 | (GET* "/user/:id" [] 30 | :return User 31 | :path-params [id :- String] 32 | :summary "returns the user with a given id" 33 | (ok (db/get-user {:id id} (:db config)))) 34 | 35 | (POST* "/authenticate" [] 36 | :return Boolean 37 | :body-params [user :- User] 38 | :summary "authenticates the user using the id and pass." 39 | (ok (db/authenticate user (:db config)))) 40 | 41 | (POST* "/user" [] 42 | :return Long 43 | :body-params [user :- User] 44 | :summary "creates a new user record." 45 | (ok (db/create-user-account! user (:db config)))) 46 | 47 | (DELETE* "/user" [] 48 | :return Long 49 | :body-params [id :- String] 50 | :summary "deletes the user record with the given id." 51 | (ok (db/delete-user! {:id id} (:db config))))))) 52 | -------------------------------------------------------------------------------- /src/swagger_service/main.clj: -------------------------------------------------------------------------------- 1 | (ns swagger-service.main 2 | (:gen-class) 3 | (:require [com.stuartsierra.component :as component] 4 | [duct.middleware.errors :refer [wrap-hide-errors]] 5 | [meta-merge.core :refer [meta-merge]] 6 | [swagger-service.config :as config] 7 | [swagger-service.system :refer [new-system]])) 8 | 9 | (def prod-config 10 | {:app {:middleware [[wrap-hide-errors :internal-error]] 11 | :internal-error "Internal Server Error"}}) 12 | 13 | (def config 14 | (meta-merge config/defaults 15 | config/environ 16 | prod-config)) 17 | 18 | (defn -main [& args] 19 | (let [system (new-system config)] 20 | (println "Starting HTTP server on port" (-> system :http :port)) 21 | (component/start system))) 22 | -------------------------------------------------------------------------------- /src/swagger_service/system.clj: -------------------------------------------------------------------------------- 1 | (ns swagger-service.system 2 | (:require [com.stuartsierra.component :as component] 3 | [duct.component.endpoint :refer [endpoint-component]] 4 | [duct.component.handler :refer [handler-component]] 5 | [duct.middleware.not-found :refer [wrap-not-found]] 6 | [meta-merge.core :refer [meta-merge]] 7 | [ring.component.jetty :refer [jetty-server]] 8 | [ring.middleware.defaults :refer [wrap-defaults api-defaults]] 9 | [swagger-service.component.db :refer [db-component]] 10 | [swagger-service.endpoint.example :refer [example-endpoint]] 11 | [swagger-service.endpoint.service :refer [service-endpoint]])) 12 | 13 | (def base-config 14 | {:app {:middleware [[wrap-not-found :not-found] 15 | [wrap-defaults :defaults]] 16 | :not-found "Resource Not Found" 17 | :defaults (meta-merge api-defaults {})}}) 18 | 19 | (defn new-system [config] 20 | (let [config (meta-merge base-config config)] 21 | (-> (component/system-map 22 | :db (db-component (:db config)) 23 | :app (handler-component (:app config)) 24 | :http (jetty-server (:http config)) 25 | :example (endpoint-component example-endpoint) 26 | :service (endpoint-component service-endpoint)) 27 | (component/system-using 28 | {:http [:app] 29 | :app [:example :service] 30 | :service [:db] 31 | :example [] 32 | :db []})))) 33 | -------------------------------------------------------------------------------- /test/swagger_service/endpoint/example_test.clj: -------------------------------------------------------------------------------- 1 | (ns swagger-service.endpoint.example-test 2 | (:require [com.stuartsierra.component :as component] 3 | [clojure.test :refer :all] 4 | [kerodon.core :refer :all] 5 | [kerodon.test :refer :all] 6 | [swagger-service.endpoint.example :as example])) 7 | 8 | (def handler 9 | (example/example-endpoint {})) 10 | 11 | (deftest smoke-test 12 | (testing "index page exists" 13 | (-> (session handler) 14 | (visit "/") 15 | (has (status? 200) "page exists")))) 16 | --------------------------------------------------------------------------------