chore(01-01): add FERNET_KEY config and email tables migration

- Add FERNET_KEY to .env.example with generation instructions
- Register email.models in aerich_config.py and app.py
- Register email blueprint in app.py
- Create database migration for email_accounts, email_sync_status, emails
- Migration includes proper foreign keys and indexes
This commit is contained in:
2026-02-08 09:15:23 -05:00
parent bee63d1c60
commit 43dd05f9d5
4 changed files with 84 additions and 0 deletions

View File

@@ -54,3 +54,14 @@ OIDC_USE_DISCOVERY=true
YNAB_ACCESS_TOKEN=your-ynab-personal-access-token
# Optional: Specify a budget ID, or leave empty to use the default/first budget
YNAB_BUDGET_ID=
# Mealie Configuration
# Base URL for your Mealie instance (e.g., http://192.168.1.5:9000 or https://mealie.example.com)
MEALIE_BASE_URL=http://192.168.1.5:9000
# Get your API token from Mealie's user settings page
MEALIE_API_TOKEN=your-mealie-api-token
# Email Integration
# Email Encryption Key (32-byte URL-safe base64)
# Generate with: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
FERNET_KEY=your-fernet-key-here

View File

@@ -16,6 +16,7 @@ TORTOISE_ORM = {
"models": [
"blueprints.conversation.models",
"blueprints.users.models",
"blueprints.email.models",
"aerich.models",
],
"default_connection": "default",

16
app.py
View File

@@ -1,4 +1,5 @@
import os
import logging
from dotenv import load_dotenv
from quart import Quart, jsonify, render_template, request, send_from_directory
@@ -7,6 +8,7 @@ from tortoise.contrib.quart import register_tortoise
import blueprints.conversation
import blueprints.conversation.logic
import blueprints.email
import blueprints.rag
import blueprints.users
import blueprints.users.models
@@ -15,6 +17,18 @@ from main import consult_simba_oracle
# Load environment variables
load_dotenv()
# Configure logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[logging.StreamHandler()],
)
# Ensure YNAB and Mealie loggers are visible
logging.getLogger("utils.ynab_service").setLevel(logging.INFO)
logging.getLogger("utils.mealie_service").setLevel(logging.INFO)
logging.getLogger("blueprints.conversation.agents").setLevel(logging.INFO)
app = Quart(
__name__,
static_folder="raggr-frontend/dist/static",
@@ -27,6 +41,7 @@ jwt = JWTManager(app)
# Register blueprints
app.register_blueprint(blueprints.users.user_blueprint)
app.register_blueprint(blueprints.conversation.conversation_blueprint)
app.register_blueprint(blueprints.email.email_blueprint)
app.register_blueprint(blueprints.rag.rag_blueprint)
@@ -42,6 +57,7 @@ TORTOISE_CONFIG = {
"models": [
"blueprints.conversation.models",
"blueprints.users.models",
"blueprints.email.models",
"aerich.models",
]
},

View File

@@ -0,0 +1,56 @@
from tortoise import BaseDBAsyncClient
RUN_IN_TRANSACTION = True
async def upgrade(db: BaseDBAsyncClient) -> str:
return """
CREATE TABLE IF NOT EXISTS "email_accounts" (
"id" UUID NOT NULL PRIMARY KEY,
"email_address" VARCHAR(255) NOT NULL UNIQUE,
"display_name" VARCHAR(255),
"imap_host" VARCHAR(255) NOT NULL,
"imap_port" INT NOT NULL DEFAULT 993,
"imap_username" VARCHAR(255) NOT NULL,
"imap_password" TEXT NOT NULL,
"is_active" BOOL NOT NULL DEFAULT TRUE,
"last_error" TEXT,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"user_id" UUID NOT NULL REFERENCES "users" ("id") ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS "email_sync_status" (
"id" UUID NOT NULL PRIMARY KEY,
"last_sync_date" TIMESTAMPTZ,
"last_message_uid" INT NOT NULL DEFAULT 0,
"message_count" INT NOT NULL DEFAULT 0,
"consecutive_failures" INT NOT NULL DEFAULT 0,
"last_failure_date" TIMESTAMPTZ,
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"account_id" UUID NOT NULL UNIQUE REFERENCES "email_accounts" ("id") ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS "emails" (
"id" UUID NOT NULL PRIMARY KEY,
"message_id" VARCHAR(255) NOT NULL UNIQUE,
"subject" VARCHAR(500) NOT NULL,
"from_address" VARCHAR(255) NOT NULL,
"to_address" TEXT NOT NULL,
"date" TIMESTAMPTZ NOT NULL,
"body_text" TEXT,
"body_html" TEXT,
"chromadb_doc_id" VARCHAR(255),
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"expires_at" TIMESTAMPTZ NOT NULL,
"account_id" UUID NOT NULL REFERENCES "email_accounts" ("id") ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS "idx_emails_message_9e3c0c" ON "emails" ("message_id");"""
async def downgrade(db: BaseDBAsyncClient) -> str:
return """
DROP TABLE IF EXISTS "emails";
DROP TABLE IF EXISTS "email_sync_status";
DROP TABLE IF EXISTS "email_accounts";"""
MODELS_STATE = ""