added seperate users for backend events

This commit is contained in:
2026-01-06 16:25:09 -05:00
parent d7fc3c0cab
commit fd1057662f
11 changed files with 172 additions and 80 deletions

View File

@@ -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']

View File

@@ -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,

View File

@@ -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':

View File

@@ -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

View File

@@ -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
View 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

View File

@@ -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()

View File

@@ -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;

View File

@@ -1,24 +1,30 @@
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)
// Emit globally for any component that cares // Emit globally for any component that cares
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()
}) })
} }

View 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>

View File

@@ -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
} }