mirror of
https://github.com/logseq/logseq.git
synced 2026-04-24 22:25:01 +00:00
Rename to logseq
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,5 +1,5 @@
|
||||
node_modules/
|
||||
web/public/static/js/main.js
|
||||
web/public/js/main.js
|
||||
|
||||
/.cpcache
|
||||
/target
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
web
|
||||
api/shadow-cljs.edn
|
||||
api/.shadow-cljs
|
||||
api/src
|
||||
.env
|
||||
@@ -5,5 +5,5 @@
|
||||
-/web/.cpcache
|
||||
-/web/.shadow-cljs/
|
||||
-/web/node_modules/
|
||||
-/web/public/static/js/cljs-runtime/
|
||||
-/web/public/static/js/main.js
|
||||
-/web/public/js/cljs-runtime/
|
||||
-/web/public/js/main.js
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
(defonce server (atom nil))
|
||||
|
||||
(defonce config
|
||||
{:allowed-origins #{"http://localhost:8080" "https://gitnotes.now.sh"}})
|
||||
{:allowed-origins #{"http://localhost:8080" "https://logseq.now.sh"}})
|
||||
|
||||
(def cors-options
|
||||
{:origin (fn [origin callback]
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
;; utils
|
||||
(def dev? ^boolean goog.DEBUG)
|
||||
|
||||
(def cookie-domain (if dev? "" ".gitnotes.now.sh"))
|
||||
(def cookie-domain (if dev? "" ".logseq.now.sh"))
|
||||
|
||||
(def cookie-options
|
||||
{:maxAge (* 1000 60 60 24 30) ; 30 days
|
||||
|
||||
12
backend/.gitignore
vendored
Normal file
12
backend/.gitignore
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
/target
|
||||
/classes
|
||||
/checkouts
|
||||
profiles.clj
|
||||
pom.xml
|
||||
pom.xml.asc
|
||||
*.jar
|
||||
*.class
|
||||
/.lein-*
|
||||
/.nrepl-port
|
||||
.hgignore
|
||||
.hg/
|
||||
24
backend/CHANGELOG.md
Normal file
24
backend/CHANGELOG.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# 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
|
||||
277
backend/LICENSE
Normal file
277
backend/LICENSE
Normal file
@@ -0,0 +1,277 @@
|
||||
Eclipse Public License - v 2.0
|
||||
|
||||
THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
|
||||
PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION
|
||||
OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
|
||||
|
||||
1. DEFINITIONS
|
||||
|
||||
"Contribution" means:
|
||||
|
||||
a) in the case of the initial Contributor, the initial content
|
||||
Distributed under this Agreement, and
|
||||
|
||||
b) in the case of each subsequent Contributor:
|
||||
i) changes to the Program, and
|
||||
ii) additions to the Program;
|
||||
where such changes and/or additions to the Program originate from
|
||||
and are Distributed by that particular Contributor. A Contribution
|
||||
"originates" from a Contributor if it was added to the Program by
|
||||
such Contributor itself or anyone acting on such Contributor's behalf.
|
||||
Contributions do not include changes or additions to the Program that
|
||||
are not Modified Works.
|
||||
|
||||
"Contributor" means any person or entity that Distributes the Program.
|
||||
|
||||
"Licensed Patents" mean patent claims licensable by a Contributor which
|
||||
are necessarily infringed by the use or sale of its Contribution alone
|
||||
or when combined with the Program.
|
||||
|
||||
"Program" means the Contributions Distributed in accordance with this
|
||||
Agreement.
|
||||
|
||||
"Recipient" means anyone who receives the Program under this Agreement
|
||||
or any Secondary License (as applicable), including Contributors.
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source Code or other
|
||||
form, that is based on (or derived from) the Program and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship.
|
||||
|
||||
"Modified Works" shall mean any work in Source Code or other form that
|
||||
results from an addition to, deletion from, or modification of the
|
||||
contents of the Program, including, for purposes of clarity any new file
|
||||
in Source Code form that contains any contents of the Program. Modified
|
||||
Works shall not include works that contain only declarations,
|
||||
interfaces, types, classes, structures, or files of the Program solely
|
||||
in each case in order to link to, bind by name, or subclass the Program
|
||||
or Modified Works thereof.
|
||||
|
||||
"Distribute" means the acts of a) distributing or b) making available
|
||||
in any manner that enables the transfer of a copy.
|
||||
|
||||
"Source Code" means the form of a Program preferred for making
|
||||
modifications, including but not limited to software source code,
|
||||
documentation source, and configuration files.
|
||||
|
||||
"Secondary License" means either the GNU General Public License,
|
||||
Version 2.0, or any later versions of that license, including any
|
||||
exceptions or additional permissions as identified by the initial
|
||||
Contributor.
|
||||
|
||||
2. GRANT OF RIGHTS
|
||||
|
||||
a) Subject to the terms of this Agreement, each Contributor hereby
|
||||
grants Recipient a non-exclusive, worldwide, royalty-free copyright
|
||||
license to reproduce, prepare Derivative Works of, publicly display,
|
||||
publicly perform, Distribute and sublicense the Contribution of such
|
||||
Contributor, if any, and such Derivative Works.
|
||||
|
||||
b) Subject to the terms of this Agreement, each Contributor hereby
|
||||
grants Recipient a non-exclusive, worldwide, royalty-free patent
|
||||
license under Licensed Patents to make, use, sell, offer to sell,
|
||||
import and otherwise transfer the Contribution of such Contributor,
|
||||
if any, in Source Code or other form. This patent license shall
|
||||
apply to the combination of the Contribution and the Program if, at
|
||||
the time the Contribution is added by the Contributor, such addition
|
||||
of the Contribution causes such combination to be covered by the
|
||||
Licensed Patents. The patent license shall not apply to any other
|
||||
combinations which include the Contribution. No hardware per se is
|
||||
licensed hereunder.
|
||||
|
||||
c) Recipient understands that although each Contributor grants the
|
||||
licenses to its Contributions set forth herein, no assurances are
|
||||
provided by any Contributor that the Program does not infringe the
|
||||
patent or other intellectual property rights of any other entity.
|
||||
Each Contributor disclaims any liability to Recipient for claims
|
||||
brought by any other entity based on infringement of intellectual
|
||||
property rights or otherwise. As a condition to exercising the
|
||||
rights and licenses granted hereunder, each Recipient hereby
|
||||
assumes sole responsibility to secure any other intellectual
|
||||
property rights needed, if any. For example, if a third party
|
||||
patent license is required to allow Recipient to Distribute the
|
||||
Program, it is Recipient's responsibility to acquire that license
|
||||
before distributing the Program.
|
||||
|
||||
d) Each Contributor represents that to its knowledge it has
|
||||
sufficient copyright rights in its Contribution, if any, to grant
|
||||
the copyright license set forth in this Agreement.
|
||||
|
||||
e) Notwithstanding the terms of any Secondary License, no
|
||||
Contributor makes additional grants to any Recipient (other than
|
||||
those set forth in this Agreement) as a result of such Recipient's
|
||||
receipt of the Program under the terms of a Secondary License
|
||||
(if permitted under the terms of Section 3).
|
||||
|
||||
3. REQUIREMENTS
|
||||
|
||||
3.1 If a Contributor Distributes the Program in any form, then:
|
||||
|
||||
a) the Program must also be made available as Source Code, in
|
||||
accordance with section 3.2, and the Contributor must accompany
|
||||
the Program with a statement that the Source Code for the Program
|
||||
is available under this Agreement, and informs Recipients how to
|
||||
obtain it in a reasonable manner on or through a medium customarily
|
||||
used for software exchange; and
|
||||
|
||||
b) the Contributor may Distribute the Program under a license
|
||||
different than this Agreement, provided that such license:
|
||||
i) effectively disclaims on behalf of all other Contributors all
|
||||
warranties and conditions, express and implied, including
|
||||
warranties or conditions of title and non-infringement, and
|
||||
implied warranties or conditions of merchantability and fitness
|
||||
for a particular purpose;
|
||||
|
||||
ii) effectively excludes on behalf of all other Contributors all
|
||||
liability for damages, including direct, indirect, special,
|
||||
incidental and consequential damages, such as lost profits;
|
||||
|
||||
iii) does not attempt to limit or alter the recipients' rights
|
||||
in the Source Code under section 3.2; and
|
||||
|
||||
iv) requires any subsequent distribution of the Program by any
|
||||
party to be under a license that satisfies the requirements
|
||||
of this section 3.
|
||||
|
||||
3.2 When the Program is Distributed as Source Code:
|
||||
|
||||
a) it must be made available under this Agreement, or if the
|
||||
Program (i) is combined with other material in a separate file or
|
||||
files made available under a Secondary License, and (ii) the initial
|
||||
Contributor attached to the Source Code the notice described in
|
||||
Exhibit A of this Agreement, then the Program may be made available
|
||||
under the terms of such Secondary Licenses, and
|
||||
|
||||
b) a copy of this Agreement must be included with each copy of
|
||||
the Program.
|
||||
|
||||
3.3 Contributors may not remove or alter any copyright, patent,
|
||||
trademark, attribution notices, disclaimers of warranty, or limitations
|
||||
of liability ("notices") contained within the Program from any copy of
|
||||
the Program which they Distribute, provided that Contributors may add
|
||||
their own appropriate notices.
|
||||
|
||||
4. COMMERCIAL DISTRIBUTION
|
||||
|
||||
Commercial distributors of software may accept certain responsibilities
|
||||
with respect to end users, business partners and the like. While this
|
||||
license is intended to facilitate the commercial use of the Program,
|
||||
the Contributor who includes the Program in a commercial product
|
||||
offering should do so in a manner which does not create potential
|
||||
liability for other Contributors. Therefore, if a Contributor includes
|
||||
the Program in a commercial product offering, such Contributor
|
||||
("Commercial Contributor") hereby agrees to defend and indemnify every
|
||||
other Contributor ("Indemnified Contributor") against any losses,
|
||||
damages and costs (collectively "Losses") arising from claims, lawsuits
|
||||
and other legal actions brought by a third party against the Indemnified
|
||||
Contributor to the extent caused by the acts or omissions of such
|
||||
Commercial Contributor in connection with its distribution of the Program
|
||||
in a commercial product offering. The obligations in this section do not
|
||||
apply to any claims or Losses relating to any actual or alleged
|
||||
intellectual property infringement. In order to qualify, an Indemnified
|
||||
Contributor must: a) promptly notify the Commercial Contributor in
|
||||
writing of such claim, and b) allow the Commercial Contributor to control,
|
||||
and cooperate with the Commercial Contributor in, the defense and any
|
||||
related settlement negotiations. The Indemnified Contributor may
|
||||
participate in any such claim at its own expense.
|
||||
|
||||
For example, a Contributor might include the Program in a commercial
|
||||
product offering, Product X. That Contributor is then a Commercial
|
||||
Contributor. If that Commercial Contributor then makes performance
|
||||
claims, or offers warranties related to Product X, those performance
|
||||
claims and warranties are such Commercial Contributor's responsibility
|
||||
alone. Under this section, the Commercial Contributor would have to
|
||||
defend claims against the other Contributors related to those performance
|
||||
claims and warranties, and if a court requires any other Contributor to
|
||||
pay any damages as a result, the Commercial Contributor must pay
|
||||
those damages.
|
||||
|
||||
5. NO WARRANTY
|
||||
|
||||
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT
|
||||
PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS"
|
||||
BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
|
||||
IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF
|
||||
TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR
|
||||
PURPOSE. Each Recipient is solely responsible for determining the
|
||||
appropriateness of using and distributing the Program and assumes all
|
||||
risks associated with its exercise of rights under this Agreement,
|
||||
including but not limited to the risks and costs of program errors,
|
||||
compliance with applicable laws, damage to or loss of data, programs
|
||||
or equipment, and unavailability or interruption of operations.
|
||||
|
||||
6. DISCLAIMER OF LIABILITY
|
||||
|
||||
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT
|
||||
PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS
|
||||
SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST
|
||||
PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
|
||||
EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
7. GENERAL
|
||||
|
||||
If any provision of this Agreement is invalid or unenforceable under
|
||||
applicable law, it shall not affect the validity or enforceability of
|
||||
the remainder of the terms of this Agreement, and without further
|
||||
action by the parties hereto, such provision shall be reformed to the
|
||||
minimum extent necessary to make such provision valid and enforceable.
|
||||
|
||||
If Recipient institutes patent litigation against any entity
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that the
|
||||
Program itself (excluding combinations of the Program with other software
|
||||
or hardware) infringes such Recipient's patent(s), then such Recipient's
|
||||
rights granted under Section 2(b) shall terminate as of the date such
|
||||
litigation is filed.
|
||||
|
||||
All Recipient's rights under this Agreement shall terminate if it
|
||||
fails to comply with any of the material terms or conditions of this
|
||||
Agreement and does not cure such failure in a reasonable period of
|
||||
time after becoming aware of such noncompliance. If all Recipient's
|
||||
rights under this Agreement terminate, Recipient agrees to cease use
|
||||
and distribution of the Program as soon as reasonably practicable.
|
||||
However, Recipient's obligations under this Agreement and any licenses
|
||||
granted by Recipient relating to the Program shall continue and survive.
|
||||
|
||||
Everyone is permitted to copy and distribute copies of this Agreement,
|
||||
but in order to avoid inconsistency the Agreement is copyrighted and
|
||||
may only be modified in the following manner. The Agreement Steward
|
||||
reserves the right to publish new versions (including revisions) of
|
||||
this Agreement from time to time. No one other than the Agreement
|
||||
Steward has the right to modify this Agreement. The Eclipse Foundation
|
||||
is the initial Agreement Steward. The Eclipse Foundation may assign the
|
||||
responsibility to serve as the Agreement Steward to a suitable separate
|
||||
entity. Each new version of the Agreement will be given a distinguishing
|
||||
version number. The Program (including Contributions) may always be
|
||||
Distributed subject to the version of the Agreement under which it was
|
||||
received. In addition, after a new version of the Agreement is published,
|
||||
Contributor may elect to Distribute the Program (including its
|
||||
Contributions) under the new version.
|
||||
|
||||
Except as expressly stated in Sections 2(a) and 2(b) above, Recipient
|
||||
receives no rights or licenses to the intellectual property of any
|
||||
Contributor under this Agreement, whether expressly, by implication,
|
||||
estoppel or otherwise. All rights in the Program not expressly granted
|
||||
under this Agreement are reserved. Nothing in this Agreement is intended
|
||||
to be enforceable by any entity that is not a Contributor or Recipient.
|
||||
No third-party beneficiary rights are created under this Agreement.
|
||||
|
||||
Exhibit A - Form of Secondary Licenses Notice
|
||||
|
||||
"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: {name license(s),
|
||||
version(s), and exceptions or additional permissions here}."
|
||||
|
||||
Simply including a copy of this Agreement, including this Exhibit A
|
||||
is not sufficient to license the Source Code under Secondary Licenses.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to
|
||||
look for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
22
backend/README.md
Normal file
22
backend/README.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# 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.
|
||||
89
backend/dev/user.clj
Normal file
89
backend/dev/user.clj
Normal file
@@ -0,0 +1,89 @@
|
||||
(ns user
|
||||
(:require [com.stuartsierra.component :as component]
|
||||
[clojure.tools.namespace.repl :as namespace]
|
||||
[backend.config :as config]
|
||||
[backend.db-migrate :as migrate]
|
||||
[io.pedestal.service-tools.dev :as dev]
|
||||
[clj-time
|
||||
[coerce :as tc]
|
||||
[core :as t]]
|
||||
[clojure.java.io :as io]
|
||||
[clojure.string :as string]))
|
||||
|
||||
(namespace/disable-reload!)
|
||||
(namespace/set-refresh-dirs "src" "dev")
|
||||
(defonce *system (atom nil))
|
||||
(defonce *db (atom nil))
|
||||
|
||||
(defn migrate []
|
||||
(migrate/migrate @*db))
|
||||
|
||||
(defn rollback []
|
||||
(migrate/rollback @*db))
|
||||
|
||||
(defn stop []
|
||||
(some-> @*system (component/stop))
|
||||
(reset! *system nil))
|
||||
|
||||
(defn refresh []
|
||||
(let [res (namespace/refresh)]
|
||||
(when (not= res :ok)
|
||||
(throw res))
|
||||
:ok))
|
||||
|
||||
(defn go
|
||||
[]
|
||||
(require 'backend.core)
|
||||
(dev/watch)
|
||||
(when-some [f (resolve 'backend.system/new-system)]
|
||||
(when-some [system (f config/config)]
|
||||
(when-some [system' (component/start system)]
|
||||
(reset! *system system')
|
||||
(reset! *db {:datasource (get-in @*system [:hikari :datasource])}))))
|
||||
(migrate))
|
||||
|
||||
(defn reset []
|
||||
(stop)
|
||||
(refresh)
|
||||
(go))
|
||||
|
||||
(defn get-unix-timestamp []
|
||||
(tc/to-long (t/now)))
|
||||
|
||||
(def date-format
|
||||
"Format for DateTime"
|
||||
"yyyyMMddHHmmss")
|
||||
(def migrations-dir
|
||||
"Default migrations directory"
|
||||
"resources/migrations/")
|
||||
(def ragtime-format-edn
|
||||
"EDN template for SQL migrations"
|
||||
"{:up [\"\"]\n :down [\"\"]}")
|
||||
|
||||
(defn migrations-dir-exist?
|
||||
"Checks if 'resources/migrations' directory exists"
|
||||
[]
|
||||
(.isDirectory (io/file migrations-dir)))
|
||||
|
||||
(defn now
|
||||
"Gets the current DateTime" []
|
||||
(.format (java.text.SimpleDateFormat. date-format) (new java.util.Date)))
|
||||
|
||||
(defn migration-file-path
|
||||
"Complete migration file path"
|
||||
[name]
|
||||
(str migrations-dir (now) "_" (string/replace name #"\s+|-+|_+" "_") ".edn"))
|
||||
|
||||
(defn create-migration
|
||||
"Creates a migration file with the current DateTime"
|
||||
[name]
|
||||
(let [migration-file (migration-file-path name)]
|
||||
(if-not (migrations-dir-exist?)
|
||||
(io/make-parents migration-file))
|
||||
(spit migration-file ragtime-format-edn)))
|
||||
|
||||
(defn reset-db
|
||||
[]
|
||||
(dotimes [i 100]
|
||||
(rollback))
|
||||
(migrate))
|
||||
3
backend/doc/intro.md
Normal file
3
backend/doc/intro.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Introduction to backend
|
||||
|
||||
TODO: write [great documentation](http://jacobian.org/writing/what-to-write/)
|
||||
36
backend/project.clj
Normal file
36
backend/project.clj
Normal file
@@ -0,0 +1,36 @@
|
||||
(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"]})
|
||||
24
backend/resources/config.edn
Normal file
24
backend/resources/config.edn
Normal file
@@ -0,0 +1,24 @@
|
||||
{:env #or [#env ENVIRONMENT "dev"]
|
||||
:port #or [#env PORT 8080]
|
||||
: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}}
|
||||
52
backend/resources/logback.xml
Normal file
52
backend/resources/logback.xml
Normal file
@@ -0,0 +1,52 @@
|
||||
<!-- 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>
|
||||
@@ -0,0 +1,10 @@
|
||||
{: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"]}
|
||||
@@ -0,0 +1,9 @@
|
||||
{: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"]}
|
||||
@@ -0,0 +1,9 @@
|
||||
{: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"]}
|
||||
@@ -0,0 +1,11 @@
|
||||
{: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
backend/resources/public
Symbolic link
1
backend/resources/public
Symbolic link
@@ -0,0 +1 @@
|
||||
/home/tienson/codes/projects/gitnotes/frontend/public
|
||||
35
backend/src/backend/auth.clj
Normal file
35
backend/src/backend/auth.clj
Normal file
@@ -0,0 +1,35 @@
|
||||
(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)))))))
|
||||
24
backend/src/backend/components/hikari.clj
Normal file
24
backend/src/backend/components/hikari.clj
Normal file
@@ -0,0 +1,24 @@
|
||||
(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}))
|
||||
26
backend/src/backend/components/http.clj
Normal file
26
backend/src/backend/components/http.clj
Normal file
@@ -0,0 +1,26 @@
|
||||
(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 {}))
|
||||
12
backend/src/backend/config.clj
Normal file
12
backend/src/backend/config.clj
Normal file
@@ -0,0 +1,12 @@
|
||||
(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:8080"
|
||||
"https://logseq.com"))
|
||||
58
backend/src/backend/cookie.clj
Normal file
58
backend/src/backend/cookie.clj
Normal file
@@ -0,0 +1,58 @@
|
||||
(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)))
|
||||
21
backend/src/backend/core.clj
Normal file
21
backend/src/backend/core.clj
Normal file
@@ -0,0 +1,21 @@
|
||||
(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"))
|
||||
33
backend/src/backend/db/refresh_token.clj
Normal file
33
backend/src/backend/db/refresh_token.clj
Normal file
@@ -0,0 +1,33 @@
|
||||
(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)))))
|
||||
32
backend/src/backend/db/repo.clj
Normal file
32
backend/src/backend/db/repo.clj
Normal file
@@ -0,0 +1,32 @@
|
||||
(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}))
|
||||
36
backend/src/backend/db/token.clj
Normal file
36
backend/src/backend/db/token.clj
Normal file
@@ -0,0 +1,36 @@
|
||||
(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}))
|
||||
45
backend/src/backend/db/user.clj
Normal file
45
backend/src/backend/db/user.clj
Normal file
@@ -0,0 +1,45 @@
|
||||
(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)}))
|
||||
16
backend/src/backend/db_migrate.clj
Normal file
16
backend/src/backend/db_migrate.clj
Normal file
@@ -0,0 +1,16 @@
|
||||
(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)))
|
||||
29
backend/src/backend/interceptors.clj
Normal file
29
backend/src/backend/interceptors.clj
Normal file
@@ -0,0 +1,29 @@
|
||||
(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)))})
|
||||
23
backend/src/backend/jwt.clj
Normal file
23
backend/src/backend/jwt.clj
Normal file
@@ -0,0 +1,23 @@
|
||||
(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}))
|
||||
103
backend/src/backend/routes.clj
Normal file
103
backend/src/backend/routes.clj
Normal file
@@ -0,0 +1,103 @@
|
||||
(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}}))}}]]])
|
||||
101
backend/src/backend/system.clj
Normal file
101
backend/src/backend/system.clj
Normal file
@@ -0,0 +1,101 @@
|
||||
(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]))))
|
||||
82
backend/src/backend/util.clj
Normal file
82
backend/src/backend/util.clj
Normal file
@@ -0,0 +1,82 @@
|
||||
(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))
|
||||
29
backend/src/backend/views/home.clj
Normal file
29
backend/src/backend/views/home.clj
Normal file
@@ -0,0 +1,29 @@
|
||||
(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 "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 "https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.0.1/dist/alpine.js" :defer true}]
|
||||
[:script {:src "/js/main.js"}]
|
||||
[:script {:src "/js/highlight.pack.js"}]]))
|
||||
7
backend/test/backend/core_test.clj
Normal file
7
backend/test/backend/core_test.clj
Normal file
@@ -0,0 +1,7 @@
|
||||
(ns backend.core-test
|
||||
(:require [clojure.test :refer :all]
|
||||
[backend.core :refer :all]))
|
||||
|
||||
(deftest a-test
|
||||
(testing "FIXME, I fail."
|
||||
(is (= 0 1))))
|
||||
20
now.json
20
now.json
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"version": 2,
|
||||
"env": {
|
||||
"NODE_ENV": "production",
|
||||
"GITHUB_APP_KEY": "@github_app_key",
|
||||
"GITHUB_APP_SECRET": "@github_app_secret",
|
||||
"GITHUB_REDIRECT_URI": "@github_redirect_uri",
|
||||
"COOKIE_SECRET": "@cookie_secret"
|
||||
},
|
||||
"builds": [
|
||||
{ "src": "api/index.js", "use": "@now/node"},
|
||||
{ "src": "/public/**", "use": "@now/static"}
|
||||
],
|
||||
"routes": [
|
||||
{ "src": "/api(.*)", "dest": "api/index.js" },
|
||||
{ "src": "/static/(.*)", "dest": "/public/static/$1" },
|
||||
{ "src": "/(.+js|.+css|.+png|.+svg|.+jpg|.+ico|robots.txt|.+map)", "dest": "/public/$1" },
|
||||
{ "src": "/(.*)", "dest": "/public/index.html" }
|
||||
]
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
* Gitnotes
|
||||
* Logseq
|
||||
A client-only note app which sync with your github repo. *Use it at your own risk!!!!*
|
||||
#+CAPTION: gitnote screenshot
|
||||
#+NAME: fig:screenshot.png
|
||||
[[./images/screenshot.png]]
|
||||
|
||||
** Demo
|
||||
http://gitnotes.tiensonqin.now.sh/
|
||||
http://logseq.tiensonqin.now.sh/
|
||||
|
||||
** Setup
|
||||
*** Create a github basic token
|
||||
1. Go to github.com Settings / Developer settings / Personal access tokens
|
||||
2. Click the button *Generate new token*
|
||||
3. Input =gitnotes= or anything in the *Note*
|
||||
3. Input =logseq= or anything in the *Note*
|
||||
4. Select the =repo= checkbox under *Select scopes*
|
||||
5. Click the *Generate token* button
|
||||
*** Go to the demo or your now.sh instance
|
||||
|
||||
6
web/.gitignore
vendored
6
web/.gitignore
vendored
@@ -1,7 +1,7 @@
|
||||
node_modules/
|
||||
public/static/js/cljs-runtime
|
||||
public/static/js/main.js
|
||||
public/static/js/manifest.edn
|
||||
public/js/cljs-runtime
|
||||
public/js/main.js
|
||||
public/js/manifest.edn
|
||||
|
||||
/.cpcache
|
||||
/target
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "gitnotes",
|
||||
"name": "logseq",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
@@ -9,7 +9,7 @@
|
||||
"watch": "npx shadow-cljs watch app",
|
||||
"release": "npx shadow-cljs release app",
|
||||
"server": "npx shadow-cljs server;",
|
||||
"clean": "rm -rf target; rm -rf public/static/js/compiled; rm -rf public/static/js/cljs-runtime"
|
||||
"clean": "rm -rf target; rm -rf public/js/compiled; rm -rf public/js/cljs-runtime"
|
||||
},
|
||||
"dependencies": {
|
||||
"browserfs": "^1.4.3",
|
||||
|
||||
@@ -209,15 +209,15 @@ dt { font-weight: bold; }
|
||||
|
||||
/* scroll-bg */
|
||||
.scroll-background {
|
||||
height: 400%; width: 400%; top: -25%; left: -100%; background-size: 800px auto; background-image: url('/static/img/hero-pattern-lg.png');
|
||||
height: 400%; width: 400%; top: -25%; left: -100%; background-size: 800px auto; background-image: url('/img/hero-pattern-lg.png');
|
||||
}
|
||||
|
||||
.angled-background {
|
||||
background-image: url('/static/img/angled-background.svg'); background-size: 100% auto; background-position: -5px -5px;
|
||||
background-image: url('/img/angled-background.svg'); background-size: 100% auto; background-position: -5px -5px;
|
||||
}
|
||||
|
||||
.scroll-background-2 {
|
||||
height: 800%; width: 400%; top: -100%; left: -100%; background-size: 400px auto; background-image: url('/static/img/hero-pattern-lg.png');
|
||||
height: 800%; width: 400%; top: -100%; left: -100%; background-size: 400px auto; background-image: url('/img/hero-pattern-lg.png');
|
||||
}
|
||||
|
||||
@-webkit-keyframes scrollSmall {0%{transform:rotate(-13deg) translateY(0)}to{transform:rotate(-13deg) translateY(-639px)}}
|
||||
|
Before Width: | Height: | Size: 125 B After Width: | Height: | Size: 125 B |
|
Before Width: | Height: | Size: 396 KiB After Width: | Height: | Size: 396 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@@ -3,13 +3,13 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no" name="viewport">
|
||||
<!-- <link href="/static/css/tailwind.min.css" rel="stylesheet" type="text/css"> -->
|
||||
<!-- <link href="/css/tailwind.min.css" rel="stylesheet" type="text/css"> -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tailwindcss/ui@latest/dist/tailwind-ui.min.css">
|
||||
<link href="/static/css/org.css" rel="stylesheet">
|
||||
<link href="/static/css/style.css" rel="stylesheet" type="text/css">
|
||||
<link href="/css/org.css" rel="stylesheet">
|
||||
<link href="/css/style.css" rel="stylesheet" type="text/css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet">
|
||||
<link href="/static/css/highlight.css" rel="stylesheet">
|
||||
<title>Gitnotes</title>
|
||||
<link href="/css/highlight.css" rel="stylesheet">
|
||||
<title>Logseq</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
@@ -17,11 +17,11 @@
|
||||
</script>
|
||||
<script src="https://unpkg.com/isomorphic-git@0.78.5/dist/bundle.umd.min.js"></script>
|
||||
<script>
|
||||
window.fs = new LightningFS('gitnotes');
|
||||
window.fs = new LightningFS('logseq');
|
||||
git.plugins.set('fs', window.fs);
|
||||
window.pfs = window.fs.promises;
|
||||
</script>
|
||||
<script src="/static/js/highlight.pack.js"></script>
|
||||
<script src="/static/js/main.js"></script>
|
||||
<script src="/js/highlight.pack.js"></script>
|
||||
<script src="/js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -20,16 +20,16 @@
|
||||
{:target :browser
|
||||
:modules {:main {:init-fn frontend.core/init}}
|
||||
|
||||
:output-dir "public/static/js"
|
||||
:asset-path "/static/js"
|
||||
:output-dir "public/js"
|
||||
:asset-path "/js"
|
||||
|
||||
:compiler-options {:infer-externs :auto
|
||||
:externs ["datascript/externs.js"]}
|
||||
;; TODO: purge-css not working properly
|
||||
;; :build-hooks [(shadow.hooks/purge-css
|
||||
;; {:css-source "node_modules/tailwindcss/dist/tailwind.min.css"
|
||||
;; :js-globs ["public/static/js/*.js"]
|
||||
;; :public-dir "public/static/css"})]
|
||||
;; :js-globs ["public/js/*.js"]
|
||||
;; :public-dir "public/css"})]
|
||||
:devtools
|
||||
;; before live-reloading any code call this function
|
||||
{:before-load frontend.core/stop
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
[:div.flex.items-center.justify-between
|
||||
[:div
|
||||
[:img.h-6.lg:h-8.xl:h-9
|
||||
{:alt "Gitnotes",
|
||||
:src "/static/img/tailwindui-logo-on-dark.svg"}]]
|
||||
{:alt "Logseq",
|
||||
:src "/img/logo.png"}]]
|
||||
[:div
|
||||
[:a.text-sm.font-semibold.text-white.focus:outline-none.focus:underline
|
||||
{:href (str config/api "login/github")}
|
||||
@@ -55,7 +55,7 @@
|
||||
{:href "https://twitter.com/adamwathan"}]
|
||||
[:div.flex-shrink-0
|
||||
[:img.h-12.w-12.rounded-full.border-2.border-white
|
||||
{:alt "", :src "/static/img/adam.jpg"}]]
|
||||
{:alt "", :src "/img/adam.jpg"}]]
|
||||
[:div.ml-3
|
||||
[:p.font-semibold.text-white.leading-tight "Adam Wathan"]
|
||||
[:p.text-sm.text-gray-500.leading-tight
|
||||
@@ -64,7 +64,7 @@
|
||||
{:href "https://twitter.com/steveschoger"}]
|
||||
[:div.flex-shrink-0
|
||||
[:img.h-12.w-12.rounded-full.border-2.border-white
|
||||
{:alt "", :src "/static/img/steve.jpg"}]]
|
||||
{:alt "", :src "/img/steve.jpg"}]]
|
||||
[:div.ml-3
|
||||
[:p.font-semibold.text-white.leading-tight "Steve Schoger"]
|
||||
[:p.text-sm.text-gray-500.leading-tight
|
||||
|
||||
@@ -90,8 +90,8 @@
|
||||
:stroke-linecap "round"}]]]])
|
||||
[:div.flex-shrink-0.flex.items-center.h-16.px-4.bg-gray-900
|
||||
[:img.h-8.w-auto
|
||||
{:alt "Gitnotes",
|
||||
:src "/static/img/logo.png"}]]
|
||||
{:alt "Logseq",
|
||||
:src "/img/logo.png"}]]
|
||||
[:div.flex-1.h-0.overflow-y-auto
|
||||
(sidebar-nav)]
|
||||
]]
|
||||
@@ -99,8 +99,8 @@
|
||||
[:div.flex.flex-col.w-64
|
||||
[:div.flex.items-center.h-16.flex-shrink-0.px-4.bg-gray-900
|
||||
[:img.h-8.w-auto
|
||||
{:alt "Gitnotes",
|
||||
:src "/static/img/logo.png"}]]
|
||||
{:alt "Logseq",
|
||||
:src "/img/logo.png"}]]
|
||||
[:div.h-0.flex-1.flex.flex-col.overflow-y-auto
|
||||
(sidebar-nav)]]]
|
||||
[:div.flex.flex-col.w-0.flex-1.overflow-hidden
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
(def website
|
||||
(if dev?
|
||||
"http://localhost:8080"
|
||||
"https://gitnotes.now.sh"))
|
||||
"https://logseq.now.sh"))
|
||||
|
||||
(def api
|
||||
(if dev?
|
||||
"http://localhost:3000/api/"
|
||||
(str website "/api/")))
|
||||
"http://localhost:3000/api/v1/"
|
||||
(str website "/api/v1/")))
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
;; TODO: don't persistent :github/token
|
||||
|
||||
(def datascript-db "gitnotes/DB")
|
||||
(def datascript-db "logseq/DB")
|
||||
(def schema
|
||||
{:db/ident {:db/unique :db.unique/identity}
|
||||
:github/token {}
|
||||
|
||||
@@ -160,7 +160,7 @@
|
||||
|
||||
(defn new-notification
|
||||
[text]
|
||||
(js/Notification. "Gitnotes" #js {:body text
|
||||
(js/Notification. "Logseq" #js {:body text
|
||||
;; :icon logo
|
||||
}))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user