added seperate users for backend events
This commit is contained in:
@@ -1,22 +1,26 @@
|
|||||||
|
import logging
|
||||||
import secrets, jwt
|
import secrets, jwt
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
|
|
||||||
from flask import Blueprint, request, jsonify, current_app
|
from flask import Blueprint, request, jsonify, current_app
|
||||||
from flask_mail import Mail, Message
|
from flask_mail import Mail, Message
|
||||||
from tinydb import Query
|
from tinydb import Query
|
||||||
|
import os
|
||||||
|
|
||||||
|
from api.utils import sanitize_email
|
||||||
|
from config.paths import get_user_image_dir
|
||||||
|
|
||||||
from api.error_codes import MISSING_FIELDS, EMAIL_EXISTS, MISSING_TOKEN, INVALID_TOKEN, TOKEN_TIMESTAMP_MISSING, \
|
from api.error_codes import MISSING_FIELDS, EMAIL_EXISTS, MISSING_TOKEN, INVALID_TOKEN, TOKEN_TIMESTAMP_MISSING, \
|
||||||
TOKEN_EXPIRED, ALREADY_VERIFIED, MISSING_EMAIL, USER_NOT_FOUND, MISSING_EMAIL_OR_PASSWORD, INVALID_CREDENTIALS, \
|
TOKEN_EXPIRED, ALREADY_VERIFIED, MISSING_EMAIL, USER_NOT_FOUND, MISSING_EMAIL_OR_PASSWORD, INVALID_CREDENTIALS, \
|
||||||
NOT_VERIFIED
|
NOT_VERIFIED
|
||||||
from db.db import users_db
|
from db.db import users_db
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
auth_api = Blueprint('auth_api', __name__)
|
auth_api = Blueprint('auth_api', __name__)
|
||||||
UserQuery = Query()
|
UserQuery = Query()
|
||||||
mail = Mail()
|
mail = Mail()
|
||||||
TOKEN_EXPIRY_MINUTES = 60*4
|
TOKEN_EXPIRY_MINUTES = 60*4
|
||||||
|
|
||||||
SECRET_KEY = "your-secret-key" # Use a secure key in production
|
|
||||||
#SECRET_KEY = os.environ.get('SECRET_KEY')
|
|
||||||
|
|
||||||
def send_verification_email(to_email, token):
|
def send_verification_email(to_email, token):
|
||||||
verify_url = f"{current_app.config['FRONTEND_URL']}/auth/verify?token={token}"
|
verify_url = f"{current_app.config['FRONTEND_URL']}/auth/verify?token={token}"
|
||||||
@@ -58,6 +62,7 @@ def verify():
|
|||||||
status = 'success'
|
status = 'success'
|
||||||
reason = ''
|
reason = ''
|
||||||
code = ''
|
code = ''
|
||||||
|
user = None
|
||||||
|
|
||||||
if not token:
|
if not token:
|
||||||
status = 'error'
|
status = 'error'
|
||||||
@@ -85,6 +90,13 @@ def verify():
|
|||||||
users_db.update({'verified': True, 'verify_token': None, 'verify_token_created': None}, Query().verify_token == token)
|
users_db.update({'verified': True, 'verify_token': None, 'verify_token_created': None}, Query().verify_token == token)
|
||||||
|
|
||||||
http_status = 200 if status == 'success' else 400
|
http_status = 200 if status == 'success' else 400
|
||||||
|
if http_status == 200 and user is not None: ##user is verified, create the user's image directory
|
||||||
|
if 'email' not in user:
|
||||||
|
logger.error("Verified user has no email field.")
|
||||||
|
else:
|
||||||
|
user_image_dir = get_user_image_dir(sanitize_email(user['email']))
|
||||||
|
os.makedirs(user_image_dir, exist_ok=True)
|
||||||
|
|
||||||
return jsonify({'status': status, 'reason': reason, 'code': code}), http_status
|
return jsonify({'status': status, 'reason': reason, 'code': code}), http_status
|
||||||
|
|
||||||
@auth_api.route('/resend-verify', methods=['POST'])
|
@auth_api.route('/resend-verify', methods=['POST'])
|
||||||
@@ -130,7 +142,7 @@ def login():
|
|||||||
'email': email,
|
'email': email,
|
||||||
'exp': datetime.utcnow() + timedelta(hours=24*7)
|
'exp': datetime.utcnow() + timedelta(hours=24*7)
|
||||||
}
|
}
|
||||||
token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
|
token = jwt.encode(payload, current_app.config['SECRET_KEY'], algorithm='HS256')
|
||||||
|
|
||||||
resp = jsonify({'message': 'Login successful'})
|
resp = jsonify({'message': 'Login successful'})
|
||||||
resp.set_cookie('token', token, httponly=True, secure=True, samesite='Strict')
|
resp.set_cookie('token', token, httponly=True, secure=True, samesite='Strict')
|
||||||
@@ -143,13 +155,14 @@ def me():
|
|||||||
return jsonify({'error': 'Missing token', 'code': MISSING_TOKEN}), 401
|
return jsonify({'error': 'Missing token', 'code': MISSING_TOKEN}), 401
|
||||||
|
|
||||||
try:
|
try:
|
||||||
payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
|
payload = jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=['HS256'])
|
||||||
email = payload.get('email')
|
email = payload.get('email')
|
||||||
user = users_db.get(UserQuery.email == email)
|
user = users_db.get(UserQuery.email == email)
|
||||||
if not user:
|
if not user:
|
||||||
return jsonify({'error': 'User not found', 'code': USER_NOT_FOUND}), 404
|
return jsonify({'error': 'User not found', 'code': USER_NOT_FOUND}), 404
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'email': user['email'],
|
'email': user['email'],
|
||||||
|
'id': sanitize_email(user['email']),
|
||||||
'first_name': user['first_name'],
|
'first_name': user['first_name'],
|
||||||
'last_name': user['last_name'],
|
'last_name': user['last_name'],
|
||||||
'verified': user['verified']
|
'verified': user['verified']
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
from flask import Blueprint, request, jsonify
|
from flask import Blueprint, request, jsonify
|
||||||
from tinydb import Query
|
from tinydb import Query
|
||||||
from db.db import child_db, task_db, reward_db, pending_reward_db
|
|
||||||
from api.reward_status import RewardStatus
|
|
||||||
from api.child_tasks import ChildTask
|
|
||||||
from api.child_rewards import ChildReward
|
from api.child_rewards import ChildReward
|
||||||
from events.sse import send_event_to_user
|
from api.child_tasks import ChildTask
|
||||||
|
from api.pending_reward import PendingReward as PendingRewardResponse
|
||||||
|
from api.reward_status import RewardStatus
|
||||||
|
from api.utils import send_event_for_current_user
|
||||||
|
from db.db import child_db, task_db, reward_db, pending_reward_db
|
||||||
from events.types.child_modified import ChildModified
|
from events.types.child_modified import ChildModified
|
||||||
from events.types.child_reward_request import ChildRewardRequest
|
from events.types.child_reward_request import ChildRewardRequest
|
||||||
from events.types.child_reward_triggered import ChildRewardTriggered
|
from events.types.child_reward_triggered import ChildRewardTriggered
|
||||||
@@ -13,12 +15,10 @@ from events.types.child_task_triggered import ChildTaskTriggered
|
|||||||
from events.types.child_tasks_set import ChildTasksSet
|
from events.types.child_tasks_set import ChildTasksSet
|
||||||
from events.types.event import Event
|
from events.types.event import Event
|
||||||
from events.types.event_types import EventType
|
from events.types.event_types import EventType
|
||||||
from api.pending_reward import PendingReward as PendingRewardResponse
|
|
||||||
|
|
||||||
from models.child import Child
|
from models.child import Child
|
||||||
from models.pending_reward import PendingReward
|
from models.pending_reward import PendingReward
|
||||||
from models.task import Task
|
|
||||||
from models.reward import Reward
|
from models.reward import Reward
|
||||||
|
from models.task import Task
|
||||||
|
|
||||||
child_api = Blueprint('child_api', __name__)
|
child_api = Blueprint('child_api', __name__)
|
||||||
|
|
||||||
@@ -44,7 +44,10 @@ def add_child():
|
|||||||
|
|
||||||
child = Child(name=name, age=age, image_id=image)
|
child = Child(name=name, age=age, image_id=image)
|
||||||
child_db.insert(child.to_dict())
|
child_db.insert(child.to_dict())
|
||||||
send_event_to_user("user123", Event(EventType.CHILD_MODIFIED.value, ChildModified(child.id, ChildModified.OPERATION_ADD)))
|
resp = send_event_for_current_user(
|
||||||
|
Event(EventType.CHILD_MODIFIED.value, ChildModified(child.id, ChildModified.OPERATION_ADD)))
|
||||||
|
if resp:
|
||||||
|
return resp
|
||||||
return jsonify({'message': f'Child {name} added.'}), 201
|
return jsonify({'message': f'Child {name} added.'}), 201
|
||||||
|
|
||||||
@child_api.route('/child/<id>/edit', methods=['PUT'])
|
@child_api.route('/child/<id>/edit', methods=['PUT'])
|
||||||
@@ -85,16 +88,14 @@ def edit_child(id):
|
|||||||
pending_reward_db.remove(
|
pending_reward_db.remove(
|
||||||
(PendingQuery.child_id == id) & (PendingQuery.reward_id == reward.id)
|
(PendingQuery.child_id == id) & (PendingQuery.reward_id == reward.id)
|
||||||
)
|
)
|
||||||
send_event_to_user(
|
resp = send_event_for_current_user(
|
||||||
"user123",
|
Event(EventType.CHILD_REWARD_REQUEST.value, ChildRewardRequest(id, reward.id, ChildRewardRequest.REQUEST_CANCELLED)))
|
||||||
Event(
|
if resp:
|
||||||
EventType.CHILD_REWARD_REQUEST.value,
|
return resp
|
||||||
ChildRewardRequest(id, reward.id, ChildRewardRequest.REQUEST_CANCELLED)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
child_db.update(child.to_dict(), ChildQuery.id == id)
|
child_db.update(child.to_dict(), ChildQuery.id == id)
|
||||||
send_event_to_user("user123", Event(EventType.CHILD_MODIFIED.value, ChildModified(id, ChildModified.OPERATION_EDIT)))
|
resp = send_event_for_current_user(Event(EventType.CHILD_MODIFIED.value, ChildModified(id, ChildModified.OPERATION_EDIT)))
|
||||||
|
if resp:
|
||||||
|
return resp
|
||||||
return jsonify({'message': f'Child {id} updated.'}), 200
|
return jsonify({'message': f'Child {id} updated.'}), 200
|
||||||
|
|
||||||
@child_api.route('/child/list', methods=['GET'])
|
@child_api.route('/child/list', methods=['GET'])
|
||||||
@@ -107,8 +108,9 @@ def list_children():
|
|||||||
def delete_child(id):
|
def delete_child(id):
|
||||||
ChildQuery = Query()
|
ChildQuery = Query()
|
||||||
if child_db.remove(ChildQuery.id == id):
|
if child_db.remove(ChildQuery.id == id):
|
||||||
send_event_to_user("user123",
|
resp = send_event_for_current_user(Event(EventType.CHILD_MODIFIED.value, ChildModified(id, ChildModified.OPERATION_DELETE)))
|
||||||
Event(EventType.CHILD_MODIFIED.value, ChildModified(id, ChildModified.OPERATION_DELETE)))
|
if resp:
|
||||||
|
return resp
|
||||||
return jsonify({'message': f'Child {id} deleted.'}), 200
|
return jsonify({'message': f'Child {id} deleted.'}), 200
|
||||||
return jsonify({'error': 'Child not found'}), 404
|
return jsonify({'error': 'Child not found'}), 404
|
||||||
|
|
||||||
@@ -154,7 +156,9 @@ def set_child_tasks(id):
|
|||||||
valid_task_ids.append(tid)
|
valid_task_ids.append(tid)
|
||||||
# Replace tasks with validated IDs
|
# Replace tasks with validated IDs
|
||||||
child_db.update({'tasks': valid_task_ids}, ChildQuery.id == id)
|
child_db.update({'tasks': valid_task_ids}, ChildQuery.id == id)
|
||||||
send_event_to_user("user123", Event(EventType.CHILD_TASKS_SET.value, ChildTasksSet(id, valid_task_ids)))
|
resp = send_event_for_current_user(Event(EventType.CHILD_TASKS_SET.value, ChildTasksSet(id, valid_task_ids)))
|
||||||
|
if resp:
|
||||||
|
return resp
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'message': f'Tasks set for child {id}.',
|
'message': f'Tasks set for child {id}.',
|
||||||
'task_ids': valid_task_ids,
|
'task_ids': valid_task_ids,
|
||||||
@@ -302,8 +306,9 @@ def trigger_child_task(id):
|
|||||||
child.points = max(child.points, 0)
|
child.points = max(child.points, 0)
|
||||||
# update the child in the database
|
# update the child in the database
|
||||||
child_db.update({'points': child.points}, ChildQuery.id == id)
|
child_db.update({'points': child.points}, ChildQuery.id == id)
|
||||||
send_event_to_user("user123", Event(EventType.CHILD_TASK_TRIGGERED.value, ChildTaskTriggered(task.id, child.id, child.points)))
|
resp = send_event_for_current_user(Event(EventType.CHILD_TASK_TRIGGERED.value, ChildTaskTriggered(task.id, child.id, child.points)))
|
||||||
|
if resp:
|
||||||
|
return resp
|
||||||
return jsonify({'message': f'{task.name} points assigned to {child.name}.', 'points': child.points, 'id': child.id}), 200
|
return jsonify({'message': f'{task.name} points assigned to {child.name}.', 'points': child.points, 'id': child.id}), 200
|
||||||
|
|
||||||
@child_api.route('/child/<id>/assign-reward', methods=['POST'])
|
@child_api.route('/child/<id>/assign-reward', methods=['POST'])
|
||||||
@@ -388,7 +393,9 @@ def set_child_rewards(id):
|
|||||||
|
|
||||||
# Replace rewards with validated IDs
|
# Replace rewards with validated IDs
|
||||||
child_db.update({'rewards': valid_reward_ids}, ChildQuery.id == id)
|
child_db.update({'rewards': valid_reward_ids}, ChildQuery.id == id)
|
||||||
send_event_to_user("user123", Event(EventType.CHILD_REWARDS_SET.value, ChildRewardsSet(id, valid_reward_ids)))
|
resp = send_event_for_current_user(Event(EventType.CHILD_REWARDS_SET.value, ChildRewardsSet(id, valid_reward_ids)))
|
||||||
|
if resp:
|
||||||
|
return resp
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'message': f'Rewards set for child {id}.',
|
'message': f'Rewards set for child {id}.',
|
||||||
'reward_ids': valid_reward_ids,
|
'reward_ids': valid_reward_ids,
|
||||||
@@ -488,16 +495,18 @@ def trigger_child_reward(id):
|
|||||||
(PendingQuery.child_id == child.id) & (PendingQuery.reward_id == reward.id)
|
(PendingQuery.child_id == child.id) & (PendingQuery.reward_id == reward.id)
|
||||||
)
|
)
|
||||||
if removed:
|
if removed:
|
||||||
send_event_to_user("user123", Event(EventType.CHILD_REWARD_REQUEST.value, ChildRewardRequest(reward.id, child.id, ChildRewardRequest.REQUEST_GRANTED)))
|
resp = send_event_for_current_user(Event(EventType.CHILD_REWARD_REQUEST.value, ChildRewardRequest(reward.id, child.id, ChildRewardRequest.REQUEST_GRANTED)))
|
||||||
|
if resp:
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
# update the child's points based on reward cost
|
# update the child's points based on reward cost
|
||||||
child.points -= reward.cost
|
child.points -= reward.cost
|
||||||
# update the child in the database
|
# update the child in the database
|
||||||
child_db.update({'points': child.points}, ChildQuery.id == id)
|
child_db.update({'points': child.points}, ChildQuery.id == id)
|
||||||
send_event_to_user("user123", Event(EventType.CHILD_REWARD_TRIGGERED.value, ChildRewardTriggered(reward.id, child.id, child.points)))
|
resp = send_event_for_current_user(Event(EventType.CHILD_REWARD_TRIGGERED.value, ChildRewardTriggered(reward.id, child.id, child.points)))
|
||||||
|
if resp:
|
||||||
|
return resp
|
||||||
return jsonify({'message': f'{reward.name} assigned to {child.name}.', 'points': child.points, 'id': child.id}), 200
|
return jsonify({'message': f'{reward.name} assigned to {child.name}.', 'points': child.points, 'id': child.id}), 200
|
||||||
|
|
||||||
@child_api.route('/child/<id>/affordable-rewards', methods=['GET'])
|
@child_api.route('/child/<id>/affordable-rewards', methods=['GET'])
|
||||||
@@ -580,8 +589,9 @@ def request_reward(id):
|
|||||||
|
|
||||||
pending = PendingReward(child_id=child.id, reward_id=reward.id)
|
pending = PendingReward(child_id=child.id, reward_id=reward.id)
|
||||||
pending_reward_db.insert(pending.to_dict())
|
pending_reward_db.insert(pending.to_dict())
|
||||||
send_event_to_user("user123", Event(EventType.CHILD_REWARD_REQUEST.value, ChildRewardRequest(child.id, reward.id, ChildRewardRequest.REQUEST_CREATED)))
|
resp = send_event_for_current_user(Event(EventType.CHILD_REWARD_REQUEST.value, ChildRewardRequest(child.id, reward.id, ChildRewardRequest.REQUEST_CREATED)))
|
||||||
|
if resp:
|
||||||
|
return resp
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'message': f'Reward request for {reward.name} submitted for {child.name}.',
|
'message': f'Reward request for {reward.name} submitted for {child.name}.',
|
||||||
'reward_id': reward.id,
|
'reward_id': reward.id,
|
||||||
@@ -615,14 +625,9 @@ def cancel_request_reward(id):
|
|||||||
return jsonify({'error': 'No pending request found for this reward'}), 404
|
return jsonify({'error': 'No pending request found for this reward'}), 404
|
||||||
|
|
||||||
# Notify user that the request was cancelled
|
# Notify user that the request was cancelled
|
||||||
send_event_to_user(
|
resp = send_event_for_current_user(Event(EventType.CHILD_REWARD_REQUEST.value, ChildRewardRequest(child.id, reward_id, ChildRewardRequest.REQUEST_CANCELLED)))
|
||||||
"user123",
|
if resp:
|
||||||
Event(
|
return resp
|
||||||
EventType.CHILD_REWARD_REQUEST.value,
|
|
||||||
ChildRewardRequest(child.id, reward_id, ChildRewardRequest.REQUEST_CANCELLED)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'message': f'Reward request cancelled for {child.name}.',
|
'message': f'Reward request cancelled for {child.name}.',
|
||||||
'child_id': child.id,
|
'child_id': child.id,
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ import os
|
|||||||
from PIL import Image as PILImage, UnidentifiedImageError
|
from PIL import Image as PILImage, UnidentifiedImageError
|
||||||
from flask import Blueprint, request, jsonify, send_file
|
from flask import Blueprint, request, jsonify, send_file
|
||||||
from tinydb import Query
|
from tinydb import Query
|
||||||
|
|
||||||
|
from api.utils import get_current_user_id, sanitize_email
|
||||||
from config.paths import get_user_image_dir
|
from config.paths import get_user_image_dir
|
||||||
|
|
||||||
from db.db import image_db
|
from db.db import image_db
|
||||||
from models.image import Image
|
from models.image import Image
|
||||||
|
|
||||||
image_api = Blueprint('image_api', __name__)
|
image_api = Blueprint('image_api', __name__)
|
||||||
UPLOAD_FOLDER = get_user_image_dir("user123")
|
|
||||||
ALLOWED_EXTENSIONS = {'jpg', 'jpeg', 'png'}
|
ALLOWED_EXTENSIONS = {'jpg', 'jpeg', 'png'}
|
||||||
IMAGE_TYPE_PROFILE = 1
|
IMAGE_TYPE_PROFILE = 1
|
||||||
IMAGE_TYPE_ICON = 2
|
IMAGE_TYPE_ICON = 2
|
||||||
@@ -20,6 +21,9 @@ def allowed_file(filename):
|
|||||||
|
|
||||||
@image_api.route('/image/upload', methods=['POST'])
|
@image_api.route('/image/upload', methods=['POST'])
|
||||||
def upload():
|
def upload():
|
||||||
|
user_id = get_current_user_id()
|
||||||
|
if not user_id:
|
||||||
|
return jsonify({'error': 'User not authenticated'}), 401
|
||||||
if 'file' not in request.files:
|
if 'file' not in request.files:
|
||||||
return jsonify({'error': 'No file part in the request'}), 400
|
return jsonify({'error': 'No file part in the request'}), 400
|
||||||
file = request.files['file']
|
file = request.files['file']
|
||||||
@@ -60,13 +64,11 @@ def upload():
|
|||||||
|
|
||||||
format_extension_map = {'JPEG': '.jpg', 'PNG': '.png'}
|
format_extension_map = {'JPEG': '.jpg', 'PNG': '.png'}
|
||||||
extension = format_extension_map.get(original_format, '.png')
|
extension = format_extension_map.get(original_format, '.png')
|
||||||
|
image_record = Image(extension=extension, permanent=perm, type=image_type, user=user_id)
|
||||||
image_record = Image(extension=extension, permanent=perm, type=image_type, user="user123")
|
|
||||||
filename = image_record.id + extension
|
filename = image_record.id + extension
|
||||||
filepath = os.path.abspath(os.path.join(UPLOAD_FOLDER, filename))
|
filepath = os.path.abspath(os.path.join(get_user_image_dir(sanitize_email(user_id)), filename))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
|
||||||
# Save with appropriate format
|
# Save with appropriate format
|
||||||
save_params = {}
|
save_params = {}
|
||||||
if pil_image.format == 'JPEG':
|
if pil_image.format == 'JPEG':
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
from flask import Blueprint, request, jsonify
|
from flask import Blueprint, request, jsonify
|
||||||
from tinydb import Query
|
from tinydb import Query
|
||||||
|
|
||||||
from events.sse import send_event_to_user
|
from api.utils import send_event_for_current_user
|
||||||
|
from db.db import reward_db, child_db
|
||||||
from events.types.event import Event
|
from events.types.event import Event
|
||||||
from events.types.event_types import EventType
|
from events.types.event_types import EventType
|
||||||
from events.types.reward_modified import RewardModified
|
from events.types.reward_modified import RewardModified
|
||||||
from models.reward import Reward
|
from models.reward import Reward
|
||||||
from db.db import reward_db, child_db
|
|
||||||
|
|
||||||
reward_api = Blueprint('reward_api', __name__)
|
reward_api = Blueprint('reward_api', __name__)
|
||||||
|
|
||||||
@@ -22,9 +22,10 @@ def add_reward():
|
|||||||
return jsonify({'error': 'Name, description, and cost are required'}), 400
|
return jsonify({'error': 'Name, description, and cost are required'}), 400
|
||||||
reward = Reward(name=name, description=description, cost=cost, image_id=image)
|
reward = Reward(name=name, description=description, cost=cost, image_id=image)
|
||||||
reward_db.insert(reward.to_dict())
|
reward_db.insert(reward.to_dict())
|
||||||
send_event_to_user("user123", Event(EventType.REWARD_MODIFIED.value,
|
resp = send_event_for_current_user(Event(EventType.REWARD_MODIFIED.value,
|
||||||
RewardModified(reward.id, RewardModified.OPERATION_ADD)))
|
RewardModified(reward.id, RewardModified.OPERATION_ADD)))
|
||||||
|
if resp:
|
||||||
|
return resp
|
||||||
return jsonify({'message': f'Reward {name} added.'}), 201
|
return jsonify({'message': f'Reward {name} added.'}), 201
|
||||||
|
|
||||||
|
|
||||||
@@ -54,9 +55,10 @@ def delete_reward(id):
|
|||||||
if id in rewards:
|
if id in rewards:
|
||||||
rewards.remove(id)
|
rewards.remove(id)
|
||||||
child_db.update({'rewards': rewards}, ChildQuery.id == child.get('id'))
|
child_db.update({'rewards': rewards}, ChildQuery.id == child.get('id'))
|
||||||
send_event_to_user("user123", Event(EventType.REWARD_MODIFIED.value,
|
resp = send_event_for_current_user(Event(EventType.REWARD_MODIFIED.value,
|
||||||
RewardModified(id, RewardModified.OPERATION_DELETE)))
|
RewardModified(id, RewardModified.OPERATION_DELETE)))
|
||||||
|
if resp:
|
||||||
|
return resp
|
||||||
return jsonify({'message': f'Reward {id} deleted.'}), 200
|
return jsonify({'message': f'Reward {id} deleted.'}), 200
|
||||||
return jsonify({'error': 'Reward not found'}), 404
|
return jsonify({'error': 'Reward not found'}), 404
|
||||||
|
|
||||||
@@ -98,7 +100,9 @@ def edit_reward(id):
|
|||||||
|
|
||||||
reward_db.update(updates, RewardQuery.id == id)
|
reward_db.update(updates, RewardQuery.id == id)
|
||||||
updated = reward_db.get(RewardQuery.id == id)
|
updated = reward_db.get(RewardQuery.id == id)
|
||||||
send_event_to_user("user123", Event(EventType.REWARD_MODIFIED.value,
|
resp = send_event_for_current_user(Event(EventType.REWARD_MODIFIED.value,
|
||||||
RewardModified(id, RewardModified.OPERATION_EDIT)))
|
RewardModified(id, RewardModified.OPERATION_EDIT)))
|
||||||
|
if resp:
|
||||||
|
return resp
|
||||||
|
|
||||||
return jsonify(updated), 200
|
return jsonify(updated), 200
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
from flask import Blueprint, request, jsonify
|
from flask import Blueprint, request, jsonify
|
||||||
from tinydb import Query
|
from tinydb import Query
|
||||||
|
|
||||||
from events.sse import send_event_to_user
|
from api.utils import send_event_for_current_user
|
||||||
|
from db.db import task_db, child_db
|
||||||
from events.types.event import Event
|
from events.types.event import Event
|
||||||
from events.types.event_types import EventType
|
from events.types.event_types import EventType
|
||||||
from events.types.task_modified import TaskModified
|
from events.types.task_modified import TaskModified
|
||||||
from models.task import Task
|
from models.task import Task
|
||||||
from db.db import task_db, child_db
|
|
||||||
|
|
||||||
task_api = Blueprint('task_api', __name__)
|
task_api = Blueprint('task_api', __name__)
|
||||||
|
|
||||||
@@ -22,8 +22,10 @@ def add_task():
|
|||||||
return jsonify({'error': 'Name, points, and is_good are required'}), 400
|
return jsonify({'error': 'Name, points, and is_good are required'}), 400
|
||||||
task = Task(name=name, points=points, is_good=is_good, image_id=image)
|
task = Task(name=name, points=points, is_good=is_good, image_id=image)
|
||||||
task_db.insert(task.to_dict())
|
task_db.insert(task.to_dict())
|
||||||
send_event_to_user("user123", Event(EventType.TASK_MODIFIED.value,
|
resp = send_event_for_current_user(Event(EventType.TASK_MODIFIED.value,
|
||||||
TaskModified(task.id, TaskModified.OPERATION_ADD)))
|
TaskModified(task.id, TaskModified.OPERATION_ADD)))
|
||||||
|
if resp:
|
||||||
|
return resp
|
||||||
return jsonify({'message': f'Task {name} added.'}), 201
|
return jsonify({'message': f'Task {name} added.'}), 201
|
||||||
|
|
||||||
@task_api.route('/task/<id>', methods=['GET'])
|
@task_api.route('/task/<id>', methods=['GET'])
|
||||||
@@ -51,8 +53,10 @@ def delete_task(id):
|
|||||||
if id in tasks:
|
if id in tasks:
|
||||||
tasks.remove(id)
|
tasks.remove(id)
|
||||||
child_db.update({'tasks': tasks}, ChildQuery.id == child.get('id'))
|
child_db.update({'tasks': tasks}, ChildQuery.id == child.get('id'))
|
||||||
send_event_to_user("user123", Event(EventType.TASK_MODIFIED.value,
|
resp = send_event_for_current_user(Event(EventType.TASK_MODIFIED.value,
|
||||||
TaskModified(id, TaskModified.OPERATION_DELETE)))
|
TaskModified(id, TaskModified.OPERATION_DELETE)))
|
||||||
|
if resp:
|
||||||
|
return resp
|
||||||
|
|
||||||
return jsonify({'message': f'Task {id} deleted.'}), 200
|
return jsonify({'message': f'Task {id} deleted.'}), 200
|
||||||
return jsonify({'error': 'Task not found'}), 404
|
return jsonify({'error': 'Task not found'}), 404
|
||||||
@@ -95,6 +99,8 @@ def edit_task(id):
|
|||||||
|
|
||||||
task_db.update(updates, TaskQuery.id == id)
|
task_db.update(updates, TaskQuery.id == id)
|
||||||
updated = task_db.get(TaskQuery.id == id)
|
updated = task_db.get(TaskQuery.id == id)
|
||||||
send_event_to_user("user123", Event(EventType.TASK_MODIFIED.value,
|
resp = send_event_for_current_user(Event(EventType.TASK_MODIFIED.value,
|
||||||
TaskModified(id, TaskModified.OPERATION_EDIT)))
|
TaskModified(id, TaskModified.OPERATION_EDIT)))
|
||||||
|
if resp:
|
||||||
|
return resp
|
||||||
return jsonify(updated), 200
|
return jsonify(updated), 200
|
||||||
|
|||||||
28
api/utils.py
Normal file
28
api/utils.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import jwt
|
||||||
|
from flask import request, current_app, jsonify
|
||||||
|
|
||||||
|
from events.sse import send_event_to_user
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_email(email):
|
||||||
|
return email.replace('@', '_at_').replace('.', '_dot_')
|
||||||
|
|
||||||
|
def get_current_user_id():
|
||||||
|
token = request.cookies.get('token')
|
||||||
|
if not token:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
payload = jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=['HS256'])
|
||||||
|
email = payload.get('email')
|
||||||
|
if not email:
|
||||||
|
return None
|
||||||
|
return sanitize_email(email)
|
||||||
|
except jwt.InvalidTokenError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def send_event_for_current_user(event):
|
||||||
|
user_id = get_current_user_id()
|
||||||
|
if not user_id:
|
||||||
|
return jsonify({'error': 'Unauthorized'}), 401
|
||||||
|
send_event_to_user(user_id, event)
|
||||||
|
return None
|
||||||
4
main.py
4
main.py
@@ -38,7 +38,8 @@ app.config.update(
|
|||||||
MAIL_USERNAME='ryan.kegel@gmail.com',
|
MAIL_USERNAME='ryan.kegel@gmail.com',
|
||||||
MAIL_PASSWORD='ruyj hxjf nmrz buar',
|
MAIL_PASSWORD='ruyj hxjf nmrz buar',
|
||||||
MAIL_DEFAULT_SENDER='ryan.kegel@gmail.com',
|
MAIL_DEFAULT_SENDER='ryan.kegel@gmail.com',
|
||||||
FRONTEND_URL='https://localhost:5173' # Adjust as needed
|
FRONTEND_URL='https://localhost:5173', # Adjust as needed
|
||||||
|
SECRET_KEY='supersecretkey' # Replace with a secure key in production
|
||||||
)
|
)
|
||||||
mail.init_app(app)
|
mail.init_app(app)
|
||||||
|
|
||||||
@@ -75,7 +76,6 @@ def start_background_threads():
|
|||||||
broadcaster.start()
|
broadcaster.start()
|
||||||
|
|
||||||
# TODO: implement users
|
# TODO: implement users
|
||||||
os.makedirs(get_user_image_dir("user123"), exist_ok=True)
|
|
||||||
initializeImages()
|
initializeImages()
|
||||||
createDefaultTasks()
|
createDefaultTasks()
|
||||||
createDefaultRewards()
|
createDefaultRewards()
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { useBackendEvents } from './common/backendEvents'
|
|
||||||
import { checkAuth } from '@/stores/auth'
|
|
||||||
useBackendEvents('user123')
|
|
||||||
checkAuth()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<BackendEventsListener />
|
||||||
<router-view />
|
<router-view />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import BackendEventsListener from '@/components/BackendEventsListener.vue'
|
||||||
|
import { checkAuth } from '@/stores/auth'
|
||||||
|
|
||||||
|
checkAuth()
|
||||||
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import { onMounted, onBeforeUnmount } from 'vue'
|
import { onMounted, onBeforeUnmount, watch } from 'vue'
|
||||||
|
import type { Ref } from 'vue'
|
||||||
import { eventBus } from './eventBus'
|
import { eventBus } from './eventBus'
|
||||||
|
|
||||||
export function useBackendEvents(userId: string) {
|
export function useBackendEvents(userId: Ref<string>) {
|
||||||
let eventSource: EventSource | null = null
|
let eventSource: EventSource | null = null
|
||||||
|
|
||||||
onMounted(() => {
|
const connect = () => {
|
||||||
console.log('Connecting to backend events for user:', userId)
|
if (eventSource) eventSource.close()
|
||||||
eventSource = new EventSource(`/events?user_id=${userId}`)
|
if (userId.value) {
|
||||||
|
console.log('Connecting to backend events for user:', userId.value)
|
||||||
|
eventSource = new EventSource(`/events?user_id=${userId.value}`)
|
||||||
|
|
||||||
eventSource.onmessage = (event) => {
|
eventSource.onmessage = (event) => {
|
||||||
const data = JSON.parse(event.data)
|
const data = JSON.parse(event.data)
|
||||||
@@ -15,10 +18,13 @@ export function useBackendEvents(userId: string) {
|
|||||||
eventBus.emit(data.type, data)
|
eventBus.emit(data.type, data)
|
||||||
eventBus.emit('sse', data) // optional: catch-all channel
|
eventBus.emit('sse', data) // optional: catch-all channel
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(connect)
|
||||||
|
watch(userId, connect)
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
console.log('Disconnecting from backend events for user:', userId)
|
console.log('Disconnecting from backend events for user:', userId.value)
|
||||||
eventSource?.close()
|
eventSource?.close()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
18
web/vue-app/src/components/BackendEventsListener.vue
Normal file
18
web/vue-app/src/components/BackendEventsListener.vue
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
import { useBackendEvents } from '@/common/backendEvents'
|
||||||
|
import { currentUserId } from '@/stores/auth'
|
||||||
|
|
||||||
|
const userId = ref(currentUserId.value)
|
||||||
|
|
||||||
|
watch(currentUserId, (id) => {
|
||||||
|
userId.value = id
|
||||||
|
})
|
||||||
|
|
||||||
|
// Always call useBackendEvents in setup, passing the reactive userId
|
||||||
|
useBackendEvents(userId)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div></div>
|
||||||
|
</template>
|
||||||
@@ -3,6 +3,7 @@ import { ref } from 'vue'
|
|||||||
export const isParentAuthenticated = ref(false)
|
export const isParentAuthenticated = ref(false)
|
||||||
export const isUserLoggedIn = ref(false)
|
export const isUserLoggedIn = ref(false)
|
||||||
export const isAuthReady = ref(false)
|
export const isAuthReady = ref(false)
|
||||||
|
export const currentUserId = ref('')
|
||||||
|
|
||||||
export function authenticateParent() {
|
export function authenticateParent() {
|
||||||
isParentAuthenticated.value = true
|
isParentAuthenticated.value = true
|
||||||
@@ -23,9 +24,17 @@ export function logoutUser() {
|
|||||||
export async function checkAuth() {
|
export async function checkAuth() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/me', { method: 'GET' })
|
const res = await fetch('/api/me', { method: 'GET' })
|
||||||
isUserLoggedIn.value = res.ok
|
if (res.ok) {
|
||||||
|
const data = await res.json()
|
||||||
|
currentUserId.value = data.id
|
||||||
|
isUserLoggedIn.value = true
|
||||||
|
} else {
|
||||||
|
isUserLoggedIn.value = false
|
||||||
|
currentUserId.value = ''
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
isUserLoggedIn.value = false
|
isUserLoggedIn.value = false
|
||||||
|
currentUserId.value = ''
|
||||||
}
|
}
|
||||||
isAuthReady.value = true
|
isAuthReady.value = true
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user