initialize repo
This commit is contained in:
parent
eb4dd15e36
commit
da83e1e956
9
Dockerfile
Normal file
9
Dockerfile
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
FROM python:3.9-slim
|
||||||
|
|
||||||
|
WORKDIR /openguestbook
|
||||||
|
|
||||||
|
RUN pip install flask flask-cors requests gunicorn
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:5000", "app:app"]
|
||||||
38
README.md
38
README.md
@ -1,2 +1,38 @@
|
|||||||
# OpenGuestbook
|
# 📖 OpenGuestbook
|
||||||
|

|
||||||
|
[](https://www.gnu.org/licenses/gpl-3.0.en.html#license-text)
|
||||||
|
|
||||||
|
OpenGuestbook is a self-hosted lightweight Guestbook for small static websites made using python and flask. OpenGuestbook does not use databases or an admin panel for managing entries. Instead it saves each guestbook entry as a file with a human readable format in a /guestbook folder in your server. The comments can be moderated by deleting or editing files manually.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- No login required
|
||||||
|
- No database required
|
||||||
|
- Limited total comments per day.
|
||||||
|
- Easy setup, and low CPU and RAM use.
|
||||||
|
- Privacy Friendly: No tracking, no cookies, AD blocker friendly.
|
||||||
|
- Notification support via Notfy.sh!
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
OpenGuestbook comes in two parts, a backend that needs to be hosted on a server, and a JS file that cn be embedded directly on your website.
|
||||||
|
### Backend deployment with Docker
|
||||||
|
For production, running via Docker Compose is recommended.
|
||||||
|
|
||||||
|
#### 1) Create docker-compose.yml
|
||||||
|
```yaml
|
||||||
|
version: '3.8'
|
||||||
|
services:
|
||||||
|
guestbook:
|
||||||
|
build: .
|
||||||
|
container_name: OpenGuestbook
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "5000:5000"
|
||||||
|
volumes:
|
||||||
|
- ./guestbook_data:/openguestbook/guestbook # Persist comments on host to make it easily reachable
|
||||||
|
environment:
|
||||||
|
- FRONTEND_URL=<https://yourwebsite.com> # if a frontend url is not provided the app will default the allow all utl's which is not recommended!
|
||||||
|
- NTFY_TOPIC=<enter-ntfy-topic> # for psuh notification support (optional)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
## Configuration
|
||||||
118
app.py
Normal file
118
app.py
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import os
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import requests
|
||||||
|
from datetime import datetime
|
||||||
|
from flask import Flask, request, jsonify
|
||||||
|
from flask_cors import CORS
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
# configure daily limit and data directory.
|
||||||
|
DAILY_LIMIT = 50
|
||||||
|
DATA_DIR = 'guestbook'
|
||||||
|
|
||||||
|
currentDate = datetime.now().strftime('%Y-%m-%d')
|
||||||
|
submissionCountDay = 0
|
||||||
|
|
||||||
|
|
||||||
|
frontend_url = os.environ.get("FRONTEND_URL","*")
|
||||||
|
|
||||||
|
topic = os.environ.get("NTFY_TOPIC")
|
||||||
|
|
||||||
|
CORS(app, resources={r"/*": {"origins": frontend_url}})
|
||||||
|
|
||||||
|
if not os.path.exists(DATA_DIR):
|
||||||
|
os.makedirs(DATA_DIR)
|
||||||
|
|
||||||
|
@app.route('/comments', methods=['GET'])
|
||||||
|
def getComments():
|
||||||
|
comments = []
|
||||||
|
try:
|
||||||
|
files = sorted([f for f in os.listdir(DATA_DIR) if f.endswith('.json')], reverse=True)
|
||||||
|
|
||||||
|
for filename in files:
|
||||||
|
filepath = os.path.join(DATA_DIR, filename)
|
||||||
|
with open(filepath, 'r', encoding='utf-8') as f:
|
||||||
|
try:
|
||||||
|
data = json.load(f)
|
||||||
|
comments.append(data)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
continue
|
||||||
|
return jsonify(comments)
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
|
@app.route('/comments', methods=['POST'])
|
||||||
|
def addComment():
|
||||||
|
global currentDate, submissionCountDay
|
||||||
|
|
||||||
|
# Check date
|
||||||
|
today_str = datetime.now().strftime('%Y-%m-%d')
|
||||||
|
if today_str != currentDate:
|
||||||
|
currentDate = today_str
|
||||||
|
submissionCountDay = 0
|
||||||
|
|
||||||
|
# Check limit
|
||||||
|
if submissionCountDay >= DAILY_LIMIT:
|
||||||
|
return jsonify({"error": "Guestbook full for the day, try tomorrow!"}), 403
|
||||||
|
|
||||||
|
data = request.json
|
||||||
|
name = data.get('name', '').strip()
|
||||||
|
message = data.get('message', '').strip()
|
||||||
|
website = data.get('website', '').strip()
|
||||||
|
|
||||||
|
if not name or not message:
|
||||||
|
return jsonify({"error": "Missing fields"}), 400
|
||||||
|
|
||||||
|
#URL cleanup
|
||||||
|
if website:
|
||||||
|
# If forgot http://, add it for them
|
||||||
|
if not website.startswith(('http://', 'https://')):
|
||||||
|
website = 'https://' + website
|
||||||
|
|
||||||
|
entry = {
|
||||||
|
'name': name,
|
||||||
|
'message': message,
|
||||||
|
'website': website,
|
||||||
|
'date': time.strftime("%d-%m-%Y %H:%M")
|
||||||
|
}
|
||||||
|
|
||||||
|
now = datetime.now()
|
||||||
|
readable_time = now.strftime('%Y-%m-%d_%H-%M-%S')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
filename = f"{readable_time}.json"
|
||||||
|
filepath = os.path.join(DATA_DIR, filename)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(filepath, 'x', encoding='utf-8') as f:
|
||||||
|
json.dump(entry, f)
|
||||||
|
except FileExistsError:
|
||||||
|
filename = f"{readable_time}_2.json"
|
||||||
|
filepath = os.path.join(DATA_DIR, filename)
|
||||||
|
with open(filepath, 'x', encoding='utf-8') as f:
|
||||||
|
json.dump(entry, f)
|
||||||
|
|
||||||
|
|
||||||
|
send_ntfy_notification(name, message)
|
||||||
|
|
||||||
|
|
||||||
|
submissionCountDay += 1
|
||||||
|
|
||||||
|
return jsonify({"status": "success"})
|
||||||
|
|
||||||
|
def send_ntfy_notification(name, message):
|
||||||
|
if not topic:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
requests.post(f"https://ntfy.sh/{topic}",
|
||||||
|
data=f"{name} wrote: {message}",
|
||||||
|
headers={
|
||||||
|
"Title": "Someone Signed Your Guestbook!"
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Notification failed: {e}")
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(host='0.0.0.0', port=5000)
|
||||||
Loading…
Reference in New Issue
Block a user