Compare commits

...

6 commits

Author SHA1 Message Date
bbad13f570 add config.json again, part 2
All checks were successful
ci / build (push) Successful in 1m6s
2025-04-17 16:11:34 +02:00
628855b5d9 add config.json again
All checks were successful
ci / build (push) Successful in 1m11s
2025-04-17 14:46:32 +02:00
b61a166649 make unit tests runnable again
All checks were successful
ci / build (push) Successful in 1m8s
2025-04-17 13:50:24 +02:00
1d62a62f33 run locally part 1.0
All checks were successful
ci / build (push) Successful in 1m6s
2025-04-16 15:37:16 +02:00
f5265a4e1d run in docker part 1.0
All checks were successful
ci / build (push) Successful in 1m7s
2025-04-16 13:53:25 +02:00
1ddfc8c769 add http-server part 9.1
All checks were successful
ci / build (push) Successful in 1m9s
2025-04-15 16:28:48 +02:00
13 changed files with 144 additions and 45 deletions

View file

@ -11,16 +11,9 @@ This app will send an I-Ching horoscope to the pre-configured mailhog instance i
This app uses the I-Ching library app https://github.com/Velfi/i-ching.git.
<!--## Configuration
## Configuration
Properties of the app can be configured in the file config.json. It is possible to configure the mail host and port.
It is also possible to configure the intervall of days between the sending of horoscopes and the time of sending. The default is to send one email every seven days at 8 am.-->
## First Start
The app can be deployed by running:
```bash
$ kubectl apply -f deployment.yaml
```
It is also possible to configure the intervall of days between the sending of horoscopes and the time of sending. The default is to send one email every seven days at 8 am.
This will start the deployment of the app in the pod, the service and an ingress for the HTTP frontend server and an ingress for the backend server.
When a pod with the app is initally started, one email will be sent to the configured receiver.
@ -38,10 +31,35 @@ The backend server has a separate ingress, so API requests can get forwarded to
The frontend server is running inside the pod on the address http://localhost:8080/iching. It provides an HTML homepage with a button. Upon pressing the button, the backend server will be called (to send an E-Mail).
The frontend server has a separate ingress from the backend server, since the frontend server needs the 'rewrite target' annotation: So the Frontend is not running in the root folder but using the sub-path 'iching'.
<!--## Testing
## Testing
The Jest unit tests can be run with
```bash
$ npm test
```
-->
## Running the App
### Locally
The app can be deployed locally by running:
The app can be deployed by running:
```bash
$ ./start-local.sh
```
Der HTTP-Server can be access in a browser with the address http://localhost:8080/.
### Locally with Docker
The app can be deployed by running:
```bash
$ docker run -e DEPLOY_MODE=docker -p 8080:8080 -p 8090:8090 --rm my-typescript-app
```
Der HTTP-Server can be access in a browser with the address http://localhost:8080/.
### In Kubernetes
The app can be deployed by running:
```bash
$ kubectl apply -f deployment.yaml
```
Der HTTP-Server can be access in a browser with the address https://192-168-197-2.c-one-infra.de/iching/

View file

@ -1,5 +1,5 @@
/*import { html } from "./broker";
import { test } from "@jest/globals";
import { html } from "./broker";
//import { test } from "@jest/globals";
jest.useFakeTimers();
@ -17,4 +17,3 @@ test("Generate HTML for images", () => {
expect(html("Images:\nOver the earth, the lake:\nThe image of Gathering Together.\nThus the superior man renews his weapons\nIn order to meet the unforseen."))
.toBe("<p><h3>Images:</h3>Over the earth, the lake:<br>The image of Gathering Together.<br>Thus the superior man renews his weapons<br>In order to meet the unforseen.<br></p></div>");
});
*/

View file

