first commit
This commit is contained in:
BIN
__pycache__/main.cpython-39.pyc
Normal file
BIN
__pycache__/main.cpython-39.pyc
Normal file
Binary file not shown.
465
index.html
Normal file
465
index.html
Normal file
@@ -0,0 +1,465 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>🎄 Christmas Wishlist</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<style>
|
||||
body {
|
||||
background: linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #0f172a 100%);
|
||||
min-height: 100vh;
|
||||
}
|
||||
.snowflake {
|
||||
position: fixed;
|
||||
top: -10px;
|
||||
z-index: 9999;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
animation: fall linear infinite;
|
||||
}
|
||||
@keyframes fall {
|
||||
to {
|
||||
transform: translateY(100vh);
|
||||
}
|
||||
}
|
||||
.card-glow {
|
||||
box-shadow: 0 0 20px rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="text-gray-100">
|
||||
<div id="snowflakes"></div>
|
||||
|
||||
<!-- Auth Screen -->
|
||||
<div id="authScreen" class="min-h-screen flex items-center justify-center p-4">
|
||||
<div class="bg-slate-800 rounded-2xl shadow-2xl p-8 w-full max-w-md card-glow">
|
||||
<div class="text-center mb-8">
|
||||
<h1 class="text-4xl font-bold text-red-500 mb-2">🎄 Christmas Wishlist</h1>
|
||||
<p class="text-gray-400">Made with ❤️ by Claude Sonnet 4.5</p>
|
||||
<p class="text-xs text-gray-500 mt-1">Prompted by <a href="https://space.reversed.dev/" class="text-red-400 hover:underline">Space</a> • <span class="font-bold">ONE SHOT</span> try</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<input type="text" id="username" placeholder="🎅 Username"
|
||||
class="w-full px-4 py-3 bg-slate-700 border border-slate-600 rounded-lg focus:outline-none focus:border-red-500 transition">
|
||||
<input type="password" id="password" placeholder="🔒 Password"
|
||||
class="w-full px-4 py-3 bg-slate-700 border border-slate-600 rounded-lg focus:outline-none focus:border-red-500 transition">
|
||||
|
||||
<div class="flex gap-3">
|
||||
<button onclick="login()"
|
||||
class="flex-1 bg-red-600 hover:bg-red-700 text-white font-semibold py-3 rounded-lg transition">
|
||||
Login
|
||||
</button>
|
||||
<button onclick="register()"
|
||||
class="flex-1 bg-green-600 hover:bg-green-700 text-white font-semibold py-3 rounded-lg transition">
|
||||
Register
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p id="authError" class="text-red-400 text-sm text-center hidden"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Wishlist Screen -->
|
||||
<div id="wishlistScreen" class="hidden min-h-screen p-4 md:p-8">
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<div class="bg-slate-800 rounded-2xl shadow-2xl p-6 md:p-8 card-glow">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h1 class="text-3xl font-bold text-red-500">🎁 My Wishlist</h1>
|
||||
<div class="flex gap-2">
|
||||
<button onclick="shareWishlist()"
|
||||
class="bg-yellow-500 hover:bg-yellow-600 px-4 py-2 rounded-lg transition text-black font-semibold">
|
||||
Share
|
||||
</button>
|
||||
<button onclick="logout()"
|
||||
class="bg-slate-700 hover:bg-slate-600 px-4 py-2 rounded-lg transition">
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Item Form -->
|
||||
<div class="bg-slate-700 rounded-xl p-6 mb-6">
|
||||
<h2 class="text-xl font-semibold mb-4 text-green-400">✨ Add New Item</h2>
|
||||
<div class="grid md:grid-cols-2 gap-4">
|
||||
<input type="text" id="itemName" placeholder="🎁 Item name"
|
||||
class="px-4 py-2 bg-slate-600 border border-slate-500 rounded-lg focus:outline-none focus:border-green-500 transition">
|
||||
<input type="text" id="itemLink" placeholder="🔗 Link (optional)"
|
||||
class="px-4 py-2 bg-slate-600 border border-slate-500 rounded-lg focus:outline-none focus:border-green-500 transition">
|
||||
<input type="number" id="itemPrice" placeholder="💰 Price" step="0.01"
|
||||
class="px-4 py-2 bg-slate-600 border border-slate-500 rounded-lg focus:outline-none focus:border-green-500 transition">
|
||||
<input type="text" id="itemImage" placeholder="🖼️ Image URL (optional)"
|
||||
class="px-4 py-2 bg-slate-600 border border-slate-500 rounded-lg focus:outline-none focus:border-green-500 transition">
|
||||
</div>
|
||||
<button onclick="addItem()"
|
||||
class="mt-4 w-full bg-green-600 hover:bg-green-700 text-white font-semibold py-3 rounded-lg transition">
|
||||
Add to Wishlist
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Items List -->
|
||||
<div id="itemsList" class="space-y-4 mb-6"></div>
|
||||
|
||||
<!-- Summary -->
|
||||
<div class="bg-gradient-to-r from-red-900 to-green-900 rounded-xl p-6 text-center">
|
||||
<div class="flex justify-center gap-8">
|
||||
<div>
|
||||
<p class="text-gray-300 text-sm">Total Items</p>
|
||||
<p class="text-3xl font-bold" id="itemCount">0</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-gray-300 text-sm">Total Value</p>
|
||||
<p class="text-3xl font-bold" id="totalPrice">€0.00</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Modal -->
|
||||
<div id="editModal" class="hidden fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center p-4 z-50">
|
||||
<div class="bg-slate-800 rounded-2xl p-6 w-full max-w-md card-glow">
|
||||
<h2 class="text-2xl font-bold text-red-500 mb-4">✏️ Edit Item</h2>
|
||||
<input type="hidden" id="editItemId">
|
||||
<div class="space-y-4">
|
||||
<input type="text" id="editItemName" placeholder="🎁 Item name"
|
||||
class="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg focus:outline-none focus:border-red-500 transition">
|
||||
<input type="text" id="editItemLink" placeholder="🔗 Link (optional)"
|
||||
class="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg focus:outline-none focus:border-red-500 transition">
|
||||
<input type="number" id="editItemPrice" placeholder="💰 Price" step="0.01"
|
||||
class="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg focus:outline-none focus:border-red-500 transition">
|
||||
<input type="text" id="editItemImage" placeholder="🖼️ Image URL (optional)"
|
||||
class="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg focus:outline-none focus:border-red-500 transition">
|
||||
|
||||
<div class="flex gap-3">
|
||||
<button onclick="saveEdit()"
|
||||
class="flex-1 bg-green-600 hover:bg-green-700 text-white font-semibold py-3 rounded-lg transition">
|
||||
Save
|
||||
</button>
|
||||
<button onclick="closeEditModal()"
|
||||
class="flex-1 bg-slate-700 hover:bg-slate-600 text-white font-semibold py-3 rounded-lg transition">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Share Modal -->
|
||||
<div id="shareModal" class="hidden fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center p-4 z-50">
|
||||
<div class="bg-slate-800 rounded-2xl p-6 w-full max-w-md card-glow text-center">
|
||||
<h2 class="text-2xl font-bold text-yellow-400 mb-4">🔗 Share Your Wishlist</h2>
|
||||
<input id="shareLink" type="text" readonly class="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg text-yellow-300 mb-4 text-center" />
|
||||
<a id="shareUiLink" href="#" target="_blank" class="block mb-4 text-blue-400 hover:underline break-all"></a>
|
||||
<button onclick="copyShareLink()" class="bg-yellow-500 hover:bg-yellow-600 text-black font-semibold py-2 px-6 rounded-lg transition mb-2">Copy Link</button>
|
||||
<button onclick="closeShareModal()" class="block w-full mt-2 bg-slate-700 hover:bg-slate-600 text-white font-semibold py-2 rounded-lg transition">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentUser = null;
|
||||
|
||||
// Create snowflakes
|
||||
function createSnowflakes() {
|
||||
const container = document.getElementById('snowflakes');
|
||||
for (let i = 0; i < 50; i++) {
|
||||
const snowflake = document.createElement('div');
|
||||
snowflake.className = 'snowflake';
|
||||
snowflake.innerHTML = '❄️';
|
||||
snowflake.style.left = Math.random() * 100 + '%';
|
||||
snowflake.style.animationDuration = Math.random() * 3 + 2 + 's';
|
||||
snowflake.style.animationDelay = Math.random() * 5 + 's';
|
||||
snowflake.style.opacity = Math.random() * 0.6 + 0.4;
|
||||
snowflake.style.fontSize = Math.random() * 10 + 10 + 'px';
|
||||
container.appendChild(snowflake);
|
||||
}
|
||||
}
|
||||
createSnowflakes();
|
||||
|
||||
async function login() {
|
||||
const username = document.getElementById('username').value;
|
||||
const password = document.getElementById('password').value;
|
||||
|
||||
try {
|
||||
const response = await fetch('https://shsf-api.reversed.dev/api/exec/4/57aca15b-665e-4015-b7e3-526a87b39b33/login', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({username, password})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
currentUser = username;
|
||||
showWishlist();
|
||||
loadItems();
|
||||
} else {
|
||||
showError(data.error || 'Login failed');
|
||||
}
|
||||
} catch (error) {
|
||||
showError('Connection error');
|
||||
}
|
||||
}
|
||||
|
||||
async function register() {
|
||||
const username = document.getElementById('username').value;
|
||||
const password = document.getElementById('password').value;
|
||||
|
||||
try {
|
||||
const response = await fetch('https://shsf-api.reversed.dev/api/exec/4/57aca15b-665e-4015-b7e3-526a87b39b33/register', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({username, password})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
currentUser = username;
|
||||
showWishlist();
|
||||
loadItems();
|
||||
} else {
|
||||
showError(data.error || 'Registration failed');
|
||||
}
|
||||
} catch (error) {
|
||||
showError('Connection error');
|
||||
}
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
const errorEl = document.getElementById('authError');
|
||||
errorEl.textContent = message;
|
||||
errorEl.classList.remove('hidden');
|
||||
setTimeout(() => errorEl.classList.add('hidden'), 3000);
|
||||
}
|
||||
|
||||
function showWishlist() {
|
||||
document.getElementById('authScreen').classList.add('hidden');
|
||||
document.getElementById('wishlistScreen').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function logout() {
|
||||
currentUser = null;
|
||||
document.getElementById('authScreen').classList.remove('hidden');
|
||||
document.getElementById('wishlistScreen').classList.add('hidden');
|
||||
document.getElementById('username').value = '';
|
||||
document.getElementById('password').value = '';
|
||||
}
|
||||
|
||||
async function loadItems() {
|
||||
try {
|
||||
const response = await fetch(`https://shsf-api.reversed.dev/api/exec/4/57aca15b-665e-4015-b7e3-526a87b39b33/items?username=${currentUser}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
displayItems(data.items);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load items:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function displayItems(items) {
|
||||
const container = document.getElementById('itemsList');
|
||||
|
||||
if (items.length === 0) {
|
||||
container.innerHTML = '<p class="text-center text-gray-400 py-8">🎄 No items yet. Add your first wish!</p>';
|
||||
updateSummary(items);
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = items.map(item => `
|
||||
<div class="bg-slate-700 rounded-xl p-4 flex gap-4">
|
||||
${item.image ? `<img src="${item.image}" alt="${item.name}" class="w-24 h-24 object-cover rounded-lg">` :
|
||||
'<div class="w-24 h-24 bg-slate-600 rounded-lg flex items-center justify-center text-4xl">🎁</div>'}
|
||||
<div class="flex-1">
|
||||
<h3 class="text-xl font-semibold text-red-400">${item.name}</h3>
|
||||
${item.link ? `<a href="${item.link}" target="_blank" class="text-blue-400 hover:underline text-sm">🔗 View Item</a>` : ''}
|
||||
<p class="text-2xl font-bold text-green-400 mt-2">${item.price ? '€' + parseFloat(item.price).toFixed(2) : 'No price'}</p>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<button onclick="editItem('${item.id}')"
|
||||
class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded-lg transition">
|
||||
✏️ Edit
|
||||
</button>
|
||||
<button onclick="deleteItem('${item.id}')"
|
||||
class="bg-red-600 hover:bg-red-700 px-4 py-2 rounded-lg transition">
|
||||
🗑️ Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
updateSummary(items);
|
||||
}
|
||||
|
||||
function updateSummary(items) {
|
||||
const count = items.length;
|
||||
const total = items.reduce((sum, item) => sum + (parseFloat(item.price) || 0), 0);
|
||||
|
||||
document.getElementById('itemCount').textContent = count;
|
||||
document.getElementById('totalPrice').textContent = '€' + total.toFixed(2);
|
||||
}
|
||||
|
||||
async function addItem() {
|
||||
const name = document.getElementById('itemName').value;
|
||||
const link = document.getElementById('itemLink').value;
|
||||
const price = document.getElementById('itemPrice').value;
|
||||
const image = document.getElementById('itemImage').value;
|
||||
|
||||
if (!name) {
|
||||
alert('Please enter an item name');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('https://shsf-api.reversed.dev/api/exec/4/57aca15b-665e-4015-b7e3-526a87b39b33/add', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({username: currentUser, name, link, price, image})
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
document.getElementById('itemName').value = '';
|
||||
document.getElementById('itemLink').value = '';
|
||||
document.getElementById('itemPrice').value = '';
|
||||
document.getElementById('itemImage').value = '';
|
||||
loadItems();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to add item:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function editItem(id) {
|
||||
fetch(`https://shsf-api.reversed.dev/api/exec/4/57aca15b-665e-4015-b7e3-526a87b39b33/items?username=${currentUser}`)
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
const item = data.items.find(i => i.id === id);
|
||||
if (item) {
|
||||
document.getElementById('editItemId').value = id;
|
||||
document.getElementById('editItemName').value = item.name;
|
||||
document.getElementById('editItemLink').value = item.link || '';
|
||||
document.getElementById('editItemPrice').value = item.price || '';
|
||||
document.getElementById('editItemImage').value = item.image || '';
|
||||
document.getElementById('editModal').classList.remove('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function saveEdit() {
|
||||
const id = document.getElementById('editItemId').value;
|
||||
const name = document.getElementById('editItemName').value;
|
||||
const link = document.getElementById('editItemLink').value;
|
||||
const price = document.getElementById('editItemPrice').value;
|
||||
const image = document.getElementById('editItemImage').value;
|
||||
|
||||
try {
|
||||
const response = await fetch('https://shsf-api.reversed.dev/api/exec/4/57aca15b-665e-4015-b7e3-526a87b39b33/update', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({username: currentUser, id, name, link, price, image})
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
closeEditModal();
|
||||
loadItems();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to update item:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function closeEditModal() {
|
||||
document.getElementById('editModal').classList.add('hidden');
|
||||
}
|
||||
|
||||
async function deleteItem(id) {
|
||||
if (!confirm('Are you sure you want to delete this item?')) return;
|
||||
|
||||
try {
|
||||
const response = await fetch('https://shsf-api.reversed.dev/api/exec/4/57aca15b-665e-4015-b7e3-526a87b39b33/delete', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({username: currentUser, id})
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
loadItems();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to delete item:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function shareWishlist() {
|
||||
if (!currentUser) return;
|
||||
try {
|
||||
const response = await fetch('https://shsf-api.reversed.dev/api/exec/4/57aca15b-665e-4015-b7e3-526a87b39b33/share', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({username: currentUser})
|
||||
});
|
||||
const data = await response.json();
|
||||
if (response.ok && data.share_id) {
|
||||
// UI link that triggers redirect to UI with share_id
|
||||
const uiLink = `https://shsf-api.reversed.dev/api/exec/4/57aca15b-665e-4015-b7e3-526a87b39b33/?share_id=${data.share_id}`;
|
||||
document.getElementById('shareLink').value = uiLink;
|
||||
document.getElementById('shareUiLink').href = uiLink;
|
||||
document.getElementById('shareUiLink').textContent = uiLink;
|
||||
document.getElementById('shareModal').classList.remove('hidden');
|
||||
} else {
|
||||
alert(data.error || 'Failed to share wishlist');
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Failed to share wishlist');
|
||||
}
|
||||
}
|
||||
|
||||
function closeShareModal() {
|
||||
document.getElementById('shareModal').classList.add('hidden');
|
||||
}
|
||||
|
||||
function copyShareLink() {
|
||||
const input = document.getElementById('shareLink');
|
||||
input.select();
|
||||
input.setSelectionRange(0, 99999);
|
||||
document.execCommand('copy');
|
||||
}
|
||||
|
||||
// Public wishlist view
|
||||
async function loadPublicWishlist(shareId) {
|
||||
try {
|
||||
const response = await fetch(`https://shsf-api.reversed.dev/api/exec/4/57aca15b-665e-4015-b7e3-526a87b39b33/public?share_id=${shareId}`);
|
||||
const data = await response.json();
|
||||
if (response.ok && data.items) {
|
||||
document.getElementById('authScreen').classList.add('hidden');
|
||||
document.getElementById('wishlistScreen').classList.remove('hidden');
|
||||
// Hide add/edit/delete controls for public view
|
||||
document.querySelectorAll('[onclick^="addItem"]').forEach(el => el.style.display = 'none');
|
||||
document.querySelectorAll('[onclick^="editItem"]').forEach(el => el.style.display = 'none');
|
||||
document.querySelectorAll('[onclick^="deleteItem"]').forEach(el => el.style.display = 'none');
|
||||
document.querySelectorAll('[onclick^="shareWishlist"]').forEach(el => el.style.display = 'none');
|
||||
displayItems(data.items);
|
||||
} else {
|
||||
alert(data.error || 'Shared wishlist not found');
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Failed to load shared wishlist');
|
||||
}
|
||||
}
|
||||
|
||||
// On page load, check for share_id in URL
|
||||
(function() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const shareId = params.get('share_id');
|
||||
if (shareId) {
|
||||
loadPublicWishlist(shareId);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
223
main.py
Normal file
223
main.py
Normal file
@@ -0,0 +1,223 @@
|
||||
import redis
|
||||
import json
|
||||
import os
|
||||
import uuid
|
||||
|
||||
r = redis.Redis(
|
||||
host="185.14.92.202",
|
||||
port=12003,
|
||||
decode_responses=True,
|
||||
username="default",
|
||||
password=os.getenv("REDID"),
|
||||
db=5,
|
||||
)
|
||||
|
||||
def main(args):
|
||||
route = args.get("route", "default")
|
||||
body = args.get("body", {})
|
||||
try:
|
||||
body = json.loads(body) if isinstance(body, str) else body
|
||||
except:
|
||||
body = {}
|
||||
queries = args.get("queries", {})
|
||||
|
||||
# Serve HTML for default route
|
||||
if route == "default":
|
||||
try:
|
||||
with open("/app/index.html", "r", encoding="utf-8") as f:
|
||||
html = f.read()
|
||||
return {
|
||||
"_shsf": "v2",
|
||||
"_code": 200,
|
||||
"_headers": {"Content-Type": "text/html"},
|
||||
"_res": html
|
||||
}
|
||||
except Exception as e:
|
||||
return {"_shsf": "v2", "_code": 500, "_res": {"error": str(e)}}
|
||||
|
||||
# Login endpoint
|
||||
elif route == "login":
|
||||
username = body.get("username", "").strip()
|
||||
password = body.get("password", "").strip()
|
||||
|
||||
if not username or not password:
|
||||
return {"_shsf": "v2", "_code": 400, "_res": {"error": "Username and password required"}}
|
||||
|
||||
# Get stored user
|
||||
user_key = f"storage1:user:{username}"
|
||||
stored_pass = r.get(user_key)
|
||||
|
||||
if stored_pass == password:
|
||||
return {"_shsf": "v2", "_code": 200, "_res": {"success": True, "username": username}}
|
||||
else:
|
||||
return {"_shsf": "v2", "_code": 401, "_res": {"error": "Invalid credentials"}}
|
||||
|
||||
# Register endpoint
|
||||
elif route == "register":
|
||||
username = body.get("username", "").strip()
|
||||
password = body.get("password", "").strip()
|
||||
|
||||
if not username or not password:
|
||||
return {"_shsf": "v2", "_code": 400, "_res": {"error": "Username and password required"}}
|
||||
|
||||
user_key = f"storage1:user:{username}"
|
||||
|
||||
if r.exists(user_key):
|
||||
return {"_shsf": "v2", "_code": 409, "_res": {"error": "Username already exists"}}
|
||||
|
||||
r.set(user_key, password)
|
||||
return {"_shsf": "v2", "_code": 200, "_res": {"success": True, "username": username}}
|
||||
|
||||
# Get wishlist items
|
||||
elif route == "items":
|
||||
username = queries.get("username", "")
|
||||
|
||||
if not username:
|
||||
return {"_shsf": "v2", "_code": 400, "_res": {"error": "Username required"}}
|
||||
|
||||
items_key = f"storage1:items:{username}"
|
||||
items_json = r.get(items_key)
|
||||
|
||||
if items_json:
|
||||
items = json.loads(items_json)
|
||||
else:
|
||||
items = []
|
||||
|
||||
return {"_shsf": "v2", "_code": 200, "_res": {"items": items}}
|
||||
|
||||
# Add wishlist item
|
||||
elif route == "add":
|
||||
username = body.get("username", "")
|
||||
name = body.get("name", "").strip()
|
||||
link = body.get("link", "").strip()
|
||||
price = body.get("price", "")
|
||||
image = body.get("image", "").strip()
|
||||
|
||||
if not username or not name:
|
||||
return {"_shsf": "v2", "_code": 400, "_res": {"error": "Username and name required"}}
|
||||
|
||||
items_key = f"storage1:items:{username}"
|
||||
items_json = r.get(items_key)
|
||||
|
||||
if items_json:
|
||||
items = json.loads(items_json)
|
||||
else:
|
||||
items = []
|
||||
|
||||
# Generate ID
|
||||
item_id = str(len(items) + 1)
|
||||
|
||||
new_item = {
|
||||
"id": item_id,
|
||||
"name": name,
|
||||
"link": link,
|
||||
"price": price,
|
||||
"image": image
|
||||
}
|
||||
|
||||
items.append(new_item)
|
||||
r.set(items_key, json.dumps(items))
|
||||
|
||||
return {"_shsf": "v2", "_code": 200, "_res": {"success": True, "item": new_item}}
|
||||
|
||||
# Update wishlist item
|
||||
elif route == "update":
|
||||
username = body.get("username", "")
|
||||
item_id = body.get("id", "")
|
||||
name = body.get("name", "").strip()
|
||||
link = body.get("link", "").strip()
|
||||
price = body.get("price", "")
|
||||
image = body.get("image", "").strip()
|
||||
|
||||
if not username or not item_id:
|
||||
return {"_shsf": "v2", "_code": 400, "_res": {"error": "Username and item ID required"}}
|
||||
|
||||
items_key = f"storage1:items:{username}"
|
||||
items_json = r.get(items_key)
|
||||
|
||||
if not items_json:
|
||||
return {"_shsf": "v2", "_code": 404, "_res": {"error": "No items found"}}
|
||||
|
||||
items = json.loads(items_json)
|
||||
|
||||
found = False
|
||||
for item in items:
|
||||
if item["id"] == item_id:
|
||||
item["name"] = name
|
||||
item["link"] = link
|
||||
item["price"] = price
|
||||
item["image"] = image
|
||||
found = True
|
||||
break
|
||||
|
||||
if not found:
|
||||
return {"_shsf": "v2", "_code": 404, "_res": {"error": "Item not found"}}
|
||||
|
||||
r.set(items_key, json.dumps(items))
|
||||
|
||||
return {"_shsf": "v2", "_code": 200, "_res": {"success": True}}
|
||||
|
||||
# Delete wishlist item
|
||||
elif route == "delete":
|
||||
username = body.get("username", "")
|
||||
item_id = body.get("id", "")
|
||||
|
||||
if not username or not item_id:
|
||||
return {"_shsf": "v2", "_code": 400, "_res": {"error": "Username and item ID required"}}
|
||||
|
||||
items_key = f"storage1:items:{username}"
|
||||
items_json = r.get(items_key)
|
||||
|
||||
if not items_json:
|
||||
return {"_shsf": "v2", "_code": 404, "_res": {"error": "No items found"}}
|
||||
|
||||
items = json.loads(items_json)
|
||||
items = [item for item in items if item["id"] != item_id]
|
||||
|
||||
r.set(items_key, json.dumps(items))
|
||||
|
||||
return {"_shsf": "v2", "_code": 200, "_res": {"success": True}}
|
||||
|
||||
# Share wishlist (create public copy and return link)
|
||||
elif route == "share":
|
||||
username = body.get("username", "")
|
||||
if not username:
|
||||
return {"_shsf": "v2", "_code": 400, "_res": {"error": "Username required"}}
|
||||
items_key = f"storage1:items:{username}"
|
||||
items_json = r.get(items_key)
|
||||
if not items_json:
|
||||
return {"_shsf": "v2", "_code": 404, "_res": {"error": "No items found"}}
|
||||
# Generate unique share ID
|
||||
share_id = str(uuid.uuid4())
|
||||
public_key = f"storage1:public:{share_id}"
|
||||
r.set(public_key, items_json)
|
||||
share_url = f"https://shsf-api.reversed.dev/api/exec/4/57aca15b-665e-4015-b7e3-526a87b39b33/public?share_id={share_id}"
|
||||
return {"_shsf": "v2", "_code": 200, "_res": {"share_id": share_id, "share_url": share_url}}
|
||||
|
||||
# Get public wishlist by share_id
|
||||
elif route == "public":
|
||||
share_id = queries.get("share_id", "")
|
||||
if not share_id:
|
||||
return {"_shsf": "v2", "_code": 400, "_res": {"error": "Share ID required"}}
|
||||
public_key = f"storage1:public:{share_id}"
|
||||
items_json = r.get(public_key)
|
||||
if not items_json:
|
||||
return {"_shsf": "v2", "_code": 404, "_res": {"error": "Shared wishlist not found"}}
|
||||
items = json.loads(items_json)
|
||||
return {"_shsf": "v2", "_code": 200, "_res": {"items": items}}
|
||||
|
||||
# Redirect to UI with share_id for public viewing
|
||||
elif route == "redirect_share":
|
||||
share_id = queries.get("share_id", "")
|
||||
if not share_id:
|
||||
return {"_shsf": "v2", "_code": 400, "_res": {"error": "Share ID required"}}
|
||||
# Change this URL if your UI is hosted elsewhere
|
||||
ui_url = f"https://shsf-api.reversed.dev/api/exec/4/57aca15b-665e-4015-b7e3-526a87b39b33/?share_id={share_id}"
|
||||
return {
|
||||
"_shsf": "v2",
|
||||
"_code": 302,
|
||||
"_location": ui_url
|
||||
}
|
||||
|
||||
else:
|
||||
return {"_shsf": "v2", "_code": 404, "_res": {"error": "Route not found"}}
|
||||
1
requirements.txt
Normal file
1
requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
redis==5.0.1
|
||||
Reference in New Issue
Block a user