Add procfile

pull/645/head
Tienson Qin 2020-04-09 21:56:53 +08:00
parent 655ef707ed
commit b0c0b6ad40
35 changed files with 15 additions and 942 deletions

24
.gitignore vendored
View File

@ -1,4 +1,17 @@
node_modules/
/target
/classes
/checkouts
profiles.clj
pom.xml
pom.xml.asc
*.jar
*.class
/.lein-*
/.nrepl-port
.hgignore
.hg/
web/node_modules/
web/public/js/main.js
/.cpcache
@ -6,19 +19,10 @@ web/public/js/main.js
/checkouts
/src/gen
pom.xml
pom.xml.asc
*.iml
*.jar
*.log
.shadow-cljs
.idea
.lein-*
.nrepl-*
.DS_Store
.hgignore
.hg/
.now
.env

12
backend/.gitignore vendored
View File

@ -1,12 +0,0 @@
/target
/classes
/checkouts
profiles.clj
pom.xml
pom.xml.asc
*.jar
*.class
/.lein-*
/.nrepl-port
.hgignore
.hg/

View File

@ -1,24 +0,0 @@
# Change Log
All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/).
## [Unreleased]
### Changed
- Add a new arity to `make-widget-async` to provide a different widget shape.
## [0.1.1] - 2020-02-20
### Changed
- Documentation on how to make the widgets.
### Removed
- `make-widget-sync` - we're all async, all the time.
### Fixed
- Fixed widget maker to keep working when daylight savings switches over.
## 0.1.0 - 2020-02-20
### Added
- Files from the new template.
- Widget maker public API - `make-widget-sync`.
[Unreleased]: https://github.com/your-name/backend/compare/0.1.1...HEAD
[0.1.1]: https://github.com/your-name/backend/compare/0.1.0...0.1.1

View File

@ -1,22 +0,0 @@
# backend
A Clojure library designed to ... well, that part is up to you.
## Usage
FIXME
## License
Copyright © 2020 FIXME
This program and the accompanying materials are made available under the
terms of the Eclipse Public License 2.0 which is available at
http://www.eclipse.org/legal/epl-2.0.
This Source Code may also be made available under the following Secondary
Licenses when the conditions for such availability set forth in the Eclipse
Public License, v. 2.0 are satisfied: GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or (at your
option) any later version, with the GNU Classpath Exception which is available
at https://www.gnu.org/software/classpath/license.html.

View File

@ -1,3 +0,0 @@
# Introduction to backend
TODO: write [great documentation](http://jacobian.org/writing/what-to-write/)

View File

@ -1,36 +0,0 @@
(defproject backend "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
:url "https://www.eclipse.org/legal/epl-2.0/"}
:dependencies [[org.clojure/clojure "1.10.0"]
[clj-social "0.1.6"]
[org.postgresql/postgresql "42.2.8"]
[org.clojure/java.jdbc "0.7.10"]
[honeysql "0.9.8"]
[hikari-cp "2.9.0"]
[toucan "1.15.0"]
[ragtime "0.8.0"]
[com.taoensso/timbre "4.10.0"]
[org.clojure/tools.namespace "0.3.1"]
[buddy/buddy-sign "3.1.0"]
[buddy/buddy-hashers "1.4.0"]
[enlive "1.1.6"]
[io.pedestal/pedestal.service "0.5.5"]
[io.pedestal/pedestal.jetty "0.5.5"]
[metosin/reitit-pedestal "0.4.2"]
[metosin/reitit "0.4.2"]
[metosin/jsonista "0.2.5"]
[aero "1.1.6"]
[com.stuartsierra/component "0.4.0"]
[com.taoensso/nippy "2.14.0"]
[hiccup "1.0.5"]]
;; :main backend.core
:profiles {:repl {:dependencies [[io.pedestal/pedestal.service-tools "0.5.7"]]
:source-paths ["src/backend" "dev"]}
:uberjar {:main backend.core
:aot :all}}
:repl-options {:init-ns user}
:jvm-opts ["-Duser.timezone=UTC" "-Dclojure.spec.check-asserts=true"]
:aliases {"migrate" ["run" "-m" "user/migrate"]
"rollback" ["run" "-m" "user/rollback"]})

View File