@ -1,13 +1,15 @@
import { exec } from 'child_process';
import * as fs from 'fs';
import * as nodemailer from 'nodemailer';
import { fileURLToPath } from 'url';
import * as path from 'path';
// Config for scheduling the next time the main process (sending of horoscope) is run
/*interface Config {
interface Config {
emailReceiver: string;
mailHost: string;
mailPort: number;
}*/
}
// Node structure for the Parse Tree (it's a degenerated tree with one or zero children per node)
interface Node {
@ -16,13 +18,13 @@ interface Node {
value?: string;
}
//var config : Config;
var config : Config;
export function executeCommand(): void {
exec('iching divine', (error, stdout, stderr) => {
console.log(`Begin`);
console.log(`I-Ching-Broker: \'iching divine\' called`);
if (error) {
console.error(`Error: ${error.message}`);
@ -35,11 +37,11 @@ export function executeCommand(): void {
}
//Load config once
/*if (config == undefined) {
if (config == undefined) {
config = loadConfig();
} */
}
console.log(`Send E-Mail`);
console.log(`Horoscope received; sending E-Mail next`);
sendEmail(stdout);
});
}
@ -48,36 +50,43 @@ export function executeCommand(): void {
executeCommand();
// Load the Configuration
/*function loadConfig(): Config {
function loadConfig(): Config {
console.log(`Load Config`);
const data = fs.readFileSync('config.json', 'utf-8');
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const configPath = path.join(__dirname, 'config.json');
const data = fs.readFileSync(configPath, 'utf-8');
return JSON.parse(data);
}*/
}
// Send E-Mail
async function sendEmail(content: string) {
// Create Transporter
const transporter = nodemailer.createTransport({
host: "mailhog.mailhog.svc.cluster.local", //config.mailHost,
port: 1025, //config.mailPort,
host: config.mailHost, //"mailhog.mailhog.svc.cluster.local", //config.mailHost,
port: config.mailPort, //1025, //config.mailPort,
secure: false
});
try {
const info = await transporter.sendMail({
from: '"The Oracle" <the.oracle@holy.mountain>',
to: "test@mailhog.local", //config.emailReceiver,
to: config.emailReceiver, //"test@mailhog.local", //config.emailReceiver,
subject: "Your Horoscope Is Ready",
text: content,
html: html(content)
});
console.log("E-Mail sent: ", info.messageId + "\n");
console.log("E-Mail sent: ", info.messageId + "\n\n");
} catch (error) {
console.error("Error Sending E-Mail:", error);
console.error("Error Sending E-Mail:", error + "\n\n");
console.log("Failed to send this horoscope: \n", content + "\n");
}
}
@ -119,7 +128,7 @@ function parse(input: string): Node {
const changingLines: Node = { type: "ChangingLines"};
currentNode.child = changingLines;
currentNode = changingLines;
currentNode.value = line; // + "<br>"; TODO: try without this <br>
currentNode.value = line;
} else {
currentNode.value = currentNode.value + line + "<br>";
}
@ -132,13 +141,11 @@ function parse(input: string): Node {
function render(node: Node): string {
if (node == undefined) {
console.log("Rendering of nodes finished!")
console.log("I-Ching-Broker: Rendering of nodes finished!")
return "";
}
console.log("Render node" + node.type);
var outputHTML: string = "";
switch (node.type) {
@ -161,7 +168,6 @@ function render(node: Node): string {
case "ChangingLines" :
const regex = new RegExp("~", "g");
node.value = node.value?.replace(regex, "");
//outputHTML = "<br><p><div style=\"border: 1px dashed gray; border-radius: 10px; padding-left: 10px; padding-right: 10px; padding-top: 10px; width: auto; display: inline-block; color: gray; text-align: center; box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.2);\">" + node.value + "</div></p><br>";
outputHTML = "<br><br><p><div style=\"padding-left: 20px; padding-right: 20px; padding-top: 20px; display: inline-block; color: gray; text-align: center;\">" + node.value + "</div></p><br>";
outputHTML = outputHTML + render(node.child!);
return outputHTML;

View file

@ -8,10 +8,13 @@ const port = 8090;
app.use(cors());
app.post('/iching/api/command', (req, res) => {
//TODO no logging from inside this method???
console.log(`Backend-Server: receiving POST to /iching/api/command`);
executeCommand();
res.status(200).send('Command executed\n');
res.status(200).send('Backend-Server: Broker was called\n');
});
app.listen(port, '0.0.0.0', () => {
console.log(`Server läuft auf http://0.0.0.0:${port}`);
console.log(`Backend-Server running on http://0.0.0.0:${port}`);
});

View file

@ -1,3 +1,11 @@
#env:
# - name: DEPLOY_MODE
# value: "kubernetes"
# - name: BACKEND_PORT
# value: "8090"
#---
# --- DEPLOYMENT --------------------------------------------
apiVersion: apps/v1

1
frontend/deploy.js Normal file
View file

@ -0,0 +1 @@
window.DEPLOY_MODE = "k8s"

View file

@ -1,10 +1,20 @@
function handleClick(): void {
console.log("Der Button wurde geklickt!");
fetch("/iching/api/command", { method: "POST" })
//const deployMode = (window as { DEPLOY_MODE?: 'docker' | 'k8s' }).DEPLOY_MODE ?? 'k8s';
const deployMode = (window as { DEPLOY_MODE?: 'docker' | 'k8s' | 'local' }).DEPLOY_MODE ?? 'k8s';
const endpoint =
deployMode === 'docker' || deployMode === 'local'
? 'http://localhost:8090/iching/api/command'
: '/iching/api/command';
console.log(`DEPLOY_MODE=${deployMode}, sende POST an: ${endpoint}`);
fetch(endpoint, { method: 'POST' })
.then(res => res.text())
.then(text => console.log("Server sagt:", text))
.catch(error => console.error("Fehler:", error));
.then(text => console.log("HTTP-Server says:", text))
.catch(error => console.error("HTTP-Server - Error:", error));
}
document.addEventListener("DOMContentLoaded", () => {

View file

@ -53,12 +53,15 @@
</head>
<body>
<div class="container">
<h1>🧘 My I-Ging Horoscope</h1>
<h1>Do you want to know the future?</h1>
<h2> ☝️ Look into the future with your personal I-Ging Horoscope! ☝️</h2>
<p>Click on the Button to receive your personal Horoscope. 100% accuracy guaranteed!</p>
<button id="myButton">✨ Receive Horoscope ✨</button>
<div class="footer">With Love & Fortune from the Cosmos 💫</div>
<div class="footer">🧘 With Love & Fortune from the Cosmos 💫</div>
</div>
<script src="./deploy.js"></script>
<script type="module" src="./event.mjs"></script>
</body>
</html>

View file

@ -24,5 +24,9 @@
"express": "^5.1.0",
"http-server": "^14.1.1",
"nodemailer": "^6.10.0"
}
},
"jest": {
"preset": "ts-jest",
"testEnvironment": "node"
}
}

38
start-local.sh Executable file
View file

@ -0,0 +1,38 @@
# build
#npm install
#npx tsc -p tsconfig.backend.json
#npx tsc -p tsconfig.frontend.json
#npm run start:local
#!/bin/bash
# Exit on error
set -e
# 1. Install dependencies
npm install
# 2. Compile backend and frontend
npx tsc -p tsconfig.backend.json
npx tsc -p tsconfig.frontend.json
# 3. Ensure dist/frontend exists and copy index.html
mkdir -p dist/frontend
cp frontend/index.html dist/frontend/
mkdir -p dist/backend
cp backend/config.json dist/backend/
# 4. Rename compiled frontend event.js → event.mjs
find dist/frontend -name "event.js" -exec bash -c 'mv "$0" "${0%.js}.mjs"' {} \;
# 5. Inject deploy mode
echo "window.DEPLOY_MODE = 'local';" > dist/frontend/deploy.js
# 6. Start backend
node dist/backend/server.js &
# 7. Start frontend
npx http-server dist/frontend -p 8080 --cors --mime application/javascript=js

10
start.sh Normal file → Executable file
View file

@ -1,3 +1,13 @@
#!/bin/bash
# Setze Standard-Wert auf "k8s" wenn DEPLOY_MODE nicht gesetzt ist
DEPLOY_MODE=${DEPLOY_MODE:-k8s}
echo "DEPLOY_MODE ist: $DEPLOY_MODE"
# Injektiere ins Frontend
echo "window.DEPLOY_MODE = \"$DEPLOY_MODE\";" > dist/frontend/deploy.js
# start backend in the background
node dist/backend/server.js &

View file

@ -8,7 +8,6 @@
"esModuleInterop": true,
"skipLibCheck": true
},
"include": [
"backend/**/*.ts"
]
"include": ["backend/**/*.ts"],
"exclude": ["backend/**/*.test.ts"]
}