Compare commits
5 Commits
7c3c2cc41e
..
v1.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 24ab43cceb | |||
| 898ad38303 | |||
| 16ef30de83 | |||
| a45397d003 | |||
| eec6fd4b3d |
@@ -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
|
||||
@@ -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-internal-htz-01 ]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Apply manifests
|
||||
run: |
|
||||
sudo kubectl replace -f deployment.yml --grace-period=60 --force
|
||||
@@ -2,3 +2,4 @@ tailwindcss
|
||||
static/output.css
|
||||
.env
|
||||
__pycache__
|
||||
echolog.db
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"packages": {
|
||||
".": {
|
||||
"release-type": "simple",
|
||||
"version-file": "VERSION"
|
||||
}
|
||||
},
|
||||
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json"
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
".": "1.1.2"
|
||||
}
|
||||
@@ -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))
|
||||
@@ -1,6 +1,9 @@
|
||||
FROM python:3.13-slim
|
||||
LABEL org.opencontainers.image.vendor="JDB-NET"
|
||||
WORKDIR /app
|
||||
COPY . /app
|
||||
ARG VERSION=unknown
|
||||
ENV VERSION=${VERSION}
|
||||
RUN pip install -r requirements.txt \
|
||||
&& apt-get update \
|
||||
&& apt-get install curl -y \
|
||||
|
||||
@@ -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
|
||||
- **Streak Tracking**: Automatically calculates consecutive days with entries
|
||||
- **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
|
||||
- **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
|
||||
- **Kubernetes Support**: Includes Kubernetes deployment manifest
|
||||
|
||||
## Quick Start with Docker
|
||||
|
||||
### Docker Run
|
||||
|
||||
```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
|
||||
```
|
||||
For deployment examples, see the [examples folder](examples/).
|
||||
|
||||
## 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
|
||||
|
||||
#### Database Configuration
|
||||
|
||||
- `DB_TYPE`: Database type - `mysql` (default) or `sqlite`
|
||||
|
||||
**MySQL/MariaDB options:**
|
||||
- `MYSQL_HOST`: MySQL/MariaDB host (default: localhost)
|
||||
- `MYSQL_USER`: Database user (default: root)
|
||||
- `MYSQL_PASSWORD`: Database password (default: empty)
|
||||
- `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!**)
|
||||
- `TZ`: Timezone for date handling (default: UTC)
|
||||
- `LOGIN_ENABLED`: Enable login protection (default: false)
|
||||
@@ -78,6 +58,8 @@ services:
|
||||
|
||||
### Database Setup
|
||||
|
||||
#### MySQL/MariaDB
|
||||
|
||||
The application automatically initializes the database schema on first run. Ensure the database and user exist with appropriate permissions:
|
||||
|
||||
```sql
|
||||
@@ -87,6 +69,10 @@ GRANT ALL PRIVILEGES ON echolog.* TO 'echolog'@'%';
|
||||
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
|
||||
|
||||
### 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.
|
||||
|
||||
## 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
|
||||
|
||||
### Database Connection Issues
|
||||
|
||||
@@ -1,50 +1,75 @@
|
||||
import os
|
||||
import pytz
|
||||
import logging
|
||||
import sqlite3
|
||||
from flask import Flask, render_template, request, redirect, url_for, session, jsonify
|
||||
from datetime import datetime, date as datedate, datetime as dt
|
||||
import mysql.connector
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Read version from VERSION file
|
||||
try:
|
||||
with open('VERSION', 'r') as f:
|
||||
__version__ = f.read().strip()
|
||||
except FileNotFoundError:
|
||||
__version__ = 'unknown'
|
||||
load_dotenv()
|
||||
|
||||
__version__ = os.getenv('VERSION', 'unknown')
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
load_dotenv()
|
||||
|
||||
TIMEZONE = os.getenv('TZ', 'UTC')
|
||||
tz = pytz.timezone(TIMEZONE)
|
||||
|
||||
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'defaultsecret')
|
||||
app.config['LOGIN_ENABLED'] = os.getenv('LOGIN_ENABLED', 'false').lower() == 'true'
|
||||
|
||||
mysql_config = {
|
||||
'user': os.getenv('MYSQL_USER', 'root'),
|
||||
'password': os.getenv('MYSQL_PASSWORD', ''),
|
||||
'host': os.getenv('MYSQL_HOST', 'localhost'),
|
||||
'database': os.getenv('MYSQL_DATABASE', 'echolog')
|
||||
}
|
||||
# Database configuration
|
||||
DB_TYPE = os.getenv('DB_TYPE', 'mysql').lower()
|
||||
|
||||
if DB_TYPE == 'mysql':
|
||||
import mysql.connector
|
||||
mysql_config = {
|
||||
'user': os.getenv('MYSQL_USER', 'root'),
|
||||
'password': os.getenv('MYSQL_PASSWORD', ''),
|
||||
'host': os.getenv('MYSQL_HOST', 'localhost'),
|
||||
'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')
|
||||
|
||||
def get_db_connection():
|
||||
return mysql.connector.connect(**mysql_config)
|
||||
if DB_TYPE == 'mysql':
|
||||
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():
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS journal_entry (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
date DATE NOT NULL,
|
||||
content TEXT NOT NULL
|
||||
);
|
||||
""")
|
||||
if DB_TYPE == 'mysql':
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS journal_entry (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
date DATE 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()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
@@ -53,7 +78,10 @@ def calculate_streak():
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT date FROM journal_entry ORDER BY date DESC")
|
||||
dates = [row[0] for row in cursor.fetchall()]
|
||||
if DB_TYPE == 'mysql':
|
||||
dates = [row[0] for row in cursor.fetchall()]
|
||||
elif DB_TYPE == 'sqlite':
|
||||
dates = [row[0] for row in cursor.fetchall()]
|
||||
cursor.close()
|
||||
conn.close()
|
||||
if not dates:
|
||||
@@ -89,14 +117,25 @@ def index():
|
||||
per_page = 7
|
||||
offset = (page - 1) * per_page
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor(dictionary=True)
|
||||
cursor.execute("SELECT SQL_CALC_FOUND_ROWS * FROM journal_entry ORDER BY date DESC LIMIT %s OFFSET %s", (per_page, offset))
|
||||
entries = cursor.fetchall()
|
||||
cursor.execute("SELECT FOUND_ROWS() as total")
|
||||
total = cursor.fetchone()['total']
|
||||
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))
|
||||
entries = cursor.fetchall()
|
||||
cursor.execute("SELECT FOUND_ROWS() as 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()
|
||||
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()
|
||||
if DB_TYPE == 'sqlite' and todays_entry:
|
||||
todays_entry = dict_from_row(todays_entry)
|
||||
|
||||
cursor.close()
|
||||
conn.close()
|
||||
has_prev = page > 1
|
||||
@@ -112,12 +151,13 @@ def add_entry():
|
||||
if content.strip():
|
||||
conn = get_db_connection()
|
||||
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()
|
||||
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:
|
||||
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()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
@@ -129,30 +169,49 @@ def entry_for_date():
|
||||
if not date:
|
||||
return jsonify({'content': ''})
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor(dictionary=True)
|
||||
cursor.execute("SELECT content FROM journal_entry WHERE date = %s", (date,))
|
||||
cursor = conn.cursor()
|
||||
param_marker = "?" if DB_TYPE == 'sqlite' else "%s"
|
||||
cursor.execute(f"SELECT content FROM journal_entry WHERE date = {param_marker}", (date,))
|
||||
entry = cursor.fetchone()
|
||||
cursor.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'])
|
||||
def search():
|
||||
query = request.args.get('query', '')
|
||||
date = request.args.get('date', None)
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor(dictionary=True)
|
||||
sql = "SELECT * FROM journal_entry WHERE 1=1"
|
||||
params = []
|
||||
if query:
|
||||
sql += " AND content LIKE %s"
|
||||
params.append(f"%{query}%")
|
||||
if date:
|
||||
sql += " AND date = %s"
|
||||
params.append(date)
|
||||
sql += " ORDER BY date DESC"
|
||||
cursor.execute(sql, params)
|
||||
entries = cursor.fetchall()
|
||||
cursor = conn.cursor()
|
||||
if DB_TYPE == 'mysql':
|
||||
sql = "SELECT * FROM journal_entry WHERE 1=1"
|
||||
params = []
|
||||
if query:
|
||||
sql += " AND content LIKE %s"
|
||||
params.append(f"%{query}%")
|
||||
if date:
|
||||
sql += " AND date = %s"
|
||||
params.append(date)
|
||||
sql += " ORDER BY date DESC"
|
||||
cursor.execute(sql, params)
|
||||
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()
|
||||
conn.close()
|
||||
today = datetime.now(tz).date().isoformat()
|
||||
@@ -191,22 +250,26 @@ def require_login():
|
||||
@app.route('/edit/<int:entry_id>', methods=['GET', 'POST'])
|
||||
def edit_entry(entry_id):
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor(dictionary=True)
|
||||
cursor = conn.cursor()
|
||||
param_marker = "?" if DB_TYPE == 'sqlite' else "%s"
|
||||
|
||||
if request.method == 'POST':
|
||||
date = request.form.get('date')
|
||||
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()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
return redirect(url_for('index'))
|
||||
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()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
if not entry:
|
||||
return redirect(url_for('index'))
|
||||
if DB_TYPE == 'sqlite':
|
||||
entry = dict_from_row(entry)
|
||||
return render_template('edit.html', entry=entry)
|
||||
|
||||
@app.route('/edit_modal', methods=['POST'])
|
||||
@@ -217,7 +280,8 @@ def edit_entry_modal():
|
||||
if entry_id and date and content:
|
||||
conn = get_db_connection()
|
||||
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()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
@@ -227,7 +291,8 @@ def edit_entry_modal():
|
||||
def delete_entry(entry_id):
|
||||
conn = get_db_connection()
|
||||
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()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: echolog
|
||||
image: ghcr.io/jdb-net/echolog:latest
|
||||
image: cr.jdbnet.co.uk/public/echolog:latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 5000
|
||||
|
||||
@@ -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:
|
||||
@@ -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
@@ -1,5 +1,8 @@
|
||||
Flask
|
||||
mysql-connector-python
|
||||
python-dotenv
|
||||
pytz
|
||||
gunicorn
|
||||
# Optional database drivers:
|
||||
# For MySQL/MariaDB support, uncomment the line below:
|
||||
# mysql-connector-python
|
||||
# SQLite is built-in with Python
|
||||
|
||||
@@ -213,7 +213,7 @@
|
||||
<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> © {{ now.year }}</span>
|
||||
·
|
||||
<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>
|
||||
|
||||
<script>
|
||||
|
||||
Reference in New Issue
Block a user