@ -1,24 +0,0 @@
{:env #or [#env ENVIRONMENT "dev"]
:port #or [#env PORT 3000]
:oauth {:github {:app-key #env GITHUB_APP_KEY
:app-secret #env GITHUB_APP_SECRET
:redirect-uri #env GITHUB_REDIRECT_URI}}
:jwt-secret #env JWT_SECRET
:cookie-secret #env COOKIE_SECRET
:log-path #or [#env LOG_PATH "/tmp/logseq"]
:hikari-spec {:auto-commit true
:read-only false
:connection-timeout 30000
:validation-timeout 5000
:idle-timeout 600000
:max-lifetime 1800000
:minimum-idle 10
:maximum-pool-size 48
:pool-name "logseq-clj-db-pool"
:adapter "postgresql"
:username #env PG_USERNAME
:password #env PG_PASSWORD
:database-name "logseq"
:server-name "localhost"
:port-number 5432
:register-mbeans false}}

View File

@ -1,52 +0,0 @@
<!-- Logback configuration. See http://logback.qos.ch/manual/index.html -->
<!-- Scanning is currently turned on; This will impact performance! -->
<configuration scan="true" scanPeriod="10 seconds">
<!-- Silence Logback's own status messages about config parsing
<statusListener class="ch.qos.logback.core.status.NopStatusListener" /> -->
<!-- Simple file output -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- encoder defaults to ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} %X{io.pedestal} - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- rollover daily -->
<fileNamePattern>logs/restream-api-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- or whenever the file size reaches 64 MB -->
<maxFileSize>64 MB</maxFileSize>
</rollingPolicy>
<!-- Safely log to the same file from multiple JVMs. Degrades performance! -->
<prudent>true</prudent>
</appender>
<!-- Console output -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoder defaults to ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder>
<pattern>%-5level %logger{36} %X{io.pedestal} - %msg%n</pattern>
</encoder>
<!-- Only log level INFO and above -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
</appender>
<!-- Enable FILE and STDOUT appenders for all log messages.
By default, only log at level INFO and above. -->
<root level="INFO">
<appender-ref ref="FILE" />
<appender-ref ref="STDOUT" />
</root>
<!-- For loggers in the these namespaces, log at all levels. -->
<logger name="user" level="ALL" />
<!-- To log pedestal internals, enable this and change ThresholdFilter to DEBUG
<logger name="io.pedestal" level="ALL" />
-->
</configuration>

View File

@ -1,10 +0,0 @@
{:up ["create extension if not exists \"uuid-ossp\";
CREATE TABLE users (
id uuid DEFAULT uuid_generate_v4() NOT NULL UNIQUE,
name text NOT NULL,
email text NOT NULL UNIQUE,
created_at timestamp with time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
CONSTRAINT created_at_chk CHECK ((date_part('timezone'::text, created_at) = '0'::double precision))
);
"]
:down ["drop table users"]}

View File

@ -1,9 +0,0 @@
{:up ["CREATE TABLE repos (
id uuid DEFAULT uuid_generate_v4() NOT NULL UNIQUE,
user_id uuid NOT NULL,
url text NOT NULL,
created_at timestamp with time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
CONSTRAINT created_at_chk CHECK ((date_part('timezone'::text, created_at) = '0'::double precision))
);"
"CREATE UNIQUE INDEX idx_repos_user_repo ON repos(user_id, url);"]
:down ["drop table repos"]}

View File

@ -1,9 +0,0 @@
{:up ["CREATE TABLE refresh_tokens (
user_id uuid NOT NULL UNIQUE,
token uuid NOT NULL UNIQUE
)"
"ALTER TABLE refresh_tokens
ADD CONSTRAINT refresh_tokens_users_fkey FOREIGN KEY (user_id)
REFERENCES users (id)
ON UPDATE CASCADE ON DELETE CASCADE;"]
:down ["drop table refresh_tokens"]}

View File

@ -1,11 +0,0 @@
{:up ["CREATE TABLE tokens (
id uuid DEFAULT uuid_generate_v4() NOT NULL UNIQUE,
user_id uuid NOT NULL,
oauth_type text NOT NULL,
oauth_id text NOT NULL UNIQUE,
oauth_token text NOT NULL UNIQUE,
created_at timestamp with time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
CONSTRAINT created_at_chk CHECK ((date_part('timezone'::text, created_at) = '0'::double precision))
);"
]
:down ["drop table tokens"]}

View File

@ -1 +0,0 @@
../../web/public

