mirror of https://github.com/logseq/logseq
Add procfile
parent
655ef707ed
commit
b0c0b6ad40
|
@ -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
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
/target
|
||||
/classes
|
||||
/checkouts
|
||||
profiles.clj
|
||||
pom.xml
|
||||
pom.xml.asc
|
||||
*.jar
|
||||
*.class
|
||||
/.lein-*
|
||||
/.nrepl-port
|
||||
.hgignore
|
||||
.hg/
|
|
@ -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
|
|
@ -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.
|
|
@ -1,3 +0,0 @@
|
|||
# Introduction to backend
|
||||
|
||||
TODO: write [great documentation](http://jacobian.org/writing/what-to-write/)
|
|
@ -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"]})
|
|
@ -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}}
|
|
@ -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>
|
|
@ -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"]}
|
|
@ -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"]}
|
|
@ -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"]}
|
|
@ -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"]}
|
|
@ -1 +0,0 @@
|
|||
../../web/public
|
|
@ -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)))))))
|
|
@ -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}))
|
|
@ -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 {}))
|
|
@ -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"))
|
|
@ -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)))
|
|
@ -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"))
|
|
@ -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)))))
|
|
@ -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}))
|
|
@ -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}))
|
|
@ -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)}))
|
|
@ -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)))
|
|
@ -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)))})
|
|
@ -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}))
|
|
@ -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}}))}}]]])
|
|
@ -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]))))
|
|
@ -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))
|
|
@ -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"}]]))
|
|
@ -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))))
|
7
build.sh
7
build.sh
|
@ -1,7 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
cd api && yarn release
|
||||
cd ../web
|
||||
yarn clean && yarn release
|
||||
cd ../
|
||||
now
|
Loading…
Reference in New Issue