Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 68 additions & 53 deletions .env.example
Comment thread
michaelgichia marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -1,53 +1,68 @@
# Domain
# This would be set to the production domain with an env var on deployment
# used by Traefik to transmit traffic and aqcuire TLS certificates
DOMAIN=localhost
# To test the local Traefik config
# DOMAIN=localhost.tiangolo.com

# Used by the backend to generate links in emails to the frontend
# In staging and production, set this env var to the frontend host, e.g.
# FRONTEND_HOST=https://dashboard.example.com

# Environment: local, staging, production
ENVIRONMENT=local

PROJECT_NAME="Study Companion Project"
STACK_NAME=study-companion-project

# Backend
BACKEND_CORS_ORIGINS="http://localhost,http://localhost:5173,http://localhost:3000,http://localhost:3001,https://localhost,https://localhost:5173,http://localhost.tiangolo.com"
SECRET_KEY=changethis
FIRST_SUPERUSER=admin@example.com
FIRST_SUPERUSER_PASSWORD=changethis

# Emails
SMTP_HOST=
SMTP_USER=
SMTP_PASSWORD=
EMAILS_FROM_EMAIL=info@example.com
SMTP_TLS=True
SMTP_SSL=False
SMTP_PORT=587

# Postgres
POSTGRES_SERVER=localhost
POSTGRES_PORT=5432
POSTGRES_DB=app
POSTGRES_USER=postgres
POSTGRES_PASSWORD=changethis

SENTRY_DSN=

# Configure these with your own Docker registry images
DOCKER_IMAGE_BACKEND=backend
DOCKER_IMAGE_NEXT_APP=frontend

NEXT_PUBLIC_API_URL=http://localhost:8000


PINECONE_API_KEY=changethis

OPENAI_API_KEY=changethis

NEXT_PUBLIC_BACKEND_BASE_URL=http://localhost:8000
# Domain
# This would be set to the production domain with an env var on deployment
# used by Traefik to transmit traffic and aqcuire TLS certificates
DOMAIN=localhost
# To test the local Traefik config
# DOMAIN=localhost.tiangolo.com

# Used by the backend to generate links in emails to the frontend
# In staging and production, set this env var to the frontend host, e.g.
# FRONTEND_HOST=https://dashboard.example.com

# Environment: local, staging, production
ENVIRONMENT=local

PROJECT_NAME="Study Companion Project"
STACK_NAME=study-companion-project

# Backend
BACKEND_CORS_ORIGINS="http://localhost,http://localhost:5173,http://localhost:3000,http://localhost:3001,https://localhost,https://localhost:5173,http://localhost.tiangolo.com"
SECRET_KEY=changethis
FIRST_SUPERUSER=admin@example.com
FIRST_SUPERUSER_PASSWORD=changethis

# Emails
SMTP_HOST=
SMTP_USER=
SMTP_PASSWORD=
EMAILS_FROM_EMAIL=info@example.com
SMTP_TLS=True
SMTP_SSL=False
SMTP_PORT=587

# Postgres
POSTGRES_SERVER=localhost
POSTGRES_PORT=5432
POSTGRES_DB=app
POSTGRES_USER=postgres
POSTGRES_PASSWORD=changethis

SENTRY_DSN=

# Configure these with your own Docker registry images
DOCKER_IMAGE_BACKEND=backend
DOCKER_IMAGE_NEXT_APP=frontend

NEXT_PUBLIC_API_URL=http://localhost:8000


PINECONE_API_KEY=changethis

OPENAI_API_KEY=changethis

NEXT_PUBLIC_BACKEND_BASE_URL=http://localhost:8000

NEXT_INTERNAL_BACKEND_BASE_URL=http://backend:8000