View File

@ -1,35 +0,0 @@
(ns backend.auth
(:require [taoensso.timbre :as timbre]
[clj-social.core :as social]
[backend.config :as config]
[backend.util :as util]
[backend.db.user :as u]
[backend.db.token :as token]
[backend.cookie :as cookie]
[clojure.java.jdbc :as j]))
(defn github [data]
(let [{:keys [app-key app-secret redirect-uri]} (get-in config/config [:oauth :github])
instance (social/make-social :github app-key app-secret redirect-uri
:state (str (util/uuid))
:scope "user:email,repo")
access-token (social/getAccessToken instance (:code data))
info (social/getUserInfo instance access-token)
oauth-type "github"
oauth-id (str (:id info))
access-token (.getAccessToken access-token)]
(toucan.db/transaction
(if-let [token (token/get oauth-type oauth-id)]
;; user already exists
(do
(token/update (:id token) access-token)
(let [token (assoc token :token access-token)]
(some-> (u/get (:user_id token))
(assoc :token token))))
(when-let [user (u/insert {:name (:login info)
:email (:email info)})]
(let [token (token/create {:user_id (:id user)
:oauth_type oauth-type
:oauth_id oauth-id
:oauth_token access-token})]
(assoc user :token token)))))))

View File

@ -1,24 +0,0 @@
(ns backend.components.hikari
(:require [com.stuartsierra.component :as component]
[hikari-cp.core :as hikari]
[clojure.java.jdbc :as j]
[toucan.db :as toucan]
[backend.db-migrate :as migrate]))
(defrecord Hikari [db-spec datasource]
component/Lifecycle
(start [component]
(let [s (or datasource (hikari/make-datasource db-spec))]
;; set time zone
(j/execute! {:datasource s} ["set time zone 'UTC'"])
;; migrate
(migrate/migrate {:datasource s})
(toucan/set-default-db-connection! {:datasource s})
(assoc component :datasource s)))
(stop [component]
(when datasource
(hikari/close-datasource datasource))
(assoc component :datasource nil)))
(defn new-hikari-cp [db-spec]
(map->Hikari {:db-spec db-spec}))

View File

@ -1,26 +0,0 @@
(ns backend.components.http
(:require [com.stuartsierra.component :as component]
[io.pedestal.http :as http]))
(defn test?
[service-map]
(= :test (:env service-map)))
(defrecord Server [service-map service]
component/Lifecycle
(start [this]
(prn "service-map: " service-map)
(if service
this
(cond-> service-map
true http/create-server
(not (test? service-map)) http/start
true ((partial assoc this :service)))))
(stop [this]
(when (and service (not (test? service-map)))
(http/stop service))
(assoc this :service nil)))
(defn new-server
[]
(map->Server {}))

View File

@ -1,12 +0,0 @@
(ns backend.config
(:require [aero.core :refer (read-config)]
[clojure.java.io :as io]))
(def config (read-config (io/resource "config.edn")))
(def production? (= "production" (:env config)))
(def dev? (= "dev" (:env config)))
(def test? (= "test" (:env config)))
(def website-uri (if dev?
"http://localhost:3000"
"https://logseq.com"))

View File

@ -1,58 +0,0 @@
(ns backend.cookie
(:require [buddy.sign.compact :as buddy]
[backend.util :as util]
[backend.config :as config]))
(defn sign [token]
(buddy/sign token (:cookie-secret config/config)))
(defn unsign [cookie]
(buddy/unsign cookie (:cookie-secret config/config)))
;; domain path expires
(defn token-cookie [value & {:keys [max-age path]
:or {path "/"
max-age (* (* 3600 24) 30)}}]
(let [dev? config/dev?
xsrf-token (str (util/uuid))
domain (if-not dev?
".logseq.com"
"")
secure (if-not dev?
true
false)]
{"x" (cond->
{:value (sign value)
:max-age max-age
:http-only true
:path path
:secure secure}
domain
(assoc :domain domain))
"xsrf-token" (cond->
{:value xsrf-token
:max-age max-age
:http-only true
:path "/"
:secure secure}
domain
(assoc :domain domain))}))
(def delete-token
(let [domain (if-not config/dev?
".logseq.com"
"")]
{"x" {:value ""
:path "/"
:expires "Thu, 01 Jan 1970 00:00:00 GMT"
:http-only true
:domain domain}
"xsrf-token" {:value ""
:path "/"
:expires "Thu, 01 Jan 1970 00:00:00 GMT"
:http-only true
:domain domain}}))
(defn get-token [req]
(when-let [access-token (get-in req [:cookies "x" :value])]
(unsign access-token)))

