main
Simon Moser vor 1 Monat
Commit f22d2cd83c

8
.idea/.gitignore vendored

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="letter_data.db" uuid="53ed27a5-d5a3-4e0e-8f9c-ee4b0e04ddbb">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/letter_data.db</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PublishConfigData" remoteFilesAllowedToDisappearOnAutoupload="false">
<serverData>
<paths name="mosers@localhost:4222 agent">
<serverdata>
<mappings>
<mapping local="$PROJECT_DIR$" web="/" />
</mappings>
</serverdata>
</paths>
<paths name="mosers@localhost:4222 agent (2)">
<serverdata>
<mappings>
<mapping local="$PROJECT_DIR$" web="/" />
</mappings>
</serverdata>
</paths>
</serverData>
</component>
</project>

@ -0,0 +1,34 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="GrazieInspection" enabled="false" level="GRAMMAR_ERROR" enabled_by_default="false" />
<inspection_tool class="LanguageDetectionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredPackages">
<value>
<list size="6">
<item index="0" class="java.lang.String" itemvalue="rich-pixels" />
<item index="1" class="java.lang.String" itemvalue="matplotlib" />
<item index="2" class="java.lang.String" itemvalue="pillow" />
<item index="3" class="java.lang.String" itemvalue="rich" />
<item index="4" class="java.lang.String" itemvalue="numpy" />
<item index="5" class="java.lang.String" itemvalue="textual" />
</list>
</value>
</option>
</inspection_tool>
<inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoredErrors">
<list>
<option value="N806" />
</list>
</option>
</inspection_tool>
<inspection_tool class="QodanaSanity" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
</profile>
</component>

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.11" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11" project-jdk-type="Python SDK" />
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/tharanor-letter.iml" filepath="$PROJECT_DIR$/.idea/tharanor-letter.iml" />
</modules>
</component>
</project>

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="file://$PROJECT_DIR$/tharanor-letter.py" dialect="GenericSQL" />
<file url="PROJECT" dialect="SQLite" />
</component>
</project>

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.idea" />
</content>
<orderEntry type="jdk" jdkName="Python 3.11" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Jinja2" />
<option name="TEMPLATE_FOLDERS">
<list>
<option value="$MODULE_DIR$/templates" />
</list>
</option>
</component>
</module>

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="WebResourcesPaths">
<contentEntries>
<entry url="file://$PROJECT_DIR$">
<entryData>
<resourceRoots>
<path value="file://$PROJECT_DIR$/static" />
</resourceRoots>
</entryData>
</entry>
</contentEntries>
</component>
</project>

@ -0,0 +1,25 @@
import sqlite3
DB_PATH = 'letter_data.db'
def init_db():
with sqlite3.connect(DB_PATH) as conn:
cur = conn.cursor()
cur.execute('''CREATE TABLE IF NOT EXISTS sender (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
address TEXT,
logo TEXT,
signature TEXT)''')
cur.execute('''CREATE TABLE IF NOT EXISTS state (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT)''')
cur.execute('''CREATE TABLE IF NOT EXISTS recipient (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
address TEXT,
state_id INTEGER,
FOREIGN KEY(state_id) REFERENCES state(id))''')
init_db()

Binäre Datei nicht angezeigt.

