initial commit
This commit is contained in:
parent
701138857e
commit
0f47b8abd6
23
dockerfile
Normal file
23
dockerfile
Normal file
@ -0,0 +1,23 @@
|
||||
# Use a lightweight Python image
|
||||
FROM python:3.10-slim
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies
|
||||
COPY requirements.txt ./
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy the application code
|
||||
COPY . .
|
||||
|
||||
# Set environment variables (optional for local testing)
|
||||
ENV FLASK_ENV=production
|
||||
ENV HOST=0.0.0.0
|
||||
ENV PORT=5000
|
||||
|
||||
# Expose the Flask port
|
||||
EXPOSE 5000
|
||||
|
||||
# Run the application
|
||||
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:5000", "micropub_server:app"]
|
152
micropub_server.py
Normal file
152
micropub_server.py
Normal file
@ -0,0 +1,152 @@
|
||||
import os
|
||||
import base64
|
||||
import mimetypes
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from flask import Flask, request, jsonify
|
||||
from werkzeug.utils import secure_filename
|
||||
from dotenv import load_dotenv
|
||||
import requests
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
# Configuration from environment variables
|
||||
GITEA_API_URL = os.environ.get("GITEA_API_URL", "https://your-gitea-instance/api/v1")
|
||||
GITEA_TOKEN = os.environ.get("GITEA_TOKEN", "")
|
||||
REPO_OWNER = os.environ.get("REPO_OWNER", "default-owner")
|
||||
REPO_NAME = os.environ.get("REPO_NAME", "default-repo")
|
||||
CONTENT_PATH = os.environ.get("CONTENT_PATH", "content")
|
||||
MEDIA_DIR = os.environ.get("MEDIA_DIR", "static/images")
|
||||
BRANCH = os.environ.get("BRANCH", "main")
|
||||
TOKEN_ENDPOINT = os.environ.get("TOKEN_ENDPOINT", "https://tokens.indieauth.com/token")
|
||||
|
||||
# Initialize Flask app
|
||||
app = Flask(__name__)
|
||||
|
||||
# Logging configuration
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Helper: Call Gitea API
|
||||
def gitea_api_request(method, endpoint, data=None):
|
||||
url = f"{GITEA_API_URL}{endpoint}"
|
||||
headers = {"Authorization": f"token {GITEA_TOKEN}"}
|
||||
try:
|
||||
response = requests.request(method, url, headers=headers, json=data)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.error(f"Gitea API request failed: {e}")
|
||||
raise
|
||||
|
||||
# Helper: Validate IndieAuth token
|
||||
def validate_token(token):
|
||||
try:
|
||||
response = requests.post(TOKEN_ENDPOINT, data={"token": token})
|
||||
response.raise_for_status()
|
||||
return response.json() # Should contain "me" (authenticated user's URL)
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.error(f"Token validation failed: {e}")
|
||||
return None
|
||||
|
||||
# Upload content to Gitea
|
||||
def upload_to_gitea(filepath, content, commit_message):
|
||||
encoded_content = base64.b64encode(content.encode() if isinstance(content, str) else content).decode()
|
||||
endpoint = f"/repos/{REPO_OWNER}/{REPO_NAME}/contents/{filepath}"
|
||||
data = {
|
||||
"content": encoded_content,
|
||||
"message": commit_message,
|
||||
"branch": BRANCH,
|
||||
}
|
||||
return gitea_api_request("POST", endpoint, data)
|
||||
|
||||
# Micropub endpoint
|
||||
@app.route("/micropub", methods=["POST", "GET"])
|
||||
def micropub():
|
||||
token = request.headers.get("Authorization", "").replace("Bearer ", "")
|
||||
if not token:
|
||||
return jsonify({"error": "Missing authorization token"}), 401
|
||||
|
||||
user = validate_token(token)
|
||||
if not user:
|
||||
return jsonify({"error": "Invalid token"}), 403
|
||||
|
||||
if request.method == "GET":
|
||||
return jsonify({
|
||||
"media-endpoint": "/micropub/media",
|
||||
"configurations": {},
|
||||
"actions": ["create", "update", "delete"],
|
||||
})
|
||||
|
||||
data = request.form
|
||||
if data.get("h") == "entry":
|
||||
return create_post(data, user)
|
||||
|
||||
return jsonify({"error": "Unsupported Micropub request"}), 400
|
||||
|
||||
# Create a new post
|
||||
def create_post(data, user):
|
||||
title = data.get("name", "Untitled Post")
|
||||
content = data.get("content", "")
|
||||
photo = data.get("photo") # Optional: URL of uploaded photo
|
||||
slug = data.get("slug", title.lower().replace(" ", "-"))
|
||||
date = datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
|
||||
|
||||
md_content = f"""---
|
||||
title: "{title}"
|
||||
date: {date}
|
||||
author: "{user.get('me')}"
|
||||
---
|
||||
"""
|
||||
if photo:
|
||||
md_content += f"![Image]({photo})\n\n"
|
||||
md_content += content
|
||||
|
||||
filepath = f"{CONTENT_PATH}/{slug}.md"
|
||||
try:
|
||||
response = upload_to_gitea(filepath, md_content, f"Create post: {title}")
|
||||
logger.info(f"Post created: {response['content']['html_url']}")
|
||||
return jsonify({"success": True, "location": response["content"]["html_url"]}), 201
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to create post: {e}")
|
||||
return jsonify({"error": "Failed to create post"}), 500
|
||||
|
||||
# Media upload endpoint
|
||||
@app.route("/micropub/media", methods=["POST"])
|
||||
def media_upload():
|
||||
token = request.headers.get("Authorization", "").replace("Bearer ", "")
|
||||
if not token:
|
||||
return jsonify({"error": "Missing authorization token"}), 401
|
||||
|
||||
user = validate_token(token)
|
||||
if not user:
|
||||
return jsonify({"error": "Invalid token"}), 403
|
||||
|
||||
if "file" not in request.files:
|
||||
return jsonify({"error": "No file provided"}), 400
|
||||
|
||||
file = request.files["file"]
|
||||
if file.filename == "":
|
||||
return jsonify({"error": "Empty filename"}), 400
|
||||
|
||||
filename = secure_filename(file.filename)
|
||||
mimetype = mimetypes.guess_type(filename)[0]
|
||||
|
||||
if mimetype not in ["image/png", "image/jpeg", "image/gif"]:
|
||||
return jsonify({"error": "Invalid file type"}), 400
|
||||
|
||||
try:
|
||||
response = upload_to_gitea(
|
||||
f"{MEDIA_DIR}/{filename}", file.read(), f"Upload media: {filename}"
|
||||
)
|
||||
media_url = f"/{MEDIA_DIR}/{filename}"
|
||||
logger.info(f"Media uploaded: {media_url}")
|
||||
return jsonify({"success": True, "url": media_url}), 201
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to upload media: {e}")
|
||||
return jsonify({"error": "Failed to upload media"}), 500
|
||||
|
||||
# Run the app
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=5000)
|
5
requirements.txt
Normal file
5
requirements.txt
Normal file
@ -0,0 +1,5 @@
|
||||
flask
|
||||
requests
|
||||
python-dotenv
|
||||
gunicorn
|
||||
werkzeug
|
Loading…
x
Reference in New Issue
Block a user