git init m8
This commit is contained in:
commit
a5c4ffe3e9
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
users.txt
|
||||
.venv
|
||||
venv
|
||||
env
|
||||
.env
|
||||
notes
|
37
static/note.js
Normal file
37
static/note.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
const textarea = document.getElementById("editor");
|
||||
const status = document.getElementById("status");
|
||||
|
||||
fetch("/api/" + noteName)
|
||||
.then(res => res.ok ? res.text() : "")
|
||||
.then(text => {
|
||||
textarea.value = text;
|
||||
status.textContent = "Loaded";
|
||||
});
|
||||
|
||||
let timeout;
|
||||
let last = "";
|
||||
|
||||
textarea.addEventListener("input", () => {
|
||||
status.textContent = "Typing...";
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
const text = textarea.value;
|
||||
if (text !== last) {
|
||||
fetch("/api/" + noteName, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ content: text })
|
||||
}).then(res => {
|
||||
if (res.ok) {
|
||||
last = text;
|
||||
status.textContent = "Saved";
|
||||
} else {
|
||||
status.textContent = "Save failed";
|
||||
}
|
||||
});
|
||||
} else {
|
||||
status.textContent = "No changes";
|
||||
}
|
||||
}, 500);
|
||||
});
|
||||
|
20
templates/note.html
Normal file
20
templates/note.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title> {{ note }} </title>
|
||||
<style>
|
||||
textarea { width: 100%; height: 90vh; font-family: monospace; font-size: 1em; }
|
||||
#status { font-size: 0.9em; color: gray; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<textarea id="editor" placeholder="Type here!"></textarea>
|
||||
<div id="status">Loading...</div>
|
||||
<script>
|
||||
const noteName = "{{ note|e }}";
|
||||
</script>
|
||||
<script src="{{ url_for('static', filename='note.js') }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
69
test.html
Normal file
69
test.html
Normal file
|
@ -0,0 +1,69 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Autosave Textbox</title>
|
||||
<style>
|
||||
textarea {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
font-family: monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
#status {
|
||||
font-size: 0.9em;
|
||||
color: gray;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Autosave Textbox</h1>
|
||||
<textarea id="editor" placeholder="Start typing..."></textarea>
|
||||
<div id="status">Idle</div>
|
||||
|
||||
<script>
|
||||
const textarea = document.getElementById("editor");
|
||||
const status = document.getElementById("status");
|
||||
|
||||
let timeoutId;
|
||||
let lastSentValue = "";
|
||||
|
||||
textarea.addEventListener("input", () => {
|
||||
status.textContent = "Typing...";
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
timeoutId = setTimeout(() => {
|
||||
const content = textarea.value;
|
||||
|
||||
if (content !== lastSentValue) {
|
||||
saveContent(content);
|
||||
} else {
|
||||
status.textContent = "No changes";
|
||||
}
|
||||
}, 400);
|
||||
});
|
||||
|
||||
function saveContent(text) {
|
||||
status.textContent = "Saving...";
|
||||
|
||||
fetch("https://your-api-endpoint.example.com/save", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({ content: text })
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) throw new Error("Network response was not ok");
|
||||
lastSentValue = text;
|
||||
status.textContent = "Saved";
|
||||
})
|
||||
.catch(error => {
|
||||
status.textContent = "Save failed";
|
||||
console.error("Autosave failed:", error);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
140
testflask.py
Normal file
140
testflask.py
Normal file
|
@ -0,0 +1,140 @@
|
|||
from flask import Flask, request, session, redirect, url_for, render_template, jsonify
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
from cryptography.fernet import Fernet
|
||||
import os
|
||||
import hashlib
|
||||
import base64
|
||||
import dotenv
|
||||
|
||||
dotenv.load_dotenv()
|
||||
|
||||
app = Flask(__name__)
|
||||
app.secret_key = os.getenv("SECRET")
|
||||
|
||||
USERS_FILE = "users.txt"
|
||||
DATA_DIR = "notes"
|
||||
os.makedirs(DATA_DIR, exist_ok=True)
|
||||
|
||||
|
||||
# === UTILS ===
|
||||
|
||||
def get_user():
|
||||
return session.get("user")
|
||||
|
||||
def get_note_path(user, note):
|
||||
safe_user = user.replace("/", "_")
|
||||
safe_note = note.replace("/", "_")
|
||||
user_dir = os.path.join(DATA_DIR, safe_user)
|
||||
os.makedirs(user_dir, exist_ok=True)
|
||||
return os.path.join(user_dir, safe_note + ".txt")
|
||||
|
||||
def get_key_for_user(user, password):
|
||||
salt = b"fixed_salt" # You can store a per-user salt in USERS_FILE if needed
|
||||
key = hashlib.pbkdf2_hmac("sha256", password.encode(), salt + user.encode(), 100_000)
|
||||
return base64.urlsafe_b64encode(key[:32])
|
||||
|
||||
|
||||
# === ROUTES ===
|
||||
|
||||
@app.route("/api/<note>", methods=["GET", "POST"])
|
||||
def api(note):
|
||||
user = get_user()
|
||||
key = session.get("key")
|
||||
if not user or not key:
|
||||
return "Unauthorized", 401
|
||||
|
||||
fernet = Fernet(key.encode())
|
||||
path = get_note_path(user, note)
|
||||
|
||||
if request.method == "GET":
|
||||
if not os.path.exists(path):
|
||||
return "", 200
|
||||
with open(path, "rb") as f:
|
||||
try:
|
||||
return fernet.decrypt(f.read()).decode(), 200
|
||||
except Exception:
|
||||
return "Corrupted note or invalid key", 500
|
||||
|
||||
if request.method == "POST":
|
||||
data = request.get_json()
|
||||
if not data or "content" not in data:
|
||||
return "Bad request", 400
|
||||
ciphertext = fernet.encrypt(data["content"].encode())
|
||||
with open(path, "wb") as f:
|
||||
f.write(ciphertext)
|
||||
return jsonify({"status": "saved"})
|
||||
|
||||
|
||||
@app.route("/n/<note>")
|
||||
@app.route("/notes/<note>")
|
||||
def serve_note(note):
|
||||
if not get_user():
|
||||
return redirect(url_for("login"))
|
||||
return render_template("note.html", note=note)
|
||||
|
||||
|
||||
@app.route("/register", methods=["GET", "POST"])
|
||||
def register():
|
||||
if request.method == "POST":
|
||||
username = request.form.get("username")
|
||||
password = request.form.get("password")
|
||||
if not username or not password:
|
||||
return "Missing fields", 400
|
||||
|
||||
with open(USERS_FILE, "a+") as f:
|
||||
f.seek(0)
|
||||
for line in f:
|
||||
if line.strip().split(":")[0] == username:
|
||||
return "User already exists", 400
|
||||
hashed_pw = generate_password_hash(password)
|
||||
f.write(f"{username}:{hashed_pw}\n")
|
||||
|
||||
session["user"] = username
|
||||
session["key"] = get_key_for_user(username, password).decode()
|
||||
return redirect(url_for("serve_note", note="home"))
|
||||
|
||||
return '''
|
||||
<form method="post">
|
||||
Username: <input name="username"><br>
|
||||
Password: <input name="password" type="password"><br>
|
||||
<input type="submit" value="Register">
|
||||
</form>
|
||||
<p>Already have an account? <a href="/login">Login here</a>.</p>
|
||||
'''
|
||||
|
||||
|
||||
@app.route("/login", methods=["GET", "POST"])
|
||||
def login():
|
||||
if request.method == "POST":
|
||||
username = request.form.get("username")
|
||||
password = request.form.get("password")
|
||||
|
||||
with open(USERS_FILE, "r") as f:
|
||||
for line in f:
|
||||
user, stored_hash = line.strip().split(":", 1)
|
||||
if user == username and check_password_hash(stored_hash, password):
|
||||
session["user"] = username
|
||||
session["key"] = get_key_for_user(username, password).decode()
|
||||
return redirect(url_for("serve_note", note="home"))
|
||||
return "Invalid login", 401
|
||||
|
||||
return '''
|
||||
<form method="post">
|
||||
Username: <input name="username"><br>
|
||||
Password: <input name="password" type="password"><br>
|
||||
<input type="submit" value="Login">
|
||||
</form>
|
||||
<p>Don't have an account? <a href="/register">Register here</a>.</p>
|
||||
'''
|
||||
|
||||
|
||||
@app.route("/logout")
|
||||
def logout():
|
||||
session.pop("user", None)
|
||||
session.pop("key", None)
|
||||
return redirect(url_for("login"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(debug=True)
|
||||
|
Loading…
Reference in a new issue