Compare commits
No commits in common. "3c23c49912ae776a55c6d25d540a6dfed41f0490" and "7f0977d8e53850670603c3f6b37fff1da5051397" have entirely different histories.
3c23c49912
...
7f0977d8e5
31 changed files with 178 additions and 1810 deletions
10
.env.example
10
.env.example
|
|
@ -22,13 +22,3 @@ GEMINI_API_KEY=your-gemini-api-key-here
|
|||
STORAGE_ACCESS_KEY=langlearn
|
||||
STORAGE_SECRET_KEY=changeme-use-a-long-random-string
|
||||
STORAGE_BUCKET=langlearn
|
||||
|
||||
# Transactional email — set to "scaleway" in production, "stub" logs instead of sending
|
||||
TRANSACTIONAL_EMAIL_PROVIDER=stub
|
||||
|
||||
# Scaleway Transactional Email (https://console.scaleway.com/transactional-email)
|
||||
# Required when TRANSACTIONAL_EMAIL_PROVIDER=scaleway
|
||||
SCALEWAY_TEM_SECRET_KEY=your-scaleway-secret-key-here
|
||||
SCALEWAY_TEM_PROJECT_ID=your-scaleway-project-id-here
|
||||
SCALEWAY_TEM_FROM_ADDRESS=noreply@yourdomain.com
|
||||
# SCALEWAY_TEM_REGION=fr-par # default; change to nl-ams if needed
|
||||
|
|
|
|||
|
|
@ -1,41 +0,0 @@
|
|||
"""add email verification tokens table
|
||||
|
||||
Revision ID: 0011
|
||||
Revises: 0010
|
||||
Create Date: 2026-04-10
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
revision: str = "0011"
|
||||
down_revision: Union[str, None] = "0010"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"email_verification_tokens",
|
||||
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
|
||||
sa.Column(
|
||||
"user_id",
|
||||
postgresql.UUID(as_uuid=True),
|
||||
sa.ForeignKey("users.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column("token", sa.Text(), nullable=False, unique=True),
|
||||
sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("used_at", sa.DateTime(timezone=True), nullable=True),
|
||||
)
|
||||
op.create_index("ix_email_verification_tokens_token", "email_verification_tokens", ["token"])
|
||||
op.create_index("ix_email_verification_tokens_user_id", "email_verification_tokens", ["user_id"])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("ix_email_verification_tokens_user_id", table_name="email_verification_tokens")
|
||||
op.drop_index("ix_email_verification_tokens_token", table_name="email_verification_tokens")
|
||||
op.drop_table("email_verification_tokens")
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
"""add human_name to users
|
||||
|
||||
Revision ID: 0012
|
||||
Revises: 0011
|
||||
Create Date: 2026-04-11
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "0012"
|
||||
down_revision: Union[str, None] = "0011"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column("users", sa.Column("human_name", sa.Text(), nullable=True))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("users", "human_name")
|
||||
|
|
@ -10,11 +10,6 @@ class Settings(BaseSettings):
|
|||
gemini_api_key: str
|
||||
admin_user_emails: str = "" # comma-separated list of admin email addresses
|
||||
api_base_url: str = "http://localhost:8000"
|
||||
transactional_email_provider: str = "stub" # "stub" | "scaleway"
|
||||
scaleway_tem_secret_key: str = ""
|
||||
scaleway_tem_project_id: str = ""
|
||||
scaleway_tem_from_address: str = ""
|
||||
scaleway_tem_region: str = "fr-par"
|
||||
storage_endpoint_url: str
|
||||
storage_access_key: str
|
||||
storage_secret_key: str
|
||||
|
|
|
|||
|
|
@ -11,5 +11,4 @@ class Account:
|
|||
is_active: bool
|
||||
is_email_verified: bool
|
||||
created_at: datetime
|
||||
human_name: str | None = None
|
||||
learnable_languages: list[LearnableLanguage] = field(default_factory=list)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from dataclasses import dataclass, field
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
@ -9,15 +9,6 @@ class Wordform:
|
|||
tags: list[str]
|
||||
|
||||
|
||||
@dataclass
|
||||
class SenseLink:
|
||||
id: str
|
||||
sense_id: str
|
||||
link_text: str
|
||||
link_target: str
|
||||
target_lemma_id: str | None
|
||||
|
||||
|
||||
@dataclass
|
||||
class Sense:
|
||||
id: str
|
||||
|
|
@ -26,7 +17,6 @@ class Sense:
|
|||
gloss: str
|
||||
topics: list[str]
|
||||
tags: list[str]
|
||||
links: list[SenseLink] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
|
@ -7,69 +6,54 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||
|
||||
from ..models.account import Account
|
||||
from ..models.learnable_language import LearnableLanguage
|
||||
from ...auth import hash_password, verify_password
|
||||
from ...config import settings
|
||||
from ...outbound.email.protocol import TransactionalEmailClient
|
||||
from ...auth import hash_password
|
||||
from ...outbound.postgres.entities.user_entity import User as UserEntity
|
||||
from ...outbound.postgres.repositories import (
|
||||
email_verification_token_repository,
|
||||
learnable_language_repository,
|
||||
user_repository,
|
||||
)
|
||||
from ...outbound.postgres.repositories import learnable_language_repository, user_repository
|
||||
|
||||
|
||||
class AccountService:
|
||||
"""Handles account-level operations: registration, authentication, email
|
||||
verification, and managing the set of languages a user is learning.
|
||||
"""Handles account-level operations: registration, profile retrieval, and managing
|
||||
the set of languages a user is learning.
|
||||
|
||||
All methods operate on behalf of a single authenticated user (or, for
|
||||
``create_account``, the user being created).
|
||||
|
||||
Usage::
|
||||
|
||||
service = AccountService(db)
|
||||
|
||||
# Registration — returns the new account and the verification link
|
||||
account, link = await service.register_new_account("alice@example.com", "s3cr3t")
|
||||
|
||||
# Email verification
|
||||
await service.verify_email_address(token_from_link)
|
||||
|
||||
# Authentication — raises ValueError on bad credentials or disabled account
|
||||
account = await service.authenticate_with_password("alice@example.com", "s3cr3t")
|
||||
# Registration
|
||||
account = await service.create_account("alice@example.com", "s3cr3t")
|
||||
|
||||
# Profile retrieval
|
||||
account = await service.get_account(user_id)
|
||||
print(account.learnable_languages) # [LearnableLanguage(...), ...]
|
||||
|
||||
# Language management
|
||||
lang = await service.add_learnable_language(user_id, "en", "fr", ["B1"])
|
||||
# Add French (B1) to the account
|
||||
lang = await service.add_learnable_language(
|
||||
user_id, source_language="en", target_language="fr", proficiencies=["B1"]
|
||||
)
|
||||
|
||||
# Remove it again
|
||||
await service.remove_learnable_language(user_id, lang.id)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
email_client: TransactionalEmailClient | None = None,
|
||||
) -> None:
|
||||
def __init__(self, db: AsyncSession) -> None:
|
||||
self.db = db
|
||||
# Defer import to avoid circular dependency at module load time.
|
||||
if email_client is not None:
|
||||
self._email_client = email_client
|
||||
else:
|
||||
from ...outbound.email.factory import get_email_client
|
||||
self._email_client = get_email_client()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Registration & authentication
|
||||
# ------------------------------------------------------------------
|
||||
async def create_account(self, email: str, password: str) -> Account:
|
||||
"""Create a new user account, hashing the plain-text password before storage.
|
||||
|
||||
async def register_new_account(self, email: str, password: str) -> tuple[Account, str]:
|
||||
"""Create a new account, generate an email verification token, and send
|
||||
the verification email.
|
||||
|
||||
Returns ``(account, verification_link)`` on success.
|
||||
Raises ``ValueError`` if the email address is already registered.
|
||||
Raises ``ValueError`` if the email address is already registered, so the
|
||||
caller does not need to catch SQLAlchemy exceptions directly.
|
||||
|
||||
Usage::
|
||||
|
||||
account, link = await service.register_new_account("alice@example.com", "s3cr3t")
|
||||
try:
|
||||
account = await service.create_account("alice@example.com", "s3cr3t")
|
||||
except ValueError:
|
||||
# email already taken
|
||||
...
|
||||
"""
|
||||
try:
|
||||
user = await user_repository.create(
|
||||
|
|
@ -81,99 +65,14 @@ class AccountService:
|
|||
await self.db.rollback()
|
||||
raise ValueError("Email already registered")
|
||||
|
||||
account = Account(
|
||||
id=str(user.id),
|
||||
email=user.email,
|
||||
human_name=user.human_name,
|
||||
is_active=user.is_active,
|
||||
is_email_verified=user.is_email_verified,
|
||||
created_at=user.created_at,
|
||||
)
|
||||
|
||||
token_row = await email_verification_token_repository.create(
|
||||
self.db, uuid.UUID(account.id)
|
||||
)
|
||||
link = f"{settings.api_base_url}/api/auth/verify-email?token={token_row.token}"
|
||||
|
||||
await self._email_client.send_email(
|
||||
to=account.email,
|
||||
subject="Verify your email address",
|
||||
html_body=(
|
||||
f"<p>Thanks for signing up! Please verify your email address by clicking the link below:</p>"
|
||||
f'<p><a href="{link}">{link}</a></p>'
|
||||
f"<p>This link expires in 24 hours.</p>"
|
||||
),
|
||||
)
|
||||
|
||||
return account, link
|
||||
|
||||
async def authenticate_with_password(self, email: str, password: str) -> Account:
|
||||
"""Validate credentials and return the matching account.
|
||||
|
||||
Raises ``ValueError("invalid_credentials")`` for an unrecognised email
|
||||
or wrong password, and ``ValueError("account_disabled")`` if the account
|
||||
has been deactivated.
|
||||
|
||||
Usage::
|
||||
|
||||
try:
|
||||
account = await service.authenticate_with_password(email, password)
|
||||
except ValueError as exc:
|
||||
if str(exc) == "account_disabled":
|
||||
... # 403
|
||||
... # 401
|
||||
"""
|
||||
user = await user_repository.get_by_email(self.db, email)
|
||||
|
||||
if user is None or not verify_password(password, user.hashed_password):
|
||||
raise ValueError("invalid_credentials")
|
||||
|
||||
if not user.is_active:
|
||||
raise ValueError("account_disabled")
|
||||
|
||||
# TODO(email-verification): uncomment once email verification is tested end-to-end
|
||||
# if not user.is_email_verified:
|
||||
# raise ValueError("email_not_verified")
|
||||
|
||||
return Account(
|
||||
id=str(user.id),
|
||||
email=user.email,
|
||||
human_name=user.human_name,
|
||||
is_active=user.is_active,
|
||||
is_email_verified=user.is_email_verified,
|
||||
created_at=user.created_at,
|
||||
)
|
||||
|
||||
async def verify_email_address(self, token: str) -> None:
|
||||
"""Consume a verification token and mark the account as email-verified.
|
||||
|
||||
Raises ``ValueError`` if the token is invalid, already used, or expired.
|
||||
|
||||
Usage::
|
||||
|
||||
await service.verify_email_address(token_from_query_string)
|
||||
"""
|
||||
token_row = await email_verification_token_repository.get_by_token(self.db, token)
|
||||
|
||||
if token_row is None or token_row.used_at is not None:
|
||||
raise ValueError("Verification link is invalid or has already been used")
|
||||
|
||||
if token_row.expires_at < datetime.now(timezone.utc):
|
||||
raise ValueError("Verification link has expired")
|
||||
|
||||
await email_verification_token_repository.mark_used(self.db, token_row.id)
|
||||
|
||||
result = await self.db.execute(
|
||||
select(UserEntity).where(UserEntity.id == token_row.user_id)
|
||||
)
|
||||
user = result.scalar_one()
|
||||
user.is_email_verified = True
|
||||
await self.db.commit()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Profile
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
async def get_account(self, user_id: uuid.UUID) -> Account:
|
||||
"""Retrieve a user's account profile including all their learnable languages.
|
||||
|
||||
|
|
@ -185,6 +84,7 @@ class AccountService:
|
|||
for lang in account.learnable_languages:
|
||||
print(lang.target_language, lang.proficiencies)
|
||||
"""
|
||||
# user_repository only exposes get_by_email; query by id directly
|
||||
result = await self.db.execute(
|
||||
select(UserEntity).where(UserEntity.id == user_id)
|
||||
)
|
||||
|
|
@ -197,70 +97,12 @@ class AccountService:
|
|||
return Account(
|
||||
id=str(user.id),
|
||||
email=user.email,
|
||||
human_name=user.human_name,
|
||||
is_active=user.is_active,
|
||||
is_email_verified=user.is_email_verified,
|
||||
created_at=user.created_at,
|
||||
learnable_languages=languages,
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Onboarding
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
async def complete_onboarding(
|
||||
self,
|
||||
user_id: uuid.UUID,
|
||||
human_name: str,
|
||||
language_pair: str,
|
||||
proficiencies: list[str],
|
||||
) -> Account:
|
||||
"""Record the user's name and first language pair, completing onboarding.
|
||||
|
||||
``language_pair`` is a comma-separated ``"source,target"`` string (e.g. ``"en,fr"``).
|
||||
|
||||
Usage::
|
||||
|
||||
account = await service.complete_onboarding(
|
||||
user_id, human_name="Alice", language_pair="en,fr", proficiencies=["B1"]
|
||||
)
|
||||
"""
|
||||
source, target = language_pair.split(",", 1)
|
||||
await user_repository.set_human_name(self.db, user_id, human_name)
|
||||
await self.add_learnable_language(user_id, source, target, proficiencies)
|
||||
return await self.get_account(user_id)
|
||||
|
||||
async def get_account_status(self, user_id: uuid.UUID) -> tuple[list[str], list[str]]:
|
||||
"""Return ``(problem_flags, error_messages)`` describing any blockers on the account.
|
||||
|
||||
Current flags:
|
||||
- ``unvalidated_email`` — the user has not verified their email address
|
||||
- ``no_onboarding`` — the user has not added any language pairs yet
|
||||
|
||||
Usage::
|
||||
|
||||
flags, messages = await service.get_account_status(user_id)
|
||||
if flags:
|
||||
... # surface to the user
|
||||
"""
|
||||
account = await self.get_account(user_id)
|
||||
flags: list[str] = []
|
||||
messages: list[str] = []
|
||||
|
||||
if not account.is_email_verified:
|
||||
flags.append("unvalidated_email")
|
||||
messages.append("Please validate your email address")
|
||||
|
||||
if not account.learnable_languages:
|
||||
flags.append("no_onboarding")
|
||||
messages.append("Please complete onboarding")
|
||||
|
||||
return flags, messages
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Language management
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
async def add_learnable_language(
|
||||
self,
|
||||
user_id: uuid.UUID,
|
||||
|
|
@ -279,6 +121,7 @@ class AccountService:
|
|||
target_language="fr",
|
||||
proficiencies=["B1", "B2"],
|
||||
)
|
||||
print(lang.id) # UUID of the learnable_language row
|
||||
"""
|
||||
return await learnable_language_repository.upsert(
|
||||
self.db,
|
||||
|
|
@ -293,8 +136,8 @@ class AccountService:
|
|||
) -> None:
|
||||
"""Remove a learnable language from the user's account by its row ID.
|
||||
|
||||
Raises ``ValueError`` if the language entry does not exist or does not
|
||||
belong to ``user_id``.
|
||||
Raises ``ValueError`` if the language entry does not exist or does not belong
|
||||
to ``user_id``.
|
||||
|
||||
Usage::
|
||||
|
||||
|
|
|
|||
|
|
@ -7,15 +7,3 @@ SUPPORTED_LANGUAGES: dict[str, str] = {
|
|||
}
|
||||
|
||||
SUPPORTED_LEVELS = {"A1", "A2", "B1", "B2", "C1", "C2"}
|
||||
|
||||
# Ordered for display (SUPPORTED_LEVELS is a set and has no guaranteed order).
|
||||
LEVEL_ORDER: list[str] = ["A1", "A2", "B1", "B2", "C1", "C2"]
|
||||
|
||||
LEVEL_DESCRIPTIONS: dict[str, str] = {
|
||||
"A1": "Little to no prior knowledge of the language",
|
||||
"A2": "Basic knowledge; can understand simple phrases and expressions",
|
||||
"B1": "Intermediate; can handle most everyday situations",
|
||||
"B2": "Upper intermediate; can interact fluently with native speakers",
|
||||
"C1": "Advanced; able to use language flexibly and effectively",
|
||||
"C2": "Near-native mastery of the language",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from .routers.api import generation, pos
|
|||
from fastapi import FastAPI
|
||||
|
||||
from .routers.api import jobs
|
||||
from .routers import auth as auth_router
|
||||
from .routers import media as media_router
|
||||
from .routers.api.main import api_router
|
||||
from .routers.bff.main import bff_router
|
||||
|
|
@ -28,6 +29,7 @@ app = FastAPI(title="Language Learning API", lifespan=lifespan)
|
|||
|
||||
app.include_router(api_router)
|
||||
app.include_router(bff_router)
|
||||
app.include_router(auth_router.router)
|
||||
app.include_router(media_router.router)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
from .protocol import TransactionalEmailClient
|
||||
|
||||
|
||||
def get_email_client() -> TransactionalEmailClient:
|
||||
from ...config import settings
|
||||
|
||||
if settings.transactional_email_provider == "scaleway":
|
||||
from ..scaleway_tem.tem_client import ScalewayTEMClient
|
||||
|
||||
return ScalewayTEMClient(
|
||||
secret_key=settings.scaleway_tem_secret_key,
|
||||
from_address=settings.scaleway_tem_from_address,
|
||||
project_id=settings.scaleway_tem_project_id,
|
||||
region=settings.scaleway_tem_region,
|
||||
)
|
||||
|
||||
if settings.transactional_email_provider == "stub":
|
||||
from .stub_client import StubEmailClient
|
||||
|
||||
return StubEmailClient()
|
||||
|
||||
raise ValueError(
|
||||
f"Unknown transactional_email_provider: {settings.transactional_email_provider!r}. "
|
||||
"Valid options: 'scaleway', 'stub'."
|
||||
)
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
from typing import Protocol
|
||||
|
||||
|
||||
class TransactionalEmailClient(Protocol):
|
||||
async def send_email(self, to: str, subject: str, html_body: str) -> None:
|
||||
...
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class StubEmailClient:
|
||||
"""Logs emails instead of sending them. Use for local development."""
|
||||
|
||||
async def send_email(self, to: str, subject: str, html_body: str) -> None:
|
||||
logger.info(
|
||||
"STUB EMAIL — would have sent:\n To: %s\n Subject: %s\n Body:\n%s",
|
||||
to,
|
||||
subject,
|
||||
html_body,
|
||||
)
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
import secrets
|
||||
import uuid
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from sqlalchemy import DateTime, ForeignKey, Text
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from ..database import Base
|
||||
|
||||
TOKEN_TTL_HOURS = 24
|
||||
|
||||
|
||||
class EmailVerificationToken(Base):
|
||||
__tablename__ = "email_verification_tokens"
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
user_id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("users.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
)
|
||||
token: Mapped[str] = mapped_column(Text, nullable=False, unique=True)
|
||||
expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
||||
used_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
|
||||
@staticmethod
|
||||
def make(user_id: uuid.UUID) -> "EmailVerificationToken":
|
||||
return EmailVerificationToken(
|
||||
user_id=user_id,
|
||||
token=secrets.token_urlsafe(32),
|
||||
expires_at=datetime.now(timezone.utc) + timedelta(hours=TOKEN_TTL_HOURS),
|
||||
)
|
||||
|
|
@ -16,9 +16,9 @@ class User(Base):
|
|||
)
|
||||
email: Mapped[str] = mapped_column(String(255), nullable=False, unique=True, index=True)
|
||||
hashed_password: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
human_name: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
||||
is_email_verified: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||||
# TODO(email-verification): set to False and require verification once transactional email is implemented
|
||||
is_email_verified: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
default=lambda: datetime.now(timezone.utc),
|
||||
|
|
|
|||
|
|
@ -1,44 +0,0 @@
|
|||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from ..entities.email_verification_token_entity import EmailVerificationToken
|
||||
|
||||
|
||||
async def create(db: AsyncSession, user_id: uuid.UUID) -> EmailVerificationToken:
|
||||
row = EmailVerificationToken.make(user_id)
|
||||
db.add(row)
|
||||
await db.commit()
|
||||
await db.refresh(row)
|
||||
return row
|
||||
|
||||
|
||||
async def get_by_token(db: AsyncSession, token: str) -> EmailVerificationToken | None:
|
||||
result = await db.execute(
|
||||
select(EmailVerificationToken).where(EmailVerificationToken.token == token)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
|
||||
async def get_active_for_user(db: AsyncSession, user_id: uuid.UUID) -> EmailVerificationToken | None:
|
||||
"""Return the most recently created unused, unexpired token for this user."""
|
||||
result = await db.execute(
|
||||
select(EmailVerificationToken)
|
||||
.where(EmailVerificationToken.user_id == user_id)
|
||||
.where(EmailVerificationToken.used_at.is_(None))
|
||||
.where(EmailVerificationToken.expires_at > datetime.now(timezone.utc))
|
||||
.order_by(EmailVerificationToken.expires_at.desc())
|
||||
.limit(1)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
|
||||
async def mark_used(db: AsyncSession, token_id: uuid.UUID) -> None:
|
||||
result = await db.execute(
|
||||
select(EmailVerificationToken).where(EmailVerificationToken.id == token_id)
|
||||
)
|
||||
row = result.scalar_one()
|
||||
row.used_at = datetime.now(timezone.utc)
|
||||
await db.commit()
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
import uuid
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
|
|
@ -17,10 +15,3 @@ async def create(db: AsyncSession, email: str, hashed_password: str) -> User:
|
|||
async def get_by_email(db: AsyncSession, email: str) -> User | None:
|
||||
result = await db.execute(select(User).where(User.email == email))
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
|
||||
async def set_human_name(db: AsyncSession, user_id: uuid.UUID, human_name: str) -> None:
|
||||
result = await db.execute(select(User).where(User.id == user_id))
|
||||
user = result.scalar_one()
|
||||
user.human_name = human_name
|
||||
await db.commit()
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
import httpx
|
||||
|
||||
_TEM_API_URL = "https://api.scaleway.com/transactional-email/v1alpha1/regions/{region}/emails"
|
||||
|
||||
|
||||
class ScalewayTEMClient:
|
||||
def __init__(self, secret_key: str, from_address: str, project_id: str, region: str = "fr-par") -> None:
|
||||
self._secret_key = secret_key
|
||||
self._from_address = from_address
|
||||
self._project_id = project_id
|
||||
self._url = _TEM_API_URL.format(region=region)
|
||||
|
||||
async def send_email(self, to: str, subject: str, html_body: str) -> None:
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.post(
|
||||
self._url,
|
||||
headers={
|
||||
"X-Auth-Token": self._secret_key,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
json={
|
||||
"project_id": self._project_id,
|
||||
"from": {"email": self._from_address},
|
||||
"to": [{"email": to}],
|
||||
"subject": subject,
|
||||
"html": html_body,
|
||||
},
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import uuid
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from pydantic import BaseModel, field_validator, model_validator
|
||||
from pydantic import BaseModel, field_validator
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from ...auth import verify_token
|
||||
|
|
@ -76,71 +76,6 @@ async def add_learnable_language(
|
|||
)
|
||||
|
||||
|
||||
class OnboardingRequest(BaseModel):
|
||||
human_name: str
|
||||
language_pairs: list[str]
|
||||
proficiencies: list[list[str]]
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_shape(self) -> "OnboardingRequest":
|
||||
if len(self.language_pairs) != 1:
|
||||
raise ValueError("language_pairs must contain exactly one entry")
|
||||
if len(self.proficiencies) != 1:
|
||||
raise ValueError("proficiencies must contain exactly one entry")
|
||||
|
||||
pair = self.language_pairs[0]
|
||||
parts = pair.split(",")
|
||||
if len(parts) != 2:
|
||||
raise ValueError(f"language_pairs entry must be 'source,target', got {pair!r}")
|
||||
source, target = parts
|
||||
if source not in SUPPORTED_LANGUAGES:
|
||||
raise ValueError(f"Unsupported source language: {source!r}")
|
||||
if target not in SUPPORTED_LANGUAGES:
|
||||
raise ValueError(f"Unsupported target language: {target!r}")
|
||||
if source == target:
|
||||
raise ValueError("Source and target language must differ")
|
||||
|
||||
levels = self.proficiencies[0]
|
||||
if not (1 <= len(levels) <= 2):
|
||||
raise ValueError("proficiencies entry must contain 1 or 2 levels")
|
||||
invalid = [l for l in levels if l not in SUPPORTED_LEVELS]
|
||||
if invalid:
|
||||
raise ValueError(f"Invalid proficiency levels: {invalid}")
|
||||
|
||||
return self
|
||||
|
||||
|
||||
class AccountStatusResponse(BaseModel):
|
||||
problem_flags: list[str]
|
||||
error_messages: list[str]
|
||||
|
||||
|
||||
@router.post("/onboarding", status_code=status.HTTP_200_OK)
|
||||
async def complete_onboarding(
|
||||
body: OnboardingRequest,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
token_data: dict = Depends(verify_token),
|
||||
) -> dict:
|
||||
user_id = uuid.UUID(token_data["sub"])
|
||||
await AccountService(db).complete_onboarding(
|
||||
user_id=user_id,
|
||||
human_name=body.human_name,
|
||||
language_pair=body.language_pairs[0],
|
||||
proficiencies=body.proficiencies[0],
|
||||
)
|
||||
return {"success": True}
|
||||
|
||||
|
||||
@router.get("/status", response_model=AccountStatusResponse)
|
||||
async def get_account_status(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
token_data: dict = Depends(verify_token),
|
||||
) -> AccountStatusResponse:
|
||||
user_id = uuid.UUID(token_data["sub"])
|
||||
flags, messages = await AccountService(db).get_account_status(user_id)
|
||||
return AccountStatusResponse(problem_flags=flags, error_messages=messages)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/learnable-languages/{language_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
|
|
|
|||
|
|
@ -1,88 +0,0 @@
|
|||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from pydantic import BaseModel, EmailStr, field_validator
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from ...auth import create_access_token
|
||||
from ...domain.services.account_service import AccountService
|
||||
from ...outbound.postgres.database import get_db
|
||||
|
||||
router = APIRouter(prefix="/auth", tags=["auth"])
|
||||
|
||||
|
||||
class RegisterRequest(BaseModel):
|
||||
email: EmailStr
|
||||
password: str
|
||||
|
||||
@field_validator("password")
|
||||
@classmethod
|
||||
def password_min_length(cls, v: str) -> str:
|
||||
if len(v) < 8:
|
||||
raise ValueError("Password must be at least 8 characters long")
|
||||
return v
|
||||
|
||||
|
||||
class RegisterResponse(BaseModel):
|
||||
success: bool
|
||||
error_message: str | None = None
|
||||
_todo_remove_me_validate_account_link: str | None = None
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
class LoginRequest(BaseModel):
|
||||
email: EmailStr
|
||||
password: str
|
||||
|
||||
|
||||
class TokenResponse(BaseModel):
|
||||
access_token: str
|
||||
token_type: str = "bearer"
|
||||
|
||||
|
||||
@router.post("/register", response_model=RegisterResponse, status_code=status.HTTP_201_CREATED)
|
||||
async def register(
|
||||
body: RegisterRequest,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
) -> RegisterResponse:
|
||||
try:
|
||||
account, link = await AccountService(db).register_new_account(body.email, body.password)
|
||||
except ValueError:
|
||||
return RegisterResponse(
|
||||
success=False,
|
||||
error_message="Email address not valid or already in use",
|
||||
)
|
||||
|
||||
return RegisterResponse(
|
||||
success=True,
|
||||
**{"_todo_remove_me_validate_account_link": link},
|
||||
)
|
||||
|
||||
|
||||
@router.post("/login", response_model=TokenResponse)
|
||||
async def login(
|
||||
body: LoginRequest,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
) -> TokenResponse:
|
||||
try:
|
||||
account = await AccountService(db).authenticate_with_password(body.email, body.password)
|
||||
except ValueError as exc:
|
||||
if str(exc) == "account_disabled":
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Account disabled")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid email or password"
|
||||
)
|
||||
|
||||
return TokenResponse(access_token=create_access_token(account.id, account.email))
|
||||
|
||||
|
||||
@router.get("/verify-email")
|
||||
async def verify_email(
|
||||
token: str,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
) -> dict:
|
||||
try:
|
||||
await AccountService(db).verify_email_address(token)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc))
|
||||
|
||||
return {"success": True}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
from .account import router as account_router
|
||||
from .auth import router as auth_router
|
||||
from .flashcards import router as flashcards_router
|
||||
from .pos import router as pos_router
|
||||
from .translate import router as translate_router
|
||||
|
|
@ -12,7 +11,6 @@ from fastapi import APIRouter
|
|||
|
||||
api_router = APIRouter(prefix="/api", tags=["api"])
|
||||
|
||||
api_router.include_router(auth_router)
|
||||
api_router.include_router(account_router)
|
||||
api_router.include_router(flashcards_router)
|
||||
api_router.include_router(pos_router)
|
||||
|
|
|
|||
65
api/app/routers/auth.py
Normal file
65
api/app/routers/auth.py
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from pydantic import BaseModel, EmailStr
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from ..auth import create_access_token, verify_password
|
||||
from ..domain.services.account_service import AccountService
|
||||
from ..outbound.postgres.database import get_db
|
||||
from ..outbound.postgres.repositories import user_repository
|
||||
|
||||
router = APIRouter(prefix="/auth", tags=["auth"])
|
||||
|
||||
|
||||
class RegisterRequest(BaseModel):
|
||||
email: EmailStr
|
||||
password: str
|
||||
|
||||
|
||||
class LoginRequest(BaseModel):
|
||||
email: EmailStr
|
||||
password: str
|
||||
|
||||
|
||||
class TokenResponse(BaseModel):
|
||||
access_token: str
|
||||
token_type: str = "bearer"
|
||||
|
||||
|
||||
@router.post("/register", status_code=status.HTTP_201_CREATED)
|
||||
async def register(body: RegisterRequest, db: AsyncSession = Depends(get_db)):
|
||||
# TODO(email-verification): send verification email here once transactional
|
||||
# email is implemented. Set is_email_verified=False on the User model and
|
||||
# require verification before allowing login.
|
||||
try:
|
||||
account = await AccountService(db).create_account(body.email, body.password)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(exc))
|
||||
|
||||
return {"id": account.id, "email": account.email}
|
||||
|
||||
|
||||
@router.post("/login", response_model=TokenResponse)
|
||||
async def login(body: LoginRequest, db: AsyncSession = Depends(get_db)):
|
||||
user = await user_repository.get_by_email(db, body.email)
|
||||
|
||||
if user is None or not verify_password(body.password, user.hashed_password):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid email or password",
|
||||
)
|
||||
|
||||
if not user.is_active:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Account disabled",
|
||||
)
|
||||
|
||||
# TODO(email-verification): uncomment once email verification is in place
|
||||
# if not user.is_email_verified:
|
||||
# raise HTTPException(
|
||||
# status_code=status.HTTP_403_FORBIDDEN,
|
||||
# detail="Email address not verified",
|
||||
# )
|
||||
|
||||
token = create_access_token(str(user.id), user.email)
|
||||
return TokenResponse(access_token=token)
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
import uuid
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from ...auth import verify_token
|
||||
from ...config import settings
|
||||
from ...domain.services.account_service import AccountService
|
||||
from ...languages import SUPPORTED_LANGUAGES, SUPPORTED_LEVELS, LEVEL_ORDER, LEVEL_DESCRIPTIONS
|
||||
from ...outbound.postgres.database import get_db
|
||||
from ...outbound.postgres.repositories import email_verification_token_repository
|
||||
|
||||
router = APIRouter(prefix="/account", tags=["bff"])
|
||||
|
||||
|
||||
class LanguagePairOption(BaseModel):
|
||||
value: str
|
||||
label: str
|
||||
description: str
|
||||
|
||||
|
||||
class ProficiencyOption(BaseModel):
|
||||
value: str
|
||||
label: str
|
||||
description: str
|
||||
|
||||
|
||||
class OnboardingResponse(BaseModel):
|
||||
_todo_remove_me_validate_account_link: str | None = None
|
||||
language_pairs: list[LanguagePairOption]
|
||||
proficiencies: list[ProficiencyOption]
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
def _build_language_pairs() -> list[LanguagePairOption]:
|
||||
pairs = []
|
||||
for source_code, source_name in SUPPORTED_LANGUAGES.items():
|
||||
for target_code, target_name in SUPPORTED_LANGUAGES.items():
|
||||
if source_code == target_code:
|
||||
continue
|
||||
pairs.append(
|
||||
LanguagePairOption(
|
||||
value=f"{source_code},{target_code}",
|
||||
label=f"{source_name} to {target_name}",
|
||||
description=f"You are a {source_name} speaker, learning {target_name}",
|
||||
)
|
||||
)
|
||||
return pairs
|
||||
|
||||
|
||||
def _build_proficiencies() -> list[ProficiencyOption]:
|
||||
return [
|
||||
ProficiencyOption(
|
||||
value=level,
|
||||
label=level,
|
||||
description=LEVEL_DESCRIPTIONS[level],
|
||||
)
|
||||
for level in LEVEL_ORDER
|
||||
if level in SUPPORTED_LEVELS
|
||||
]
|
||||
|
||||
|
||||
class AccountLanguagePair(BaseModel):
|
||||
id: str
|
||||
source_language: str
|
||||
target_language: str
|
||||
proficiencies: list[str]
|
||||
|
||||
|
||||
class AccountResponse(BaseModel):
|
||||
email: str
|
||||
human_name: str | None
|
||||
language_pairs: list[AccountLanguagePair]
|
||||
|
||||
|
||||
@router.get("", response_model=AccountResponse)
|
||||
async def get_account(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
token_data: dict = Depends(verify_token),
|
||||
) -> AccountResponse:
|
||||
user_id = uuid.UUID(token_data["sub"])
|
||||
account = await AccountService(db).get_account(user_id)
|
||||
return AccountResponse(
|
||||
email=account.email,
|
||||
human_name=account.human_name,
|
||||
language_pairs=[
|
||||
AccountLanguagePair(
|
||||
id=lang.id,
|
||||
source_language=lang.source_language,
|
||||
target_language=lang.target_language,
|
||||
proficiencies=lang.proficiencies,
|
||||
)
|
||||
for lang in account.learnable_languages
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@router.get("/onboarding", response_model=OnboardingResponse)
|
||||
async def get_onboarding(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
token_data: dict = Depends(verify_token),
|
||||
) -> OnboardingResponse:
|
||||
user_id = uuid.UUID(token_data["sub"])
|
||||
|
||||
active_token = await email_verification_token_repository.get_active_for_user(db, user_id)
|
||||
link = (
|
||||
f"{settings.api_base_url}/api/auth/verify-email?token={active_token.token}"
|
||||
if active_token
|
||||
else None
|
||||
)
|
||||
|
||||
return OnboardingResponse(
|
||||
**{"_todo_remove_me_validate_account_link": link},
|
||||
language_pairs=_build_language_pairs(),
|
||||
proficiencies=_build_proficiencies(),
|
||||
)
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
from .account import router as account_router
|
||||
from .articles import router as article_router
|
||||
from .user_profile import router as user_profile_router
|
||||
|
||||
|
|
@ -6,6 +5,5 @@ from fastapi import APIRouter
|
|||
|
||||
bff_router = APIRouter(prefix="/bff", tags=["bff"])
|
||||
|
||||
bff_router.include_router(account_router)
|
||||
bff_router.include_router(article_router)
|
||||
bff_router.include_router(user_profile_router)
|
||||
|
|
|
|||
|
|
@ -2,29 +2,7 @@
|
|||
|
||||
This is a HTTP API, written in Python, using the Fastapi framework.
|
||||
|
||||
The code should be organised using both Domain Driven Design and Hexagonal Architecture principles. Domain driven design is present when method names are human readable, and translate to what's actually happening or the process it's trying to model.
|
||||
|
||||
For example, consider the following:
|
||||
|
||||
```py
|
||||
flashcard_service.create('bonjour', 'hello', 'en', 'fr', user_id)
|
||||
flashcard_service.record_event(flashcard_id, 'seen', user_id)
|
||||
```
|
||||
|
||||
vs.
|
||||
|
||||
```py
|
||||
flascard_service.create_flashcard_in_language_pair_for_user(
|
||||
['bonjour', 'hello'],
|
||||
['fr', 'en'],
|
||||
user_id
|
||||
)
|
||||
flashcard_service.record_card_seen_by_user(flashcard_id, user_id, now)
|
||||
```
|
||||
|
||||
The latter is closer to how the code should read, as a set of more human-readable statements.
|
||||
|
||||
Although the structure is outlined below, pragmatism wins over zealous adherence.
|
||||
The code should be organised using both Domain Driven Design and Hexagonal Architecture principles.
|
||||
|
||||
## Domain
|
||||
|
||||
|
|
@ -36,8 +14,6 @@ This is where all of the logic around the actual language learning lives.
|
|||
|
||||
In `app/domain/models` contains the core Domain Entities, i.e. classes that represent objects for core domain processes.
|
||||
|
||||
Where possible, the codebase adopts an object-oriented approach. The models therefore present tangible entities. in the system.
|
||||
|
||||
### Services
|
||||
|
||||
The `app/domain/services` directory contains modules that encapsulate the "orchestration" or "choreography" of other components of the system to achieve complex, domain actions.
|
||||
|
|
@ -46,32 +22,6 @@ For example:
|
|||
|
||||
- `TextGenerationService` details the step-by-step process of how a series of text is synthesised, audio is generated, parts of speech tagged, timed transcripts generated, etc. which is then used by the learner for language learning.
|
||||
|
||||
### Routers
|
||||
|
||||
This is where definitions of the HTTP endpoints lives.
|
||||
|
||||
Logic that lives here is called 'controller logic' (after the Rails convention), and it should be responsible for processing an incoming request and formatting an outgoing response.
|
||||
|
||||
Business logic should be delegated, as much as possible, to the relevant services and utilities.
|
||||
|
||||
There are two kinds of endpoints: `/api` and `/bff`
|
||||
|
||||
#### API Routers
|
||||
|
||||
Routes which start with `/api` often signify _actions_ the user wants to take, e.g. registering or logging in; creating a Flashcard Event; retrieving the next batch of cards.
|
||||
|
||||
These are resource-oriented typical RESTful endpoints. GET for retrieving, POST for creating, PUT for updating, DELETE for deletion.
|
||||
|
||||
#### BFF Routers
|
||||
|
||||
BFF stands for "backend for frontend". BFF routes are those used on specific screens in the Web UI an allow the loading of varies types of data from various sources. Because they are not "pure" RESTful endpoints, which are resource-driven, they acknowledge that they are tied to a specific screen in the UI.
|
||||
|
||||
BFF endpoints are only ever GET, never POST.
|
||||
|
||||
Even if two screens _seem_ identical, they will get separate BFFs, to allow separate evolution.
|
||||
|
||||
The BFF route will, as much as possible, be the route on the UI prefaced with `/bff`. E.g. the `/account` screen on the UI would be powered by the `/bff/account` endpoint.
|
||||
|
||||
## Outbound
|
||||
|
||||
The `app/outbound` directory contains modules that allow this project to communicate with external systems.
|
||||
|
|
|
|||
|
|
@ -49,7 +49,6 @@ services:
|
|||
STORAGE_ACCESS_KEY: ${STORAGE_ACCESS_KEY:-langlearn}
|
||||
STORAGE_SECRET_KEY: ${STORAGE_SECRET_KEY}
|
||||
STORAGE_BUCKET: ${STORAGE_BUCKET:-langlearn}
|
||||
TRANSACTIONAL_EMAIL_PROVIDER: ${TRANSACTIONAL_EMAIL_PROVIDER:-stub}
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import type { Client, Options as Options2, TDataShape } from './client';
|
||||
import { client } from './client.gen';
|
||||
import type { AddFromTokenApiVocabFromTokenPostData, AddFromTokenApiVocabFromTokenPostErrors, AddFromTokenApiVocabFromTokenPostResponses, AddLearnableLanguageApiAccountLearnableLanguagesPostData, AddLearnableLanguageApiAccountLearnableLanguagesPostErrors, AddLearnableLanguageApiAccountLearnableLanguagesPostResponses, AddWordApiVocabPostData, AddWordApiVocabPostErrors, AddWordApiVocabPostResponses, AnalyzePosApiPosPostData, AnalyzePosApiPosPostErrors, AnalyzePosApiPosPostResponses, CompleteOnboardingApiAccountOnboardingPostData, CompleteOnboardingApiAccountOnboardingPostErrors, CompleteOnboardingApiAccountOnboardingPostResponses, CreateGenerationJobApiGeneratePostData, CreateGenerationJobApiGeneratePostErrors, CreateGenerationJobApiGeneratePostResponses, GenerateFlashcardsApiVocabEntryIdFlashcardsPostData, GenerateFlashcardsApiVocabEntryIdFlashcardsPostErrors, GenerateFlashcardsApiVocabEntryIdFlashcardsPostResponses, GetAccountBffAccountGetData, GetAccountBffAccountGetResponses, GetAccountStatusApiAccountStatusGetData, GetAccountStatusApiAccountStatusGetResponses, GetArticleBffArticlesArticleIdGetData, GetArticleBffArticlesArticleIdGetErrors, GetArticleBffArticlesArticleIdGetResponses, GetJobApiJobsJobIdGetData, GetJobApiJobsJobIdGetErrors, GetJobApiJobsJobIdGetResponses, GetJobsApiJobsGetData, GetJobsApiJobsGetResponses, GetMediaFileMediaFilenameGetData, GetMediaFileMediaFilenameGetErrors, GetMediaFileMediaFilenameGetResponses, GetOnboardingBffAccountOnboardingGetData, GetOnboardingBffAccountOnboardingGetResponses, GetUserProfileBffUserProfileGetData, GetUserProfileBffUserProfileGetResponses, HealthHealthGetData, HealthHealthGetResponses, ListArticlesBffArticlesGetData, ListArticlesBffArticlesGetErrors, ListArticlesBffArticlesGetResponses, ListEntriesApiVocabGetData, ListEntriesApiVocabGetErrors, ListEntriesApiVocabGetResponses, ListFlashcardsApiFlashcardsGetData, ListFlashcardsApiFlashcardsGetResponses, LoginApiAuthLoginPostData, LoginApiAuthLoginPostErrors, LoginApiAuthLoginPostResponses, PendingDisambiguationApiVocabPendingDisambiguationGetData, PendingDisambiguationApiVocabPendingDisambiguationGetResponses, RecordEventApiFlashcardsFlashcardIdEventsPostData, RecordEventApiFlashcardsFlashcardIdEventsPostErrors, RecordEventApiFlashcardsFlashcardIdEventsPostResponses, RegenerateAudioApiJobsJobIdRegenerateAudioPostData, RegenerateAudioApiJobsJobIdRegenerateAudioPostErrors, RegenerateAudioApiJobsJobIdRegenerateAudioPostResponses, RegisterApiAuthRegisterPostData, RegisterApiAuthRegisterPostErrors, RegisterApiAuthRegisterPostResponses, RemoveLearnableLanguageApiAccountLearnableLanguagesLanguageIdDeleteData, RemoveLearnableLanguageApiAccountLearnableLanguagesLanguageIdDeleteErrors, RemoveLearnableLanguageApiAccountLearnableLanguagesLanguageIdDeleteResponses, ResolveSenseApiVocabEntryIdSensePatchData, ResolveSenseApiVocabEntryIdSensePatchErrors, ResolveSenseApiVocabEntryIdSensePatchResponses, TranslateTextApiTranslateGetData, TranslateTextApiTranslateGetErrors, TranslateTextApiTranslateGetResponses, UpsertLearnableLanguageApiLearnableLanguagesPostData, UpsertLearnableLanguageApiLearnableLanguagesPostErrors, UpsertLearnableLanguageApiLearnableLanguagesPostResponses, VerifyEmailApiAuthVerifyEmailGetData, VerifyEmailApiAuthVerifyEmailGetErrors, VerifyEmailApiAuthVerifyEmailGetResponses } from './types.gen';
|
||||
import type { AnalyzePosApiPosPostData, AnalyzePosApiPosPostErrors, AnalyzePosApiPosPostResponses, CreateGenerationJobApiGeneratePostData, CreateGenerationJobApiGeneratePostErrors, CreateGenerationJobApiGeneratePostResponses, GetArticleBffArticlesArticleIdGetData, GetArticleBffArticlesArticleIdGetErrors, GetArticleBffArticlesArticleIdGetResponses, GetJobApiJobsJobIdGetData, GetJobApiJobsJobIdGetErrors, GetJobApiJobsJobIdGetResponses, GetJobsApiJobsGetData, GetJobsApiJobsGetResponses, GetMediaFileMediaFilenameGetData, GetMediaFileMediaFilenameGetErrors, GetMediaFileMediaFilenameGetResponses, GetUserProfileBffUserProfileGetData, GetUserProfileBffUserProfileGetResponses, HealthHealthGetData, HealthHealthGetResponses, ListArticlesBffArticlesGetData, ListArticlesBffArticlesGetErrors, ListArticlesBffArticlesGetResponses, LoginAuthLoginPostData, LoginAuthLoginPostErrors, LoginAuthLoginPostResponses, RegenerateAudioApiJobsJobIdRegenerateAudioPostData, RegenerateAudioApiJobsJobIdRegenerateAudioPostErrors, RegenerateAudioApiJobsJobIdRegenerateAudioPostResponses, RegisterAuthRegisterPostData, RegisterAuthRegisterPostErrors, RegisterAuthRegisterPostResponses, TranslateTextApiTranslateGetData, TranslateTextApiTranslateGetErrors, TranslateTextApiTranslateGetResponses, UpsertLearnableLanguageApiLearnableLanguagesPostData, UpsertLearnableLanguageApiLearnableLanguagesPostErrors, UpsertLearnableLanguageApiLearnableLanguagesPostResponses } from './types.gen';
|
||||
|
||||
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = Options2<TData, ThrowOnError> & {
|
||||
/**
|
||||
|
|
@ -18,114 +18,6 @@ export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends
|
|||
meta?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Register
|
||||
*/
|
||||
export const registerApiAuthRegisterPost = <ThrowOnError extends boolean = false>(options: Options<RegisterApiAuthRegisterPostData, ThrowOnError>) => (options.client ?? client).post<RegisterApiAuthRegisterPostResponses, RegisterApiAuthRegisterPostErrors, ThrowOnError>({
|
||||
url: '/api/auth/register',
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Login
|
||||
*/
|
||||
export const loginApiAuthLoginPost = <ThrowOnError extends boolean = false>(options: Options<LoginApiAuthLoginPostData, ThrowOnError>) => (options.client ?? client).post<LoginApiAuthLoginPostResponses, LoginApiAuthLoginPostErrors, ThrowOnError>({
|
||||
url: '/api/auth/login',
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Verify Email
|
||||
*/
|
||||
export const verifyEmailApiAuthVerifyEmailGet = <ThrowOnError extends boolean = false>(options: Options<VerifyEmailApiAuthVerifyEmailGetData, ThrowOnError>) => (options.client ?? client).get<VerifyEmailApiAuthVerifyEmailGetResponses, VerifyEmailApiAuthVerifyEmailGetErrors, ThrowOnError>({ url: '/api/auth/verify-email', ...options });
|
||||
|
||||
/**
|
||||
* Add Learnable Language
|
||||
*/
|
||||
export const addLearnableLanguageApiAccountLearnableLanguagesPost = <ThrowOnError extends boolean = false>(options: Options<AddLearnableLanguageApiAccountLearnableLanguagesPostData, ThrowOnError>) => (options.client ?? client).post<AddLearnableLanguageApiAccountLearnableLanguagesPostResponses, AddLearnableLanguageApiAccountLearnableLanguagesPostErrors, ThrowOnError>({
|
||||
security: [{ scheme: 'bearer', type: 'http' }],
|
||||
url: '/api/account/learnable-languages',
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Complete Onboarding
|
||||
*/
|
||||
export const completeOnboardingApiAccountOnboardingPost = <ThrowOnError extends boolean = false>(options: Options<CompleteOnboardingApiAccountOnboardingPostData, ThrowOnError>) => (options.client ?? client).post<CompleteOnboardingApiAccountOnboardingPostResponses, CompleteOnboardingApiAccountOnboardingPostErrors, ThrowOnError>({
|
||||
security: [{ scheme: 'bearer', type: 'http' }],
|
||||
url: '/api/account/onboarding',
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Get Account Status
|
||||
*/
|
||||
export const getAccountStatusApiAccountStatusGet = <ThrowOnError extends boolean = false>(options?: Options<GetAccountStatusApiAccountStatusGetData, ThrowOnError>) => (options?.client ?? client).get<GetAccountStatusApiAccountStatusGetResponses, unknown, ThrowOnError>({
|
||||
security: [{ scheme: 'bearer', type: 'http' }],
|
||||
url: '/api/account/status',
|
||||
...options
|
||||
});
|
||||
|
||||
/**
|
||||
* Remove Learnable Language
|
||||
*/
|
||||
export const removeLearnableLanguageApiAccountLearnableLanguagesLanguageIdDelete = <ThrowOnError extends boolean = false>(options: Options<RemoveLearnableLanguageApiAccountLearnableLanguagesLanguageIdDeleteData, ThrowOnError>) => (options.client ?? client).delete<RemoveLearnableLanguageApiAccountLearnableLanguagesLanguageIdDeleteResponses, RemoveLearnableLanguageApiAccountLearnableLanguagesLanguageIdDeleteErrors, ThrowOnError>({
|
||||
security: [{ scheme: 'bearer', type: 'http' }],
|
||||
url: '/api/account/learnable-languages/{language_id}',
|
||||
...options
|
||||
});
|
||||
|
||||
/**
|
||||
* Generate Flashcards
|
||||
*/
|
||||
export const generateFlashcardsApiVocabEntryIdFlashcardsPost = <ThrowOnError extends boolean = false>(options: Options<GenerateFlashcardsApiVocabEntryIdFlashcardsPostData, ThrowOnError>) => (options.client ?? client).post<GenerateFlashcardsApiVocabEntryIdFlashcardsPostResponses, GenerateFlashcardsApiVocabEntryIdFlashcardsPostErrors, ThrowOnError>({
|
||||
security: [{ scheme: 'bearer', type: 'http' }],
|
||||
url: '/api/vocab/{entry_id}/flashcards',
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* List Flashcards
|
||||
*/
|
||||
export const listFlashcardsApiFlashcardsGet = <ThrowOnError extends boolean = false>(options?: Options<ListFlashcardsApiFlashcardsGetData, ThrowOnError>) => (options?.client ?? client).get<ListFlashcardsApiFlashcardsGetResponses, unknown, ThrowOnError>({
|
||||
security: [{ scheme: 'bearer', type: 'http' }],
|
||||
url: '/api/flashcards',
|
||||
...options
|
||||
});
|
||||
|
||||
/**
|
||||
* Record Event
|
||||
*/
|
||||
export const recordEventApiFlashcardsFlashcardIdEventsPost = <ThrowOnError extends boolean = false>(options: Options<RecordEventApiFlashcardsFlashcardIdEventsPostData, ThrowOnError>) => (options.client ?? client).post<RecordEventApiFlashcardsFlashcardIdEventsPostResponses, RecordEventApiFlashcardsFlashcardIdEventsPostErrors, ThrowOnError>({
|
||||
security: [{ scheme: 'bearer', type: 'http' }],
|
||||
url: '/api/flashcards/{flashcard_id}/events',
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Analyze Pos
|
||||
*/
|
||||
|
|
@ -201,81 +93,6 @@ export const upsertLearnableLanguageApiLearnableLanguagesPost = <ThrowOnError ex
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* List Entries
|
||||
*/
|
||||
export const listEntriesApiVocabGet = <ThrowOnError extends boolean = false>(options: Options<ListEntriesApiVocabGetData, ThrowOnError>) => (options.client ?? client).get<ListEntriesApiVocabGetResponses, ListEntriesApiVocabGetErrors, ThrowOnError>({
|
||||
security: [{ scheme: 'bearer', type: 'http' }],
|
||||
url: '/api/vocab',
|
||||
...options
|
||||
});
|
||||
|
||||
/**
|
||||
* Add Word
|
||||
*/
|
||||
export const addWordApiVocabPost = <ThrowOnError extends boolean = false>(options: Options<AddWordApiVocabPostData, ThrowOnError>) => (options.client ?? client).post<AddWordApiVocabPostResponses, AddWordApiVocabPostErrors, ThrowOnError>({
|
||||
security: [{ scheme: 'bearer', type: 'http' }],
|
||||
url: '/api/vocab',
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Add From Token
|
||||
*/
|
||||
export const addFromTokenApiVocabFromTokenPost = <ThrowOnError extends boolean = false>(options: Options<AddFromTokenApiVocabFromTokenPostData, ThrowOnError>) => (options.client ?? client).post<AddFromTokenApiVocabFromTokenPostResponses, AddFromTokenApiVocabFromTokenPostErrors, ThrowOnError>({
|
||||
security: [{ scheme: 'bearer', type: 'http' }],
|
||||
url: '/api/vocab/from-token',
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Pending Disambiguation
|
||||
*/
|
||||
export const pendingDisambiguationApiVocabPendingDisambiguationGet = <ThrowOnError extends boolean = false>(options?: Options<PendingDisambiguationApiVocabPendingDisambiguationGetData, ThrowOnError>) => (options?.client ?? client).get<PendingDisambiguationApiVocabPendingDisambiguationGetResponses, unknown, ThrowOnError>({
|
||||
security: [{ scheme: 'bearer', type: 'http' }],
|
||||
url: '/api/vocab/pending-disambiguation',
|
||||
...options
|
||||
});
|
||||
|
||||
/**
|
||||
* Resolve Sense
|
||||
*/
|
||||
export const resolveSenseApiVocabEntryIdSensePatch = <ThrowOnError extends boolean = false>(options: Options<ResolveSenseApiVocabEntryIdSensePatchData, ThrowOnError>) => (options.client ?? client).patch<ResolveSenseApiVocabEntryIdSensePatchResponses, ResolveSenseApiVocabEntryIdSensePatchErrors, ThrowOnError>({
|
||||
security: [{ scheme: 'bearer', type: 'http' }],
|
||||
url: '/api/vocab/{entry_id}/sense',
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Get Account
|
||||
*/
|
||||
export const getAccountBffAccountGet = <ThrowOnError extends boolean = false>(options?: Options<GetAccountBffAccountGetData, ThrowOnError>) => (options?.client ?? client).get<GetAccountBffAccountGetResponses, unknown, ThrowOnError>({
|
||||
security: [{ scheme: 'bearer', type: 'http' }],
|
||||
url: '/bff/account',
|
||||
...options
|
||||
});
|
||||
|
||||
/**
|
||||
* Get Onboarding
|
||||
*/
|
||||
export const getOnboardingBffAccountOnboardingGet = <ThrowOnError extends boolean = false>(options?: Options<GetOnboardingBffAccountOnboardingGetData, ThrowOnError>) => (options?.client ?? client).get<GetOnboardingBffAccountOnboardingGetResponses, unknown, ThrowOnError>({
|
||||
security: [{ scheme: 'bearer', type: 'http' }],
|
||||
url: '/bff/account/onboarding',
|
||||
...options
|
||||
});
|
||||
|
||||
/**
|
||||
* List Articles
|
||||
*/
|
||||
|
|
@ -303,6 +120,30 @@ export const getUserProfileBffUserProfileGet = <ThrowOnError extends boolean = f
|
|||
...options
|
||||
});
|
||||
|
||||
/**
|
||||
* Register
|
||||
*/
|
||||
export const registerAuthRegisterPost = <ThrowOnError extends boolean = false>(options: Options<RegisterAuthRegisterPostData, ThrowOnError>) => (options.client ?? client).post<RegisterAuthRegisterPostResponses, RegisterAuthRegisterPostErrors, ThrowOnError>({
|
||||
url: '/auth/register',
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Login
|
||||
*/
|
||||
export const loginAuthLoginPost = <ThrowOnError extends boolean = false>(options: Options<LoginAuthLoginPostData, ThrowOnError>) => (options.client ?? client).post<LoginAuthLoginPostResponses, LoginAuthLoginPostErrors, ThrowOnError>({
|
||||
url: '/auth/login',
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Get Media File
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -4,134 +4,6 @@ export type ClientOptions = {
|
|||
baseUrl: `${string}://src` | (string & {});
|
||||
};
|
||||
|
||||
/**
|
||||
* AccountLanguagePair
|
||||
*/
|
||||
export type AccountLanguagePair = {
|
||||
/**
|
||||
* Id
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Source Language
|
||||
*/
|
||||
source_language: string;
|
||||
/**
|
||||
* Target Language
|
||||
*/
|
||||
target_language: string;
|
||||
/**
|
||||
* Proficiencies
|
||||
*/
|
||||
proficiencies: Array<string>;
|
||||
};
|
||||
|
||||
/**
|
||||
* AccountResponse
|
||||
*/
|
||||
export type AccountResponse = {
|
||||
/**
|
||||
* Email
|
||||
*/
|
||||
email: string;
|
||||
/**
|
||||
* Human Name
|
||||
*/
|
||||
human_name: string | null;
|
||||
/**
|
||||
* Language Pairs
|
||||
*/
|
||||
language_pairs: Array<AccountLanguagePair>;
|
||||
};
|
||||
|
||||
/**
|
||||
* AccountStatusResponse
|
||||
*/
|
||||
export type AccountStatusResponse = {
|
||||
/**
|
||||
* Problem Flags
|
||||
*/
|
||||
problem_flags: Array<string>;
|
||||
/**
|
||||
* Error Messages
|
||||
*/
|
||||
error_messages: Array<string>;
|
||||
};
|
||||
|
||||
/**
|
||||
* AddFromTokenRequest
|
||||
*/
|
||||
export type AddFromTokenRequest = {
|
||||
/**
|
||||
* Language Pair Id
|
||||
*/
|
||||
language_pair_id: string;
|
||||
/**
|
||||
* Surface
|
||||
*/
|
||||
surface: string;
|
||||
/**
|
||||
* Spacy Lemma
|
||||
*/
|
||||
spacy_lemma: string;
|
||||
/**
|
||||
* Pos Ud
|
||||
*/
|
||||
pos_ud: string;
|
||||
/**
|
||||
* Language
|
||||
*/
|
||||
language: string;
|
||||
/**
|
||||
* Source Article Id
|
||||
*/
|
||||
source_article_id?: string | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* AddLearnableLanguageRequest
|
||||
*/
|
||||
export type AddLearnableLanguageRequest = {
|
||||
/**
|
||||
* Source Language
|
||||
*/
|
||||
source_language: string;
|
||||
/**
|
||||
* Target Language
|
||||
*/
|
||||
target_language: string;
|
||||
/**
|
||||
* Proficiencies
|
||||
*/
|
||||
proficiencies: Array<string>;
|
||||
};
|
||||
|
||||
/**
|
||||
* AddWordRequest
|
||||
*/
|
||||
export type AddWordRequest = {
|
||||
/**
|
||||
* Language Pair Id
|
||||
*/
|
||||
language_pair_id: string;
|
||||
/**
|
||||
* Surface Text
|
||||
*/
|
||||
surface_text: string;
|
||||
/**
|
||||
* Entry Pathway
|
||||
*/
|
||||
entry_pathway?: string;
|
||||
/**
|
||||
* Is Phrase
|
||||
*/
|
||||
is_phrase?: boolean;
|
||||
/**
|
||||
* Source Article Id
|
||||
*/
|
||||
source_article_id?: string | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* ArticleDetail
|
||||
*/
|
||||
|
|
@ -240,115 +112,6 @@ export type ArticleListResponse = {
|
|||
articles: Array<ArticleItem>;
|
||||
};
|
||||
|
||||
/**
|
||||
* FlashcardEventResponse
|
||||
*/
|
||||
export type FlashcardEventResponse = {
|
||||
/**
|
||||
* Id
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Flashcard Id
|
||||
*/
|
||||
flashcard_id: string;
|
||||
/**
|
||||
* User Id
|
||||
*/
|
||||
user_id: string;
|
||||
/**
|
||||
* Event Type
|
||||
*/
|
||||
event_type: string;
|
||||
/**
|
||||
* User Response
|
||||
*/
|
||||
user_response: string | null;
|
||||
/**
|
||||
* Created At
|
||||
*/
|
||||
created_at: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* FlashcardResponse
|
||||
*/
|
||||
export type FlashcardResponse = {
|
||||
/**
|
||||
* Id
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* User Id
|
||||
*/
|
||||
user_id: string;
|
||||
/**
|
||||
* Bank Entry Id
|
||||
*/
|
||||
bank_entry_id: string;
|
||||
/**
|
||||
* Source Lang
|
||||
*/
|
||||
source_lang: string;
|
||||
/**
|
||||
* Target Lang
|
||||
*/
|
||||
target_lang: string;
|
||||
/**
|
||||
* Prompt Text
|
||||
*/
|
||||
prompt_text: string;
|
||||
/**
|
||||
* Answer Text
|
||||
*/
|
||||
answer_text: string;
|
||||
/**
|
||||
* Prompt Context Text
|
||||
*/
|
||||
prompt_context_text: string | null;
|
||||
/**
|
||||
* Answer Context Text
|
||||
*/
|
||||
answer_context_text: string | null;
|
||||
/**
|
||||
* Card Direction
|
||||
*/
|
||||
card_direction: string;
|
||||
/**
|
||||
* Prompt Modality
|
||||
*/
|
||||
prompt_modality: string;
|
||||
/**
|
||||
* Created At
|
||||
*/
|
||||
created_at: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* FromTokenResponse
|
||||
*/
|
||||
export type FromTokenResponse = {
|
||||
entry: WordBankEntryResponse;
|
||||
/**
|
||||
* Sense Candidates
|
||||
*/
|
||||
sense_candidates: Array<SenseCandidateResponse>;
|
||||
/**
|
||||
* Matched Via
|
||||
*/
|
||||
matched_via: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* GenerateFlashcardsRequest
|
||||
*/
|
||||
export type GenerateFlashcardsRequest = {
|
||||
/**
|
||||
* Direction
|
||||
*/
|
||||
direction?: string | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* GenerationRequest
|
||||
*/
|
||||
|
|
@ -461,24 +224,6 @@ export type JobSummary = {
|
|||
error_message?: string | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* LanguagePairOption
|
||||
*/
|
||||
export type LanguagePairOption = {
|
||||
/**
|
||||
* Value
|
||||
*/
|
||||
value: string;
|
||||
/**
|
||||
* Label
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
description: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* LearnableLanguageItem
|
||||
*/
|
||||
|
|
@ -555,38 +300,6 @@ export type LoginRequest = {
|
|||
password: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* OnboardingRequest
|
||||
*/
|
||||
export type OnboardingRequest = {
|
||||
/**
|
||||
* Human Name
|
||||
*/
|
||||
human_name: string;
|
||||
/**
|
||||
* Language Pairs
|
||||
*/
|
||||
language_pairs: Array<string>;
|
||||
/**
|
||||
* Proficiencies
|
||||
*/
|
||||
proficiencies: Array<Array<string>>;
|
||||
};
|
||||
|
||||
/**
|
||||
* OnboardingResponse
|
||||
*/
|
||||
export type OnboardingResponse = {
|
||||
/**
|
||||
* Language Pairs
|
||||
*/
|
||||
language_pairs: Array<LanguagePairOption>;
|
||||
/**
|
||||
* Proficiencies
|
||||
*/
|
||||
proficiencies: Array<ProficiencyOption>;
|
||||
};
|
||||
|
||||
/**
|
||||
* POSRequest
|
||||
*/
|
||||
|
|
@ -615,38 +328,6 @@ export type PosResponse = {
|
|||
tokens: Array<TokenInfo>;
|
||||
};
|
||||
|
||||
/**
|
||||
* ProficiencyOption
|
||||
*/
|
||||
export type ProficiencyOption = {
|
||||
/**
|
||||
* Value
|
||||
*/
|
||||
value: string;
|
||||
/**
|
||||
* Label
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
description: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* RecordEventRequest
|
||||
*/
|
||||
export type RecordEventRequest = {
|
||||
/**
|
||||
* Event Type
|
||||
*/
|
||||
event_type: string;
|
||||
/**
|
||||
* User Response
|
||||
*/
|
||||
user_response?: string | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* RegisterRequest
|
||||
*/
|
||||
|
|
@ -661,52 +342,6 @@ export type RegisterRequest = {
|
|||
password: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* RegisterResponse
|
||||
*/
|
||||
export type RegisterResponse = {
|
||||
/**
|
||||
* Success
|
||||
*/
|
||||
success: boolean;
|
||||
/**
|
||||
* Error Message
|
||||
*/
|
||||
error_message?: string | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* SenseCandidateResponse
|
||||
*/
|
||||
export type SenseCandidateResponse = {
|
||||
/**
|
||||
* Id
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Gloss
|
||||
*/
|
||||
gloss: string;
|
||||
/**
|
||||
* Topics
|
||||
*/
|
||||
topics: Array<string>;
|
||||
/**
|
||||
* Tags
|
||||
*/
|
||||
tags: Array<string>;
|
||||
};
|
||||
|
||||
/**
|
||||
* SetSenseRequest
|
||||
*/
|
||||
export type SetSenseRequest = {
|
||||
/**
|
||||
* Sense Id
|
||||
*/
|
||||
sense_id: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* TokenInfo
|
||||
*/
|
||||
|
|
@ -807,320 +442,6 @@ export type ValidationError = {
|
|||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* WordBankEntryResponse
|
||||
*/
|
||||
export type WordBankEntryResponse = {
|
||||
/**
|
||||
* Id
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* User Id
|
||||
*/
|
||||
user_id: string;
|
||||
/**
|
||||
* Language Pair Id
|
||||
*/
|
||||
language_pair_id: string;
|
||||
/**
|
||||
* Sense Id
|
||||
*/
|
||||
sense_id: string | null;
|
||||
/**
|
||||
* Wordform Id
|
||||
*/
|
||||
wordform_id: string | null;
|
||||
/**
|
||||
* Surface Text
|
||||
*/
|
||||
surface_text: string;
|
||||
/**
|
||||
* Is Phrase
|
||||
*/
|
||||
is_phrase: boolean;
|
||||
/**
|
||||
* Entry Pathway
|
||||
*/
|
||||
entry_pathway: string;
|
||||
/**
|
||||
* Source Article Id
|
||||
*/
|
||||
source_article_id: string | null;
|
||||
/**
|
||||
* Disambiguation Status
|
||||
*/
|
||||
disambiguation_status: string;
|
||||
/**
|
||||
* Created At
|
||||
*/
|
||||
created_at: string;
|
||||
};
|
||||
|
||||
export type RegisterApiAuthRegisterPostData = {
|
||||
body: RegisterRequest;
|
||||
path?: never;
|
||||
query?: never;
|
||||
url: '/api/auth/register';
|
||||
};
|
||||
|
||||
export type RegisterApiAuthRegisterPostErrors = {
|
||||
/**
|
||||
* Validation Error
|
||||
*/
|
||||
422: HttpValidationError;
|
||||
};
|
||||
|
||||
export type RegisterApiAuthRegisterPostError = RegisterApiAuthRegisterPostErrors[keyof RegisterApiAuthRegisterPostErrors];
|
||||
|
||||
export type RegisterApiAuthRegisterPostResponses = {
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
201: RegisterResponse;
|
||||
};
|
||||
|
||||
export type RegisterApiAuthRegisterPostResponse = RegisterApiAuthRegisterPostResponses[keyof RegisterApiAuthRegisterPostResponses];
|
||||
|
||||
export type LoginApiAuthLoginPostData = {
|
||||
body: LoginRequest;
|
||||
path?: never;
|
||||
query?: never;
|
||||
url: '/api/auth/login';
|
||||
};
|
||||
|
||||
export type LoginApiAuthLoginPostErrors = {
|
||||
/**
|
||||
* Validation Error
|
||||
*/
|
||||
422: HttpValidationError;
|
||||
};
|
||||
|
||||
export type LoginApiAuthLoginPostError = LoginApiAuthLoginPostErrors[keyof LoginApiAuthLoginPostErrors];
|
||||
|
||||
export type LoginApiAuthLoginPostResponses = {
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
200: TokenResponse;
|
||||
};
|
||||
|
||||
export type LoginApiAuthLoginPostResponse = LoginApiAuthLoginPostResponses[keyof LoginApiAuthLoginPostResponses];
|
||||
|
||||
export type VerifyEmailApiAuthVerifyEmailGetData = {
|
||||
body?: never;
|
||||
path?: never;
|
||||
query: {
|
||||
/**
|
||||
* Token
|
||||
*/
|
||||
token: string;
|
||||
};
|
||||
url: '/api/auth/verify-email';
|
||||
};
|
||||
|
||||
export type VerifyEmailApiAuthVerifyEmailGetErrors = {
|
||||
/**
|
||||
* Validation Error
|
||||
*/
|
||||
422: HttpValidationError;
|
||||
};
|
||||
|
||||
export type VerifyEmailApiAuthVerifyEmailGetError = VerifyEmailApiAuthVerifyEmailGetErrors[keyof VerifyEmailApiAuthVerifyEmailGetErrors];
|
||||
|
||||
export type VerifyEmailApiAuthVerifyEmailGetResponses = {
|
||||
/**
|
||||
* Response Verify Email Api Auth Verify Email Get
|
||||
*
|
||||
* Successful Response
|
||||
*/
|
||||
200: {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
};
|
||||
|
||||
export type VerifyEmailApiAuthVerifyEmailGetResponse = VerifyEmailApiAuthVerifyEmailGetResponses[keyof VerifyEmailApiAuthVerifyEmailGetResponses];
|
||||
|
||||
export type AddLearnableLanguageApiAccountLearnableLanguagesPostData = {
|
||||
body: AddLearnableLanguageRequest;
|
||||
path?: never;
|
||||
query?: never;
|
||||
url: '/api/account/learnable-languages';
|
||||
};
|
||||
|
||||
export type AddLearnableLanguageApiAccountLearnableLanguagesPostErrors = {
|
||||
/**
|
||||
* Validation Error
|
||||
*/
|
||||
422: HttpValidationError;
|
||||
};
|
||||
|
||||
export type AddLearnableLanguageApiAccountLearnableLanguagesPostError = AddLearnableLanguageApiAccountLearnableLanguagesPostErrors[keyof AddLearnableLanguageApiAccountLearnableLanguagesPostErrors];
|
||||
|
||||
export type AddLearnableLanguageApiAccountLearnableLanguagesPostResponses = {
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
201: LearnableLanguageResponse;
|
||||
};
|
||||
|
||||
export type AddLearnableLanguageApiAccountLearnableLanguagesPostResponse = AddLearnableLanguageApiAccountLearnableLanguagesPostResponses[keyof AddLearnableLanguageApiAccountLearnableLanguagesPostResponses];
|
||||
|
||||
export type CompleteOnboardingApiAccountOnboardingPostData = {
|
||||
body: OnboardingRequest;
|
||||
path?: never;
|
||||
query?: never;
|
||||
url: '/api/account/onboarding';
|
||||
};
|
||||
|
||||
export type CompleteOnboardingApiAccountOnboardingPostErrors = {
|
||||
/**
|
||||
* Validation Error
|
||||
*/
|
||||
422: HttpValidationError;
|
||||
};
|
||||
|
||||
export type CompleteOnboardingApiAccountOnboardingPostError = CompleteOnboardingApiAccountOnboardingPostErrors[keyof CompleteOnboardingApiAccountOnboardingPostErrors];
|
||||
|
||||
export type CompleteOnboardingApiAccountOnboardingPostResponses = {
|
||||
/**
|
||||
* Response Complete Onboarding Api Account Onboarding Post
|
||||
*
|
||||
* Successful Response
|
||||
*/
|
||||
200: {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
};
|
||||
|
||||
export type CompleteOnboardingApiAccountOnboardingPostResponse = CompleteOnboardingApiAccountOnboardingPostResponses[keyof CompleteOnboardingApiAccountOnboardingPostResponses];
|
||||
|
||||
export type GetAccountStatusApiAccountStatusGetData = {
|
||||
body?: never;
|
||||
path?: never;
|
||||
query?: never;
|
||||
url: '/api/account/status';
|
||||
};
|
||||
|
||||
export type GetAccountStatusApiAccountStatusGetResponses = {
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
200: AccountStatusResponse;
|
||||
};
|
||||
|
||||
export type GetAccountStatusApiAccountStatusGetResponse = GetAccountStatusApiAccountStatusGetResponses[keyof GetAccountStatusApiAccountStatusGetResponses];
|
||||
|
||||
export type RemoveLearnableLanguageApiAccountLearnableLanguagesLanguageIdDeleteData = {
|
||||
body?: never;
|
||||
path: {
|
||||
/**
|
||||
* Language Id
|
||||
*/
|
||||
language_id: string;
|
||||
};
|
||||
query?: never;
|
||||
url: '/api/account/learnable-languages/{language_id}';
|
||||
};
|
||||
|
||||
export type RemoveLearnableLanguageApiAccountLearnableLanguagesLanguageIdDeleteErrors = {
|
||||
/**
|
||||
* Validation Error
|
||||
*/
|
||||
422: HttpValidationError;
|
||||
};
|
||||
|
||||
export type RemoveLearnableLanguageApiAccountLearnableLanguagesLanguageIdDeleteError = RemoveLearnableLanguageApiAccountLearnableLanguagesLanguageIdDeleteErrors[keyof RemoveLearnableLanguageApiAccountLearnableLanguagesLanguageIdDeleteErrors];
|
||||
|
||||
export type RemoveLearnableLanguageApiAccountLearnableLanguagesLanguageIdDeleteResponses = {
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
204: void;
|
||||
};
|
||||
|
||||
export type RemoveLearnableLanguageApiAccountLearnableLanguagesLanguageIdDeleteResponse = RemoveLearnableLanguageApiAccountLearnableLanguagesLanguageIdDeleteResponses[keyof RemoveLearnableLanguageApiAccountLearnableLanguagesLanguageIdDeleteResponses];
|
||||
|
||||
export type GenerateFlashcardsApiVocabEntryIdFlashcardsPostData = {
|
||||
body: GenerateFlashcardsRequest;
|
||||
path: {
|
||||
/**
|
||||
* Entry Id
|
||||
*/
|
||||
entry_id: string;
|
||||
};
|
||||
query?: never;
|
||||
url: '/api/vocab/{entry_id}/flashcards';
|
||||
};
|
||||
|
||||
export type GenerateFlashcardsApiVocabEntryIdFlashcardsPostErrors = {
|
||||
/**
|
||||
* Validation Error
|
||||
*/
|
||||
422: HttpValidationError;
|
||||
};
|
||||
|
||||
export type GenerateFlashcardsApiVocabEntryIdFlashcardsPostError = GenerateFlashcardsApiVocabEntryIdFlashcardsPostErrors[keyof GenerateFlashcardsApiVocabEntryIdFlashcardsPostErrors];
|
||||
|
||||
export type GenerateFlashcardsApiVocabEntryIdFlashcardsPostResponses = {
|
||||
/**
|
||||
* Response Generate Flashcards Api Vocab Entry Id Flashcards Post
|
||||
*
|
||||
* Successful Response
|
||||
*/
|
||||
201: Array<FlashcardResponse>;
|
||||
};
|
||||
|
||||
export type GenerateFlashcardsApiVocabEntryIdFlashcardsPostResponse = GenerateFlashcardsApiVocabEntryIdFlashcardsPostResponses[keyof GenerateFlashcardsApiVocabEntryIdFlashcardsPostResponses];
|
||||
|
||||
export type ListFlashcardsApiFlashcardsGetData = {
|
||||
body?: never;
|
||||
path?: never;
|
||||
query?: never;
|
||||
url: '/api/flashcards';
|
||||
};
|
||||
|
||||
export type ListFlashcardsApiFlashcardsGetResponses = {
|
||||
/**
|
||||
* Response List Flashcards Api Flashcards Get
|
||||
*
|
||||
* Successful Response
|
||||
*/
|
||||
200: Array<FlashcardResponse>;
|
||||
};
|
||||
|
||||
export type ListFlashcardsApiFlashcardsGetResponse = ListFlashcardsApiFlashcardsGetResponses[keyof ListFlashcardsApiFlashcardsGetResponses];
|
||||
|
||||
export type RecordEventApiFlashcardsFlashcardIdEventsPostData = {
|
||||
body: RecordEventRequest;
|
||||
path: {
|
||||
/**
|
||||
* Flashcard Id
|
||||
*/
|
||||
flashcard_id: string;
|
||||
};
|
||||
query?: never;
|
||||
url: '/api/flashcards/{flashcard_id}/events';
|
||||
};
|
||||
|
||||
export type RecordEventApiFlashcardsFlashcardIdEventsPostErrors = {
|
||||
/**
|
||||
* Validation Error
|
||||
*/
|
||||
422: HttpValidationError;
|
||||
};
|
||||
|
||||
export type RecordEventApiFlashcardsFlashcardIdEventsPostError = RecordEventApiFlashcardsFlashcardIdEventsPostErrors[keyof RecordEventApiFlashcardsFlashcardIdEventsPostErrors];
|
||||
|
||||
export type RecordEventApiFlashcardsFlashcardIdEventsPostResponses = {
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
201: FlashcardEventResponse;
|
||||
};
|
||||
|
||||
export type RecordEventApiFlashcardsFlashcardIdEventsPostResponse = RecordEventApiFlashcardsFlashcardIdEventsPostResponses[keyof RecordEventApiFlashcardsFlashcardIdEventsPostResponses];
|
||||
|
||||
export type AnalyzePosApiPosPostData = {
|
||||
body: PosRequest;
|
||||
path?: never;
|
||||
|
|
@ -1314,168 +635,6 @@ export type UpsertLearnableLanguageApiLearnableLanguagesPostResponses = {
|
|||
|
||||
export type UpsertLearnableLanguageApiLearnableLanguagesPostResponse = UpsertLearnableLanguageApiLearnableLanguagesPostResponses[keyof UpsertLearnableLanguageApiLearnableLanguagesPostResponses];
|
||||
|
||||
export type ListEntriesApiVocabGetData = {
|
||||
body?: never;
|
||||
path?: never;
|
||||
query: {
|
||||
/**
|
||||
* Language Pair Id
|
||||
*/
|
||||
language_pair_id: string;
|
||||
};
|
||||
url: '/api/vocab';
|
||||
};
|
||||
|
||||
export type ListEntriesApiVocabGetErrors = {
|
||||
/**
|
||||
* Validation Error
|
||||
*/
|
||||
422: HttpValidationError;
|
||||
};
|
||||
|
||||
export type ListEntriesApiVocabGetError = ListEntriesApiVocabGetErrors[keyof ListEntriesApiVocabGetErrors];
|
||||
|
||||
export type ListEntriesApiVocabGetResponses = {
|
||||
/**
|
||||
* Response List Entries Api Vocab Get
|
||||
*
|
||||
* Successful Response
|
||||
*/
|
||||
200: Array<WordBankEntryResponse>;
|
||||
};
|
||||
|
||||
export type ListEntriesApiVocabGetResponse = ListEntriesApiVocabGetResponses[keyof ListEntriesApiVocabGetResponses];
|
||||
|
||||
export type AddWordApiVocabPostData = {
|
||||
body: AddWordRequest;
|
||||
path?: never;
|
||||
query?: never;
|
||||
url: '/api/vocab';
|
||||
};
|
||||
|
||||
export type AddWordApiVocabPostErrors = {
|
||||
/**
|
||||
* Validation Error
|
||||
*/
|
||||
422: HttpValidationError;
|
||||
};
|
||||
|
||||
export type AddWordApiVocabPostError = AddWordApiVocabPostErrors[keyof AddWordApiVocabPostErrors];
|
||||
|
||||
export type AddWordApiVocabPostResponses = {
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
201: WordBankEntryResponse;
|
||||
};
|
||||
|
||||
export type AddWordApiVocabPostResponse = AddWordApiVocabPostResponses[keyof AddWordApiVocabPostResponses];
|
||||
|
||||
export type AddFromTokenApiVocabFromTokenPostData = {
|
||||
body: AddFromTokenRequest;
|
||||
path?: never;
|
||||
query?: never;
|
||||
url: '/api/vocab/from-token';
|
||||
};
|
||||
|
||||
export type AddFromTokenApiVocabFromTokenPostErrors = {
|
||||
/**
|
||||
* Validation Error
|
||||
*/
|
||||
422: HttpValidationError;
|
||||
};
|
||||
|
||||
export type AddFromTokenApiVocabFromTokenPostError = AddFromTokenApiVocabFromTokenPostErrors[keyof AddFromTokenApiVocabFromTokenPostErrors];
|
||||
|
||||
export type AddFromTokenApiVocabFromTokenPostResponses = {
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
201: FromTokenResponse;
|
||||
};
|
||||
|
||||
export type AddFromTokenApiVocabFromTokenPostResponse = AddFromTokenApiVocabFromTokenPostResponses[keyof AddFromTokenApiVocabFromTokenPostResponses];
|
||||
|
||||
export type PendingDisambiguationApiVocabPendingDisambiguationGetData = {
|
||||
body?: never;
|
||||
path?: never;
|
||||
query?: never;
|
||||
url: '/api/vocab/pending-disambiguation';
|
||||
};
|
||||
|
||||
export type PendingDisambiguationApiVocabPendingDisambiguationGetResponses = {
|
||||
/**
|
||||
* Response Pending Disambiguation Api Vocab Pending Disambiguation Get
|
||||
*
|
||||
* Successful Response
|
||||
*/
|
||||
200: Array<WordBankEntryResponse>;
|
||||
};
|
||||
|
||||
export type PendingDisambiguationApiVocabPendingDisambiguationGetResponse = PendingDisambiguationApiVocabPendingDisambiguationGetResponses[keyof PendingDisambiguationApiVocabPendingDisambiguationGetResponses];
|
||||
|
||||
export type ResolveSenseApiVocabEntryIdSensePatchData = {
|
||||
body: SetSenseRequest;
|
||||
path: {
|
||||
/**
|
||||
* Entry Id
|
||||
*/
|
||||
entry_id: string;
|
||||
};
|
||||
query?: never;
|
||||
url: '/api/vocab/{entry_id}/sense';
|
||||
};
|
||||
|
||||
export type ResolveSenseApiVocabEntryIdSensePatchErrors = {
|
||||
/**
|
||||
* Validation Error
|
||||
*/
|
||||
422: HttpValidationError;
|
||||
};
|
||||
|
||||
export type ResolveSenseApiVocabEntryIdSensePatchError = ResolveSenseApiVocabEntryIdSensePatchErrors[keyof ResolveSenseApiVocabEntryIdSensePatchErrors];
|
||||
|
||||
export type ResolveSenseApiVocabEntryIdSensePatchResponses = {
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
200: WordBankEntryResponse;
|
||||
};
|
||||
|
||||
export type ResolveSenseApiVocabEntryIdSensePatchResponse = ResolveSenseApiVocabEntryIdSensePatchResponses[keyof ResolveSenseApiVocabEntryIdSensePatchResponses];
|
||||
|
||||
export type GetAccountBffAccountGetData = {
|
||||
body?: never;
|
||||
path?: never;
|
||||
query?: never;
|
||||
url: '/bff/account';
|
||||
};
|
||||
|
||||
export type GetAccountBffAccountGetResponses = {
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
200: AccountResponse;
|
||||
};
|
||||
|
||||
export type GetAccountBffAccountGetResponse = GetAccountBffAccountGetResponses[keyof GetAccountBffAccountGetResponses];
|
||||
|
||||
export type GetOnboardingBffAccountOnboardingGetData = {
|
||||
body?: never;
|
||||
path?: never;
|
||||
query?: never;
|
||||
url: '/bff/account/onboarding';
|
||||
};
|
||||
|
||||
export type GetOnboardingBffAccountOnboardingGetResponses = {
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
200: OnboardingResponse;
|
||||
};
|
||||
|
||||
export type GetOnboardingBffAccountOnboardingGetResponse = GetOnboardingBffAccountOnboardingGetResponses[keyof GetOnboardingBffAccountOnboardingGetResponses];
|
||||
|
||||
export type ListArticlesBffArticlesGetData = {
|
||||
body?: never;
|
||||
path?: never;
|
||||
|
|
@ -1552,6 +711,54 @@ export type GetUserProfileBffUserProfileGetResponses = {
|
|||
|
||||
export type GetUserProfileBffUserProfileGetResponse = GetUserProfileBffUserProfileGetResponses[keyof GetUserProfileBffUserProfileGetResponses];
|
||||
|
||||
export type RegisterAuthRegisterPostData = {
|
||||
body: RegisterRequest;
|
||||
path?: never;
|
||||
query?: never;
|
||||
url: '/auth/register';
|
||||
};
|
||||
|
||||
export type RegisterAuthRegisterPostErrors = {
|
||||
/**
|
||||
* Validation Error
|
||||
*/
|
||||
422: HttpValidationError;
|
||||
};
|
||||
|
||||
export type RegisterAuthRegisterPostError = RegisterAuthRegisterPostErrors[keyof RegisterAuthRegisterPostErrors];
|
||||
|
||||
export type RegisterAuthRegisterPostResponses = {
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
201: unknown;
|
||||
};
|
||||
|
||||
export type LoginAuthLoginPostData = {
|
||||
body: LoginRequest;
|
||||
path?: never;
|
||||
query?: never;
|
||||
url: '/auth/login';
|
||||
};
|
||||
|
||||
export type LoginAuthLoginPostErrors = {
|
||||
/**
|
||||
* Validation Error
|
||||
*/
|
||||
422: HttpValidationError;
|
||||
};
|
||||
|
||||
export type LoginAuthLoginPostError = LoginAuthLoginPostErrors[keyof LoginAuthLoginPostErrors];
|
||||
|
||||
export type LoginAuthLoginPostResponses = {
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
200: TokenResponse;
|
||||
};
|
||||
|
||||
export type LoginAuthLoginPostResponse = LoginAuthLoginPostResponses[keyof LoginAuthLoginPostResponses];
|
||||
|
||||
export type GetMediaFileMediaFilenameGetData = {
|
||||
body?: never;
|
||||
path: {
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue