fixed comments

This commit is contained in:
c-d-p
2025-04-26 12:43:19 +02:00
parent 10e5a3c489
commit 22a4fc50a5
49 changed files with 165 additions and 148 deletions

View File

@@ -1,7 +1,6 @@
# core/celery_app.py
from celery import Celery
from core.config import settings # Import your settings
from core.config import settings
celery_app = Celery(
"worker",
broker=settings.REDIS_URL,
@@ -9,9 +8,5 @@ celery_app = Celery(
include=[
"modules.auth.tasks",
"modules.admin.tasks",
], # Add paths to modules containing tasks
# Add other modules with tasks here, e.g., "modules.some_other_module.tasks"
],
)
# Optional: Update Celery configuration directly if needed
# celery_app.conf.update(task_track_started=True)

View File

@@ -20,7 +20,7 @@ class Settings(BaseSettings):
JWT_SECRET_KEY: str
# Other settings
GOOGLE_API_KEY: str = "" # Example with a default
GOOGLE_API_KEY: str
class Config:
# Tell pydantic-settings to load variables from a .env file

View File

@@ -1,4 +1,3 @@
# core/database.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session, declarative_base
from typing import Generator
@@ -13,7 +12,7 @@ _SessionLocal = None
def get_engine():
global _engine
if _engine is None:
if (_engine is None):
if not settings.DB_URL:
raise ValueError("DB_URL is not set in Settings.")
print(f"Connecting to database at {settings.DB_URL}")

View File

@@ -1,4 +1,3 @@
# main.py
from contextlib import _AsyncGeneratorContextManager, asynccontextmanager
from typing import Any, Callable
from fastapi import FastAPI
@@ -7,14 +6,9 @@ from core.database import get_engine, Base
from modules import router
import logging
# import all models to ensure they are registered before create_all
logging.getLogger("passlib").setLevel(logging.ERROR) # fix bc package logging is broken
# Create DB tables (remove in production; use migrations instead)
def lifespan_factory() -> Callable[[FastAPI], _AsyncGeneratorContextManager[Any]]:
@asynccontextmanager
@@ -29,16 +23,11 @@ def lifespan_factory() -> Callable[[FastAPI], _AsyncGeneratorContextManager[Any]
lifespan = lifespan_factory()
app = FastAPI(lifespan=lifespan)
# Include module router
app.include_router(router)
# CORS
app.add_middleware(
CORSMiddleware,
allow_origins=[
"http://localhost:8081", # Keep for web testing if needed
"http://192.168.1.9:8081", # Add your mobile device/emulator origin (adjust port if needed)
"http://192.168.255.221:8081",
"https://maia.depaoli.id.au",
],
allow_credentials=True,
@@ -47,7 +36,6 @@ app.add_middleware(
)
# Health endpoint
@app.get("/api/health")
def health():
return {"status": "ok"}

View File

@@ -1,7 +1,7 @@
# modules/admin/api.py
from typing import Annotated
from fastapi import APIRouter, Depends # Import Body
from pydantic import BaseModel # Import BaseModel
from fastapi import APIRouter, Depends
from pydantic import BaseModel
from sqlalchemy.orm import Session
from core.database import get_db
from modules.auth.dependencies import admin_only
@@ -10,7 +10,6 @@ from .tasks import cleardb
router = APIRouter(prefix="/admin", tags=["admin"], dependencies=[Depends(admin_only)])
# Define a Pydantic model for the request body
class ClearDbRequest(BaseModel):
hard: bool
@@ -20,7 +19,6 @@ def read_admin():
return {"message": "Admin route"}
# Change to POST and use the request body model
@router.post("/cleardb")
def clear_db(payload: ClearDbRequest, db: Annotated[Session, Depends(get_db)]):
"""
@@ -28,6 +26,6 @@ def clear_db(payload: ClearDbRequest, db: Annotated[Session, Depends(get_db)]):
'hard'=True: Drop and recreate all tables.
'hard'=False: Delete data from tables except users.
"""
hard = payload.hard # Get 'hard' from the payload
hard = payload.hard
cleardb.delay(hard)
return {"message": "Clearing database in the background", "hard": hard}

View File

@@ -1,4 +1 @@
# modules/admin/services.py
## temp

View File

@@ -18,16 +18,13 @@ def cleardb(hard: bool):
db = SessionLocal()
if hard:
# Drop and recreate all tables
Base.metadata.drop_all(bind=engine)
Base.metadata.create_all(bind=engine)
db.commit()
return {"message": "Database reset (HARD)"}
else:
# Delete data from tables except users
tables = Base.metadata.tables.keys()
for table_name in tables:
# delete all tables that isn't the users table
if table_name != "users":
table = Base.metadata.tables[table_name]
print(f"Deleting table: {table_name}")

View File

@@ -1,4 +1,3 @@
# modules/auth/api.py
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from jose import JWTError
@@ -25,7 +24,7 @@ from sqlalchemy.orm import Session
from typing import Annotated
from core.database import get_db
from datetime import timedelta
from core.config import settings # Assuming settings is defined in core.config
from core.config import settings
from core.exceptions import unauthorized_exception
router = APIRouter(prefix="/auth", tags=["auth"])

View File

@@ -1,4 +1,3 @@
# modules/auth/dependencies.py
from fastapi import Depends
from modules.auth.security import get_current_user
from modules.auth.schemas import UserRole

View File

@@ -1,4 +1,3 @@
# modules/auth/schemas.py
from enum import Enum as PyEnum
from pydantic import BaseModel

View File

@@ -29,7 +29,7 @@ password_hasher = PasswordHasher()
def hash_password(password: str) -> str:
"""Hash a password with Argon2 (and optional pepper)."""
peppered_password = password + settings.PEPPER # Prepend/append pepper
peppered_password = password + settings.PEPPER
return password_hasher.hash(peppered_password)
@@ -47,10 +47,8 @@ def authenticate_user(username: str, password: str, db: Session) -> User | None:
Authenticate a user by checking username/password against the database.
Returns User object if valid, None otherwise.
"""
# Get user from database
user = db.query(User).filter(User.username == username).first()
# If user not found or password doesn't match
if not user or not verify_password(password, user.hashed_password):
return None
@@ -65,7 +63,6 @@ def create_access_token(data: dict, expires_delta: timedelta | None = None):
expire = datetime.now(timezone.utc) + timedelta(
minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES
)
# expire = datetime.now(timezone.utc) + timedelta(seconds=5)
to_encode.update({"exp": expire, "token_type": TokenType.ACCESS})
return jwt.encode(
to_encode, settings.JWT_SECRET_KEY, algorithm=settings.JWT_ALGORITHM
@@ -89,22 +86,6 @@ def create_refresh_token(data: dict, expires_delta: timedelta | None = None):
def verify_token(
token: str, expected_token_type: TokenType, db: Session
) -> TokenData | None:
"""Verify a JWT token and return TokenData if valid.
Parameters
----------
token: str
The JWT token to be verified.
expected_token_type: TokenType
The expected type of token (access or refresh)
db: Session
Database session to fetch user data.
Returns
-------
TokenData | None
TokenData instance if the token is valid, None otherwise.
"""
is_blacklisted = (
db.query(TokenBlacklist).filter(TokenBlacklist.token == token).first()
is not None
@@ -137,7 +118,6 @@ def get_current_user(
headers={"WWW-Authenticate": "Bearer"},
)
# Check if the token is blacklisted
is_blacklisted = (
db.query(TokenBlacklist).filter(TokenBlacklist.token == token).first()
is not None
@@ -178,7 +158,6 @@ def blacklist_tokens(access_token: str, refresh_token: str, db: Session) -> None
)
expires_at = datetime.fromtimestamp(payload.get("exp"))
# Add the token to the blacklist
blacklisted_token = TokenBlacklist(token=token, expires_at=expires_at)
db.add(blacklisted_token)
@@ -191,7 +170,6 @@ def blacklist_token(token: str, db: Session) -> None:
)
expires_at = datetime.fromtimestamp(payload.get("exp"))
# Add the token to the blacklist
blacklisted_token = TokenBlacklist(token=token, expires_at=expires_at)
db.add(blacklisted_token)
db.commit()

View File

@@ -1,4 +1,3 @@
# modules/auth/services.py
from sqlalchemy.orm import Session
from modules.auth.models import User
from modules.auth.schemas import UserResponse

View File

@@ -1,4 +1,3 @@
# modules/calendar/api.py
from fastapi import APIRouter, Depends, status
from sqlalchemy.orm import Session
from datetime import datetime

View File

@@ -1,7 +1,6 @@
# modules/calendar/schemas.py
from datetime import datetime
from pydantic import BaseModel, field_validator # Add field_validator
from typing import List, Optional # Add List and Optional
from pydantic import BaseModel, field_validator
from typing import List, Optional
# Base schema for common fields, including tags

View File

@@ -1,4 +1,3 @@
# modules/nlp/api.py
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from typing import List
@@ -8,7 +7,6 @@ from core.database import get_db
from modules.auth.dependencies import get_current_user
from modules.auth.models import User
# Import the new service functions and Enum
from modules.nlp.service import (
process_request,
ask_ai,
@@ -17,7 +15,6 @@ from modules.nlp.service import (
MessageSender,
)
# Import the response schema and the new ChatMessage model for response type hinting
from modules.nlp.schemas import ProcessCommandRequest, ProcessCommandResponse
from modules.calendar.service import (
create_calendar_event,
@@ -28,7 +25,6 @@ from modules.calendar.service import (
from modules.calendar.models import CalendarEvent
from modules.calendar.schemas import CalendarEventCreate, CalendarEventUpdate
# Import TODO services, schemas, and model
from modules.todo import service as todo_service
from modules.todo.models import Todo
from modules.todo.schemas import TodoCreate, TodoUpdate
@@ -38,24 +34,22 @@ from datetime import datetime
class ChatMessageResponse(BaseModel):
id: int
sender: MessageSender # Use the enum directly
sender: MessageSender
text: str
timestamp: datetime
class Config:
from_attributes = True # Allow Pydantic to work with ORM models
from_attributes = True
router = APIRouter(prefix="/nlp", tags=["nlp"])
# Helper to format calendar events (expects list of CalendarEvent models)
def format_calendar_events(events: List[CalendarEvent]) -> List[str]:
if not events:
return ["You have no events matching that criteria."]
formatted = ["Here are the events:"]
for event in events:
# Access attributes directly from the model instance
start_str = (
event.start.strftime("%Y-%m-%d %H:%M") if event.start else "No start time"
)
@@ -65,7 +59,6 @@ def format_calendar_events(events: List[CalendarEvent]) -> List[str]:
return formatted
# Helper to format TODO items (expects list of Todo models)
def format_todos(todos: List[Todo]) -> List[str]:
if not todos:
return ["Your TODO list is empty."]
@@ -80,7 +73,6 @@ def format_todos(todos: List[Todo]) -> List[str]:
return formatted
# Update the response model for the endpoint
@router.post("/process-command", response_model=ProcessCommandResponse)
def process_command(
request_data: ProcessCommandRequest,
@@ -92,34 +84,25 @@ def process_command(
"""
user_input = request_data.user_input
# --- Save User Message ---
save_chat_message(
db, user_id=current_user.id, sender=MessageSender.USER, text=user_input
)
# ------------------------
command_data = process_request(user_input)
intent = command_data["intent"]
params = command_data["params"]
response_text = command_data["response_text"]
responses = [response_text] # Start with the initial response
responses = [response_text]
# --- Save Initial AI Response ---
# Save the first response generated by process_request
save_chat_message(
db, user_id=current_user.id, sender=MessageSender.AI, text=response_text
)
# -----------------------------
if intent == "error":
# Don't raise HTTPException here if we want to save the error message
# Instead, return the error response directly
# save_chat_message(db, user_id=current_user.id, sender=MessageSender.AI, text=response_text) # Already saved above
return ProcessCommandResponse(responses=responses)
if intent == "clarification_needed" or intent == "unknown":
# save_chat_message(db, user_id=current_user.id, sender=MessageSender.AI, text=response_text) # Already saved above
return ProcessCommandResponse(responses=responses)
try:
@@ -127,11 +110,9 @@ def process_command(
case "ask_ai":
ai_answer = ask_ai(**params)
responses.append(ai_answer)
# --- Save Additional AI Response ---
save_chat_message(
db, user_id=current_user.id, sender=MessageSender.AI, text=ai_answer
)
# ---------------------------------
return ProcessCommandResponse(responses=responses)
case "get_calendar_events":
@@ -140,12 +121,10 @@ def process_command(
)
formatted_responses = format_calendar_events(events)
responses.extend(formatted_responses)
# --- Save Additional AI Responses ---
for resp in formatted_responses:
save_chat_message(
db, user_id=current_user.id, sender=MessageSender.AI, text=resp
)
# ----------------------------------
return ProcessCommandResponse(responses=responses)
case "add_calendar_event":
@@ -159,20 +138,17 @@ def process_command(
title = created_event.title or "Untitled Event"
add_response = f"Added: {title} starting at {start_str}."
responses.append(add_response)
# --- Save Additional AI Response ---
save_chat_message(
db,
user_id=current_user.id,
sender=MessageSender.AI,
text=add_response,
)
# ---------------------------------
return ProcessCommandResponse(responses=responses)
case "update_calendar_event":
event_id = params.pop("event_id", None)
if event_id is None:
# Save the error message before raising
error_msg = "Event ID is required for update."
save_chat_message(
db,
@@ -188,20 +164,17 @@ def process_command(
title = updated_event.title or "Untitled Event"
update_response = f"Updated event ID {updated_event.id}: {title}."
responses.append(update_response)
# --- Save Additional AI Response ---
save_chat_message(
db,
user_id=current_user.id,
sender=MessageSender.AI,
text=update_response,
)
# ---------------------------------
return ProcessCommandResponse(responses=responses)
case "delete_calendar_event":
event_id = params.get("event_id")
if event_id is None:
# Save the error message before raising
error_msg = "Event ID is required for delete."
save_chat_message(
db,
@@ -213,29 +186,24 @@ def process_command(
delete_calendar_event(db, current_user.id, event_id)
delete_response = f"Deleted event ID {event_id}."
responses.append(delete_response)
# --- Save Additional AI Response ---
save_chat_message(
db,
user_id=current_user.id,
sender=MessageSender.AI,
text=delete_response,
)
# ---------------------------------
return ProcessCommandResponse(responses=responses)
# --- Add TODO Cases ---
case "get_todos":
todos: List[Todo] = todo_service.get_todos(
db, user=current_user, **params
)
formatted_responses = format_todos(todos)
responses.extend(formatted_responses)
# --- Save Additional AI Responses ---
for resp in formatted_responses:
save_chat_message(
db, user_id=current_user.id, sender=MessageSender.AI, text=resp
)
# ----------------------------------
return ProcessCommandResponse(responses=responses)
case "add_todo":
@@ -247,14 +215,12 @@ def process_command(
f"Added TODO: '{created_todo.task}' (ID: {created_todo.id})."
)
responses.append(add_response)
# --- Save Additional AI Response ---
save_chat_message(
db,
user_id=current_user.id,
sender=MessageSender.AI,
text=add_response,
)
# ---------------------------------
return ProcessCommandResponse(responses=responses)
case "update_todo":
@@ -279,14 +245,12 @@ def process_command(
status = "complete" if params["complete"] else "incomplete"
update_response += f" Marked as {status}."
responses.append(update_response)
# --- Save Additional AI Response ---
save_chat_message(
db,
user_id=current_user.id,
sender=MessageSender.AI,
text=update_response,
)
# ---------------------------------
return ProcessCommandResponse(responses=responses)
case "delete_todo":
@@ -307,26 +271,21 @@ def process_command(
f"Deleted TODO ID {deleted_todo.id}: '{deleted_todo.task}'."
)
responses.append(delete_response)
# --- Save Additional AI Response ---
save_chat_message(
db,
user_id=current_user.id,
sender=MessageSender.AI,
text=delete_response,
)
# ---------------------------------
return ProcessCommandResponse(responses=responses)
# --- End TODO Cases ---
case _:
print(
f"Warning: Unhandled intent '{intent}' reached api.py match statement."
)
# The initial response_text was already saved
return ProcessCommandResponse(responses=responses)
except HTTPException as http_exc:
# Don't save again if already saved before raising
if http_exc.status_code != 400 or ("event_id" not in http_exc.detail.lower()):
save_chat_message(
db,
@@ -340,11 +299,9 @@ def process_command(
error_response = (
"Sorry, I encountered an error while trying to perform that action."
)
# --- Save Final Error AI Response ---
save_chat_message(
db, user_id=current_user.id, sender=MessageSender.AI, text=error_response
)
# ----------------------------------
return ProcessCommandResponse(responses=[error_response])
@@ -355,6 +312,3 @@ def read_chat_history(
"""Retrieves the last 50 chat messages for the current user."""
history = get_chat_history(db, user_id=current_user.id, limit=50)
return history
# -------------------------------------

View File

@@ -1,4 +1,3 @@
# /home/cdp/code/MAIA/backend/modules/nlp/models.py
from sqlalchemy import Column, Integer, Text, DateTime, ForeignKey, Enum as SQLEnum
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func

View File

@@ -1,4 +1,3 @@
# modules/nlp/schemas.py
from pydantic import BaseModel
from typing import List
@@ -9,5 +8,4 @@ class ProcessCommandRequest(BaseModel):
class ProcessCommandResponse(BaseModel):
responses: List[str]
# Optional: Keep details if needed for specific frontend logic beyond display
# details: dict | None = None

View File

@@ -1,5 +1,3 @@
# modules/nlp/service.py
from sqlalchemy.orm import Session
from sqlalchemy import desc # Import desc for ordering
from google import genai

View File

@@ -1,2 +1 @@
# backend/modules/todo/__init__.py
# This file makes the 'todo' directory a Python package.

View File

@@ -1,17 +1,16 @@
# backend/modules/todo/api.py
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from typing import List
from . import service, schemas
from core.database import get_db
from modules.auth.dependencies import get_current_user # Corrected import
from modules.auth.models import User # Assuming User model is in auth.models
from modules.auth.dependencies import get_current_user
from modules.auth.models import User
router = APIRouter(
prefix="/todos",
tags=["todos"],
dependencies=[Depends(get_current_user)], # Corrected dependency
dependencies=[Depends(get_current_user)],
responses={404: {"description": "Not found"}},
)
@@ -20,7 +19,7 @@ router = APIRouter(
def create_todo_endpoint(
todo: schemas.TodoCreate,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user), # Corrected dependency
current_user: User = Depends(get_current_user),
):
return service.create_todo(db=db, todo=todo, user=current_user)
@@ -30,7 +29,7 @@ def read_todos_endpoint(
skip: int = 0,
limit: int = 100,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user), # Corrected dependency
current_user: User = Depends(get_current_user),
):
todos = service.get_todos(db=db, user=current_user, skip=skip, limit=limit)
return todos
@@ -40,7 +39,7 @@ def read_todos_endpoint(
def read_todo_endpoint(
todo_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user), # Corrected dependency
current_user: User = Depends(get_current_user),
):
db_todo = service.get_todo(db=db, todo_id=todo_id, user=current_user)
if db_todo is None:
@@ -53,7 +52,7 @@ def update_todo_endpoint(
todo_id: int,
todo_update: schemas.TodoUpdate,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user), # Corrected dependency
current_user: User = Depends(get_current_user),
):
return service.update_todo(
db=db, todo_id=todo_id, todo_update=todo_update, user=current_user
@@ -64,6 +63,6 @@ def update_todo_endpoint(
def delete_todo_endpoint(
todo_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user), # Corrected dependency
current_user: User = Depends(get_current_user),
):
return service.delete_todo(db=db, todo_id=todo_id, user=current_user)

View File

@@ -16,4 +16,4 @@ class Todo(Base):
owner = relationship(
"User"
) # Add relationship if needed, assuming User model exists in auth.models
)

View File

@@ -1,4 +1,3 @@
# modules/user/api.py
from typing import Annotated
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session

View File

@@ -20,7 +20,8 @@
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"softwareKeyboardLayoutMode": "resize"
"softwareKeyboardLayoutMode": "resize",
"package": "com.seedeep.maia"
},
"web": {
"favicon": "./assets/favicon.png"
@@ -28,6 +29,12 @@
"plugins": [
"expo-secure-store",
"expo-font"
]
],
"extra": {
"eas": {
"projectId": "4d7d70ce-a4d8-4307-8827-8ef713b95b78"
}
},
"owner": "cdp202"
}
}

View File

@@ -0,0 +1,21 @@
{
"cli": {
"version": ">= 16.3.2",
"appVersionSource": "remote"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
},
"preview": {
"distribution": "internal"
},
"production": {
"autoIncrement": true
}
},
"submit": {
"production": {}
}
}

View File

@@ -18,6 +18,7 @@
"axios": "^1.8.4",
"date-fns": "^4.1.0",
"expo": "^52.0.46",
"expo-dev-client": "~5.0.20",
"expo-font": "~13.0.4",
"expo-secure-store": "~14.0.1",
"expo-splash-screen": "~0.29.24",
@@ -3746,6 +3747,21 @@
"node": ">=8"
}
},
"node_modules/ajv": {
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
"integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/anser": {
"version": "1.4.10",
"resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz",
@@ -5307,6 +5323,54 @@
"react-native": "*"
}
},
"node_modules/expo-dev-client": {
"version": "5.0.20",
"resolved": "https://registry.npmjs.org/expo-dev-client/-/expo-dev-client-5.0.20.tgz",
"integrity": "sha512-bLNkHdU7V3I4UefgJbJnIDUBUL0LxIal/xYEx9BbgDd3B7wgQKY//+BpPIxBOKCQ22lkyiHY8y9tLhO903sAgg==",
"dependencies": {
"expo-dev-launcher": "5.0.35",
"expo-dev-menu": "6.0.25",
"expo-dev-menu-interface": "1.9.3",
"expo-manifests": "~0.15.8",
"expo-updates-interface": "~1.0.0"
},
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-dev-launcher": {
"version": "5.0.35",
"resolved": "https://registry.npmjs.org/expo-dev-launcher/-/expo-dev-launcher-5.0.35.tgz",
"integrity": "sha512-hEQr0ZREnUMxZ6wtQgfK1lzYnbb0zar3HqYZhmANzXmE6UEPbQ4GByLzhpfz/d+xxdBVQZsrHdtiV28KPG2sog==",
"dependencies": {
"ajv": "8.11.0",
"expo-dev-menu": "6.0.25",
"expo-manifests": "~0.15.8",
"resolve-from": "^5.0.0"
},
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-dev-menu": {
"version": "6.0.25",
"resolved": "https://registry.npmjs.org/expo-dev-menu/-/expo-dev-menu-6.0.25.tgz",
"integrity": "sha512-K2m4z/I+CPWbMtHlDzU68lHaQs52De0v5gbsjAmA5ig8FrYh4MKZvPxSVANaiKENzgmtglu8qaFh7ua9Gt2TfA==",
"dependencies": {
"expo-dev-menu-interface": "1.9.3"
},
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-dev-menu-interface": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/expo-dev-menu-interface/-/expo-dev-menu-interface-1.9.3.tgz",
"integrity": "sha512-KY/dWTBE1l47i9V366JN5rC6YIdOc9hz8yAmZzkl5DrPia5l3M2WIjtnpHC9zUkNjiSiG2urYoOAq4H/uLdmyg==",
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-file-system": {
"version": "18.0.12",
"resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-18.0.12.tgz",
@@ -5331,6 +5395,11 @@
"react": "*"
}
},
"node_modules/expo-json-utils": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/expo-json-utils/-/expo-json-utils-0.14.0.tgz",
"integrity": "sha512-xjGfK9dL0B1wLnOqNkX0jM9p48Y0I5xEPzHude28LY67UmamUyAACkqhZGaPClyPNfdzczk7Ej6WaRMT3HfXvw=="
},
"node_modules/expo-keep-awake": {
"version": "14.0.3",
"resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-14.0.3.tgz",
@@ -5340,6 +5409,18 @@
"react": "*"
}
},
"node_modules/expo-manifests": {
"version": "0.15.8",
"resolved": "https://registry.npmjs.org/expo-manifests/-/expo-manifests-0.15.8.tgz",
"integrity": "sha512-VuIyaMfRfLZeETNsRohqhy1l7iZ7I+HKMPfZXVL2Yn17TT0WkOhZoq1DzYwPbOHPgp1Uk6phNa86EyaHrD2DLw==",
"dependencies": {
"@expo/config": "~10.0.11",
"expo-json-utils": "~0.14.0"
},
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-modules-autolinking": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-2.0.8.tgz",
@@ -5427,6 +5508,14 @@
"react-native": "*"
}
},
"node_modules/expo-updates-interface": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/expo-updates-interface/-/expo-updates-interface-1.0.0.tgz",
"integrity": "sha512-93oWtvULJOj+Pp+N/lpTcFfuREX1wNeHtp7Lwn8EbzYYmdn37MvZU3TPW2tYYCZuhzmKEXnUblYcruYoDu7IrQ==",
"peerDependencies": {
"expo": "*"
}
},
"node_modules/exponential-backoff": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.2.tgz",
@@ -6596,6 +6685,11 @@
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
"integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw=="
},
"node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
},
"node_modules/json5": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
@@ -10308,6 +10402,14 @@
"browserslist": ">= 4.21.0"
}
},
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"dependencies": {
"punycode": "^2.1.0"
}
},
"node_modules/use-latest-callback": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.2.3.tgz",

