Add file store
This commit is contained in:
parent
da2b13597a
commit
371379e981
64 changed files with 327 additions and 8343 deletions
|
@ -1 +0,0 @@
|
|||
*
|
|
@ -1,49 +0,0 @@
|
|||
{
|
||||
"$id": "https://schemas.ory.sh/presets/kratos/quickstart/email-password/identity.schema.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Person",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"traits": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
"format": "email",
|
||||
"title": "E-Mail",
|
||||
"minLength": 3,
|
||||
"ory.sh/kratos": {
|
||||
"credentials": {
|
||||
"password": {
|
||||
"identifier": true
|
||||
}
|
||||
},
|
||||
"verification": {
|
||||
"via": "email"
|
||||
},
|
||||
"recovery": {
|
||||
"via": "email"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"first": {
|
||||
"title": "First Name",
|
||||
"type": "string"
|
||||
},
|
||||
"last": {
|
||||
"title": "Last Name",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"email"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
version: v0.8.0-alpha.3
|
||||
|
||||
dsn: memory
|
||||
|
||||
serve:
|
||||
public:
|
||||
base_url: http://localhost:4433/
|
||||
cors:
|
||||
enabled: true
|
||||
allowed_origins:
|
||||
- http://localhost:3000
|
||||
allowed_methods:
|
||||
- POST
|
||||
- GET
|
||||
- PUT
|
||||
- PATCH
|
||||
- DELETE
|
||||
allowed_headers:
|
||||
- Authorization
|
||||
- Cookie
|
||||
- Content-Type
|
||||
exposed_headers:
|
||||
- Content-Type
|
||||
- Set-Cookie
|
||||
admin:
|
||||
base_url: http://kratos:4434/
|
||||
|
||||
selfservice:
|
||||
default_browser_return_url: http://localhost:3000/
|
||||
allowed_return_urls:
|
||||
- http://localhost:3000
|
||||
|
||||
methods:
|
||||
password:
|
||||
enabled: true
|
||||
|
||||
flows:
|
||||
error:
|
||||
ui_url: http://localhost:3000/auth/login
|
||||
|
||||
settings:
|
||||
ui_url: http://localhost:3000/auth/settings
|
||||
privileged_session_max_age: 15m
|
||||
|
||||
recovery:
|
||||
enabled: true
|
||||
ui_url: http://localhost:3000/auth/recovery
|
||||
|
||||
verification:
|
||||
enabled: true
|
||||
ui_url: http://localhost:3000/auth/verification
|
||||
after:
|
||||
default_browser_return_url: http://localhost:3000/
|
||||
|
||||
logout:
|
||||
after:
|
||||
default_browser_return_url: http://localhost:3000/auth/login
|
||||
|
||||
login:
|
||||
ui_url: http://localhost:3000/auth/login
|
||||
|
||||
registration:
|
||||
ui_url: http://localhost:3000/auth/registration
|
||||
after:
|
||||
password:
|
||||
hooks:
|
||||
-
|
||||
hook: session
|
||||
|
||||
log:
|
||||
level: info
|
||||
format: text
|
||||
|
||||
secrets:
|
||||
cookie:
|
||||
- PLEASE-CHANGE-ME-I-AM-VERY-INSECURE
|
||||
|
||||
hashers:
|
||||
algorithm: bcrypt
|
||||
bcrypt:
|
||||
cost: 8
|
||||
|
||||
identity:
|
||||
default_schema_id: preset://email
|
||||
schemas:
|
||||
- id: preset://email
|
||||
url: file:///etc/config/kratos/identity.schema.json
|
||||
|
||||
courier:
|
||||
smtp:
|
||||
connection_uri: smtps://test:test@mailslurper:1025/?skip_ssl_verify=true
|
|
@ -1,42 +0,0 @@
|
|||
version: '3.7'
|
||||
|
||||
services:
|
||||
kratos:
|
||||
volumes:
|
||||
- type: volume
|
||||
source: kratos-sqlite
|
||||
target: /var/lib/sqlite
|
||||
read_only: false
|
||||
- type: bind
|
||||
source: ./contrib/quickstart/kratos/cloud
|
||||
target: /etc/config/kratos
|
||||
kratos-migrate:
|
||||
volumes:
|
||||
- type: volume
|
||||
source: kratos-sqlite
|
||||
target: /var/lib/sqlite
|
||||
read_only: false
|
||||
- type: bind
|
||||
source: ./contrib/quickstart/kratos/cloud
|
||||
target: /etc/config/kratos
|
||||
|
||||
# kratos-selfservice-ui-node:
|
||||
# ports:
|
||||
# - "4438:4438"
|
||||
# environment:
|
||||
# - PORT=4438
|
||||
# - KRATOS_BROWSER_URL=http://localhost:4455/
|
||||
|
||||
# kratos-caddy:
|
||||
# image: caddy:2.4.5-alpine
|
||||
# ports:
|
||||
# - "4455:4455"
|
||||
# volumes:
|
||||
# - type: bind
|
||||
# source: ./contrib/quickstart/kratos/cloud/Caddyfile
|
||||
# target: /etc/caddy/Caddyfile
|
||||
# command: caddy run -watch -config /etc/caddy/Caddyfile
|
||||
# restart: on-failure
|
||||
# networks:
|
||||
# - intranet
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
{
|
||||
"$id": "https://schemas.ory.sh/presets/kratos/quickstart/email-password/identity.schema.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Person",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"traits": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
"format": "email",
|
||||
"title": "E-Mail",
|
||||
"minLength": 3,
|
||||
"ory.sh/kratos": {
|
||||
"credentials": {
|
||||
"password": {
|
||||
"identifier": true
|
||||
}
|
||||
},
|
||||
"verification": {
|
||||
"via": "email"
|
||||
},
|
||||
"recovery": {
|
||||
"via": "email"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"first": {
|
||||
"title": "First Name",
|
||||
"type": "string"
|
||||
},
|
||||
"last": {
|
||||
"title": "Last Name",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"email"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
version: v0.7.1-alpha.1
|
||||
|
||||
dsn: memory
|
||||
|
||||
serve:
|
||||
public:
|
||||
base_url: http://127.0.0.1:4433/
|
||||
cors:
|
||||
enabled: true
|
||||
admin:
|
||||
base_url: http://kratos:4434/
|
||||
|
||||
selfservice:
|
||||
default_browser_return_url: http://127.0.0.1:4455/
|
||||
allowed_return_urls:
|
||||
- http://127.0.0.1:4455
|
||||
|
||||
methods:
|
||||
password:
|
||||
enabled: true
|
||||
|
||||
flows:
|
||||
error:
|
||||
ui_url: http://127.0.0.1:4455/error
|
||||
|
||||
settings:
|
||||
ui_url: http://127.0.0.1:4455/settings
|
||||
privileged_session_max_age: 15m
|
||||
|
||||
recovery:
|
||||
enabled: true
|
||||
ui_url: http://127.0.0.1:4455/recovery
|
||||
|
||||
verification:
|
||||
enabled: true
|
||||
ui_url: http://127.0.0.1:4455/verification
|
||||
after:
|
||||
default_browser_return_url: http://127.0.0.1:4455/
|
||||
|
||||
logout:
|
||||
after:
|
||||
default_browser_return_url: http://127.0.0.1:4455/login
|
||||
|
||||
login:
|
||||
ui_url: http://127.0.0.1:4455/login
|
||||
lifespan: 10m
|
||||
|
||||
registration:
|
||||
lifespan: 10m
|
||||
ui_url: http://127.0.0.1:4455/registration
|
||||
after:
|
||||
password:
|
||||
hooks:
|
||||
-
|
||||
hook: session
|
||||
|
||||
log:
|
||||
level: debug
|
||||
format: text
|
||||
leak_sensitive_values: true
|
||||
|
||||
secrets:
|
||||
cookie:
|
||||
- PLEASE-CHANGE-ME-I-AM-VERY-INSECURE
|
||||
cipher:
|
||||
- 32-LONG-SECRET-NOT-SECURE-AT-ALL
|
||||
|
||||
ciphers:
|
||||
algorithm: xchacha20-poly1305
|
||||
|
||||
hashers:
|
||||
algorithm: bcrypt
|
||||
bcrypt:
|
||||
cost: 8
|
||||
|
||||
identity:
|
||||
default_schema_id: default
|
||||
schemas:
|
||||
- id: default
|
||||
url: file:///etc/config/kratos/identity.schema.json
|
||||
|
||||
courier:
|
||||
smtp:
|
||||
connection_uri: smtps://test:test@mailslurper:1025/?skip_ssl_verify=true
|
|
@ -1,40 +0,0 @@
|
|||
{
|
||||
"$id": "https://schemas.ory.sh/presets/kratos/quickstart/email-password/identity.schema.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Person",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"traits": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
"format": "email",
|
||||
"title": "E-Mail",
|
||||
"minLength": 3,
|
||||
"ory.sh/kratos": {
|
||||
"credentials": {
|
||||
"password": {
|
||||
"identifier": true
|
||||
}
|
||||
},
|
||||
"verification": {
|
||||
"via": "email"
|
||||
},
|
||||
"recovery": {
|
||||
"via": "email"
|
||||
}
|
||||
}
|
||||
},
|
||||
"website": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"website",
|
||||
"email"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
local claims = {
|
||||
email_verified: false
|
||||
} + std.extVar('claims');
|
||||
|
||||
{
|
||||
identity: {
|
||||
traits: {
|
||||
// Allowing unverified email addresses enables account
|
||||
// enumeration attacks, especially if the value is used for
|
||||
// e.g. verification or as a password login identifier.
|
||||
//
|
||||
// Therefore we only return the email if it (a) exists and (b) is marked verified
|
||||
// by GitHub.
|
||||
[if "email" in claims && claims.email_verified then "email" else null]: claims.email,
|
||||
},
|
||||
},
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
-
|
||||
id: "ory:kratos:public"
|
||||
upstream:
|
||||
preserve_host: true
|
||||
url: "http://kratos:4433"
|
||||
strip_path: /.ory/kratos/public
|
||||
match:
|
||||
url: "http://127.0.0.1:4455/.ory/kratos/public/<**>"
|
||||
methods:
|
||||
- GET
|
||||
- POST
|
||||
- PUT
|
||||
- DELETE
|
||||
- PATCH
|
||||
authenticators:
|
||||
-
|
||||
handler: noop
|
||||
authorizer:
|
||||
handler: allow
|
||||
mutators:
|
||||
- handler: noop
|
||||
|
||||
-
|
||||
id: "ory:kratos-selfservice-ui-node:anonymous"
|
||||
upstream:
|
||||
preserve_host: true
|
||||
url: "http://kratos-selfservice-ui-node:4435"
|
||||
match:
|
||||
url: "http://127.0.0.1:4455/<{registration,welcome,recovery,verification,login,error,**.css,**.js,**.png,}>"
|
||||
methods:
|
||||
- GET
|
||||
authenticators:
|
||||
-
|
||||
handler: anonymous
|
||||
authorizer:
|
||||
handler: allow
|
||||
mutators:
|
||||
-
|
||||
handler: noop
|
||||
|
||||
-
|
||||
id: "ory:kratos-selfservice-ui-node:protected"
|
||||
upstream:
|
||||
preserve_host: true
|
||||
url: "http://kratos-selfservice-ui-node:4435"
|
||||
match:
|
||||
url: "http://127.0.0.1:4455/<{debug,dashboard,settings}>"
|
||||
methods:
|
||||
- GET
|
||||
authenticators:
|
||||
-
|
||||
handler: cookie_session
|
||||
authorizer:
|
||||
handler: allow
|
||||
mutators:
|
||||
- handler: id_token
|
||||
errors:
|
||||
- handler: redirect
|
||||
config:
|
||||
to: http://127.0.0.1:4455/login
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"keys": [
|
||||
{
|
||||
"use": "sig",
|
||||
"kty": "RSA",
|
||||
"kid": "a2aa9739-d753-4a0d-87ee-61f101050277",
|
||||
"alg": "RS256",
|
||||
"n": "zpjSl0ySsdk_YC4ZJYYV-cSznWkzndTo0lyvkYmeBkW60YHuHzXaviHqonY_DjFBdnZC0Vs_QTWmBlZvPzTp4Oni-eOetP-Ce3-B8jkGWpKFOjTLw7uwR3b3jm_mFNiz1dV_utWiweqx62Se0SyYaAXrgStU8-3P2Us7_kz5NnBVL1E7aEP40aB7nytLvPhXau-YhFmUfgykAcov0QrnNY0DH0eTcwL19UysvlKx6Uiu6mnbaFE1qx8X2m2xuLpErfiqj6wLCdCYMWdRTHiVsQMtTzSwuPuXfH7J06GTo3I1cEWN8Mb-RJxlosJA_q7hEd43yYisCO-8szX0lgCasw",
|
||||
"e": "AQAB",
|
||||
"d": "x3dfY_rna1UQTmFToBoMn6Edte47irhkra4VSNPwwaeTTvI-oN2TO51td7vo91_xD1nw-0c5FFGi4V2UfRcudBv9LD1rHt_O8EPUh7QtAUeT3_XXgjx1Xxpqu5goMZpkTyGZ-B6JzOY3L8lvWQ_Qeia1EXpvxC-oTOjJnKZeuwIPlcoNKMRU-mIYOnkRFfnUvrDm7N9UZEp3PfI3vhE9AquP1PEvz5KTUYkubsfmupqqR6FmMUm6ulGT7guhBw9A3vxIYbYGKvXLdBvn68mENrEYxXrwmu6ITMh_y208M5rC-hgEHIAIvMu1aVW6jNgyQTunsGST3UyrSbwjI0K9UQ",
|
||||
"p": "77fDvnfHRFEgyi7mh0c6fAdtMEMJ05W8NwTG_D-cSwfWipfTwJJrroWoRwEgdAg5AWGq-MNUzrubTVXoJdC2T4g1o-VRZkKKYoMvav3CvOIMzCBxBs9I_GAKr5NCSk7maksMqiCTMhmkoZ5RPuMYMY_YzxKNAbjBd9qFLfaVAqs",
|
||||
"q": "3KEmPA2XQkf7dvtpY1Xkp1IfMV_UBdmYk7J6dB5BYqzviQWdEFvWaSATJ_7qV1dw0JDZynOgipp8gvoL-RepfjtArhPz41wB3J2xmBYrBr1sJ-x5eqAvMkQk2bd5KTor44e79TRIkmkFYAIdUQ5JdVXPA13S8WUZfb_bAbwaCBk",
|
||||
"dp": "5uyy32AJkNFKchqeLsE6INMSp0RdSftbtfCfM86fZFQno5lA_qjOnO_avJPkTILDT4ZjqoKYxxJJOEXCffNCPPltGvbE5GrDXsUbP8k2-LgWNeoml7XFjIGEqcCFQoohQ1IK4DTDN6cmRh76C0e_Pbdh15D6TydJEIlsdGuu_kM",
|
||||
"dq": "aegFNYCEojFxeTzX6vIZL2RRSt8oJKK-Be__reu0EUzYMtr5-RdMhev6phFMph54LfXKRc9ZOg9MQ4cJ5klAeDKzKpyzTukkj6U20b2aa8LTvxpZec6YuTVSxxu2Ul71IGRQijTNvVIiXWLGddk409Ub6Q7JqkyQfvdwhpWnnUk",
|
||||
"qi": "P68-EwgcRy9ce_PZ75c909cU7dzCiaGcTX1psJiXmQAFBcG0msWfsyHGbllOZG27pKde78ORGJDYDNk1FqTwsogZyCP87EiBmOoqXWnMvKYfJ1DOx7x42LMAGwMD3bgQj9jgRACxFJG4n3NI6uFlFruyl_CLQzwW_rQFHshLK7Q"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
log:
|
||||
level: debug
|
||||
format: json
|
||||
|
||||
serve:
|
||||
proxy:
|
||||
cors:
|
||||
enabled: true
|
||||
allowed_origins:
|
||||
- "*"
|
||||
allowed_methods:
|
||||
- POST
|
||||
- GET
|
||||
- PUT
|
||||
- PATCH
|
||||
- DELETE
|
||||
allowed_headers:
|
||||
- Authorization
|
||||
- Content-Type
|
||||
exposed_headers:
|
||||
- Content-Type
|
||||
allow_credentials: true
|
||||
debug: true
|
||||
|
||||
errors:
|
||||
fallback:
|
||||
- json
|
||||
|
||||
handlers:
|
||||
redirect:
|
||||
enabled: true
|
||||
config:
|
||||
to: http://127.0.0.1:4455/login
|
||||
when:
|
||||
-
|
||||
error:
|
||||
- unauthorized
|
||||
- forbidden
|
||||
request:
|
||||
header:
|
||||
accept:
|
||||
- text/html
|
||||
json:
|
||||
enabled: true
|
||||
config:
|
||||
verbose: true
|
||||
|
||||
access_rules:
|
||||
matching_strategy: glob
|
||||
repositories:
|
||||
- file:///etc/config/oathkeeper/access-rules.yml
|
||||
|
||||
authenticators:
|
||||
anonymous:
|
||||
enabled: true
|
||||
config:
|
||||
subject: guest
|
||||
|
||||
cookie_session:
|
||||
enabled: true
|
||||
config:
|
||||
check_session_url: http://kratos:4433/sessions/whoami
|
||||
preserve_path: true
|
||||
extra_from: "@this"
|
||||
subject_from: "identity.id"
|
||||
only:
|
||||
- ory_kratos_session
|
||||
|
||||
noop:
|
||||
enabled: true
|
||||
|
||||
authorizers:
|
||||
allow:
|
||||
enabled: true
|
||||
|
||||
mutators:
|
||||
noop:
|
||||
enabled: true
|
||||
|
||||
id_token:
|
||||
enabled: true
|
||||
config:
|
||||
issuer_url: http://127.0.0.1:4455/
|
||||
jwks_url: file:///etc/config/oathkeeper/id_token.jwks.json
|
||||
claims: |
|
||||
{
|
||||
"session": {{ .Extra | toJson }}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
version: '3.7'
|
||||
services:
|
||||
kratos-migrate:
|
||||
image: oryd/kratos:v0.10.1
|
||||
environment:
|
||||
- DSN=sqlite:///var/lib/sqlite/db.sqlite?_fk=true&mode=rwc
|
||||
volumes:
|
||||
- type: volume
|
||||
source: kratos-sqlite
|
||||
target: /var/lib/sqlite
|
||||
read_only: false
|
||||
- type: bind
|
||||
source: ./contrib/quickstart/kratos/email-password
|
||||
target: /etc/config/kratos
|
||||
command: -c /etc/config/kratos/kratos.yml migrate sql -e --yes
|
||||
restart: on-failure
|
||||
networks:
|
||||
- intranet
|
||||
# kratos-selfservice-ui-node:
|
||||
# image: oryd/kratos-selfservice-ui-node:v0.10.1
|
||||
# environment:
|
||||
# - KRATOS_PUBLIC_URL=http://kratos:4433/
|
||||
# - KRATOS_BROWSER_URL=http://127.0.0.1:4433/
|
||||
# networks:
|
||||
# - intranet
|
||||
# restart: on-failure
|
||||
kratos:
|
||||
depends_on:
|
||||
- kratos-migrate
|
||||
image: oryd/kratos:v0.10.1
|
||||
ports:
|
||||
- '4433:4433' # public
|
||||
- '4434:4434' # admin
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- DSN=sqlite:///var/lib/sqlite/db.sqlite?_fk=true
|
||||
- LOG_LEVEL=trace
|
||||
command: serve -c /etc/config/kratos/kratos.yml --dev --watch-courier
|
||||
volumes:
|
||||
- type: volume
|
||||
source: kratos-sqlite
|
||||
target: /var/lib/sqlite
|
||||
read_only: false
|
||||
- type: bind
|
||||
source: ./contrib/quickstart/kratos/email-password
|
||||
target: /etc/config/kratos
|
||||
networks:
|
||||
- intranet
|
||||
# mailslurper:
|
||||
# image: oryd/mailslurper:latest-smtps
|
||||
# ports:
|
||||
# - '4436:4436'
|
||||
# - '4437:4437'
|
||||
# networks:
|
||||
# - intranet
|
||||
|
||||
networks:
|
||||
intranet:
|
||||
|
||||
volumes:
|
||||
kratos-sqlite:
|
2
frontend/.gitignore
vendored
2
frontend/.gitignore
vendored
|
@ -1,2 +0,0 @@
|
|||
node_modules
|
||||
.svelte-kit
|
|
@ -1,70 +0,0 @@
|
|||
# kratos-selfservice-svelte-node
|
||||
|
||||
Self-service [Svelte](https://svelte.dev/) node for
|
||||
[Ory Kratos](https://github.com/ory/kratos). It has no style or decoration.
|
||||
Apply your custom style according to your application.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
git clone https://github.com/emrahcom/kratos-selfservice-svelte-node.git
|
||||
|
||||
cd kratos-selfservice-svelte-node
|
||||
npm install
|
||||
```
|
||||
|
||||
## Config
|
||||
|
||||
Change `src/lib/config.ts` according to your environment.
|
||||
|
||||
```javascript
|
||||
export const KRATOS = "https://___KRATOS_FQDN___";
|
||||
export const APP = "https://___APP_FQDN___";
|
||||
```
|
||||
|
||||
## Run (dev)
|
||||
|
||||
```bash
|
||||
npm run dev -- --host --port 3000
|
||||
```
|
||||
|
||||
## Run (prod)
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
node build/index.js
|
||||
```
|
||||
|
||||
## Pages
|
||||
|
||||
- Landing page
|
||||
|
||||
`/`
|
||||
|
||||
- Secure dashboard
|
||||
|
||||
`/dashboard`
|
||||
|
||||
- Registration
|
||||
|
||||
`/registration`
|
||||
|
||||
- Login
|
||||
|
||||
`/login`
|
||||
|
||||
- Settings
|
||||
|
||||
`/settings`
|
||||
|
||||
- Recovery
|
||||
|
||||
`/recovery`
|
||||
|
||||
- Verification
|
||||
|
||||
`/verification`
|
||||
|
||||
- Logout
|
||||
|
||||
`/logout`
|
6593
frontend/package-lock.json
generated
6593
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,37 +0,0 @@
|
|||
{
|
||||
"name": "kratos-svelte-ui",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"lint": "prettier --plugin-search-dir . --check . && eslint .",
|
||||
"format": "prettier --plugin-search-dir . --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "next",
|
||||
"@sveltejs/adapter-node": "^1.0.0-next.94",
|
||||
"@sveltejs/kit": "next",
|
||||
"@typescript-eslint/eslint-plugin": "^5.27.0",
|
||||
"@typescript-eslint/parser": "^5.27.0",
|
||||
"eslint": "^8.16.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-svelte3": "^4.0.0",
|
||||
"prettier": "^2.6.2",
|
||||
"prettier-plugin-svelte": "^2.7.0",
|
||||
"svelte": "^3.44.0",
|
||||
"svelte-check": "^2.7.1",
|
||||
"svelte-preprocess": "^4.10.6",
|
||||
"tslib": "^2.3.1",
|
||||
"typescript": "^4.7.4",
|
||||
"vite": "^3.1.0"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@codemirror/lang-javascript": "^6.1.0",
|
||||
"codemirror": "^6.0.1"
|
||||
}
|
||||
}
|
9
frontend/src/app.d.ts
vendored
9
frontend/src/app.d.ts
vendored
|
@ -1,9 +0,0 @@
|
|||
// See https://kit.svelte.dev/docs/types#app
|
||||
// for information about these interfaces
|
||||
// and what to do when importing types
|
||||
declare namespace App {
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface PageError {}
|
||||
// interface Platform {}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css"
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body>
|
||||
<div>%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,139 +0,0 @@
|
|||
<script context="module">
|
||||
import { EditorView, minimalSetup, basicSetup } from "codemirror";
|
||||
import { StateEffect } from "@codemirror/state";
|
||||
export { minimalSetup, basicSetup };
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { onMount, onDestroy, createEventDispatcher } from "svelte";
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let dom: any;
|
||||
|
||||
let _mounted = false;
|
||||
|
||||
onMount(() => {
|
||||
_mounted = true;
|
||||
return () => {
|
||||
_mounted = false;
|
||||
};
|
||||
});
|
||||
|
||||
export let view: any = null;
|
||||
|
||||
/* `doc` is deliberately made non-reactive for not storing a reduntant string
|
||||
besides the editor. Also, setting doc to undefined will not trigger an
|
||||
update, so that you can clear it after setting one. */
|
||||
export let doc: string;
|
||||
|
||||
/* Set this if you would like to listen to all transactions via `update` event. */
|
||||
export let verbose = false;
|
||||
|
||||
/* Cached doc string so that we don't extract strings in bulk over and over. */
|
||||
let _docCached: string = "";
|
||||
|
||||
/* Overwrite the bulk of the text with the one specified. */
|
||||
function _setText(text: string) {
|
||||
view.dispatch({
|
||||
changes: { from: 0, to: view.state.doc.length, insert: text },
|
||||
});
|
||||
}
|
||||
|
||||
const subscribers = new Set();
|
||||
|
||||
/* And here comes the reactivity, implemented as a r/w store. */
|
||||
export const docStore = {
|
||||
ready: () => view !== null,
|
||||
subscribe(cb) {
|
||||
subscribers.add(cb);
|
||||
|
||||
if (!this.ready()) {
|
||||
cb(null);
|
||||
} else {
|
||||
if (_docCached == null) {
|
||||
_docCached = view.state.doc.toString();
|
||||
}
|
||||
cb(_docCached);
|
||||
}
|
||||
|
||||
return () => void subscribers.delete(cb);
|
||||
},
|
||||
set(newValue: string) {
|
||||
if (!_mounted) {
|
||||
throw new Error(
|
||||
"Cannot set docStore when the component is not mounted."
|
||||
);
|
||||
}
|
||||
|
||||
const inited = _initEditorView(newValue);
|
||||
if (!inited) _setText(newValue);
|
||||
},
|
||||
};
|
||||
|
||||
export let extensions = minimalSetup;
|
||||
|
||||
function _reconfigureExtensions() {
|
||||
if (view === null) return;
|
||||
view.dispatch({
|
||||
effects: StateEffect.reconfigure.of(extensions),
|
||||
});
|
||||
}
|
||||
$: extensions, _reconfigureExtensions();
|
||||
|
||||
function _editorTxHandler(tr: any) {
|
||||
this.update([tr]);
|
||||
|
||||
if (verbose) {
|
||||
dispatch("update", tr);
|
||||
}
|
||||
|
||||
if (tr.docChanged) {
|
||||
_docCached = "";
|
||||
if (subscribers.size) {
|
||||
dispatchDocStore((_docCached = tr.newDoc.toString()));
|
||||
}
|
||||
dispatch("change", { view: this, tr });
|
||||
}
|
||||
}
|
||||
|
||||
function dispatchDocStore(s) {
|
||||
for (const cb of subscribers) {
|
||||
cb(s);
|
||||
}
|
||||
}
|
||||
|
||||
// the view will be inited with the either doc (as long as that it is not `undefined`)
|
||||
// or the value in docStore once set
|
||||
function _initEditorView(initialDoc: string) {
|
||||
if (view !== null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
view = new EditorView({
|
||||
doc: initialDoc,
|
||||
extensions,
|
||||
parent: dom,
|
||||
dispatch: _editorTxHandler,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
$: if (_mounted && doc !== undefined) {
|
||||
_initEditorView(doc);
|
||||
dispatchDocStore(doc);
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
if (view !== null) {
|
||||
view.destroy();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="codemirror" bind:this="{dom}"></div>
|
||||
|
||||
<style>
|
||||
.codemirror {
|
||||
display: contents;
|
||||
}
|
||||
</style>
|
|
@ -1,29 +0,0 @@
|
|||
<script lang="ts">
|
||||
import Messages from "$lib/components/kratos/messages.svelte";
|
||||
import type { Node } from "$lib/kratos/types";
|
||||
|
||||
export let node: Node;
|
||||
|
||||
const attr = node.attributes;
|
||||
let labelText: string;
|
||||
|
||||
labelText = attr.name;
|
||||
if (node.meta && node.meta.label) labelText = node.meta.label.text;
|
||||
</script>
|
||||
|
||||
<!-- -------------------------------------------------------------------------->
|
||||
<fieldset>
|
||||
<label>
|
||||
<span>{labelText}</span>
|
||||
<input
|
||||
type="email"
|
||||
name={attr.name}
|
||||
value={attr.value ?? ""}
|
||||
placeholder={labelText}
|
||||
disabled={attr.disabled}
|
||||
required={attr.required}
|
||||
/>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<Messages messages={node.messages} />
|
|
@ -1,10 +0,0 @@
|
|||
<script lang="ts">
|
||||
import type { Node } from "$lib/kratos/types";
|
||||
|
||||
export let node: Node;
|
||||
|
||||
const attr = node.attributes;
|
||||
</script>
|
||||
|
||||
<!-- -------------------------------------------------------------------------->
|
||||
<input type="hidden" name={attr.name} value={attr.value ?? ""} />
|
|
@ -1,74 +0,0 @@
|
|||
<script lang="ts">
|
||||
export let isHidden: boolean;
|
||||
|
||||
const toggleVisibility = () => {
|
||||
isHidden = !isHidden;
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- -------------------------------------------------------------------------->
|
||||
<svg class="password-visibility-toggle" on:click={toggleVisibility}>
|
||||
{#if isHidden}
|
||||
<path
|
||||
d="M8 2.36365
|
||||
C 4.36364 2.36365 1.25818 4.62547 0 7.81819
|
||||
C 1.25818 11.0109 4.36364 13.2727 8 13.2727
|
||||
C 11.6364 13.2727 14.7418 11.0109 16 7.81819
|
||||
C 14.7418 4.62547 11.6364 2.36365 8 2.36365
|
||||
Z
|
||||
M8 11.4546
|
||||
C 5.99273 11.4546 4.36364 9.82547 4.36364 7.81819
|
||||
C 4.36364 5.81092 5.99273 4.18183 8 4.18183
|
||||
C 10.0073 4.18183 11.6364 5.81092 11.6364 7.81819
|
||||
C 11.6364 9.82547 10.0073 11.4546 8 11.4546
|
||||
Z
|
||||
M8 5.63637
|
||||
C 6.79273 5.63637 5.81818 6.61092 5.81818 7.81819
|
||||
C 5.81818 9.02547 6.79273 10 8 10
|
||||
C 9.20727 10 10.1818 9.02547 10.1818 7.81819
|
||||
C 10.1818 6.61092 9.20727 5.63637 8 5.63637
|
||||
Z"
|
||||
/>
|
||||
{:else}
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M14.8222 1.85355
|
||||
C 15.0175 1.65829 15.0175 1.34171 14.8222 1.14645
|
||||
C 14.627 0.951184 14.3104 0.951184 14.1151 1.14645
|
||||
L 12.005 3.25653
|
||||
C 10.8901 2.482 9.56509 1.92505 8.06 1.92505
|
||||
C 3 1.92505 0 7.92505 0 7.92505
|
||||
C 0 7.92505 1.16157 10.2482 3.25823 12.0033
|
||||
L 1.19366 14.0679
|
||||
C 0.998396 14.2632 0.998396 14.5798 1.19366 14.775
|
||||
C 1.38892 14.9703 1.7055 14.9703 1.90076 14.775
|
||||
L 14.8222 1.85355
|
||||
Z
|
||||
M 4.85879 10.4028
|
||||
L 6.29159 8.96998
|
||||
C 6.10643 8.66645 6 8.3089 6 7.92505
|
||||
C 6 6.81505 6.89 5.92505 8 5.92505
|
||||
C 8.38385 5.92505 8.7414 6.03148 9.04493 6.21664
|
||||
L 10.4777 4.78384
|
||||
C 9.79783 4.24654 8.93821 3.92505 8 3.92505
|
||||
C 5.8 3.92505 4 5.72505 4 7.92505
|
||||
C 4 8.86326 4.32149 9.72288 4.85879 10.4028
|
||||
Z
|
||||
M 11.8644 6.88906
|
||||
L 13.8567 4.8968
|
||||
C 15.2406 6.40616 16 7.92505 16 7.92505
|
||||
C 16 7.92505 13 13.925 8.06 13.925
|
||||
C 7.09599 13.925 6.20675 13.7073 5.39878 13.3547
|
||||
L 6.96401 11.7895
|
||||
C 7.29473 11.8779 7.64207 11.925 8 11.925
|
||||
C 10.22 11.925 12 10.145 12 7.92505
|
||||
C 12 7.56712 11.9529 7.21978 11.8644 6.88906
|
||||
Z
|
||||
M 9.33847 9.41501
|
||||
L 9.48996 9.26352
|
||||
C 9.44222 9.31669 9.39164 9.36726 9.33847 9.41501
|
||||
Z"
|
||||
/>
|
||||
{/if}
|
||||
</svg>
|
|
@ -1,26 +0,0 @@
|
|||
<script lang="ts">
|
||||
import Messages from "$lib/components/kratos/messages.svelte";
|
||||
import PasswordToggle from "$lib/components/kratos/fieldset-password-toggle.svelte";
|
||||
import type { Node } from "$lib/kratos/types";
|
||||
|
||||
export let node: Node;
|
||||
|
||||
const attr = node.attributes;
|
||||
let isHidden = true;
|
||||
let labelText: string;
|
||||
|
||||
labelText = attr.name;
|
||||
if (node.meta && node.meta.label) labelText = node.meta.label.text;
|
||||
</script>
|
||||
|
||||
<!-- -------------------------------------------------------------------------->
|
||||
<input
|
||||
type={isHidden ? "password" : "text"}
|
||||
name={attr.name}
|
||||
value={attr.value ?? ""}
|
||||
placeholder={labelText}
|
||||
disabled={attr.disabled}
|
||||
required={attr.required}
|
||||
/>
|
||||
|
||||
<Messages messages={node.messages} />
|
|
@ -1,16 +0,0 @@
|
|||
<script lang="ts">
|
||||
import type { Node } from "$lib/kratos/types";
|
||||
|
||||
export let node: Node;
|
||||
|
||||
const attr = node.attributes;
|
||||
let labelText: string;
|
||||
|
||||
labelText = "Submit";
|
||||
if (node.meta && node.meta.label) labelText = node.meta.label.text;
|
||||
</script>
|
||||
|
||||
<!-- -------------------------------------------------------------------------->
|
||||
<button type="submit" name={attr.name} value={attr.value}>
|
||||
{labelText}
|
||||
</button>
|
|
@ -1,24 +0,0 @@
|
|||
<script lang="ts">
|
||||
import Messages from "$lib/components/kratos/messages.svelte";
|
||||
import type { Node } from "$lib/kratos/types";
|
||||
|
||||
export let node: Node;
|
||||
|
||||
const attr = node.attributes;
|
||||
let labelText: string;
|
||||
|
||||
labelText = attr.name;
|
||||
if (node.meta && node.meta.label) labelText = node.meta.label.text;
|
||||
</script>
|
||||
|
||||
<!-- -------------------------------------------------------------------------->
|
||||
<input
|
||||
type="text"
|
||||
name={attr.name}
|
||||
value={attr.value ?? ""}
|
||||
placeholder={labelText}
|
||||
disabled={attr.disabled}
|
||||
required={attr.required}
|
||||
/>
|
||||
|
||||
<Messages messages={node.messages} />
|
|
@ -1,27 +0,0 @@
|
|||
<script lang="ts">
|
||||
import Hidden from "$lib/components/kratos/fieldset-hidden.svelte";
|
||||
import Password from "$lib/components/kratos/fieldset-password.svelte";
|
||||
import Text from "$lib/components/kratos/fieldset-text.svelte";
|
||||
import Email from "$lib/components/kratos/fieldset-email.svelte";
|
||||
import Submit from "$lib/components/kratos/fieldset-submit.svelte";
|
||||
import type { Node } from "$lib/kratos/types";
|
||||
|
||||
export let nodes: Node[];
|
||||
</script>
|
||||
|
||||
<!-- -------------------------------------------------------------------------->
|
||||
{#each nodes as node}
|
||||
{#if node.attributes.type === "hidden"}
|
||||
<Hidden {node} />
|
||||
{:else if node.attributes.type === "password"}
|
||||
<Password {node} />
|
||||
{:else if node.attributes.type === "text"}
|
||||
<Text {node} />
|
||||
{:else if node.attributes.type === "email"}
|
||||
<Email {node} />
|
||||
{:else if node.attributes.type === "submit"}
|
||||
<Submit {node} />
|
||||
{:else}
|
||||
unknow type
|
||||
{/if}
|
||||
{/each}
|
|
@ -1,16 +0,0 @@
|
|||
<script lang="ts">
|
||||
import Fieldsets from "$lib/components/kratos/fieldsets.svelte";
|
||||
import type { KratosForm } from "$lib/kratos/types";
|
||||
|
||||
export let dm: KratosForm;
|
||||
export let groups: string[];
|
||||
|
||||
const nodes = dm.ui.nodes.filter(
|
||||
(n) => n.type === "input" && groups.includes(n.group),
|
||||
);
|
||||
</script>
|
||||
|
||||
<!-- -------------------------------------------------------------------------->
|
||||
<form action={dm.ui.action} method={dm.ui.method}>
|
||||
<Fieldsets {nodes} />
|
||||
</form>
|
|
@ -1,14 +0,0 @@
|
|||
<script lang="ts">
|
||||
import type { Message } from "$lib/kratos/types";
|
||||
|
||||
export let messages: Message[];
|
||||
</script>
|
||||
|
||||
<!-- -------------------------------------------------------------------------->
|
||||
{#if messages}
|
||||
{#each messages as msg}
|
||||
{msg.id} -
|
||||
{msg.type} -
|
||||
{msg.text}<br />
|
||||
{/each}
|
||||
{/if}
|
|
@ -1,2 +0,0 @@
|
|||
export const KRATOS = "http://localhost:4433";
|
||||
export const APP = "http://localhost:3000";
|
|
@ -1,11 +0,0 @@
|
|||
export async function get(url: string) {
|
||||
const res = await fetch(url, {
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
},
|
||||
mode: "cors",
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
import { browser } from "$app/environment";
|
||||
import { KRATOS } from "$lib/config";
|
||||
import { get } from "$lib/http";
|
||||
import type {
|
||||
KratosError,
|
||||
KratosForm,
|
||||
KratosIdentity,
|
||||
KratosLogout,
|
||||
} from "$lib/kratos/types";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
export function getFlowId(urlSearch: string): string {
|
||||
const qs = new URLSearchParams(urlSearch);
|
||||
const flowId = qs.get("flow");
|
||||
|
||||
if (flowId) return flowId;
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
export async function getIdentity(): Promise<KratosIdentity> {
|
||||
if (!browser) throw new Error("no browser environment");
|
||||
|
||||
const url = `${KRATOS}/sessions/whoami`;
|
||||
const res = await get(url);
|
||||
|
||||
if (res.status !== 200) {
|
||||
throw new Error("no identity");
|
||||
}
|
||||
|
||||
const dm = await res.json();
|
||||
return dm.identity;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
export async function getDataModels(
|
||||
flow: string,
|
||||
flowId: string
|
||||
): Promise<KratosForm | KratosError> {
|
||||
if (!flowId) throw new Error("no flowId");
|
||||
// if (!browser) throw new Error("no browser environment");
|
||||
|
||||
const url = `${KRATOS}/self-service/${flow}/flows?id=${flowId}`;
|
||||
const res = await get(url);
|
||||
const dm = await res.json();
|
||||
|
||||
if (dm.error) {
|
||||
dm.instanceOf = "KratosError";
|
||||
|
||||
if (dm.error.details && dm.error.details.redirect_to) {
|
||||
window.location.href = dm.error.details.redirect_to;
|
||||
}
|
||||
} else if (dm.ui) {
|
||||
dm.instanceOf = "KratosForm";
|
||||
} else {
|
||||
throw new Error("unexpected Kratos object");
|
||||
}
|
||||
|
||||
return dm;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
export async function getLogoutDataModels(): Promise<
|
||||
KratosLogout | KratosError
|
||||
> {
|
||||
if (!browser) throw new Error("no browser environment");
|
||||
|
||||
const url = `${KRATOS}/self-service/logout/browser`;
|
||||
const res = await get(url);
|
||||
const dm = await res.json();
|
||||
|
||||
if (dm.error) {
|
||||
dm.instanceOf = "KratosError";
|
||||
} else if (dm.logout_url) {
|
||||
dm.instanceOf = "KratosLogout";
|
||||
} else {
|
||||
throw new Error("unexpected Kratos object");
|
||||
}
|
||||
|
||||
return dm;
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
// -----------------------------------------------------------------------------
|
||||
export interface Attributes {
|
||||
name: string;
|
||||
type: string;
|
||||
value?: string;
|
||||
disabled: boolean;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
export interface Label {
|
||||
id: number;
|
||||
type: string;
|
||||
text: string;
|
||||
context?: unknown;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
export interface Message {
|
||||
id: number;
|
||||
type: string;
|
||||
text: string;
|
||||
context?: unknown;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
export interface Meta {
|
||||
label?: Label;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
export interface Node {
|
||||
type: string;
|
||||
group: string;
|
||||
attributes: Attributes;
|
||||
messages: Message[];
|
||||
meta: Meta;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
export interface UI {
|
||||
action: string;
|
||||
method: string;
|
||||
messages?: Message[];
|
||||
nodes: Node[];
|
||||
"updated_at": string;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
export interface KratosForm {
|
||||
instanceOf: "KratosForm";
|
||||
id: string;
|
||||
type: string;
|
||||
forced?: boolean;
|
||||
ui: UI;
|
||||
"created_at"?: string;
|
||||
"expires_at": string;
|
||||
"issued_at": string;
|
||||
"updated_at"?: string;
|
||||
"request_url": string;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
export interface KratosError {
|
||||
instanceOf: "KratosError";
|
||||
error: {
|
||||
code: number;
|
||||
message: string;
|
||||
status: string;
|
||||
reason?: string;
|
||||
details?: {
|
||||
docs: string;
|
||||
hint: string;
|
||||
"redirect_to": string;
|
||||
"reject_reason": string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
export interface KratosLogout {
|
||||
instanceOf: "KratosLogout";
|
||||
"logout_url": string;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
export interface KratosIdentity {
|
||||
id: string;
|
||||
traits: {
|
||||
email: string;
|
||||
};
|
||||
state: string;
|
||||
"created_at": string;
|
||||
"updated_at": string;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
export interface KratosLoad {
|
||||
status?: number;
|
||||
redirect?: string;
|
||||
props?: {
|
||||
[key: string]:
|
||||
| string
|
||||
| KratosError
|
||||
| KratosForm
|
||||
| KratosIdentity
|
||||
| KratosLogout;
|
||||
};
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
import { writable } from "svelte/store";
|
||||
import type { KratosIdentity } from "$lib/kratos/types";
|
||||
|
||||
export default writable({} as KratosIdentity);
|
|
@ -1,18 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { browser } from "$app/environment";
|
||||
import { get } from "svelte/store";
|
||||
import identity from "$lib/stores/kratos/identity";
|
||||
|
||||
const _identity = get(identity);
|
||||
|
||||
if (browser) {
|
||||
if (_identity.id) {
|
||||
console.log("signed in");
|
||||
} else {
|
||||
console.log("not signed in");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- -------------------------------------------------------------------------->
|
||||
<slot />
|
|
@ -1,16 +0,0 @@
|
|||
import { getIdentity } from "$lib/kratos";
|
||||
import identity from "$lib/stores/kratos/identity";
|
||||
|
||||
// ---------------------------Q--------------------------------------------------
|
||||
export const csr = true;
|
||||
export const prerender = false;
|
||||
|
||||
export async function load() {
|
||||
await getIdentity()
|
||||
.then((_identity) => {
|
||||
identity.set(_identity);
|
||||
})
|
||||
.catch(() => {
|
||||
//no identity
|
||||
});
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { APP } from "$lib/config";
|
||||
import { get } from "svelte/store";
|
||||
import identity from "$lib/stores/kratos/identity";
|
||||
|
||||
const _identity = get(identity);
|
||||
</script>
|
||||
|
||||
<section id="welcome">
|
||||
<h1>Probo</h1>
|
||||
|
||||
{#if !_identity.id}
|
||||
<p><a href="{APP}/auth/login">Login</a></p>
|
||||
{:else}
|
||||
<p><a href="{APP}/dashboard">Dashboard</a></p>
|
||||
{/if}
|
||||
</section>
|
|
@ -1,39 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { APP, KRATOS } from "$lib/config";
|
||||
import { page } from "$app/stores";
|
||||
import { browser } from "$app/environment";
|
||||
import { getFlowId, getDataModels } from "$lib/kratos";
|
||||
import Form from "$lib/components/kratos/form.svelte";
|
||||
import Messages from "$lib/components/kratos/messages.svelte";
|
||||
|
||||
// const flowId = getFlowId($page.url.search); -->
|
||||
// if (browser && !flowId) -->
|
||||
// window.location.href = `${KRATOS}/self-service/login/browser`; -->
|
||||
|
||||
// const pr = getDataModels("login", flowId); -->
|
||||
|
||||
export let data: PageData;
|
||||
$: pr = data.pr;
|
||||
</script>
|
||||
|
||||
<!-- -------------------------------------------------------------------------->
|
||||
<div id="login">
|
||||
{#await data.pr then dm} {#if dm.instanceOf === "KratosForm"}
|
||||
<div id="login">
|
||||
<h2>Sign in</h2>
|
||||
|
||||
{#if dm.ui.messages}
|
||||
<Messages messages="{dm.ui.messages}" />
|
||||
{/if} <Form {dm} groups={["default", "password"]} />
|
||||
|
||||
<hr />
|
||||
|
||||
<div>
|
||||
<p><a href="{APP}/auth/recovery">Forget password?</a></p>
|
||||
<p><a href="{APP}/auth/registration">Don't have an account?</a></p>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<p>Something went wrong</p>
|
||||
{/if} {/await}
|
||||
</div>
|
|
@ -1,16 +0,0 @@
|
|||
import { getFlowId, getDataModels } from "$lib/kratos";
|
||||
import { page } from "$app/stores";
|
||||
import { browser } from "$app/environment";
|
||||
import { KRATOS } from "$lib/config";
|
||||
|
||||
/** @type {import('./$types').PageLoad} */
|
||||
export async function load({ url }) {
|
||||
const flowId = getFlowId(url.search);
|
||||
|
||||
if (browser && !flowId)
|
||||
window.location.href = `${KRATOS}/self-service/login/browser`;
|
||||
|
||||
const pr = getDataModels("login", flowId);
|
||||
|
||||
return { pr: pr };
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
import { browser } from "$app/environment";
|
||||
import { getLogoutDataModels } from "$lib/kratos";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
export async function load() {
|
||||
if (!browser) return {};
|
||||
|
||||
const dm = await getLogoutDataModels();
|
||||
|
||||
if (dm.instanceOf === "KratosLogout") {
|
||||
window.location.replace(dm.logout_url);
|
||||
} else {
|
||||
window.location.replace("/");
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { KRATOS } from "$lib/config";
|
||||
import { page } from "$app/stores";
|
||||
import { browser } from "$app/environment";
|
||||
import { getFlowId, getDataModels } from "$lib/kratos";
|
||||
import Form from "$lib/components/kratos/form.svelte";
|
||||
import Messages from "$lib/components/kratos/messages.svelte";
|
||||
|
||||
const flowId = getFlowId($page.url.search);
|
||||
if (browser && !flowId)
|
||||
window.location.href = `${KRATOS}/self-service/recovery/browser`;
|
||||
|
||||
const pr = getDataModels("recovery", flowId);
|
||||
</script>
|
||||
|
||||
<!-- -------------------------------------------------------------------------->
|
||||
<section id="recovery">
|
||||
{#await pr then dm}
|
||||
{#if dm.instanceOf === "KratosForm"}
|
||||
<div class="container" id="recovery">
|
||||
<h2 class="subheading">recovery</h2>
|
||||
|
||||
{#if dm.ui.messages}
|
||||
<Messages messages={dm.ui.messages} />
|
||||
{:else}
|
||||
<Form {dm} groups={["default", "link"]} />
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<p>Something went wrong</p>
|
||||
{/if}
|
||||
{/await}
|
||||
</section>
|
|
@ -1,39 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { APP, KRATOS } from "$lib/config";
|
||||
import { page } from "$app/stores";
|
||||
import { browser } from "$app/environment";
|
||||
import { getFlowId, getDataModels } from "$lib/kratos";
|
||||
import Form from "$lib/components/kratos/form.svelte";
|
||||
import Messages from "$lib/components/kratos/messages.svelte";
|
||||
|
||||
const flowId = getFlowId($page.url.search);
|
||||
|
||||
if (browser && !flowId)
|
||||
window.location.href = `${KRATOS}/self-service/registration/browser`;
|
||||
|
||||
const pr = getDataModels("registration", flowId);
|
||||
</script>
|
||||
|
||||
<!-- -------------------------------------------------------------------------->
|
||||
<section id="registration">
|
||||
{#await pr then dm}
|
||||
{#if dm.instanceOf === "KratosForm"}
|
||||
<div class="container" id="registration">
|
||||
<h2 class="subheading">Registration</h2>
|
||||
|
||||
{#if dm.ui.messages}
|
||||
<Messages messages={dm.ui.messages} />
|
||||
{/if}
|
||||
<Form {dm} groups={["default", "password"]} />
|
||||
|
||||
<hr class="divider" />
|
||||
|
||||
<section class="alternative-actions">
|
||||
<p><a href="{APP}/login">Already have an account?</a></p>
|
||||
</section>
|
||||
</div>
|
||||
{:else}
|
||||
<p>Something went wrong</p>
|
||||
{/if}
|
||||
{/await}
|
||||
</section>
|
|
@ -1,35 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { KRATOS } from "$lib/config";
|
||||
import { page } from "$app/stores";
|
||||
import { browser } from "$app/environment";
|
||||
import { getFlowId, getDataModels } from "$lib/kratos";
|
||||
import Form from "$lib/components/kratos/form.svelte";
|
||||
import Messages from "$lib/components/kratos/messages.svelte";
|
||||
|
||||
const flowId = getFlowId($page.url.search);
|
||||
if (browser && !flowId)
|
||||
window.location.href = `${KRATOS}/self-service/settings/browser`;
|
||||
|
||||
const pr = getDataModels("settings", flowId);
|
||||
</script>
|
||||
|
||||
<!-- -------------------------------------------------------------------------->
|
||||
<section id="settings">
|
||||
{#await pr then dm}
|
||||
{#if dm.instanceOf === "KratosForm"}
|
||||
<div class="container" id="settings">
|
||||
<h2 class="subheading">Settings</h2>
|
||||
|
||||
{#if dm.ui.messages}
|
||||
<Messages messages={dm.ui.messages} />
|
||||
{:else}
|
||||
<Form {dm} groups={["default", "profile"]} />
|
||||
<hr class="divider" />
|
||||
<Form {dm} groups={["default", "password"]} />
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<p>Something went wrong</p>
|
||||
{/if}
|
||||
{/await}
|
||||
</section>
|
|
@ -1,42 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { KRATOS } from "$lib/config";
|
||||
import { page } from "$app/stores";
|
||||
import { browser } from "$app/environment";
|
||||
import { get } from "svelte/store";
|
||||
import { getFlowId, getDataModels } from "$lib/kratos";
|
||||
import identity from "$lib/stores/kratos/identity";
|
||||
import Form from "$lib/components/kratos/form.svelte";
|
||||
import Messages from "$lib/components/kratos/messages.svelte";
|
||||
|
||||
const _identity = get(identity);
|
||||
const flowId = getFlowId($page.url.search);
|
||||
|
||||
if (browser) {
|
||||
if (!_identity) {
|
||||
window.location.href = `${KRATOS}/self-service/login/browser`;
|
||||
} else if (!flowId) {
|
||||
window.location.href = `${KRATOS}/self-service/verification/browser`;
|
||||
}
|
||||
}
|
||||
|
||||
const pr = getDataModels("verification", flowId);
|
||||
</script>
|
||||
|
||||
<!-- -------------------------------------------------------------------------->
|
||||
<section id="verification">
|
||||
{#await pr then dm}
|
||||
{#if dm.instanceOf === "KratosForm"}
|
||||
<div class="container" id="verification">
|
||||
<h2 class="subheading">verification</h2>
|
||||
|
||||
{#if dm.ui.messages}
|
||||
<Messages messages={dm.ui.messages} />
|
||||
{:else}
|
||||
<Form {dm} groups={["default", "link"]} />
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<p>Something went wrong</p>
|
||||
{/if}
|
||||
{/await}
|
||||
</section>
|
|
@ -1,16 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { browser } from "$app/environment";
|
||||
import { KRATOS } from "$lib/config";
|
||||
import { get } from "svelte/store";
|
||||
import identity from "$lib/stores/kratos/identity";
|
||||
|
||||
const _identity = get(identity);
|
||||
|
||||
if (browser && !_identity.id) {
|
||||
const loginUrl = `${KRATOS}/self-service/login/browser`;
|
||||
window.location.replace(loginUrl);
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- -------------------------------------------------------------------------->
|
||||
<slot />
|
|
@ -1,29 +0,0 @@
|
|||
import { get } from "svelte/store";
|
||||
import identity from "$lib/stores/kratos/identity";
|
||||
|
||||
type Question = {
|
||||
text: string;
|
||||
};
|
||||
|
||||
type Answer = {
|
||||
text: string;
|
||||
};
|
||||
|
||||
type Quiz = {
|
||||
uid: string;
|
||||
question: Question;
|
||||
answers: Answer[];
|
||||
};
|
||||
|
||||
/** @type {import('./$types').PageLoad} */
|
||||
export async function load() {
|
||||
const _identity = get(identity);
|
||||
if (_identity) {
|
||||
const res = await fetch("http://localhost:8080/quizzes");
|
||||
const response = await res.json();
|
||||
|
||||
if (response.status === "success") {
|
||||
return { quizzes: (await response.content) as Quiz[] };
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { APP } from "$lib/config";
|
||||
// import { get } from "svelte/store";
|
||||
// import identity from "$lib/stores/kratos/identity";
|
||||
import CodeMirror, { basicSetup } from "$lib/components/codemirror/codemirror.svelte";
|
||||
import type { PageData } from './$types';
|
||||
|
||||
export let data: PageData;
|
||||
$: quizzes = data.quizzes;
|
||||
|
||||
// const _identity = get(identity);
|
||||
let email = "";
|
||||
let store: any;
|
||||
|
||||
// if (_identity && _identity.traits) email = _identity.traits.email;
|
||||
|
||||
function changeHandler({ detail: {tr: any} }) {
|
||||
console.log('change', tr.changes.toJSON())
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<section id="dashboard">
|
||||
<h2>Probo Dashboard</h2>
|
||||
|
||||
<p>Hello {email}</p>
|
||||
|
||||
{#each quizzes as quiz}
|
||||
<ul>
|
||||
<li>{quiz.Question.Text}</li>
|
||||
</ul>
|
||||
{/each}
|
||||
|
||||
<CodeMirror doc={'Testo della domanda\n\n* Risposta 1\n* Risposta 2\n* Risposta 3'}
|
||||
bind:docStore={store}
|
||||
extensions={basicSetup}
|
||||
on:change={changeHandler}>
|
||||
</CodeMirror>
|
||||
|
||||
<p><a href="{APP}/auth/settings">Settings</a></p>
|
||||
<p><a href="{APP}/auth/logout">Logout</a></p>
|
||||
</section>
|
Binary file not shown.
Before Width: | Height: | Size: 1.5 KiB |
|
@ -1,16 +0,0 @@
|
|||
//import adapter from '@sveltejs/adapter-auto';
|
||||
import adapter from "@sveltejs/adapter-node";
|
||||
import preprocess from "svelte-preprocess";
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
// Consult https://github.com/sveltejs/svelte-preprocess
|
||||
// for more information about preprocessors
|
||||
preprocess: preprocess(),
|
||||
|
||||
kit: {
|
||||
adapter: adapter(),
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
|
@ -1,26 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Set Session Name
|
||||
SESSION="kratos-svelte-login"
|
||||
SESSIONEXISTS=$(tmux list-sessions | grep $SESSION)
|
||||
|
||||
# Only create tmux session if it doesn't already exist
|
||||
if [ "$SESSIONEXISTS" = "" ]
|
||||
then
|
||||
# Start New Session with our name
|
||||
tmux new-session -d -s $SESSION
|
||||
|
||||
# Name first Pane and start zsh
|
||||
tmux rename-window -t 0 'src'
|
||||
|
||||
# Create and setup pane for running the backend
|
||||
# tmux send-keys -t 'Main' 'bash' C-m 'clear' C-m 'cd backend && go build -o backend . && ./backend' C-m
|
||||
tmux send-keys -t 'src' 'cd src && npm run dev -- --host --port 3000' C-m
|
||||
|
||||
# Create an horizontal pane for terminal commands
|
||||
tmux split-window -vf -l 1
|
||||
tmux send-keys 'emacs src/routes/+page.svelte &' Enter
|
||||
fi
|
||||
|
||||
# Attach Session, on the Main window
|
||||
tmux attach-session -t $SESSION:0
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true
|
||||
}
|
||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
||||
//
|
||||
// If you want to overwrite includes/excludes, make sure to copy over the
|
||||
// relevant includes/excludes from the referenced tsconfig.json
|
||||
// TypeScript does not merge them in
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
import { sveltekit } from "@sveltejs/kit/vite";
|
||||
import type { UserConfig } from "vite";
|
||||
|
||||
const config: UserConfig = {
|
||||
plugins: [sveltekit()],
|
||||
server: {
|
||||
hmr: {
|
||||
clientPort: 3000,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
3
main.go
3
main.go
|
@ -5,6 +5,7 @@ import (
|
|||
"net/http"
|
||||
|
||||
"git.andreafazzi.eu/andrea/probo/hasher/sha256"
|
||||
"git.andreafazzi.eu/andrea/probo/logger"
|
||||
"git.andreafazzi.eu/andrea/probo/store/memory"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
@ -12,7 +13,7 @@ import (
|
|||
const port = "8080"
|
||||
|
||||
func main() {
|
||||
// logger.SetLevel(logger.DebugLevel)
|
||||
logger.SetLevel(logger.DebugLevel)
|
||||
|
||||
server := NewProboCollectorServer(
|
||||
memory.NewMemoryProboCollectorStore(
|
||||
|
|
21
misc/logseq/LICENSE
Normal file
21
misc/logseq/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 Andrea Fazzi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -2,6 +2,7 @@ package models
|
|||
|
||||
type Quiz struct {
|
||||
ID string
|
||||
Hash string
|
||||
Question *Question
|
||||
Answers []*Answer
|
||||
Correct *Answer
|
||||
|
|
BIN
probo
Executable file
BIN
probo
Executable file
Binary file not shown.
|
@ -10,7 +10,6 @@ import (
|
|||
"git.andreafazzi.eu/andrea/probo/models"
|
||||
"git.andreafazzi.eu/andrea/probo/store"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"log"
|
||||
)
|
||||
|
||||
type ProboCollectorServer struct {
|
||||
|
@ -101,7 +100,6 @@ func (ps *ProboCollectorServer) updateQuizHandler(w http.ResponseWriter, r *http
|
|||
}
|
||||
|
||||
func (ps *ProboCollectorServer) readAllQuiz(w http.ResponseWriter, r *http.Request) ([]*models.Quiz, error) {
|
||||
log.Println(r)
|
||||
quizzes, err := ps.store.ReadAllQuizzes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
7
store/file/.md
Normal file
7
store/file/.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
Newly created question text.
|
||||
|
||||
* Answer 1
|
||||
* Answer 1
|
||||
* Answer 2
|
||||
* Answer 3
|
||||
* Answer 4
|
162
store/file/file.go
Normal file
162
store/file/file.go
Normal file
|
@ -0,0 +1,162 @@
|
|||
package file
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"git.andreafazzi.eu/andrea/probo/client"
|
||||
"git.andreafazzi.eu/andrea/probo/hasher/sha256"
|
||||
"git.andreafazzi.eu/andrea/probo/models"
|
||||
"git.andreafazzi.eu/andrea/probo/store/memory"
|
||||
)
|
||||
|
||||
type FileProboCollectorStore struct {
|
||||
Dir string
|
||||
|
||||
memoryStore *memory.MemoryProboCollectorStore
|
||||
}
|
||||
|
||||
func NewFileProboCollectorStore(dirname string) (*FileProboCollectorStore, error) {
|
||||
s := new(FileProboCollectorStore)
|
||||
|
||||
files, err := ioutil.ReadDir(dirname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
markdownFiles := make([]fs.FileInfo, 0)
|
||||
|
||||
for _, file := range files {
|
||||
filename := file.Name()
|
||||
if !file.IsDir() && strings.HasSuffix(filename, ".md") {
|
||||
markdownFiles = append(markdownFiles, file)
|
||||
}
|
||||
}
|
||||
|
||||
if len(markdownFiles) == 0 {
|
||||
return nil, fmt.Errorf("The directory is empty.")
|
||||
}
|
||||
|
||||
s.memoryStore = memory.NewMemoryProboCollectorStore(
|
||||
sha256.NewDefault256Hasher(sha256.DefaultSHA256HashingFn),
|
||||
)
|
||||
|
||||
for _, file := range markdownFiles {
|
||||
filename := file.Name()
|
||||
fullPath := filepath.Join(dirname, filename)
|
||||
|
||||
content, err := os.ReadFile(fullPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
quiz, err := QuizFromMarkdown(string(content))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.memoryStore.CreateQuiz(&client.CreateUpdateQuizRequest{
|
||||
Quiz: quiz,
|
||||
})
|
||||
}
|
||||
|
||||
s.Dir = dirname
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *FileProboCollectorStore) ReadAllQuizzes() ([]*models.Quiz, error) {
|
||||
return s.memoryStore.ReadAllQuizzes()
|
||||
}
|
||||
func (s *FileProboCollectorStore) CreateQuiz(r *client.CreateUpdateQuizRequest) (*models.Quiz, error) {
|
||||
quiz, err := s.memoryStore.CreateQuiz(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = s.writeMarkdownFile(quiz)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return quiz, nil
|
||||
}
|
||||
|
||||
func MarkdownFromQuiz(quiz *models.Quiz) (string, error) {
|
||||
if quiz.Question == nil {
|
||||
return "", errors.New("Quiz should contain a question but it wasn't provided.")
|
||||
}
|
||||
|
||||
if len(quiz.Answers) == 0 {
|
||||
return "", errors.New("Quiz should contain at least 2 answers but none was provided.")
|
||||
}
|
||||
|
||||
if quiz.Correct == nil {
|
||||
return "", errors.New("Quiz should contain a correct answer but non was provided.")
|
||||
}
|
||||
|
||||
correctAnswer := "* " + quiz.Correct.Text
|
||||
var otherAnswers string
|
||||
|
||||
for _, answer := range quiz.Answers {
|
||||
otherAnswers += "* " + answer.Text + "\n"
|
||||
}
|
||||
|
||||
markdown := quiz.Question.Text + "\n\n" + correctAnswer + "\n" + otherAnswers
|
||||
|
||||
return markdown, nil
|
||||
}
|
||||
|
||||
func QuizFromMarkdown(markdown string) (*client.Quiz, error) {
|
||||
lines := strings.Split(markdown, "\n")
|
||||
|
||||
questionText := ""
|
||||
answers := []*client.Answer{}
|
||||
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, "*") {
|
||||
answerText := strings.TrimPrefix(line, "* ")
|
||||
correct := len(answers) == 0
|
||||
answer := &client.Answer{Text: answerText, Correct: correct}
|
||||
answers = append(answers, answer)
|
||||
} else {
|
||||
if questionText != "" {
|
||||
questionText += "\n"
|
||||
}
|
||||
questionText += line
|
||||
}
|
||||
}
|
||||
|
||||
questionText = strings.TrimRight(questionText, "\n")
|
||||
|
||||
if questionText == "" {
|
||||
return nil, fmt.Errorf("Question text should not be empty.")
|
||||
}
|
||||
|
||||
if len(answers) < 2 {
|
||||
return nil, fmt.Errorf("Number of answers should be at least 2 but parsed answers are %d.", len(answers))
|
||||
}
|
||||
|
||||
question := &client.Question{Text: questionText}
|
||||
quiz := &client.Quiz{Question: question, Answers: answers}
|
||||
|
||||
return quiz, nil
|
||||
}
|
||||
|
||||
func (s *FileProboCollectorStore) writeMarkdownFile(quiz *models.Quiz) error {
|
||||
markdown, err := MarkdownFromQuiz(quiz)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filename := filepath.Join(s.Dir, quiz.Hash+".md")
|
||||
err = ioutil.WriteFile(filename, []byte(markdown), 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
114
store/file/file_test.go
Normal file
114
store/file/file_test.go
Normal file
|
@ -0,0 +1,114 @@
|
|||
package file
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"git.andreafazzi.eu/andrea/probo/client"
|
||||
"git.andreafazzi.eu/andrea/probo/models"
|
||||
"github.com/remogatto/prettytest"
|
||||
)
|
||||
|
||||
type testSuite struct {
|
||||
prettytest.Suite
|
||||
}
|
||||
|
||||
func TestRunner(t *testing.T) {
|
||||
prettytest.Run(
|
||||
t,
|
||||
new(testSuite),
|
||||
)
|
||||
}
|
||||
|
||||
func (t *testSuite) TestQuizFromMarkdown() {
|
||||
markdown := `Question text (1).
|
||||
|
||||
Question text (2).
|
||||
|
||||
Question text (3).
|
||||
|
||||
* Answer 1
|
||||
* Answer 2
|
||||
* Answer 3
|
||||
* Answer 4`
|
||||
|
||||
expectedQuiz := &client.Quiz{
|
||||
Question: &client.Question{Text: "Question text (1).\n\nQuestion text (2).\n\nQuestion text (3)."},
|
||||
Answers: []*client.Answer{
|
||||
{Text: "Answer 1", Correct: true},
|
||||
{Text: "Answer 2", Correct: false},
|
||||
{Text: "Answer 3", Correct: false},
|
||||
{Text: "Answer 4", Correct: false},
|
||||
},
|
||||
}
|
||||
|
||||
quiz, err := QuizFromMarkdown(markdown)
|
||||
t.Nil(err, fmt.Sprintf("Quiz should be parsed without errors: %v", err))
|
||||
|
||||
if !t.Failed() {
|
||||
t.True(reflect.DeepEqual(quiz, expectedQuiz), fmt.Sprintf("Expected %+v, got %+v", expectedQuiz, quiz))
|
||||
}
|
||||
}
|
||||
|
||||
func (t *testSuite) TestReadAllQuizzes() {
|
||||
store, err := NewFileProboCollectorStore("./test/quizzes")
|
||||
t.True(err == nil, fmt.Sprintf("A file store should be initialized without problems but an error occurred: %v", err))
|
||||
|
||||
if !t.Failed() {
|
||||
result, err := store.ReadAllQuizzes()
|
||||
|
||||
t.True(err == nil, fmt.Sprintf("Quizzes should be returned without errors: %v", err))
|
||||
|
||||
if !t.Failed() {
|
||||
t.Equal(
|
||||
2,
|
||||
len(result),
|
||||
fmt.Sprintf("The store contains 3 files but only 2 should be parsed (duplicated quiz). Total of parsed quizzes are instead %v", len(result)),
|
||||
)
|
||||
t.Equal("Question text 1.", result[0].Question.Text)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (t *testSuite) TestCreateQuiz() {
|
||||
dirname := "./test/quizzes"
|
||||
store, err := NewFileProboCollectorStore(dirname)
|
||||
|
||||
t.True(err == nil, fmt.Sprintf("A file store should be initialized without problems but an error occurred: %v", err))
|
||||
|
||||
_, err = store.CreateQuiz(
|
||||
&client.CreateUpdateQuizRequest{
|
||||
Quiz: &client.Quiz{
|
||||
Question: &client.Question{Text: "Newly created question text."},
|
||||
Answers: []*client.Answer{
|
||||
{Text: "Answer 1", Correct: true},
|
||||
{Text: "Answer 2", Correct: false},
|
||||
{Text: "Answer 3", Correct: false},
|
||||
{Text: "Answer 4", Correct: false},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
t.Nil(err, fmt.Sprintf("An error was raised when saving the quiz on disk: %v", err))
|
||||
|
||||
newFilename := filepath.Join(
|
||||
dirname,
|
||||
"94ed4e9cdf8e0a75a2c5ce925cb791ebc5977ce1801e12059f58ce4d66c0c7f6.md",
|
||||
)
|
||||
exists, err := os.Stat(newFilename)
|
||||
t.Nil(err, "Stat should not return an error")
|
||||
|
||||
if !t.Failed() {
|
||||
t.True(exists != nil, "The new quiz file was not created.")
|
||||
err := os.Remove(newFilename)
|
||||
t.Nil(err, "Stat should not return an error")
|
||||
}
|
||||
}
|
||||
|
||||
func testsAreEqual(got, want []*models.Quiz) bool {
|
||||
return reflect.DeepEqual(got, want)
|
||||
}
|
5
store/file/test/quizzes/quiz_1.md
Normal file
5
store/file/test/quizzes/quiz_1.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
Question text 1.
|
||||
|
||||
* Answer 1
|
||||
* Answer 2
|
||||
* Answer 3
|
7
store/file/test/quizzes/quiz_2.md
Normal file
7
store/file/test/quizzes/quiz_2.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
Question text 2.
|
||||
|
||||
* Answer 1
|
||||
* Answer 2
|
||||
* Answer 3
|
||||
* Answer 4
|
||||
|
7
store/file/test/quizzes/quiz_3.md
Normal file
7
store/file/test/quizzes/quiz_3.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
Question text 2.
|
||||
|
||||
* Answer 1
|
||||
* Answer 2
|
||||
* Answer 3
|
||||
* Answer 4
|
||||
|
|
@ -90,6 +90,7 @@ func (s *MemoryProboCollectorStore) createQuizFromHash(id string, hash string, q
|
|||
defer s.lock.Unlock()
|
||||
|
||||
quiz.ID = id
|
||||
quiz.Hash = hash
|
||||
|
||||
s.quizzesHashes[hash] = quiz
|
||||
s.quizzes[id] = quiz
|
||||
|
|
Loading…
Reference in a new issue