@ -0,0 +1,179 @@
/* CSS for Paged.js interface – v0.4 */
/* Change the look */
:root {
--color-background: whitesmoke;
--color-pageSheet: #cfcfcf;
--color-pageBox: violet;
--color-paper: white;
--color-marginBox: transparent;
--pagedjs-crop-color: black;
--pagedjs-crop-shadow: white;
--pagedjs-crop-stroke: 1px;
}
/* To define how the book look on the screen: */
@media screen, pagedjs-ignore {
body {
background-color: var(--color-background);
}
.pagedjs_pages {
display: flex;
width: calc(var(--pagedjs-width) * 2);
flex: 0;
flex-wrap: wrap;
margin: 0 auto;
}
.pagedjs_page {
background-color: var(--color-paper);
box-shadow: 0 0 0 1px var(--color-pageSheet);
margin: 0;
flex-shrink: 0;
flex-grow: 0;
margin-top: 10mm;
}
.pagedjs_first_page {
margin-left: var(--pagedjs-width);
}
.pagedjs_page:last-of-type {
margin-bottom: 10mm;
}
.pagedjs_pagebox{
box-shadow: 0 0 0 1px var(--color-pageBox);
}
.pagedjs_left_page{
z-index: 20;
width: calc(var(--pagedjs-bleed-left) + var(--pagedjs-pagebox-width))!important;
}
.pagedjs_left_page .pagedjs_bleed-right .pagedjs_marks-crop {
border-color: transparent;
}
.pagedjs_left_page .pagedjs_bleed-right .pagedjs_marks-middle{
width: 0;
}
.pagedjs_right_page{
z-index: 10;
position: relative;
left: calc(var(--pagedjs-bleed-left)*-1);
}
/* show the margin-box */
.pagedjs_margin-top-left-corner-holder,
.pagedjs_margin-top,
.pagedjs_margin-top-left,
.pagedjs_margin-top-center,
.pagedjs_margin-top-right,
.pagedjs_margin-top-right-corner-holder,
.pagedjs_margin-bottom-left-corner-holder,
.pagedjs_margin-bottom,
.pagedjs_margin-bottom-left,
.pagedjs_margin-bottom-center,
.pagedjs_margin-bottom-right,
.pagedjs_margin-bottom-right-corner-holder,
.pagedjs_margin-right,
.pagedjs_margin-right-top,
.pagedjs_margin-right-middle,
.pagedjs_margin-right-bottom,
.pagedjs_margin-left,
.pagedjs_margin-left-top,
.pagedjs_margin-left-middle,
.pagedjs_margin-left-bottom {
box-shadow: 0 0 0 1px inset var(--color-marginBox);
}
/* uncomment this part for recto/verso book : ------------------------------------ */
.pagedjs_pages {
flex-direction: column;
width: 100%;
}
.pagedjs_first_page {
margin-left: 0;
}
.pagedjs_page {
margin: 0 auto;
margin-top: 10mm;
}
.pagedjs_left_page{
width: calc(var(--pagedjs-bleed-left) + var(--pagedjs-pagebox-width) + var(--pagedjs-bleed-left))!important;
}
.pagedjs_left_page .pagedjs_bleed-right .pagedjs_marks-crop{
border-color: var(--pagedjs-crop-color);
}
.pagedjs_left_page .pagedjs_bleed-right .pagedjs_marks-middle{
width: var(--pagedjs-cross-size)!important;
}
.pagedjs_right_page{
left: 0;
}
/*--------------------------------------------------------------------------------------*/
/* uncomment this par to see the baseline : -------------------------------------------*/
/* .pagedjs_pagebox {
--pagedjs-baseline: 22px;
--pagedjs-baseline-position: 5px;
--pagedjs-baseline-color: cyan;
background: linear-gradient(transparent 0%, transparent calc(var(--pagedjs-baseline) - 1px), var(--pagedjs-baseline-color) calc(var(--pagedjs-baseline) - 1px), var(--pagedjs-baseline-color) var(--pagedjs-baseline)), transparent;
background-size: 100% var(--pagedjs-baseline);
background-repeat: repeat-y;
background-position-y: var(--pagedjs-baseline-position);
} */
/*--------------------------------------------------------------------------------------*/
}
/* Marks (to delete when merge in paged.js) */
.pagedjs_marks-crop{
z-index: 999999999999;
}
.pagedjs_bleed-top .pagedjs_marks-crop,
.pagedjs_bleed-bottom .pagedjs_marks-crop{
box-shadow: 1px 0px 0px 0px var(--pagedjs-crop-shadow);
}
.pagedjs_bleed-top .pagedjs_marks-crop:last-child,
.pagedjs_bleed-bottom .pagedjs_marks-crop:last-child{
box-shadow: -1px 0px 0px 0px var(--pagedjs-crop-shadow);
}
.pagedjs_bleed-left .pagedjs_marks-crop,
.pagedjs_bleed-right .pagedjs_marks-crop{
box-shadow: 0px 1px 0px 0px var(--pagedjs-crop-shadow);
}
.pagedjs_bleed-left .pagedjs_marks-crop:last-child,
.pagedjs_bleed-right .pagedjs_marks-crop:last-child{
box-shadow: 0px -1px 0px 0px var(--pagedjs-crop-shadow);
}

