generated from cms/tibi-docs
Initial commit
This commit is contained in:
12
api/assets/demoassets.yml
Normal file
12
api/assets/demoassets.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
# Ordnerpfade, die über den tibi-server direkt erreichbar seien sollen,
|
||||
# können über den "path" relativ zur "config.yml" definiert werden.
|
||||
# Durch die "name"-Definition werden diese Pfade eindeutig unterschieden.
|
||||
# Für folgende Beispielangaben bildet sich folgende URL:
|
||||
#
|
||||
# TIBI-SERVER-URL/api/v1/_/NAMESPACE/_/assets/_dist_/
|
||||
#
|
||||
# Jeder Zugriff wird intern umgeleitet auf ../frontend/_dist_/
|
||||
# (relativ zur "config.yml").
|
||||
# Es ist ausschließlich ein unbeschränkter Lesezugriff (GET-Methode) möglich.
|
||||
name: _dist_
|
||||
path: ../frontend/_dist_
|
||||
95
api/collections/backups.yml
Normal file
95
api/collections/backups.yml
Normal file
@@ -0,0 +1,95 @@
|
||||
name: backups
|
||||
meta:
|
||||
isBackupcollection: true
|
||||
|
||||
permissions:
|
||||
public:
|
||||
methods:
|
||||
get: false
|
||||
post: false
|
||||
put: false
|
||||
delete: false
|
||||
user:
|
||||
methods:
|
||||
get: true
|
||||
post: false
|
||||
put: false
|
||||
delete: false
|
||||
|
||||
# token als Zusatzsicherung gegen Spam, mehr siehe Hook
|
||||
"token:${PUBLIC_TOKEN}":
|
||||
methods:
|
||||
get: false
|
||||
post: true
|
||||
put: false
|
||||
delete: false
|
||||
|
||||
hooks:
|
||||
post:
|
||||
create:
|
||||
type: javascript
|
||||
file: hooks/backups/post_create.js
|
||||
|
||||
fields:
|
||||
- name: collectionName
|
||||
type: string
|
||||
meta:
|
||||
label: Collection Name
|
||||
|
||||
- name: entryId
|
||||
type: string
|
||||
meta:
|
||||
label: Entry ID
|
||||
|
||||
- name: versionNr
|
||||
type: number
|
||||
meta:
|
||||
label: Version Nr
|
||||
|
||||
- name: manipulatedBy
|
||||
type: string
|
||||
meta:
|
||||
label: Manipulated By
|
||||
|
||||
- name: eventDescription
|
||||
type: string
|
||||
meta:
|
||||
label: Event Description
|
||||
widget: select
|
||||
choices:
|
||||
- id: create
|
||||
name: Create
|
||||
- id: update
|
||||
name: Update
|
||||
- id: delete
|
||||
name: Delete
|
||||
- id: recreate
|
||||
name: Recreate
|
||||
- id: activate
|
||||
name: Activate
|
||||
|
||||
- name: updateLogs
|
||||
type: object[]
|
||||
meta:
|
||||
label: Veränderungen
|
||||
|
||||
subFields:
|
||||
- name: field
|
||||
type: string
|
||||
meta:
|
||||
label: Feldname
|
||||
|
||||
- name: previous
|
||||
type: string
|
||||
meta:
|
||||
label: Vorheriger Wert
|
||||
|
||||
- name: current
|
||||
type: string
|
||||
meta:
|
||||
label: Aktueller Wert
|
||||
|
||||
- name: entry
|
||||
type: object
|
||||
meta:
|
||||
label: Entry
|
||||
227
api/collections/democol.yml
Normal file
227
api/collections/democol.yml
Normal file
@@ -0,0 +1,227 @@
|
||||
# Der Name der Kollektion wird in der Rest-API-URL verwendet, z.B.
|
||||
# /_/demo/democol
|
||||
name: democol
|
||||
|
||||
# Enthält die Kollektion Felder vom Typ "file", so werden die
|
||||
# hochgeladenen Dateien unter dem Ordner abgelegt, der mit
|
||||
# "uploadPath" bestimmt wird.
|
||||
uploadPath: ../media/democol
|
||||
|
||||
# "fields" stellen die Eigentliche Struktur der Kollektion dar.
|
||||
# "fields" ist als Array angelegt um eine Standard-Sortierung
|
||||
# im tibi-admin vorzugeben.
|
||||
fields:
|
||||
# Das Einbinden von Feldern über extra Dateien bietet sich nur
|
||||
# an, wenn das jeweilige Feld mehrfach von dieser oder anderen
|
||||
# Kollektionen verwendet wird.
|
||||
# Auf die möglichen Definitionen wird im Kapitel "fields"
|
||||
# eingegangen.
|
||||
- !include fields/title.yml
|
||||
- !include fields/type.yml
|
||||
- !include fields/date.yml
|
||||
- !include fields/content.yml
|
||||
- !include fields/info.yml
|
||||
- !include fields/isEmployed.yml
|
||||
- !include fields/profilePic.yml
|
||||
- !include fields/skills.yml
|
||||
- !include fields/image.yml
|
||||
- !include fields/tags.yml
|
||||
- !include fields/gender.yml
|
||||
- !include fields/emplymentDetails.yml
|
||||
- !include fields/employmentDetails.yml
|
||||
- !include fields/description.yml
|
||||
- !include fields/age.yml
|
||||
- !include fields/additionalData.yml
|
||||
|
||||
# Neben der Definition der Indexe innerhalbd des Feld-Objektes selbst,
|
||||
|
||||
# ist die Index-Definition global für die Kollektion auch hier möglich.
|
||||
# Diese Definition ist z.B. für zusammengesetzte Index-Typen notwendig.
|
||||
# Außerdem sind hier feinere Einstellungen für den Index möglich.
|
||||
|
||||
# Mehr dazu im "indexes" Kapitel
|
||||
indexes:
|
||||
- !include democol/textindex.yml
|
||||
|
||||
# Standardsprache für Text-Index in der Datenbank
|
||||
defaultLanguage: de
|
||||
|
||||
# "hooks" definieren die Algorithmen, die Daten und Abläufe zu bestimmten
|
||||
# HTTP-Methoden und Schritten der API manipulieren können.
|
||||
hooks:
|
||||
# Hooks für die Methode "get"
|
||||
get:
|
||||
# "read"-Schritt wird ausgeführt, bevor die Daten von der Datenbank
|
||||
# gelesen werden.
|
||||
read:
|
||||
# "type" ist derzeit immer "javascript"
|
||||
type: javascript
|
||||
# "file" zeigt auf die Datei mit dem Javascript-Code relativ zum
|
||||
# Ordner der "config.yml" Datei.
|
||||
file: hooks/democol/get_read.js
|
||||
# "return"-Schritt wird ausgeführt, bevor die gelesenen Daten über
|
||||
# HTTP übertragen werden.
|
||||
return:
|
||||
type: javascript
|
||||
file: hooks/democol/get_return.js
|
||||
|
||||
# Hooks für die Methode "post"
|
||||
post:
|
||||
# "bind" wird ausgeführt, bevor die übertragenen Daten in eine
|
||||
# Objekt-Struktur umgewandelt werden.
|
||||
# Der tibi-server erwarten nach diesem Schritt gültige JSON-Daten,
|
||||
# d.h. sollte es möglich gemacht werden, dass andere Daten übertragen
|
||||
# werden, sind diese in diesem Hook abzufangen und zu verarbeiten.
|
||||
bind:
|
||||
type: javascript
|
||||
file: hooks/democol/post_bind.js
|
||||
# "validate" wird ausgeführt, bevor die Daten validiert werden.
|
||||
validate:
|
||||
type: javascript
|
||||
file: hooks/democol/post_validate.js
|
||||
# "create" wird ausgeführt, bevor das Objekt/Dokument in der Datenbank
|
||||
# angelegt wird.
|
||||
create:
|
||||
type: javascript
|
||||
file: hooks/democol/post_create.js
|
||||
# "return" wird ausgeführt, bevor die Serverantwort über HTTP
|
||||
# übertragen wird.
|
||||
return:
|
||||
type: javascript
|
||||
file: hooks/democol/post_return.js
|
||||
|
||||
# Hooks für die Methode "put"
|
||||
put:
|
||||
bind:
|
||||
type: javascript
|
||||
file: hooks/democol/put_bind.js
|
||||
validate:
|
||||
type: javascript
|
||||
file: hooks/democol/put_validate.js
|
||||
# "bind" und "validate" habe die gleiche Bedeutung wie Hooks der
|
||||
# Methode "post".
|
||||
# "update" wird ausgeführt bevor das Objekt in der Datenbank
|
||||
# aktualisiert wird.
|
||||
update:
|
||||
type: javascript
|
||||
file: hooks/democol/put_update.js
|
||||
# "return" wird auch hier vor der Serverantwort ausgeführt.
|
||||
return:
|
||||
type: javascript
|
||||
file: hooks/democol/put_return.js
|
||||
|
||||
# Hooks für die Methode "delete"
|
||||
delete:
|
||||
# Der "delete"-Hook wird vor dem eigentlichen Löschen ausgeführt
|
||||
delete:
|
||||
type: javascript
|
||||
file: hooks/democol/delete_delete.js
|
||||
# "return"-Hook kann ebenso hier die Serverantwort manipulieren
|
||||
return:
|
||||
type: javascript
|
||||
file: hooks/democol/delete_return.js
|
||||
|
||||
# Projektionen der Daten werden via GET-Parameter "projection=..."
|
||||
# referenziert.
|
||||
# "projections" is ein Objekt, dass die Namen der Projektionen
|
||||
# als Key führt.
|
||||
projections:
|
||||
# "list" = Name der Projektion
|
||||
list:
|
||||
# "select" definiert als Keys die Felder, die beim Abruf
|
||||
# dieser Projektion in den Ausgabe-Daten enthalten sind.
|
||||
# Felder werden über die Punkt-Notation referenziert.
|
||||
select:
|
||||
title: 1
|
||||
date: 1
|
||||
# refenziert das "subField" "author" unterhalb von "meta"
|
||||
meta.author: 1
|
||||
details:
|
||||
# Alternativ kann "select" auch Auschlussregeln definieren.
|
||||
# Eine Mischung von Inkludieren und Auschluss ist NICHT
|
||||
# möglich.
|
||||
select:
|
||||
comment: 0
|
||||
full:
|
||||
# Ein leeres "select" Objekt beschränkt die Ausgabe der
|
||||
# Daten nicht und ist Standard, wenn der "projection="
|
||||
# Parameter nicht verwendet wurde.
|
||||
select:
|
||||
|
||||
# Allgeine Zugriffsregeln auf Kollektions-Ebene werden mit dem
|
||||
# "permissions" Objekt festgelegt.
|
||||
permissions:
|
||||
# Unter "public" werden die Zugriffsrechte für die Öffentlichkeit
|
||||
# definiert.
|
||||
public:
|
||||
# "methods" führt die HTTP-Methoden auf, die erlaubt sind
|
||||
methods:
|
||||
# "get: true" bedeutet hier, dass jeder die Daten lesen darf
|
||||
get: true
|
||||
# "post", also Einträge erstellen, "put" = Bearbeiten und
|
||||
# "delete" = löschen darf die Öffentlichkeit nicht.
|
||||
post: false
|
||||
put: false
|
||||
delete: false
|
||||
# Ist "validProjections" definiert, sind auch nur genau die
|
||||
# aufgelisteten Projektionen erlaubt, welche zwingend mit dem
|
||||
# GET-Parameter "projection=..." ausgewählt werden müssen.
|
||||
validProjections:
|
||||
- list
|
||||
- details
|
||||
|
||||
# Der Key "user" steht für ALLE Benutzer die dem Projekt
|
||||
# zugeordnet sind.
|
||||
# D.h. eine feinere Abstufung auf Benutzerebene ist mit dem
|
||||
# Key "user" allein nicht möglich.
|
||||
# Für eine feinere Abstufung können nachgelagerte Hooks
|
||||
# dienen oder die Verwendung von zugeordneten benutzerdefinierten
|
||||
# "permissions" (siehe meta Objekt).
|
||||
user:
|
||||
methods:
|
||||
get: true
|
||||
post: false
|
||||
put: false
|
||||
delete: false
|
||||
# Fehlt "validProjections", sind automatisch alle Projektionen
|
||||
# erlaubt, wobei hier auch der GET-Parameter "projection="
|
||||
# weggelassen werden darf und somit alle Felder in der Ausgabe
|
||||
# zu finden sind.
|
||||
|
||||
# Folgende Brechtigung wird angewandt, wenn der Zugriff über
|
||||
# den GET-Parameter "token=" oder die Header-Anweisung "token: "
|
||||
# angefragt wird.
|
||||
# "token" ist dabei die Markierung, dass es sich um einen Token
|
||||
# handelt und "${TOKEN}" ist der benutzerdefinierte Token selbst.
|
||||
# Dieser wird hier über eine Umgebungsvariable "TOKEN" injiziert,
|
||||
# die in "config.yml.env" definiert werden kann mit "TOKEN=...".
|
||||
token:${TOKEN}:
|
||||
methods:
|
||||
get: true
|
||||
post: true
|
||||
put: true
|
||||
delete: true
|
||||
|
||||
# Alle Berechtigungs-Namen, die nicht "public", "user" oder "token:..."
|
||||
# heißen, sind benutzerdefinierte Berechtigungen, die Benutzern
|
||||
# zugeordnet werden können.
|
||||
# Eine mögliche Auflistung um Vorschläge im tibi-admin anzubieten,
|
||||
# werden im Top-Level meta-Objekt der "config.yml" unter "permissions"
|
||||
# definiert.
|
||||
pages:
|
||||
methods:
|
||||
get: true
|
||||
post: true
|
||||
put: true
|
||||
delete: true
|
||||
|
||||
# "imageFilter" definieren Filter, die Bilder bearbeiten, wie
|
||||
# z.B. Verkleinerung.
|
||||
# Mögliche Angaben werden im seperaten Kapitel behandelt.
|
||||
imageFilter: !include democol/imageFilter.yml
|
||||
|
||||
# Wie auch in der Top-Level-Konfig "config.yml" ist auch hier ein
|
||||
# "meta" Objekt möglich und nötig für die Konfiguration des
|
||||
# tibi-admin.
|
||||
# Mögliche Angaben werden im seperaten Kapitel behandelt.
|
||||
meta: !include democol/meta.yml
|
||||
15
api/collections/democol/cardList.yml
Normal file
15
api/collections/democol/cardList.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
type: cardList
|
||||
mediaQuery: "(min-width:600px)"
|
||||
selectionPriority: 3 #gibt an, wenn mediaQuery passt, mit welcher priorität es default mäßig ausgewählt sein soll, je niedriger, desto wichtiger
|
||||
fields:
|
||||
- source: updateTime
|
||||
label:
|
||||
de: letztes Update
|
||||
en: last update
|
||||
type: date
|
||||
- source: title
|
||||
filter: true
|
||||
- source: date
|
||||
filter: true
|
||||
- source: type
|
||||
filter: true
|
||||
25
api/collections/democol/imageFilter.yml
Normal file
25
api/collections/democol/imageFilter.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
# Der Key des Objektes definiert den Namen des Filters.
|
||||
# Jeder Filter ist eine Liste von Bildmanipulationen, die
|
||||
# nacheinander angewandt werden.
|
||||
# Die manipulierten Bilder werden gecachet. Ein nachträgliches
|
||||
# Anpassen der Filter erfordert also das Löschen der gecachten
|
||||
# Dateien welche sich jeweils neben den original Bilddateien
|
||||
# im "uploadPath" der Kollektion befinden.
|
||||
s:
|
||||
- fit: true
|
||||
height: 300
|
||||
width: 300
|
||||
resampling: lanczos
|
||||
quality: 60
|
||||
m:
|
||||
- fit: true
|
||||
height: 600
|
||||
width: 600
|
||||
resampling: lanczos
|
||||
quality: 60
|
||||
l:
|
||||
- fit: true
|
||||
height: 1200
|
||||
width: 1200
|
||||
resampling: lanczos
|
||||
quality: 60
|
||||
129
api/collections/democol/meta.yml
Normal file
129
api/collections/democol/meta.yml
Normal file
@@ -0,0 +1,129 @@
|
||||
# Ein Label für tibi-admin wird mehrsprachig folgendermaßen definiert
|
||||
label:
|
||||
de: Demo-Kolletion
|
||||
en: Demo-Collection
|
||||
|
||||
# Jede Kolletion kann ein eigenes Icon aus mdijs bekommen.
|
||||
muiIcon: web
|
||||
|
||||
# erlabt gleichzeitigen export von allen einträgen
|
||||
allowExportAll: true
|
||||
|
||||
# backup objekt
|
||||
backup:
|
||||
active: true # backup ist aktiviert
|
||||
collectionName: backups # name der backups collection
|
||||
|
||||
# wenn keine fields gesetzt sind, werden alle Felder der Kollektion angezeigt
|
||||
quickEdit:
|
||||
enabled: true # Standardmäßig ist die Schnellbearbeitung aktiviert
|
||||
fields:
|
||||
- title
|
||||
- age
|
||||
- date
|
||||
|
||||
# Die Standardsortierung bei ersten Aufruf der Kollektion.
|
||||
defaultSort:
|
||||
# Nach welchem Feld soll sortiert werden?
|
||||
field: updatedTime
|
||||
# ASC für aufsteigend oder DESC für absteigend oder MANUALLY um manuell sortieren zu können
|
||||
order: DESC
|
||||
|
||||
# Ist ein Javascript Message-Object-Empfänger implementiert, der empfangene
|
||||
# Daten als Vorschau rendern kann, so ist dieser hier zu definieren.
|
||||
# Implementierungshinweise zu einem Solchen gibt es später.
|
||||
previewUrl: https://demo.testversion.online/preview
|
||||
|
||||
# Aus den definierten "imageFilter"-Angaben kann ein Filter für die
|
||||
# Ausgabe der Thunbnails in der Admin-Ansicht ausgewählt werden.
|
||||
defaultImageFilter: s
|
||||
|
||||
# Jede Kollektion kann über media-Querys mit mehreren Ansichten veknüpft werden.
|
||||
# Mögliche Ansichten und die dazugehörigen CSS-Queries sind hier zu defineren.
|
||||
views:
|
||||
# Natürlich können die Angaben auch ausgelagert und mehrfach verwendet werden.
|
||||
# Die möglichen Angaben werden im Kapitel "views" gezeigt.
|
||||
- !include simpleList.yml
|
||||
- !include table.yml
|
||||
|
||||
# Wird eine Kollektion als eine Gesamtliste schnell unübersichtlich, hild die
|
||||
# Definition von "subNavigation".
|
||||
# Die meisten Angaben sind aus obiger Beschreibung den meta-Objektes bekannt.
|
||||
# Es wird hier nur auf die zusätzlichen Angaben eingegangen.
|
||||
subNavigation:
|
||||
- # Jede Unternavigation braucht einen eindeutigen Namen um diese später
|
||||
# in z.B. Javascript-Code wieder erkennen zu können.
|
||||
name: pages
|
||||
|
||||
# Die Angabe des "label" ist optional. Wird sie nicht angegeben, wird
|
||||
# wird diese Unternavigation nicht in der Navigation angezeigt.
|
||||
# Ohne "label" kann die Unternavigation aber weiterhin für die Referenzierung
|
||||
# z.B. in der Mediathek-Ansicht des ContentBuilders verwendet werden.
|
||||
label:
|
||||
de: Seiten
|
||||
en: Pages
|
||||
|
||||
muiIcon: page-layout-body
|
||||
|
||||
defaultSort:
|
||||
field: titel
|
||||
order: ASC
|
||||
|
||||
# Standardmäßig wird man beim Klick auf einen Eintrag der Kollektion
|
||||
# (z.B. Zeile in der Tabelle) direkt zum Editieren des Datensatzes weitergeleitet.
|
||||
# Möchte man das nicht, so kann hier ein alternatives Verhalten definiert werden.
|
||||
# Mögliche Werte sind:
|
||||
# - "edit" (Standard)
|
||||
# - "view" (Anzeigen des Datensatzes)
|
||||
# - Objekt mit "eval"-Attribut
|
||||
#
|
||||
# Beim Objekt mit "eval"-Attribut wird der Code mit dem Javascript-Kontext für
|
||||
# Kollektionen im tibi-admin ausgeführt. Das Ergebnis kann hierbei wieder "edit"
|
||||
# oder "view" sein.
|
||||
# Außerdem ist es möglich, eine eigene Funktion zu definieren, die den Datensatz
|
||||
# als Parameter erhält. Diese Funktion wird dann für den jweiligen Datensatz
|
||||
# ausgeführt, auf den geklickt wurde. Mehr dazu unter dem Widget "ContentBuilder".
|
||||
defaultCallback: view
|
||||
|
||||
views:
|
||||
- !include simpleList.yml
|
||||
- !include table.yml
|
||||
- !include cardList.yml
|
||||
# Um mehr Übersicht zu bekommen können zum Einen andere "views" und "defaultSort"
|
||||
# genutzt werden. Es kann aber auch eine Einschränkung der Daten über eine
|
||||
# Vorfilterung via "filter" geben. "filter" ist ein Objekt mit MongoDB-Filterangaben.
|
||||
# siehe: https://www.mongodb.com/docs/compass/current/query/filter/
|
||||
filter:
|
||||
type: page
|
||||
|
||||
- name: news
|
||||
label:
|
||||
de: Neuigkeiten
|
||||
en: News
|
||||
muiIcon: newspaper
|
||||
defaultSort:
|
||||
field: date
|
||||
order: DESC
|
||||
defaultCallback: edit
|
||||
views:
|
||||
- !include simpleList.yml
|
||||
- !include table.yml
|
||||
filter:
|
||||
type: news
|
||||
|
||||
# Standardmäßig werden im Formular zu Eingabe der Daten alle Felder von "fields"
|
||||
# untereinander angeordnet.
|
||||
# Um diese Anordnung in Tabs zu strukturieren, ist die Verwendung von "tablist"
|
||||
# vorgesehen.
|
||||
# Die Definition befindet sich in einem gesonderten Kapitel
|
||||
tablist: !include tablist.yml
|
||||
|
||||
# OpenAPI-Spezifikation für die API-Endpunkte der Kollektion
|
||||
openapi:
|
||||
get:
|
||||
summary:
|
||||
en: list all datasets of democol
|
||||
de: listet alle Datensätze der Kollektion democol auf
|
||||
description:
|
||||
en: list all datasets of democol with pagination and/or filtering
|
||||
de: listet alle Datensätze der Kollektion democol mit Paginierung und/oder Filterung
|
||||
19
api/collections/democol/simpleList.yml
Normal file
19
api/collections/democol/simpleList.yml
Normal file
@@ -0,0 +1,19 @@
|
||||
# "type" legt den Typ des Views fest.
|
||||
type: simpleList
|
||||
# Die Auswahl erfolgt über folgende "mediaQuery".
|
||||
mediaQuery: "(min-width:0px)"
|
||||
|
||||
selectionPriority: 2 #gibt an, wenn mediaQuery passt, mit welcher priorität es default mäßig ausgewählt sein soll, je niedriger, desto wichtiger
|
||||
# 3 Blöcke können in der simpleList verwendet werden.
|
||||
# Haupttext "primaryText" und optional 2 weitere Angaben über
|
||||
# "secondaryText" und "tertiaryText".
|
||||
# Die Angabe des jeweiligen Feldes erfolgt als String oder
|
||||
# Objekt mit der "source"-Eigenschaft.
|
||||
# Das Feld selbst wird in Punkt-Notation angegeben.
|
||||
# Die Darstellung selbst ist abhängig von der Feld-Konfiguration
|
||||
# selbst, die unter fields in der Kollektions-Konfiguration
|
||||
# stattfindet.
|
||||
primaryText: title
|
||||
secondaryText:
|
||||
source: date
|
||||
tertiaryText: meta.author
|
||||
15
api/collections/democol/table.yml
Normal file
15
api/collections/democol/table.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
type: table
|
||||
mediaQuery: "(min-width:600px)"
|
||||
selectionPriority: 1 #gibt an, wenn mediaQuery passt, mit welcher priorität es default mäßig ausgewählt sein soll, je niedriger, desto wichtiger
|
||||
columns:
|
||||
- source: updateTime
|
||||
label:
|
||||
de: letztes Update
|
||||
en: last update
|
||||
type: date
|
||||
- source: title
|
||||
filter: true
|
||||
- source: date
|
||||
filter: true
|
||||
- source: type
|
||||
filter: true
|
||||
41
api/collections/democol/tablist.yml
Normal file
41
api/collections/democol/tablist.yml
Normal file
@@ -0,0 +1,41 @@
|
||||
# Hier wird der initial zu öffnende Tab festgelegt.
|
||||
# Ist dieser nicht festgelegt, wird automatisch der erste Tab
|
||||
# aus der "tabs" Liste gewaählt.
|
||||
activeTab: general
|
||||
# "tabs" ist die eigentliche Liste
|
||||
tabs:
|
||||
- # Jeder Tab braucht einen Namen, über den er refereziert
|
||||
# werden kann.
|
||||
name: general
|
||||
# Die übliche Labelangabe kann auch hier mehrpsrachig erfolgen.
|
||||
label:
|
||||
de: Allgemein
|
||||
en: General
|
||||
# Welche Felder dieser Tab anzeigen soll, wird über "subFields"
|
||||
# beschrieben.
|
||||
subFields:
|
||||
- source: type
|
||||
- source: title
|
||||
- source: date
|
||||
- source: additionalData
|
||||
- source: age
|
||||
- source: description
|
||||
- source: paymentValues
|
||||
- source: gender
|
||||
- source: isEmployed
|
||||
- source: profilePic
|
||||
- source: skills
|
||||
- source: image
|
||||
- source: tags
|
||||
- name: content
|
||||
label:
|
||||
de: Inhalt
|
||||
en: Content
|
||||
subFields:
|
||||
- source: content
|
||||
- name: info
|
||||
label:
|
||||
de: Informationen
|
||||
en: Information
|
||||
subFields:
|
||||
- source: info
|
||||
6
api/collections/democol/textindex.yml
Normal file
6
api/collections/democol/textindex.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
name: fulltextindex # Ein eindeutiger Name für den Index. Es ist optional, wird jedoch empfohlen, um den Index später leicht identifizieren zu können.
|
||||
key: # Bestimmt, auf welche Felder der Index angewendet werden soll. Dies kann ein einfacher String sein, wenn der Index nur ein Feld umfasst, oder ein Array von Strings, wenn der Index mehrere Felder umfasst.
|
||||
- $text:$** # definiert einen Volltextindex über alle Felder. Der spezielle Operator $text wird verwendet, um einen Volltextindex zu erstellen, und der Operator $\*\* bezeichnet alle Felder in der Sammlung.
|
||||
background: true # Wenn auf true gesetzt, erzwingt dies, dass der Index eindeutige Werte enthält. Wenn Sie versuchen, einen Eintrag mit einem bereits indizierten Wert hinzuzufügen, wird ein Fehler ausgelöst.
|
||||
unique: false # Wenn auf true gesetzt, erzwingt dies, dass der Index eindeutige Werte enthält. Wenn Sie versuchen, einen Eintrag mit einem bereits indizierten Wert hinzuzufügen, wird ein Fehler ausgelöst.
|
||||
defaultLanguage: german # Wird verwendet, um die Sprache für Textindizes festzulegen. Dies ist wichtig für die Volltextsuche, da verschiedene Sprachen unterschiedliche Tokenisierungs- und Stemmungsregeln haben.
|
||||
76
api/collections/fieldLists/useDefaultArray.yml
Normal file
76
api/collections/fieldLists/useDefaultArray.yml
Normal file
@@ -0,0 +1,76 @@
|
||||
- name: testArray
|
||||
type: string[]
|
||||
meta:
|
||||
label: { de: "Test Array richtext", en: "test array" }
|
||||
widget: richtext
|
||||
useDefaultArray: true
|
||||
|
||||
- name: testArrayString
|
||||
type: string[]
|
||||
meta:
|
||||
label: { de: "Test Array string", en: "test array" }
|
||||
widget: string
|
||||
useDefaultArray: true
|
||||
|
||||
- name: testArrayNumber
|
||||
type: number[]
|
||||
meta:
|
||||
label: { de: "Test Array number", en: "test array" }
|
||||
widget: number
|
||||
useDefaultArray: true
|
||||
|
||||
- name: testArrayBool
|
||||
type: boolean[]
|
||||
meta:
|
||||
label: { de: "Test Array checkbox", en: "test array" }
|
||||
widget: checkbox
|
||||
useDefaultArray: true
|
||||
|
||||
- name: paymentValueObjdefault # Name des Eingabefelds für das erste Tab.
|
||||
type: object[] # Datentyp des Eingabefelds, in diesem Fall ein Array von Objekten.
|
||||
meta:
|
||||
label: Zeilen # Tab-Label.
|
||||
useDefaultArray: true
|
||||
subFields: # Liste der Unterfelder für das Tab.
|
||||
- name: test
|
||||
type: string
|
||||
meta:
|
||||
label: test1
|
||||
- name: test2
|
||||
type: string
|
||||
meta:
|
||||
label: test2
|
||||
- name: paymentValue # Name des ersten Eingabefelds in diesem Tab.
|
||||
type: number # Datentyp des Eingabefelds.
|
||||
meta:
|
||||
label: Überweisungswert1 # Feldlabel.
|
||||
- name: paymentValuee # Name des zweiten Eingabefelds in diesem Tab.
|
||||
type: number # Datentyp des Eingabefelds.
|
||||
meta:
|
||||
label: Überweisungswert2 # Feldlabel.
|
||||
- name: paymentValueObj2 # Name des Eingabefelds für das zweite Tab.
|
||||
type: object[] # Datentyp des Eingabefelds, in diesem Fall ein Array von Objekten.
|
||||
meta:
|
||||
label: Spalten # Tab-Label.
|
||||
direction: horizontal
|
||||
widget: grid
|
||||
metaElements:
|
||||
- test1
|
||||
- test2
|
||||
subFields: # Liste der Unterfelder für das Tab.
|
||||
- name: test
|
||||
type: string
|
||||
meta:
|
||||
label: test1
|
||||
- name: test2
|
||||
type: string
|
||||
meta:
|
||||
label: test2
|
||||
- name: paymentValue # Name des ersten Eingabefelds in diesem Tab.
|
||||
type: number # Datentyp des Eingabefelds.
|
||||
meta:
|
||||
label: Überweisungswert1 # Feldlabel.
|
||||
- name: paymentValuee # Name des zweiten Eingabefelds in diesem Tab.
|
||||
type: number # Datentyp des Eingabefelds.
|
||||
meta:
|
||||
label: Überweisungswert2 # Feldlabel.
|
||||
5
api/collections/fields/additionalData.yml
Normal file
5
api/collections/fields/additionalData.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
name: additionalData # Name des Eingabefelds.
|
||||
type: object # Datentyp des Eingabefelds.
|
||||
meta:
|
||||
label: { de: "Zusätzliche Daten", en: "Additional Data" } # Feldlabel.
|
||||
widget: jsonField # Verwendetes Widget.
|
||||
5
api/collections/fields/age.yml
Normal file
5
api/collections/fields/age.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
name: age # Name des Eingabefelds.
|
||||
type: int # Datentyp des Eingabefelds.
|
||||
meta:
|
||||
label: { de: "Alter", en: "Age" } # Feldlabel.
|
||||
widget: number # Verwendetes Widget.
|
||||
117
api/collections/fields/content.yml
Normal file
117
api/collections/fields/content.yml
Normal file
@@ -0,0 +1,117 @@
|
||||
# Der Name des Feldes ist natürlich beliebig wählbar.
|
||||
name: content
|
||||
|
||||
# "string" als Datentyp ist zwingend.
|
||||
type: string
|
||||
|
||||
meta:
|
||||
label:
|
||||
de: Inhalt
|
||||
en: content
|
||||
|
||||
# Die Bezeichnung des ContentBuilder-Widgets ist "contentbuilder".
|
||||
widget: contentbuilder
|
||||
|
||||
# Die Anzeige des ContentBuilder im tibi-admin geschieht innerhalb
|
||||
# eines iframes. Das ist notwendig, da der ContentBuilder eigene
|
||||
# Styles mitbringet, die sich nicht mit den Styles des tibi-admin
|
||||
# vermischen sollten.
|
||||
|
||||
# Via "baseHref" wird der <base>-Tag im iframe gesetzt.
|
||||
# somit können alle relativen Pfade im ContentBuilder (z.B. Bilder)
|
||||
# aufgelöst werden.
|
||||
# Wie man hier sieht, ist die Angabe via "eval" mittels möglich.
|
||||
# Der Kontext ist auf Feldebene, wie bei "dependsOn" und "defaultValue".
|
||||
# Alternativ kann die Angabe auch direkt als String erfolgend.
|
||||
# Dann natürlich ohne die Evaluierung der Variable "$projectBase", wie hier.
|
||||
baseHref:
|
||||
eval: $projectBase
|
||||
|
||||
# Sollen weitere CSS-Datei in das iframe geladen werden, können diese
|
||||
# hier aufgelistet werden.
|
||||
# Die Angabe kann direkt als Array erfolgen oder via "eval", dessen
|
||||
# Code das Array der Strings mit den Dateipfaden zurückgibt.
|
||||
# Auch zu beachten ist hier die relative Angabe. Da "baseHref" gesetzt
|
||||
# ist, wird der Pfad relativ zu dieser Projekt-Basis innerhalb des
|
||||
# tibi-server aufgelöst.
|
||||
# Die Auslieferung der CSS-Dateien direkt über den tibi-server kann
|
||||
# nur funktionieren, wenn "_dist_" in der "assets" Konfiguration der
|
||||
# "config.yml" definiert ist.
|
||||
cssHref:
|
||||
- _/assets/_dist_/index.css
|
||||
|
||||
# Um eine Kollektion stellvertretend als Mediathek anzubinden, sind die
|
||||
# Angaben unter "imageSelect", "fileSelect" und "videoSelect" zu tätigen.
|
||||
# "imageSelect" betrifft die Einbindung von Bildern, "fileSelect" die
|
||||
# Einbindung von Dateien, "videoSelect" die Einbindung von Videos.
|
||||
imageSelect:
|
||||
|
||||
# Die Angabe "collection" ist zwingend. Hier wird die Kollektion
|
||||
# definiert, die als Sammlung für die Bilder/Datei dient.
|
||||
# Der Aufbau der Kollektion ist dabei frei, solange ein Upload-Feld
|
||||
# für die Dateien existiert, welches die URL zur Datei zurückgibt.
|
||||
collection: medialib
|
||||
|
||||
# Optional kann ein Filter und View für die Einbindung der Bilder/Dateien
|
||||
# definiert werden. Dies geschieht über einen "subNavigation"
|
||||
# Eintrag innerhalb des "meta.subNavigation" Arrays, der Kollektion
|
||||
# (hier bei "medialib").
|
||||
# Die Angabe hier ist die auszuwählende Navigation per Index des Arrays.
|
||||
subNavigation: 0
|
||||
|
||||
fileSelect:
|
||||
collection: medialib
|
||||
subNavigation: 0
|
||||
videoSelect:
|
||||
collection: medialib
|
||||
subNavigation: 0
|
||||
|
||||
# "customTags" des ContentBuilder können verwendet werden um die Einbindung
|
||||
# von Modulen ins HTML zu ermöglichen.
|
||||
# Die folgende Auflistung ist dabei ein Beispiel für ein Modul.
|
||||
customTags:
|
||||
|
||||
- # Der Platzhalter wird 1:1 ins HTML übernommen und ist dabei frei
|
||||
# definierbar.
|
||||
# Die eigentliche Funktion eines Modul-Systems muss dann später
|
||||
# im Frontend implementiert werden.
|
||||
placeholder: "<my-module class='tibi-module' title='Titel' description='Beschreibung'>Mein Modul</my-module>"
|
||||
|
||||
# Die Benennung für die UI des ContentBuilder geschieht über die
|
||||
# "label" Angabe, die mehrsprachig erfolgen kann.
|
||||
label:
|
||||
de: "Mein Modul"
|
||||
en: "My Module"
|
||||
|
||||
# Um direkt Style-Angaben in das iframe des ContentBuilder zu übernehmen,
|
||||
# werden diese hier angegeben.
|
||||
# Natürlich ist auch hier wieder die Angabe via "eval" möglich.
|
||||
# Nachfolgendes Beispiel erzeugt im ContentBuilder eine deutliche Darstellung
|
||||
# des eingebundenen Moduls.
|
||||
style: |
|
||||
/*css*/
|
||||
.is-builder {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.tibi-module {
|
||||
padding: 10px;
|
||||
border: 3px dashed #c4c4c4;
|
||||
display: block;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: black;
|
||||
}
|
||||
.tibi-module::before {
|
||||
content: "\1F5BD ";
|
||||
font-size: 16px;
|
||||
color: black;
|
||||
}
|
||||
.tibi-module::after {
|
||||
content: " title=\"" attr(title) "\" description=\"" attr(description) "\"";
|
||||
font-size: 10px;
|
||||
color: #555;
|
||||
display: block;
|
||||
padding-top: 5px;
|
||||
}
|
||||
/*!css*/
|
||||
57
api/collections/fields/date.yml
Normal file
57
api/collections/fields/date.yml
Normal file
@@ -0,0 +1,57 @@
|
||||
# Der Name des Feldes wird in der Datenbank zum Objekt ebenso
|
||||
# wie in der Ein- und Ausgabe über die API verwendet.
|
||||
name: date
|
||||
|
||||
# Über "type" wird der Datentyp in der Datenbank festgelegt.
|
||||
# Mögliche Typen sind weiter unten aufgelistet.
|
||||
type: date
|
||||
|
||||
# Direkt am Feld kann eine Index-Definition erfolgen.
|
||||
# Folgende mögliche Werte können ihn die Liste aufgenommen werden:
|
||||
# "single" - Standard-Index für diese Feld
|
||||
# "unique" - Das Feld muss einen eindeutigen Wert haben
|
||||
# "text" - Alle "text"-Indexanganben aller Felder werden zu einem
|
||||
# gemeinsamen Volltext-Index kombiniert
|
||||
#
|
||||
# Die Angabe des Volltextindex ist besser unter "collections.X.indexes"
|
||||
# vorzunehmen.
|
||||
index:
|
||||
- single
|
||||
|
||||
# Jede Datenübertragung an des Server wird validiert, d.h. es werden
|
||||
# keine Datentypen angenommen, die nicht zu "type" passen.
|
||||
# Darüber hinaus kann via "validator" eine zusätzliche Validierung
|
||||
# vorgenommen werden.
|
||||
# Dazu gibt es ein extra Kapitel.
|
||||
validator:
|
||||
required: true
|
||||
eval: new Date($this) > new Date()
|
||||
|
||||
# Und natürlich gibt es auch hier ein "meta" Objekt zur Steuerung
|
||||
# des tibi-admin.
|
||||
meta:
|
||||
# Das "label" des Feldes wird als Label vor dem Widget verwendet.
|
||||
label:
|
||||
de: Titel
|
||||
en: title
|
||||
|
||||
# Abgelkeitet vom "type" gibt es Standard-Widgets. für spezielle
|
||||
# Aufgaben stehen aber eine Hand voll Widgets bereit, die später
|
||||
# beschrieben werden.
|
||||
widget: date
|
||||
|
||||
# Standardwerte für neue Enträge können entweder direkt angegeben
|
||||
# werden oder via Javascript client-seitig generiert werden.
|
||||
# In den Kontext injizierte Variablen werden später beschrieben.
|
||||
defaultValue:
|
||||
# Das Ergebnis von "eval" wird hier als Standardwert verwendet.
|
||||
# (hier das aktuelle Datum)
|
||||
eval: new Date()
|
||||
|
||||
# Sollen Felder abhängig von bestimmten Bedingungen ein- oder
|
||||
# ausgeblendet werden, geschieht das über Anweisungen in "dependsOn".
|
||||
dependsOn:
|
||||
# Das Feld wird nur eingeblendet wenn der Wert von "type"
|
||||
# (auf gleicher Ebene wie das Feld "date" selbst)
|
||||
# gleich "news" ist.
|
||||
eval: $parent?.type == "news"
|
||||
5
api/collections/fields/description.yml
Normal file
5
api/collections/fields/description.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
name: description # Name des Eingabefelds.
|
||||
type: string # Datentyp des Eingabefelds.
|
||||
meta:
|
||||
label: { de: "Beschreibung", en: "Description" } # Feldlabel.
|
||||
widget: richtext # Verwendetes Widget.
|
||||
60
api/collections/fields/employmentDetails.yml
Normal file
60
api/collections/fields/employmentDetails.yml
Normal file
@@ -0,0 +1,60 @@
|
||||
name: paymentValueObj
|
||||
type: object[]
|
||||
meta:
|
||||
label: Zeilen
|
||||
widget: grid
|
||||
metaElements:
|
||||
- test1
|
||||
- test2
|
||||
direction: vertical
|
||||
pathStep: #widget spezifisch, gibt dem objekt einen namen und ein zugehöriges icon, Zeilen und spalten sind hier bereits default (Oberste Objekt zeile verschachtelte objekt spalte mit zugehörigen icons)
|
||||
title: "Zeile"
|
||||
icon: viewSequentialOutline
|
||||
folding:
|
||||
previewFolded:
|
||||
eval: $this.test
|
||||
previewUnfolded:
|
||||
eval: $this.test
|
||||
subFields:
|
||||
- name: test
|
||||
type: string
|
||||
meta:
|
||||
label: test1
|
||||
- name: test2
|
||||
type: string
|
||||
meta:
|
||||
label: test2
|
||||
- name: paymentValue
|
||||
type: number
|
||||
meta:
|
||||
label: Überweisungswert1
|
||||
- name: paymentValuee
|
||||
type: number
|
||||
meta:
|
||||
label: Überweisungswert2
|
||||
- name: paymentValueObj2
|
||||
type: object[]
|
||||
meta:
|
||||
label: Spalten
|
||||
direction: horizontal
|
||||
widget: grid
|
||||
metaElements:
|
||||
- test1
|
||||
- test2
|
||||
subFields:
|
||||
- name: test
|
||||
type: string
|
||||
meta:
|
||||
label: test1
|
||||
- name: test2
|
||||
type: string
|
||||
meta:
|
||||
label: test2
|
||||
- name: paymentValue
|
||||
type: number
|
||||
meta:
|
||||
label: Überweisungswert1
|
||||
- name: paymentValuee
|
||||
type: number
|
||||
meta:
|
||||
label: Überweisungswert2
|
||||
32
api/collections/fields/emplymentDetails.yml
Normal file
32
api/collections/fields/emplymentDetails.yml
Normal file
@@ -0,0 +1,32 @@
|
||||
name: paymentValues # Name des Eingabefelds.
|
||||
type: object # Datentyp des Eingabefelds, in diesem Fall ein Objekt.
|
||||
meta:
|
||||
label: "Überweisungswerte" # Feldlabel.
|
||||
widget: tabs # Verwendetes Widget. Die tabs Widget wird zur Organisation von komplexen Eingaben in Tab-Form verwendet.
|
||||
subFields: # Liste der Unterfelder für jedes Tab.
|
||||
- name: paymentValueObj # Name des Eingabefelds für das erste Tab.
|
||||
type: object[] # Datentyp des Eingabefelds, in diesem Fall ein Array von Objekten.
|
||||
meta:
|
||||
label: Überweisungswerte1 # Tab-Label.
|
||||
subFields: # Liste der Unterfelder für das Tab.
|
||||
- name: paymentValue # Name des ersten Eingabefelds in diesem Tab.
|
||||
type: number # Datentyp des Eingabefelds.
|
||||
meta:
|
||||
label: Überweisungswert1 # Feldlabel.
|
||||
- name: paymentValuee # Name des zweiten Eingabefelds in diesem Tab.
|
||||
type: number # Datentyp des Eingabefelds.
|
||||
meta:
|
||||
label: Überweisungswert2 # Feldlabel.
|
||||
- name: paymentValueObj2 # Name des Eingabefelds für das zweite Tab.
|
||||
type: object[] # Datentyp des Eingabefelds, in diesem Fall ein Array von Objekten.
|
||||
meta:
|
||||
label: Überweisungswerte2 # Tab-Label.
|
||||
subFields: # Liste der Unterfelder für das Tab.
|
||||
- name: paymentValue # Name des ersten Eingabefelds in diesem Tab.
|
||||
type: number # Datentyp des Eingabefelds.
|
||||
meta:
|
||||
label: Überweisungswert1 # Feldlabel.
|
||||
- name: paymentValuee # Name des zweiten Eingabefelds in diesem Tab.
|
||||
type: number # Datentyp des Eingabefelds.
|
||||
meta:
|
||||
label: Überweisungswert2 # Feldlabel.
|
||||
23
api/collections/fields/gender.yml
Normal file
23
api/collections/fields/gender.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
name: gender # Name des Eingabefelds.
|
||||
type: string # Datentyp des Eingabefelds.
|
||||
meta:
|
||||
label: { de: "Geschlecht", en: "Gender" } # Feldlabel.
|
||||
widget: select # Verwendetes Widget.
|
||||
choices: # Auswahlmöglichkeiten.
|
||||
- name: "männlich" # Anzeigename der Auswahl.
|
||||
id: "male" # Wert der Auswahl.
|
||||
- name: "weiblich" # Anzeigename der Auswahl.
|
||||
id: "female" # Wert der Auswahl.
|
||||
chipStyle:
|
||||
backgroundImage: "linear-gradient(black 33.3%, red 33.3%, red 66.6%, gold 66.6%);"
|
||||
color: white
|
||||
textShadow: 0px 0px 4px black
|
||||
#alternative:
|
||||
#choices:
|
||||
#DEPRECATED - FOREIGNKEY STATTDESSEN!
|
||||
#endpoint: content
|
||||
#params:
|
||||
#sort:
|
||||
#mapping:
|
||||
#id: id
|
||||
#name: path
|
||||
20
api/collections/fields/image.yml
Normal file
20
api/collections/fields/image.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
name: image # Name des Eingabefelds.
|
||||
type: string # Datentyp des Eingabefelds.
|
||||
meta:
|
||||
label: { de: "Bild", en: "Image" } # Feldlabel.
|
||||
widget: foreignKey # Verwendetes Widget.
|
||||
foreign:
|
||||
collection: medialib # Name der Sammlung, in der die ausgewählten Daten gespeichert sind.
|
||||
id: id # Feldname, das als eindeutige Kennung für die ausgewählten Daten verwendet wird.
|
||||
subNavigation: 1 # Bestimmt, welche Navigation für die Auswahl der ausgewählten Daten angezeigt wird.
|
||||
#projection: xyz
|
||||
#sort: "title"
|
||||
render:
|
||||
#alternativ zu raw und eval kann hier auch das attribut "defaultCollectionViews" auf true gesetzt werden, dabei werden dann die ausgewählten elemente in der in collection definierten collectionview angezeigt.
|
||||
raw: true
|
||||
eval: |
|
||||
(function() {
|
||||
var out = "";
|
||||
out += "<div style=\"color: #999;\">" + $foreignEntry.title + "</div>";
|
||||
return out;
|
||||
})()
|
||||
51
api/collections/fields/info.yml
Normal file
51
api/collections/fields/info.yml
Normal file
@@ -0,0 +1,51 @@
|
||||
name: info
|
||||
type: object
|
||||
meta:
|
||||
label:
|
||||
de: Info
|
||||
en: Info
|
||||
direction: horizontal # vertical oder horizontal
|
||||
metaElements: # macht bestimmte Widgets im Zahnrad verfügbar und entfernt sie aus dem drop down
|
||||
# alternaitv wären auch diese Angabenformen anstatt von tablist möglich:
|
||||
#- author
|
||||
#- source: author
|
||||
tablist: # macht im Modal eine tabliste
|
||||
tabs:
|
||||
- name: authorInfos
|
||||
label: Autorkram
|
||||
subFields:
|
||||
- source: author
|
||||
- name: publishingInfos
|
||||
label: Veröffentlichungskram
|
||||
subFields:
|
||||
- source: published
|
||||
subFields:
|
||||
- name: author
|
||||
type: string
|
||||
meta:
|
||||
label:
|
||||
de: Autor
|
||||
en: Author
|
||||
- name: published
|
||||
type: date
|
||||
meta:
|
||||
label: Veröffentlicht
|
||||
- name: tags
|
||||
type: object[]
|
||||
meta:
|
||||
label:
|
||||
de: Tags
|
||||
en: Tags
|
||||
subFields:
|
||||
- name: name
|
||||
type: string
|
||||
meta:
|
||||
label:
|
||||
de: Name
|
||||
en: Name
|
||||
- name: color
|
||||
type: string
|
||||
meta:
|
||||
label:
|
||||
de: Farbe
|
||||
en: Color
|
||||
5
api/collections/fields/isEmployed.yml
Normal file
5
api/collections/fields/isEmployed.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
name: isEmployed # Name des Eingabefelds.
|
||||
type: boolean # Datentyp des Eingabefelds.
|
||||
meta:
|
||||
label: { de: "Angestellt?", en: "Employed?" } # Feldlabel.
|
||||
widget: checkbox # Verwendetes Widget.
|
||||
5
api/collections/fields/profilePic.yml
Normal file
5
api/collections/fields/profilePic.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
name: profilePic # Name des Eingabefelds.
|
||||
type: file # Datentyp des Eingabefelds.
|
||||
meta:
|
||||
label: { de: "Profilbild", en: "Profile Picture" } # Feldlabel.
|
||||
widget: file # Verwendetes Widget.
|
||||
10
api/collections/fields/skills.yml
Normal file
10
api/collections/fields/skills.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
name: skills # Name des Eingabefelds.
|
||||
type: string[] # Datentyp des Eingabefelds.
|
||||
meta:
|
||||
label: { de: "Fähigkeiten", en: "Skills" } # Feldlabel.
|
||||
widget: checkboxArray # Verwendetes Widget.
|
||||
choices: # Auswahlmöglichkeiten.
|
||||
- name: "Kochen" # Anzeigename der Auswahl.
|
||||
id: "cooking" # Wert der Auswahl.
|
||||
- name: "Backen" # Anzeigename der Auswahl.
|
||||
id: "baking" # Wert der Auswahl.
|
||||
11
api/collections/fields/tags.yml
Normal file
11
api/collections/fields/tags.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
name: tags # Name des Eingabefelds.
|
||||
type: string[] # Datentyp des Eingabefelds.
|
||||
meta:
|
||||
label: { de: "Tags", en: "Tags" } # Feldlabel.
|
||||
widget: chipArray # Verwendetes Widget.
|
||||
choices: # Auswahlmöglichkeiten.
|
||||
- name: "Tech" # Anzeigename der Auswahl.
|
||||
id: "tech" # Wert der Auswahl.
|
||||
- name: "Wissenschaft" # Anzeigename der Auswahl.
|
||||
id: "science" # Wert der Auswahl.
|
||||
autocomplete: true # Option für Autovervollständigung.
|
||||
11
api/collections/fields/title.yml
Normal file
11
api/collections/fields/title.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
name: title
|
||||
type: string
|
||||
meta:
|
||||
label:
|
||||
de: Titel
|
||||
en: Title
|
||||
inputProps:
|
||||
multiline: true
|
||||
placeholder: Ihr Titel
|
||||
openapi:
|
||||
example: Demo Titel
|
||||
16
api/collections/fields/type.yml
Normal file
16
api/collections/fields/type.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
name: type
|
||||
type: string
|
||||
meta:
|
||||
label:
|
||||
de: Typ
|
||||
en: Type
|
||||
widget: select
|
||||
choices:
|
||||
- name:
|
||||
de: Standardseite
|
||||
en: Standard page
|
||||
id: page
|
||||
- name:
|
||||
de: News
|
||||
en: News
|
||||
id: news
|
||||
233
api/collections/medialib.yml
Normal file
233
api/collections/medialib.yml
Normal file
@@ -0,0 +1,233 @@
|
||||
# Der Name der Kollektion ist beliebig, aber wird in unserem
|
||||
# Beispiel vom ContentBuilder als "medialib" referenziert.
|
||||
name: medialib
|
||||
uploadPath: ../media/medialib
|
||||
|
||||
meta:
|
||||
label:
|
||||
de: Medienbibliothek
|
||||
en: Media Library
|
||||
muiIcon: multimedia
|
||||
defaultSort:
|
||||
field: sort
|
||||
order: MANUALLY #alternativ auch ASC und DESC möglich
|
||||
|
||||
# "defaultImageFilter" dient auch hier nur zur Reduzierung der
|
||||
# Bildgröße bei der Anzeige im tibi-admin (Listen).
|
||||
# Die Bildgröße für die Einbindung ins erzeugte HTML des ContentBuilder
|
||||
# hat hiermit nix zu tun.
|
||||
defaultImageFilter: s
|
||||
|
||||
multiupload:
|
||||
fields:
|
||||
- source: description #specify wich fields should be editable in Modal, if property isnt set, then all fields will automatically be selected
|
||||
prefilledFields: #specifies wich fields should have a default value, wont be visible to the user, just an informational text for wich fields will recieve an default value.
|
||||
- source: title
|
||||
defaultValue:
|
||||
eval: |
|
||||
(function(){
|
||||
return "Title" + $file.name
|
||||
})()
|
||||
|
||||
# Wird unter "image-/file-/videoSelect" im ContentBuilder Feld kein
|
||||
# "subNavigation" Index definiert, werden auch folgende "views"
|
||||
# verwendet.
|
||||
views:
|
||||
- type: simpleList
|
||||
mediaQuery: "(min-width: 0px)"
|
||||
selectionPriority: 3 #gibt an, wenn mediaQuery passt, mit welcher priorität es default mäßig ausgewählt sein soll, je niedriger, desto wichtiger
|
||||
primaryText:
|
||||
source: path
|
||||
filter: true
|
||||
|
||||
secondaryText:
|
||||
source: title
|
||||
filter: true
|
||||
|
||||
tertiaryText:
|
||||
source: description
|
||||
filter: true
|
||||
|
||||
fileDropArea:
|
||||
label:
|
||||
{
|
||||
de: "Ziehen Sie Dateien per Drag and Drop hierher oder klicken Sie, um Dateien auszuwählen.",
|
||||
en: "Drag and drop some files here, or click to upload.",
|
||||
}
|
||||
helperText: { de: "Maximale Uploadgröße: 1,54 MB", en: "Maximum upload size: 1.54MB" }
|
||||
targetField: file
|
||||
pageAsDropArea: true
|
||||
columns:
|
||||
- source: file
|
||||
- source: updateTime
|
||||
type: datetime
|
||||
label: letztes Update
|
||||
- source: title
|
||||
filter: true
|
||||
- source: description
|
||||
filter: true
|
||||
- type: table
|
||||
mediaQuery: "(min-width: 768px)"
|
||||
selectionPriority: 2
|
||||
fileDropArea:
|
||||
label:
|
||||
{
|
||||
de: "Ziehen Sie Dateien per Drag and Drop hierher oder klicken Sie, um Dateien auszuwählen.",
|
||||
en: "Drag and drop some files here, or click to upload.",
|
||||
}
|
||||
helperText: { de: "Maximale Uploadgröße: 1,54 MB", en: "Maximum upload size: 1.54MB" }
|
||||
targetField: file
|
||||
pageAsDropArea: true
|
||||
columns:
|
||||
- source: file
|
||||
- source: updateTime
|
||||
type: datetime
|
||||
label: letztes Update
|
||||
- source: title
|
||||
filter: true
|
||||
- source: description
|
||||
filter: true
|
||||
|
||||
- type: cardList
|
||||
mediaQuery: "(min-width: 1200px)"
|
||||
selectionPriority: 1
|
||||
fileDropArea:
|
||||
label:
|
||||
{
|
||||
de: "Ziehen Sie Dateien per Drag and Drop hierher oder klicken Sie, um Dateien auszuwählen.",
|
||||
en: "Drag and drop some files here, or click to upload.",
|
||||
}
|
||||
helperText: { de: "Maximale Uploadgröße: 1,54 MB", en: "Maximum upload size: 1.54MB" }
|
||||
targetField: file
|
||||
pageAsDropArea: false
|
||||
|
||||
fields:
|
||||
- source: file
|
||||
- source: updateTime
|
||||
type: datetime
|
||||
label: letztes Update
|
||||
- source: title
|
||||
filter: true
|
||||
- source: description
|
||||
filter: true
|
||||
|
||||
# Wird ein "subNavigation" Index für "image-/file-/videoSelect" definiert,
|
||||
# wird die entsprechende Navigation aus folgender Liste angesprochen.
|
||||
# "0" ist dabei der Index für das erste Element dieser Liste.
|
||||
subNavigation:
|
||||
- # Der "name" der Navigation ist für die Mediathek nicht von Bedeutung,
|
||||
# kann aber für "eval"-Code interessant sein.
|
||||
name: modal
|
||||
|
||||
# Auf "label" wurde hier verzichtet, damit dieses Element nicht in der
|
||||
# Hauptnavigation des tibi-admin auftaucht.
|
||||
|
||||
# Folgende Ansicht wird für unsere Auswahl der Datei im ContentBuilder
|
||||
# angeboten.
|
||||
views:
|
||||
- type: cards
|
||||
mediaQuery: "(min-width: 0px)"
|
||||
fields:
|
||||
- source: file
|
||||
- source: updateTime
|
||||
type: datetime
|
||||
label: letztes Update
|
||||
- source: title
|
||||
filter: true
|
||||
- source: description
|
||||
filter: true
|
||||
|
||||
# Damit der ContentBuilder weiß, welche Datei ausgewählt wurde, ist
|
||||
# ist folgender "defaultCallback" notwendig.
|
||||
# Die Funktion wird beim Klick auf die entsprechende Datei aufgerufen.
|
||||
# Als Funktionsparameter steht der gesamte Datensatz der Auswahl zur
|
||||
# Verfügung.
|
||||
# Die Funktionen "parent.selectAsset" und "parent.focus" sind ContentBuilder
|
||||
# spezifisch und schließen die Listenansicht direkt nach Übergabe der
|
||||
# Datei-URL.
|
||||
# Die URL setzt sich aus dem Pfad zur Datei und dem Filter "l" zusammen.
|
||||
# Es wurde eine relative URL konstruiert, da das ContentBuilder-Widget
|
||||
# mit "baseHref" zur Projekt-URL erstellt wird.
|
||||
defaultCallback:
|
||||
eval: |
|
||||
//js
|
||||
(entry) => {
|
||||
parent.selectAsset("medialib/" + entry.id + "/" + entry.file?.src + "?filter=l")
|
||||
parent.focus()
|
||||
}
|
||||
//!js
|
||||
|
||||
- name: modalForeign # Name des Eingabefelds oder der Ansicht.
|
||||
defaultSort: # Standard-Sortierkriterien, die angewendet werden, wenn keine anderen Sortierkriterien spezifiziert sind.
|
||||
field: "path" # Standardmäßig wird nach dem "path"-Feld sortiert.
|
||||
order: "ASC" # Standardmäßig wird in aufsteigender Reihenfolge (ASC) sortiert.
|
||||
views: # Liste der Ansichten, die in diesem Feld angezeigt werden können.
|
||||
- type: table # Es wird eine Tabellenansicht verwendet.
|
||||
columns: # Liste der Spalten, die in der Tabelle angezeigt werden.
|
||||
- path # Es wird nur die Spalte "path" angezeigt.
|
||||
defaultCallback: # Standard-Callback-Funktion, die ausgeführt wird, wenn keine andere spezifiziert ist.
|
||||
eval: | # Der Code wird als JavaScript evaluiert.
|
||||
//js
|
||||
(entry) => { // Diese Funktion nimmt den Eintrag (entry) als Argument.
|
||||
parent.selectEntry(entry) // Die Funktion selectEntry auf dem übergeordneten Objekt wird mit dem Eintrag als Argument aufgerufen.
|
||||
}
|
||||
//!js
|
||||
|
||||
permissions:
|
||||
public:
|
||||
methods:
|
||||
get: true
|
||||
post: false
|
||||
put: false
|
||||
delete: false
|
||||
user:
|
||||
methods:
|
||||
get: true
|
||||
post: true
|
||||
put: true
|
||||
delete: true
|
||||
|
||||
projections:
|
||||
dashboard:
|
||||
select:
|
||||
hooks:
|
||||
delete:
|
||||
return:
|
||||
type: javascript
|
||||
file: hooks/medialib/delete_return.js
|
||||
|
||||
fields:
|
||||
# Ein Feld vom Typ "file" wird für die Mediathek natürlich
|
||||
# benötigt.
|
||||
- name: file
|
||||
type: file
|
||||
meta:
|
||||
label:
|
||||
de: Datei
|
||||
en: File
|
||||
- name: title
|
||||
type: string
|
||||
meta:
|
||||
label:
|
||||
de: Titel
|
||||
en: Title
|
||||
- name: description
|
||||
type: string
|
||||
meta:
|
||||
widget: richtext
|
||||
label:
|
||||
de: Kurzbeschreibung
|
||||
en: Short Description
|
||||
|
||||
- name: sort
|
||||
type: number
|
||||
|
||||
meta:
|
||||
label:
|
||||
de: Manuelle Sortierung
|
||||
en: Manual Sorting
|
||||
inputProps:
|
||||
{ readonly: true, placeholder: { de: "Wert wird automatisch gesetzt", en: "Value is set automatically" } }
|
||||
helperText:
|
||||
de: Dieses Feld wird für die manuelle Sortierung benötigt. Sobald ein Eintrag per Drag&Drop verschoben wurde, wird die neue Position innerhalb der Liste eingetragen.
|
||||
en: This field is required for manual sorting. As soon as an entry is moved using Drag&Drop, the new position is entered in the list.
|
||||
62
api/collections/ssr.yml
Normal file
62
api/collections/ssr.yml
Normal file
@@ -0,0 +1,62 @@
|
||||
########################################################################
|
||||
# SSR Dummy collections
|
||||
########################################################################
|
||||
|
||||
name: ssr
|
||||
meta:
|
||||
label: { de: "SSR Dummy", en: "ssr dummy" }
|
||||
muiIcon: server
|
||||
rowIdentTpl: { twig: "{{ id }}" }
|
||||
views:
|
||||
- type: simpleList
|
||||
mediaQuery: "(max-width: 600px)"
|
||||
primaryText: id
|
||||
secondaryText: insertTime
|
||||
tertiaryText: path
|
||||
- type: table
|
||||
columns:
|
||||
- id
|
||||
- insertTime
|
||||
- path
|
||||
|
||||
permissions:
|
||||
public:
|
||||
methods:
|
||||
get: false
|
||||
post: false
|
||||
put: false
|
||||
delete: false
|
||||
user:
|
||||
methods:
|
||||
get: false
|
||||
post: false
|
||||
put: false
|
||||
delete: false
|
||||
"token:${SSR_TOKEN}":
|
||||
methods:
|
||||
# only via url=
|
||||
get: true
|
||||
post: true
|
||||
put: false
|
||||
delete: false
|
||||
|
||||
hooks:
|
||||
get:
|
||||
read:
|
||||
type: javascript
|
||||
file: hooks/ssr/get_read.js
|
||||
post:
|
||||
bind:
|
||||
type: javascript
|
||||
file: hooks/ssr/post_bind.js
|
||||
|
||||
# we only need hooks
|
||||
fields:
|
||||
- name: path
|
||||
type: string
|
||||
index: [single, unique]
|
||||
- name: content
|
||||
type: string
|
||||
meta:
|
||||
inputProps:
|
||||
multiline: true
|
||||
58
api/config.yml
Normal file
58
api/config.yml
Normal file
@@ -0,0 +1,58 @@
|
||||
# Der Namespace legt die eigentliche Projektbezeichnung und den Datenbankkontext fest.
|
||||
# Er sollte nach Projektinitialisierung auf dem tibi-server nicht mehr angepasst werden.
|
||||
# In den Projekteinstellungen im tibi-server kann der Namespace durch einen Datenbankeintrag
|
||||
# überschrieben werden.
|
||||
# Über die Bezeichnung des Namespace plus einen Prefix der in der globalen Server-Konfig
|
||||
# hinterlegt ist, definiert sich der Datenbank-Name innerhalb der MongoDB.
|
||||
namespace: demo
|
||||
|
||||
# Das "meta"-Objekt ist frei definierbar, wird aber vom tibi-admin in spezieller Form erwartet.
|
||||
# Mögliche Angaben, die der tibi-admin versteht, sind hier mit aufgeführt.
|
||||
meta:
|
||||
# OpenAPI Spezifikationen
|
||||
openapi:
|
||||
#info:
|
||||
# title: Demo API
|
||||
# version: 1.0.0
|
||||
# description: Eine Demo-API für den tibi-server
|
||||
servers:
|
||||
- url: https://tibi-admin-server.code.testversion.online/api/v1/_/demo
|
||||
description: code-server
|
||||
|
||||
# Pfad zu einer Bilddatei die als Projektbild im tibi-admin verwendet wird
|
||||
imageUrl:
|
||||
eval: "$projectBase + '_/assets/img/pic.jpg'"
|
||||
|
||||
# Liste möglicher Berechtigungen, die Benutzern zugeordnet werden können
|
||||
permissions:
|
||||
- # Name der Berechtigung
|
||||
name: news
|
||||
# Label für die Anzeige im Admin
|
||||
# (kann string oder object mit Sprachen als keys sein)
|
||||
label:
|
||||
de: Neuigkeiten
|
||||
en: News
|
||||
|
||||
- name: pages
|
||||
label:
|
||||
de: Seiten
|
||||
en: Pages
|
||||
|
||||
# "collections" ist eine Auflistung von Kollektions-Konfigurationen.
|
||||
# Hier bietet sich eine Auslagerung und Einbindung via YAML-Tag "!include" an.
|
||||
collections:
|
||||
- !include collections/democol.yml
|
||||
- !include collections/medialib.yml
|
||||
# Dummy Kollektion für Hooks, die für serverseitiges Rendering benötigt werden
|
||||
- !include collections/ssr.yml
|
||||
|
||||
# Unter "jobs" können Jobs definiert werden, die regelmäßig ausgeführt werden sollen.
|
||||
jobs:
|
||||
- !include jobs/demojob.yml
|
||||
|
||||
# Werden Dateien innerhalb vom tibi-admin benötigt, bietet es sich an diese über
|
||||
# "assets"-Pfade erreichbar zu machen.
|
||||
assets:
|
||||
- !include assets/demoassets.yml
|
||||
- name: img
|
||||
path: img
|
||||
1
api/config.yml.env
Normal file
1
api/config.yml.env
Normal file
@@ -0,0 +1 @@
|
||||
TOKEN=geheim
|
||||
12
api/hooks/config-client.js
Normal file
12
api/hooks/config-client.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const release = "tibi-docs.dirty"
|
||||
const apiClientBaseURL = "/api/"
|
||||
|
||||
// @ts-ignore
|
||||
if (release && typeof context !== "undefined") {
|
||||
context.response.header("X-Release", release)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
release,
|
||||
apiClientBaseURL,
|
||||
}
|
||||
43
api/hooks/config.js
Normal file
43
api/hooks/config.js
Normal file
@@ -0,0 +1,43 @@
|
||||
const publishedFilter = {
|
||||
$or: [
|
||||
{ publishDate: { $exists: false } },
|
||||
{ publishDate: null },
|
||||
{
|
||||
publishDate: { $lte: { $date: new Date().toISOString() } },
|
||||
// publishDate: { $lte: new Date() },
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const apiSsrBaseURL = "http://localhost:8080/api/v1/_/demo/"
|
||||
|
||||
module.exports = {
|
||||
publishedFilter,
|
||||
apiSsrBaseURL,
|
||||
ssrValidatePath: function (/** @type {string} */ path) {
|
||||
// validate if path ssr rendering is ok, -1 = NOTFOUND, 0 = NO SSR, 1 = SSR
|
||||
// pe. use context.readCollection("product", {filter: {path: path}}) ... to validate dynamic urls
|
||||
|
||||
// // / is de home
|
||||
// if (path == "/") return 1
|
||||
|
||||
// // all other sites are in db
|
||||
// path = path?.replace(/^\//, "")
|
||||
|
||||
// filter for path or alternativePaths
|
||||
const resp = context.db.find("content", {
|
||||
filter: {
|
||||
$and: [{ $or: [{ path }, { "alternativePaths.path": path }] }, publishedFilter],
|
||||
},
|
||||
|
||||
selector: { _id: 1 },
|
||||
})
|
||||
if (resp && resp.length) {
|
||||
return 1
|
||||
}
|
||||
|
||||
// not found
|
||||
return -1
|
||||
},
|
||||
ssrPublishCheckCollections: ["content"],
|
||||
}
|
||||
0
api/hooks/democol/delete_delete.js
Normal file
0
api/hooks/democol/delete_delete.js
Normal file
0
api/hooks/democol/delete_return.js
Normal file
0
api/hooks/democol/delete_return.js
Normal file
0
api/hooks/democol/get_read.js
Normal file
0
api/hooks/democol/get_read.js
Normal file
0
api/hooks/democol/get_return.js
Normal file
0
api/hooks/democol/get_return.js
Normal file
0
api/hooks/democol/post_bind.js
Normal file
0
api/hooks/democol/post_bind.js
Normal file
0
api/hooks/democol/post_create.js
Normal file
0
api/hooks/democol/post_create.js
Normal file
0
api/hooks/democol/post_return.js
Normal file
0
api/hooks/democol/post_return.js
Normal file
0
api/hooks/democol/post_validate.js
Normal file
0
api/hooks/democol/post_validate.js
Normal file
0
api/hooks/democol/put_bind.js
Normal file
0
api/hooks/democol/put_bind.js
Normal file
0
api/hooks/democol/put_return.js
Normal file
0
api/hooks/democol/put_return.js
Normal file
0
api/hooks/democol/put_update.js
Normal file
0
api/hooks/democol/put_update.js
Normal file
0
api/hooks/democol/put_validate.js
Normal file
0
api/hooks/democol/put_validate.js
Normal file
65
api/hooks/lib/ssr-server.js
Normal file
65
api/hooks/lib/ssr-server.js
Normal file
@@ -0,0 +1,65 @@
|
||||
const { apiSsrBaseURL, ssrPublishCheckCollections } = require("../config")
|
||||
|
||||
/**
|
||||
* api request via server, cache result in context.ssrCache
|
||||
* should be elimated in client code via tree shaking
|
||||
*
|
||||
* @param {string} cacheKey
|
||||
* @param {string} endpoint
|
||||
* @param {string} query
|
||||
* @param {ApiOptions} options
|
||||
* @returns {ApiResult<any>}
|
||||
*/
|
||||
function ssrRequest(cacheKey, endpoint, query, options) {
|
||||
let url = endpoint + (query ? "?" + query : "")
|
||||
|
||||
if (ssrPublishCheckCollections.includes(endpoint)) {
|
||||
// @ts-ignore
|
||||
let validUntil = context.ssrCacheValidUntil
|
||||
|
||||
// check in db for publish date to invalidate cache
|
||||
const _optionsPublishSearch = Object.assign(
|
||||
{},
|
||||
{ filter: options?.filter },
|
||||
{
|
||||
selector: { publishDate: 1 },
|
||||
// projection: null,
|
||||
}
|
||||
)
|
||||
const publishSearch = context.db.find(endpoint, _optionsPublishSearch)
|
||||
publishSearch?.forEach((item) => {
|
||||
const publishDate = item.publishDate ? new Date(item.publishDate.unixMilli()) : null
|
||||
|
||||
if (publishDate && publishDate > new Date()) {
|
||||
// entry has a publish date in the future, set global validUntil
|
||||
if (validUntil == null || validUntil > publishDate) {
|
||||
validUntil = publishDate
|
||||
}
|
||||
}
|
||||
})
|
||||
// @ts-ignore
|
||||
context.ssrCacheValidUntil = validUntil
|
||||
}
|
||||
|
||||
// console.log("############ FETCHING ", apiSsrBaseURL + url)
|
||||
|
||||
const response = context.http.fetch(apiSsrBaseURL + url, {
|
||||
method: options.method,
|
||||
headers: options.headers,
|
||||
})
|
||||
|
||||
const json = response.body.json()
|
||||
const count = parseInt(response.headers["x-results-count"] || "0")
|
||||
|
||||
// json is go data structure and incompatible with js, so we need to convert it
|
||||
const r = { data: JSON.parse(JSON.stringify(json)), count: count }
|
||||
|
||||
// @ts-ignore
|
||||
context.ssrCache[cacheKey] = r
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ssrRequest,
|
||||
}
|
||||
169
api/hooks/lib/ssr.js
Normal file
169
api/hooks/lib/ssr.js
Normal file
@@ -0,0 +1,169 @@
|
||||
const { apiClientBaseURL } = require("../config-client")
|
||||
|
||||
/**
|
||||
* convert object to string
|
||||
* @param {any} obj object
|
||||
*/
|
||||
function obj2str(obj) {
|
||||
if (Array.isArray(obj)) {
|
||||
return JSON.stringify(
|
||||
obj.map(function (idx) {
|
||||
return obj2str(idx)
|
||||
})
|
||||
)
|
||||
} else if (typeof obj === "object" && obj !== null) {
|
||||
var elements = Object.keys(obj)
|
||||
.sort()
|
||||
.map(function (key) {
|
||||
var val = obj2str(obj[key])
|
||||
if (val) {
|
||||
return key + ":" + val
|
||||
}
|
||||
})
|
||||
|
||||
var elementsCleaned = []
|
||||
for (var i = 0; i < elements.length; i++) {
|
||||
if (elements[i]) elementsCleaned.push(elements[i])
|
||||
}
|
||||
|
||||
return "{" + elementsCleaned.join("|") + "}"
|
||||
}
|
||||
|
||||
if (obj) return obj
|
||||
}
|
||||
|
||||
// fetch polyfill
|
||||
// [MIT License](LICENSE.md) © [Jason Miller](https://jasonformat.com/)
|
||||
const _f = function (url, options) {
|
||||
if (typeof XMLHttpRequest === "undefined") {
|
||||
return Promise.resolve(null)
|
||||
}
|
||||
|
||||
options = options || {}
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = new XMLHttpRequest()
|
||||
const keys = []
|
||||
const all = []
|
||||
const headers = {}
|
||||
|
||||
const response = () => ({
|
||||
ok: ((request.status / 100) | 0) == 2, // 200-299
|
||||
statusText: request.statusText,
|
||||
status: request.status,
|
||||
url: request.responseURL,
|
||||
text: () => Promise.resolve(request.responseText),
|
||||
json: () => Promise.resolve(request.responseText).then(JSON.parse),
|
||||
blob: () => Promise.resolve(new Blob([request.response])),
|
||||
clone: response,
|
||||
headers: {
|
||||
// @ts-ignore
|
||||
keys: () => keys,
|
||||
// @ts-ignore
|
||||
entries: () => all,
|
||||
get: (n) => headers[n.toLowerCase()],
|
||||
has: (n) => n.toLowerCase() in headers,
|
||||
},
|
||||
})
|
||||
|
||||
request.open(options.method || "get", url, true)
|
||||
|
||||
request.onload = () => {
|
||||
request
|
||||
.getAllResponseHeaders()
|
||||
// @ts-ignore
|
||||
.replace(/^(.*?):[^\S\n]*([\s\S]*?)$/gm, (m, key, value) => {
|
||||
keys.push((key = key.toLowerCase()))
|
||||
all.push([key, value])
|
||||
headers[key] = headers[key] ? `${headers[key]},${value}` : value
|
||||
})
|
||||
resolve(response())
|
||||
}
|
||||
|
||||
request.onerror = reject
|
||||
|
||||
request.withCredentials = options.credentials == "include"
|
||||
|
||||
for (const i in options.headers) {
|
||||
request.setRequestHeader(i, options.headers[i])
|
||||
}
|
||||
|
||||
request.send(options.body || null)
|
||||
})
|
||||
}
|
||||
|
||||
const _fetch = typeof fetch === "undefined" ? (typeof window === "undefined" ? _f : window.fetch || _f) : fetch
|
||||
|
||||
/**
|
||||
* api request via client or server
|
||||
* server function ssrRequest is called via context.ssrRequest, binded in ssr hook
|
||||
*
|
||||
* @param {string} endpoint
|
||||
* @param {ApiOptions} options
|
||||
* @returns {Promise<ApiResult>}
|
||||
*/
|
||||
function apiRequest(endpoint, options) {
|
||||
// first check cache if on client
|
||||
const cacheKey = obj2str({ endpoint: endpoint, options: options })
|
||||
|
||||
// @ts-ignore
|
||||
if (typeof window !== "undefined" && window.__SSR_CACHE__) {
|
||||
// @ts-ignore
|
||||
const cache = window.__SSR_CACHE__[cacheKey]
|
||||
console.log("SSR:", cacheKey, cache)
|
||||
if (cache) {
|
||||
return Promise.resolve(cache)
|
||||
}
|
||||
}
|
||||
|
||||
let method = "GET"
|
||||
|
||||
let query = "&count=1"
|
||||
if (options?.filter) query += "&filter=" + encodeURIComponent(JSON.stringify(options.filter))
|
||||
if (options?.sort) query += "&sort=" + options.sort + "&sort=_id"
|
||||
if (options?.limit) query += "&limit=" + options.limit
|
||||
if (options?.offset) query += "&offset=" + options.offset
|
||||
if (options?.projection) query += "&projection=" + options.projection
|
||||
if (options?.lookup) query += "&lookup=" + options.lookup
|
||||
|
||||
if (options?.params) {
|
||||
Object.keys(options.params).forEach((p) => {
|
||||
query += "&" + p + "=" + encodeURIComponent(options.params[p])
|
||||
})
|
||||
}
|
||||
|
||||
let headers = {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
if (options?.headers) headers = { ...headers, ...options.headers }
|
||||
|
||||
if (typeof window === "undefined") {
|
||||
// server
|
||||
|
||||
// reference via context from get hook to tree shake in client
|
||||
// @ts-ignore
|
||||
const d = context.ssrRequest(cacheKey, endpoint, query, Object.assign({}, options, { method, headers }))
|
||||
return d
|
||||
} else {
|
||||
// client
|
||||
let url = endpoint + (query ? "?" + query : "")
|
||||
console.log("fetch", apiClientBaseURL + url)
|
||||
return _fetch(apiClientBaseURL + url, {
|
||||
method,
|
||||
mode: "cors",
|
||||
headers,
|
||||
}).then((response) => {
|
||||
return response?.json().then((json) => {
|
||||
if (response?.status < 200 || response?.status >= 400) {
|
||||
return Promise.reject({ response, data: json })
|
||||
}
|
||||
return Promise.resolve({ data: json || null, count: response.headers?.get("x-results-count") || 0 })
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
obj2str,
|
||||
apiRequest,
|
||||
}
|
||||
20
api/hooks/lib/utils.js
Normal file
20
api/hooks/lib/utils.js
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
*
|
||||
* @param {any} str
|
||||
*/
|
||||
function log(str) {
|
||||
console.log(JSON.stringify(str, undefined, 4))
|
||||
}
|
||||
|
||||
/**
|
||||
* clear SSR cache
|
||||
*/
|
||||
function clearSSRCache() {
|
||||
var info = context.db.deleteMany("ssr", {})
|
||||
context.response.header("X-SSR-Cleared", info.removed)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
log,
|
||||
clearSSRCache,
|
||||
}
|
||||
18
api/hooks/medialib/delete_return.js
Normal file
18
api/hooks/medialib/delete_return.js
Normal file
@@ -0,0 +1,18 @@
|
||||
;(function () {
|
||||
const request = context.request()
|
||||
//get id of deleted image entry (delete request URL is : .../api/_/demo/medialib/id)
|
||||
const id = request.param("id")
|
||||
|
||||
//find all entrys where there is an image field with the id of this deleted one (image is an foreignKey widget, so stores the id as a string internally)
|
||||
const referencingElements = context.db.find("democol", {
|
||||
filter: {
|
||||
image: id,
|
||||
},
|
||||
})
|
||||
referencingElements.forEach((e) => {
|
||||
//update all those entries to remove the image id from them, since its deleted...
|
||||
context.db.update("democol", e.id, {
|
||||
image: "",
|
||||
})
|
||||
})
|
||||
})()
|
||||
174
api/hooks/ssr/get_read.js
Normal file
174
api/hooks/ssr/get_read.js
Normal file
@@ -0,0 +1,174 @@
|
||||
// TODO: add query string functionality to cache
|
||||
|
||||
const { ssrValidatePath } = require("../config")
|
||||
const { log } = require("../lib/utils")
|
||||
const { ssrRequest } = require("../lib/ssr-server")
|
||||
|
||||
;(function () {
|
||||
/** @type {HookResponse} */
|
||||
// @ts-ignore
|
||||
let response = null
|
||||
|
||||
const request = context.request()
|
||||
let url = request.query("url")
|
||||
const noCache = request.query("noCache")
|
||||
|
||||
// add sentry trace id to head
|
||||
const trace_id = context.debug.sentryTraceId()
|
||||
/**
|
||||
* @param {string} content
|
||||
*/
|
||||
function addSentryTrace(content) {
|
||||
return content.replace("</head>", '<meta name="sentry-trace" content="' + trace_id + '" /></head>')
|
||||
}
|
||||
context.response.header("sentry-trace", trace_id)
|
||||
|
||||
if (url) {
|
||||
// comment will be printed to html later
|
||||
let comment = ""
|
||||
/** @type {Date} */ // @ts-ignore
|
||||
context.ssrCacheValidUntil = null
|
||||
|
||||
url = url.split("?")[0]
|
||||
comment += "url: " + url
|
||||
|
||||
if (url && url.length > 1) {
|
||||
url = url.replace(/\/$/, "")
|
||||
}
|
||||
if (url == "/noindex" || !url) {
|
||||
url = "/" // see .htaccess
|
||||
}
|
||||
|
||||
// check if url is in cache
|
||||
/** @type {Ssr[]} */ // @ts-ignore
|
||||
const cache =
|
||||
!noCache &&
|
||||
context.db.find("ssr", {
|
||||
filter: {
|
||||
path: url,
|
||||
},
|
||||
})
|
||||
if (cache && cache.length) {
|
||||
const validUntil = cache[0].validUntil ? new Date(cache[0].validUntil.unixMilli()) : null
|
||||
// context.debug.dump("cache validUntil", validUntil)
|
||||
if (!validUntil || validUntil > new Date()) {
|
||||
// context.debug.dump("using cache")
|
||||
// use cache
|
||||
context.response.header("X-SSR-Cache", "true")
|
||||
throw {
|
||||
status: 200,
|
||||
log: false,
|
||||
html: addSentryTrace(cache[0].content),
|
||||
}
|
||||
} else {
|
||||
// cache is invalid, delete it
|
||||
context.response.header("X-SSR-Cache", "invalid")
|
||||
// @ts-ignore
|
||||
context.db.delete("ssr", cache[0].id)
|
||||
}
|
||||
}
|
||||
|
||||
// validate url
|
||||
let status = 200
|
||||
|
||||
let pNorender = false
|
||||
let pNotfound = false
|
||||
|
||||
const pR = ssrValidatePath(url)
|
||||
if (pR < 0) {
|
||||
pNotfound = true
|
||||
} else if (!pR) {
|
||||
pNorender = true
|
||||
}
|
||||
|
||||
let head = ""
|
||||
let html = ""
|
||||
let error = ""
|
||||
|
||||
comment += ", path: " + url
|
||||
|
||||
let cacheIt = false
|
||||
if (pNorender) {
|
||||
html = "<!-- NO SSR RENDERING -->"
|
||||
} else if (pNotfound) {
|
||||
status = 404
|
||||
html = "404 NOT FOUND"
|
||||
} else {
|
||||
// @ts-ignore
|
||||
context.ssrCache = {}
|
||||
// @ts-ignore
|
||||
context.ssrRequest = ssrRequest
|
||||
|
||||
// try rendering, if error output plain html
|
||||
try {
|
||||
// include App.svelte and render it
|
||||
// @ts-ignore
|
||||
|
||||
// console.log("####### RENDERING ", url)
|
||||
const app = require("../lib/app.server")
|
||||
const rendered = app.default.render({
|
||||
url: url,
|
||||
})
|
||||
head = rendered.head
|
||||
html = rendered.html
|
||||
|
||||
// add ssrCache to head, cache is built in ssr.js/apiRequest
|
||||
head +=
|
||||
"\n\n" +
|
||||
"<script>window.__SSR_CACHE__ = " +
|
||||
// @ts-ignore
|
||||
JSON.stringify(context.ssrCache) +
|
||||
"</script>"
|
||||
|
||||
// status from webapp
|
||||
// @ts-ignore
|
||||
if (context.is404) {
|
||||
// console.log("########## 404")
|
||||
status = 404
|
||||
} else {
|
||||
cacheIt = true
|
||||
}
|
||||
} catch (/** @type {any} */ e) {
|
||||
// save error for later insert into html
|
||||
log(e.message)
|
||||
log(e.stack)
|
||||
error = "error: " + e.message + "\n\n" + e.stack
|
||||
}
|
||||
}
|
||||
|
||||
// read html template and replace placeholders
|
||||
let tpl = context.fs.readFile("templates/spa.html")
|
||||
tpl = tpl.replace("<!--HEAD-->", head)
|
||||
tpl = tpl.replace("<!--HTML-->", html)
|
||||
tpl = tpl.replace("<!--SSR.ERROR-->", error ? "<!--" + error + "-->" : "")
|
||||
tpl = tpl.replace("<!--SSR.COMMENT-->", comment ? "<!--" + comment + "-->" : "")
|
||||
|
||||
// save cache if adviced
|
||||
if (cacheIt && !noCache) {
|
||||
context.db.create("ssr", {
|
||||
// context.debug.dump("ssr", {
|
||||
path: url,
|
||||
content: tpl,
|
||||
// @ts-ignore
|
||||
validUntil: context.ssrCacheValidUntil,
|
||||
})
|
||||
}
|
||||
|
||||
// return html
|
||||
throw {
|
||||
status: status,
|
||||
log: false,
|
||||
html: addSentryTrace(tpl),
|
||||
}
|
||||
} else {
|
||||
// only admins are allowed to get without url parameter
|
||||
const auth = context.user.auth()
|
||||
if (!auth || auth.role !== 0) {
|
||||
throw {
|
||||
status: 403,
|
||||
message: "invalid auth",
|
||||
auth: auth,
|
||||
}
|
||||
}
|
||||
}
|
||||
})()
|
||||
16
api/hooks/ssr/post_bind.js
Normal file
16
api/hooks/ssr/post_bind.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const utils = require("../lib/utils")
|
||||
|
||||
;(function () {
|
||||
if (context.request().query("clear")) {
|
||||
utils.clearSSRCache()
|
||||
throw {
|
||||
status: 200,
|
||||
message: "cache cleared",
|
||||
}
|
||||
}
|
||||
|
||||
throw {
|
||||
status: 500,
|
||||
message: "ssr is only a dummy collection",
|
||||
}
|
||||
})()
|
||||
BIN
api/img/pic.jpg
Normal file
BIN
api/img/pic.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 133 KiB |
0
api/jobs/demojob.js
Normal file
0
api/jobs/demojob.js
Normal file
14
api/jobs/demojob.yml
Normal file
14
api/jobs/demojob.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
# Jedem Job muss ein "cron" Eintrag zugeordnet werden, der die
|
||||
# Ausführungszeitpunkte definiert.
|
||||
# Das cron-Schema ist dem üblichen Linux cron-Schema nachempfunden.
|
||||
cron: "*/10 * * * *"
|
||||
|
||||
# "type" des Jobs ist derzeit immer "javascript" mit der "file"-Referenz
|
||||
# relativ zur "config.yml".
|
||||
type: javascript
|
||||
file: jobs/demojob.js
|
||||
|
||||
# Es können beliebige "meta"-Daten hinterlegt werden, die im Javascript
|
||||
# des Jobs über "context.job.meta" abgerufen werden können.
|
||||
meta:
|
||||
name: Demo Job
|
||||
0
api/templates/email_body.html.tpl
Normal file
0
api/templates/email_body.html.tpl
Normal file
0
api/templates/email_body.txt.tpl
Normal file
0
api/templates/email_body.txt.tpl
Normal file
0
api/templates/email_subject.txt.tpl
Normal file
0
api/templates/email_subject.txt.tpl
Normal file
Reference in New Issue
Block a user