d9060b6920
- VoiceClip model (BigInteger PK voice_clip_id, structured cols + raw_json)
- Alembic migration a1b2c3d4e5f6 (chains from 8c4f1d2a9e6b)
- scrape_voice_clips(client, user_id, *, channel, page_size) per-employee scraper
over POST /wechatweb/call/list {pageNum, pageSize, userId}; channel stamped
locally (wechat/whatsapp), startTime/endTime ms-epoch string -> int
- upsert_voice_clip repo + start_time/end_time/mp3_duration int coercion
- sync_voice_clips orchestrator: iterates distinct user_id across
WechatAccount + WhatsappAccount, stamps channel per account table
- scheduler ENTITY_FUNCS['voice_clips'] + added to daily 'all' job
- GET /api/voice-clips (user_id exact beats user_real_name fuzzy; channel;
company_code; start/end window on end_time; ordered end_time desc)
- VoiceClipOut schema + router registered in main.py
- tests: scraper (5) + API (11) = 16 new; full suite 370 green
Co-Authored-By: Claude <noreply@anthropic.com>
50 lines
1.9 KiB
Python
50 lines
1.9 KiB
Python
"""voice_clip table
|
|
|
|
Revision ID: a1b2c3d4e5f6
|
|
Revises: 8c4f1d2a9e6b
|
|
Create Date: 2026-08-10 00:00:00.000000
|
|
|
|
"""
|
|
from collections.abc import Sequence
|
|
|
|
import sqlalchemy as sa
|
|
from alembic import op
|
|
|
|
|
|
# revision identifiers, used by Alembic.
|
|
revision: str = 'a1b2c3d4e5f6'
|
|
down_revision: str | Sequence[str] | None = '8c4f1d2a9e6b'
|
|
branch_labels: str | Sequence[str] | None = None
|
|
depends_on: str | Sequence[str] | None = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
"""Upgrade schema."""
|
|
op.create_table('voice_clip',
|
|
sa.Column('voice_clip_id', sa.BigInteger(), autoincrement=False, nullable=False),
|
|
sa.Column('user_id', sa.String(length=64), nullable=False),
|
|
sa.Column('user_real_name', sa.String(length=255), nullable=True),
|
|
sa.Column('wx_id', sa.String(length=128), nullable=True),
|
|
sa.Column('talker', sa.String(length=128), nullable=True),
|
|
sa.Column('status', sa.String(length=32), nullable=True),
|
|
sa.Column('is_send', sa.Boolean(), nullable=True),
|
|
sa.Column('company_code', sa.String(length=64), nullable=True),
|
|
sa.Column('start_time', sa.BigInteger(), nullable=True),
|
|
sa.Column('end_time', sa.BigInteger(), nullable=True),
|
|
sa.Column('oss_file_name', sa.String(length=1024), nullable=True),
|
|
sa.Column('mp3_duration', sa.Integer(), nullable=True),
|
|
sa.Column('duration_format', sa.String(length=32), nullable=True),
|
|
sa.Column('channel', sa.String(length=16), nullable=False),
|
|
sa.Column('raw_json', sa.JSON(), nullable=True),
|
|
sa.Column('fetched_at', sa.DateTime(), server_default=sa.func.now(), nullable=False),
|
|
sa.Column('updated_at', sa.DateTime(), server_default=sa.func.now(), nullable=False),
|
|
sa.PrimaryKeyConstraint('voice_clip_id')
|
|
)
|
|
op.create_index('ix_voice_clip_user_time', 'voice_clip', ['user_id', 'start_time'], unique=False)
|
|
|
|
|
|
def downgrade() -> None:
|
|
"""Downgrade schema."""
|
|
op.drop_index('ix_voice_clip_user_time', table_name='voice_clip')
|
|
op.drop_table('voice_clip')
|