@ -0,0 +1,116 @@
body {
margin: 0;
padding: 10px;
}
#textInput {
width: calc(100% - 20px);
max-width: 800px;
}
.form-grid {
display: grid;
grid-template-columns: max-content;
grid-row-gap: 5px;
grid-column-gap: 1em;
align-items: center;
margin-bottom: 2em;
}
select, button, textarea {
padding: 5px 10px;
background-color: transparent;
border: 1px solid #ccc;
border-radius: 4px;
transition: background 0.3s;
}
select, button {
cursor: pointer;
}
button.mini {
font-family: serif;
background: none;
border: none;
width: 1.5em;
padding-left: 4px;
border-radius: 4px;
}
button:hover {
background-color: #ddd;
}
label {
white-space: nowrap;
}
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
}
.modal img, .modal .modal-edit {
display: none;
}
.modal.edit img {
display: inline;
}
.modal.edit .modal-edit {
display: block;
}
.modal.edit .modal-add {
display: none;
}
.modal-content {
background: white;
padding: 1em;
max-width: 400px;
margin: 10% auto;
border-radius: 5px;
}
.modal-content input, .modal-content select, .modal-content button, .modal-content textarea {
display: block;
width: 100%;
margin-bottom: 0.5em;
}
.modal.active {
display: block;
}
@media screen and (min-width: 450px) {
body {
padding: 15px;
}
#textInput {
width: calc(100% - 30px);
}
.form-grid {
grid-template-columns: max-content max-content;
}
}
@media screen and (min-width: 660px) {
body {
padding: 20px;
}
#textInput {
width: calc(100% - 40px);
}
}

@ -0,0 +1,56 @@
@font-face {
src: url("/static/fonts/jorvik-informal.regular.TTF");
font-family: Jorvik;
}
@font-face {
src: url("/static/fonts/Celtica-Bold.ttf");
font-family: Celtica;
}
body {
font-size: 13pt;
}
body, button {
font-family: Jorvik, fantasy;
}
@page {
size: A4;
margin: 20mm 20mm 30mm 20mm;
background: no-repeat center/100% url("/static/uploads/bg.jpg");
}
div.siegel {
text-align: center;
}
p.kuralie, p.content {
initial-letter: 2;
}
p.content {
white-space: pre-line;
text-align: justify;
hyphens: auto;
text-align-last: left;
text-wrap: balance;
}
p.kuralie::first-letter, p.content::first-letter {
font-size: 200%;
padding: 4px 2px;
margin-right: 4px;
float: left;
font-family: Celtica, fantasy;
}
@media screen {
.pagedjs_page {
width: 100%;
height: 100%;
max-width: var(--pagedjs-width-right);
max-height: var(--pagedjs-height-right);
}
}

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Dateidiff unterdrückt, weil mindestens eine Zeile zu lang ist

Binäre Datei nicht angezeigt.

Nachher

Breite:  |  Höhe:  |  Größe: 254 KiB

Binäre Datei nicht angezeigt.

Nachher

Breite:  |  Höhe:  |  Größe: 521 KiB

Binäre Datei nicht angezeigt.

Nachher

Breite:  |  Höhe:  |  Größe: 468 KiB

Binäre Datei nicht angezeigt.

Nachher

Breite:  |  Höhe:  |  Größe: 42 KiB

Binäre Datei nicht angezeigt.

Nachher

Breite:  |  Höhe:  |  Größe: 7.7 KiB

Binäre Datei nicht angezeigt.

Nachher

Breite:  |  Höhe:  |  Größe: 20 KiB

Binäre Datei nicht angezeigt.

Nachher

Breite:  |  Höhe:  |  Größe: 13 KiB