View File

@@ -33,7 +33,8 @@
"react-native-safe-area-context": "4.12.0",
"react-native-screens": "~4.4.0",
"react-native-vector-icons": "^10.2.0",
"react-native-web": "~0.19.13"
"react-native-web": "~0.19.13",
"expo-dev-client": "~5.0.20"
},
"devDependencies": {
"@babel/core": "^7.25.2",

View File

@@ -1,15 +1,13 @@
// src/api/client.ts
import axios, { AxiosError } from 'axios'; // Import AxiosError
import axios, { AxiosError } from 'axios';
import { Platform } from 'react-native';
import * as SecureStore from 'expo-secure-store';
import AsyncStorage from '@react-native-async-storage/async-storage';
// const API_BASE_URL = process.env.EXPO_PUBLIC_API_URL || 'http://192.168.255.221:8000/api'; // Use your machine's IP
// const API_BASE_URL = process.env.EXPO_PUBLIC_API_URL || 'http://192.168.1.9:8000/api'; // Use your machine's IP
const API_BASE_URL = process.env.EXPO_PUBLIC_API_URL || 'https://maia.depaoli.id.au/api'; // Use your machine's IP
const ACCESS_TOKEN_KEY = 'maia_access_token'; // Renamed for clarity
const REFRESH_TOKEN_KEY = 'maia_refresh_token'; // Key for refresh token
const API_BASE_URL = process.env.EXPO_PUBLIC_API_URL || 'https://maia.depaoli.id.au/api';
const ACCESS_TOKEN_KEY = 'maia_access_token';
const REFRESH_TOKEN_KEY = 'maia_refresh_token';
console.log("Using API Base URL:", API_BASE_URL);
@@ -34,7 +32,7 @@ const deleteToken = async (key: string): Promise<void> => {
if (Platform.OS === 'web') {
await AsyncStorage.removeItem(key);
} else {
await SecureStore.deleteItemAsync(key).catch(() => {}); // Ignore delete error
await SecureStore.deleteItemAsync(key).catch(() => {});
}
};

View File

@@ -13,7 +13,7 @@ const AuthNavigator = () => {
return (
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="Login" component={LoginScreen} />
<Stack.Screen name="Register" component={RegisterScreen} /> {/* Add Register screen */}
<Stack.Screen name="Register" component={RegisterScreen} />
</Stack.Navigator>
);
};

View File

@@ -35,11 +35,7 @@ const RegisterScreen: React.FC<RegisterScreenProps> = ({ navigation }) => {
await register(username, password, name);
console.log("[RegisterScreen] handleRegister: Registration successful (from context perspective).");
// Show success message and navigate back to Login
Alert.alert(
'Registration Successful',
'Your account has been created. Please log in.',
[{ text: 'OK', onPress: () => navigation.navigate('Login') }]
);
navigation.navigate('Login');
} catch (err: any) {
console.log("[RegisterScreen] handleRegister: Caught error from context register.");
const errorMessage = err.response?.data?.detail ||