diff options
-rw-r--r-- | README.md | 112 | ||||
-rw-r--r-- | gn/data/hits.scm | 22 | ||||
-rw-r--r-- | gn/runner/gemma.scm | 19 | ||||
-rwxr-xr-x | scripts/precompute/list-traits-to-compute.scm | 76 | ||||
-rwxr-xr-x | scripts/precompute/run-gemma.scm | 42 | ||||
-rw-r--r-- | web/gn-uri.scm | 47 | ||||
-rw-r--r-- | web/sxml.scm | 553 | ||||
-rw-r--r-- | web/view/markdown.scm | 136 | ||||
-rw-r--r--[-rwxr-xr-x] | web/webserver.scm | 333 |
9 files changed, 853 insertions, 487 deletions
@@ -31,6 +31,118 @@ Next fire up emacs with `emacs-geiser-guile` and connect to the running web serv Some tooling and scripts that run independently are stored in `./scripts`. +Here’s the entire markdown content combined into a single, copyable file: + + +# Gn-Markdown + +Gn-Markdown is an API endpoint to edit, parse, and commit markdown files for gn-docs. + +## How to Test the APIs + +1. **Navigate to the Web Directory and Start the Server** + +```sh +cd web +export CURRENT_REPO_PATH=<path-to-git-repo-with-files> +export CGIT_REPO_PATH=<path-to-git-bare-repo> +. .guix-shell -- guile -L .. --listen=1970 -e main ./webserver.scm 8091 +``` + +2. **Test Endpoints** + +The main endpoints provided are `/edit` and `/commit`. More endpoints may be added in the future. + +## Edit (GET) + +This is a GET request to retrieve a file's details. Make sure you pass a valid file_path as search_query (the path should be relative to the repo) + +**Request Example:** + +```bash + +curl -G -d "file_path=test.md" localhost:8091/edit + +``` + +**Expected Success Response:** + +```json +{ +"file_path": "test.md", +"content": "Test for new user\n test 2 for line\n test 3 for new line\n ## real markdown two test\n", +"hash": "ecd96f27c45301279150fbda411544687db1aa45" +} +``` + +**Expected Error Response (Status 400):** + +```json +{ +"error": <error_type>, +"msg": <error_reason> +} +``` + +## Commit (POST) + +This is a POST request to commit changes to a file. + +**Request URL:** + +```bash + +curl -X POST http://127.0.0.1:8091/commit \ +-H 'Content-Type: application/json' \ +-d '{ +"content": "make test commit", +"filename": "test.md", +"email": "test@gmail.com", +"username": "test", +"commit_message": "init commit", +"prev_commit": "7cbfc40d98b49a64e98e7cd562f373053d0325bd" +}' + +``` + + + +**Expected Response for success:** + +```json +{ +"status": "201", +"message": "Committed file successfully", +"content": "Test for new user\n test 2 for line\n test 3 for new line\n ## real markdown two test\n", +"commit_sha": "47df3b7f13a935d50cc8b40e98ca9e513cba104c", +"commit_message": "commit by genetics" +} + +``` + +**If No Changes to File:** + +```json +{ +"status": "200", +"message": "Nothing to commit, working tree clean", +"commit_sha": "ecd96f27c45301279150fbda411544687db1aa45" +} +``` + +**Expected Error Response:** + +```json +{ +"error": "system-error", +"msg": "Commits do not match. Please pull in the latest changes for the current commit *ecd96f27c45301279150fbda411544687db1aa45* and previous commits." +} +``` + +## Notes + +This is meant to be used as api endpoint only to edit any local repo; Clients are expected to handle other service e.g User Interface, authentication + # Development ``` diff --git a/gn/data/hits.scm b/gn/data/hits.scm index f7ce49e..85c4912 100644 --- a/gn/data/hits.scm +++ b/gn/data/hits.scm @@ -5,6 +5,7 @@ #:use-module (ice-9 iconv) #:use-module (ice-9 receive) #:use-module (ice-9 string-fun) + #:use-module (srfi srfi-9) ;; #:use-module (gn db sparql) #:use-module (dbi dbi) #:use-module (gn db mysql) @@ -17,11 +18,26 @@ get-precompute-hit set-precompute-hit-status! update-precompute! + hit-data-id + hit-probeset-id + hit-probesetfreeze-id )) -(define (get-precompute-hits db prev-id num) - (dbi-query db (string-append "select Locus, DataId, ProbeSetId, ProbeSetFreezeId from ProbeSetXRef where DataId>" (int-to-string prev-id) " AND Locus_old is NULL ORDER BY DataId LIMIT " (format #f "~d" num))) - (get-rows db '())) + +(define-record-type <hit> + (make-hit data-id probeset-id probesetfreeze-id) + hit? + (data-id hit-data-id) + (probeset-id hit-probeset-id) + (probesetfreeze-id hit-probesetfreeze-id) + ) + +(define (get-precompute-hits db first-id num) + (dbi-query db (string-append "select Locus, DataId, ProbeSetId, ProbeSetFreezeId from ProbeSetXRef where DataId>" (int-to-string first-id) " AND Locus_old is NULL ORDER BY DataId LIMIT " (int-to-string num))) + (map (lambda (r) + (make-hit (assoc-ref r "DataId") (assoc-ref r "ProbeSetId") (assoc-ref r "ProbeSetFreezeId"))) + (get-rows db '()) + )) (define (get-precompute-hit db prev-id) (car (get-precompute-hits db prev-id 1))) diff --git a/gn/runner/gemma.scm b/gn/runner/gemma.scm index 69991dd..9a5c0fc 100644 --- a/gn/runner/gemma.scm +++ b/gn/runner/gemma.scm @@ -39,24 +39,27 @@ )) ) -(define (invoke-gemma-wrapper-loco name trait-name pheno-fn) +(define (invoke-gemma-wrapper-loco name trait-name trait-fn pheno-fn geno-fn) "Create a tmpdir and invoke gemma-wrapper using parallel LOCO. Note that at this point we use a number of defaults for BXD" (let* [(population "BXD") (sys-tmpdir (getenv "TMPDIR")) (tmpdir (mkdtemp (string-append sys-tmpdir "/run-gemma-XXXXXX"))) (k-json-fn (string-append tmpdir "/K.json")) - (gwa-json-fn (string-append tmpdir "/GWA.json"))] + (gwa-json-fn (string-append tmpdir "/GWA.json")) + (trait-json-fn (string-append tmpdir "/" trait-fn))] + (copy-file trait-fn trait-json-fn) ;; --- First we compute K - control output goes to K.json - (let [(err (system (string-append "/gemma-wrapper/bin/gemma-wrapper --verbose --population \"" population "\" --name \"" name "\" --trait \"" trait-name "\" --verbose --loco --json --parallel -- -gk -g BXD.8_geno.txt.gz -p " pheno-fn " -a BXD.8_snps.txt > " k-json-fn )))] + (let [(err (system (string-append "/gemma-wrapper/bin/gemma-wrapper --verbose --population \"" population "\" --name \"" name "\" --trait \"" trait-name "\" --verbose --loco --json --parallel -- -gk -g " geno-fn " -p " pheno-fn " -a BXD.8_snps.txt > " k-json-fn )))] (if (not (= err 0)) (exit err))) - (let [(err (system (string-append "/gemma-wrapper/bin/gemma-wrapper --population \"" population "\" --name \"" name "\" --id \"" trait-name "\" --trait \"" trait-name "\" --verbose --loco --json --input " k-json-fn " -- -g BXD.8_geno.txt.gz -p " pheno-fn " -a BXD.8_snps.txt -lmm 9 -maf 0.1 > " gwa-json-fn)))] + (let [(err (system (string-append "/gemma-wrapper/bin/gemma-wrapper --meta \"" trait-json-fn "\" --population \"" population "\" --name \"" name "\" --id \"" trait-name "\" --trait \"" trait-name "\" --verbose --loco --json --lmdb --input " k-json-fn " -- -g " geno-fn " -p " pheno-fn " -a BXD.8_snps.txt -lmm 9 -maf 0.1 > " gwa-json-fn)))] (if (not (= err 0)) (exit err))) - ;; (delete-file pheno-fn) - ;; (delete-file gwa-json-fn) - ;; (delete-file k-json-fn) - ;; (rmdir tmpdir) + (delete-file pheno-fn) + (delete-file gwa-json-fn) + (delete-file k-json-fn) + (delete-file trait-json-fn) + (rmdir tmpdir) ) ) diff --git a/scripts/precompute/list-traits-to-compute.scm b/scripts/precompute/list-traits-to-compute.scm index 2c48d83..9f900d1 100755 --- a/scripts/precompute/list-traits-to-compute.scm +++ b/scripts/precompute/list-traits-to-compute.scm @@ -78,19 +78,50 @@ When that is the case we might as well write the phenotype file because we have (json) (rnrs bytevectors) (srfi srfi-1) + (srfi srfi-9) (srfi srfi-19) ; time ) +(define (get-trait db probeset-id) + (dbi-query db (string-append "select Id,Chr,Mb,Name,Symbol,description from ProbeSet where Id=" (int-to-string probeset-id) " limit 1")) + (get-row db) + ) + +#! + +The following is produced by gemma-wrapper as metadata + + "meta": { + "type": "gemma-wrapper", + "version": "0.99.7-pre1", + "population": "BXD", + "name": "HC_U_0304_R", + "trait": "101500_at", + "url": "https://genenetwork.org/show_trait?trait_id=101500_at&dataset=HC_U_0304_R", + "archive_GRM": "46bfba373fe8c19e68be6156cad3750120280e2e-gemma-cXX.tar.xz", + "archive_GWA": "779a54a59e4cd03608178db4068791db4ca44ab3-gemma-GWA.tar.xz", + "dataid": 75629, + "probesetid": 1097, + "probesetfreezeid": 7 + } -(define (write-json-ld id recs) +!# + + +(define (write-json-ld id name trait trait-name probesetfreeze-id probeset-id recs) ;; see also https://www.w3.org/2018/jsonld-cg-reports/json-ld/ (display id) + (display ":") + (display name) + (display ":") + (display trait-name) (newline) (let* [(traits (map (lambda (r) (match r [(strain-id . value) (cons (bxd-name strain-id) value)] )) (reverse recs))) + (uri (format #f "https://genenetwork.org/show_trait?trait_id=~a&dataset=~a" trait-name name)) (sha256 (sha-256->string (sha-256 (string->utf8 (scm->json-string traits))))) (json-data `(("@context" . "https://genenetwork.org/resource") (type . traits) @@ -98,8 +129,16 @@ When that is the case we might as well write the phenotype file because we have (steps . ()) (sha256 . ((input-traits . ,sha256))) (time . ,(date->string (time-utc->date (current-time)))))) - (traits . - ((,id . ,traits)))))] + (data . + ((,id . + ((group . "BXD") + (probesetfreeze-id . ,probesetfreeze-id) + (probeset-id . ,probeset-id) + (name . ,name) + (trait-name . ,trait-name) + (uri . ,uri) + (traits . ,traits) + ))))))] (call-with-output-file (string-append (number->string id) ".json") (lambda (port) (put-string port (scm->json-string json-data)))) @@ -111,22 +150,23 @@ When that is the case we might as well write the phenotype file because we have (begin ;; (let [(bxd-strains (memo-bxd-strain-id-names #:used-for-mapping? #t))] (define (run-list-traits-to-compute db num prev-id) + ;; ---- Build a query to collect num traits (let* [(count (if (< batch-size num) batch-size num)) (rest (- num count)) (hits (get-precompute-hits db prev-id count)) - (data-ids (map (lambda (hit) - (let* [(data-id (assoc-ref hit "DataId")) - ; (data-id-str (int-to-string data-id)) - ] - data-id)) + (data-ids (map (lambda (h) + (hit-data-id h)) hits)) (data-str-ids (map (lambda (id) (string-append "Id=" (int-to-string id))) data-ids)) (data-ids-query (string-join data-str-ids " OR ")) (query (string-append "SELECT Id,StrainId,value FROM ProbeSetData WHERE " data-ids-query)) ] + (format #t "writing-phenotypes from ~d to ~d batch ~d to ~d\n" first-id (+ first-id num) prev-id (+ prev-id count)) + (display query) (dbi-query db query) + ;; ---- Walk each resulting trait and build a hash of data-id and list of trait values (let [(id-traits (get-rows db '())) (nrecs '())] (for-each (lambda (r) @@ -140,12 +180,20 @@ When that is the case we might as well write the phenotype file because we have )] (set! nrecs (assoc-set! nrecs data-id lst)))) id-traits) - (for-each (lambda (r) - (match r - ((id . recs) (if (has-bxd? recs) - (write-json-ld id recs) - )) - )) nrecs) + ;; --- create the json output as a file by walking traits and hits + (for-each (lambda (h) + (let* [ + (id (hit-data-id h)) + (probeset-id (hit-probeset-id h)) + (probesetfreeze-id (hit-probesetfreeze-id h)) + (name (dataset-name db probesetfreeze-id)) + (trait (get-trait db probeset-id)) + (trait-name (assoc-ref trait "Name")) + (recs (assoc-ref nrecs id)) + ] + (if (has-bxd? recs) + (write-json-ld id name trait trait-name probesetfreeze-id probeset-id recs) + ))) hits) (if (> rest 0) (run-list-traits-to-compute db rest (first (reverse data-ids)))) ;; start precompute ))) diff --git a/scripts/precompute/run-gemma.scm b/scripts/precompute/run-gemma.scm index e6a4e26..4952834 100755 --- a/scripts/precompute/run-gemma.scm +++ b/scripts/precompute/run-gemma.scm @@ -9,7 +9,7 @@ Run from base dir with and with some extra paths (for gemma) -~/opt/guix-pull/bin/guix shell -C -F xz tar time parallel coreutils-minimal guile guile-dbi guile-json ruby --expose=/home/wrk/iwrk/opensource/code/genetics/gemma-wrapper/=/gemma-wrapper --expose=/home/wrk/iwrk/opensource/code/genetics/gemma/=/gemma -- env TMPDIR=tmp GEMMA_COMMAND=/gemma/bin/gemma-0.98.5-linux-static-debug guile -L . -e main -s ./scripts/precompute/run-gemma.scm +~/opt/guix-pull/bin/guix shell -C -F xz python python-lmdb tar time parallel coreutils-minimal guile guile-dbi guile-json ruby --expose=/home/wrk/iwrk/opensource/code/genetics/gemma-wrapper/=/gemma-wrapper --expose=/home/wrk/iwrk/opensource/code/genetics/gemma/=/gemma -- env TMPDIR=tmp GEMMA_COMMAND=/gemma/bin/gemma-0.98.5-linux-static-debug guile -L . -e main -s ./scripts/precompute/run-gemma.scm --id 21529 !# @@ -30,24 +30,36 @@ and with some extra paths (for gemma) ;; (write args) (let* [ (option-spec '( (version (single-char #\v) (value #f)) + (id (value #t)) (help (single-char #\h) (value #f)))) (options (getopt-long args option-spec)) + (show-version (option-ref options 'version #f)) (help-wanted (option-ref options 'help #f))] - (display "RUNNING") + (if show-version + (begin + (display "run-gemma version 1.0\n") + (exit))) (if help-wanted - (format #t "list-traits-to-compute writes JSON traits files from the GN DB -Usage: list-traits-to-compute [options...] - -h, --help Display this help -")) - (let [(trait-name "115475")] - (call-with-input-file "115475.json" + (format #t "run-gemma +Usage: run-gemma [options...] filename(s) + --id Run on identifier + -v --version Display version + -h --help Display this help +") + (let* [(trait-id (option-ref options 'id "0")) + (trait-fn (string-append trait-id ".json")) + ] + + (call-with-input-file trait-fn (lambda (port) (let* [(json (json->scm port)) - (dataset (assoc-ref json "traits")) - (dataset-name (car (car dataset))) - (traits (assoc-ref dataset dataset-name)) + (dataset (car (assoc-ref json "data"))) + (data (cdr dataset)) + (dataset-name (assoc-ref data "name")) + (trait-name (assoc-ref data "trait-name")) + (traits (assoc-ref data "traits")) + (pheno-fn (string-append trait-id "-pheno.txt")) ] - (display dataset) - (write-pheno-file "pheno.txt" traits) - (invoke-gemma-wrapper-loco dataset-name trait-name "pheno.txt") - )))))) + (write-pheno-file pheno-fn traits) + (invoke-gemma-wrapper-loco dataset-name trait-name trait-fn pheno-fn "BXD.8_geno.txt.gz") + ))))))) diff --git a/web/gn-uri.scm b/web/gn-uri.scm index b849e95..3455d51 100644 --- a/web/gn-uri.scm +++ b/web/gn-uri.scm @@ -3,11 +3,10 @@ #:use-module (ice-9 match) #:use-module (ice-9 regex) - #:export ( - get-version - ; url-parse-id - ; normalize-id - ; strip-lang + #:export (get-version + ;; url-parse-id + ;; normalize-id + ;; strip-lang mk-meta mk-data mk-doc @@ -18,50 +17,38 @@ mk-predicate prefix url-parse-id - normalize-id - )) - + normalize-id)) (define (normalize-id str) ;; (string-replace-substring (string-downcase str) " " "_") (match str (#f "unknown") - (_ (string-replace-substring str " " "_")) - )) + (_ (string-replace-substring str " " "_")))) (define (url-parse-id uri) (if uri - (car (reverse (string-split uri #\057))) - "unknown" - )) + (car (reverse (string-split uri #\/))) "unknown")) (define get-version "4.0.0") -; (define base-url -; "https://luna.genenetwork.org") - -;(define (prefix) -; "Build the API URL including version" -; (string-append base-url "/api/v" get-version)) - (define base-url "http://localhost:8091") -(define uri-base-url ; always points to genenetwork.org! +(define uri-base-url + ;always points to genenetwork.org! "http://genenetwork.org") (define (prefix) - "Build the API URL including version" - base-url) + "Build the API URL including version" base-url) -(define* (mk-url postfix #:optional (ext "")) +(define* (mk-url postfix + #:optional (ext "")) "Makes a fully qualified URL by adding the path (postfix+ext) to the API URL. If there is an existing http+hostname no prefix is added" (match (string-match "^http:" postfix) - [ #f (string-append (prefix) "/" postfix ext)] - [ _ (string-append postfix ext)] - )) + (#f (string-append (prefix) "/" postfix ext)) + (_ (string-append postfix ext)))) (define* (mk-uri postfix) "Add the path to the GN URI. A URI always points to http://genenetwork.org/" @@ -92,9 +79,11 @@ (define (mk-id postfix) "Expand URL to make $api/id/identifier. If postfix is a path it will only apply the last element" - (mk-url (string-append "id" "/" (url-parse-id postfix)))) + (mk-url (string-append "id" "/" + (url-parse-id postfix)))) (define (mk-gnid postfix) "Expand URL to make http://genenetwork.org/id/identifier. If postfix is a path it will only apply the last element" - (mk-uri (string-append "id" "/" (url-parse-id postfix)))) + (mk-uri (string-append "id" "/" + (url-parse-id postfix)))) diff --git a/web/sxml.scm b/web/sxml.scm index de30b3f..29eeee3 100644 --- a/web/sxml.scm +++ b/web/sxml.scm @@ -30,277 +30,274 @@ #:export (sxml->html)) (define %self-closing-tags - '(area - base - br - col - command - embed - hr - img - input - keygen - link - meta - param - source - track - wbr)) + '(area base + br + col + command + embed + hr + img + input + keygen + link + meta + param + source + track + wbr)) (define (self-closing-tag? tag) "Return #t if TAG is self-closing." (pair? (memq tag %self-closing-tags))) (define %escape-chars - (alist->hash-table - '((#\" . "quot") - (#\& . "amp") - (#\' . "apos") - (#\< . "lt") - (#\> . "gt") - (#\¡ . "iexcl") - (#\¢ . "cent") - (#\£ . "pound") - (#\¤ . "curren") - (#\¥ . "yen") - (#\¦ . "brvbar") - (#\§ . "sect") - (#\¨ . "uml") - (#\© . "copy") - (#\ª . "ordf") - (#\« . "laquo") - (#\¬ . "not") - (#\® . "reg") - (#\¯ . "macr") - (#\° . "deg") - (#\± . "plusmn") - (#\² . "sup2") - (#\³ . "sup3") - (#\´ . "acute") - (#\µ . "micro") - (#\¶ . "para") - (#\· . "middot") - (#\¸ . "cedil") - (#\¹ . "sup1") - (#\º . "ordm") - (#\» . "raquo") - (#\¼ . "frac14") - (#\½ . "frac12") - (#\¾ . "frac34") - (#\¿ . "iquest") - (#\À . "Agrave") - (#\Á . "Aacute") - (#\ . "Acirc") - (#\à . "Atilde") - (#\Ä . "Auml") - (#\Å . "Aring") - (#\Æ . "AElig") - (#\Ç . "Ccedil") - (#\È . "Egrave") - (#\É . "Eacute") - (#\Ê . "Ecirc") - (#\Ë . "Euml") - (#\Ì . "Igrave") - (#\Í . "Iacute") - (#\Î . "Icirc") - (#\Ï . "Iuml") - (#\Ð . "ETH") - (#\Ñ . "Ntilde") - (#\Ò . "Ograve") - (#\Ó . "Oacute") - (#\Ô . "Ocirc") - (#\Õ . "Otilde") - (#\Ö . "Ouml") - (#\× . "times") - (#\Ø . "Oslash") - (#\Ù . "Ugrave") - (#\Ú . "Uacute") - (#\Û . "Ucirc") - (#\Ü . "Uuml") - (#\Ý . "Yacute") - (#\Þ . "THORN") - (#\ß . "szlig") - (#\à . "agrave") - (#\á . "aacute") - (#\â . "acirc") - (#\ã . "atilde") - (#\ä . "auml") - (#\å . "aring") - (#\æ . "aelig") - (#\ç . "ccedil") - (#\è . "egrave") - (#\é . "eacute") - (#\ê . "ecirc") - (#\ë . "euml") - (#\ì . "igrave") - (#\í . "iacute") - (#\î . "icirc") - (#\ï . "iuml") - (#\ð . "eth") - (#\ñ . "ntilde") - (#\ò . "ograve") - (#\ó . "oacute") - (#\ô . "ocirc") - (#\õ . "otilde") - (#\ö . "ouml") - (#\÷ . "divide") - (#\ø . "oslash") - (#\ù . "ugrave") - (#\ú . "uacute") - (#\û . "ucirc") - (#\ü . "uuml") - (#\ý . "yacute") - (#\þ . "thorn") - (#\ÿ . "yuml") - (#\Œ . "OElig") - (#\œ . "oelig") - (#\Š . "Scaron") - (#\š . "scaron") - (#\Ÿ . "Yuml") - (#\ƒ . "fnof") - (#\ˆ . "circ") - (#\˜ . "tilde") - (#\Α . "Alpha") - (#\Β . "Beta") - (#\Γ . "Gamma") - (#\Δ . "Delta") - (#\Ε . "Epsilon") - (#\Ζ . "Zeta") - (#\Η . "Eta") - (#\Θ . "Theta") - (#\Ι . "Iota") - (#\Κ . "Kappa") - (#\Λ . "Lambda") - (#\Μ . "Mu") - (#\Ν . "Nu") - (#\Ξ . "Xi") - (#\Ο . "Omicron") - (#\Π . "Pi") - (#\Ρ . "Rho") - (#\Σ . "Sigma") - (#\Τ . "Tau") - (#\Υ . "Upsilon") - (#\Φ . "Phi") - (#\Χ . "Chi") - (#\Ψ . "Psi") - (#\Ω . "Omega") - (#\α . "alpha") - (#\β . "beta") - (#\γ . "gamma") - (#\δ . "delta") - (#\ε . "epsilon") - (#\ζ . "zeta") - (#\η . "eta") - (#\θ . "theta") - (#\ι . "iota") - (#\κ . "kappa") - (#\λ . "lambda") - (#\μ . "mu") - (#\ν . "nu") - (#\ξ . "xi") - (#\ο . "omicron") - (#\π . "pi") - (#\ρ . "rho") - (#\ς . "sigmaf") - (#\σ . "sigma") - (#\τ . "tau") - (#\υ . "upsilon") - (#\φ . "phi") - (#\χ . "chi") - (#\ψ . "psi") - (#\ω . "omega") - (#\ϑ . "thetasym") - (#\ϒ . "upsih") - (#\ϖ . "piv") - (#\ . "ensp") - (#\ . "emsp") - (#\ . "thinsp") - (#\– . "ndash") - (#\— . "mdash") - (#\‘ . "lsquo") - (#\’ . "rsquo") - (#\‚ . "sbquo") - (#\“ . "ldquo") - (#\” . "rdquo") - (#\„ . "bdquo") - (#\† . "dagger") - (#\‡ . "Dagger") - (#\• . "bull") - (#\… . "hellip") - (#\‰ . "permil") - (#\′ . "prime") - (#\″ . "Prime") - (#\‹ . "lsaquo") - (#\› . "rsaquo") - (#\‾ . "oline") - (#\⁄ . "frasl") - (#\€ . "euro") - (#\ℑ . "image") - (#\℘ . "weierp") - (#\ℜ . "real") - (#\™ . "trade") - (#\ℵ . "alefsym") - (#\← . "larr") - (#\↑ . "uarr") - (#\→ . "rarr") - (#\↓ . "darr") - (#\↔ . "harr") - (#\↵ . "crarr") - (#\⇐ . "lArr") - (#\⇑ . "uArr") - (#\⇒ . "rArr") - (#\⇓ . "dArr") - (#\⇔ . "hArr") - (#\∀ . "forall") - (#\∂ . "part") - (#\∃ . "exist") - (#\∅ . "empty") - (#\∇ . "nabla") - (#\∈ . "isin") - (#\∉ . "notin") - (#\∋ . "ni") - (#\∏ . "prod") - (#\∑ . "sum") - (#\− . "minus") - (#\∗ . "lowast") - (#\√ . "radic") - (#\∝ . "prop") - (#\∞ . "infin") - (#\∠ . "ang") - (#\∧ . "and") - (#\∨ . "or") - (#\∩ . "cap") - (#\∪ . "cup") - (#\∫ . "int") - (#\∴ . "there4") - (#\∼ . "sim") - (#\≅ . "cong") - (#\≈ . "asymp") - (#\≠ . "ne") - (#\≡ . "equiv") - (#\≤ . "le") - (#\≥ . "ge") - (#\⊂ . "sub") - (#\⊃ . "sup") - (#\⊄ . "nsub") - (#\⊆ . "sube") - (#\⊇ . "supe") - (#\⊕ . "oplus") - (#\⊗ . "otimes") - (#\⊥ . "perp") - (#\⋅ . "sdot") - (#\⋮ . "vellip") - (#\⌈ . "lceil") - (#\⌉ . "rceil") - (#\⌊ . "lfloor") - (#\⌋ . "rfloor") - (#\〈 . "lang") - (#\〉 . "rang") - (#\◊ . "loz") - (#\♠ . "spades") - (#\♣ . "clubs") - (#\♥ . "hearts") - (#\♦ . "diams")))) + (alist->hash-table '((#\" . "quot") (#\& . "amp") + (#\' . "apos") + (#\< . "lt") + (#\> . "gt") + (#\¡ . "iexcl") + (#\¢ . "cent") + (#\£ . "pound") + (#\¤ . "curren") + (#\¥ . "yen") + (#\¦ . "brvbar") + (#\§ . "sect") + (#\¨ . "uml") + (#\© . "copy") + (#\ª . "ordf") + (#\« . "laquo") + (#\¬ . "not") + (#\® . "reg") + (#\¯ . "macr") + (#\° . "deg") + (#\± . "plusmn") + (#\² . "sup2") + (#\³ . "sup3") + (#\´ . "acute") + (#\µ . "micro") + (#\¶ . "para") + (#\· . "middot") + (#\¸ . "cedil") + (#\¹ . "sup1") + (#\º . "ordm") + (#\» . "raquo") + (#\¼ . "frac14") + (#\½ . "frac12") + (#\¾ . "frac34") + (#\¿ . "iquest") + (#\À . "Agrave") + (#\Á . "Aacute") + (#\ . "Acirc") + (#\à . "Atilde") + (#\Ä . "Auml") + (#\Å . "Aring") + (#\Æ . "AElig") + (#\Ç . "Ccedil") + (#\È . "Egrave") + (#\É . "Eacute") + (#\Ê . "Ecirc") + (#\Ë . "Euml") + (#\Ì . "Igrave") + (#\Í . "Iacute") + (#\Î . "Icirc") + (#\Ï . "Iuml") + (#\Ð . "ETH") + (#\Ñ . "Ntilde") + (#\Ò . "Ograve") + (#\Ó . "Oacute") + (#\Ô . "Ocirc") + (#\Õ . "Otilde") + (#\Ö . "Ouml") + (#\× . "times") + (#\Ø . "Oslash") + (#\Ù . "Ugrave") + (#\Ú . "Uacute") + (#\Û . "Ucirc") + (#\Ü . "Uuml") + (#\Ý . "Yacute") + (#\Þ . "THORN") + (#\ß . "szlig") + (#\à . "agrave") + (#\á . "aacute") + (#\â . "acirc") + (#\ã . "atilde") + (#\ä . "auml") + (#\å . "aring") + (#\æ . "aelig") + (#\ç . "ccedil") + (#\è . "egrave") + (#\é . "eacute") + (#\ê . "ecirc") + (#\ë . "euml") + (#\ì . "igrave") + (#\í . "iacute") + (#\î . "icirc") + (#\ï . "iuml") + (#\ð . "eth") + (#\ñ . "ntilde") + (#\ò . "ograve") + (#\ó . "oacute") + (#\ô . "ocirc") + (#\õ . "otilde") + (#\ö . "ouml") + (#\÷ . "divide") + (#\ø . "oslash") + (#\ù . "ugrave") + (#\ú . "uacute") + (#\û . "ucirc") + (#\ü . "uuml") + (#\ý . "yacute") + (#\þ . "thorn") + (#\ÿ . "yuml") + (#\Œ . "OElig") + (#\œ . "oelig") + (#\Š . "Scaron") + (#\š . "scaron") + (#\Ÿ . "Yuml") + (#\ƒ . "fnof") + (#\ˆ . "circ") + (#\˜ . "tilde") + (#\Α . "Alpha") + (#\Β . "Beta") + (#\Γ . "Gamma") + (#\Δ . "Delta") + (#\Ε . "Epsilon") + (#\Ζ . "Zeta") + (#\Η . "Eta") + (#\Θ . "Theta") + (#\Ι . "Iota") + (#\Κ . "Kappa") + (#\Λ . "Lambda") + (#\Μ . "Mu") + (#\Ν . "Nu") + (#\Ξ . "Xi") + (#\Ο . "Omicron") + (#\Π . "Pi") + (#\Ρ . "Rho") + (#\Σ . "Sigma") + (#\Τ . "Tau") + (#\Υ . "Upsilon") + (#\Φ . "Phi") + (#\Χ . "Chi") + (#\Ψ . "Psi") + (#\Ω . "Omega") + (#\α . "alpha") + (#\β . "beta") + (#\γ . "gamma") + (#\δ . "delta") + (#\ε . "epsilon") + (#\ζ . "zeta") + (#\η . "eta") + (#\θ . "theta") + (#\ι . "iota") + (#\κ . "kappa") + (#\λ . "lambda") + (#\μ . "mu") + (#\ν . "nu") + (#\ξ . "xi") + (#\ο . "omicron") + (#\π . "pi") + (#\ρ . "rho") + (#\ς . "sigmaf") + (#\σ . "sigma") + (#\τ . "tau") + (#\υ . "upsilon") + (#\φ . "phi") + (#\χ . "chi") + (#\ψ . "psi") + (#\ω . "omega") + (#\ϑ . "thetasym") + (#\ϒ . "upsih") + (#\ϖ . "piv") + (#\20002 . "ensp") + (#\20003 . "emsp") + (#\20011 . "thinsp") + (#\– . "ndash") + (#\— . "mdash") + (#\‘ . "lsquo") + (#\’ . "rsquo") + (#\‚ . "sbquo") + (#\“ . "ldquo") + (#\” . "rdquo") + (#\„ . "bdquo") + (#\† . "dagger") + (#\‡ . "Dagger") + (#\• . "bull") + (#\… . "hellip") + (#\‰ . "permil") + (#\′ . "prime") + (#\″ . "Prime") + (#\‹ . "lsaquo") + (#\› . "rsaquo") + (#\‾ . "oline") + (#\⁄ . "frasl") + (#\€ . "euro") + (#\ℑ . "image") + (#\℘ . "weierp") + (#\ℜ . "real") + (#\™ . "trade") + (#\ℵ . "alefsym") + (#\← . "larr") + (#\↑ . "uarr") + (#\→ . "rarr") + (#\↓ . "darr") + (#\↔ . "harr") + (#\↵ . "crarr") + (#\⇐ . "lArr") + (#\⇑ . "uArr") + (#\⇒ . "rArr") + (#\⇓ . "dArr") + (#\⇔ . "hArr") + (#\∀ . "forall") + (#\∂ . "part") + (#\∃ . "exist") + (#\∅ . "empty") + (#\∇ . "nabla") + (#\∈ . "isin") + (#\∉ . "notin") + (#\∋ . "ni") + (#\∏ . "prod") + (#\∑ . "sum") + (#\− . "minus") + (#\∗ . "lowast") + (#\√ . "radic") + (#\∝ . "prop") + (#\∞ . "infin") + (#\∠ . "ang") + (#\∧ . "and") + (#\∨ . "or") + (#\∩ . "cap") + (#\∪ . "cup") + (#\∫ . "int") + (#\∴ . "there4") + (#\∼ . "sim") + (#\≅ . "cong") + (#\≈ . "asymp") + (#\≠ . "ne") + (#\≡ . "equiv") + (#\≤ . "le") + (#\≥ . "ge") + (#\⊂ . "sub") + (#\⊃ . "sup") + (#\⊄ . "nsub") + (#\⊆ . "sube") + (#\⊇ . "supe") + (#\⊕ . "oplus") + (#\⊗ . "otimes") + (#\⊥ . "perp") + (#\⋅ . "sdot") + (#\⋮ . "vellip") + (#\⌈ . "lceil") + (#\⌉ . "rceil") + (#\⌊ . "lfloor") + (#\⌋ . "rfloor") + (#\〈 . "lang") + (#\〉 . "rang") + (#\◊ . "loz") + (#\♠ . "spades") + (#\♣ . "clubs") + (#\♥ . "hearts") + (#\♦ . "diams")))) (define (string->escaped-html s port) "Write the HTML escaped form of S to PORT." @@ -313,9 +310,7 @@ (define (object->escaped-html obj port) "Write the HTML escaped form of OBJ to PORT." - (string->escaped-html - (call-with-output-string (cut display obj <>)) - port)) + (string->escaped-html (call-with-output-string (cut display obj <>)) port)) (define (attribute-value->html value port) "Write the HTML escaped form of VALUE to PORT." @@ -334,11 +329,11 @@ list ATTRS and the child nodes in BODY." (format port "<~a" tag) (for-each (match-lambda - ((attr value) - (display #\space port) - (attribute->html attr value port))) - attrs) - (if (and (null? body) (self-closing-tag? tag)) + ((attr value) + (display #\space port) + (attribute->html attr value port))) attrs) + (if (and (null? body) + (self-closing-tag? tag)) (display " />" port) (begin (display #\> port) @@ -348,7 +343,8 @@ list ATTRS and the child nodes in BODY." (define (doctype->html doctype port) (format port "<!DOCTYPE ~a>" doctype)) -(define* (sxml->html tree #:optional (port (current-output-port))) +(define* (sxml->html tree + #:optional (port (current-output-port))) "Write the serialized HTML form of TREE to PORT." (match tree (() *unspecified*) @@ -357,10 +353,13 @@ list ATTRS and the child nodes in BODY." ;; Unescaped, raw HTML output (('raw html) (display html port)) - (((? symbol? tag) ('@ attrs ...) body ...) + (((? symbol? tag) + ('@ attrs ...) body ...) (element->html tag attrs body port)) - (((? symbol? tag) body ...) - (element->html tag '() body port)) + (((? symbol? tag) + body ...) + (element->html tag + '() body port)) ((nodes ...) (for-each (cut sxml->html <> port) nodes)) ((? string? text) diff --git a/web/view/markdown.scm b/web/view/markdown.scm index 2af2b26..6aa2935 100644 --- a/web/view/markdown.scm +++ b/web/view/markdown.scm @@ -6,50 +6,126 @@ #:use-module (ice-9 receive) #:use-module (ice-9 string-fun) #:use-module (ice-9 textual-ports) + #:use-module (ice-9 exceptions) + #:use-module (ice-9 popen) + #:use-module (ice-9 rdelim) #:use-module (sxml simple) #:use-module (web client) #:use-module (web uri) #:use-module (web request) #:use-module (web sxml) #:use-module (commonmark) - - #:export (markdown-file->sxml - markdown-github->sxml - fetch-raw-file) - ) - + #:export (markdown-file->sxml markdown-github->sxml fetch-file + fetch-raw-file commit-file git-invoke)) (define (markdown-file->sxml fn) "Parse a local file" - (commonmark->sxml - (call-with-input-file fn - get-string-all))) - -;; --- fetch github style URLs + (commonmark->sxml (call-with-input-file fn + get-string-all))) (define (fetch-raw-file url) (receive (response-status response-body) - (http-request url) - response-body)) + (http-request url) response-body)) -;; https://github.com/genenetwork/gn-docs/master/general/brand/aging/home.md -;; https://raw.githubusercontent.com/genenetwork/gn-docs/master/general/brand/aging/home.md -;; https://github.com/genenetwork/gn-docs/edit/master/general/brand/aging/home.md +(define* (form-github-raw-url project repo page #:optional (branch "master")) + (string-append "https://raw.githubusercontent.com/" + project + "/" + repo + "/" + branch + "/" + (string-join page "/"))) -(define (form-github-raw-url project repo page) - (string-append "https://raw.githubusercontent.com/" project "/" repo "/master/" (string-join page "/"))) - -(define (form-github-edit-url project repo page) - (string-append "https://github.com/" project "/" repo "/edit/master/" (string-join page "/"))) +(define* (form-github-edit-url project repo page #:optional (branch "master")) + (string-append "https://github.com/" + project + "/" + repo + "/edit/" + branch + "/" + (string-join page "/"))) (define (markdown-github->sxml path) "Parse a github markdown file that is formed like genenetwork/gn-docs/general/brand/aging/home.md" - (match-let (((project repo page ...) (string-split path #\/))) - `(div (@ (class "markdown")) - ,(commonmark->sxml - (fetch-raw-file (pk (form-github-raw-url project repo (pk page))))) - (p - (div (@ (class "button-align-right")) - (a (@ (href ,(form-github-edit-url project repo page)) (role "button")) "edit"))) - (br) - (br)))) + (match-let (((project repo page ...) + (string-split path #\/))) + `(div (@ (class "markdown")) + ,(commonmark->sxml (fetch-raw-file (pk (form-github-raw-url + project repo + (pk page))))) + (p (div (@ (class "button-align-right")) + (a (@ (href ,(form-github-edit-url project repo + page)) + (role "button")) "edit"))) + (br) + (br)))) + +(define (fetch-file repo query-path) + (let* ((abs-path (format #f "~a/~a" repo query-path))) + (if (file-exists? abs-path) + (let* ((full-path (canonicalize-path abs-path)) + (content (call-with-input-file full-path + get-string-all)) + (commit-sha (get-latest-commit-sha1 repo))) + `(("file_path" unquote query-path) + ("content" unquote content) + ("hash" unquote commit-sha))) + (throw 'file-error + (format #f "~a does not exists" abs-path))))) + +(define (git-invoke repo-path . args) + (apply system* "git" "-C" repo-path args)) + +(define (git-repository? repo-path) + (let ((data (git-invoke repo-path "rev-parse"))) + (zero? data))) + +(define (get-latest-commit-sha1 repo-path) + (let* ((output-port (open-input-pipe (string-append "git -C " repo-path + " log -n 1 --pretty=format:%H HEAD"))) + (commit-sha (read-line output-port))) + (close-port output-port) commit-sha)) + +(define* (commit-file repo + file-path + content + commit-message + username + email + #:optional (prev-commit "")) + (unless (string=? prev-commit + (get-latest-commit-sha1 repo)) + (throw 'system-error + (format #f + "Commits do no match.Please pull in latest changes for current * ~a * and prev * ~a * " + (get-latest-commit-sha1 repo) prev-commit))) + (if (git-repository? repo) + (match (file-exists? (format #f "~a/~a" repo file-path)) + (#t (with-output-to-file (format #f "~a/~a" repo file-path) + (lambda () + (display content))) + (let* ((git-add-file (git-invoke repo "add" file-path)) + (git-commit-file (git-invoke repo + "commit" + "-m" + commit-message + "-m" + " * Commit made via the GN Markdown Editor" + "--author" + (format #f "~a <~a>" username email))) + (git-commit-sha (get-latest-commit-sha1 repo))) + (if (zero? git-commit-file) + `(("status" . "201") + ("message" . "committed file successfully") + ("content" . ,content) + ("commit_sha" . ,git-commit-sha) + ("commit_message" . ,commit-message)) + `(("status" . "200") + ("message" . "Nothing to commit, working tree clean") + ("commit_sha" . ,git-commit-sha))))) + (#f (throw 'system-error + (format #f "~a File does not exist error" file-path)))) + (throw 'system-error + (format #f "~a is no a git repo" repo)))) diff --git a/web/webserver.scm b/web/webserver.scm index 05e842c..c0fb9a1 100755..100644 --- a/web/webserver.scm +++ b/web/webserver.scm @@ -1,70 +1,61 @@ -#!/usr/bin/env guile \ --e main -s -!# -;; Minimal web server can be started from command line. Current example routes: -;; -;; localhost:8080/ -;; - -(use-modules - (json) - (ice-9 match) - (ice-9 format) - (ice-9 iconv) - (ice-9 receive) - (ice-9 string-fun) - ;; (ice-9 debugger) - ;; (ice-9 breakpoints) - ;; (ice-9 source) - (srfi srfi-1) - (srfi srfi-11) ; let-values - (srfi srfi-19) ; time - (srfi srfi-26) - (rnrs io ports) ; bytevector-all - (web http) - (web client) - (web request) - (web response) - (web uri) - (fibers web server) - (gn cache memoize) - (web gn-uri) - (gn db sparql) - (gn data species) - (gn data group) - (web sxml) - (web view view) - (web view doc)) - - -(define info `( - ("name" . "GeneNetwork REST API") - ("version" . ,get-version) - ("comment" . "This is the official REST API for the GeneNetwork service hosted at https://genenetwork.org/") - ("license" . (("source code (unless otherwise specified)" . "Affero GNU Public License 3.0 (AGPL3)") - ("data (unless otherwise specified)" . "Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0)"))) - ("note" . "This is work in progress (WIP). Note that the final base URL will change! The temporary prefix is:") - ("prefix" . ,(prefix)) - ("links". (("species" . ,(mk-meta "species")))))) - -(define info-meta `( - ("doc" . ,(mk-html "info")) - ("API" . - ((,(mk-url "species")."Get a list of all species") - (,(mk-url "mouse")."Get information on mouse") - (,(mk-url "datasets")."Get a list of datasets"))))) +(use-modules (json) + (ice-9 match) + (ice-9 format) + (ice-9 iconv) + (ice-9 receive) + (ice-9 string-fun) + (ice-9 exceptions) + (srfi srfi-1) + (srfi srfi-11) + (srfi srfi-19) + (srfi srfi-26) + (rnrs io ports) + (rnrs bytevectors) + (web http) + (web client) + (web request) + (web response) + (web uri) + (fibers web server) + (gn cache memoize) + (web gn-uri) + (gn db sparql) + (gn data species) + (gn data group) + (web sxml) + (web view view) + (web view doc) + (web view markdown)) +(define +current-repo-path+ + (getenv "CURRENT_REPO_PATH")) + +(define +cgit-repo-path+ + (getenv "CGIT_REPO_PATH")) + +(define +info+ + `(("name" . "GeneNetwork REST API") ("version" ,get-version) + ("comment" . "This is the official REST API for the GeneNetwork service hosted at https://genenetwork.org/") + ("license" + ("source code (unless otherwise specified)" . "Affero GNU Public License 3.0 (AGPL3)") + ("data (unless otherwise specified)" . "Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0)")) + ("note" . "This is work in progress (WIP). Note that the final base URL will change! The temporary prefix is:") + ("prefix" ,(prefix)) + ("links" ("species" ,(mk-meta "species"))))) + +(define +info-meta+ + `(("doc" ,(mk-html "info")) + ("API" ((,(mk-url "species")) . "Get a list of all species") + ((,(mk-url "mouse")) . "Get information on mouse") + ((,(mk-url "datasets")) . "Get a list of datasets")))) (define (get-id-data id) "Get data based on identifier id. If it is a taxon return the taxon data, otherwise search for set/group data" - (let ([taxoninfo (get-expanded-taxon-data id)]) - (if taxoninfo - taxoninfo + (let ((taxoninfo (get-expanded-taxon-data id))) + (if taxoninfo taxoninfo (cdr (get-group-data id))))) -;; ---- REST API web server handler - (define (not-found2 request) (values (build-response #:code 404) (string-append "Resource X not found: " @@ -72,85 +63,189 @@ otherwise search for set/group data" (define (not-found uri) (list (build-response #:code 404) - (string-append "Resource not found: " (uri->string uri)))) - + (string-append "Resource not found: " + (uri->string uri)))) (define file-mime-types - '(("css" . (text/css)) - ("js" . (text/javascript)) - ("svg" . (image/svg+xml)) - ("png" . (image/png)) - ("gif" . (image/gif)) - ("jpg" . (image/jpg)) - ("woff" . (application/font-woff)) - ("ttf" . (application/octet-stream)) - ("map" . (text/json)) - ("html" . (text/html)))) + '(("css" text/css) + ("js" text/javascript) + ("svg" image/svg+xml) + ("png" image/png) + ("gif" image/gif) + ("jpg" image/jpg) + ("woff" application/font-woff) + ("ttf" application/octet-stream) + ("map" text/json) + ("html" text/html))) (define (file-extension file-name) (last (string-split file-name #\.))) -(define* (render-static-image file-name #:key (extra-headers '())) +(define* (render-static-image file-name + #:key (extra-headers '())) (let* ((stat (stat file-name #f)) (modified (and stat - (make-time time-utc 0 (stat:mtime stat))))) - (list `((content-type . ,(assoc-ref file-mime-types - (file-extension file-name))) - (last-modified . ,(time-utc->date modified))) - (call-with-input-file file-name get-bytevector-all)))) + (make-time time-utc 0 + (stat:mtime stat))))) + (list `((content-type ,(assoc-ref file-mime-types + (file-extension file-name))) + (last-modified ,(time-utc->date modified))) + (call-with-input-file file-name + get-bytevector-all)))) -(define* (render-static-file path #:optional rec #:key (extra-headers '())) +(define* (render-static-file path + #:optional rec + #:key (extra-headers '())) (let* ((stat (stat path #f)) (modified (and stat - (make-time time-utc 0 (stat:mtime stat))))) - (list `((content-type . ,(assoc-ref file-mime-types - (file-extension path))) - (last-modified . ,(time-utc->date modified))) - (call-with-input-file path get-bytevector-all)))) + (make-time time-utc 0 + (stat:mtime stat))))) + (list `((content-type ,(assoc-ref file-mime-types + (file-extension path))) + (last-modified ,(time-utc->date modified))) + (call-with-input-file path + get-bytevector-all)))) -(define* (render-doc path page #:optional rec #:key (extra-headers '())) +(define* (render-doc path + page + #:optional rec + #:key (extra-headers '())) (list (append extra-headers - '((content-type . (text/html)))) + '((content-type text/html))) (lambda (port) (sxml->html (view-doc path page rec) port)))) -(define* (render-brand path #:optional rec #:key (extra-headers '())) +(define* (render-brand path + #:optional rec + #:key (extra-headers '())) (list (append extra-headers - '((content-type . (text/html)))) + '((content-type text/html))) (lambda (port) (sxml->html (view-brand path) port)))) (define (render-json json) - (list '((content-type . (application/json))) + (list '((content-type application/json)) (lambda (port) (scm->json json port)))) (define (render-json-string2 json) - (list '((content-type . (text/plain))) - (lambda (port) - (format port "~a" "foo")))) + (list '((content-type text/plain)) + (lambda (port) + (format port "~a" "foo")))) + +(define (build-json-response status-code json) + (list (build-response #:code status-code + #:headers `((content-type application/json))) + (lambda (port) + (scm->json json port)))) + +(define (decode-request-json body) + (if (not body) + '() + (json-string->scm (utf8->string body)))) + +(define (decode-query-component component) + (let* ((index (string-index component #\=)) + (key (if index + (substring component 0 index) component)) + (value (if index + (substring component + (1+ index)) ""))) + (cons (string->symbol (uri-decode key)) + (uri-decode value)))) + +(define (edit-file-handler repo request) + (catch 'file-error + (lambda () + (let* ((query (uri-query (request-uri request))) + (params (if (not query) + '() + (map decode-query-component + (string-split query #\&)))) + (query-path (assoc-ref params + 'file_path))) + (if query-path + (build-json-response 200 + (fetch-file repo query-path)) + (throw 'file-error + "Please provide a valid file path in the query")))) + (lambda (key . args) + (let ((msg (car args))) + (build-json-response 400 + `(("error" ,key) + ("msg" ,msg))))))) + +(define (invalid-data? data target) + (if (string? (assoc-ref data target)) + (if (string-null? (assoc-ref data target)) + (throw 'system-error + (format #f "Value for Key *** ~a *** Cannot be Empty" target)) + (assoc-ref data target)) + (throw 'system-error + (format #f "The Key *** ~a *** is missing in your Json Data" + target)))) + +(define (commit-file-handler repo request body) + (catch 'system-error + (lambda () + (let* ((post-data (decode-request-json body)) + (_ (for-each (lambda (target) + (invalid-data? post-data target)) + '("filename" "content" "username" "email" + "prev_commit"))) + (file-name (assoc-ref post-data "filename")) + (content (assoc-ref post-data "content")) + (username (assoc-ref post-data "username")) + (email (assoc-ref post-data "email")) + (commit-message (assoc-ref post-data "commit_message")) + (prev-commit (assoc-ref post-data "prev_commit"))) + (build-json-response 200 + ((lambda () + (let ((message + (commit-file +current-repo-path+ + file-name + content + commit-message + username + email + prev-commit))) + (git-invoke +current-repo-path+ "push" +cgit-repo-path+) + message)))))) + (lambda (key . args) + (let ((msg (car args))) + (build-json-response 400 + `(("error" ,key) + ("msg" ,msg))))))) (define (controller request body) (match-lambda (('GET) - (render-json info)) + (render-json +info+)) (('GET "version") (render-json get-version)) - (('GET "css" fn ) + (('GET "css" fn) (render-static-file (string-append "css/" fn))) - (('GET "map" fn ) + (('GET "map" fn) (render-static-file (string-append "css/" fn))) (('GET "static" "images" fn) (render-static-image (string-append "static/images/" fn))) (('GET "home" path) (render-brand path)) (('GET "doc" "species.html") - (render-doc "doc" "species.html" (get-species-meta))) + (render-doc "doc" "species.html" + (get-species-meta))) (('GET "doc" taxon) (match (string->list taxon) - [(name ... #\. #\h #\t #\m #\l) - (render-doc "doc" taxon (get-expanded-taxon-meta (list->string name)))])) - (('GET "doc" path ... page) ; serve documents from /doc/ + ((name ... + #\. + #\h + #\t + #\m + #\l) + (render-doc "doc" taxon + (get-expanded-taxon-meta (list->string name)))))) + (('GET "doc" path ... page) + ;; serve documents from /doc/ (render-doc path page)) (('GET "species.json") (render-json (get-species-data))) @@ -158,15 +253,34 @@ otherwise search for set/group data" (render-json (get-species-meta))) (('GET "species") (render-json (get-species-meta))) + (('GET "edit") + (edit-file-handler +current-repo-path+ request)) + (('POST "commit") + (commit-file-handler +current-repo-path+ request body)) (('GET id) - (let ([names (get-species-shortnames (get-expanded-species))]) + (let ((names (get-species-shortnames (get-expanded-species)))) (match (string->list id) - [(name ... #\. #\m #\e #\t #\a #\. #\j #\s #\o #\n) (render-json (get-expanded-taxon-meta (list->string name)))] - [(name ... #\. #\j #\s #\o #\n) (render-json - (get-id-data (list->string name)))] - [rest (render-json "NOP")]))) - (_ (not-found (request-uri request))) - )) + ((name ... + #\. + #\m + #\e + #\t + #\a + #\. + #\j + #\s + #\o + #\n) + (render-json (get-expanded-taxon-meta (list->string name)))) + ((name ... + #\. + #\j + #\s + #\o + #\n) + (render-json (get-id-data (list->string name)))) + (rest (render-json "NOP"))))) + (_ (not-found (request-uri request))))) (define (request-path-components request) (split-and-decode-uri-path (uri-path (request-uri request)))) @@ -182,8 +296,7 @@ otherwise search for set/group data" (define (start-web-server address port) (format (current-error-port) - "GN REST API web server listening on http://~a:~a/~%" - address port) + "GN REST API web server listening on http://~a:~a/~%" address port) ;; Wrap handler in another function to support live hacking via the ;; REPL. If handler is passed as is and is then redefined via the ;; REPL, the web server will still be using the old handler. The @@ -199,6 +312,4 @@ otherwise search for set/group data" (newline) (let ((listen (inexact->exact (string->number (car (cdr args)))))) (display `("listening on" ,listen)) - ;; (write listen) - ;; (run-server hello-world-handler 'http `(#:port ,listen)))) - (start-web-server "127.0.0.1" listen))) + (start-web-server "127.0.0.1" listen))) |