# Podcast storage configuration
# "local" will store files under backend container at /app/podcasts
# "s3" will upload to an S3 bucket using the credentials below
PODCAST_STORAGE=local
PODCAST_LOCAL_DIR=/app/podcasts
AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=ieb...
AWS_REGION=changethis
S3_BUCKET_NAME=changethis
S3_PREFIX=podcasts/
PODCAST_TEACHER_VOICE=coral
PODCAST_STUDENT_VOICE=alloy
16 changes: 8 additions & 8 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
.DS_Store
htmlcov
.env
*.env
.env*
/.idea/
frontend/.*
.aider*
.DS_Store
htmlcov
*.env
/.idea/
frontend/.*
.aider*
config.bat
node_modules
Original file line number Diff line number Diff line change
@@ -1,46 +1,46 @@
"""Fix delete document error.

Revision ID: 10368f38610b
Revises: b5370243d0bc
Create Date: 2025-10-02 07:06:36.831373

"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy import inspect
import sqlmodel.sql.sqltypes


# revision identifiers, used by Alembic.
revision = '10368f38610b'
down_revision = 'b5370243d0bc'
branch_labels = None
depends_on = None


def upgrade():
conn = op.get_bind()
inspector = inspect(conn)
if 'chat' not in inspector.get_table_names():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('chat',
sa.Column('message', sqlmodel.sql.sqltypes.AutoString(length=1024), nullable=True),
sa.Column('is_system', sa.Boolean(), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
sa.Column('updated_at', sa.DateTime(), nullable=False),
sa.Column('id', sa.Uuid(), nullable=False),
sa.Column('course_id', sa.Uuid(), nullable=False),
sa.ForeignKeyConstraint(['course_id'], ['course.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)
op.drop_constraint(op.f('quizattempt_quiz_id_fkey'), 'quizattempt', type_='foreignkey')
op.create_foreign_key(None, 'quizattempt', 'quiz', ['quiz_id'], ['id'], ondelete='CASCADE')
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, 'quizattempt', type_='foreignkey')
op.create_foreign_key(op.f('quizattempt_quiz_id_fkey'), 'quizattempt', 'quiz', ['quiz_id'], ['id'])
op.drop_table('chat')
# ### end Alembic commands ###
"""Fix delete document error.
Revision ID: 10368f38610b
Revises: b5370243d0bc
Create Date: 2025-10-02 07:06:36.831373
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy import inspect
import sqlmodel.sql.sqltypes
# revision identifiers, used by Alembic.
revision = '10368f38610b'
down_revision = 'b5370243d0bc'
branch_labels = None
depends_on = None
def upgrade():
conn = op.get_bind()
inspector = inspect(conn)
if 'chat' not in inspector.get_table_names():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('chat',
sa.Column('message', sqlmodel.sql.sqltypes.AutoString(length=1024), nullable=True),
sa.Column('is_system', sa.Boolean(), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
sa.Column('updated_at', sa.DateTime(), nullable=False),
sa.Column('id', sa.Uuid(), nullable=False),
sa.Column('course_id', sa.Uuid(), nullable=False),
sa.ForeignKeyConstraint(['course_id'], ['course.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)
op.drop_constraint(op.f('quizattempt_quiz_id_fkey'), 'quizattempt', type_='foreignkey')
op.create_foreign_key(None, 'quizattempt', 'quiz', ['quiz_id'], ['id'], ondelete='CASCADE')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, 'quizattempt', type_='foreignkey')
op.create_foreign_key(op.f('quizattempt_quiz_id_fkey'), 'quizattempt', 'quiz', ['quiz_id'], ['id'])
op.drop_table('chat')
# ### end Alembic commands ###
38 changes: 38 additions & 0 deletions backend/app/alembic/versions/2042a1f0c0a1_add_podcast_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""add podcast table

Revision ID: 2042a1f0c0a1
Revises: 10368f38610b
Create Date: 2025-10-05 06:00:00.000000

"""
from alembic import op
import sqlalchemy as sa
import sqlmodel.sql.sqltypes


# revision identifiers, used by Alembic.
revision = '2042a1f0c0a1'
down_revision = '2cde6f094a4e'
branch_labels = None
depends_on = None


def upgrade():
op.create_table(
'podcast',
sa.Column('id', sa.Uuid(), nullable=False),
sa.Column('course_id', sa.Uuid(), nullable=False),
sa.Column('title', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False),
sa.Column('transcript', sa.Text(), nullable=False),
sa.Column('audio_path', sqlmodel.sql.sqltypes.AutoString(length=1024), nullable=False),
sa.Column('storage_backend', sqlmodel.sql.sqltypes.AutoString(length=50), nullable=False),
sa.Column('duration_seconds', sa.Float(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
sa.Column('updated_at', sa.DateTime(), nullable=False),
sa.ForeignKeyConstraint(['course_id'], ['course.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)


def downgrade():
op.drop_table('podcast')
Original file line number Diff line number Diff line change
@@ -1,47 +1,47 @@
"""Add cascade delete to quizsession.course_id

Revision ID: 98c65ba436bd
Revises: 2cde6f094a4e
Create Date: 2025-10-06 11:31:30.334302

"""
from alembic import op
import sqlalchemy as sa
import sqlmodel.sql.sqltypes
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision = '98c65ba436bd'
down_revision = '2cde6f094a4e'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_flashcardset_created_at'), table_name='flashcardset')
op.drop_index(op.f('ix_flashcardset_document_id'), table_name='flashcardset')
op.drop_index(op.f('ix_flashcardset_status'), table_name='flashcardset')
op.drop_index(op.f('ix_flashcardset_updated_at'), table_name='flashcardset')
op.drop_table('flashcardset')
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('flashcardset',
sa.Column('content_json', postgresql.JSONB(astext_type=sa.Text()), autoincrement=False, nullable=True),
sa.Column('document_id', sa.UUID(), autoincrement=False, nullable=False),
sa.Column('status', sa.VARCHAR(), autoincrement=False, nullable=False),
sa.Column('error_message', sa.VARCHAR(length=512), autoincrement=False, nullable=True),
sa.Column('id', sa.UUID(), autoincrement=False, nullable=False),
sa.Column('created_at', postgresql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), autoincrement=False, nullable=False),
sa.Column('updated_at', postgresql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), autoincrement=False, nullable=False),
sa.ForeignKeyConstraint(['document_id'], ['document.id'], name=op.f('flashcardset_document_id_fkey')),
sa.PrimaryKeyConstraint('id', name=op.f('flashcardset_pkey'))
)
op.create_index(op.f('ix_flashcardset_updated_at'), 'flashcardset', ['updated_at'], unique=False)
op.create_index(op.f('ix_flashcardset_status'), 'flashcardset', ['status'], unique=False)
op.create_index(op.f('ix_flashcardset_document_id'), 'flashcardset', ['document_id'], unique=False)
op.create_index(op.f('ix_flashcardset_created_at'), 'flashcardset', ['created_at'], unique=False)
# ### end Alembic commands ###
"""Add cascade delete to quizsession.course_id
Revision ID: 98c65ba436bd
Revises: 2cde6f094a4e
Create Date: 2025-10-06 11:31:30.334302
"""
from alembic import op
import sqlalchemy as sa
import sqlmodel.sql.sqltypes
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '98c65ba436bd'
down_revision = '2cde6f094a4e'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_flashcardset_created_at'), table_name='flashcardset')
op.drop_index(op.f('ix_flashcardset_document_id'), table_name='flashcardset')
op.drop_index(op.f('ix_flashcardset_status'), table_name='flashcardset')
op.drop_index(op.f('ix_flashcardset_updated_at'), table_name='flashcardset')
op.drop_table('flashcardset')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('flashcardset',
sa.Column('content_json', postgresql.JSONB(astext_type=sa.Text()), autoincrement=False, nullable=True),
sa.Column('document_id', sa.UUID(), autoincrement=False, nullable=False),
sa.Column('status', sa.VARCHAR(), autoincrement=False, nullable=False),
sa.Column('error_message', sa.VARCHAR(length=512), autoincrement=False, nullable=True),
sa.Column('id', sa.UUID(), autoincrement=False, nullable=False),
sa.Column('created_at', postgresql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), autoincrement=False, nullable=False),
sa.Column('updated_at', postgresql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), autoincrement=False, nullable=False),
sa.ForeignKeyConstraint(['document_id'], ['document.id'], name=op.f('flashcardset_document_id_fkey')),
sa.PrimaryKeyConstraint('id', name=op.f('flashcardset_pkey'))
)
op.create_index(op.f('ix_flashcardset_updated_at'), 'flashcardset', ['updated_at'], unique=False)
op.create_index(op.f('ix_flashcardset_status'), 'flashcardset', ['status'], unique=False)
op.create_index(op.f('ix_flashcardset_document_id'), 'flashcardset', ['document_id'], unique=False)
op.create_index(op.f('ix_flashcardset_created_at'), 'flashcardset', ['created_at'], unique=False)
# ### end Alembic commands ###
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""merge heads: podcast + dev

Revision ID: a9b7c6d5e4f3
Revises: ('2042a1f0c0a1', '64343f21e9a8')
Create Date: 2025-10-06 00:00:00

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = 'a9b7c6d5e4f3'
down_revision = ('2042a1f0c0a1', '64343f21e9a8')
branch_labels = None
depends_on = None


def upgrade():
# Merge point: no-op
pass


def downgrade():
# Merge point: no-op
pass

Loading