View File

@ -1,21 +0,0 @@
(ns backend.core
(:require [backend.config :as config]
[backend.system :as system]
[taoensso.timbre :as timbre]
[taoensso.timbre.appenders.core :as appenders]
[com.stuartsierra.component :as component]))
(defn set-logger!
[log-path]
(timbre/merge-config! (cond->
{:appenders {:spit (appenders/spit-appender {:fname log-path})}}
config/production?
(assoc :output-fn (partial timbre/default-output-fn {:stacktrace-fonts {}})))))
(defn start []
(System/setProperty "https.protocols" "TLSv1.2,TLSv1.1,SSLv3")
(set-logger! (:log-path config/config))
(let [system (system/new-system config/config)]
(component/start system))
(println "server running in port 3000"))

View File

@ -1,33 +0,0 @@
(ns backend.db.refresh-token
(:refer-clojure :exclude [get update])
(:require [toucan.db :as db]
[toucan.models :as model]
[backend.util :as util]))
(model/defmodel RefreshToken :refresh_tokens
model/IModel
(primary-key [_] :user_id))
(defn get-token
[user-id]
(db/select-one-field :token RefreshToken {:user_id user-id}))
(defn token-exists?
[token]
(db/exists? RefreshToken {:token token}))
(defn get-user-id-by-token
[token]
(db/select-one-field :user_id RefreshToken {:token token}))
(defn create
[user-id]
(if-let [token (get-token user-id)]
token
(loop [token (util/uuid)]
(if (token-exists? token)
(recur (util/uuid))
(do
(db/insert! RefreshToken {:user_id user-id
:token token})
token)))))

View File

@ -1,32 +0,0 @@
(ns backend.db.repo
(:refer-clojure :exclude [get update])
(:require [toucan.db :as db]
[toucan.models :as model]
[ring.util.response :as resp]
[backend.config :as config]
[backend.cookie :as cookie]))
(model/defmodel Repo :repos)
(defn insert
[args]
(cond
(and
(:user_id args) (:url args)
(db/exists? Repo (select-keys args [:user_id :url])))
[:bad :user-repo-exists]
:else
[:ok (db/insert! Repo args)]))
(defn get-user-repos
[user-id]
(db/select Repo {:user_id user-id}))
(defn delete
[id]
(db/delete! Repo {:id id}))
(defn update
[id url]
(db/update! Repo id {:url url}))

View File

@ -1,36 +0,0 @@
(ns backend.db.token
(:refer-clojure :exclude [get update])
(:require [toucan.db :as db]
[toucan.models :as model]
[backend.util :as util]))
(model/defmodel Token :tokens)
(defn get
[oauth-type oauth-id]
(db/select-one Token {:oauth_type oauth-type
:oauth_id oauth-id}))
(defn get-user-tokens
[user-id]
(db/select Token {:user_id user-id}))
(defn exists?
[oauth-type oauth-id]
(db/exists? Token {:oauth_type oauth-type
:oauth_id oauth-id}))
(defn delete
[oauth-type oauth-id]
(db/delete! Token {:oauth_type oauth-type
:oauth_id oauth-id}))
(defn create
[{:keys [oauth_type oauth_id] :as m}]
(if (exists? oauth_type oauth_id)
(delete oauth_type oauth_id))
(db/insert! Token m))
(defn update
[id new-token]
(db/update! Token id {:oauth_token new-token}))

View File

@ -1,45 +0,0 @@
(ns backend.db.user
(:refer-clojure :exclude [get update])
(:require [toucan.db :as db]
[toucan.models :as model]
[ring.util.response :as resp]
[backend.config :as config]
[backend.cookie :as cookie]
[backend.jwt :as jwt]
[backend.db.refresh-token :as refresh-token]))
(model/defmodel User :users)
;; move to handler
(defn logout
[]
(-> (resp/redirect config/website-uri)
(assoc :cookies cookie/delete-token)))
(defn get
[id]
(db/select-one User :id id))
(defn insert
[{:keys [name email] :as args}]
(when-not (db/exists? User {:email email})
(db/insert! User args)))
(defn delete
[id]
(db/delete! User {:id id}))
(defn update-email
[id email]
(cond
(db/exists? User {:email email})
[:bad :email-address-exists]
:else
[:ok (db/update! User id {:email email})]))
(defn generate-tokens
[user-id]
(cookie/token-cookie
{:access-token (jwt/sign {:id user-id})
:refresh-token (refresh-token/create user-id)}))

