6 Commits

Author SHA1 Message Date
jamie 24ab43cceb Merge pull request 'feat: add support for SQLite alongside MySQL' (#5) from v1.2.0 into master
Reviewed-on: #5
2026-01-08 17:32:39 +00:00
jamie 898ad38303 feat: add support for SQLite alongside MySQL
Release / release (pull_request) Successful in 18s
2026-01-08 17:32:10 +00:00
jamie 16ef30de83 Merge pull request 'v1.1.3' (#4) from v1.1.3 into master
Reviewed-on: #4
2026-01-08 17:14:49 +00:00
jamie a45397d003 chore: remove Kubernetes deployment steps from release workflow
Release / release (pull_request) Successful in 24s
2026-01-08 17:14:28 +00:00
jamie eec6fd4b3d chore: remove release-please configuration and related files; update Dockerfile and README for new image registry 2026-01-08 17:13:40 +00:00
jamie 7c3c2cc41e build: 🚀 redeploy 2025-12-20 11:00:22 +00:00
15 changed files with 274 additions and 291 deletions
+46
View File
@@ -0,0 +1,46 @@
name: Release
on:
pull_request:
branches:
- master
types: [closed]
jobs:
release:
if: github.event.pull_request.merged == true && startsWith(github.head_ref, 'v')
runs-on: build-htz-01
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Extract Version
id: get_version
run: echo "VERSION=${{ github.head_ref }}" >> $GITHUB_OUTPUT
- name: Generate Changelog
id: changelog
uses: https://github.com/metcalfc/changelog-generator@v4.6.2
with:
myToken: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Docker image
run: |
VERSION=${{ steps.get_version.outputs.VERSION }}
docker build -t cr.jdbnet.co.uk/public/echolog:$VERSION \
-t cr.jdbnet.co.uk/public/echolog:latest \
--build-arg VERSION=$VERSION \
.
docker push cr.jdbnet.co.uk/public/echolog:$VERSION
docker push cr.jdbnet.co.uk/public/echolog:latest
- name: Create Gitea Release
uses: https://gitea.com/actions/gitea-release-action@v1
with:
tag_name: ${{ steps.get_version.outputs.VERSION }}
name: ${{ steps.get_version.outputs.VERSION }}
body: ${{ steps.changelog.outputs.changelog }}
draft: false
prerelease: false
-72
View File
@@ -1,72 +0,0 @@
name: Release Please
on:
push:
branches:
- master
permissions:
contents: write
pull-requests: write
packages: write
jobs:
release-please:
runs-on: ubuntu-latest
outputs:
release_created: ${{ steps.release.outputs.release_created }}
steps:
- uses: googleapis/release-please-action@v4
id: release
with:
manifest-file: .release-please-manifest.json
config-file: .release-please-config.json
- name: Checkout
if: ${{ steps.release.outputs.release_created }}
uses: actions/checkout@v4
- name: Set up Docker Buildx
if: ${{ steps.release.outputs.release_created }}
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
if: ${{ steps.release.outputs.release_created }}
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Read version
if: ${{ steps.release.outputs.release_created }}
id: version
run: |
VERSION=$(cat VERSION)
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "VERSION=$VERSION" >> $GITHUB_ENV
- name: Build and push Docker image
if: ${{ steps.release.outputs.release_created }}
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
ghcr.io/jdb-net/echolog:${{ env.VERSION }}
ghcr.io/jdb-net/echolog:latest
build-args: |
VERSION=${{ env.VERSION }}
deploy:
name: Deploy to Kubernetes
needs: release-please
if: ${{ needs.release-please.outputs.release_created }}
runs-on: [ k3s-lan-01 ]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Apply manifests
run: |
sudo kubectl replace -f deployment.yml --grace-period=60 --force
+1
View File
@@ -2,3 +2,4 @@ tailwindcss
static/output.css static/output.css
.env .env
__pycache__ __pycache__
echolog.db
-10
View File
@@ -1,10 +0,0 @@
{
"packages": {
".": {
"release-type": "simple",
"version-file": "VERSION"
}
},
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json"
}
-3
View File
@@ -1,3 +0,0 @@
{
".": "1.1.2"
}
-33
View File
@@ -1,33 +0,0 @@
# Changelog
## [1.1.2](https://github.com/JDB-NET/echolog/compare/v1.1.1...v1.1.2) (2025-12-14)
### Bug Fixes
* :bug: docker image in readme ([049271b](https://github.com/JDB-NET/echolog/commit/049271baeeaba9266aeac4603dd9f9588fec4bfb))
## [1.1.1](https://github.com/JDB-NET/echolog/compare/v1.1.0...v1.1.1) (2025-11-02)
### Bug Fixes
* :bug: image name ([7334fb0](https://github.com/JDB-NET/echolog/commit/7334fb0ec6d077ffa1166f1b1d93194635f9454e))
## [1.1.0](https://github.com/JDB-NET/echolog/compare/v1.0.0...v1.1.0) (2025-11-02)
### Features
* :sparkles: add release please ([d2c4e08](https://github.com/JDB-NET/echolog/commit/d2c4e08e397c9e7ab9c30b2c04b04804fb81ad16))
* Added favicon and PWA ([1df4920](https://github.com/JDB-NET/echolog/commit/1df49204065529b07f4f6ba6ae8dfd9f74ba74d8))
* Added public build ([3187cbb](https://github.com/JDB-NET/echolog/commit/3187cbbcc50e57b6f459f7d5c116a49ad5b15f65))
* Added timezone environment variable ([7fb273d](https://github.com/JDB-NET/echolog/commit/7fb273d0abefbeeeaad7b81c244f48501b692f6a))
* Initial Commit ([853e78b](https://github.com/JDB-NET/echolog/commit/853e78b45f7c5d38d67d85468e35122250b1080c))
* streak counter to count how many days a user has posted consecutively ([200bc97](https://github.com/JDB-NET/echolog/commit/200bc973b8f410e9bba3ef2ace668a02ce33ce74))
### Bug Fixes
* environment variable names ([22481e5](https://github.com/JDB-NET/echolog/commit/22481e5af28581784ace8b76f666c5d5ece07c3b))
* Fixed search ([4c6328c](https://github.com/JDB-NET/echolog/commit/4c6328c2e90a368db07de5a2babf8fd3dac1808b))
+3
View File
@@ -1,6 +1,9 @@
FROM python:3.13-slim FROM python:3.13-slim
LABEL org.opencontainers.image.vendor="JDB-NET"
WORKDIR /app WORKDIR /app
COPY . /app COPY . /app
ARG VERSION=unknown
ENV VERSION=${VERSION}
RUN pip install -r requirements.txt \ RUN pip install -r requirements.txt \
&& apt-get update \ && apt-get update \
&& apt-get install curl -y \ && apt-get install curl -y \
+29 -113
View File
@@ -11,65 +11,45 @@ A Flask-based web application for personal homelab journaling. Track your daily
- **Daily Journal Entries**: Create and manage journal entries for any date - **Daily Journal Entries**: Create and manage journal entries for any date
- **Streak Tracking**: Automatically calculates consecutive days with entries - **Streak Tracking**: Automatically calculates consecutive days with entries
- **Search Functionality**: Search by keyword or filter by specific date - **Search Functionality**: Search by keyword or filter by specific date
- **PWA Support**: Progressive Web App with offline capabilities and installable on mobile devices - **PWA Support**: Progressive Web App making this installable on mobile devices
- **Modern UI**: Beautiful gradient design with Tailwind CSS and responsive layout - **Modern UI**: Beautiful gradient design with Tailwind CSS and responsive layout
- **Optional Authentication**: Enable login protection with environment variables - **Optional Authentication**: Enable login protection with environment variables
- **MySQL Backend**: Robust data persistence with MySQL/MariaDB - **Multiple Database Support**: Use MySQL/MariaDB or SQLite for data persistence
- **Docker Ready**: Easy deployment with Docker and Docker Compose - **Docker Ready**: Easy deployment with Docker and Docker Compose
- **Kubernetes Support**: Includes Kubernetes deployment manifest - **Kubernetes Support**: Includes Kubernetes deployment manifest
## Quick Start with Docker ## Quick Start with Docker
### Docker Run For deployment examples, see the [examples folder](examples/).
```bash
docker run -d \
--name echolog \
-p 5000:5000 \
-e MYSQL_HOST=10.10.2.27 \
-e MYSQL_USER=echolog \
-e MYSQL_PASSWORD=your_password \
-e MYSQL_DATABASE=echolog \
-e SECRET_KEY=your_secret_key \
-e TZ=Europe/London \
-e LOGIN_ENABLED=true \
-e LOGIN_USERNAME=admin \
-e LOGIN_PASSWORD=your_password \
ghcr.io/jdb-net/echolog:latest
```
### Docker Compose
```yaml
version: '3.8'
services:
echolog:
image: ghcr.io/jdb-net/echolog:latest
container_name: echolog
restart: unless-stopped
ports:
- "5000:5000"
environment:
- MYSQL_HOST=10.10.2.27
- MYSQL_USER=echolog
- MYSQL_PASSWORD=your_password
- MYSQL_DATABASE=echolog
- SECRET_KEY=your_secret_key
- TZ=Europe/London
- LOGIN_ENABLED=true
- LOGIN_USERNAME=admin
- LOGIN_PASSWORD=your_password
```
## Configuration ## Configuration
### Database Selection
EchoLog supports both MySQL/MariaDB and SQLite:
- SQLite (default for quick start)
- MySQL/MariaDB
See the [examples folder](examples/) for docker-compose files for both database types.
### Environment Variables ### Environment Variables
#### Database Configuration
- `DB_TYPE`: Database type - `mysql` (default) or `sqlite`
**MySQL/MariaDB options:**
- `MYSQL_HOST`: MySQL/MariaDB host (default: localhost) - `MYSQL_HOST`: MySQL/MariaDB host (default: localhost)
- `MYSQL_USER`: Database user (default: root) - `MYSQL_USER`: Database user (default: root)
- `MYSQL_PASSWORD`: Database password (default: empty) - `MYSQL_PASSWORD`: Database password (default: empty)
- `MYSQL_DATABASE`: Database name (default: echolog) - `MYSQL_DATABASE`: Database name (default: echolog)
**SQLite options:**
- `SQLITE_DB`: Path to SQLite database file (default: echolog.db)
#### Application Configuration
- `SECRET_KEY`: Flask secret key for sessions (**REQUIRED in production!**) - `SECRET_KEY`: Flask secret key for sessions (**REQUIRED in production!**)
- `TZ`: Timezone for date handling (default: UTC) - `TZ`: Timezone for date handling (default: UTC)
- `LOGIN_ENABLED`: Enable login protection (default: false) - `LOGIN_ENABLED`: Enable login protection (default: false)
@@ -78,6 +58,8 @@ services:
### Database Setup ### Database Setup
#### MySQL/MariaDB
The application automatically initializes the database schema on first run. Ensure the database and user exist with appropriate permissions: The application automatically initializes the database schema on first run. Ensure the database and user exist with appropriate permissions:
```sql ```sql
@@ -87,6 +69,10 @@ GRANT ALL PRIVILEGES ON echolog.* TO 'echolog'@'%';
FLUSH PRIVILEGES; FLUSH PRIVILEGES;
``` ```
#### SQLite
SQLite requires no setup - the database file is created automatically on first run. Ensure the directory where the database file is stored is writable by the application.
## Usage ## Usage
### Creating Entries ### Creating Entries
@@ -112,76 +98,6 @@ FLUSH PRIVILEGES;
The streak counter automatically tracks consecutive days with journal entries. The streak includes today or yesterday to start, and continues as long as you have entries on consecutive days. The streak counter automatically tracks consecutive days with journal entries. The streak includes today or yesterday to start, and continues as long as you have entries on consecutive days.
## Kubernetes Deployment
The project includes a Kubernetes deployment manifest. See `deployment.yml` for details.
**Example Kubernetes deployment:**
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: echolog
namespace: echolog
spec:
replicas: 1
template:
spec:
containers:
- name: echolog
image: ghcr.io/jdb-net/echolog:latest
ports:
- containerPort: 5000
env:
- name: SECRET_KEY
valueFrom:
secretKeyRef:
name: echolog-secrets
key: secret-key
- name: MYSQL_HOST
value: "mysql-service"
- name: MYSQL_USER
value: "echolog"
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: echolog-secrets
key: mysql-password
- name: MYSQL_DATABASE
value: "echolog"
- name: TZ
value: "Europe/London"
- name: LOGIN_ENABLED
value: "true"
- name: LOGIN_USERNAME
valueFrom:
secretKeyRef:
name: echolog-secrets
key: username
- name: LOGIN_PASSWORD
valueFrom:
secretKeyRef:
name: echolog-secrets
key: password
```
## Progressive Web App (PWA)
EchoLog can be installed as a Progressive Web App on supported devices:
- **Mobile**: Add to home screen from browser menu
- **Desktop**: Install from browser address bar
- **Offline**: Works offline with cached content
## Security Notes
- **ENABLE LOGIN** in production by setting `LOGIN_ENABLED=true`
- **CHANGE THE DEFAULT CREDENTIALS** if using authentication
- **CHANGE THE SECRET_KEY** in production - use a strong random string (e.g., `openssl rand -hex 32`)
- Use strong passwords for database access
- Ensure database connections are secured (consider SSL/TLS for MySQL connections)
## Troubleshooting ## Troubleshooting
### Database Connection Issues ### Database Connection Issues
-1
View File
@@ -1 +0,0 @@
1.1.2
+88 -23
View File
@@ -1,43 +1,60 @@
import os import os
import pytz import pytz
import logging import logging
import sqlite3
from flask import Flask, render_template, request, redirect, url_for, session, jsonify from flask import Flask, render_template, request, redirect, url_for, session, jsonify
from datetime import datetime, date as datedate, datetime as dt from datetime import datetime, date as datedate, datetime as dt
import mysql.connector
from dotenv import load_dotenv from dotenv import load_dotenv
# Read version from VERSION file load_dotenv()
try:
with open('VERSION', 'r') as f: __version__ = os.getenv('VERSION', 'unknown')
__version__ = f.read().strip()
except FileNotFoundError:
__version__ = 'unknown'
app = Flask(__name__) app = Flask(__name__)
load_dotenv()
TIMEZONE = os.getenv('TZ', 'UTC') TIMEZONE = os.getenv('TZ', 'UTC')
tz = pytz.timezone(TIMEZONE) tz = pytz.timezone(TIMEZONE)
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'defaultsecret') app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'defaultsecret')
app.config['LOGIN_ENABLED'] = os.getenv('LOGIN_ENABLED', 'false').lower() == 'true' app.config['LOGIN_ENABLED'] = os.getenv('LOGIN_ENABLED', 'false').lower() == 'true'
# Database configuration
DB_TYPE = os.getenv('DB_TYPE', 'mysql').lower()
if DB_TYPE == 'mysql':
import mysql.connector
mysql_config = { mysql_config = {
'user': os.getenv('MYSQL_USER', 'root'), 'user': os.getenv('MYSQL_USER', 'root'),
'password': os.getenv('MYSQL_PASSWORD', ''), 'password': os.getenv('MYSQL_PASSWORD', ''),
'host': os.getenv('MYSQL_HOST', 'localhost'), 'host': os.getenv('MYSQL_HOST', 'localhost'),
'database': os.getenv('MYSQL_DATABASE', 'echolog') 'database': os.getenv('MYSQL_DATABASE', 'echolog')
} }
elif DB_TYPE == 'sqlite':
SQLITE_DB = os.getenv('SQLITE_DB', 'echolog.db')
else:
raise ValueError(f"Unsupported DB_TYPE: {DB_TYPE}. Use 'sqlite' or 'mysql'")
logging.basicConfig(level=logging.INFO, format='[%(asctime)s] %(levelname)s in %(module)s: %(message)s') logging.basicConfig(level=logging.INFO, format='[%(asctime)s] %(levelname)s in %(module)s: %(message)s')
def get_db_connection(): def get_db_connection():
if DB_TYPE == 'mysql':
return mysql.connector.connect(**mysql_config) return mysql.connector.connect(**mysql_config)
elif DB_TYPE == 'sqlite':
conn = sqlite3.connect(SQLITE_DB)
conn.row_factory = sqlite3.Row
return conn
def dict_from_row(row):
"""Convert database row to dictionary"""
if DB_TYPE == 'mysql':
return row
elif DB_TYPE == 'sqlite':
return dict(row) if row else None
def init_db(): def init_db():
conn = get_db_connection() conn = get_db_connection()
cursor = conn.cursor() cursor = conn.cursor()
if DB_TYPE == 'mysql':
cursor.execute(""" cursor.execute("""
CREATE TABLE IF NOT EXISTS journal_entry ( CREATE TABLE IF NOT EXISTS journal_entry (
id INT AUTO_INCREMENT PRIMARY KEY, id INT AUTO_INCREMENT PRIMARY KEY,
@@ -45,6 +62,14 @@ def init_db():
content TEXT NOT NULL content TEXT NOT NULL
); );
""") """)
elif DB_TYPE == 'sqlite':
cursor.execute("""
CREATE TABLE IF NOT EXISTS journal_entry (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date DATE NOT NULL,
content TEXT NOT NULL
);
""")
conn.commit() conn.commit()
cursor.close() cursor.close()
conn.close() conn.close()
@@ -53,6 +78,9 @@ def calculate_streak():
conn = get_db_connection() conn = get_db_connection()
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute("SELECT date FROM journal_entry ORDER BY date DESC") cursor.execute("SELECT date FROM journal_entry ORDER BY date DESC")
if DB_TYPE == 'mysql':
dates = [row[0] for row in cursor.fetchall()]
elif DB_TYPE == 'sqlite':
dates = [row[0] for row in cursor.fetchall()] dates = [row[0] for row in cursor.fetchall()]
cursor.close() cursor.close()
conn.close() conn.close()
@@ -89,14 +117,25 @@ def index():
per_page = 7 per_page = 7
offset = (page - 1) * per_page offset = (page - 1) * per_page
conn = get_db_connection() conn = get_db_connection()
cursor = conn.cursor(dictionary=True) cursor = conn.cursor()
if DB_TYPE == 'mysql':
cursor.execute("SELECT SQL_CALC_FOUND_ROWS * FROM journal_entry ORDER BY date DESC LIMIT %s OFFSET %s", (per_page, offset)) cursor.execute("SELECT SQL_CALC_FOUND_ROWS * FROM journal_entry ORDER BY date DESC LIMIT %s OFFSET %s", (per_page, offset))
entries = cursor.fetchall() entries = cursor.fetchall()
cursor.execute("SELECT FOUND_ROWS() as total") cursor.execute("SELECT FOUND_ROWS() as total")
total = cursor.fetchone()['total'] total = cursor.fetchone()['total']
elif DB_TYPE == 'sqlite':
cursor.execute("SELECT COUNT(*) as total FROM journal_entry")
total = cursor.fetchone()[0]
cursor.execute("SELECT * FROM journal_entry ORDER BY date DESC LIMIT ? OFFSET ?", (per_page, offset))
entries = [dict_from_row(row) for row in cursor.fetchall()]
today = datetime.now(tz).date().isoformat() today = datetime.now(tz).date().isoformat()
cursor.execute("SELECT * FROM journal_entry WHERE date = %s", (today,)) cursor.execute("SELECT * FROM journal_entry WHERE date = ?" if DB_TYPE == 'sqlite' else "SELECT * FROM journal_entry WHERE date = %s", (today,))
todays_entry = cursor.fetchone() todays_entry = cursor.fetchone()
if DB_TYPE == 'sqlite' and todays_entry:
todays_entry = dict_from_row(todays_entry)
cursor.close() cursor.close()
conn.close() conn.close()
has_prev = page > 1 has_prev = page > 1
@@ -112,12 +151,13 @@ def add_entry():
if content.strip(): if content.strip():
conn = get_db_connection() conn = get_db_connection()
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute("SELECT id FROM journal_entry WHERE date = %s", (date,)) param_marker = "?" if DB_TYPE == 'sqlite' else "%s"
cursor.execute(f"SELECT id FROM journal_entry WHERE date = {param_marker}", (date,))
existing = cursor.fetchone() existing = cursor.fetchone()
if existing: if existing:
cursor.execute("UPDATE journal_entry SET content = %s WHERE date = %s", (content, date)) cursor.execute(f"UPDATE journal_entry SET content = {param_marker} WHERE date = {param_marker}", (content, date))
else: else:
cursor.execute("INSERT INTO journal_entry (date, content) VALUES (%s, %s)", (date, content)) cursor.execute(f"INSERT INTO journal_entry (date, content) VALUES ({param_marker}, {param_marker})", (date, content))
conn.commit() conn.commit()
cursor.close() cursor.close()
conn.close() conn.close()
@@ -129,19 +169,25 @@ def entry_for_date():
if not date: if not date:
return jsonify({'content': ''}) return jsonify({'content': ''})
conn = get_db_connection() conn = get_db_connection()
cursor = conn.cursor(dictionary=True) cursor = conn.cursor()
cursor.execute("SELECT content FROM journal_entry WHERE date = %s", (date,)) param_marker = "?" if DB_TYPE == 'sqlite' else "%s"
cursor.execute(f"SELECT content FROM journal_entry WHERE date = {param_marker}", (date,))
entry = cursor.fetchone() entry = cursor.fetchone()
cursor.close() cursor.close()
conn.close() conn.close()
return jsonify({'content': entry['content'] if entry else ''}) if DB_TYPE == 'sqlite':
content = entry[0] if entry else ''
else:
content = entry['content'] if entry else ''
return jsonify({'content': content})
@app.route('/search', methods=['GET']) @app.route('/search', methods=['GET'])
def search(): def search():
query = request.args.get('query', '') query = request.args.get('query', '')
date = request.args.get('date', None) date = request.args.get('date', None)
conn = get_db_connection() conn = get_db_connection()
cursor = conn.cursor(dictionary=True) cursor = conn.cursor()
if DB_TYPE == 'mysql':
sql = "SELECT * FROM journal_entry WHERE 1=1" sql = "SELECT * FROM journal_entry WHERE 1=1"
params = [] params = []
if query: if query:
@@ -153,6 +199,19 @@ def search():
sql += " ORDER BY date DESC" sql += " ORDER BY date DESC"
cursor.execute(sql, params) cursor.execute(sql, params)
entries = cursor.fetchall() entries = cursor.fetchall()
elif DB_TYPE == 'sqlite':
sql = "SELECT * FROM journal_entry WHERE 1=1"
params = []
if query:
sql += " AND content LIKE ?"
params.append(f"%{query}%")
if date:
sql += " AND date = ?"
params.append(date)
sql += " ORDER BY date DESC"
cursor.execute(sql, params)
entries = [dict_from_row(row) for row in cursor.fetchall()]
cursor.close() cursor.close()
conn.close() conn.close()
today = datetime.now(tz).date().isoformat() today = datetime.now(tz).date().isoformat()
@@ -191,22 +250,26 @@ def require_login():
@app.route('/edit/<int:entry_id>', methods=['GET', 'POST']) @app.route('/edit/<int:entry_id>', methods=['GET', 'POST'])
def edit_entry(entry_id): def edit_entry(entry_id):
conn = get_db_connection() conn = get_db_connection()
cursor = conn.cursor(dictionary=True) cursor = conn.cursor()
param_marker = "?" if DB_TYPE == 'sqlite' else "%s"
if request.method == 'POST': if request.method == 'POST':
date = request.form.get('date') date = request.form.get('date')
content = request.form.get('content') content = request.form.get('content')
cursor.execute("UPDATE journal_entry SET date=%s, content=%s WHERE id=%s", (date, content, entry_id)) cursor.execute(f"UPDATE journal_entry SET date={param_marker}, content={param_marker} WHERE id={param_marker}", (date, content, entry_id))
conn.commit() conn.commit()
cursor.close() cursor.close()
conn.close() conn.close()
return redirect(url_for('index')) return redirect(url_for('index'))
else: else:
cursor.execute("SELECT * FROM journal_entry WHERE id=%s", (entry_id,)) cursor.execute(f"SELECT * FROM journal_entry WHERE id={param_marker}", (entry_id,))
entry = cursor.fetchone() entry = cursor.fetchone()
cursor.close() cursor.close()
conn.close() conn.close()
if not entry: if not entry:
return redirect(url_for('index')) return redirect(url_for('index'))
if DB_TYPE == 'sqlite':
entry = dict_from_row(entry)
return render_template('edit.html', entry=entry) return render_template('edit.html', entry=entry)
@app.route('/edit_modal', methods=['POST']) @app.route('/edit_modal', methods=['POST'])
@@ -217,7 +280,8 @@ def edit_entry_modal():
if entry_id and date and content: if entry_id and date and content:
conn = get_db_connection() conn = get_db_connection()
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute("UPDATE journal_entry SET date=%s, content=%s WHERE id=%s", (date, content, entry_id)) param_marker = "?" if DB_TYPE == 'sqlite' else "%s"
cursor.execute(f"UPDATE journal_entry SET date={param_marker}, content={param_marker} WHERE id={param_marker}", (date, content, entry_id))
conn.commit() conn.commit()
cursor.close() cursor.close()
conn.close() conn.close()
@@ -227,7 +291,8 @@ def edit_entry_modal():
def delete_entry(entry_id): def delete_entry(entry_id):
conn = get_db_connection() conn = get_db_connection()
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute("DELETE FROM journal_entry WHERE id=%s", (entry_id,)) param_marker = "?" if DB_TYPE == 'sqlite' else "%s"
cursor.execute(f"DELETE FROM journal_entry WHERE id={param_marker}", (entry_id,))
conn.commit() conn.commit()
cursor.close() cursor.close()
conn.close() conn.close()
+2 -2
View File
@@ -15,14 +15,14 @@ spec:
spec: spec:
containers: containers:
- name: echolog - name: echolog
image: ghcr.io/jdb-net/echolog:latest image: cr.jdbnet.co.uk/public/echolog:latest
imagePullPolicy: Always imagePullPolicy: Always
ports: ports:
- containerPort: 5000 - containerPort: 5000
name: "echolog" name: "echolog"
env: env:
- name: MYSQL_HOST - name: MYSQL_HOST
value: "10.10.2.27" value: "10.10.25.4"
- name: MYSQL_USER - name: MYSQL_USER
value: "echolog" value: "echolog"
- name: MYSQL_PASSWORD - name: MYSQL_PASSWORD
+44
View File
@@ -0,0 +1,44 @@
version: '3.8'
services:
mysql:
image: mariadb:latest
container_name: echolog-mysql
restart: unless-stopped
environment:
- MYSQL_ROOT_PASSWORD=root_password
- MYSQL_DATABASE=echolog
- MYSQL_USER=echolog
- MYSQL_PASSWORD=echolog_password
volumes:
- mysql-data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
timeout: 20s
retries: 10
echolog:
image: cr.jdbnet.co.uk/public/echolog:latest
container_name: echolog
restart: unless-stopped
ports:
- "5000:5000"
depends_on:
mysql:
condition: service_healthy
environment:
- DB_TYPE=mysql
- MYSQL_HOST=mysql
- MYSQL_USER=echolog
- MYSQL_PASSWORD=echolog_password
- MYSQL_DATABASE=echolog
- SECRET_KEY=change-me-in-production
- TZ=Europe/London
- LOGIN_ENABLED=false
# Uncomment below to enable login protection
# - LOGIN_ENABLED=true
# - LOGIN_USERNAME=admin
# - LOGIN_PASSWORD=change-me
volumes:
mysql-data:
+24
View File
@@ -0,0 +1,24 @@
version: '3.8'
services:
echolog:
image: cr.jdbnet.co.uk/public/echolog:latest
container_name: echolog
restart: unless-stopped
ports:
- "5000:5000"
volumes:
- echolog-data:/data
environment:
- DB_TYPE=sqlite
- SQLITE_DB=/data/echolog.db
- SECRET_KEY=change-me-in-production
- TZ=Europe/London
- LOGIN_ENABLED=false
# Uncomment below to enable login protection
# - LOGIN_ENABLED=true
# - LOGIN_USERNAME=admin
# - LOGIN_PASSWORD=change-me
volumes:
echolog-data:
+4 -1
View File
@@ -1,5 +1,8 @@
Flask Flask
mysql-connector-python
python-dotenv python-dotenv
pytz pytz
gunicorn gunicorn
# Optional database drivers:
# For MySQL/MariaDB support, uncomment the line below:
# mysql-connector-python
# SQLite is built-in with Python
+1 -1
View File
@@ -213,7 +213,7 @@
<footer class="mt-8 py-6 border-t border-gray-800 text-center text-gray-500 text-sm"> <footer class="mt-8 py-6 border-t border-gray-800 text-center text-gray-500 text-sm">
<span class="mr-1"><a href="https://www.jdbnet.co.uk" target="_blank" rel="noopener" class="text-gray-500 hover:text-pink-400 hover:underline mx-1">JDB-NET</a> &copy; {{ now.year }}</span> <span class="mr-1"><a href="https://www.jdbnet.co.uk" target="_blank" rel="noopener" class="text-gray-500 hover:text-pink-400 hover:underline mx-1">JDB-NET</a> &copy; {{ now.year }}</span>
&middot; &middot;
<span class="mx-1"><a href="https://github.com/JDB-NET/echolog" target="_blank" rel="noopener" class="text-gray-500 hover:text-pink-400 hover:underline mx-1">v{{ version }}</a></span> <span class="mx-1"><a href="https://git.jdbnet.co.uk/jamie/echolog" target="_blank" rel="noopener" class="text-gray-500 hover:text-pink-400 hover:underline mx-1">{{ version }}</a></span>
</footer> </footer>
<script> <script>