@ -0,0 +1,228 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Schreiberling</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🪶</text></svg>">
<link href="/static/css/tharanor.form.css" type="text/css" rel="stylesheet">
<link href="/static/css/tharanor.letter.css" type="text/css" rel="stylesheet">
</head>
<body>
<h1>Schreiberling des Rates der Gilden</h1>
<form action="/generate" method="post">
<div class="form-grid">
<label for="senderSelect">Verfasser:</label>
<span>
<select name="sender" id="senderSelect">
{% for s in senders %}
<option value="{{ s.id }}">{{ s.name }}</option>
{% endfor %}
</select>
<button type="button" class="mini add" onclick="openModalAdd('senderModal')"></button>
<button type="button" class="mini edit" onclick="openModalEdit('sender')"></button>
</span>
<label for="stateSelect">Reich:</label>
<span>
<select id="stateSelect">
<option value="all">-- Alle --</option>
{% for s in states %}
<option value="{{ s.id }}">{{ s.name }}</option>
{% endfor %}
</select>
<button type="button" class="mini add" onclick="openModalAdd('stateModal')"></button>
<button type="button" class="mini edit" onclick="openModalEdit('state')"></button>
</span>
<label for="recipientSelect">Empfänger:</label>
<span>
<select name="recipient" id="recipientSelect">
{% for r in recipients %}
<option value="{{ r.id }}">{{ r.name }}</option>
{% endfor %}
</select>
<button type="button" class="mini add" onclick="openModalAdd('recipientModal')"></button>
<button type="button" class="mini edit" onclick="openModalEdit('recipient')"></button>
</span>
</div>
<label for="textInput">Brieftext:</label><br>
<textarea name="text" id="textInput"></textarea><br><br>
<button type="submit">Brief in Auftrag geben</button>
</form>
<!-- Sender Modal -->
<div id="senderModal" class="modal">
<div class="modal-content">
<h3 class="modal-add">Neuen Verfasser anlegen</h3>
<h3 class="modal-edit">Verfasser editieren</h3>
<form id="senderForm">
<input type="hidden" name="id">
<label>
Name:<br>
<input name="name" placeholder="Name" required>
</label>
<label>
Kuralie:<br>
<textarea name="kuralie" placeholder="Kuralie" rows="4"></textarea>
</label>
<label>
Siegel:<br>
<img id="siegelPreview" src="" height="100" alt="siegel"/>
<input type="file" name="siegel" required>
</label>
<label>
Signatur:<br>
<img id="signaturPreview" src="" width="300" alt="signatur"/>
<input type="file" name="signatur" required>
</label>
<button type="submit">Speichern</button>
<button type="button" onclick="closeModal('senderModal')">Abbrechen</button>
</form>
</div>
</div>
<!-- State Modal -->
<div id="stateModal" class="modal">
<div class="modal-content">
<h3 class="modal-add">Neues Reich anlegen</h3>
<h3 class="modal-edit">Reich editieren</h3>
<form id="stateForm">
<input type="hidden" name="id">
<label>
Name:<br>
<input name="name" placeholder="Name" required>
</label>
<button type="submit">Speichern</button>
<button type="button" onclick="closeModal('stateModal')">Abbrechen</button>
</form>
</div>
</div>
<!-- Recipient Modal -->
<div id="recipientModal" class="modal">
<div class="modal-content">
<h3 class="modal-add">Neuen Korrespondent anlegen</h3>
<h3 class="modal-edit">Korrespondent editieren</h3>
<form id="recipientForm">
<input type="hidden" name="id">
<label>
Name:<br>
<input name="name" placeholder="Name" required>
</label>
<label>
Kuralie:<br>
<textarea name="kuralie" placeholder="Kuralie" rows="4"></textarea>
</label>
<label>
Reich:<br>
<select name="state_id">
<option value="">-- Kein Reich --</option>
{% for state in states %}
<option value="{{ state.id }}">{{ state.name }}</option>
{% endfor %}
</select>
</label>
<button type="submit">Speichern</button>
<button type="button" onclick="closeModal('recipientModal')">Abbrechen</button>
</form>
</div>
</div>
<script>
document.querySelectorAll(".modal-content form").forEach(f => f.addEventListener('submit', modalEvent));
function openModalAdd(id) {
document.getElementById(id).classList.add('active');
}
function openModalEdit(type) {
const select = document.getElementById(`${type}Select`);
const selected_id = select.value;
fetch(`/api/${type}s/${selected_id}`)
.then(res => res.json())
.then(x => {
const form = document.getElementById(`${type}Form`);
Object.entries(x)
.filter(([k]) => !["siegel", "signatur"].includes(k))
.forEach(([k, v]) => form[k].value = v);
if (type === "sender") {
document.getElementById('siegelPreview').src = `/static/uploads/${x.siegel}`;
document.getElementById('signaturPreview').src = `/static/uploads/${x.signatur}`;
}
document.getElementById(`${type}Modal`).classList.add('active', "edit");
});
}
function closeModal(id) {
document.getElementById(id).getElementsByTagName("form")[0].reset()
document.getElementById(id).classList.remove('active', "edit");
}
function modalEvent(e) {
e.preventDefault();
const data = new FormData(e.target);
const modal = e.target.parentNode.parentNode;
const type = modal.id.replace("Modal", "");
const is_edit = modal.classList.contains("edit");
const id = data.get("id");
data.delete("id");
fetch(is_edit ? `/api/${type}s/${id}` : `/api/${type}s`, {
method: is_edit ? 'PUT' : 'POST',
body: data
}).then(res => res.json()).then(() => {
closeModal(`${type}Modal`);
refreshDropdown(type);
});
}
document.getElementById('stateSelect').addEventListener('change', function () {
const stateId = this.value;
fetch(`/api/recipients?state_id=${stateId}`)
.then(response => response.json())
.then(data => {
const recipientSelect = document.getElementById('recipientSelect');
recipientSelect.innerHTML = '';
data.forEach(r => {
const option = document.createElement('option');
option.value = r.id;
option.textContent = r.name;
recipientSelect.appendChild(option);
});
});
});
function refreshDropdown(type) {
fetch(`/api/${type}s`)
.then(res => res.json())
.then(data => {
const select = document.getElementById(`${type}Select`);
const current = select.value;
select.innerHTML = (type === "state") ? '<option value="all">-- Alle --</option>' : '';
let modalSelect;
if (type === 'state') {
modalSelect = document.querySelector('#recipientModal select[name="state_id"]');
modalSelect.innerHTML = '<option value="">-- Kein Reich --</option>';
}
data.forEach(s => {
const opt = document.createElement('option');
opt.value = s.id;
opt.textContent = s.name;
select.appendChild(opt);
if (type === 'state') {
modalSelect.appendChild(opt.cloneNode(true));
}
});
select.value = current;
});
}
</script>
</body>
</html>