View File

@ -1,16 +0,0 @@
(ns backend.db-migrate
(:require [ragtime.jdbc :as jdbc]
[ragtime.repl :as repl]))
;; db migrations
(defn load-config
[db]
{:datastore (jdbc/sql-database db)
:migrations (jdbc/load-resources "migrations")})
(defn migrate [db]
(prn "db: " db)
(repl/migrate (load-config db)))
(defn rollback [db]
(repl/rollback (load-config db)))

View File

@ -1,29 +0,0 @@
(ns backend.interceptors
(:require [io.pedestal.interceptor :refer [interceptor]]
[backend.cookie :as cookie]
[backend.jwt :as jwt]
[backend.db.user :as u]
[backend.util :as util]))
(def cookie-interceptor
{:name ::cookie-authenticate
:enter
(fn [{:keys [request] :as context}]
(let [tokens (cookie/get-token request)]
(if tokens
(let [{:keys [access-token refresh-token]} tokens]
(if access-token
(try
(let [user (jwt/unsign access-token)
uid (some-> (:id user) util/->uuid)
user (u/get uid)]
(if (:id user)
(-> context
(assoc-in [:request :app-context :uid] uid)
(assoc-in [:request :app-context :user] user))
context))
(catch Exception e ; token is expired
(when (= (ex-data e)
{:type :validation, :cause :exp})
(assoc context :response (u/logout)))))))
context)))})

View File

@ -1,23 +0,0 @@
(ns backend.jwt
(:require [buddy.sign.jwt :as jwt]
[clj-time.core :as time]
[backend.config :refer [config]]))
(defonce secret (:jwt-secret config))
(defn sign
"Serialize and sign a token with defined claims"
([m]
(sign m (* 60 60 12)))
([m expire-secs]
(let [claims (assoc m
:exp (time/plus (time/now) (time/seconds expire-secs)))]
(jwt/sign claims secret))))
(defn unsign
[token]
(jwt/unsign token secret))
(defn unsign-skip-validation
[token]
(jwt/unsign token secret {:skip-validation true}))

View File

@ -1,102 +0,0 @@
(ns backend.routes
(:require [reitit.swagger :as swagger]
[clj-social.core :as social]
[backend.config :as config]
[backend.util :as util]
[backend.auth :as auth]
[backend.db.user :as u]
[backend.db.token :as token]
[backend.db.repo :as repo]
[ring.util.response :as resp]
[backend.views.home :as home]
[backend.interceptors :as interceptors]))
;; TODO: spec validate, authorization (owner?)
(def routes
[["/swagger.json"
{:get {:no-doc true
:swagger {:info {:title "logseq api"
:description "with pedestal & reitit-http"}}
:handler (swagger/create-swagger-handler)}}]
["/"
{:get {:no-doc true
:handler (fn [_req]
{:status 200
:body (home/home)})}}]
["/login"
{:swagger {:tags ["Login"]}}
["/github"
{:get {:summary "Login with github"
:handler
(fn [req]
(let [{:keys [app-key app-secret redirect-uri]} (get-in config/config [:oauth :github])
social (social/make-social :github app-key app-secret
(str redirect-uri
"?referer="
(get-in req [:headers "referer"] ""))
:state (str (util/uuid))
:scope "user:email,repo")
url (social/getAuthorizationUrl social)]
(resp/redirect url)))}}]]
["/auth"
{:swagger {:tags ["Authenticate"]}}
["/github"
{:get {:summary "Authenticate with github"
:handler
(fn [{:keys [params] :as req}]
(if (and (:code params)
(:state params))
(if-let [user (auth/github params)]
(-> (resp/redirect config/website-uri)
(assoc :cookies (u/generate-tokens (:id user))))
{:status 500
:body "Internal Error"})
{:status 401
:body "Invalid request"}))}}]]
["/api/v1" {:interceptors [interceptors/cookie-interceptor]}
["/me"
{:get {:summary "Get current user's information"
:handler
(fn [{:keys [app-context] :as req}]
(prn "request: " req)
(if-let [user (:user app-context)]
(let [user-id (:id user)]
{:status 200
:body {:user user
:tokens (token/get-user-tokens user-id)
:repos (repo/get-user-repos user-id)}})
{:status 404
:body "not-found"}))}}]
["/repos"
{:post {:summary "Add a repo"
:handler
(fn [{:keys [app-context body-params] :as req}]
(let [user (:user app-context)
result (repo/insert {:user_id (:id user)
:url (:url body-params)})]
{:status 201
:body result}))}
}]
["/repos/:id"
{:patch {:summary "Update a repo's url"
:handler
(fn [{:keys [app-context params body-params] :as req}]
(let [user (:user app-context)
result (repo/update (:id params)
(:url body-params))]
{:status 200
:body result}))}
:delete {:summary "Delete a repo"
:handler
(fn [{:keys [app-context params] :as req}]
(let [user (:user app-context)
result (repo/delete (:id params))]
{:status 200
:body {:result true}}))}}]]])

