Versioned CMS REST API for www.marklunds.com
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
bin
src/app
.gitignore
LICENSE
Procfile
README.md
project.clj

README.md

marklunds-api

This the content API that serves the marklunds Node web app or www.marklunds.com. This API is based on the versioned framework. The versioned framework provides a CMS REST API based on MongoDB with user authentication, JSON schema validation, versioning, publishing, and relationships.

Restore Dev DB from Production

bin/db-restore

Getting Started

First make sure you have Leiningen/Clojure and Mongodb installed.

Get the source:

git clone git@github.com:peter/versioned-example.git
cd versioned-example

Create an admin user:

lein repl
(require '[app.core :as marklunds])
(def system (marklunds/-main :start-web false))
(require '[versioned.models.users :as users])
(users/create (:app system) {:name "Peter Marklund" :email "admin@example.com" :password "admin" :permission "write"})
exit

Start the server:

lein run

In a different terminal, log in:

export BASE_URL=http://localhost:5000/v1

export BASE_URL=https://marklunds-api.herokuapp.com/v1

curl -i -X POST -H 'Content-Type: application/json' -d '{"email": "peter@marklunds.com", "password": ""}' $BASE_URL/login
export TOKEN=<token in header response above>

Single step login can looks something like this:

export TOKEN=$(curl -X POST -H 'Content-Type: application/json' -d '{"email": "peter@marklunds.com", "password": ""}' $BASE_URL/login | jsonq 'data.attributes.access_token')

Create a blog post:

echo '{"data": {"attributes": {"subject": "test post", "body": "test body"}}}' | http POST $BASE_URL/blog_posts Authorization:"Bearer $TOKEN"

http $BASE_URL/blog_posts/449 Authorization:"Bearer $TOKEN"

Create model schema:

echo '{"data": {"attributes": {"model_type": "blog_posts", "schema": {"type": "object", "properties": {"title": {"type": "string"}}}}}}' | http POST $BASE_URL/models Authorization:"Bearer $TOKEN"

Basic CRUD workflow:

# create
curl -i -X POST -H 'Content-Type: application/json' -H "Authorization: Bearer $TOKEN" -d '{"data": {"attributes": {"title": "hello world"}}}' $BASE_URL/blog_posts

# get
curl -i -H "Authorization: Bearer $TOKEN" $BASE_URL/blog_posts/1

# list
curl -i -H "Authorization: Bearer $TOKEN" $BASE_URL/blog_posts

# update
curl -i -X PUT -H 'Content-Type: application/json' -H "Authorization: Bearer $TOKEN" -d '{"data": {"attributes": {"title": "hello world EDIT"}}}' $BASE_URL/blog_posts/1

# delete
curl -i -X DELETE -H "Authorization: Bearer $TOKEN" $BASE_URL/blog_posts/1

Dropbox

(require '[app.core :as marklunds])
(def system (marklunds/-main :start-web false))
(require '[app.dropbox :as dropbox :reload-all true])
(dropbox/save (:app system) {:type "foobar" :id 1 :body "foobar"})

Get a Local Checkout of Versioned Library

To checkout the versioned library (if you haven't already):

cd <your-parent-src-dir>
git clone https://github.com/peter/versioned <your-src-dir>/versioned

Then, in this repository, do:

mkdir checkouts
ln -s <your-src-dir>/versioned checkouts/versioned

Now you can make changes in your local versioned checkout and they will be reflected in this app.

Importing Blog Posts

(require '[app.core :as marklunds])
(def system (marklunds/-main :start-web false))
(def app (:app system))
(require '[versioned.util.date :as d])
(require '[versioned.model-api :as model-api])
(require '[cheshire.core :as json])
(require '[clj-http.client :as client])
(require '[versioned.db-api :as db-api])
(db-api/delete (:database app) :blog_posts {})
(db-api/delete (:database app) :blog_posts_versions {})
(def file-url "https://www.dropbox.com/s/9g9wf8mh9i2n9ht/marklunds-postgresql-2018-02.json?dl=1")
(def file-path "/Users/peter/Dropbox/data/marklunds-postgresql-2018-02.json")
(def file-str (:body (client/get file-url)))
(def lines (as-> file-str v
                (clojure.string/replace v "\\\\" "\\")
                (clojure.string/split-lines v)))
; (seq (char-array (nth lines 0)))
(defn parse-date [date-str]
  (let [parsable-str (if (re-matches #".+\.\d\d\d$" date-str)
                       (str date-str "Z")
                       (str date-str ".000Z"))]
    (d/parse-datetime parsable-str)))
(defn parse-line [line]
  (as-> line v
        (json/parse-string v)
        ; id subject body comments_count created_at
        (clojure.set/rename-keys v {"created_at" "legacy_created_at"})
        (clojure.walk/keywordize-keys v)
        (merge v {:created_by "peter@marklunds.com"})
        (assoc v :legacy_created_at (parse-date (:legacy_created_at v)))))
(def docs (map parse-line lines))
(for [doc docs]
  (let [result (model-api/create app (get-in app [:models :blog_posts]) doc)]
    (println "result" result)))
; 327 blog posts
(require '[app.core :as marklunds])
(def system (marklunds/-main :start-web false))
(def app (:app system))
(require '[versioned.db-api :as db-api])
(def blog-posts (db-api/find (:database app) :blog_posts {} {:per-page 10000}))
(for [doc blog-posts]
  (let [query (select-keys doc [:id])
        update {:$set {:created_at (:legacy_created_at doc)}
                :$unset {:legacy_created_at ""}}]
    (db-api/update (:database app) :blog_posts query update)))
(require '[app.core :as marklunds])
(def system (marklunds/-main :start-web false))
(def app (:app system))
(require '[versioned.db-api :as db-api])
(def blog-posts (db-api/find (:database app) :blog_posts {} {:per-page 10000}))
(for [doc blog-posts]
  (let [query (select-keys doc [:id])
        update {:$set {:published_version (:version doc)}}]
    (db-api/update (:database app) :blog_posts query update)))

Import Diary Entries

heroku pg:backups:capture -a savorings
curl $(heroku pg:backups:url -a savorings) > ~/tmp/savorings-prod.sql
pg_restore --verbose --clean --no-acl --no-owner -d savorings_development  ~/tmp/savorings-prod.sql

psql savorings_development
COPY (SELECT ROW_TO_JSON(t)
      FROM (SELECT * FROM entries) t)
      TO '/Users/peter/Dropbox/data/savorings-diary-postgresql-2018-02.json';
(require '[app.core :as marklunds])
(def system (marklunds/-main :start-web false))
(def app (:app system))
(require '[versioned.util.date :as d])
(require '[versioned.model-api :as model-api])
(require '[cheshire.core :as json])
(require '[clj-http.client :as client])
(require '[versioned.db-api :as db-api])
(db-api/delete (:database app) :diary {})
(db-api/delete (:database app) :diary_versions {})
(def file-path "/Users/peter/Dropbox/data/savorings-diary-postgresql-2018-02.json")
(def file-url "https://www.dropbox.com/s/djw0fkbrqybd9j7/savorings-diary-postgresql-2018-02.json?dl=1")
(def file-str (:body (client/get file-url)))
(def lines (as-> file-str v
                (clojure.string/replace v "\\\\" "\\")
                (clojure.string/split-lines v)))
(defn parse-date [date-str]
  (let [parsable-str (if (re-matches #".+\.\d\d\d$" date-str)
                       (str date-str "Z")
                       (str date-str ".000Z"))]
    (d/parse-datetime parsable-str)))
(defn parse-line [line]
  (as-> line v
        (json/parse-string v)
        ; id subject body comments_count created_at
        (clojure.set/rename-keys v {"inserted_at" "legacy_created_at"})
        (clojure.walk/keywordize-keys v)
        (merge v {:created_by "peter@marklunds.com"})
        (assoc v :legacy_created_at (parse-date (:legacy_created_at v)))
        (dissoc v :updated_at)))
(def docs (map parse-line lines))
(for [doc docs]
  (let [result (model-api/create app (get-in app [:models :diary]) doc)]
    (println result)))
; 1037 diary entries (count lines)
(require '[app.core :as marklunds])
(def system (marklunds/-main :start-web false))
(def app (:app system))
(require '[versioned.db-api :as db-api])
(def diary-entries (db-api/find (:database app) :diary {} {:per-page 10000}))
(for [doc diary-entries]
  (let [query (select-keys doc [:id])
        update {:$set {:created_at (:legacy_created_at doc)}
                :$unset {:legacy_created_at ""}}]
    (db-api/update (:database app) :diary query update)))