Endpoint working and validating against amb schema

This commit is contained in:
@s.roertgen 2024-11-20 15:22:40 +01:00
parent 0b6513bae2
commit e5ff9265b4
5 changed files with 260 additions and 13 deletions

View file

@ -0,0 +1,18 @@
; Copyright 2024 Nubank NA
; The use and distribution terms for this software are covered by the
; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0)
; which can be found in the file epl-v10.html at the root of this distribution.
;
; By using this software in any fashion, you are agreeing to be bound by
; the terms of this license.
;
; You must not remove this notice, or any other, from this software.
{:hooks
{:analyze-call
{io.pedestal.log/trace hooks.io.pedestal.log/log-expr
io.pedestal.log/debug hooks.io.pedestal.log/log-expr
io.pedestal.log/info hooks.io.pedestal.log/log-expr
io.pedestal.log/warn hooks.io.pedestal.log/log-expr
io.pedestal.log/error hooks.io.pedestal.log/log-expr}}}

View file

@ -0,0 +1,26 @@
; Copyright 2024 Nubank NA
; The use and distribution terms for this software are covered by the
; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0)
; which can be found in the file epl-v10.html at the root of this distribution.
;
; By using this software in any fashion, you are agreeing to be bound by
; the terms of this license.
;
; You must not remove this notice, or any other, from this software.
(ns hooks.io.pedestal.log
(:require [clj-kondo.hooks-api :as api]))
(defn log-expr
"Expands (log-expr :a :A :b :B ... )
to (hash-map :a :A :b :B ... ) per clj-kondo examples."
[{:keys [:node]}]
(let [[k v & _kvs] (rest (:children node))]
(when-not (and k v)
(throw (ex-info "No kv pair provided" {})))
(let [new-node (api/list-node
(list*
(api/token-node 'hash-map)
(rest (:children node))))]
{:node (vary-meta new-node assoc :clj-kondo/ignore [:unused-value])})))

View file

@ -1,8 +1,10 @@
{:deps {org.clojure/clojure {:mvn/version "1.12.0"}
{:paths ["src" "resources"]
:deps {org.clojure/clojure {:mvn/version "1.12.0"}
com.stuartsierra/component {:mvn/version "1.1.0"}
io.pedestal/pedestal.service {:mvn/version "0.7.2"}
cheshire/cheshire {:mvn/version "5.13.0"}
io.pedestal/pedestal.jetty {:mvn/version "0.7.1"}
org.slf4j/slf4j-simple {:mvn/version "2.0.10"}
luposlip/json-schema {:mvn/version "0.4.6"}
net.clojars.laoc/nostr {:local/root "/home/laoc/coding/nostr-clj"}}
:aliases

121
resources/schema.json Normal file
View file

@ -0,0 +1,121 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://w3id.org/kim/amb/20231019/schemas/schema.json",
"title": "OER",
"description": "This is a generic JSON schema for describing an Open Educational Resource with schema.org",
"type": "object",
"default": {
"@context": [
"https://w3id.org/kim/amb/context.jsonld",
{
"@language": "de"
}
],
"type": ["LearningResource"]
},
"properties": {
"@context": {
"$ref": "https://w3id.org/kim/amb/20231019/schemas/context-schema.json"
},
"id": {
"$ref": "https://w3id.org/kim/amb/20231019/schemas/id.json"
},
"type": {
"$ref": "https://w3id.org/kim/amb/20231019/schemas/type.json"
},
"name": {
"$ref": "https://w3id.org/kim/amb/20231019/schemas/name.json"
},
"creator": {
"$ref": "https://w3id.org/kim/amb/20231019/schemas/creator.json"
},
"contributor": {
"$ref": "https://w3id.org/kim/amb/20231019/schemas/contributor.json"
},
"description": {
"$ref": "https://w3id.org/kim/amb/20231019/schemas/description.json"
},
"about": {
"$ref": "https://w3id.org/kim/amb/20231019/schemas/about.json"
},
"license": {
"$ref": "https://w3id.org/kim/amb/20231019/schemas/license.json"
},
"image": {
"$ref": "https://w3id.org/kim/amb/20231019/schemas/image.json"
},
"trailer": {
"$ref": "https://w3id.org/kim/amb/20231019/schemas/trailer.json"
},
"dateCreated": {
"$ref": "https://w3id.org/kim/amb/20231019/schemas/dateCreated.json"
},
"datePublished": {
"$ref": "https://w3id.org/kim/amb/20231019/schemas/datePublished.json"
},
"dateModified": {
"$ref": "https://w3id.org/kim/amb/20231019/schemas/dateModified.json"
},
"duration": {
"$ref": "https://w3id.org/kim/amb/20231019/schemas/duration.json"
},
"inLanguage": {
"$ref": "https://w3id.org/kim/amb/20231019/schemas/inLanguage.json"
},
"publisher": {
"$ref": "https://w3id.org/kim/amb/20231019/schemas/publisher.json"
},
"learningResourceType": {
"$ref": "https://w3id.org/kim/amb/20231019/schemas/learningResourceType.json"
},
"audience": {
"$ref": "https://w3id.org/kim/amb/20231019/schemas/audience.json"
},
"isBasedOn": {
"$ref": "https://w3id.org/kim/amb/20231019/schemas/isBasedOn.json"
},
"isPartOf": {
"$ref": "https://w3id.org/kim/amb/20231019/schemas/isPartOf.json"
},
"hasPart": {
"$ref": "https://w3id.org/kim/amb/20231019/schemas/hasPart.json"
},
"mainEntityOfPage": {
"$ref": "https://w3id.org/kim/amb/20231019/schemas/mainEntityOfPage.json"
},
"keywords": {
"$ref": "https://w3id.org/kim/amb/20231019/schemas/keywords.json"
},
"encoding": {
"$ref": "https://w3id.org/kim/amb/20231019/schemas/encoding.json"
},
"caption": {
"$ref": "https://w3id.org/kim/amb/20231019/schemas/caption.json"
},
"conditionsOfAccess": {
"$ref": "https://w3id.org/kim/amb/20231019/schemas/conditionsOfAccess.json"
},
"funder": {
"$ref": "https://w3id.org/kim/amb/20231019/schemas/funder.json"
},
"assesses": {
"$ref": "https://w3id.org/kim/amb/20231019/schemas/assesses.json"
},
"competencyRequired": {
"$ref": "https://w3id.org/kim/amb/20231019/schemas/competencyRequired.json"
},
"teaches": {
"$ref": "https://w3id.org/kim/amb/20231019/schemas/teaches.json"
},
"educationalLevel": {
"$ref": "https://w3id.org/kim/amb/20231019/schemas/educationalLevel.json"
},
"isAccessibleForFree": {
"$ref": "https://w3id.org/kim/amb/20231019/schemas/isAccessibleForFree.json"
},
"interactivityType": {
"$ref": "https://w3id.org/kim/amb/20231019/schemas/interactivityType.json"
}
},
"required": ["@context", "id", "name", "type"]
}

View file

@ -1,28 +1,88 @@
(ns app.components.webserver
(:require [io.pedestal.http :as http]
[com.stuartsierra.component :as component]
[io.pedestal.http.route :as route]))
[io.pedestal.http.route :as route]
[nostr.core :as nostr]
[nostr.edufeed :as edufeed]
[cheshire.core :as json]
[json-schema.core :as json-schema]
[clojure.core.async :refer [go chan >! <!]]))
(defn respond-hello [request]
{:status 200 :body "Hello, world!"})
(defn validate [resources]
(let [schema (json-schema/prepare-schema
(-> "resources/schema.json"
slurp
(cheshire.core/parse-string true)))]
(try
(let [validation-results
(map (fn [resource]
(try
(when (json-schema/validate schema resource)
{:valid true :resource resource})
(catch Exception e
{:valid false :error e :resource resource})))
resources)]
(do
;; Log invalid resources
(doseq [{:keys [error resource]} (filter #(not (:valid %)) validation-results)]
(println "Invalid resource:" resource "Error:" error))
;; Return only valid resources
(->> validation-results
(filter :valid)
(map :resource))))
(catch Exception e
(println "Unexpected error during validation" e)
[]))))
(defn resources-by-user [request]
{:status 200 :body "hello user"})
(defn ok [body]
{:status 200 :body body})
;; Async pedestal
(defn get-resources [request]
(let [result-chan (chan)]
(go
(let [npub (get-in request [:query-params :npub])
pk (get-in request [:query-params :pk])
filter (if pk
{:kinds [30142] :authors [pk]}
{:kinds [30142]})
resources (nostr/fetch-events "ws://localhost:7778" filter)
transformed (map #(edufeed/convert-30142-to-nostr-amb % true true) resources)
validate (validate transformed)
resp (if validate
transformed
{:response "Some error occured during validation against AMB Scheme. Better give you nothing than wrong data"})]
(>! result-chan (json/generate-string resp))))
result-chan))
(def resources-by-user
{:name ::resources-by-user
:enter (fn [context]
(go
(let [result (<! (get-resources (:request context)))
;; TODO check against amb schema
]
(assoc context :response (ok result)))))})
(def routes
(route/expand-routes
#{["/greet" :get respond-hello :route-name :greet]
["/user" :get resources-by-user :route-name :user]}))
#{["/resources" :get resources-by-user :route-name :resources]}))
(def service-map
{::http/routes routes
::http/type :jetty
::http/port 8890})
(defn create-service []
(http/create-server
{::http/routes routes
::http/type :jetty
::http/port 8890}))
(http/create-server service-map))
(defn start []
(http/start (create-service)))
(comment
(http/stop (create-service)))
(defrecord WebServer []
component/Lifecycle
(start [this]
@ -37,3 +97,23 @@
(defn new-web-server []
(map->WebServer {}))
;; For interactive development
(defonce server (atom nil))
(defn start-dev []
(reset! server
(http/start (http/create-server
(assoc service-map
::http/join? false)))))
(defn stop-dev []
(http/stop @server))
(defn restart []
(stop-dev)
(start-dev))
(comment
(start-dev)
(restart))