Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add worker api #384

Merged
merged 3 commits into from
Nov 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
151 changes: 151 additions & 0 deletions backend/openapi-schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,25 @@ components:
- password
title: CreateUser
type: object
CreateWorker:
properties:
name:
title: Name
type: string
required:
- name
title: CreateWorker
type: object
DeactivateWorker:
properties:
id:
format: uuid
title: Id
type: string
required:
- id
title: DeactivateWorker
type: object
Document:
properties:
changed_at:
Expand Down Expand Up @@ -561,6 +580,52 @@ components:
- type
title: ValidationError
type: object
Worker:
properties:
deactivated_at:
format: date-time
title: Deactivated At
type: string
id:
format: uuid
title: Id
type: string
last_seen:
format: date-time
title: Last Seen
type: string
name:
title: Name
type: string
token:
title: Token
type: string
required:
- name
- token
title: Worker
type: object
WorkerWithId:
properties:
deactivated_at:
format: date-time
title: Deactivated At
type: string
id:
format: uuid
title: Id
type: string
last_seen:
format: date-time
title: Last Seen
type: string
name:
title: Name
type: string
required:
- name
title: WorkerWithId
type: object
info:
title: FastAPI
version: 0.1.0
Expand Down Expand Up @@ -1394,6 +1459,92 @@ paths:
$ref: '#/components/schemas/HTTPValidationError'
description: Validation Error
summary: Read User
/api/v1/worker/:
get:
operationId: list_workers_api_v1_worker__get
parameters:
- in: header
name: Api-Token
required: true
schema:
title: Api-Token
type: string
responses:
'200':
content:
application/json:
schema:
items:
$ref: '#/components/schemas/WorkerWithId'
title: Response List Workers Api V1 Worker Get
type: array
description: Successful Response
'422':
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
description: Validation Error
summary: List Workers
/api/v1/worker/create/:
post:
operationId: create_worker_endpoint_api_v1_worker_create__post
parameters:
- in: header
name: Api-Token
required: true
schema:
title: Api-Token
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/CreateWorker'
required: true
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Worker'
description: Successful Response
'422':
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
description: Validation Error
summary: Create Worker Endpoint
/api/v1/worker/deactivate/:
post:
operationId: deactivate_worker_endpoint_api_v1_worker_deactivate__post
parameters:
- in: header
name: Api-Token
required: true
schema:
title: Api-Token
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/DeactivateWorker'
required: true
responses:
'200':
content:
application/json:
schema: {}
description: Successful Response
'422':
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
description: Validation Error
summary: Deactivate Worker Endpoint
/media/{file}:
get:
operationId: serve_media_media__file__get
Expand Down
1 change: 1 addition & 0 deletions backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ migrate = "alembic upgrade head"
makemigrations = "alembic revision --autogenerate -m"
create_user = "scripts/create_user.py"
create_worker = "scripts/create_worker.py"
create_api_token = "scripts/create_api_token.py"
reset_task = "scripts/reset_task.py"
generate_openapi = "python -m scripts.generate_openapi"
test = "pytest tests/"
Expand Down
12 changes: 12 additions & 0 deletions backend/scripts/create_api_token.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import argparse

from transcribee_backend.auth import create_api_token
from transcribee_backend.db import SessionContextManager

if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--name", required=True)
args = parser.parse_args()
with SessionContextManager() as session:
token = create_api_token(session=session, name=args.name)
print(f"Token created: {token.token}")
47 changes: 45 additions & 2 deletions backend/transcribee_backend/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,14 @@
from transcribee_backend.db import get_session
from transcribee_backend.exceptions import UserAlreadyExists, UserDoesNotExist
from transcribee_backend.helpers.time import now_tz_aware
from transcribee_backend.models import DocumentShareToken, Task, User, UserToken, Worker
from transcribee_backend.models import (
ApiToken,
DocumentShareToken,
Task,
User,
UserToken,
Worker,
)


class NotAuthorized(Exception):
Expand Down Expand Up @@ -104,7 +111,9 @@ def validate_worker_authorization(session: Session, authorization: str) -> Worke
if token_type != "Worker":
raise HTTPException(status_code=401)

