feat: US-003 - Backend API - PUT and DELETE habits
This commit is contained in:
143
dashboard/api.py
143
dashboard/api.py
@@ -69,6 +69,18 @@ class TaskBoardHandler(SimpleHTTPRequestHandler):
|
||||
else:
|
||||
self.send_error(404)
|
||||
|
||||
def do_PUT(self):
|
||||
if self.path.startswith('/api/habits/'):
|
||||
self.handle_habits_put()
|
||||
else:
|
||||
self.send_error(404)
|
||||
|
||||
def do_DELETE(self):
|
||||
if self.path.startswith('/api/habits/'):
|
||||
self.handle_habits_delete()
|
||||
else:
|
||||
self.send_error(404)
|
||||
|
||||
def handle_git_commit(self):
|
||||
"""Run git commit and push."""
|
||||
try:
|
||||
@@ -1507,6 +1519,135 @@ class TaskBoardHandler(SimpleHTTPRequestHandler):
|
||||
except Exception as e:
|
||||
self.send_json({'error': str(e)}, 500)
|
||||
|
||||
def handle_habits_put(self):
|
||||
"""Update an existing habit."""
|
||||
try:
|
||||
# Extract habit ID from path
|
||||
path_parts = self.path.split('/')
|
||||
if len(path_parts) < 4:
|
||||
self.send_json({'error': 'Invalid path'}, 400)
|
||||
return
|
||||
|
||||
habit_id = path_parts[3]
|
||||
|
||||
# Read request body
|
||||
content_length = int(self.headers['Content-Length'])
|
||||
post_data = self.rfile.read(content_length).decode('utf-8')
|
||||
data = json.loads(post_data)
|
||||
|
||||
# Read existing habits
|
||||
if not HABITS_FILE.exists():
|
||||
self.send_json({'error': 'Habit not found'}, 404)
|
||||
return
|
||||
|
||||
with open(HABITS_FILE, 'r', encoding='utf-8') as f:
|
||||
habits_data = json.load(f)
|
||||
|
||||
# Find habit to update
|
||||
habits = habits_data.get('habits', [])
|
||||
habit_index = None
|
||||
for i, habit in enumerate(habits):
|
||||
if habit['id'] == habit_id:
|
||||
habit_index = i
|
||||
break
|
||||
|
||||
if habit_index is None:
|
||||
self.send_json({'error': 'Habit not found'}, 404)
|
||||
return
|
||||
|
||||
# Validate allowed fields
|
||||
allowed_fields = ['name', 'category', 'color', 'icon', 'priority', 'notes', 'frequency', 'reminderTime']
|
||||
|
||||
# Validate name if provided
|
||||
if 'name' in data:
|
||||
name = data['name'].strip()
|
||||
if not name:
|
||||
self.send_json({'error': 'name cannot be empty'}, 400)
|
||||
return
|
||||
if len(name) > 100:
|
||||
self.send_json({'error': 'name must be max 100 characters'}, 400)
|
||||
return
|
||||
|
||||
# Validate color if provided
|
||||
if 'color' in data:
|
||||
color = data['color']
|
||||
if color and not re.match(r'^#[0-9A-Fa-f]{6}$', color):
|
||||
self.send_json({'error': 'color must be valid hex format (#RRGGBB)'}, 400)
|
||||
return
|
||||
|
||||
# Validate frequency type if provided
|
||||
if 'frequency' in data:
|
||||
frequency_type = data.get('frequency', {}).get('type', 'daily')
|
||||
valid_types = ['daily', 'specific_days', 'x_per_week', 'weekly', 'monthly', 'custom']
|
||||
if frequency_type not in valid_types:
|
||||
self.send_json({'error': f'frequency.type must be one of: {", ".join(valid_types)}'}, 400)
|
||||
return
|
||||
|
||||
# Update only allowed fields
|
||||
habit = habits[habit_index]
|
||||
for field in allowed_fields:
|
||||
if field in data:
|
||||
habit[field] = data[field]
|
||||
|
||||
# Update timestamp
|
||||
habit['updatedAt'] = datetime.now().isoformat()
|
||||
|
||||
# Save to file
|
||||
habits_data['lastUpdated'] = datetime.now().isoformat()
|
||||
with open(HABITS_FILE, 'w', encoding='utf-8') as f:
|
||||
json.dump(habits_data, f, indent=2)
|
||||
|
||||
# Return updated habit
|
||||
self.send_json(habit)
|
||||
except json.JSONDecodeError:
|
||||
self.send_json({'error': 'Invalid JSON'}, 400)
|
||||
except Exception as e:
|
||||
self.send_json({'error': str(e)}, 500)
|
||||
|
||||
def handle_habits_delete(self):
|
||||
"""Delete a habit."""
|
||||
try:
|
||||
# Extract habit ID from path
|
||||
path_parts = self.path.split('/')
|
||||
if len(path_parts) < 4:
|
||||
self.send_json({'error': 'Invalid path'}, 400)
|
||||
return
|
||||
|
||||
habit_id = path_parts[3]
|
||||
|
||||
# Read existing habits
|
||||
if not HABITS_FILE.exists():
|
||||
self.send_json({'error': 'Habit not found'}, 404)
|
||||
return
|
||||
|
||||
with open(HABITS_FILE, 'r', encoding='utf-8') as f:
|
||||
habits_data = json.load(f)
|
||||
|
||||
# Find and remove habit
|
||||
habits = habits_data.get('habits', [])
|
||||
habit_found = False
|
||||
for i, habit in enumerate(habits):
|
||||
if habit['id'] == habit_id:
|
||||
habits.pop(i)
|
||||
habit_found = True
|
||||
break
|
||||
|
||||
if not habit_found:
|
||||
self.send_json({'error': 'Habit not found'}, 404)
|
||||
return
|
||||
|
||||
# Save to file
|
||||
habits_data['lastUpdated'] = datetime.now().isoformat()
|
||||
with open(HABITS_FILE, 'w', encoding='utf-8') as f:
|
||||
json.dump(habits_data, f, indent=2)
|
||||
|
||||
# Return 204 No Content
|
||||
self.send_response(204)
|
||||
self.send_header('Access-Control-Allow-Origin', '*')
|
||||
self.end_headers()
|
||||
except Exception as e:
|
||||
self.send_json({'error': str(e)}, 500)
|
||||
|
||||
def send_json(self, data, code=200):
|
||||
self.send_response(code)
|
||||
self.send_header('Content-Type', 'application/json')
|
||||
@@ -1520,7 +1661,7 @@ class TaskBoardHandler(SimpleHTTPRequestHandler):
|
||||
def do_OPTIONS(self):
|
||||
self.send_response(200)
|
||||
self.send_header('Access-Control-Allow-Origin', '*')
|
||||
self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
|
||||
self.send_header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
|
||||
self.send_header('Access-Control-Allow-Headers', 'Content-Type')
|
||||
self.end_headers()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user