Nicole Dresselhaus ba3fd2d09e wrong URL -.-
2025-05-09 21:51:23 +02:00

1556 lines
121 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"><head>
<meta charset="utf-8">
<meta name="generator" content="quarto-1.7.23">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<meta name="dcterms.date" content="2020-04-01">
<title>Webapp-Development in Haskell Nicole Dresselhaus</title>
<style>
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
div.columns{display: flex; gap: min(4vw, 1.5em);}
div.column{flex: auto; overflow-x: auto;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
ul.task-list li input[type="checkbox"] {
width: 0.8em;
margin: 0 0.8em 0.2em -1em; /* quarto-specific, see https://github.com/quarto-dev/quarto-cli/issues/4556 */
vertical-align: middle;
}
/* CSS for syntax highlighting */
html { -webkit-text-size-adjust: 100%; }
pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
pre > code.sourceCode > span:empty { height: 1.2em; }
.sourceCode { overflow: visible; }
code.sourceCode > span { color: inherit; text-decoration: inherit; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
pre > code.sourceCode { white-space: pre-wrap; }
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
}
pre.numberSource code
{ counter-reset: source-line 0; }
pre.numberSource code > span
{ position: relative; left: -4em; counter-increment: source-line; }
pre.numberSource code > span > a:first-child::before
{ content: counter(source-line);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
}
pre.numberSource { margin-left: 3em; padding-left: 4px; }
div.sourceCode
{ }
@media screen {
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
}
</style>
<script src="../../../site_libs/quarto-nav/quarto-nav.js"></script>
<script src="../../../site_libs/quarto-nav/headroom.min.js"></script>
<script src="../../../site_libs/clipboard/clipboard.min.js"></script>
<script src="../../../site_libs/quarto-search/autocomplete.umd.js"></script>
<script src="../../../site_libs/quarto-search/fuse.min.js"></script>
<script src="../../../site_libs/quarto-search/quarto-search.js"></script>
<meta name="quarto:offset" content="../../../">
<script src="../../../site_libs/quarto-html/quarto.js" type="module"></script>
<script src="../../../site_libs/quarto-html/tabsets/tabsets.js" type="module"></script>
<script src="../../../site_libs/quarto-html/popper.min.js"></script>
<script src="../../../site_libs/quarto-html/tippy.umd.min.js"></script>
<script src="../../../site_libs/quarto-html/anchor.min.js"></script>
<link href="../../../site_libs/quarto-html/tippy.css" rel="stylesheet">
<link href="../../../site_libs/quarto-html/quarto-syntax-highlighting-dark-2c84ecb840a13f4c7993f9e5648f0c14.css" rel="stylesheet" class="quarto-color-scheme quarto-color-alternate" id="quarto-text-highlighting-styles">
<link href="../../../site_libs/quarto-html/quarto-syntax-highlighting-6cf5824034cebd0380a5b9c74c43f006.css" rel="stylesheet" class="quarto-color-scheme" id="quarto-text-highlighting-styles">
<script src="../../../site_libs/bootstrap/bootstrap.min.js"></script>
<link href="../../../site_libs/bootstrap/bootstrap-icons.css" rel="stylesheet">
<link href="../../../site_libs/bootstrap/bootstrap-ec71cb1e120c0dd41819aca960e74e38.min.css" rel="stylesheet" append-hash="true" class="quarto-color-scheme" id="quarto-bootstrap" data-mode="light">
<link href="../../../site_libs/bootstrap/bootstrap-dark-6ed95ce66646ab2447a87e45f81c21f3.min.css" rel="stylesheet" append-hash="true" class="quarto-color-scheme quarto-color-alternate" id="quarto-bootstrap" data-mode="dark">
<link href="../../../site_libs/bootstrap/bootstrap-ec71cb1e120c0dd41819aca960e74e38.min.css" rel="stylesheet" append-hash="true" class="quarto-color-scheme-extra" id="quarto-bootstrap" data-mode="light">
<script id="quarto-search-options" type="application/json">{
"location": "navbar",
"copy-button": false,
"collapse-after": 3,
"panel-placement": "end",
"type": "overlay",
"limit": 50,
"keyboard-shortcut": [
"f",
"/",
"s"
],
"show-item-context": false,
"language": {
"search-no-results-text": "No results",
"search-matching-documents-text": "matching documents",
"search-copy-link-title": "Copy link to search",
"search-hide-matches-text": "Hide additional matches",
"search-more-match-text": "more match in this document",
"search-more-matches-text": "more matches in this document",
"search-clear-button-title": "Clear",
"search-text-placeholder": "",
"search-detached-cancel-button-title": "Cancel",
"search-submit-button-title": "Submit",
"search-label": "Search"
}
}</script>
<meta property="og:title" content="Webapp-Development in Haskell Nicole Dresselhaus">
<meta property="og:description" content="Step-by-Step-Anleitung, wie man ein neues Projekt mit einer bereits erprobten Pipeline erstellt.">
<meta property="og:site_name" content="Nicole Dresselhaus">
</head>
<body class="nav-sidebar docked nav-fixed quarto-light"><script id="quarto-html-before-body" type="application/javascript">
const toggleBodyColorMode = (bsSheetEl) => {
const mode = bsSheetEl.getAttribute("data-mode");
const bodyEl = window.document.querySelector("body");
if (mode === "dark") {
bodyEl.classList.add("quarto-dark");
bodyEl.classList.remove("quarto-light");
} else {
bodyEl.classList.add("quarto-light");
bodyEl.classList.remove("quarto-dark");
}
}
const toggleBodyColorPrimary = () => {
const bsSheetEl = window.document.querySelector("link#quarto-bootstrap:not([rel=disabled-stylesheet])");
if (bsSheetEl) {
toggleBodyColorMode(bsSheetEl);
}
}
window.setColorSchemeToggle = (alternate) => {
const toggles = window.document.querySelectorAll('.quarto-color-scheme-toggle');
for (let i=0; i < toggles.length; i++) {
const toggle = toggles[i];
if (toggle) {
if (alternate) {
toggle.classList.add("alternate");
} else {
toggle.classList.remove("alternate");
}
}
}
};
const toggleColorMode = (alternate) => {
// Switch the stylesheets
const primaryStylesheets = window.document.querySelectorAll('link.quarto-color-scheme:not(.quarto-color-alternate)');
const alternateStylesheets = window.document.querySelectorAll('link.quarto-color-scheme.quarto-color-alternate');
manageTransitions('#quarto-margin-sidebar .nav-link', false);
if (alternate) {
// note: dark is layered on light, we don't disable primary!
enableStylesheet(alternateStylesheets);
for (const sheetNode of alternateStylesheets) {
if (sheetNode.id === "quarto-bootstrap") {
toggleBodyColorMode(sheetNode);
}
}
} else {
disableStylesheet(alternateStylesheets);
enableStylesheet(primaryStylesheets)
toggleBodyColorPrimary();
}
manageTransitions('#quarto-margin-sidebar .nav-link', true);
// Switch the toggles
window.setColorSchemeToggle(alternate)
// Hack to workaround the fact that safari doesn't
// properly recolor the scrollbar when toggling (#1455)
if (navigator.userAgent.indexOf('Safari') > 0 && navigator.userAgent.indexOf('Chrome') == -1) {
manageTransitions("body", false);
window.scrollTo(0, 1);
setTimeout(() => {
window.scrollTo(0, 0);
manageTransitions("body", true);
}, 40);
}
}
const disableStylesheet = (stylesheets) => {
for (let i=0; i < stylesheets.length; i++) {
const stylesheet = stylesheets[i];
stylesheet.rel = 'disabled-stylesheet';
}
}
const enableStylesheet = (stylesheets) => {
for (let i=0; i < stylesheets.length; i++) {
const stylesheet = stylesheets[i];
if(stylesheet.rel !== 'stylesheet') { // for Chrome, which will still FOUC without this check
stylesheet.rel = 'stylesheet';
}
}
}
const manageTransitions = (selector, allowTransitions) => {
const els = window.document.querySelectorAll(selector);
for (let i=0; i < els.length; i++) {
const el = els[i];
if (allowTransitions) {
el.classList.remove('notransition');
} else {
el.classList.add('notransition');
}
}
}
const isFileUrl = () => {
return window.location.protocol === 'file:';
}
window.hasAlternateSentinel = () => {
let styleSentinel = getColorSchemeSentinel();
if (styleSentinel !== null) {
return styleSentinel === "alternate";
} else {
return false;
}
}
const setStyleSentinel = (alternate) => {
const value = alternate ? "alternate" : "default";
if (!isFileUrl()) {
window.localStorage.setItem("quarto-color-scheme", value);
} else {
localAlternateSentinel = value;
}
}
const getColorSchemeSentinel = () => {
if (!isFileUrl()) {
const storageValue = window.localStorage.getItem("quarto-color-scheme");
return storageValue != null ? storageValue : localAlternateSentinel;
} else {
return localAlternateSentinel;
}
}
const toggleGiscusIfUsed = (isAlternate, darkModeDefault) => {
const baseTheme = document.querySelector('#giscus-base-theme')?.value ?? 'light';
const alternateTheme = document.querySelector('#giscus-alt-theme')?.value ?? 'dark';
let newTheme = '';
if(darkModeDefault) {
newTheme = isAlternate ? baseTheme : alternateTheme;
} else {
newTheme = isAlternate ? alternateTheme : baseTheme;
}
const changeGiscusTheme = () => {
// From: https://github.com/giscus/giscus/issues/336
const sendMessage = (message) => {
const iframe = document.querySelector('iframe.giscus-frame');
if (!iframe) return;
iframe.contentWindow.postMessage({ giscus: message }, 'https://giscus.app');
}
sendMessage({
setConfig: {
theme: newTheme
}
});
}
const isGiscussLoaded = window.document.querySelector('iframe.giscus-frame') !== null;
if (isGiscussLoaded) {
changeGiscusTheme();
}
};
const queryPrefersDark = window.matchMedia('(prefers-color-scheme: dark)');
const darkModeDefault = queryPrefersDark.matches;
document.querySelector('link.quarto-color-scheme-extra').rel = 'disabled-stylesheet';
let localAlternateSentinel = darkModeDefault ? 'alternate' : 'default';
// Dark / light mode switch
window.quartoToggleColorScheme = () => {
// Read the current dark / light value
let toAlternate = !window.hasAlternateSentinel();
toggleColorMode(toAlternate);
setStyleSentinel(toAlternate);
toggleGiscusIfUsed(toAlternate, darkModeDefault);
};
queryPrefersDark.addEventListener("change", e => {
if(window.localStorage.getItem("quarto-color-scheme") !== null)
return;
const alternate = e.matches
toggleColorMode(alternate);
localAlternateSentinel = e.matches ? 'alternate' : 'default'; // this is used alongside local storage!
toggleGiscusIfUsed(alternate, darkModeDefault);
});
// Switch to dark mode if need be
if (window.hasAlternateSentinel()) {
toggleColorMode(true);
} else {
toggleColorMode(false);
}
</script>
<div id="quarto-search-results"></div>
<header id="quarto-header" class="headroom fixed-top">
<nav class="navbar navbar-expand-lg " data-bs-theme="dark">
<div class="navbar-container container-fluid">
<div class="navbar-brand-container mx-auto">
<a class="navbar-brand" href="../../../index.html">
<span class="navbar-title">Nicole Dresselhaus</span>
</a>
</div>
<div id="quarto-search" class="" title="Search"></div>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" role="menu" aria-expanded="false" aria-label="Toggle navigation" onclick="if (window.quartoToggleHeadroom) { window.quartoToggleHeadroom(); }">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav navbar-nav-scroll me-auto">
<li class="nav-item">
<a class="nav-link" href="../../../index.html"> <i class="bi bi-house" role="img">
</i>
<span class="menu-text">Home</span></a>
</li>
<li class="nav-item">
<a class="nav-link active" href="../../../About/index.html" aria-current="page"> <i class="bi bi-file-person" role="img">
</i>
<span class="menu-text">About</span></a>
</li>
</ul>
<ul class="navbar-nav navbar-nav-scroll ms-auto">
<li class="nav-item compact">
<a class="nav-link" href="../../../index.xml"> <i class="bi bi-rss" role="img">
</i>
<span class="menu-text"></span></a>
</li>
</ul>
</div> <!-- /navcollapse -->
<div class="quarto-navbar-tools">
<a href="" class="quarto-color-scheme-toggle quarto-navigation-tool px-1" onclick="window.quartoToggleColorScheme(); return false;" title="Toggle dark mode"><i class="bi"></i></a>
<a href="" class="quarto-reader-toggle quarto-navigation-tool px-1" onclick="window.quartoToggleReader(); return false;" title="Toggle reader mode">
<div class="quarto-reader-toggle-btn">
<i class="bi"></i>
</div>
</a>
</div>
</div> <!-- /container-fluid -->
</nav>
<nav class="quarto-secondary-nav">
<div class="container-fluid d-flex">
<button type="button" class="quarto-btn-toggle btn" data-bs-toggle="collapse" role="button" data-bs-target=".quarto-sidebar-collapse-item" aria-controls="quarto-sidebar" aria-expanded="false" aria-label="Toggle sidebar navigation" onclick="if (window.quartoToggleHeadroom) { window.quartoToggleHeadroom(); }">
<i class="bi bi-layout-text-sidebar-reverse"></i>
</button>
<nav class="quarto-page-breadcrumbs" aria-label="breadcrumb"><ol class="breadcrumb"><li class="breadcrumb-item">Serious</li><li class="breadcrumb-item">Coding</li><li class="breadcrumb-item"><a href="../../../Coding/Haskell/Advantages.html">Haskell</a></li><li class="breadcrumb-item"><a href="../../../Coding/Haskell/Webapp-Example/index.html">Webapp-Development in Haskell</a></li></ol></nav>
<a class="flex-grow-1" role="navigation" data-bs-toggle="collapse" data-bs-target=".quarto-sidebar-collapse-item" aria-controls="quarto-sidebar" aria-expanded="false" aria-label="Toggle sidebar navigation" onclick="if (window.quartoToggleHeadroom) { window.quartoToggleHeadroom(); }">
</a>
</div>
</nav>
</header>
<!-- content -->
<div id="quarto-content" class="quarto-container page-columns page-rows-contents page-layout-article page-navbar">
<!-- sidebar -->
<nav id="quarto-sidebar" class="sidebar collapse collapse-horizontal quarto-sidebar-collapse-item sidebar-navigation docked overflow-auto">
<div class="sidebar-menu-container">
<ul class="list-unstyled mt-1">
<li class="sidebar-item sidebar-item-section">
<div class="sidebar-item-container">
<a class="sidebar-item-text sidebar-link text-start" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-1" role="navigation" aria-expanded="true">
<span class="menu-text">Serious</span></a>
<a class="sidebar-item-toggle text-start" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-1" role="navigation" aria-expanded="true" aria-label="Toggle section">
<i class="bi bi-chevron-right ms-2"></i>
</a>
</div>
<ul id="quarto-sidebar-section-1" class="collapse list-unstyled sidebar-section depth1 show">
<li class="sidebar-item sidebar-item-section">
<div class="sidebar-item-container">
<a class="sidebar-item-text sidebar-link text-start collapsed" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-2" role="navigation" aria-expanded="false">
<span class="menu-text">Writing</span></a>
<a class="sidebar-item-toggle text-start collapsed" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-2" role="navigation" aria-expanded="false" aria-label="Toggle section">
<i class="bi bi-chevron-right ms-2"></i>
</a>
</div>
<ul id="quarto-sidebar-section-2" class="collapse list-unstyled sidebar-section depth2 ">
<li class="sidebar-item">
<div class="sidebar-item-container">
<a href="../../../Writing/documentation.html" class="sidebar-item-text sidebar-link">
<span class="menu-text">Anforderungskatalog für die Dokumentation von Forschungssoftware (Digital Humanities)</span></a>
</div>
</li>
<li class="sidebar-item">
<div class="sidebar-item-container">
<a href="../../../Writing/ner4all-case-study.html" class="sidebar-item-text sidebar-link">
<span class="menu-text">Case Study: Local LLM-Based NER with n8n and Ollama</span></a>
</div>
</li>
<li class="sidebar-item">
<div class="sidebar-item-container">
<a href="../../../Writing/Obsidian-RAG.html" class="sidebar-item-text sidebar-link">
<span class="menu-text">RAG für eine Obsidian-Wissensdatenbank: Technische Ansätze</span></a>
</div>
</li>
</ul>
</li>
<li class="sidebar-item sidebar-item-section">
<div class="sidebar-item-container">
<a class="sidebar-item-text sidebar-link text-start" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-3" role="navigation" aria-expanded="true">
<span class="menu-text">Coding</span></a>
<a class="sidebar-item-toggle text-start" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-3" role="navigation" aria-expanded="true" aria-label="Toggle section">
<i class="bi bi-chevron-right ms-2"></i>
</a>
</div>
<ul id="quarto-sidebar-section-3" class="collapse list-unstyled sidebar-section depth2 show">
<li class="sidebar-item sidebar-item-section">
<div class="sidebar-item-container">
<a class="sidebar-item-text sidebar-link text-start" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-4" role="navigation" aria-expanded="true">
<span class="menu-text">Haskell</span></a>
<a class="sidebar-item-toggle text-start" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-4" role="navigation" aria-expanded="true" aria-label="Toggle section">
<i class="bi bi-chevron-right ms-2"></i>
</a>
</div>
<ul id="quarto-sidebar-section-4" class="collapse list-unstyled sidebar-section depth3 show">
<li class="sidebar-item">
<div class="sidebar-item-container">
<a href="../../../Coding/Haskell/Advantages.html" class="sidebar-item-text sidebar-link">
<span class="menu-text">Talks und Posts zu Haskell</span></a>
</div>
</li>
<li class="sidebar-item">
<div class="sidebar-item-container">
<a href="../../../Coding/Haskell/FFPiH.html" class="sidebar-item-text sidebar-link">
<span class="menu-text">Fortgeschrittene funktionale Programmierung in Haskell</span></a>
</div>
</li>
<li class="sidebar-item">
<div class="sidebar-item-container">
<a href="../../../Coding/Haskell/Lenses.html" class="sidebar-item-text sidebar-link">
<span class="menu-text">Lenses</span></a>
</div>
</li>
<li class="sidebar-item sidebar-item-section">
<div class="sidebar-item-container">
<a class="sidebar-item-text sidebar-link text-start collapsed" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-5" role="navigation" aria-expanded="false">
<span class="menu-text">Code Snippets</span></a>
<a class="sidebar-item-toggle text-start collapsed" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-5" role="navigation" aria-expanded="false" aria-label="Toggle section">
<i class="bi bi-chevron-right ms-2"></i>
</a>
</div>
<ul id="quarto-sidebar-section-5" class="collapse list-unstyled sidebar-section depth4 ">
<li class="sidebar-item">
<div class="sidebar-item-container">
<a href="../../../Coding/Haskell/Code Snippets/Monoid.html" class="sidebar-item-text sidebar-link">
<span class="menu-text">Monoid? Da war doch was…</span></a>
</div>
</li>
<li class="sidebar-item">
<div class="sidebar-item-container">
<a href="../../../Coding/Haskell/Code Snippets/Morphisms.html" class="sidebar-item-text sidebar-link">
<span class="menu-text">*-Morpisms</span></a>
</div>
</li>
</ul>
</li>
<li class="sidebar-item sidebar-item-section">
<div class="sidebar-item-container">
<a href="../../../Coding/Haskell/Webapp-Example/index.html" class="sidebar-item-text sidebar-link active">
<span class="menu-text">Webapp-Development in Haskell</span></a>
<a class="sidebar-item-toggle text-start" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-6" role="navigation" aria-expanded="true" aria-label="Toggle section">
<i class="bi bi-chevron-right ms-2"></i>
</a>
</div>
<ul id="quarto-sidebar-section-6" class="collapse list-unstyled sidebar-section depth4 show">
<li class="sidebar-item">
<div class="sidebar-item-container">
<a href="../../../Coding/Haskell/Webapp-Example/Main.hs.html" class="sidebar-item-text sidebar-link">
<span class="menu-text">Webapp-Example: Main.hs</span></a>
</div>
</li>
<li class="sidebar-item">
<div class="sidebar-item-container">
<a href="../../../Coding/Haskell/Webapp-Example/MyService_Types.hs.html" class="sidebar-item-text sidebar-link">
<span class="menu-text">Webapp-Example: MyService/Types.hs</span></a>
</div>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li class="sidebar-item sidebar-item-section">
<div class="sidebar-item-container">
<a class="sidebar-item-text sidebar-link text-start collapsed" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-7" role="navigation" aria-expanded="false">
<span class="menu-text">Health</span></a>
<a class="sidebar-item-toggle text-start collapsed" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-7" role="navigation" aria-expanded="false" aria-label="Toggle section">
<i class="bi bi-chevron-right ms-2"></i>
</a>
</div>
<ul id="quarto-sidebar-section-7" class="collapse list-unstyled sidebar-section depth2 ">
<li class="sidebar-item">
<div class="sidebar-item-container">
<a href="../../../Health/Issues.html" class="sidebar-item-text sidebar-link">
<span class="menu-text">Mental Health</span></a>
</div>
</li>
</ul>
</li>
<li class="sidebar-item sidebar-item-section">
<div class="sidebar-item-container">
<a class="sidebar-item-text sidebar-link text-start collapsed" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-8" role="navigation" aria-expanded="false">
<span class="menu-text">Uni</span></a>
<a class="sidebar-item-toggle text-start collapsed" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-8" role="navigation" aria-expanded="false" aria-label="Toggle section">
<i class="bi bi-chevron-right ms-2"></i>
</a>
</div>
<ul id="quarto-sidebar-section-8" class="collapse list-unstyled sidebar-section depth2 ">
<li class="sidebar-item">
<div class="sidebar-item-container">
<a href="../../../Uni/Lernerfolg_an_der_Uni.html" class="sidebar-item-text sidebar-link">
<span class="menu-text">Wie lerne ich richtig an der Uni?</span></a>
</div>
</li>
</ul>
</li>
</ul>
</li>
<li class="px-0"><hr class="sidebar-divider hi "></li>
<li class="sidebar-item sidebar-item-section">
<div class="sidebar-item-container">
<a class="sidebar-item-text sidebar-link text-start" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-9" role="navigation" aria-expanded="true">
<span class="menu-text">Fun</span></a>
<a class="sidebar-item-toggle text-start" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-9" role="navigation" aria-expanded="true" aria-label="Toggle section">
<i class="bi bi-chevron-right ms-2"></i>
</a>
</div>
<ul id="quarto-sidebar-section-9" class="collapse list-unstyled sidebar-section depth1 show">
<li class="sidebar-item sidebar-item-section">
<div class="sidebar-item-container">
<a class="sidebar-item-text sidebar-link text-start collapsed" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-10" role="navigation" aria-expanded="false">
<span class="menu-text">Opinions</span></a>
<a class="sidebar-item-toggle text-start collapsed" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-10" role="navigation" aria-expanded="false" aria-label="Toggle section">
<i class="bi bi-chevron-right ms-2"></i>
</a>
</div>
<ul id="quarto-sidebar-section-10" class="collapse list-unstyled sidebar-section depth2 ">
<li class="sidebar-item">
<div class="sidebar-item-container">
<a href="../../../Opinions/Editors.html" class="sidebar-item-text sidebar-link">
<span class="menu-text">Editors</span></a>
</div>
</li>
<li class="sidebar-item">
<div class="sidebar-item-container">
<a href="../../../Opinions/Keyboard-Layout.html" class="sidebar-item-text sidebar-link">
<span class="menu-text">Keyboard-Layout</span></a>
</div>
</li>
</ul>
</li>
<li class="sidebar-item sidebar-item-section">
<div class="sidebar-item-container">
<a class="sidebar-item-text sidebar-link text-start collapsed" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-11" role="navigation" aria-expanded="false">
<span class="menu-text">Stuff</span></a>
<a class="sidebar-item-toggle text-start collapsed" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-11" role="navigation" aria-expanded="false" aria-label="Toggle section">
<i class="bi bi-chevron-right ms-2"></i>
</a>
</div>
<ul id="quarto-sidebar-section-11" class="collapse list-unstyled sidebar-section depth2 ">
<li class="sidebar-item">
<div class="sidebar-item-container">
<a href="../../../Stuff/Bielefeldverschwoerung.html" class="sidebar-item-text sidebar-link">
<span class="menu-text">Die Bielefeld-Verschwörung</span></a>
</div>
</li>
</ul>
</li>
</ul>
</li>
<li class="px-0"><hr class="sidebar-divider hi "></li>
<li class="sidebar-item sidebar-item-section">
<div class="sidebar-item-container">
<a class="sidebar-item-text sidebar-link text-start" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-12" role="navigation" aria-expanded="true">
<span class="menu-text">Info</span></a>
<a class="sidebar-item-toggle text-start" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-12" role="navigation" aria-expanded="true" aria-label="Toggle section">
<i class="bi bi-chevron-right ms-2"></i>
</a>
</div>
<ul id="quarto-sidebar-section-12" class="collapse list-unstyled sidebar-section depth1 show">
<li class="sidebar-item sidebar-item-section">
<div class="sidebar-item-container">
<a href="../../../About/index.html" class="sidebar-item-text sidebar-link">
<span class="menu-text">About me</span></a>
<a class="sidebar-item-toggle text-start collapsed" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-13" role="navigation" aria-expanded="false" aria-label="Toggle section">
<i class="bi bi-chevron-right ms-2"></i>
</a>
</div>
<ul id="quarto-sidebar-section-13" class="collapse list-unstyled sidebar-section depth2 ">
<li class="sidebar-item">
<div class="sidebar-item-container">
<a href="../../../About/Experience.html" class="sidebar-item-text sidebar-link">
<span class="menu-text">Highlights of my experiences in the programming world</span></a>
</div>
</li>
<li class="sidebar-item">
<div class="sidebar-item-container">
<a href="../../../About/Extracurricular.html" class="sidebar-item-text sidebar-link">
<span class="menu-text">Studium generale / University-Life</span></a>
</div>
</li>
<li class="sidebar-item">
<div class="sidebar-item-container">
<a href="../../../About/Work.html" class="sidebar-item-text sidebar-link">
<span class="menu-text">Work-Experience</span></a>
</div>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
</nav>
<div id="quarto-sidebar-glass" class="quarto-sidebar-collapse-item" data-bs-toggle="collapse" data-bs-target=".quarto-sidebar-collapse-item"></div>
<!-- margin-sidebar -->
<div id="quarto-margin-sidebar" class="sidebar margin-sidebar">
<nav id="TOC" role="doc-toc" class="toc-active">
<h2 id="toc-title">On this page</h2>
<ul>
<li><a href="#definition-der-api" id="toc-definition-der-api" class="nav-link active" data-scroll-target="#definition-der-api">Definition der API</a></li>
<li><a href="#startprojekt-in-haskell" id="toc-startprojekt-in-haskell" class="nav-link" data-scroll-target="#startprojekt-in-haskell">Startprojekt in Haskell</a>
<ul class="collapse">
<li><a href="#erstellen-eines-neuen-projektes" id="toc-erstellen-eines-neuen-projektes" class="nav-link" data-scroll-target="#erstellen-eines-neuen-projektes">Erstellen eines neuen Projektes</a></li>
<li><a href="#generierung-der-api" id="toc-generierung-der-api" class="nav-link" data-scroll-target="#generierung-der-api">Generierung der API</a></li>
<li><a href="#einbinden-anderer-microservices" id="toc-einbinden-anderer-microservices" class="nav-link" data-scroll-target="#einbinden-anderer-microservices">Einbinden anderer Microservices</a></li>
<li><a href="#entfernen-von-anderen-technologienmicroservices" id="toc-entfernen-von-anderen-technologienmicroservices" class="nav-link" data-scroll-target="#entfernen-von-anderen-technologienmicroservices">Entfernen von anderen Technologien/Microservices</a></li>
<li><a href="#woher-weiss-ich-was-wo-liegt-dokumentation-halloo" id="toc-woher-weiss-ich-was-wo-liegt-dokumentation-halloo" class="nav-link" data-scroll-target="#woher-weiss-ich-was-wo-liegt-dokumentation-halloo">Woher weiss ich, was wo liegt? Dokumentation? Halloo??</a></li>
<li><a href="#implementation-des-services-und-start" id="toc-implementation-des-services-und-start" class="nav-link" data-scroll-target="#implementation-des-services-und-start">Implementation des Services und Start</a></li>
<li><a href="#deployment" id="toc-deployment" class="nav-link" data-scroll-target="#deployment">Deployment</a></li>
<li><a href="#omg-ich-muss-meine-api-ändern.-was-mache-ich-nun" id="toc-omg-ich-muss-meine-api-ändern.-was-mache-ich-nun" class="nav-link" data-scroll-target="#omg-ich-muss-meine-api-ändern.-was-mache-ich-nun">OMG! Ich muss meine API ändern. Was mache ich nun?</a></li>
</ul></li>
</ul>
</nav>
</div>
<!-- main -->
<main class="content" id="quarto-document-content">
<header id="title-block-header" class="quarto-title-block default"><nav class="quarto-page-breadcrumbs quarto-title-breadcrumbs d-none d-lg-block" aria-label="breadcrumb"><ol class="breadcrumb"><li class="breadcrumb-item">Serious</li><li class="breadcrumb-item">Coding</li><li class="breadcrumb-item"><a href="../../../Coding/Haskell/Advantages.html">Haskell</a></li><li class="breadcrumb-item"><a href="../../../Coding/Haskell/Webapp-Example/index.html">Webapp-Development in Haskell</a></li></ol></nav>
<div class="quarto-title">
<h1 class="title">Webapp-Development in Haskell</h1>
<div class="quarto-categories">
<div class="quarto-category">Haskell</div>
<div class="quarto-category">Tutorial</div>
</div>
</div>
<div class="quarto-title-meta">
<div>
<div class="quarto-title-meta-heading">Published</div>
<div class="quarto-title-meta-contents">
<p class="date">April 1, 2020</p>
</div>
</div>
</div>
<div>
<div class="abstract">
<div class="block-title">Abstract</div>
<p>Step-by-Step-Anleitung, wie man ein neues Projekt mit einer bereits erprobten Pipeline erstellt.</p>
</div>
</div>
</header>
<section id="definition-der-api" class="level2">
<h2 class="anchored" data-anchor-id="definition-der-api">Definition der API</h2>
<p>Erster Schritt ist immer ein wünsch-dir-was bei der Api-Defenition.</p>
<p>Die meisten Services haben offensichtliche Anforderungen (Schnittstellen nach draußen, Schnittstellen intern, …). Diese kann man immer sehr gut in einem <code>Request -&gt; Response</code>-Model erfassen.</p>
<p>Um die Anforderungen und Möglichkeiten des jeweiligen Services sauber zu erfassen und automatisiert zu prüfen, dummy-implementationen zu bekommen und vieles andere mehr, empfiehlt es sich den <strong>Openapi-generator</strong> zu nutzen.</p>
<p>Diese Definition läuft über openapi-v3 und kann z.b. mit Echtzeit-Vorschau im <a href="http://editor.swagger.io/" class="uri">http://editor.swagger.io/</a> erspielen. Per Default ist der noch auf openapi-v2 (aka swagger), kann aber auch v3.</p>
<p>Nach der Definition, was man am Ende haben möchte, muss man sich entscheiden, in welcher Sprache man weiter entwickelt. Ich empfehle aus verschiedenen Gründen primär 2 Sprachen: Python-Microservices (weil die ML-Libraries sehr gut sind, allerdings Änderungen meist schwer sind und der Code wenig robust - meist nur 1 API-Endpunkt pro service) und Haskell (Stabilität, Performace, leicht zu ändern, gut anzupassen).</p>
<p>Im folgenden wird (aus offensichtlichen Gründen) nur auf das Haskell-Projekt eingegangen.</p>
</section>
<section id="startprojekt-in-haskell" class="level2">
<h2 class="anchored" data-anchor-id="startprojekt-in-haskell">Startprojekt in Haskell</h2>
<section id="erstellen-eines-neuen-projektes" class="level3">
<h3 class="anchored" data-anchor-id="erstellen-eines-neuen-projektes">Erstellen eines neuen Projektes</h3>
<p>Zunächst erstellen wir in normales Haskell-Projekt ohne Funktionalität &amp; Firlefanz:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ex">stack</span> new myservice</span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
<p>Dies erstellt ein neues Verzeichnis und das generelle scaffolding. Nach einer kurzen Anpassung der <code>stack.yaml</code> (resolver auf unserer setzen; aktuell: <code>lts-17.4</code>) fügen wir am Ende der Datei</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode yaml code-with-copy"><code class="sourceCode yaml"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="fu">allow-newer</span><span class="kw">:</span><span class="at"> </span><span class="ch">true</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="fu">ghc-options</span><span class="kw">:</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">"$locals"</span><span class="kw">:</span><span class="at"> -fwrite-ide-info</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
<p>ein. Anschließend organisieren™ wir uns noch eine gute <code>.gitignore</code> und initialisieren das git mittels <code>git init; git add .; git commit -m "initial scaffold"</code></p>
</section>
<section id="generierung-der-api" class="level3">
<h3 class="anchored" data-anchor-id="generierung-der-api">Generierung der API</h3>
<p>Da die API immer wieder neu generiert werden kann (und sollte!) liegt sich in einem unterverzeichnis des Hauptprojektes.</p>
<p>Initial ist es das einfachste ein leeres temporäres Verzeichnis woanders zu erstellen, die <code>api-doc.yml</code> hinein kopieren und folgendes ausführen:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="ex">openapi-generator</span> generate <span class="at">-g</span> haskell <span class="at">-o</span> . <span class="at">-i</span> api-doc.yml</span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
<p>Dieses erstellt einem dann eine komplette library inkl. Datentypen. Wichtig: Der Name in der <code>api-doc</code> sollte vom Namen des Services (oben <code>myservice</code>) abweichen - entweder in Casing oder im Namen direkt. Suffixe wie API schneidet der Generator hier leider ab. (Wieso das ganze? Es entstehen nachher 2 libraries, <code>foo</code> &amp; <code>fooAPI</code>. Da der generator das API abschneidet endet man mit foo &amp; foo und der compiler meckert, dass er nicht weiß, welche lib gemeint ist).</p>
<p>danach: wie gewohnt <code>git init; git add .; git commit -m "initial"</code>. Auf dem Server der Wahl (github, gitea, gitlab, …) nun ein Repository erstellen (am Besten: <code>myserviceAPI</code> - nach Konvention ist alles auf API endend autogeneriert!) und den Anweisungen nach ein remote hinzufügen &amp; pushen.</p>
<section id="wieder-zurück-im-haskell-service" class="level4">
<h4 class="anchored" data-anchor-id="wieder-zurück-im-haskell-service">Wieder zurück im Haskell-Service</h4>
<p>In unserem eigentlichen Service müssen wir nun die API einbinden. Dazu erstellen wir ein Verzeichnis <code>libs</code> (Konvention) und machen ein <code>git submodule add &lt;repository-url&gt; libs/myserviceAPI</code></p>
<p>Git hat nun die API in das submodul gepackt und wir können das oben erstellte temporäre Verzeichnis wieder löschen.</p>
<p>Anschließend müssen wir stack noch erklären, dass wir die API da nun liegen haben und passen wieder die <code>stack.yaml</code> an, indem wir das Verzeichnis unter packages hinzufügen.</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode yaml code-with-copy"><code class="sourceCode yaml"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="fu">packages</span><span class="kw">:</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="kw">-</span><span class="at"> .</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="kw">-</span><span class="at"> libs/myserviceAPI</span><span class="co"> # &lt;&lt;</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
<p>Nun können wir in der <code>package.yaml</code> (oder <code>myservice.cabal</code>, falls kein <code>hpack</code> verwendet wird) unter den dependencies unsere API hinzufügen (name wie die cabal-Datei in <code>libs/myserviceAPI</code>).</p>
</section>
</section>
<section id="einbinden-anderer-microservices" class="level3">
<h3 class="anchored" data-anchor-id="einbinden-anderer-microservices">Einbinden anderer Microservices</h3>
<p>Funktioniert komplett analog zu dem vorgehen oben (ohne das generieren natürlich :grin:). <code>stack.yaml</code> editieren und zu den packages hinzufügen:</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode yaml code-with-copy"><code class="sourceCode yaml"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="fu">packages</span><span class="kw">:</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="kw">-</span><span class="at"> .</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="kw">-</span><span class="at"> libs/myserviceAPI</span></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="kw">-</span><span class="at"> libs/myCoolMLServiceAPI</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
<p>in der <code>package.yaml</code> (oder der cabal) die dependencies hinzufügen und schon haben wir die Features zur Verfügung und können gegen diese Services reden.</p>
</section>
<section id="entfernen-von-anderen-technologienmicroservices" class="level3">
<h3 class="anchored" data-anchor-id="entfernen-von-anderen-technologienmicroservices">Entfernen von anderen Technologien/Microservices</h3>
<p>In git ist das entfernen von Submodules etwas frickelig, daher hier ein copy&amp;paste der <a href="https://gist.github.com/myusuf3/7f645819ded92bda6677">GitHub-Antwort</a>:</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="co">## Remove the submodule entry from .git/config</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a><span class="fu">git</span> submodule deinit <span class="at">-f</span> path/to/submodule</span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a><span class="co">## Remove the submodule directory from the superproject's .git/modules directory</span></span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a><span class="ex">rm-rf</span> .git/modules/path/to/submodule</span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true" tabindex="-1"></a><span class="co">## Remove the entry in .gitmodules and remove the submodule directory located at path/to/submodule</span></span>
<span id="cb6-8"><a href="#cb6-8" aria-hidden="true" tabindex="-1"></a><span class="fu">git</span> rm-f path/to/submodule</span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
<p>Falls das nicht klappt, gibt es alternative Vorschläge unter dem Link oben.</p>
</section>
<section id="woher-weiss-ich-was-wo-liegt-dokumentation-halloo" class="level3">
<h3 class="anchored" data-anchor-id="woher-weiss-ich-was-wo-liegt-dokumentation-halloo">Woher weiss ich, was wo liegt? Dokumentation? Halloo??</h3>
<p>Keine Panik. Ein <code>stack haddock --open</code> hilft da. Das generiert die Dokumentation für alle in der <code>package.yaml</code> (oder cabal-file) eingetragenen dependencies inkl. aller upstream-dependencies. Man bekommt also eine komplette lokale Dokumentation von allem. Geöffnet wird dann die Paket-Startseite inkl. der direkten dependencies:</p>
<p>Es gibt 2 wichtige Pfade im Browser:</p>
<ul>
<li><code>...../all/index.html</code> - hier sind alle Pakete aufgeführt</li>
<li><code>...../index.html</code> - hier sind nur die direkten dependencies aufgeführt.</li>
</ul>
<p>Wenn man einen lokalen Webserver startet kann man mittels “s” auch die interaktive Suche öffnen (Suche nach Typen, Funktionen, Signaturen, etc.). In Bash mit <code>python3</code> geht das z.b. einfach über:</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="bu">cd</span> <span class="va">$(</span><span class="ex">stack</span> path <span class="at">--local-doc-root</span><span class="va">)</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a><span class="ex">python3</span> <span class="at">-m</span> SimpleHTTPServer 8000</span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a><span class="ex">firefox</span> <span class="st">"http://localhost:8000"</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
</section>
<section id="implementation-des-services-und-start" class="level3">
<h3 class="anchored" data-anchor-id="implementation-des-services-und-start">Implementation des Services und Start</h3>
<section id="loaderbootstrapper" class="level4">
<h4 class="anchored" data-anchor-id="loaderbootstrapper">Loader/Bootstrapper</h4>
<p>Generelles Vorgehen:</p>
<ul>
<li><p>in <code>app/Main.hs</code>: Hier ist quasi immer nur eine Zeile drin: <code>main = myServiceMain</code></p>
<p>Grund: Applications tauchen nicht im Haddock auf. Also haben wir ein “src”-Modul, welches hier nur geladen &amp; ausgeführt wird.</p></li>
<li><p>in <code>src/MyService.hs</code>: <code>myServiceMain :: IO ()</code> definieren</p></li>
</ul>
<p>Für die Main kann man prinzipiell eine Main andere Services copy/pasten. Im folgenden eine Annotierte main-Funktion - zu den einzelnen Voraussetzungen kommen wir im Anschluss.</p>
<p></p><p></p><details><summary>Main.hs anzeigen</summary><blockquote class="blockquote"><p></p>
<div class="sourceCode" id="cb8" data-code-fold="true" data-code-summary="Code anzeigen"><pre class="sourceCode haskell code-with-copy"><code class="sourceCode haskell"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# OPTIONS_GHC -Wno-name-shadowing #-}</span></span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE FlexibleContexts #-}</span></span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE LambdaCase #-}</span></span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE OverloadedStrings #-}</span></span>
<span id="cb8-5"><a href="#cb8-5" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE RankNTypes #-}</span></span>
<span id="cb8-6"><a href="#cb8-6" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE RecordWildCards #-}</span></span>
<span id="cb8-7"><a href="#cb8-7" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE ScopedTypeVariables #-}</span></span>
<span id="cb8-8"><a href="#cb8-8" aria-hidden="true" tabindex="-1"></a><span class="kw">module</span> <span class="dt">MyService</span> <span class="kw">where</span></span>
<span id="cb8-9"><a href="#cb8-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-10"><a href="#cb8-10" aria-hidden="true" tabindex="-1"></a><span class="co">-- generische imports aus den dependencies/base, nicht in der prelude</span></span>
<span id="cb8-11"><a href="#cb8-11" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Codec.MIME.Type</span></span>
<span id="cb8-12"><a href="#cb8-12" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Configuration.Dotenv</span> <span class="kw">as</span> <span class="dt">Dotenv</span></span>
<span id="cb8-13"><a href="#cb8-13" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Concurrent</span> (forkIO, threadDelay)</span>
<span id="cb8-14"><a href="#cb8-14" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Concurrent.Async</span></span>
<span id="cb8-15"><a href="#cb8-15" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Concurrent.STM</span></span>
<span id="cb8-16"><a href="#cb8-16" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Monad</span></span>
<span id="cb8-17"><a href="#cb8-17" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Monad.Catch</span></span>
<span id="cb8-18"><a href="#cb8-18" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Monad.Except</span></span>
<span id="cb8-19"><a href="#cb8-19" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Conversion</span></span>
<span id="cb8-20"><a href="#cb8-20" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Conversion.Text</span> ()</span>
<span id="cb8-21"><a href="#cb8-21" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Binary.Builder</span></span>
<span id="cb8-22"><a href="#cb8-22" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.String</span> (<span class="dt">IsString</span> (..))</span>
<span id="cb8-23"><a href="#cb8-23" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Time</span></span>
<span id="cb8-24"><a href="#cb8-24" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Time.Clock</span></span>
<span id="cb8-25"><a href="#cb8-25" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Time.Format</span></span>
<span id="cb8-26"><a href="#cb8-26" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Default</span></span>
<span id="cb8-27"><a href="#cb8-27" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Network.HostName</span></span>
<span id="cb8-28"><a href="#cb8-28" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Network.HTTP.Client</span> <span class="kw">as</span> <span class="dt">HTTP</span> <span class="kw">hiding</span></span>
<span id="cb8-29"><a href="#cb8-29" aria-hidden="true" tabindex="-1"></a> (withConnection)</span>
<span id="cb8-30"><a href="#cb8-30" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Network.HTTP.Types</span> (<span class="dt">Status</span>, statusCode)</span>
<span id="cb8-31"><a href="#cb8-31" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Network.Mom.Stompl.Client.Queue</span></span>
<span id="cb8-32"><a href="#cb8-32" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Network.Wai</span> (<span class="dt">Middleware</span>)</span>
<span id="cb8-33"><a href="#cb8-33" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Network.Wai.Logger</span></span>
<span id="cb8-34"><a href="#cb8-34" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Network.Wai.Middleware.Cors</span></span>
<span id="cb8-35"><a href="#cb8-35" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Network.Wai.Middleware.RequestLogger</span> (<span class="dt">OutputFormat</span> (..),</span>
<span id="cb8-36"><a href="#cb8-36" aria-hidden="true" tabindex="-1"></a> logStdout,</span>
<span id="cb8-37"><a href="#cb8-37" aria-hidden="true" tabindex="-1"></a> mkRequestLogger,</span>
<span id="cb8-38"><a href="#cb8-38" aria-hidden="true" tabindex="-1"></a> outputFormat)</span>
<span id="cb8-39"><a href="#cb8-39" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Servant.Client</span> (mkClientEnv,</span>
<span id="cb8-40"><a href="#cb8-40" aria-hidden="true" tabindex="-1"></a> parseBaseUrl)</span>
<span id="cb8-41"><a href="#cb8-41" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">System.Directory</span></span>
<span id="cb8-42"><a href="#cb8-42" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">System.Envy</span></span>
<span id="cb8-43"><a href="#cb8-43" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">System.IO</span></span>
<span id="cb8-44"><a href="#cb8-44" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">System.Log.FastLogger</span></span>
<span id="cb8-45"><a href="#cb8-45" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Text.PrettyPrint.GenericPretty</span></span>
<span id="cb8-46"><a href="#cb8-46" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-47"><a href="#cb8-47" aria-hidden="true" tabindex="-1"></a><span class="co">-- generische imports, aber qualified, weil es sonst zu name-clashes kommt</span></span>
<span id="cb8-48"><a href="#cb8-48" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-49"><a href="#cb8-49" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">Data.ByteString</span> <span class="kw">as</span> <span class="dt">BS</span></span>
<span id="cb8-50"><a href="#cb8-50" aria-hidden="true" tabindex="-1"></a><span class="co">-- import qualified Data.ByteString.Char8 as BS8</span></span>
<span id="cb8-51"><a href="#cb8-51" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">Data.ByteString.Lazy</span> <span class="kw">as</span> <span class="dt">LBS</span></span>
<span id="cb8-52"><a href="#cb8-52" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">Network.HTTP.Client.TLS</span> <span class="kw">as</span> <span class="dt">UseDefaultHTTPSSettings</span> (tlsManagerSettings)</span>
<span id="cb8-53"><a href="#cb8-53" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">Network.Mom.Stompl.Client.Queue</span> <span class="kw">as</span> <span class="dt">AMQ</span></span>
<span id="cb8-54"><a href="#cb8-54" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">Network.Wai</span> <span class="kw">as</span> <span class="dt">WAI</span></span>
<span id="cb8-55"><a href="#cb8-55" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-56"><a href="#cb8-56" aria-hidden="true" tabindex="-1"></a><span class="co">-- Handler für den MyServiceBackend-Typen und Imports aus den Libraries</span></span>
<span id="cb8-57"><a href="#cb8-57" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">MyService.Handler</span> <span class="kw">as</span> <span class="dt">H</span> <span class="co">-- handler der H.myApiEndpointV1Post implementiert</span></span>
<span id="cb8-58"><a href="#cb8-58" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">MyService.Types</span> <span class="co">-- weitere Type (s. nächste box)</span></span>
<span id="cb8-59"><a href="#cb8-59" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">MyServiceGen.API</span> <span class="kw">as</span> <span class="dt">MS</span> <span class="co">-- aus der generierten library</span></span>
<span id="cb8-60"><a href="#cb8-60" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-61"><a href="#cb8-61" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-62"><a href="#cb8-62" aria-hidden="true" tabindex="-1"></a><span class="ot">myServicemain ::</span> <span class="dt">IO</span> ()</span>
<span id="cb8-63"><a href="#cb8-63" aria-hidden="true" tabindex="-1"></a>myServicemain <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb8-64"><a href="#cb8-64" aria-hidden="true" tabindex="-1"></a> <span class="co">-- .env-Datei ins Prozess-Environment laden, falls noch nicht von außen gesetzt</span></span>
<span id="cb8-65"><a href="#cb8-65" aria-hidden="true" tabindex="-1"></a> void <span class="op">$</span> loadFile <span class="op">$</span> <span class="dt">Dotenv.Config</span> [<span class="st">".env"</span>] [] <span class="dt">False</span></span>
<span id="cb8-66"><a href="#cb8-66" aria-hidden="true" tabindex="-1"></a> <span class="co">-- Config holen (defaults + overrides aus dem Environment)</span></span>
<span id="cb8-67"><a href="#cb8-67" aria-hidden="true" tabindex="-1"></a> sc<span class="op">@</span><span class="dt">ServerConfig</span>{<span class="op">..</span>} <span class="ot">&lt;-</span> decodeWithDefaults defConfig</span>
<span id="cb8-68"><a href="#cb8-68" aria-hidden="true" tabindex="-1"></a> <span class="co">-- Backend-Setup</span></span>
<span id="cb8-69"><a href="#cb8-69" aria-hidden="true" tabindex="-1"></a> <span class="co">-- legt sowas wie Proxy-Server fest und wo man wie dran kommt. Benötigt für das Sprechen mit anderen Microservices</span></span>
<span id="cb8-70"><a href="#cb8-70" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> defaultHTTPSSettings <span class="ot">=</span> UseDefaultHTTPSSettings.tlsManagerSettings { managerResponseTimeout <span class="ot">=</span> responseTimeoutMicro <span class="op">$</span> <span class="dv">1000</span> <span class="op">*</span> <span class="dv">1000</span> <span class="op">*</span> myserviceMaxTimeout }</span>
<span id="cb8-71"><a href="#cb8-71" aria-hidden="true" tabindex="-1"></a> createBackend url proxy <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb8-72"><a href="#cb8-72" aria-hidden="true" tabindex="-1"></a> manager <span class="ot">&lt;-</span> newManager <span class="op">.</span> managerSetProxy proxy</span>
<span id="cb8-73"><a href="#cb8-73" aria-hidden="true" tabindex="-1"></a> <span class="op">$</span> defaultHTTPSSettings</span>
<span id="cb8-74"><a href="#cb8-74" aria-hidden="true" tabindex="-1"></a> url' <span class="ot">&lt;-</span> parseBaseUrl url</span>
<span id="cb8-75"><a href="#cb8-75" aria-hidden="true" tabindex="-1"></a> <span class="fu">return</span> (mkClientEnv manager url')</span>
<span id="cb8-76"><a href="#cb8-76" aria-hidden="true" tabindex="-1"></a> internalProxy <span class="ot">=</span> <span class="kw">case</span> myserviceInternalProxyUrl <span class="kw">of</span></span>
<span id="cb8-77"><a href="#cb8-77" aria-hidden="true" tabindex="-1"></a> <span class="st">""</span> <span class="ot">-&gt;</span> noProxy</span>
<span id="cb8-78"><a href="#cb8-78" aria-hidden="true" tabindex="-1"></a> url <span class="ot">-&gt;</span> useProxy <span class="op">$</span> <span class="dt">HTTP.Proxy</span> (fromString url) myserviceInternalProxyPort</span>
<span id="cb8-79"><a href="#cb8-79" aria-hidden="true" tabindex="-1"></a> <span class="co">-- externalProxy = case myserviceExternalProxyUrl of</span></span>
<span id="cb8-80"><a href="#cb8-80" aria-hidden="true" tabindex="-1"></a> <span class="co">-- "" -&gt; noProxy</span></span>
<span id="cb8-81"><a href="#cb8-81" aria-hidden="true" tabindex="-1"></a> <span class="co">-- url -&gt; useProxy $ HTTP.Proxy (fromString url) myserviceExternalProxyPort</span></span>
<span id="cb8-82"><a href="#cb8-82" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-83"><a href="#cb8-83" aria-hidden="true" tabindex="-1"></a> <span class="co">-- Definieren &amp; Erzeugen der Funktionen um die anderen Services anzusprechen.</span></span>
<span id="cb8-84"><a href="#cb8-84" aria-hidden="true" tabindex="-1"></a> calls <span class="ot">&lt;-</span> (,)</span>
<span id="cb8-85"><a href="#cb8-85" aria-hidden="true" tabindex="-1"></a> <span class="op">&lt;$&gt;</span> createBackend myserviceAUri internalProxy</span>
<span id="cb8-86"><a href="#cb8-86" aria-hidden="true" tabindex="-1"></a> <span class="op">&lt;*&gt;</span> createBackend myserviceBUri internalProxy</span>
<span id="cb8-87"><a href="#cb8-87" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-88"><a href="#cb8-88" aria-hidden="true" tabindex="-1"></a> <span class="co">-- Logging-Setup</span></span>
<span id="cb8-89"><a href="#cb8-89" aria-hidden="true" tabindex="-1"></a> hSetBuffering stdout <span class="dt">LineBuffering</span></span>
<span id="cb8-90"><a href="#cb8-90" aria-hidden="true" tabindex="-1"></a> hSetBuffering stderr <span class="dt">LineBuffering</span></span>
<span id="cb8-91"><a href="#cb8-91" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-92"><a href="#cb8-92" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-93"><a href="#cb8-93" aria-hidden="true" tabindex="-1"></a> <span class="co">-- Infos holen, brauchen wir später</span></span>
<span id="cb8-94"><a href="#cb8-94" aria-hidden="true" tabindex="-1"></a> myName <span class="ot">&lt;-</span> getHostName</span>
<span id="cb8-95"><a href="#cb8-95" aria-hidden="true" tabindex="-1"></a> today <span class="ot">&lt;-</span> formatTime defaultTimeLocale <span class="st">"%F"</span> <span class="op">.</span> utctDay <span class="op">&lt;$&gt;</span> getCurrentTime</span>
<span id="cb8-96"><a href="#cb8-96" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-97"><a href="#cb8-97" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-98"><a href="#cb8-98" aria-hidden="true" tabindex="-1"></a> <span class="co">-- activeMQ-Transaktional-Queue zum schreiben nachher vorbereiten</span></span>
<span id="cb8-99"><a href="#cb8-99" aria-hidden="true" tabindex="-1"></a> amqPost <span class="ot">&lt;-</span> newTQueueIO</span>
<span id="cb8-100"><a href="#cb8-100" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-101"><a href="#cb8-101" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-102"><a href="#cb8-102" aria-hidden="true" tabindex="-1"></a> <span class="co">-- bracket a b c == erst a machen, ergebnis an c als variablen übergeben. Schmeisst c ne exception/wird gekillt/..., werden die variablen an b übergeben.</span></span>
<span id="cb8-103"><a href="#cb8-103" aria-hidden="true" tabindex="-1"></a> bracket</span>
<span id="cb8-104"><a href="#cb8-104" aria-hidden="true" tabindex="-1"></a> <span class="co">-- logfiles öffnen</span></span>
<span id="cb8-105"><a href="#cb8-105" aria-hidden="true" tabindex="-1"></a> (<span class="dt">LogFiles</span> <span class="op">&lt;$&gt;</span> openFile (<span class="st">"/logs/myservice-"</span><span class="op">&lt;&gt;</span>myName<span class="op">&lt;&gt;</span><span class="st">"-"</span><span class="op">&lt;&gt;</span>today<span class="op">&lt;&gt;</span><span class="st">".info"</span>) <span class="dt">AppendMode</span></span>
<span id="cb8-106"><a href="#cb8-106" aria-hidden="true" tabindex="-1"></a> <span class="op">&lt;*&gt;</span> openFile (<span class="kw">if</span> myserviceDebug <span class="kw">then</span> <span class="st">"/logs/myservice-"</span><span class="op">&lt;&gt;</span>myName<span class="op">&lt;&gt;</span><span class="st">"-"</span><span class="op">&lt;&gt;</span>today<span class="op">&lt;&gt;</span><span class="st">".debug"</span> <span class="kw">else</span> <span class="st">"/dev/null"</span>) <span class="dt">AppendMode</span></span>
<span id="cb8-107"><a href="#cb8-107" aria-hidden="true" tabindex="-1"></a> <span class="op">&lt;*&gt;</span> openFile (<span class="st">"/logs/myservice-"</span><span class="op">&lt;&gt;</span>myName<span class="op">&lt;&gt;</span><span class="st">"-"</span><span class="op">&lt;&gt;</span>today<span class="op">&lt;&gt;</span><span class="st">".error"</span>) <span class="dt">AppendMode</span></span>
<span id="cb8-108"><a href="#cb8-108" aria-hidden="true" tabindex="-1"></a> <span class="op">&lt;*&gt;</span> openFile (<span class="st">"/logs/myservice-"</span><span class="op">&lt;&gt;</span>myName<span class="op">&lt;&gt;</span><span class="st">"-"</span><span class="op">&lt;&gt;</span>today<span class="op">&lt;&gt;</span><span class="st">".timings"</span>) <span class="dt">AppendMode</span></span>
<span id="cb8-109"><a href="#cb8-109" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb8-110"><a href="#cb8-110" aria-hidden="true" tabindex="-1"></a> <span class="co">-- und bei exception/beendigung schlißen.h</span></span>
<span id="cb8-111"><a href="#cb8-111" aria-hidden="true" tabindex="-1"></a> (\(<span class="dt">LogFiles</span> a b c d) <span class="ot">-&gt;</span> <span class="fu">mapM_</span> hClose [a,b,c,d])</span>
<span id="cb8-112"><a href="#cb8-112" aria-hidden="true" tabindex="-1"></a> <span class="op">$</span> \logfiles <span class="ot">-&gt;</span> <span class="kw">do</span></span>
<span id="cb8-113"><a href="#cb8-113" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-114"><a href="#cb8-114" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-115"><a href="#cb8-115" aria-hidden="true" tabindex="-1"></a> <span class="co">-- logschreibe-funktionen aliasen; log ist hier abstrakt, iolog spezialisiert auf io.</span></span>
<span id="cb8-116"><a href="#cb8-116" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> <span class="fu">log</span> <span class="ot">=</span> printLogFiles<span class="ot"> logfiles ::</span> <span class="dt">MonadIO</span> m <span class="ot">=&gt;</span> [<span class="dt">LogItem</span>] <span class="ot">-&gt;</span> m ()</span>
<span id="cb8-117"><a href="#cb8-117" aria-hidden="true" tabindex="-1"></a> iolog <span class="ot">=</span> printLogFilesIO<span class="ot"> logfiles ::</span> [<span class="dt">LogItem</span>] <span class="ot">-&gt;</span> <span class="dt">IO</span> ()</span>
<span id="cb8-118"><a href="#cb8-118" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-119"><a href="#cb8-119" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-120"><a href="#cb8-120" aria-hidden="true" tabindex="-1"></a> <span class="co">-- H.myApiEndpointV1Post ist ein Handler (alle Handler werden mit alias H importiert) und in einer eigenen Datei</span></span>
<span id="cb8-121"><a href="#cb8-121" aria-hidden="true" tabindex="-1"></a> <span class="co">-- Per Default bekommen Handler sowas wie die server-config, die Funktionen um mit anderen Services zu reden, die AMQ-Queue um ins Kibana zu loggen und eine Datei-Logging-Funktion</span></span>
<span id="cb8-122"><a href="#cb8-122" aria-hidden="true" tabindex="-1"></a> <span class="co">-- Man kann aber noch viel mehr machen - z.b. gecachte Daten übergeben, eine Talk-Instanz, etc. pp.</span></span>
<span id="cb8-123"><a href="#cb8-123" aria-hidden="true" tabindex="-1"></a> server <span class="ot">=</span> <span class="dt">MyServiceBackend</span>{ myApiEndpointV1Post <span class="ot">=</span> H.myApiEndpointV1Post sc calls amqPost <span class="fu">log</span></span>
<span id="cb8-124"><a href="#cb8-124" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb8-125"><a href="#cb8-125" aria-hidden="true" tabindex="-1"></a> config <span class="ot">=</span> <span class="dt">MS.Config</span> <span class="op">$</span> <span class="st">"http://"</span> <span class="op">++</span> myserviceHost <span class="op">++</span> <span class="st">":"</span> <span class="op">++</span> <span class="fu">show</span> myservicePort <span class="op">++</span> <span class="st">"/"</span></span>
<span id="cb8-126"><a href="#cb8-126" aria-hidden="true" tabindex="-1"></a> iolog <span class="op">.</span> <span class="fu">pure</span> <span class="op">.</span> <span class="dt">Info</span> <span class="op">$</span> <span class="st">"Using Server configuration:"</span></span>
<span id="cb8-127"><a href="#cb8-127" aria-hidden="true" tabindex="-1"></a> iolog <span class="op">.</span> <span class="fu">pure</span> <span class="op">.</span> <span class="dt">Info</span> <span class="op">$</span> pretty sc { myserviceActivemqPassword <span class="ot">=</span> <span class="st">"******"</span> <span class="co">-- Do NOT log the password ;)</span></span>
<span id="cb8-128"><a href="#cb8-128" aria-hidden="true" tabindex="-1"></a> , myserviceMongoPassword <span class="ot">=</span> <span class="st">"******"</span></span>
<span id="cb8-129"><a href="#cb8-129" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb8-130"><a href="#cb8-130" aria-hidden="true" tabindex="-1"></a> <span class="co">-- alle Services starten (Hintergrund-Aktionen wie z.b. einen MongoDB-Dumper, einen Talk-Server oder wie hier die ActiveMQ</span></span>
<span id="cb8-131"><a href="#cb8-131" aria-hidden="true" tabindex="-1"></a> void <span class="op">$</span> forkIO <span class="op">$</span> keepActiveMQConnected sc iolog amqPost</span>
<span id="cb8-132"><a href="#cb8-132" aria-hidden="true" tabindex="-1"></a> <span class="co">-- logging-Framework erzeugen</span></span>
<span id="cb8-133"><a href="#cb8-133" aria-hidden="true" tabindex="-1"></a> loggingMW <span class="ot">&lt;-</span> loggingMiddleware</span>
<span id="cb8-134"><a href="#cb8-134" aria-hidden="true" tabindex="-1"></a> <span class="co">-- server starten</span></span>
<span id="cb8-135"><a href="#cb8-135" aria-hidden="true" tabindex="-1"></a> <span class="kw">if</span> myserviceDebug</span>
<span id="cb8-136"><a href="#cb8-136" aria-hidden="true" tabindex="-1"></a> <span class="kw">then</span> runMyServiceMiddlewareServer config (cors (\_ <span class="ot">-&gt;</span> <span class="dt">Just</span> (simpleCorsResourcePolicy {corsRequestHeaders <span class="ot">=</span> [<span class="st">"Content-Type"</span>]})) <span class="op">.</span> loggingMW <span class="op">.</span> logStdout) server</span>
<span id="cb8-137"><a href="#cb8-137" aria-hidden="true" tabindex="-1"></a> <span class="kw">else</span> runMyServiceMiddlewareServer config (cors (\_ <span class="ot">-&gt;</span> <span class="dt">Just</span> (simpleCorsResourcePolicy {corsRequestHeaders <span class="ot">=</span> [<span class="st">"Content-Type"</span>]}))) server</span>
<span id="cb8-138"><a href="#cb8-138" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-139"><a href="#cb8-139" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-140"><a href="#cb8-140" aria-hidden="true" tabindex="-1"></a><span class="co">-- Sollte bald in die Library hs-stomp ausgelagert werden</span></span>
<span id="cb8-141"><a href="#cb8-141" aria-hidden="true" tabindex="-1"></a><span class="co">-- ist ein Beispiel für einen ActiveMQ-Dumper</span></span>
<span id="cb8-142"><a href="#cb8-142" aria-hidden="true" tabindex="-1"></a><span class="ot">keepActiveMQConnected ::</span> <span class="dt">ServerConfig</span> <span class="ot">-&gt;</span> ([<span class="dt">LogItem</span>] <span class="ot">-&gt;</span> <span class="dt">IO</span> ()) <span class="ot">-&gt;</span> <span class="dt">TQueue</span> <span class="dt">BS.ByteString</span> <span class="ot">-&gt;</span> <span class="dt">IO</span> ()</span>
<span id="cb8-143"><a href="#cb8-143" aria-hidden="true" tabindex="-1"></a>keepActiveMQConnected sc<span class="op">@</span><span class="dt">ServerConfig</span>{<span class="op">..</span>} printLog var <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb8-144"><a href="#cb8-144" aria-hidden="true" tabindex="-1"></a> res <span class="ot">&lt;-</span> handle (\(<span class="ot">e ::</span> <span class="dt">SomeException</span>) <span class="ot">-&gt;</span> <span class="kw">do</span></span>
<span id="cb8-145"><a href="#cb8-145" aria-hidden="true" tabindex="-1"></a> printLog <span class="op">.</span> <span class="fu">pure</span> <span class="op">.</span> <span class="dt">Error</span> <span class="op">$</span> <span class="st">"Exception in AMQ-Thread: "</span><span class="op">&lt;&gt;</span><span class="fu">show</span> e</span>
<span id="cb8-146"><a href="#cb8-146" aria-hidden="true" tabindex="-1"></a> <span class="fu">return</span> <span class="op">$</span> <span class="dt">Right</span> ()</span>
<span id="cb8-147"><a href="#cb8-147" aria-hidden="true" tabindex="-1"></a> ) <span class="op">$</span> AMQ.try <span class="op">$</span> <span class="kw">do</span> <span class="co">-- catches all AMQ-Exception that we can handle. All others bubble up.</span></span>
<span id="cb8-148"><a href="#cb8-148" aria-hidden="true" tabindex="-1"></a> printLog <span class="op">.</span> <span class="fu">pure</span> <span class="op">.</span> <span class="dt">Info</span> <span class="op">$</span> <span class="st">"AMQ: connecting..."</span></span>
<span id="cb8-149"><a href="#cb8-149" aria-hidden="true" tabindex="-1"></a> withConnection myserviceActivemqHost myserviceActivemqPort [ <span class="dt">OAuth</span> myserviceActivemqUsername myserviceActivemqPassword</span>
<span id="cb8-150"><a href="#cb8-150" aria-hidden="true" tabindex="-1"></a> , <span class="dt">OTmo</span> (<span class="dv">30</span><span class="op">*</span><span class="dv">1000</span>) <span class="co">{- 30 sec timeout -}</span></span>
<span id="cb8-151"><a href="#cb8-151" aria-hidden="true" tabindex="-1"></a> ]</span>
<span id="cb8-152"><a href="#cb8-152" aria-hidden="true" tabindex="-1"></a> [] <span class="op">$</span> \c <span class="ot">-&gt;</span> <span class="kw">do</span></span>
<span id="cb8-153"><a href="#cb8-153" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> oconv <span class="ot">=</span> <span class="fu">return</span></span>
<span id="cb8-154"><a href="#cb8-154" aria-hidden="true" tabindex="-1"></a> printLog <span class="op">.</span> <span class="fu">pure</span> <span class="op">.</span> <span class="dt">Info</span> <span class="op">$</span> <span class="st">"AMQ: connected"</span></span>
<span id="cb8-155"><a href="#cb8-155" aria-hidden="true" tabindex="-1"></a> withWriter c <span class="st">"Chaos-Logger for Kibana"</span> <span class="st">"chaos.logs"</span> [] [] oconv <span class="op">$</span> \writer <span class="ot">-&gt;</span> <span class="kw">do</span></span>
<span id="cb8-156"><a href="#cb8-156" aria-hidden="true" tabindex="-1"></a> printLog <span class="op">.</span> <span class="fu">pure</span> <span class="op">.</span> <span class="dt">Info</span> <span class="op">$</span> <span class="st">"AMQ: queue created"</span></span>
<span id="cb8-157"><a href="#cb8-157" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> postfun <span class="ot">=</span> writeQ writer (<span class="dt">Type</span> (<span class="dt">Application</span> <span class="st">"json"</span>) []) []</span>
<span id="cb8-158"><a href="#cb8-158" aria-hidden="true" tabindex="-1"></a> void <span class="op">$</span> race</span>
<span id="cb8-159"><a href="#cb8-159" aria-hidden="true" tabindex="-1"></a> (forever <span class="op">$</span> atomically (readTQueue var) <span class="op">&gt;&gt;=</span> postfun)</span>
<span id="cb8-160"><a href="#cb8-160" aria-hidden="true" tabindex="-1"></a> (threadDelay (<span class="dv">600</span><span class="op">*</span><span class="dv">1000</span><span class="op">*</span><span class="dv">1000</span>)) <span class="co">-- wait 10 Minutes</span></span>
<span id="cb8-161"><a href="#cb8-161" aria-hidden="true" tabindex="-1"></a> <span class="co">-- close writer</span></span>
<span id="cb8-162"><a href="#cb8-162" aria-hidden="true" tabindex="-1"></a> <span class="co">-- close connection</span></span>
<span id="cb8-163"><a href="#cb8-163" aria-hidden="true" tabindex="-1"></a> <span class="co">-- get outside of all try/handle/...-constructions befor recursing.</span></span>
<span id="cb8-164"><a href="#cb8-164" aria-hidden="true" tabindex="-1"></a> <span class="kw">case</span> res <span class="kw">of</span></span>
<span id="cb8-165"><a href="#cb8-165" aria-hidden="true" tabindex="-1"></a> <span class="dt">Left</span> ex <span class="ot">-&gt;</span> <span class="kw">do</span></span>
<span id="cb8-166"><a href="#cb8-166" aria-hidden="true" tabindex="-1"></a> printLog <span class="op">.</span> <span class="fu">pure</span> <span class="op">.</span> <span class="dt">Error</span> <span class="op">$</span> <span class="st">"AMQ: "</span><span class="op">&lt;&gt;</span><span class="fu">show</span> ex</span>
<span id="cb8-167"><a href="#cb8-167" aria-hidden="true" tabindex="-1"></a> keepActiveMQConnected sc printLog var</span>
<span id="cb8-168"><a href="#cb8-168" aria-hidden="true" tabindex="-1"></a> <span class="dt">Right</span> _ <span class="ot">-&gt;</span> keepActiveMQConnected sc printLog var</span>
<span id="cb8-169"><a href="#cb8-169" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-170"><a href="#cb8-170" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-171"><a href="#cb8-171" aria-hidden="true" tabindex="-1"></a><span class="co">-- Beispiel für eine Custom-Logging-Middleware.</span></span>
<span id="cb8-172"><a href="#cb8-172" aria-hidden="true" tabindex="-1"></a><span class="co">-- Hier werden z.B. alle 4xx-Status-Codes inkl. Payload ins stdout-Log geschrieben.</span></span>
<span id="cb8-173"><a href="#cb8-173" aria-hidden="true" tabindex="-1"></a><span class="co">-- Nützlich, wenn die Kollegen ihre Requests nicht ordentlich schreiben können und der Server das Format zurecht mit einem BadRequest ablehnt ;)</span></span>
<span id="cb8-174"><a href="#cb8-174" aria-hidden="true" tabindex="-1"></a><span class="ot">loggingMiddleware ::</span> <span class="dt">IO</span> <span class="dt">Middleware</span></span>
<span id="cb8-175"><a href="#cb8-175" aria-hidden="true" tabindex="-1"></a>loggingMiddleware <span class="ot">=</span> liftIO <span class="op">$</span> mkRequestLogger <span class="op">$</span> def { outputFormat <span class="ot">=</span> <span class="dt">CustomOutputFormatWithDetails</span> out }</span>
<span id="cb8-176"><a href="#cb8-176" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb8-177"><a href="#cb8-177" aria-hidden="true" tabindex="-1"></a><span class="ot"> out ::</span> <span class="dt">ZonedDate</span> <span class="ot">-&gt;</span> <span class="dt">WAI.Request</span> <span class="ot">-&gt;</span> <span class="dt">Status</span> <span class="ot">-&gt;</span> <span class="dt">Maybe</span> <span class="dt">Integer</span> <span class="ot">-&gt;</span> <span class="dt">NominalDiffTime</span> <span class="ot">-&gt;</span> [<span class="dt">BS.ByteString</span>] <span class="ot">-&gt;</span> <span class="dt">Builder</span> <span class="ot">-&gt;</span> <span class="dt">LogStr</span></span>
<span id="cb8-178"><a href="#cb8-178" aria-hidden="true" tabindex="-1"></a> out _ r status _ _ payload _</span>
<span id="cb8-179"><a href="#cb8-179" aria-hidden="true" tabindex="-1"></a> <span class="op">|</span> statusCode status <span class="op">&lt;</span> <span class="dv">300</span> <span class="ot">=</span> <span class="st">""</span></span>
<span id="cb8-180"><a href="#cb8-180" aria-hidden="true" tabindex="-1"></a> <span class="op">|</span> statusCode status <span class="op">&gt;</span> <span class="dv">399</span> <span class="op">&amp;&amp;</span> statusCode status <span class="op">&lt;</span> <span class="dv">500</span> <span class="ot">=</span> <span class="st">"Error code "</span><span class="op">&lt;&gt;</span>toLogStr (statusCode status) <span class="op">&lt;&gt;</span><span class="st">" sent. Request-Payload was: "</span><span class="op">&lt;&gt;</span> <span class="fu">mconcat</span> (toLogStr <span class="op">&lt;$&gt;</span> payload) <span class="op">&lt;&gt;</span> <span class="st">"\n"</span></span>
<span id="cb8-181"><a href="#cb8-181" aria-hidden="true" tabindex="-1"></a> <span class="op">|</span> <span class="fu">otherwise</span> <span class="ot">=</span> toLogStr (<span class="fu">show</span> r) <span class="op">&lt;&gt;</span> <span class="st">"\n"</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
{{&lt; dend &gt;}}
</blockquote></details></section>
<section id="weitere-instanzen-und-definitionen-die-der-generator-noch-nicht-macht" class="level4">
<h4 class="anchored" data-anchor-id="weitere-instanzen-und-definitionen-die-der-generator-noch-nicht-macht">Weitere Instanzen und Definitionen, die der Generator (noch) nicht macht</h4>
<p>In der <code>Myservice.Types</code> werden ein paar hilfreiche Typen und Typ-Instanzen definiert. Im Folgenden geht es dabei um Dinge für:</p>
<ul>
<li><code>Envy</code>
<ul>
<li>Laden von <code>$ENV_VAR</code> in Datentypen</li>
<li>Definitionen für Default-Settings</li>
</ul></li>
<li><code>ServerConfig</code>
<ul>
<li>Definition der Server-Konfiguration &amp; Benennung der Environment-Variablen</li>
</ul></li>
<li><code>ExtraTypes</code>
<ul>
<li>ggf. Paketweite extra-Typen, die der Generator nicht macht, weil sie nicht aus der API kommen (z.B. cache)</li>
</ul></li>
<li><code>Out</code>/<code>BSON</code>-Instanzen
<ul>
<li>Der API-Generator generiert nur wenige Instanzen automatisch (z.B. <code>aeson</code>), daher werden hier die fehlenden definiert.</li>
<li><code>BSON</code>: Kommunikation mit <code>MongoDB</code></li>
<li><code>Out</code>: pretty-printing im Log
<ul>
<li>Nur nötig, wenn man pretty-printing via <code>Out</code> statt über Generics wie z.b. <code>pretty-generic</code> oder die automatische Show-Instanz via <code>prerryShow</code> macht.</li>
</ul></li>
</ul></li>
</ul>
<p></p><p></p><details><summary>Types.hs anzeigen</summary><blockquote class="blockquote"><p></p>
<div class="sourceCode" id="cb9"><pre class="sourceCode haskell code-with-copy"><code class="sourceCode haskell"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# OPTIONS_GHC -Wno-orphans #-}</span></span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# OPTIONS_GHC -Wno-name-shadowing #-}</span></span>
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE DeriveAnyClass #-}</span></span>
<span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE DeriveFunctor #-}</span></span>
<span id="cb9-5"><a href="#cb9-5" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE DeriveGeneric #-}</span></span>
<span id="cb9-6"><a href="#cb9-6" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE DerivingVia #-}</span></span>
<span id="cb9-7"><a href="#cb9-7" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE DuplicateRecordFields #-}</span></span>
<span id="cb9-8"><a href="#cb9-8" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE FlexibleContexts #-}</span></span>
<span id="cb9-9"><a href="#cb9-9" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE FlexibleInstances #-}</span></span>
<span id="cb9-10"><a href="#cb9-10" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE GADTs #-}</span></span>
<span id="cb9-11"><a href="#cb9-11" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE LambdaCase #-}</span></span>
<span id="cb9-12"><a href="#cb9-12" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE MultiParamTypeClasses #-}</span></span>
<span id="cb9-13"><a href="#cb9-13" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE OverloadedStrings #-}</span></span>
<span id="cb9-14"><a href="#cb9-14" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE RankNTypes #-}</span></span>
<span id="cb9-15"><a href="#cb9-15" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE RecordWildCards #-}</span></span>
<span id="cb9-16"><a href="#cb9-16" aria-hidden="true" tabindex="-1"></a><span class="kw">module</span> <span class="dt">MyService.Types</span> <span class="kw">where</span></span>
<span id="cb9-17"><a href="#cb9-17" aria-hidden="true" tabindex="-1"></a> </span>
<span id="cb9-18"><a href="#cb9-18" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Aeson</span> (<span class="dt">FromJSON</span>, <span class="dt">ToJSON</span>)</span>
<span id="cb9-19"><a href="#cb9-19" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Text</span></span>
<span id="cb9-20"><a href="#cb9-20" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Time.Clock</span></span>
<span id="cb9-21"><a href="#cb9-21" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">GHC.Generics</span></span>
<span id="cb9-22"><a href="#cb9-22" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">System.Envy</span></span>
<span id="cb9-23"><a href="#cb9-23" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Text.PrettyPrint</span> (text)</span>
<span id="cb9-24"><a href="#cb9-24" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Text.PrettyPrint.GenericPretty</span></span>
<span id="cb9-25"><a href="#cb9-25" aria-hidden="true" tabindex="-1"></a> </span>
<span id="cb9-26"><a href="#cb9-26" aria-hidden="true" tabindex="-1"></a><span class="co">-- Out hat hierfür keine Instanzen, daher kurz eine einfach Definition.</span></span>
<span id="cb9-27"><a href="#cb9-27" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Out</span> <span class="dt">Text</span> <span class="kw">where</span></span>
<span id="cb9-28"><a href="#cb9-28" aria-hidden="true" tabindex="-1"></a> doc <span class="ot">=</span> text <span class="op">.</span> unpack</span>
<span id="cb9-29"><a href="#cb9-29" aria-hidden="true" tabindex="-1"></a> docPrec i a <span class="ot">=</span> text <span class="op">$</span> <span class="fu">showsPrec</span> i a <span class="st">""</span></span>
<span id="cb9-30"><a href="#cb9-30" aria-hidden="true" tabindex="-1"></a> </span>
<span id="cb9-31"><a href="#cb9-31" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Out</span> <span class="dt">UTCTime</span> <span class="kw">where</span></span>
<span id="cb9-32"><a href="#cb9-32" aria-hidden="true" tabindex="-1"></a> doc <span class="ot">=</span> text <span class="op">.</span> <span class="fu">show</span></span>
<span id="cb9-33"><a href="#cb9-33" aria-hidden="true" tabindex="-1"></a> docPrec i a <span class="ot">=</span> text <span class="op">$</span> <span class="fu">showsPrec</span> i a <span class="st">""</span></span>
<span id="cb9-34"><a href="#cb9-34" aria-hidden="true" tabindex="-1"></a> </span>
<span id="cb9-35"><a href="#cb9-35" aria-hidden="true" tabindex="-1"></a><span class="co">-- Der ServerConfig-Typ. Wird mit den defaults unten initialisiert, dann mit den Variablen aus der .env-Datei überschrieben und zum Schluss können Serveradmins diese via $MYSERVICE_FOO nochmal überschreiben.</span></span>
<span id="cb9-36"><a href="#cb9-36" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">ServerConfig</span> <span class="ot">=</span> <span class="dt">ServerConfig</span></span>
<span id="cb9-37"><a href="#cb9-37" aria-hidden="true" tabindex="-1"></a> {<span class="ot"> myserviceHost ::</span> <span class="dt">String</span> <span class="co">-- ^ Environment: $MYSERVICE_HOST</span></span>
<span id="cb9-38"><a href="#cb9-38" aria-hidden="true" tabindex="-1"></a> ,<span class="ot"> myservicePort ::</span> <span class="dt">Int</span> <span class="co">-- ^ Environment: $MYSERVICE_PORT</span></span>
<span id="cb9-39"><a href="#cb9-39" aria-hidden="true" tabindex="-1"></a> ,<span class="ot"> myserviceMaxTimeout ::</span> <span class="dt">Int</span> <span class="co">-- ^ Environment: $MYSERVICE_MAX_TIMEOUT</span></span>
<span id="cb9-40"><a href="#cb9-40" aria-hidden="true" tabindex="-1"></a> ,<span class="ot"> myserviceInternalProxyUrl ::</span> <span class="dt">String</span> <span class="co">-- ^ Environment: $MYSERVICE_INTERNAL_PROXY_URL</span></span>
<span id="cb9-41"><a href="#cb9-41" aria-hidden="true" tabindex="-1"></a> ,<span class="ot"> myserviceInternalProxyPort ::</span> <span class="dt">Int</span> <span class="co">-- ^ Environment: $MYSERVICE_INTERNAL_PROXY_PORT</span></span>
<span id="cb9-42"><a href="#cb9-42" aria-hidden="true" tabindex="-1"></a> ,<span class="ot"> myserviceExternalProxyUrl ::</span> <span class="dt">String</span> <span class="co">-- ^ Environment: $MYSERVICE_EXTERNAL_PROXY_URL</span></span>
<span id="cb9-43"><a href="#cb9-43" aria-hidden="true" tabindex="-1"></a> ,<span class="ot"> myserviceExternalProxyPort ::</span> <span class="dt">Int</span> <span class="co">-- ^ Environment: $MYSERVICE_EXTERNAL_PROXY_PORT</span></span>
<span id="cb9-44"><a href="#cb9-44" aria-hidden="true" tabindex="-1"></a> ,<span class="ot"> myserviceActivemqHost ::</span> <span class="dt">String</span> <span class="co">-- ^ Environment: $MYSERVICE_ACTIVEMQ_HOST</span></span>
<span id="cb9-45"><a href="#cb9-45" aria-hidden="true" tabindex="-1"></a> ,<span class="ot"> myserviceActivemqPort ::</span> <span class="dt">Int</span> <span class="co">-- ^ Environment: $MYSERVICE_ACTIVEMQ_PORT</span></span>
<span id="cb9-46"><a href="#cb9-46" aria-hidden="true" tabindex="-1"></a> ,<span class="ot"> myserviceActivemqUsername ::</span> <span class="dt">String</span> <span class="co">-- ^ Environment: $MYSERVICE_ACTIVEMQ_USERNAME</span></span>
<span id="cb9-47"><a href="#cb9-47" aria-hidden="true" tabindex="-1"></a> ,<span class="ot"> myserviceActivemqPassword ::</span> <span class="dt">String</span> <span class="co">-- ^ Environment: $MYSERVICE_ACTIVEMQ_PASSWORD</span></span>
<span id="cb9-48"><a href="#cb9-48" aria-hidden="true" tabindex="-1"></a> ,<span class="ot"> myserviceMongoUsername ::</span> <span class="dt">String</span> <span class="co">-- ^ Environment: $MYSERVICE_MONGO_USERNAME</span></span>
<span id="cb9-49"><a href="#cb9-49" aria-hidden="true" tabindex="-1"></a> ,<span class="ot"> myserviceMongoPassword ::</span> <span class="dt">String</span> <span class="co">-- ^ Environment: $MYSERVICE_MONGO_PASSWORD</span></span>
<span id="cb9-50"><a href="#cb9-50" aria-hidden="true" tabindex="-1"></a> ,<span class="ot"> myserviceDebug ::</span> <span class="dt">Bool</span> <span class="co">-- ^ Environment: $MYSERVICE_DEBUG</span></span>
<span id="cb9-51"><a href="#cb9-51" aria-hidden="true" tabindex="-1"></a> } <span class="kw">deriving</span> (<span class="dt">Show</span>, <span class="dt">Eq</span>, <span class="dt">Generic</span>)</span>
<span id="cb9-52"><a href="#cb9-52" aria-hidden="true" tabindex="-1"></a> </span>
<span id="cb9-53"><a href="#cb9-53" aria-hidden="true" tabindex="-1"></a><span class="co">-- Default-Konfigurations-Instanz für diesen Service.</span></span>
<span id="cb9-54"><a href="#cb9-54" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">DefConfig</span> <span class="dt">ServerConfig</span> <span class="kw">where</span></span>
<span id="cb9-55"><a href="#cb9-55" aria-hidden="true" tabindex="-1"></a> defConfig <span class="ot">=</span> <span class="dt">ServerConfig</span> <span class="st">"0.0.0.0"</span> <span class="dv">8080</span> <span class="dv">20</span></span>
<span id="cb9-56"><a href="#cb9-56" aria-hidden="true" tabindex="-1"></a> <span class="st">""</span></span>
<span id="cb9-57"><a href="#cb9-57" aria-hidden="true" tabindex="-1"></a> <span class="st">""</span></span>
<span id="cb9-58"><a href="#cb9-58" aria-hidden="true" tabindex="-1"></a> <span class="st">""</span></span>
<span id="cb9-59"><a href="#cb9-59" aria-hidden="true" tabindex="-1"></a> <span class="dv">0</span></span>
<span id="cb9-60"><a href="#cb9-60" aria-hidden="true" tabindex="-1"></a> <span class="st">""</span></span>
<span id="cb9-61"><a href="#cb9-61" aria-hidden="true" tabindex="-1"></a> <span class="dv">0</span></span>
<span id="cb9-62"><a href="#cb9-62" aria-hidden="true" tabindex="-1"></a> <span class="st">""</span></span>
<span id="cb9-63"><a href="#cb9-63" aria-hidden="true" tabindex="-1"></a> <span class="dv">0</span></span>
<span id="cb9-64"><a href="#cb9-64" aria-hidden="true" tabindex="-1"></a> <span class="st">""</span></span>
<span id="cb9-65"><a href="#cb9-65" aria-hidden="true" tabindex="-1"></a> <span class="st">""</span></span>
<span id="cb9-66"><a href="#cb9-66" aria-hidden="true" tabindex="-1"></a> <span class="st">""</span></span>
<span id="cb9-67"><a href="#cb9-67" aria-hidden="true" tabindex="-1"></a> <span class="st">""</span></span>
<span id="cb9-68"><a href="#cb9-68" aria-hidden="true" tabindex="-1"></a> <span class="dt">False</span></span>
<span id="cb9-69"><a href="#cb9-69" aria-hidden="true" tabindex="-1"></a> </span>
<span id="cb9-70"><a href="#cb9-70" aria-hidden="true" tabindex="-1"></a><span class="co">-- Kann auch aus dem ENV gefüllt werden</span></span>
<span id="cb9-71"><a href="#cb9-71" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">FromEnv</span> <span class="dt">ServerConfig</span></span>
<span id="cb9-72"><a href="#cb9-72" aria-hidden="true" tabindex="-1"></a><span class="co">-- Und hübsch ausgegeben werden.</span></span>
<span id="cb9-73"><a href="#cb9-73" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Out</span> <span class="dt">ServerConfig</span></span>
<span id="cb9-74"><a href="#cb9-74" aria-hidden="true" tabindex="-1"></a> </span>
<span id="cb9-75"><a href="#cb9-75" aria-hidden="true" tabindex="-1"></a> </span>
<span id="cb9-76"><a href="#cb9-76" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Out</span> <span class="dt">Response</span></span>
<span id="cb9-77"><a href="#cb9-77" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">FromBSON</span> <span class="dt">Repsonse</span> <span class="co">-- FromBSON-Instanz geht immer davon aus, dass alle keys da sind (ggf. mit null bei Nothing).</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
{{&lt; dend &gt;}}
</blockquote></details></section>
<section id="was-noch-zu-tun-ist" class="level4">
<h4 class="anchored" data-anchor-id="was-noch-zu-tun-ist">Was noch zu tun ist</h4>
<p>Den Service implementieren. Einfach ein neues Modul aufmachen (z.B. <code>MyService.Handler</code> oder <code>MyService.DieserEndpunktbereich</code>/<code>MyService.JenerEndpunktbereich</code>) und dort die Funktion implementieren, die man in der <code>Main.hs</code> benutzt hat. In dem Handler habt ihr dann keinen Stress mehr mit Validierung, networking, logging, etc. pp. weil alles in der Main abgehandelt wurde und ihr nur noch den “Happy-Case” implementieren müsst. Beispiel für unseren Handler oben:</p>
<div class="sourceCode" id="cb10"><pre class="sourceCode haskell code-with-copy"><code class="sourceCode haskell"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="ot">myApiEndpointV1Post ::</span> <span class="dt">MonadIO</span> m <span class="ot">=&gt;</span> <span class="dt">ServerConfig</span> <span class="ot">-&gt;</span> (<span class="dt">ClientEnv</span>,<span class="dt">ClientEnv</span>) <span class="ot">-&gt;</span> <span class="dt">TQueue</span> <span class="dt">BS.ByteString</span> <span class="ot">-&gt;</span> ([<span class="dt">LogItem</span>] <span class="ot">-&gt;</span> <span class="dt">IO</span> ()) <span class="ot">-&gt;</span> <span class="dt">Request</span> <span class="ot">-&gt;</span> m <span class="dt">Response</span></span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a>myApiEndpointV1Post sc calls amqPost <span class="fu">log</span> req <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a> liftIO <span class="op">.</span> <span class="fu">log</span> <span class="op">$</span> [<span class="dt">Info</span> <span class="op">$</span> <span class="st">"recieved "</span><span class="op">&lt;&gt;</span>pretty req] <span class="co">-- input-logging</span></span>
<span id="cb10-4"><a href="#cb10-4" aria-hidden="true" tabindex="-1"></a> liftIO <span class="op">.</span> atomically <span class="op">.</span> writeTQueue <span class="op">.</span> LBS.toStrict <span class="op">$</span> <span class="st">"{\"hey Kibana, i recieved:\""</span> <span class="op">&lt;&gt;</span> A.encode (pretty req) <span class="op">&lt;&gt;</span> <span class="st">"}"</span> <span class="co">-- log in activeMQ/Kibana</span></span>
<span id="cb10-5"><a href="#cb10-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-6"><a href="#cb10-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-7"><a href="#cb10-7" aria-hidden="true" tabindex="-1"></a> <span class="co">--- .... gaaaanz viel komplizierter code um die Response zu erhalten ;)</span></span>
<span id="cb10-8"><a href="#cb10-8" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> ret <span class="ot">=</span> <span class="dt">Response</span> <span class="dv">1337</span> <span class="dt">Nothing</span> <span class="co">-- dummy-response ;)</span></span>
<span id="cb10-9"><a href="#cb10-9" aria-hidden="true" tabindex="-1"></a> <span class="co">-- gegeben wir haben eine gültige mongodb-pipe;</span></span>
<span id="cb10-10"><a href="#cb10-10" aria-hidden="true" tabindex="-1"></a> <span class="co">-- mehr logik will ich in die Beispiele nicht packen.</span></span>
<span id="cb10-11"><a href="#cb10-11" aria-hidden="true" tabindex="-1"></a> <span class="co">-- Man kann die z.b. als weiteren Wert in einer TMVar (damit man sie ändern &amp; updaten kann) an die Funktion übergeben.</span></span>
<span id="cb10-12"><a href="#cb10-12" aria-hidden="true" tabindex="-1"></a> liftIO <span class="op">.</span> access pipe master <span class="st">"DatabaseName"</span> <span class="op">$</span> <span class="kw">do</span></span>
<span id="cb10-13"><a href="#cb10-13" aria-hidden="true" tabindex="-1"></a> ifM (auth (myServiceMongoUsername sc) (myServiceMongoPassword sc)) (<span class="fu">return</span> ()) (liftIO <span class="op">.</span> printLog <span class="op">.</span> <span class="fu">pure</span> <span class="op">.</span> <span class="dt">Error</span> <span class="op">$</span> <span class="st">"MongoDB: Login failed."</span>)</span>
<span id="cb10-14"><a href="#cb10-14" aria-hidden="true" tabindex="-1"></a> save <span class="st">"DatabaseCollection"</span> [<span class="st">"_id"</span> <span class="op">=:</span> <span class="dv">1337</span>, <span class="st">"entry"</span> <span class="op">=:</span> ret] <span class="co">-- selbe id wie oben ;)</span></span>
<span id="cb10-15"><a href="#cb10-15" aria-hidden="true" tabindex="-1"></a> <span class="fu">return</span> ret</span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
<p>Diese dummy-Antwort führt auf, wie gut man die ganzen Sachen mischen kann.</p>
<ul>
<li>Logging in die Dateien/<code>stdout</code> - je nach Konfiguration</li>
<li>Logging von Statistiken in Kibana</li>
<li>Speichern der Antwort in der MongoDB</li>
<li>Generieren einer Serverantwort und ausliefern dieser über die Schnittstelle</li>
</ul>
</section>
<section id="tipps-tricks" class="level4">
<h4 class="anchored" data-anchor-id="tipps-tricks">Tipps &amp; Tricks</h4>
<section id="dateien-die-statisch-ausgeliefert-werden-sollen" class="level5">
<h5 class="anchored" data-anchor-id="dateien-die-statisch-ausgeliefert-werden-sollen">Dateien, die statisch ausgeliefert werden sollen</h5>
<p>Hierzu erstellt man ein Verzeichnis <code>static/</code> (Konvention; ist im generator so generiert, dass das ausgeliefert wird). Packt man hier z.b. eine <code>index.html</code> rein, erscheint die, wenn man den Service ansurft.</p>
</section>
<section id="wie-bekomme-ich-diese-fancy-preview-hin" class="level5">
<h5 class="anchored" data-anchor-id="wie-bekomme-ich-diese-fancy-preview-hin">Wie bekomme ich diese fancy Preview hin?</h5>
<p>Der Editor, der ganz am Anfang zum Einsatz gekommen ist, braucht nur die <code>api-doc.yml</code> um diese Ansicht zu erzeugen. Daher empfiehlt sich hier ein angepasster Fork davon indem die Pfade in der index.html korrigiert sind. Am einfachsten (und von den meisten services so benutzt): In meiner Implementation liegt dann nach dem starten auf <a href="http://localhost:PORT/ui/" class="uri">http://localhost:PORT/ui/</a> und kann direkt dort getestet werden.</p>
</section>
<section id="wie-sorge-ich-für-bessere-warnungen-damit-der-compiler-meine-bugs-fängt" class="level5">
<h5 class="anchored" data-anchor-id="wie-sorge-ich-für-bessere-warnungen-damit-der-compiler-meine-bugs-fängt">Wie sorge ich für bessere Warnungen, damit der Compiler meine Bugs fängt?</h5>
<div class="sourceCode" id="cb11"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="ex">stack</span> build <span class="at">--file-watch</span> <span class="at">--ghc-options</span> <span class="st">'-freverse-errors -W -Wall -Wcompat'</span> <span class="at">--interleaved-output</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
<p>Was tut das?</p>
<ul>
<li><code>--file-watch</code>: automatisches (minimales) kompilieren bei dateiänderungen</li>
<li><code>--ghc-options</code>
<ul>
<li><code>-freverse-errors</code>: Fehlermeldungen in umgekehrter Reihenfolge (Erster Fehler ganz unten; wenig scrollen )</li>
<li><code>-W</code>: Warnungen an</li>
<li><code>-Wall</code>: Alle sinnvollen Warnungen an (im gegensatz zu <code>-Weverything</code>, was WIRKLICH alles ist )</li>
<li><code>-Wcompat</code>: Warnungen für Sachen, die in der nächsten Compilerversion kaputt brechen werden &amp; vermieden werden sollten</li>
</ul></li>
<li><code>--interleaved-output</code>: stack-log direkt ausgeben &amp; nicht in Dateien schreiben und die dann am ende zusammen cat'en.</li>
</ul>
<p>Um pro Datei Warnungen auszuschalten (z.B. weil man ganz sicher weiss, was man tut -.-): <code>{-# OPTIONS_GHC -Wno-whatsoever #-}</code> als pragma in die Datei.</p>
<p><strong>Idealerweise sollte das Projekt keine Warnungen erzeugen.</strong></p>
</section>
</section>
</section>
<section id="deployment" class="level3">
<h3 class="anchored" data-anchor-id="deployment">Deployment</h3>
<p>Als Beispiel sei hier ein einfaches Docker-Build mit Jenkins-CI gezeigt, weil ich das aus Gründen rumliegen hatte. Kann man analog in fast alle anderen CI übersetzen.</p>
<section id="docker" class="level4">
<h4 class="anchored" data-anchor-id="docker">Docker</h4>
<p>Die angehängten Scripte gehen von einer Standard-Einrichtung aus (statische Sachen in static, 2-3 händische Anpassungen auf das eigene Projekt nach auspacken). Nachher liegt dann auch unter static/version die gebaute Versionsnummer &amp; kann abgerufen werden. In der <code>Dockerfile.release</code> und der <code>Jenkinsfile</code> müssen noch Anpassungen gemacht werden. Konkret:</p>
<ul>
<li>in der <code>Dockerfile.release</code>: alle <code>&lt;&lt;&lt;HIER&gt;&gt;&gt;</code>-Stellen sinnvoll befüllen</li>
<li>in der <code>Jenkinsfile</code> die defs für “servicename” und “servicebinary” ausfüllen. Binary ist das, was bei stack exec aufgerufen wird; name ist der Image-Name für das docker-repository.</li>
</ul>
</section>
<section id="jenkins" class="level4">
<h4 class="anchored" data-anchor-id="jenkins">Jenkins</h4>
<p>Änderungen die dann noch gemacht werden müssen:</p>
<ul>
<li>git-repository URL anpassen</li>
<li>Environment-Vars anpassen ($BRANCH = test &amp; live haben keine zusatzdinger im docker-image-repository; ansonsten hat das image $BRANCH im Namen)</li>
</ul>
<p>Wenn das fertig gebaut ist, liegt im test/live-repository ein docker-image namens <code>servicename:version</code>.</p>
</section>
</section>
<section id="omg-ich-muss-meine-api-ändern.-was-mache-ich-nun" class="level3">
<h3 class="anchored" data-anchor-id="omg-ich-muss-meine-api-ändern.-was-mache-ich-nun">OMG! Ich muss meine API ändern. Was mache ich nun?</h3>
<ol type="1">
<li>api-doc.yml bearbeiten, wie gewünscht</li>
<li>mittels generator die Api &amp; submodule neu generieren</li>
<li>ggf. custom Änderungen übernehmen (:Gitdiffsplit hilft)</li>
<li>Alle Compilerfehler + Warnungen in der eigentlichen Applikation fixen</li>
<li>If it comipilez, ship it! (Besser nicht :grin:)</li>
</ol>
</section>
</section>
</main> <!-- /main -->
<script id="quarto-html-after-body" type="application/javascript">
window.document.addEventListener("DOMContentLoaded", function (event) {
// Ensure there is a toggle, if there isn't float one in the top right
if (window.document.querySelector('.quarto-color-scheme-toggle') === null) {
const a = window.document.createElement('a');
a.classList.add('top-right');
a.classList.add('quarto-color-scheme-toggle');
a.href = "";
a.onclick = function() { try { window.quartoToggleColorScheme(); } catch {} return false; };
const i = window.document.createElement("i");
i.classList.add('bi');
a.appendChild(i);
window.document.body.appendChild(a);
}
window.setColorSchemeToggle(window.hasAlternateSentinel())
const icon = "";
const anchorJS = new window.AnchorJS();
anchorJS.options = {
placement: 'right',
icon: icon
};
anchorJS.add('.anchored');
const isCodeAnnotation = (el) => {
for (const clz of el.classList) {
if (clz.startsWith('code-annotation-')) {
return true;
}
}
return false;
}
const onCopySuccess = function(e) {
// button target
const button = e.trigger;
// don't keep focus
button.blur();
// flash "checked"
button.classList.add('code-copy-button-checked');
var currentTitle = button.getAttribute("title");
button.setAttribute("title", "Copied!");
let tooltip;
if (window.bootstrap) {
button.setAttribute("data-bs-toggle", "tooltip");
button.setAttribute("data-bs-placement", "left");
button.setAttribute("data-bs-title", "Copied!");
tooltip = new bootstrap.Tooltip(button,
{ trigger: "manual",
customClass: "code-copy-button-tooltip",
offset: [0, -8]});
tooltip.show();
}
setTimeout(function() {
if (tooltip) {
tooltip.hide();
button.removeAttribute("data-bs-title");
button.removeAttribute("data-bs-toggle");
button.removeAttribute("data-bs-placement");
}
button.setAttribute("title", currentTitle);
button.classList.remove('code-copy-button-checked');
}, 1000);
// clear code selection
e.clearSelection();
}
const getTextToCopy = function(trigger) {
const codeEl = trigger.previousElementSibling.cloneNode(true);
for (const childEl of codeEl.children) {
if (isCodeAnnotation(childEl)) {
childEl.remove();
}
}
return codeEl.innerText;
}
const clipboard = new window.ClipboardJS('.code-copy-button:not([data-in-quarto-modal])', {
text: getTextToCopy
});
clipboard.on('success', onCopySuccess);
if (window.document.getElementById('quarto-embedded-source-code-modal')) {
const clipboardModal = new window.ClipboardJS('.code-copy-button[data-in-quarto-modal]', {
text: getTextToCopy,
container: window.document.getElementById('quarto-embedded-source-code-modal')
});
clipboardModal.on('success', onCopySuccess);
}
var localhostRegex = new RegExp(/^(?:http|https):\/\/localhost\:?[0-9]*\//);
var mailtoRegex = new RegExp(/^mailto:/);
var filterRegex = new RegExp("https:\/\/drezil\.de");
var isInternal = (href) => {
return filterRegex.test(href) || localhostRegex.test(href) || mailtoRegex.test(href);
}
// Inspect non-navigation links and adorn them if external
var links = window.document.querySelectorAll('a[href]:not(.nav-link):not(.navbar-brand):not(.toc-action):not(.sidebar-link):not(.sidebar-item-toggle):not(.pagination-link):not(.no-external):not([aria-hidden]):not(.dropdown-item):not(.quarto-navigation-tool):not(.about-link)');
for (var i=0; i<links.length; i++) {
const link = links[i];
if (!isInternal(link.href)) {
// undo the damage that might have been done by quarto-nav.js in the case of
// links that we want to consider external
if (link.dataset.originalHref !== undefined) {
link.href = link.dataset.originalHref;
}
// target, if specified
link.setAttribute("target", "_blank");
if (link.getAttribute("rel") === null) {
link.setAttribute("rel", "noopener");
}
// default icon
link.classList.add("external");
}
}
function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) {
const config = {
allowHTML: true,
maxWidth: 500,
delay: 100,
arrow: false,
appendTo: function(el) {
return el.parentElement;
},
interactive: true,
interactiveBorder: 10,
theme: 'quarto',
placement: 'bottom-start',
};
if (contentFn) {
config.content = contentFn;
}
if (onTriggerFn) {
config.onTrigger = onTriggerFn;
}
if (onUntriggerFn) {
config.onUntrigger = onUntriggerFn;
}
window.tippy(el, config);
}
const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]');
for (var i=0; i<noterefs.length; i++) {
const ref = noterefs[i];
tippyHover(ref, function() {
// use id or data attribute instead here
let href = ref.getAttribute('data-footnote-href') || ref.getAttribute('href');
try { href = new URL(href).hash; } catch {}
const id = href.replace(/^#\/?/, "");
const note = window.document.getElementById(id);
if (note) {
return note.innerHTML;
} else {
return "";
}
});
}
const xrefs = window.document.querySelectorAll('a.quarto-xref');
const processXRef = (id, note) => {
// Strip column container classes
const stripColumnClz = (el) => {
el.classList.remove("page-full", "page-columns");
if (el.children) {
for (const child of el.children) {
stripColumnClz(child);
}
}
}
stripColumnClz(note)
if (id === null || id.startsWith('sec-')) {
// Special case sections, only their first couple elements
const container = document.createElement("div");
if (note.children && note.children.length > 2) {
container.appendChild(note.children[0].cloneNode(true));
for (let i = 1; i < note.children.length; i++) {
const child = note.children[i];
if (child.tagName === "P" && child.innerText === "") {
continue;
} else {
container.appendChild(child.cloneNode(true));
break;
}
}
if (window.Quarto?.typesetMath) {
window.Quarto.typesetMath(container);
}
return container.innerHTML
} else {
if (window.Quarto?.typesetMath) {
window.Quarto.typesetMath(note);
}
return note.innerHTML;
}
} else {
// Remove any anchor links if they are present
const anchorLink = note.querySelector('a.anchorjs-link');
if (anchorLink) {
anchorLink.remove();
}
if (window.Quarto?.typesetMath) {
window.Quarto.typesetMath(note);
}
if (note.classList.contains("callout")) {
return note.outerHTML;
} else {
return note.innerHTML;
}
}
}
for (var i=0; i<xrefs.length; i++) {
const xref = xrefs[i];
tippyHover(xref, undefined, function(instance) {
instance.disable();
let url = xref.getAttribute('href');
let hash = undefined;
if (url.startsWith('#')) {
hash = url;
} else {
try { hash = new URL(url).hash; } catch {}
}
if (hash) {
const id = hash.replace(/^#\/?/, "");
const note = window.document.getElementById(id);
if (note !== null) {
try {
const html = processXRef(id, note.cloneNode(true));
instance.setContent(html);
} finally {
instance.enable();
instance.show();
}
} else {
// See if we can fetch this
fetch(url.split('#')[0])
.then(res => res.text())
.then(html => {
const parser = new DOMParser();
const htmlDoc = parser.parseFromString(html, "text/html");
const note = htmlDoc.getElementById(id);
if (note !== null) {
const html = processXRef(id, note);
instance.setContent(html);
}
}).finally(() => {
instance.enable();
instance.show();
});
}
} else {
// See if we can fetch a full url (with no hash to target)
// This is a special case and we should probably do some content thinning / targeting
fetch(url)
.then(res => res.text())
.then(html => {
const parser = new DOMParser();
const htmlDoc = parser.parseFromString(html, "text/html");
const note = htmlDoc.querySelector('main.content');
if (note !== null) {
// This should only happen for chapter cross references
// (since there is no id in the URL)
// remove the first header
if (note.children.length > 0 && note.children[0].tagName === "HEADER") {
note.children[0].remove();
}
const html = processXRef(null, note);
instance.setContent(html);
}
}).finally(() => {
instance.enable();
instance.show();
});
}
}, function(instance) {
});
}
let selectedAnnoteEl;
const selectorForAnnotation = ( cell, annotation) => {
let cellAttr = 'data-code-cell="' + cell + '"';
let lineAttr = 'data-code-annotation="' + annotation + '"';
const selector = 'span[' + cellAttr + '][' + lineAttr + ']';
return selector;
}
const selectCodeLines = (annoteEl) => {
const doc = window.document;
const targetCell = annoteEl.getAttribute("data-target-cell");
const targetAnnotation = annoteEl.getAttribute("data-target-annotation");
const annoteSpan = window.document.querySelector(selectorForAnnotation(targetCell, targetAnnotation));
const lines = annoteSpan.getAttribute("data-code-lines").split(",");
const lineIds = lines.map((line) => {
return targetCell + "-" + line;
})
let top = null;
let height = null;
let parent = null;
if (lineIds.length > 0) {
//compute the position of the single el (top and bottom and make a div)
const el = window.document.getElementById(lineIds[0]);
top = el.offsetTop;
height = el.offsetHeight;
parent = el.parentElement.parentElement;
if (lineIds.length > 1) {
const lastEl = window.document.getElementById(lineIds[lineIds.length - 1]);
const bottom = lastEl.offsetTop + lastEl.offsetHeight;
height = bottom - top;
}
if (top !== null && height !== null && parent !== null) {
// cook up a div (if necessary) and position it
let div = window.document.getElementById("code-annotation-line-highlight");
if (div === null) {
div = window.document.createElement("div");
div.setAttribute("id", "code-annotation-line-highlight");
div.style.position = 'absolute';
parent.appendChild(div);
}
div.style.top = top - 2 + "px";
div.style.height = height + 4 + "px";
div.style.left = 0;
let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter");
if (gutterDiv === null) {
gutterDiv = window.document.createElement("div");
gutterDiv.setAttribute("id", "code-annotation-line-highlight-gutter");
gutterDiv.style.position = 'absolute';
const codeCell = window.document.getElementById(targetCell);
const gutter = codeCell.querySelector('.code-annotation-gutter');
gutter.appendChild(gutterDiv);
}
gutterDiv.style.top = top - 2 + "px";
gutterDiv.style.height = height + 4 + "px";
}
selectedAnnoteEl = annoteEl;
}
};
const unselectCodeLines = () => {
const elementsIds = ["code-annotation-line-highlight", "code-annotation-line-highlight-gutter"];
elementsIds.forEach((elId) => {
const div = window.document.getElementById(elId);
if (div) {
div.remove();
}
});
selectedAnnoteEl = undefined;
};
// Handle positioning of the toggle
window.addEventListener(
"resize",
throttle(() => {
elRect = undefined;
if (selectedAnnoteEl) {
selectCodeLines(selectedAnnoteEl);
}
}, 10)
);
function throttle(fn, ms) {
let throttle = false;
let timer;
return (...args) => {
if(!throttle) { // first call gets through
fn.apply(this, args);
throttle = true;
} else { // all the others get throttled
if(timer) clearTimeout(timer); // cancel #2
timer = setTimeout(() => {
fn.apply(this, args);
timer = throttle = false;
}, ms);
}
};
}
// Attach click handler to the DT
const annoteDls = window.document.querySelectorAll('dt[data-target-cell]');
for (const annoteDlNode of annoteDls) {
annoteDlNode.addEventListener('click', (event) => {
const clickedEl = event.target;
if (clickedEl !== selectedAnnoteEl) {
unselectCodeLines();
const activeEl = window.document.querySelector('dt[data-target-cell].code-annotation-active');
if (activeEl) {
activeEl.classList.remove('code-annotation-active');
}
selectCodeLines(clickedEl);
clickedEl.classList.add('code-annotation-active');
} else {
// Unselect the line
unselectCodeLines();
clickedEl.classList.remove('code-annotation-active');
}
});
}
const findCites = (el) => {
const parentEl = el.parentElement;
if (parentEl) {
const cites = parentEl.dataset.cites;
if (cites) {
return {
el,
cites: cites.split(' ')
};
} else {
return findCites(el.parentElement)
}
} else {
return undefined;
}
};
var bibliorefs = window.document.querySelectorAll('a[role="doc-biblioref"]');
for (var i=0; i<bibliorefs.length; i++) {
const ref = bibliorefs[i];
const citeInfo = findCites(ref);
if (citeInfo) {
tippyHover(citeInfo.el, function() {
var popup = window.document.createElement('div');
citeInfo.cites.forEach(function(cite) {
var citeDiv = window.document.createElement('div');
citeDiv.classList.add('hanging-indent');
citeDiv.classList.add('csl-entry');
var biblioDiv = window.document.getElementById('ref-' + cite);
if (biblioDiv) {
citeDiv.innerHTML = biblioDiv.innerHTML;
}
popup.appendChild(citeDiv);
});
return popup.innerHTML;
});
}
}
});
</script>
</div> <!-- /content -->
</body></html>