statement = select(Worker).where(Worker.token == token)
statement = select(Worker).where(
Worker.token == token, col(Worker.deactivated_at).is_(None)
)
worker = session.exec(statement).one_or_none()
if worker is None:
raise HTTPException(status_code=401)
Expand Down Expand Up @@ -194,3 +203,37 @@ def validate_share_authorization(
return token

raise HTTPException(status_code=401)


def get_api_token(
session: Session = Depends(get_session),
api_token: str = Header(alias="Api-Token"),
) -> Optional[ApiToken]:
return validate_api_token_authorization(session, api_token)


def validate_api_token_authorization(session: Session, api_token: str):
statement = select(ApiToken).where(
ApiToken.token == api_token,
)
token = session.exec(statement).one_or_none()
if token:
return token

raise HTTPException(status_code=401)


def create_worker(session: Session, name: str) -> Worker:
token = b64encode(os.urandom(32)).decode()
worker = Worker(name=name, token=token, last_seen=None, deactivated_at=None)
session.add(worker)
session.commit()
return worker


def create_api_token(session: Session, name: str) -> ApiToken:
token = b64encode(os.urandom(64)).decode()
token = ApiToken(name=name, token=token)
session.add(token)
session.commit()
return token
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""Add Worker.deactivated_at flag

Revision ID: 417eece003cb
Revises: 937846561faf
Create Date: 2023-11-18 17:14:05.221788

"""
import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = "417eece003cb"
down_revision = "937846561faf"
branch_labels = None
depends_on = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("worker", schema=None) as batch_op:
batch_op.add_column(
sa.Column("deactivated_at", sa.DateTime(timezone=True), nullable=True)
)

# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("worker", schema=None) as batch_op:
batch_op.drop_column("deactivated_at")

# ### end Alembic commands ###
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""Add ApiToken

Revision ID: 937846561faf
Revises: d679c226343d
Create Date: 2023-11-16 19:42:31.560991

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

# revision identifiers, used by Alembic.
revision = "937846561faf"
down_revision = "d679c226343d"
branch_labels = None
depends_on = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"apitoken",
sa.Column("id", sqlmodel.sql.sqltypes.GUID(), nullable=False),
sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column("token", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.PrimaryKeyConstraint("id"),
)
with op.batch_alter_table("apitoken", schema=None) as batch_op:
batch_op.create_index(batch_op.f("ix_apitoken_id"), ["id"], unique=False)

# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("apitoken", schema=None) as batch_op:
batch_op.drop_index(batch_op.f("ix_apitoken_id"))

op.drop_table("apitoken")
# ### end Alembic commands ###
2 changes: 2 additions & 0 deletions backend/transcribee_backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from transcribee_backend.routers.page import page_router
from transcribee_backend.routers.task import task_router
from transcribee_backend.routers.user import user_router
from transcribee_backend.routers.worker import worker_router

from .media_storage import serve_media

Expand All @@ -32,6 +33,7 @@
app.include_router(task_router, prefix="/api/v1/tasks")
app.include_router(config_router, prefix="/api/v1/config")
app.include_router(page_router, prefix="/api/v1/page")
app.include_router(worker_router, prefix="/api/v1/worker")


@app.get("/")
Expand Down
2 changes: 2 additions & 0 deletions backend/transcribee_backend/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .api import ApiToken
from .document import (
Document,
DocumentMediaFile,
Expand All @@ -19,6 +20,7 @@
from .worker import Worker

__all__ = [
"ApiToken",
"Document",
"DocumentMediaFile",
"DocumentMediaTag",
Expand Down
18 changes: 18 additions & 0 deletions backend/transcribee_backend/models/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import uuid

from sqlmodel import Field, SQLModel


class ApiTokenBase(SQLModel):
id: uuid.UUID
name: str


class ApiToken(ApiTokenBase, table=True):
id: uuid.UUID = Field(
default_factory=uuid.uuid4,
primary_key=True,
index=True,
nullable=False,
)
token: str
Loading