View File

@ -1,101 +0,0 @@
(ns backend.system
(:require [io.pedestal.http :as server]
[reitit.ring :as ring]
[reitit.http :as http]
[reitit.coercion.spec]
[reitit.swagger :as swagger]
[reitit.swagger-ui :as swagger-ui]
[reitit.http.coercion :as coercion]
[reitit.dev.pretty :as pretty]
[reitit.http.interceptors.parameters :as parameters]
[reitit.http.interceptors.muuntaja :as muuntaja]
[reitit.http.interceptors.exception :as exception]
[reitit.http.interceptors.multipart :as multipart]
[reitit.http.interceptors.dev :as dev]
[reitit.http.spec :as spec]
[spec-tools.spell :as spell]
[io.pedestal.http :as server]
[reitit.pedestal :as pedestal]
[clojure.core.async :as a]
[muuntaja.core :as m]
[com.stuartsierra.component :as component]
[backend.components.http :as component-http]
[backend.components.hikari :as hikari]
[backend.routes :as routes]
[backend.config :as config]
[io.pedestal.http.ring-middlewares :as ring-middlewares]))
(def router
(pedestal/routing-interceptor
(http/router
routes/routes
{;:reitit.interceptor/transform dev/print-context-diffs ;; pretty context diffs
;;:validate spec/validate ;; enable spec validation for route data
;;:reitit.spec/wrap spell/closed ;; strict top-level validation
;; :exception pretty/exception
:data {:coercion reitit.coercion.spec/coercion
:muuntaja m/instance
:interceptors [;; swagger feature
swagger/swagger-feature
;; query-params & form-params
(parameters/parameters-interceptor)
;; content-negotiation
(muuntaja/format-negotiate-interceptor)
;; encoding response body
(muuntaja/format-response-interceptor)
;; exception handling
;; (exception/exception-interceptor)
;; decoding request body
(muuntaja/format-request-interceptor)
;; coercing response bodys
(coercion/coerce-response-interceptor)
;; coercing request parameters
(coercion/coerce-request-interceptor)
;; multipart
(multipart/multipart-interceptor)]}})
;; optional default ring handler (if no routes have matched)
(ring/routes
(swagger-ui/create-swagger-ui-handler
{:path "/swagger"
:config {:validatorUrl nil
:operationsSorter "alpha"}})
(ring/create-resource-handler)
(ring/create-default-handler))))
(defn merge-interceptors-map
[system-map interceptors]
(update system-map :io.pedestal.http/interceptors
(fn [old]
(vec (concat interceptors old)))))
(defn new-system
[{:keys [env port hikari-spec] :as config}]
(let [service-map (-> {:env env
::server/type :jetty
::server/port port
::server/join? false
;; no pedestal routes
::server/routes []
;; allow serving the swagger-ui styles & scripts from self
;; ::server/secure-headers {:content-security-policy-settings
;; {:default-src "'self'"
;; :style-src "'self' 'unsafe-inline'"
;; :script-src "'self' 'unsafe-inline'"}}
::server/secure-headers {:content-security-policy-settings {:object-src "'none'"}}
::server/resource-path "/public"}
(server/default-interceptors)
;; use the reitit router
(pedestal/replace-last-interceptor router))
service-map (merge-interceptors-map
service-map
[ring-middlewares/cookies
server/html-body])
service-map (if config/dev? (server/dev-interceptors service-map) service-map)]
(component/system-map :service-map service-map
:hikari (hikari/new-hikari-cp hikari-spec)
:http
(component/using
(component-http/new-server)
[:service-map]))))