@ -0,0 +1,23 @@
<!doctype html>
<html lang="de">
<head>
<title>{{ recipient.state_name }} | {{ recipient.name }}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="/static/css/interface.css" rel="stylesheet" type="text/css">
<link href="/static/css/tharanor.letter.css" rel="stylesheet" type="text/css">
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🪶</text></svg>">
<script src="/static/js/paged.polyfill.min.js"></script>
</head>
<body>
<div class="siegel">
<img class="siegel" src="{{ url_for('static', filename='uploads/' + sender.siegel) }}" alt="Siegel" height="120">
</div>
<p class="kuralie">{{ sender.kuralie }}</p>
<p class="kuralie">{{ recipient.kuralie }}<br>
<br>
<p class="content">{{ text | e }}</p>
<div class="signatur">
<img class="signatur" src="{{ url_for('static', filename='uploads/' + sender.signatur) }}" alt="Signatur" width="300">
</div>
</body>
</html>

@ -0,0 +1,156 @@
from flask import Flask, render_template, request, jsonify
from werkzeug.utils import secure_filename
from pathlib import Path
import os
import sqlite3
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = Path('./static/uploads')
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
DB_PATH = 'letter_data.db'
def query_db(query, args=(), one=False):
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
cur = conn.cursor()
cur.execute(query, args)
rv = cur.fetchall()
conn.commit()
conn.close()
return (rv[0] if rv else None) if one else rv
@app.route('/')
def index():
senders = query_db('SELECT * FROM sender')
recipients = query_db('SELECT * FROM recipient')
states = query_db('SELECT * FROM state')
return render_template('index.html', senders=senders, recipients=recipients, states=states)
@app.route('/generate', methods=['POST'])
def generate():
sender_id = request.form['sender']
recipient_id = request.form['recipient']
text = request.form['text']
sender = query_db('SELECT * FROM sender WHERE id = ?', [sender_id], one=True)
recipient = query_db('SELECT recipient.*, state.name as state_name FROM recipient LEFT JOIN state ON recipient.state_id = state.id WHERE recipient.id = ?', [recipient_id], one=True)
return render_template('letter.html', sender=sender, recipient=recipient, text=text)
@app.route('/api/senders', methods=['POST'])
def api_add_sender():
name = request.form['name']
kuralie = request.form['kuralie']
siegel = request.files['logo']
signatur = request.files['signature']
siegel_filename = secure_filename(siegel.filename)
siegel.save(app.config['UPLOAD_FOLDER'] / siegel)
sig_filename = secure_filename(signatur.filename)
signatur.save(app.config['UPLOAD_FOLDER'] / sig_filename)
query_db('INSERT INTO sender (name, kuralie, siegel, signatur) VALUES (?, ?, ?, ?)',
[name, kuralie, siegel_filename, sig_filename])
return jsonify(success=True)
@app.route('/api/recipients', methods=['POST'])
def api_add_recipient():
name = request.form['name']
kuralie = request.form['kuralie']
state_id = request.form['state_id'] or None
query_db('INSERT INTO recipient (name, kuralie, state_id) VALUES (?, ?, ?)', [name, kuralie, state_id])
return jsonify(success=True)
@app.route('/api/states', methods=['POST'])
def api_add_state():
name = request.form['name']
query_db('INSERT INTO state (name) VALUES (?)', [name])
return jsonify(success=True)
@app.route('/api/senders', methods=['GET'])
def api_get_senders():
senders = query_db('SELECT id, name FROM sender')
return jsonify([dict(row) for row in senders])
@app.route('/api/senders/<int:sender_id>', methods=['GET'])
def api_get_sender(sender_id):
sender = query_db('SELECT * FROM sender WHERE id = ?', [sender_id], one=True)
return jsonify(dict(sender))
@app.route('/api/recipients', methods=['GET'])
def api_get_recipients():
state_id = request.args.get('state_id')
if state_id and state_id != 'all':
recipients = query_db('SELECT id, name FROM recipient WHERE state_id = ?', [state_id])
else:
recipients = query_db('SELECT id, name FROM recipient')
return jsonify([dict(row) for row in recipients])
@app.route('/api/recipients/<int:recipient_id>', methods=['GET'])
def api_get_recipient(recipient_id):
recipient = query_db('SELECT * FROM recipient WHERE id = ?', [recipient_id], one=True)
return jsonify(dict(recipient))
@app.route('/api/states', methods=['GET'])
def api_get_states():
states = query_db('SELECT id, name FROM state')
return jsonify([dict(row) for row in states])
@app.route('/api/states/<int:state_id>', methods=['GET'])
def api_get_state(state_id):
state = query_db('SELECT * FROM state WHERE id = ?', [state_id], one=True)
return jsonify(dict(state))
@app.route('/api/senders/<int:sender_id>', methods=['PUT'])
def api_edit_sender(sender_id):
name = request.form['name']
kuralie = request.form['kuralie']
siegel = request.files['siegel']
signatur = request.files['signatur']
siegel_filename = secure_filename(siegel.filename) if siegel else None
sig_filename = secure_filename(signatur.filename) if signatur else None
if siegel:
siegel.save(app.config['UPLOAD_FOLDER'] / siegel_filename)
if signatur:
signatur.save(app.config['UPLOAD_FOLDER'] / sig_filename)
query_db('UPDATE sender SET name = ?, kuralie = ?, siegel = ?, signatur = ? WHERE id = ?',
[name, kuralie, siegel_filename, sig_filename, sender_id])
return jsonify(success=True)
@app.route('/api/recipients/<int:recipient_id>', methods=['PUT'])
def api_edit_recipient(recipient_id):
name = request.form['name']
kuralie = request.form['kuralie']
state_id = request.form['state_id']
query_db('UPDATE recipient SET name = ?, kuralie = ?, state_id = ? WHERE id = ?',
[name, kuralie, state_id, recipient_id])
return jsonify(success=True)
@app.route('/api/states/<int:state_id>', methods=['PUT'])
def api_edit_state(state_id):
name = request.form['name']
query_db('UPDATE state SET name = ? WHERE id = ?', [name, state_id])
return jsonify(success=True)
if __name__ == '__main__':
app.run(debug=True)
Laden…
Abbrechen
Speichern