View File

@ -1,82 +0,0 @@
(ns backend.util
(:require [clojure.string :as str]
[clj-time
[coerce :as tc]
[core :as t]
[format :as tf]])
(:import [java.util UUID]
[java.util TimerTask Timer]))
(defn uuid
"Generate uuid."
[]
(UUID/randomUUID))
(defn ->uuid
[s]
(if (uuid? s)
s
(UUID/fromString s)))
(defn update-if
"Update m if k exists."
[m k f]
(if-let [v (get m k)]
(assoc m k (f v))
m))
(defn dissoc-in
"Dissociates an entry from a nested associative structure returning a new
nested structure. keys is a sequence of keys. Any empty maps that result
will not be present in the new structure."
[m [k & ks :as keys]]
(if ks
(if-let [nextmap (get m k)]
(let [newmap (dissoc-in nextmap ks)]
(if (seq newmap)
(assoc m k newmap)
(dissoc m k)))
m)
(dissoc m k)))
(defmacro doseq-indexed
"loops over a set of values, binding index-sym to the 0-based index of each value"
([[val-sym values index-sym] & code]
`(loop [vals# (seq ~values)
~index-sym (long 0)]
(if vals#
(let [~val-sym (first vals#)]
~@code
(recur (next vals#) (inc ~index-sym)))
nil))))
(defn indexed [coll] (map-indexed vector coll))
(defn set-timeout [f interval]
(let [task (proxy [TimerTask] []
(run [] (f)))
timer (new Timer)]
(.schedule timer task (long interval))
timer))
;; http://yellerapp.com/posts/2014-12-11-14-race-condition-in-clojure-println.html
(defn safe-println [& more]
(.write *out* (str (clojure.string/join " " more) "\n")))
(defn safe->int
[s]
(if (string? s)
(Integer/parseInt s)
s))
(defn remove-nils
[m]
(reduce (fn [acc [k v]] (if v (assoc acc k v)
acc))
{} m))
(defn deep-merge [& maps]
(apply merge-with (fn [& args]
(if (every? map? args)
(apply deep-merge args)
(last args)))
maps))

View File

@ -1,30 +0,0 @@
(ns backend.views.home
(:require [hiccup.page :as html]))
(defn home
[]
(html/html5
[:head
[:meta {:charset "utf-8"}]
[:meta
{:content
"minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no",
:name "viewport"}]
;; [:link {:type "text/css", :href "css/tailwind.min.css", :rel "stylesheet"}]
[:link {:type "text/css", :href "https://cdn.jsdelivr.net/npm/@tailwindcss/ui@latest/dist/tailwind-ui.min.css", :rel "stylesheet"}]
[:link {:type "text/css", :href "css/org.css", :rel "stylesheet"}]
[:link {:type "text/css", :href "css/style.css", :rel "stylesheet"}]
[:link
{:href
"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap",
:rel "stylesheet"}]
[:link {:href "css/highlight.css", :rel "stylesheet"}]
[:title "Logseq"]]
[:body
[:div#root]
[:script {:src "https://unpkg.com/@isomorphic-git/lightning-fs@3.4.1/dist/lightning-fs.min.js"}]
[:script {:src "https://unpkg.com/isomorphic-git@0.78.5/dist/bundle.umd.min.js"}]
[:script
"window.fs = new LightningFS('logseq');git.plugins.set('fs', window.fs);window.pfs = window.fs.promises;"]
[:script {:src "/js/highlight.pack.js"}]
[:script {:src "/js/main.js"}]]))

View File

@ -1,7 +0,0 @@
(ns backend.core-test
(:require [clojure.test :refer :all]
[backend.core :refer :all]))
(deftest a-test
(testing "FIXME, I fail."
(is (= 0 1))))

View File

@ -1,7 +0,0 @@
#!/bin/bash
cd api && yarn release
cd ../web
yarn clean && yarn release
cd ../
now

1
procfile Normal file
View File

@ -0,0 +1 @@
web: java -Dclojure.main.report=stderr -cp target/uberjar/logseq.jar clojure.main -m backend.core