Merge remote-tracking branch 'old_a/master'

Following https://stackoverflow.com/a/14470212
This commit is contained in:
Brandon Thomas committed 2022-03-26 19:26:15 -04:00
commit 7ebb3382d3
1629 files changed
+287734

No files matched your search

+14
View File
@@ -0,0 +1,14 @@
*secret*sh
*~
.env
.env-secrets
.git
.idea/
__pycache__/
_ignored_schema.rs
models/Wav2Lip/python/
models/tacotron2/python/
production_database_secret_connect.sh
secrets.toml
secrets.yaml
target/
+62
View File
@@ -0,0 +1,62 @@
DATABASE_URL=mysql://storyteller:password@localhost/storyteller
#DATABASE_URL=mysql://root:root@localhost/storyteller
#DATABASE_URL=mysql://root@localhost/storyteller
# SQLX normally requires a live database to reflect upon at build time.
# This setting turns that on and requires a `sql-data.json` cache file instead.
# See https://github.com/launchbadge/sqlx/tree/master/sqlx-cli#readme
#SQLX_OFFLINE=true
# We can't serve HTTPS locally without self-signed certificate nonsense.
COOKIE_SECURE=false
COOKIE_DOMAIN=.jungle.horse
WEBSITE_HOMEPAGE_REDIRECT=http://jungle.horse
# Bucket config (non-secret)
REGION_NAME=us-east1
TTS_DOWNLOAD_BUCKET_NAME=dev-vocodes-private-uploads
TTS_DOWNLOAD_BUCKET_ROOT=/user_uploaded_tts_models
W2L_PRIVATE_DOWNLOAD_BUCKET_NAME=dev-vocodes-private-uploads
W2L_PUBLIC_DOWNLOAD_BUCKET_NAME=dev-vocodes-public
PRIVATE_BUCKET_NAME=dev-vocodes-private-uploads
PUBLIC_BUCKET_NAME=dev-vocodes-public
# Bucket roots
AUDIO_UPLOADS_BUCKET_ROOT=/user_uploaded_w2l_audio
W2L_DOWNLOAD_BUCKET_ROOT=/user_uploaded_w2l_templates
W2L_INFERENCE_OUTPUT_BUCKET_ROOT=/w2l_output
W2L_CODE_DIRECTORY=/home/bt/dev/storyteller/storyteller-web/models/Wav2Lip
W2L_INFERENCE_SCRIPT_NAME=vocodes_process_inference.py
W2L_MODEL_FILENAME=wav2lip_gan.pth
#W2L_MODEL_CHECKPOINT=/home/bt/models/wav2lip/s3fd-619a316812.pth
W2L_MODEL_CHECKPOINT=/home/bt/models/wav2lip/wav2lip_gan.pth
W2L_END_BUMP_FILENAME=vocodes-short-end-bump.mp4
W2L_DOWNLOAD_SCRIPT_NAME=vocodes_process_upload.py
TTS_CODE_DIRECTORY=/home/bt/dev/storyteller/storyteller-web/models/tts
TTS_INFERENCE_SCRIPT_NAME=vocodes_inference.py
TACOTRON_ROOT_CODE_DIRECTORY=/home/bt/dev/storyteller/storyteller-web/models/tts
TACOTRON_VIRTUAL_ENV_ACTIVATION_COMMAND="source python-tacotron/bin/activate"
TACOTRON_MODEL_CHECK_SCRIPT_NAME="vocodes_model_check_tacotron.py"
TALKNET_ROOT_CODE_DIRECTORY=/home/bt/dev/storyteller/storyteller-web/models/tts
TALKNET_MODEL_CHECK_SCRIPT_NAME="vocodes_model_check_talknet.py"
TTS_INFERENCE_SIDECAR_HOSTNAME=localhost:8000
#TTS_WAVEGLOW_VOCODER_MODEL_FILENAME=waveglow_256channels_universal_v5.pt # THIS SOUNDS HORRID
#TTS_WAVEGLOW_VOCODER_MODEL_FILENAME=waveglow_256channels_v4_uberduck.pt # THIS IS TOO SMALL! 300MB vs 1.2 GB!
TTS_WAVEGLOW_VOCODER_MODEL_FILENAME=waveglow_256channels_v4.pt
TTS_HIFIGAN_VOCODER_MODEL_FILENAME=hifigan_universal_g_02500000.pt
TTS_HIFIGAN_SUPERRES_VOCODER_MODEL_FILENAME=hifigan_superres_twilight_33000.pt
# Development key. Permissible to check in.
NEWRELIC_API_KEY=c402be1c4c4216ec83d0cfc0851520cfFFFFNRAL
+65
View File
@@ -0,0 +1,65 @@
# Adapted from
# https://github.com/actions/starter-workflows/ci/docker-publish.yml
# If using base images, don't forget to add READ access, eg. from a package settings page like:
# https://github.com/orgs/storytold/packages/container/docker-base-images-rust-ssl/settings/actions_access
name: Publish Docker Container to GHCR
on:
push:
branches:
# NB: Default-branch doesn't work, despite Github's documentation.
#- $default-branch
- master
env:
IMAGE_NAME: storyteller-web
jobs:
# Push image to GitHub Packages.
# See also https://docs.docker.com/docker-hub/builds/
push:
runs-on: ubuntu-latest
if: github.event_name == 'push'
permissions:
packages: write
contents: read
steps:
- uses: actions/checkout@v2
- name: Login to GitHub Container Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
#username: ${{ github.repository_owner }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build image
run: docker build . --file Dockerfile --tag $IMAGE_NAME --label "runnumber=${GITHUB_RUN_ID}"
- name: Push image to GitHub Container Registry
run: |
IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME
# Change all uppercase to lowercase
IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]')
# Strip git ref prefix from version
VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
# Strip "v" prefix from tag name
[[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//')
# Use Docker `latest` tag convention
[ "$VERSION" == "$default-branch" ] && VERSION=latest
echo IMAGE_ID=$IMAGE_ID
echo VERSION=$VERSION
docker tag $IMAGE_NAME $IMAGE_ID:$VERSION
docker push $IMAGE_ID:$VERSION
SHORT_SHA=$(echo ${GITHUB_SHA} | cut -c1-12)
docker tag $IMAGE_NAME $IMAGE_ID:$SHORT_SHA
docker push $IMAGE_ID:$SHORT_SHA
+17
View File
@@ -0,0 +1,17 @@
*.swo
*.swp
*secret*sh
*~
.env
.env-secrets
.idea/
__pycache__/
_ignored_schema.rs
models/Wav2Lip/python/
models/tacotron2/python/
production_database_secret_connect.sh
production_database_secret_query.sh
secrets.toml
secrets.yaml
target/
twitch_secrets.toml
Generated
+4937
View File
File diff suppressed because it is too large. Load diff
+19
View File
@@ -0,0 +1,19 @@
[workspace]
members = [
# Shared libraries
"crates/lib/config",
"crates/lib/container_common",
"crates/lib/database_queries",
"crates/lib/http_server_common",
"crates/lib/redis_common",
"crates/lib/tts_common",
"crates/lib/twitch_common",
"crates/lib/user_input_common",
# Application
"crates/service/storyteller_web",
"crates/service/twitch_pubsub_subscriber",
"crates/service/websocket_gateway",
]
+318
View File
@@ -0,0 +1,318 @@
# ==================== Python Build Base ====================
FROM ghcr.io/storytold/docker-base-images-nvidia-cuda-experimental:080f96bc7087 as pybuild-base
## Authenticating with GHCR locally:
## https://docs.github.com/en/packages/working-with-a-github-packages-registry/migrating-to-the-container-registry-from-the-docker-registry
## ```
## export CR_PAT=YOUR_TOKEN
## echo $CR_PAT | docker login ghcr.io -u USERNAME --password-stdin
## ```
#
##FROM nvidia/cuda:10.1-devel-ubuntu18.04 as pybuild-base
#FROM nvidia/cuda:11.3.0-devel-ubuntu20.04 as pybuild-base
#
## tzdata install hangs without presetting the time zone
#ENV TZ=UTC
#RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
#
## NB(2021-05-30): This is the image that Tacotron 2's Dockerfile specifies.
## I have not tried this, but it might be necessary.
##FROM pytorch/pytorch:nightly-devel-cuda10.0-cudnn7
##ENV PATH /usr/local/nvidia/bin:/usr/local/cuda/bin:${PATH}
#
## NB: https://github.com/NVIDIA/nvidia-docker/issues/864#issuecomment-439848887
## NB: We do not install ffmpeg, since the version is 3.8.* series and we need 4.2.*
##
## NB(2021-05-30): We need to install `cython3` for building modern Tacotron 2
## dependencies (ie. protoc) on old Ubuntu
## RUN apt-get update \
## && apt-get install -y \
#RUN apt-get update
##RUN apt-cache search python3.8
#
## NB: Here we install nvidia drivers
## NB: Avoid keyboard prompt
#RUN DEBIAN_FRONTEND=noninteractive apt-get install -y nvidia-384 --no-install-recommends
#
## ffmpeg version 4.2.4-1ubuntu0.1 Copyright (c) 2000-2020 the FFmpeg developers
##RUN apt-get install -y ffmpeg
##RUN ffmpeg -version
##RUN sleep 100
#
## Notes on packages:
## * software-properties-common - needed for `apt-add-repository`
## * pciutils - needed for `lspci | grep -i nvidia`
#RUN apt-get install -y \
# build-essential \
# curl \
# cython3 \
# ffmpeg \
# g++-7 \
# gcc-7 \
# git \
# htop \
# imagemagick \
# libc++-7-dev \
# libffi-dev \
# libgcc-7-dev \
# libmagic1 \
# libsm6 \
# libsndfile1 \
# libssl-dev \
# libxext6 \
# libxrender-dev \
# netcat \
# pciutils \
# python-dev \
# python3-pip \
# python3.8 \
# python3.8-dev \
# python3.8-venv\
# redis-tools \
# silversearcher-ag \
# software-properties-common \
# sox \
# tmux \
# vim \
# wget
#
#
## Wav2Lip needs Python3.6
## NB: we install python3-virtualenv as `python3.6 -m venv` won't work anymore
#RUN add-apt-repository ppa:deadsnakes/ppa
#RUN apt-get update
#RUN apt-get install -y python3.6
#RUN apt-get install -y python3-virtualenv
#RUN apt-get install -y python3.6-venv
#RUN apt-get install -y python3.6-dev
##RUN apt-get install python3-virtualenv
#
#
#RUN python3.6 --version
#RUN python3.8 --version
# We need ffmpeg version >= 4.2 (https://superuser.com/a/579110)
# http://ubuntuhandbook.org/index.php/2020/06/install-ffmpeg-4-3-via-ppa-ubuntu-18-04-16-04/
#RUN apt-get install -y software-properties-common \
# && add-apt-repository ppa:jonathonf/ffmpeg-4 \
# && apt-get update \
# && apt-get install -y ffmpeg
#
# The downloader needs youtube-dl as a python library
# NB: No longer maintained: https://news.ycombinator.com/item?id=28319624
#RUN pip3 install --upgrade youtube-dl
# Used by the downloader script.
# And it looks like youtube-dl is dead...
# Per the README, it looks like pip will always pull the latest version.
# https://github.com/yt-dlp/yt-dlp#update
RUN python3 -m pip install --upgrade yt-dlp
# ==================== Python Build Step 2: Wav2Lip Requirements ====================
FROM pybuild-base as pybuild-requirements
WORKDIR /
COPY models/Wav2Lip ./models/Wav2Lip
WORKDIR models/Wav2Lip
# NB: We need Python3.6
RUN python3.6 -m venv python
#RUN virtualenv -p /usr/bin/python3.6 ... wait
RUN . python/bin/activate \
&& pip install --upgrade pip \
&& pip install -r requirements.txt \
&& deactivate
WORKDIR /
COPY models/tts ./models/tts
WORKDIR models/tts
# NB: Not sure if we need Python3.6 for Tacotron2, but Python3.8 gave me trouble.
# NB: Setuptools fix: https://github.com/tensorflow/tensorflow/issues/34302#issuecomment-554450289
RUN python3.6 -m venv python
RUN . python/bin/activate \
&& pip install --upgrade pip setuptools \
&& pip install -r requirements-tacotron-python36.txt \
&& deactivate
# ==================== Rust Build Base ====================
# Custom base image
# Make sure to add this repository so it has read acces to the base image:
# https://github.com/orgs/storytold/packages/container/docker-base-images-rust-ssl/settings/actions_access
FROM ghcr.io/storytold/docker-base-images-rust-ssl:latest as rust-build
WORKDIR /tmp
COPY Cargo.lock .
COPY Cargo.toml .
COPY sqlx-data.json .
COPY crates/ ./crates
COPY db/ ./db
# TODO(2022-01-16): Might not be necessary:
# COPY _migrations/ ./_migrations
RUN $HOME/.cargo/bin/cargo fetch
# Build all the binaries.
RUN SQLX_OFFLINE=true \
LD_LIBRARY_PATH=/usr/lib:${LD_LIBRARY_PATH} \
$HOME/.cargo/bin/cargo build \
--release \
--bin storyteller-web
RUN SQLX_OFFLINE=true \
LD_LIBRARY_PATH=/usr/lib:${LD_LIBRARY_PATH} \
$HOME/.cargo/bin/cargo build \
--release \
--bin tts-download-job
RUN SQLX_OFFLINE=true \
LD_LIBRARY_PATH=/usr/lib:${LD_LIBRARY_PATH} \
$HOME/.cargo/bin/cargo build \
--release \
--bin w2l-download-job
RUN SQLX_OFFLINE=true \
LD_LIBRARY_PATH=/usr/lib:${LD_LIBRARY_PATH} \
$HOME/.cargo/bin/cargo build \
--release \
--bin tts-inference-job
RUN SQLX_OFFLINE=true \
LD_LIBRARY_PATH=/usr/lib:${LD_LIBRARY_PATH} \
$HOME/.cargo/bin/cargo build \
--release \
--bin w2l-inference-job
RUN SQLX_OFFLINE=true \
LD_LIBRARY_PATH=/usr/lib:${LD_LIBRARY_PATH} \
$HOME/.cargo/bin/cargo build \
--release \
--bin websocket-gateway
RUN SQLX_OFFLINE=true \
LD_LIBRARY_PATH=/usr/lib:${LD_LIBRARY_PATH} \
$HOME/.cargo/bin/cargo build \
--release \
--bin twitch-pubsub-subscriber
# Final image
# FROM ubuntu:xenial
FROM pybuild-requirements as final
#RUN mkdir /storyteller
#WORKDIR /storyteller
WORKDIR /
COPY scripts/ ./scripts
# See: https://github.com/opencontainers/image-spec/blob/master/annotations.md
LABEL org.opencontainers.image.authors='bt@brand.io, echelon@gmail.com'
LABEL org.opencontainers.image.url='https://github.com/storytold/storyteller-web'
LABEL org.opencontainers.image.documentation='https://github.com/storytold/storyteller-web'
LABEL org.opencontainers.image.source='https://github.com/storytold/storyteller-web'
# NB: Comment this out for non-debug images
# TODO: Figure out how this is done elsewhere with just the static binaries
# Others: `mysql-client`
RUN apt-get update \
&& apt-get install -y \
curl \
python3-pip \
python3 \
wget
RUN python3 --version
#RUN python3.7 -m venv python
#RUN . python/bin/activate \
# && pip install --upgrade pip \
# && pip install -r requirements.txt
# TODO: Use venv to do this instead.
RUN pip3 install gdown
# Copy all the binaries.
COPY --from=rust-build /tmp/target/release/storyteller-web /
COPY --from=rust-build /tmp/target/release/tts-download-job /
COPY --from=rust-build /tmp/target/release/tts-inference-job /
COPY --from=rust-build /tmp/target/release/w2l-download-job /
COPY --from=rust-build /tmp/target/release/w2l-inference-job /
COPY --from=rust-build /tmp/target/release/websocket-gateway /
COPY --from=rust-build /tmp/target/release/twitch-pubsub-subscriber /
# SSL certs are required for crypto
COPY --from=rust-build /etc/ssl /etc/ssl
# Required dynamically linked libraries
COPY --from=rust-build /lib/x86_64-linux-gnu/libssl.* /lib/x86_64-linux-gnu/
COPY --from=rust-build /lib/x86_64-linux-gnu/libcrypto.* /lib/x86_64-linux-gnu/
# Make sure all the links resolve
RUN ldd storyteller-web
# Without a .env file, Rust crashes "mysteriously" (ugh)
RUN touch .env
RUN touch .env-secrets
EXPOSE 8080
CMD LD_LIBRARY_PATH=/usr/lib /storyteller-web
# ===================================================================================================
# WAV2LIP WORKER
# ==================== Rust Build Base ====================
# #FROM ubuntu:xenial as rustbuild-base
# WORKDIR /tmp
# RUN apt-get update \
# && apt-get install -y \
# build-essential \
# curl \
# libssl-dev \
# libssl1.0.0 \
# pkg-config \
# unzip \
# wget
# RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
# | sh -s -- --default-toolchain stable -y
# ==================== Rust Build Binary ====================
# FROM rustbuild-base as rustbuild-binary
#
# COPY Cargo.lock .
# COPY Cargo.toml .
# COPY w2l_server ./w2l_server
# COPY w2l_shared ./w2l_shared
# COPY w2l_worker ./w2l_worker
#
# RUN $HOME/.cargo/bin/cargo fetch
# RUN LD_LIBRARY_PATH=/usr/lib:${LD_LIBRARY_PATH} $HOME/.cargo/bin/cargo build --release --bin wav2lip_worker
# ==================== Final Image ====================
# FROM pybuild-requirements as final
# WORKDIR /
#
# COPY --from=rustbuild-binary /tmp/target/release/wav2lip_worker /
# COPY --from=rustbuild-binary /etc/ssl /etc/ssl
# COPY --from=rustbuild-binary /lib/x86_64-linux-gnu/libssl.* /lib/x86_64-linux-gnu/
# COPY --from=rustbuild-binary /lib/x86_64-linux-gnu/libcrypto.* /lib/x86_64-linux-gnu/
#
# # Without a .env file, Rust crashes "mysteriously" (ugh)
# RUN touch .env
#
# RUN ldd wav2lip_worker
#
# EXPOSE 8080
# CMD LD_LIBRARY_PATH=/usr/lib /wav2lip_worker
+75
View File
@@ -0,0 +1,75 @@
storyteller-web
===============
This is the main user account monolith that we'll also bake other pieces into.
Application overview
--------------------
* storyteller_web
* `storyteller-web` - HTTP + database monolith for FakeYou
* `tts-download-job` - Async download of models
* `tts-inference-job` - Async TTS inference
* `w2l-download-job` - Async download of videos and images
* `w2l-inference-job` - Async W2L inference
* obs_gateway
* `obs-gateway-server` - websocket for hosting OBS, twitch pieces (move!)
* `twitch-pubsub-job` - Subscribes to Twitch PubSub
* `twitch-chat-job` (*TODO*) - Subscribes to Twitch chat
* `reddit-chat-job` (*TODO*) - Subscribes to Reddit RPAN chat
* social
* `discord-chat-job` (*TODO*) - Subscribes to Reddit RPAN chat
* `twitter-feed-job` (*TODO*) - Subscribes to Reddit RPAN chat
Schema and API notes
--------------------
* [Redis Schema Notes README](_docs/redis_schema.md)
Local development
-----------------
[Local development setup README](_docs/local_development_setup.md)
Production and Deployment
-------------------------
[Production builds and deployment README](_docs/production_builds_and_deployment.md)
Development notes
-----------------
[Development notes README](_docs/development_notes.md)
Dump a MySQL report
-------------------
```
mysql -u storyteller \
-pPasswordHere \
-h IP_HERE \
-P 3306 \
-D storyteller \
-B \
-e "select * from voice_clone_requests;" | sed "s/'/\'/;s/\t/\",\"/g;s/^/\"/;s/$/\"/;s/\n//g" > voice_clone_report.csv
```
TODO
----
* Revise this README to be more useful.
Notes / TODOs:
* Examples for good Actix+Sqlx Tests:
https://stackoverflow.com/questions/65370752/how-do-i-create-an-actix-web-server-that-accepts-both-sqlx-database-pools-and-tr
* Actix/sqlx runtime compat:
https://github.com/launchbadge/sqlx/issues/1117#issuecomment-801237734
* Redis caching
* Jobs for analytics queries
* Pull jobs into own repo
+19
View File
@@ -0,0 +1,19 @@
database schema
================
Twitch Model
------------
- `twitch_user_id`
Though numeric, this should always be a string per
[https://discuss.dev.twitch.tv/t/bug-v5-api-returns--id-as-string-for-featured-channels/10310]
and
[https://discuss.dev.twitch.tv/t/type-of-user-id-in-api-responses/10205]
- `twitch_channel_id`
This is the same as `twitch_user_id`. They're exactly 1:1. Prefer to
use 'user_id' terminology.
- `twitch_username`
Between 4 and 25 characters.
+21
View File
@@ -0,0 +1,21 @@
development notes
=================
Actix notes
-----------
json request
```
async fn handler(request: web::Json<Mytype>) -> impl Responder { "whatever" }
```
form-multipart request
```
async fn handler(request: web::Form<Mytype>) -> impl Responder { "whatever" }
```
route parameters
```
#[get("/{name}")]
async fn hello(name: web::Path<String>) -> impl Responder { format!("Hi {}", name) }
```
+207
View File
@@ -0,0 +1,207 @@
dev setup
=========
[Back to main README](../README.md)
### Debugging and Fixing CUDA/PyTorch
See this spreadsheet for previous battles with version incompatibility:
https://docs.google.com/spreadsheets/d/1BEdLmwOzo3r83-iJn9sj6co1VT92t3lIeJ-lNH25bdQ/edit#gid=0
Ubuntu might upgrade the driver by accident, and it might need reinstallation. Who knows.
This stuff is a nightmare.
### Database Setup
Install the following libraries, and see the notes further below about MySQL on Ubuntu 20.04.
```
mysql-server
imagemagick
```
To manage the database and perform migrations, install the Rust tools diesel and sqlx.
We'll be using diesel to manage the migrations, but sqlx within the app to actually perform queries.
Diesel is an ORM, which is dumb, so we use sqlx as at-compile-time typesafe SQL.
```
sudo apt-get install libmysqlclient-dev
cargo install sqlx-cli --no-default-features --features mysql [2022-01-16: is this needed?]
```
To install a more modern version of Diesel (unreleased) that
supports the "migrations_dir" toml config option:
```
cargo install diesel_cli \
--git https://github.com/diesel-rs/diesel.git \
--rev a213fe232a122f35a812b0ce0269708a1845a4c9 \
--no-default-features \
--features mysql
```
#### Sqlx Error on Linux when Performing Schema Migrations
You might get this error message during migration,
```
Encountered unknown type for Mysql: enum
thread 'main' panicked at 'internal error: entered unreachable code: Mysql only supports a closed set of types.
If you ever see this error message please open an issue at https://github.com/diesel-rs/diesel containing a dump of your schema definition.', diesel_cli/src/print_schema.rs:310:17
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```
I haven't found the cause (it doesn't happen on Mac), but the migrations appear to work regardless of the error message.
#### Linux database notes
If MySql in local dev can't be connected to, reset the accounts:
https://linuxconfig.org/how-to-reset-root-mysql-mariadb-password-on-ubuntu-20-04-focal-fossa-linux
#### Fixing dev MySql on Ubuntu 20.04
For some reason, the MySql default install on 20.04 gave me a bunch of trouble.
In retrospect, I _think_ this is because 'root@localhost' requires sudo to access, but if this
gives any trouble in the future, here's how I got around it (two full hours of distraction!)
```
sudo apt-get install mysql-server
# But for some reason the default password doesn't work and diesel/sqlx can't connect?
# Kill everything
sudo apt purge mysql-server mysql-client mysql-common
sudo apt autoremove
sudo mv -iv /var/lib/mysql /var/tmp/mysql-backup
sudo rm -rf /var/lib/mysql*
sudo /usr/bin/mysql_secure_installation
# still no work...
sudo systemctl stop mysql.service
# ugh, it wasn't chowned or created (both states observed in different installs)…
sudo mkdir -p /var/run/mysqld
sudo chown mysql:mysql /var/run/mysqld
sudo mysqld_safe --skip-grant-tables --skip-networking &
mysql -u root
flush privileges;
USE mysql;
ALTER USER 'root'@'localhost' IDENTIFIED BY 'root';
sudo killall -u mysql
sudo systemctl restart mysql.service
# now it works
sudo mysql -u root -p
# but now I have to use "sudo" !?!?
use mysql;
CREATE USER 'storyteller'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON storyteller.* TO 'storyteller'@'localhost';
```
### Install Diesel (migrations only)
We use Diesel to manage migrations, but we don't use it in server code.
Actual server code uses SQLx. To install the CLI tool, run the following:
```
cargo install diesel_cli
```
Mac [has issues](https://github.com/diesel-rs/diesel/issues/2605) and requires a few dependencies:
```
brew install libpq
```
### Database migrations
To reset the entire database (drop, migrate), run:
```
diesel database reset
```
To migrate at the current step and beyond:
```
diesel migration run
```
To undo migrations at the current step:
```
diesel migration redo
```
### Server Query Codegen
We use SQLx instead of Diesel in the production server. It's typesafe
SQL instead of an ORM like Diesel.
SQLx connects to a database to derive type information, but obviously
cannot do this for builds in CI. In order to cache the types, we build
and check in a cache file (necessary for builds):
```
SQLX_OFFLINE=true cargo sqlx prepare
```
Now that we have multiple binaries, it's required to include all the queries in the main
binary so we can generate the cached queries as a single target. That's then executed
with:
```
SQLX_OFFLINE=true cargo sqlx prepare -- --bin storyteller-web
```
### Setting Up a Local Nginx Proxy
Set up a local nginx to proxy to the frontend and backend so cookie issues aren't annoying
Configure Nginx per the checked in Nginx configs (and instructions) in `localdev/nginx-http-config`.
```
And in /etc/hosts,
```
127.0.0.1 jungle.horse
127.0.0.1 api.jungle.horse
```
### Python 3.6 on Apple M1 Mac
Python3.6 isn't supported on Apple silicon, and it's not in homebrew. It can be installed with
[nix using Rosetta](https://stackoverflow.com/a/65980989):
Download: https://nixos.org/download.html#nix-quick-install
```
nix run nixpkgs.python36 -c python
```
Install venv:
```
nix run nixpkgs.python36 -c python -m venv python
```
Install other packages on Mac that aren't used in venv:
```
python3 -m pip install --user requests gdown youtube_dl
```
+36
View File
@@ -0,0 +1,36 @@
production builds and deployment
================================
### Github Docker builds
The repository needs to be given read access to the base docker image:
https://github.com/orgs/storytold/packages/container/docker-base-images-rust-ssl/settings
These instructions assume running on GCP.
### Database migrations
1. Set `DATABASE_URL` in `.env` to the production secrets (DO NOT COMMIT!)
2. Run `diesel migration run`
### Setting up public buckets without list permission
Public buckets that deny the `list` action should use the following Role:
`roles/storage.legacyObjectReader`
See:
* https://stackoverflow.com/a/56354633
* https://cloud.google.com/storage/docs/access-control/making-data-public#buckets
### Generating Bucket Access Key and Secret Key
https://cloud.telestream.net/tutorials/how-to-setting-up-google-cloud-storage/
1. Go to the GCS page
2. Click "settings"
3. Click "interoperability" tab
4. (enable interoperable access if not already set)
5. Click "create new key"
+27
View File
@@ -0,0 +1,27 @@
redis schema
============
Database 0: TTS / RateLimiter
-----------------------------
TODO: Document rate limiter
Public statistics on usage displayed on the website:
* `ttsUseCount:{model_token}`: INCR, GET
* `w2lUseCount:{template_token}`: INCR, GET
Temporary job progress reports to the user:
These are written as a single string, such as "downloading model", "uploading model",
"generating preview", "executing", etc.
* `ttsDownloadExtraStatus:{inference_job_token}`: SETEX, GET
* `ttsInferenceExtraStatus:{inference_job_token}`: SETEX, GET
* `w2lDownloadExtraStatus:{inference_job_token}`: SETEX, GET
* `w2lInferenceExtraStatus:{inference_job_token}`: SETEX, GET
Database 1: Obs / Twitch
------------------------
* WIP
View File
Whitespace-only changes.
@@ -0,0 +1,7 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
drop table user_roles;
drop table user_sessions;
drop table users;
+235
View File
@@ -0,0 +1,235 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
CREATE TABLE users (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Visible "primary key"
token VARCHAR(32) NOT NULL,
-- Username is a lookup key; display_name allows the user to add custom case.
username VARCHAR(20) NOT NULL,
display_name VARCHAR(20) NOT NULL,
email_address VARCHAR(255) NOT NULL,
email_confirmed BOOLEAN NOT NULL DEFAULT false,
-- The role assigned to the user confers permissions.
user_role_slug VARCHAR(16) NOT NULL,
-- ========== CREDENTIALS ==========
-- Bcrypt password hash. Granted, there are newer methods:
-- https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
-- Passwords may be a max of 64 characters per BCrypt.
password_hash BINARY(60) NOT NULL,
-- Incremented with every update to the password.
password_version INT NOT NULL DEFAULT 0,
-- ========== ABUSE TRACKING ==========
-- For abuse tracking.
-- Wide enough for IPv4/6
ip_address_creation VARCHAR(40) NOT NULL,
ip_address_last_login VARCHAR(40) NOT NULL,
ip_address_last_update VARCHAR(40) NOT NULL,
-- ========== DISPLAY / PROFILE ==========
-- Gravatar image hashes are precomputed.
email_gravatar_hash CHAR(32) NOT NULL,
-- The profile of the user in markdown (user editable).
profile_markdown TEXT NOT NULL,
-- Generated HTML (not user-editable).
profile_rendered_html TEXT NOT NULL,
-- An uploaded avatar. Public hash in our bucket.
-- avatar_public_bucket_hash CHAR(32) DEFAULT NULL,
-- ========== SOCIAL MEDIA ==========
-- Social media usernames
-- These are not confirmed. We'll need to build an OAuth system to handle that.
discord_username VARCHAR(36) DEFAULT NULL,
twitter_username VARCHAR(36) DEFAULT NULL,
twitch_username VARCHAR(36) DEFAULT NULL,
patreon_username VARCHAR(36) DEFAULT NULL,
github_username VARCHAR(36) DEFAULT NULL,
cashapp_username VARCHAR(36) DEFAULT NULL,
website_url VARCHAR(255) DEFAULT NULL,
-- ========== USER PREFERENCES ==========
-- If the user doesn't want to use gravatar and doesn't have an uploaded avatar.
disable_gravatar BOOLEAN NOT NULL DEFAULT false,
-- (THIS MIGHT NOT BE USED)
-- NB: DO NOT SORT!
-- THIS MUST MATCH THE RESPECTIVE JOBS TABLE.
-- Visibility preference used at TTS inference time (which can be changed on the result itself).
-- Moderators will still see them.
preferred_tts_result_visibility ENUM(
'public',
'hidden',
'private'
) NOT NULL DEFAULT 'public',
-- (THIS MIGHT NOT BE USED)
-- NB: DO NOT SORT!
-- THIS MUST MATCH THE RESPECTIVE JOBS TABLE.
-- Visibility preference used at TTS inference time (which can be changed on the result itself).
-- Moderators will still see them.
preferred_w2l_result_visibility ENUM(
'public',
'hidden',
'private'
) NOT NULL DEFAULT 'public',
-- Auto play preferences
-- Tri-state because we want to potentially change the default behavior.
auto_play_audio_preference BOOLEAN DEFAULT NULL,
auto_play_video_preference BOOLEAN DEFAULT NULL,
-- Favorite TTS voice to use by default
-- maybe_preferred_tts_model_token VARCHAR(32) DEFAULT NULL,
-- Favorite W2L model to use by default
-- maybe_preferred_w2l_template_token VARCHAR(32) DEFAULT NULL,
-- Settings
-- DO NOT REORDER.
-- dark_mode_preference ENUM(
-- 'light-mode',
-- 'dark-mode',
-- 'use-clock'
-- ) NOT NULL DEFAULT 'light-mode',
-- ========== STATS ==========
-- For tracking stats.
-- The "cached" values are updated by a background job.
-- cached_tts_rendered_counter INT(10) NOT NULL DEFAULT 0,
-- cached_w2l_rendered_counter INT(10) NOT NULL DEFAULT 0,
-- ========== MODERATION DETAILS ==========
-- Different than deleted.
-- Users still show up, but can't do anything.
is_banned BOOLEAN NOT NULL DEFAULT false,
-- If a moderator has comments.
maybe_mod_comments VARCHAR(255) DEFAULT NULL,
-- The last moderator that made changes.
maybe_mod_user_token VARCHAR(32) DEFAULT NULL,
-- ========== VECTOR CLOCK ==========
-- Incremented with every update.
version INT NOT NULL DEFAULT 0,
-- ========== TIMESTAMPS ==========
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- If the user is deleted, we set this.
-- This is different than banned. These users won't show up at all.
user_deleted_at TIMESTAMP NULL,
mod_deleted_at TIMESTAMP NULL,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (token),
UNIQUE KEY (username),
UNIQUE KEY (email_address),
KEY fk_user_role_slug (user_role_slug)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
CREATE TABLE user_roles (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Effective "primary key"
slug CHAR(16) NOT NULL,
name VARCHAR(255) NOT NULL,
-- Usage
can_use_tts BOOLEAN NOT NULL DEFAULT FALSE,
can_use_w2l BOOLEAN NOT NULL DEFAULT FALSE,
can_delete_own_tts_results BOOLEAN NOT NULL DEFAULT FALSE,
can_delete_own_w2l_results BOOLEAN NOT NULL DEFAULT FALSE,
can_delete_own_account BOOLEAN NOT NULL DEFAULT FALSE,
-- Contribution
can_upload_tts_models BOOLEAN NOT NULL DEFAULT FALSE,
can_upload_w2l_templates BOOLEAN NOT NULL DEFAULT FALSE,
can_delete_own_tts_models BOOLEAN NOT NULL DEFAULT FALSE,
can_delete_own_w2l_templates BOOLEAN NOT NULL DEFAULT FALSE,
-- Moderation
can_approve_w2l_templates BOOLEAN NOT NULL DEFAULT FALSE,
-- Edit includes "lock_from_use" and "lock_from_modification"
can_edit_other_users_profiles BOOLEAN NOT NULL DEFAULT FALSE,
can_edit_other_users_tts_models BOOLEAN NOT NULL DEFAULT FALSE,
can_edit_other_users_w2l_templates BOOLEAN NOT NULL DEFAULT FALSE,
can_delete_other_users_tts_models BOOLEAN NOT NULL DEFAULT FALSE,
can_delete_other_users_tts_results BOOLEAN NOT NULL DEFAULT FALSE,
can_delete_other_users_w2l_templates BOOLEAN NOT NULL DEFAULT FALSE,
can_delete_other_users_w2l_results BOOLEAN NOT NULL DEFAULT FALSE,
can_ban_users BOOLEAN NOT NULL DEFAULT FALSE,
can_delete_users BOOLEAN NOT NULL DEFAULT FALSE,
-- Incremented with every update.
version INT NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (slug)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
CREATE TABLE user_sessions (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Session entropy
token VARCHAR(32) NOT NULL,
-- Foreign key to user
user_token VARCHAR(32) NOT NULL,
-- Track each session's creation IP.
-- Wide enough for IPv4/6
ip_address_creation VARCHAR(40) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- Session termination time.
-- This must be set by the server code, or the session is invalid
expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- deletion = session termination
-- Typically these are destroyed by users, but if in the future we allow mods to
-- delete them, it doesn't really matter who did the deletion: sessions are not
-- designed to be recoverable.
deleted_at TIMESTAMP NULL,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (token),
KEY fk_user_token (user_token),
KEY index_ip_address_creation (ip_address_creation)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,5 @@
# noinspection SqlNoDataSourceInspectionForFile
# noinspection SqlResolveForFile
drop table tts_model_upload_jobs;
drop table w2l_template_upload_jobs;
@@ -0,0 +1,231 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- NB: See the "squashed" copy of this for better documentation
-- and the current state of the table.
-- TTS MODELS
CREATE TABLE tts_model_upload_jobs (
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Effective "primary key" (PUBLIC)
-- This is so the in-progress results can be looked up by the UI.
token VARCHAR(32) NOT NULL,
-- Idempotency token
-- This is so the frontend client doesn't submit duplicate jobs.
uuid_idempotency_token VARCHAR(36) NOT NULL,
-- ========== SUCCESS CASE ==========
-- If the job completes successfully, this is the result token.
-- This is only populated on a successful result.
on_success_result_token VARCHAR(32) DEFAULT NULL,
-- ========== UPLOAD DETAILS ==========
-- The name of the voice
-- The "name" of the voice model, which might be complicated.
-- If maybe_subject_token (etc.) is set, then it's authoritative instead.
title VARCHAR(255) NOT NULL,
-- NB: THIS ENUM IS OUT OF DATE!
-- Check the squashed sql file as an updated reference!
-- NB: DO NOT CHANGE ORDER; APPEND ONLY!
-- THIS MUST MATCH THE RESPECTIVE JOBS TABLE.
tts_model_type ENUM(
'not-set',
'tacotron2',
'glowtts',
'glowtts-vocodes'
) NOT NULL DEFAULT 'not-set',
-- Can be linked to a well-known voice
-- maybe_subject_token VARCHAR(32) DEFAULT NULL,
-- maybe_actor_subject_token VARCHAR(32) DEFAULT NULL,
-- If we need to download the file from Google Drive.
download_url VARCHAR(512) DEFAULT NULL,
-- NB: DO NOT SORT!
-- download_url_type ENUM(
-- 'google-drive',
-- 'web'
-- ) NOT NULL DEFAULT 'web',
-- ========== CREATOR DETAILS AND PREFERENCES ==========
-- Foreign key to user
creator_user_token VARCHAR(32) NOT NULL,
-- For abuse tracking.
-- Wide enough for IPv4/6
creator_ip_address VARCHAR(40) NOT NULL,
-- (THIS MIGHT NOT BE USED)
-- NB: DO NOT SORT!
-- THIS MUST MATCH THE RESPECTIVE JOBS TABLE.
creator_set_visibility ENUM(
'public',
'hidden',
'private'
) NOT NULL DEFAULT 'public',
-- ========== JOB SYSTEM DETAILS ==========
-- Jobs begin as "pending", then transition to other states.
--
-- * Pending = job is ready to go
-- * Started = job is running
-- * Complete_Success = job is done (success)
-- * Complete_Failure = job is done (failure)
-- * Attempt_Failed = job failed but may retry.
-- * Dead = job failed permanently.
--
-- Pending -> Started -> Complete_Success
-- |-> Complete_Failure
-- \-> Attempt_Failed -> Started -> { Complete, Failed, Dead }
status ENUM(
'pending',
'started',
'complete_success',
'complete_failure',
'attempt_failed',
'dead') NOT NULL DEFAULT 'pending',
-- We can track this against a "max_attempt_count"
attempt_count INT(3) NOT NULL DEFAULT 0,
-- If there is a failure, tell the user why.
failure_reason VARCHAR(512) DEFAULT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- Failed jobs will set a next attempt time.
-- Subsequent tries can increase the timeout.
-- Failures because of permissions require human intervention => [retry_at=null].
-- Failures because of invalid files are dead => [status=dead].
retry_at TIMESTAMP NULL,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (token),
UNIQUE KEY (uuid_idempotency_token),
KEY fk_on_success_result_token (on_success_result_token),
KEY fk_creator_user_token (creator_user_token),
KEY index_status (status),
KEY index_creator_ip_address (creator_ip_address)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
-- WAV2LIP TEMPLATES
CREATE TABLE w2l_template_upload_jobs (
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Effective "primary key" (PUBLIC)
-- This is so the in-progress results can be looked up by the UI.
token VARCHAR(32) NOT NULL,
-- Idempotency token
-- This is so the frontend client doesn't submit duplicate jobs.
uuid_idempotency_token VARCHAR(36) NOT NULL,
-- ========== SUCCESS CASE ==========
-- If the job completes successfully, this is the result token.
-- This is only populated on a successful result.
on_success_result_token VARCHAR(32) DEFAULT NULL,
-- ========== UPLOAD DETAILS ==========
-- The title of the template
title VARCHAR(255) NOT NULL,
-- NB: DO NOT SORT!
-- THIS MUST MATCH THE RESPECTIVE JOBS TABLE.
template_type ENUM(
'unknown',
'video',
'image'
) NOT NULL DEFAULT 'unknown',
-- Can be linked to a well-known subject
-- maybe_subject_token VARCHAR(32) DEFAULT NULL,
-- maybe_actor_subject_token VARCHAR(32) DEFAULT NULL,
-- If we need to download the file from Google Drive.
download_url VARCHAR(512) DEFAULT NULL,
-- NB: DO NOT SORT!
-- download_url_type ENUM(
-- 'google-drive',
-- 'web'
-- ) NOT NULL DEFAULT 'web',
-- ========== CREATOR DETAILS AND PREFERENCES ==========
-- Foreign key to user
creator_user_token VARCHAR(32) NOT NULL,
-- For abuse tracking.
-- Wide enough for IPv4/6
creator_ip_address VARCHAR(40) NOT NULL,
-- (THIS MIGHT NOT BE USED)
-- NB: DO NOT SORT!
-- THIS MUST MATCH THE RESPECTIVE JOBS TABLE.
creator_set_visibility ENUM(
'public',
'hidden',
'private'
) NOT NULL DEFAULT 'public',
-- ========== JOB SYSTEM DETAILS ==========
-- Jobs begin as "pending", then transition to other states.
--
-- * Pending = job is ready to go
-- * Started = job is running
-- * Complete_Success = job is done (success)
-- * Complete_Failure = job is done (failure)
-- * Attempt_Failed = job failed but may retry.
-- * Dead = job failed permanently.
--
-- Pending -> Started -> Complete_Success
-- |-> Complete_Failure
-- \-> Attempt_Failed -> Started -> { Complete, Failed, Dead }
status ENUM(
'pending',
'started',
'complete_success',
'complete_failure',
'attempt_failed',
'dead') NOT NULL DEFAULT 'pending',
-- We can track this against a "max_attempt_count"
attempt_count INT(3) NOT NULL DEFAULT 0,
-- If there is a failure, tell the user why.
failure_reason VARCHAR(512) DEFAULT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- Failed jobs will set a next attempt time.
-- Subsequent tries can increase the timeout.
-- Failures because of permissions require human intervention => [retry_at=null].
-- Failures because of invalid files are dead => [status=dead].
retry_at TIMESTAMP NULL,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (token),
UNIQUE KEY (uuid_idempotency_token),
KEY fk_on_success_result_token (on_success_result_token),
KEY fk_creator_user_token (creator_user_token),
KEY index_status (status),
KEY index_creator_ip_address (creator_ip_address)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,5 @@
# noinspection SqlNoDataSourceInspectionForFile
# noinspection SqlResolveForFile
drop table tts_models;
drop table w2l_templates;
+328
View File
@@ -0,0 +1,328 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- NB: See the "squashed" copy of this for better documentation
-- and the current state of the table.
CREATE TABLE tts_models (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Effective "primary key" (PUBLIC)
token VARCHAR(32) NOT NULL,
-- NB: THIS ENUM IS OUT OF DATE!
-- Check the squashed sql file as an updated reference!
-- NB: DO NOT CHANGE ORDER; APPEND ONLY!
-- THIS MUST MATCH THE RESPECTIVE JOBS TABLE.
tts_model_type ENUM(
'not-set',
'tacotron2',
'glowtts',
'glowtts-vocodes'
) NOT NULL DEFAULT 'not-set',
-- NB: DO NOT CHANGE ORDER; APPEND ONLY!
-- How text should be handled.
text_preprocessing_algorithm ENUM(
'basic',
'arpabet'
) NOT NULL DEFAULT 'basic',
-- NB: ADDED BY ALTER
-- maybe_default_pretrained_vocoder VARCHAR(64) DEFAULT NULL,
-- Optional Pointer to a newer version of the voice
-- If there's a newer version, we can disable this one.
-- maybe_updated_model_token VARCHAR(32) DEFAULT NULL,
-- We can set as a combination of ['username' + 'voice-name']
-- There can be public aliases for voices, eg. a voice's default model.
-- A user can change this.
-- As such, these should not be foreign keys.
-- updatable_slug VARCHAR(64) NOT NULL,
-- Can be linked to a well-known voice
-- maybe_subject_token VARCHAR(32) DEFAULT NULL,
-- maybe_actor_subject_token VARCHAR(32) DEFAULT NULL,
-- ========== UNSTRUCTURED METADATA ==========
-- The "name" of the voice model, which might be complicated.
-- If maybe_subject_token (etc.) is set, then it's authoritative instead.
title VARCHAR(255) NOT NULL,
-- The description of the model in markdown.
description_markdown TEXT NOT NULL,
-- Generated HTML (not user-editable).
description_rendered_html TEXT NOT NULL,
-- eg. Bill Clinton, Batman, or Goku.
voice_name VARCHAR(255) DEFAULT NULL,
-- Optional; eg. Sean Schemmel
actor_name VARCHAR(255) DEFAULT NULL,
-- Optional; eg. if the voice is "happy" or a singer "a-capella", etc.
characteristic VARCHAR(255) DEFAULT NULL,
-- Optional; eg. if you train the model more than once.
version_string VARCHAR(255) DEFAULT NULL,
-- ========== IMMUTABLE PROVENANCE AND METADATA ==========
-- Where the file was originally downloaded (if it was downloaded)
original_download_url VARCHAR(512) DEFAULT NULL,
-- The filename that was used at upload time (if available)
original_filename VARCHAR(255) DEFAULT NULL,
-- File characteristics
file_size_bytes INT(10) NOT NULL DEFAULT 0,
-- ========== CREATOR DETAILS ==========
-- The person that created the template.
creator_user_token VARCHAR(32) NOT NULL,
-- For abuse tracking.
-- Wide enough for IPv4/6
creator_ip_address_creation VARCHAR(40) NOT NULL,
creator_ip_address_last_update VARCHAR(40) NOT NULL,
-- (THIS MIGHT NOT BE USED)
-- NB: DO NOT SORT!
-- THIS MUST MATCH THE RESPECTIVE JOBS TABLE.
creator_set_visibility ENUM(
'public',
'hidden',
'private'
) NOT NULL DEFAULT 'public',
-- ========== BUCKET STORAGE ==========
-- The pytorch model
-- For now, this will be a hash of the file contents.
-- NB: NOT UNIQUE! We can allow duplicate uploads.
private_bucket_hash CHAR(64) NOT NULL,
-- The "full url" version of the path
private_bucket_object_name VARCHAR(255) NOT NULL,
-- ========== RATING AND RANKING ==========
-- Calculated average, on a scale of 0-100
-- Null with zero ratings.
-- calculated_average_score INT(3) DEFAULT NULL,
-- Other metrics
-- calculated_total_ratings_submitted_count INT(10) NOT NULL DEFAULT 0,
-- calculated_total_uses_count BIGINT(10) NOT NULL DEFAULT 0,
-- ========== MODERATION DETAILS ==========
-- Mods have to approve of tts models for them to show up in a public index.
-- Models can be used by the author (or other parties that know the URL) before
-- they're approved, but unapproved models won't show up in public indices.
-- This is independent of other visibility controls (is_mod_disabled, deleted_at,
-- and creator_set_visibility)
is_public_listing_approved BOOLEAN DEFAULT NULL,
-- Extremely popular models may be locked from deletion or modification by users.
is_locked_from_user_modification BOOLEAN NOT NULL DEFAULT FALSE,
-- In this case, a moderator disables it from being used for inference.
-- This also disables it for the creator.
-- Unlike deletion, it remains "visible" to those that have access.
is_locked_from_use BOOLEAN NOT NULL DEFAULT FALSE,
-- If a moderator has comments.
maybe_mod_comments VARCHAR(255) DEFAULT NULL,
-- The last moderator that made changes.
maybe_mod_user_token VARCHAR(32) DEFAULT NULL,
-- ========== VECTOR CLOCK ==========
-- Incremented with every update.
version INT NOT NULL DEFAULT 0,
-- ========== TIMESTAMPS ==========
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- If this is removed by a mod.
-- It completely disappears from the system.
user_deleted_at TIMESTAMP NULL,
mod_deleted_at TIMESTAMP NULL,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (token),
KEY fk_creator_user_token (creator_user_token),
KEY fk_maybe_mod_user_token (maybe_mod_user_token),
KEY index_creator_ip_address_creation (creator_ip_address_creation),
KEY index_creator_ip_address_last_update (creator_ip_address_last_update),
KEY index_creator_set_visibility (creator_set_visibility),
KEY index_private_bucket_hash (private_bucket_hash),
KEY index_is_public_listing_approved (is_public_listing_approved),
KEY index_is_locked_from_user_modification (is_locked_from_user_modification),
KEY index_is_locked_from_use (is_locked_from_use)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
CREATE TABLE w2l_templates (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Effective "primary key" (PUBLIC)
token VARCHAR(32) NOT NULL,
-- NB: DO NOT CHANGE ORDER; APPEND ONLY!
-- THIS MUST MATCH THE RESPECTIVE JOBS TABLE.
template_type ENUM(
'not-set',
'image',
'video'
) NOT NULL DEFAULT 'not-set',
-- Optional Pointer to a newer version of the template
-- If there's a newer version, we can disable this one.
-- maybe_updated_template_token VARCHAR(32) DEFAULT NULL,
-- A combination of ['username' + 'template-name']
-- There can be public aliases for voices, eg. a voice's default model.
-- A user can change this.
-- As such, these should not be foreign keys.
-- updatable_slug VARCHAR(64) NOT NULL,
-- Can be linked to a well-known voice
-- maybe_subject_token VARCHAR(32) DEFAULT NULL,
-- maybe_actor_subject_token VARCHAR(32) DEFAULT NULL,
-- ========== UNSTRUCTURED METADATA ==========
-- The title of the template.
title CHAR(255) NOT NULL,
-- The description of the template in markdown.
description_markdown TEXT NOT NULL,
-- Generated HTML (not user-editable).
description_rendered_html TEXT NOT NULL,
-- ========== IMMUTABLE PROVENANCE AND METADATA ==========
-- Where the file was originally downloaded (if it was downloaded)
original_download_url VARCHAR(512) DEFAULT NULL,
-- The filename that was used at upload time (if available)
original_filename CHAR(255) DEFAULT NULL,
-- Dimensions and other characteristics
file_size_bytes INT(10) NOT NULL DEFAULT 0,
mime_type VARCHAR(32) NOT NULL DEFAULT '',
frame_width INT(10) NOT NULL DEFAULT 0,
frame_height INT(10) NOT NULL DEFAULT 0,
frame_count INT(10) NOT NULL DEFAULT 0,
fps FLOAT NOT NULL DEFAULT 0.0,
duration_millis INT(10) NOT NULL DEFAULT 0,
-- ========== CREATOR DETAILS ==========
-- The person that created the template.
creator_user_token VARCHAR(32) NOT NULL,
-- For abuse tracking.
-- Wide enough for IPv4/6
creator_ip_address_creation VARCHAR(40) NOT NULL,
creator_ip_address_last_update VARCHAR(40) NOT NULL,
-- (THIS MIGHT NOT BE USED)
-- NB: DO NOT SORT!
-- THIS MUST MATCH THE RESPECTIVE JOBS TABLE.
creator_set_visibility ENUM(
'public',
'hidden',
'private'
) NOT NULL DEFAULT 'public',
-- ========== BUCKET STORAGE ==========
-- The hash of the original source image/video
private_bucket_hash CHAR(64) NOT NULL,
-- The "full path" version of the path. Contains the hash.
private_bucket_object_name VARCHAR(255) NOT NULL,
-- The "full path" of the cached faces object. Contains the hash.
private_bucket_cached_faces_object_name VARCHAR(255) NOT NULL,
-- The "full url" of the preview image and/or video. Contains the hash.
maybe_public_bucket_preview_image_object_name VARCHAR(255) DEFAULT NULL,
maybe_public_bucket_preview_video_object_name VARCHAR(255) DEFAULT NULL,
-- ========== RATING AND RANKING ==========
-- Calculated average, on a scale of 0-100
-- Null with zero ratings.
-- calculated_average_score INT(3) DEFAULT NULL,
-- Other metrics
-- calculated_total_ratings_submitted_count INT(10) NOT NULL DEFAULT 0,
-- calculated_total_uses_count BIGINT(10) NOT NULL DEFAULT 0,
-- ========== MODERATION DETAILS ==========
-- Mods have to approve of w2l templates for them to show up in a public index.
-- Templates can be used by the author (or other parties that know the URL) before
-- they're approved, but unapproved templates won't show up in public indices.
-- This is independent of other visibility controls (is_mod_disabled, deleted_at,
-- and creator_set_visibility)
is_public_listing_approved BOOLEAN DEFAULT NULL,
-- Extremely popular models may be locked from deletion or modification by users.
is_locked_from_user_modification BOOLEAN NOT NULL DEFAULT FALSE,
-- In this case, a moderator disables it from being used for inference.
-- This also disables it for the creator.
-- Unlike deletion, it remains "visible" to those that have access.
is_locked_from_use BOOLEAN NOT NULL DEFAULT FALSE,
-- If a moderator has comments.
maybe_mod_comments VARCHAR(255) DEFAULT NULL,
-- The last moderator that made changes.
maybe_mod_user_token VARCHAR(32) DEFAULT NULL,
-- ========== VECTOR CLOCK ==========
-- Incremented with every update.
version INT NOT NULL DEFAULT 0,
-- ========== TIMESTAMPS ==========
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- If this is removed by a mod.
-- It shows up nowhere if deleted.
user_deleted_at TIMESTAMP NULL,
mod_deleted_at TIMESTAMP NULL,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (token),
KEY fk_creator_user_token (creator_user_token),
KEY fk_maybe_mod_user_token (maybe_mod_user_token),
KEY index_template_type (template_type),
KEY index_creator_ip_address_creation (creator_ip_address_creation),
KEY index_creator_ip_address_last_update (creator_ip_address_last_update),
KEY index_creator_set_visibility (creator_set_visibility),
KEY index_private_bucket_hash (private_bucket_hash),
KEY index_is_public_listing_approved (is_public_listing_approved),
KEY index_is_locked_from_user_modification (is_locked_from_user_modification),
KEY index_is_locked_from_use (is_locked_from_use)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,5 @@
# noinspection SqlResolveForFile
# noinspection SqlNoDataSourceInspectionForFile
drop table tts_inference_jobs;
drop table w2l_inference_jobs;
@@ -0,0 +1,229 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- NB: See the "squashed" copy of this for better documentation
-- and the current state of the table.
CREATE TABLE tts_inference_jobs (
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Effective "primary key" (PUBLIC)
-- This is so the in-progress results can be looked up by the UI.
token VARCHAR(32) NOT NULL,
-- Idempotency token from client
-- This is so the frontend client doesn't submit duplicate jobs.
uuid_idempotency_token VARCHAR(36) NOT NULL,
-- ========== SUCCESS CASE ==========
-- If the job completes successfully, this is the result token.
-- This is only populated on a successful result.
on_success_result_token VARCHAR(32) DEFAULT NULL,
-- ========== INFERENCE DETAILS ==========
-- The model to use.
-- This also determines which architecture we're using.
model_token VARCHAR(32) NOT NULL,
-- NB: ADDED BY ALTER
-- maybe_override_pretrained_vocoder VARCHAR(64) DEFAULT NULL,
-- The raw, unprocessed user input.
raw_inference_text TEXT NOT NULL,
-- ========== CREATOR DETAILS ==========
-- Foreign key to user
-- If no user is logged in, this is null.
maybe_creator_user_token VARCHAR(32) DEFAULT NULL,
-- Based on a cookie sent by the frontend.
-- We'll save this even if the user is logged in.
maybe_creator_anonymous_visitor_token VARCHAR(32) DEFAULT NULL,
-- For abuse tracking.
-- Wide enough for IPv4/6
creator_ip_address VARCHAR(40) NOT NULL,
-- (THIS MIGHT NOT BE USED)
-- NB: DO NOT SORT!
-- THIS MUST MATCH THE RESPECTIVE JOBS TABLE.
creator_set_visibility ENUM(
'public',
'hidden',
'private'
) NOT NULL DEFAULT 'public',
-- ========== JOB SYSTEM DETAILS ==========
-- Jobs begin as "pending", then transition to other states.
--
-- * Pending = job is ready to go
-- * Started = job is running
-- * Complete_Success = job is done (success)
-- * Complete_Failure = job is done (failure)
-- * Attempt_Failed = job failed but may retry.
-- * Dead = job failed permanently.
--
-- Pending -> Started -> Complete_Success
-- |-> Complete_Failure
-- \-> Attempt_Failed -> Started -> { Complete, Failed, Dead }
status ENUM(
'pending',
'started',
'complete_success',
'complete_failure',
'attempt_failed',
'dead') NOT NULL DEFAULT 'pending',
-- We can track this against a "max_attempt_count"
attempt_count INT(3) NOT NULL DEFAULT 0,
-- If there is a failure, tell the user why.
failure_reason VARCHAR(512) DEFAULT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- Failed jobs will set a next attempt time.
-- Subsequent tries can increase the timeout.
-- Failures because of permissions require human intervention => [retry_at=null].
-- Failures because of invalid files are dead => [status=dead].
retry_at TIMESTAMP NULL,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (token),
UNIQUE KEY (uuid_idempotency_token),
KEY fk_on_success_result_token (on_success_result_token),
KEY fk_model_token (model_token),
KEY fk_maybe_creator_user_token (maybe_creator_user_token),
KEY index_status (status),
KEY index_creator_ip_address (creator_ip_address)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
CREATE TABLE w2l_inference_jobs (
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Effective "primary key" (PUBLIC)
-- This is so the in-progress results can be looked up by the UI.
token VARCHAR(32) NOT NULL,
-- Idempotency token from client
-- This is so the frontend client doesn't submit duplicate jobs.
uuid_idempotency_token VARCHAR(36) NOT NULL,
-- ========== SUCCESS CASE ==========
-- If the job completes successfully, this is the result token.
-- This is only populated on a successful result.
on_success_result_token VARCHAR(32) DEFAULT NULL,
-- ========== INFERENCE DETAILS : FACE TEMPLATE ==========
-- The W2L template to use
-- Can be an image or video.
-- This is null if we're using a custom uploaded image.
maybe_w2l_template_token VARCHAR(32) DEFAULT NULL,
-- If we're using a custom uploaded image, this will be present.
-- NOTE: This may never be supported.
maybe_public_image_bucket_location VARCHAR(255) DEFAULT NULL,
-- ========== INFERENCE DETAILS : AUDIO SOURCE ==========
-- If we're using TTS results, this will be present
maybe_tts_inference_result_token VARCHAR(32) DEFAULT NULL,
-- If we're using custom uploaded audio, this will be present.
maybe_public_audio_bucket_hash VARCHAR(64) DEFAULT NULL,
maybe_public_audio_bucket_location VARCHAR(255) DEFAULT NULL,
-- The filename that was used at upload time (if available)
maybe_original_audio_filename CHAR(255) DEFAULT NULL,
-- Where the audio file was originally downloaded (if it was downloaded)
maybe_original_audio_download_url VARCHAR(512) DEFAULT NULL,
maybe_audio_mime_type VARCHAR(32) DEFAULT NULL,
-- ========== CREATOR DETAILS AND PREFERENCES ==========
-- Foreign key to user
-- If no user is logged in, this is null.
maybe_creator_user_token VARCHAR(32) DEFAULT NULL,
-- Based on a cookie sent by the frontend.
-- We'll save this even if the user is logged in.
maybe_creator_anonymous_visitor_token VARCHAR(32) DEFAULT NULL,
-- For abuse tracking.
-- Wide enough for IPv4/6
creator_ip_address VARCHAR(40) NOT NULL,
-- (THIS MIGHT NOT BE USED)
-- NB: DO NOT SORT!
-- THIS MUST MATCH THE RESPECTIVE JOBS TABLE.
creator_set_visibility ENUM(
'public',
'hidden',
'private'
) NOT NULL DEFAULT 'public',
-- Flags to disable branding
disable_end_bump BOOL NOT NULL DEFAULT false,
disable_watermark BOOL NOT NULL DEFAULT false,
-- ========== JOB SYSTEM DETAILS ==========
-- Jobs begin as "pending", then transition to other states.
--
-- * Pending = job is ready to go
-- * Started = job is running
-- * Complete_Success = job is done (success)
-- * Complete_Failure = job is done (failure)
-- * Attempt_Failed = job failed but may retry.
-- * Dead = job failed permanently.
--
-- Pending -> Started -> Complete_Success
-- |-> Complete_Failure
-- \-> Attempt_Failed -> Started -> { Complete, Failed, Dead }
status ENUM(
'pending',
'started',
'complete_success',
'complete_failure',
'attempt_failed',
'dead') NOT NULL DEFAULT 'pending',
-- We can track this against a "max_attempt_count"
attempt_count INT(3) NOT NULL DEFAULT 0,
-- If there is a failure, tell the user why.
failure_reason VARCHAR(512) DEFAULT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- Failed jobs will set a next attempt time.
-- Subsequent tries can increase the timeout.
-- Failures because of permissions require human intervention => [retry_at=null].
-- Failures because of invalid files are dead => [status=dead].
retry_at TIMESTAMP NULL,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (token),
UNIQUE KEY (uuid_idempotency_token),
KEY fk_on_success_result_token (on_success_result_token),
KEY fk_maybe_w2l_template_token (maybe_w2l_template_token),
KEY fk_maybe_tts_inference_result_token (maybe_tts_inference_result_token),
KEY fk_maybe_creator_user_token (maybe_creator_user_token),
KEY index_status (status),
KEY index_creator_ip_address (creator_ip_address)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,5 @@
# noinspection SqlResolveForFile
# noinspection SqlNoDataSourceInspectionForFile
drop table tts_results;
drop table w2l_results;
@@ -0,0 +1,184 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- NB: See the "squashed" copy of this for better documentation
-- and the current state of the table.
CREATE TABLE tts_results (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Effective "primary key" (PUBLIC)
token VARCHAR(32) NOT NULL,
-- ========== INFERENCE DETAILS ==========
-- The TTS model that was used
model_token VARCHAR(32) NOT NULL,
-- NB: ADDED BY ALTER
-- maybe_pretrained_vocoder_used VARCHAR(64) DEFAULT NULL,
-- The original raw, unprocessed user input.
raw_inference_text TEXT NOT NULL,
-- SHA2 hash of the text [SHA2 = CHAR(64), SHA1 = CHAR(40), MD5 = CHAR(32)]
raw_inference_text_hash_sha2 CHAR(64) NOT NULL,
-- First pass at text normalization.
-- eg. 14th -> fourteenth, etc.
normalized_inference_text TEXT NOT NULL,
-- If the model uses arpabet, we'll save the arpabet we used.
maybe_arpabet_text TEXT DEFAULT NULL,
-- ========== CREATOR DETAILS ==========
-- The person that created the template.
-- If the user wasn't logged in, this is null
maybe_creator_user_token VARCHAR(32) DEFAULT NULL,
-- Based on a cookie sent by the frontend.
-- We'll save this even if the user is logged in.
maybe_creator_anonymous_visitor_token VARCHAR(32) DEFAULT NULL,
-- For abuse tracking.
-- Wide enough for IPv4/6
creator_ip_address VARCHAR(40) NOT NULL,
-- (THIS MIGHT NOT BE USED)
-- NB: DO NOT SORT!
-- THIS MUST MATCH THE RESPECTIVE JOBS TABLE.
creator_set_visibility ENUM(
'public',
'hidden',
'private'
) NOT NULL DEFAULT 'public',
-- The synthetic id associated with this result.
-- These ids are incremented on a per-user basis to help users
-- sequence their own work. They serve no other purpose.
maybe_creator_synthetic_id BIGINT(20) DEFAULT NULL,
-- ========== BUCKET STORAGE ==========
-- Where the wav, spectrogram, and etc. are located.
public_bucket_wav_audio_path VARCHAR(255) NOT NULL,
public_bucket_spectrogram_path VARCHAR(255) NOT NULL,
-- ========== METADATA ==========
file_size_bytes INT(10) NOT NULL DEFAULT 0,
duration_millis INT(10) NOT NULL DEFAULT 0,
-- ========== MODERATION DETAILS ==========
-- The last moderator that made changes.
maybe_mod_user_token VARCHAR(32) DEFAULT NULL,
-- ========== TIMESTAMPS ==========
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- If this is removed by a mod or the creator.
user_deleted_at TIMESTAMP NULL,
mod_deleted_at TIMESTAMP NULL,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (token),
KEY fk_model_token (model_token),
KEY fk_maybe_creator_user_token (maybe_creator_user_token),
KEY fk_maybe_creator_anonymous_visitor_token (maybe_creator_anonymous_visitor_token),
KEY fk_maybe_mod_user_token (maybe_mod_user_token),
KEY index_creator_ip_address (creator_ip_address),
KEY index_user_deleted_at(user_deleted_at),
KEY index_mod_deleted_at(mod_deleted_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
CREATE TABLE w2l_results (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Effective "primary key" (PUBLIC)
token VARCHAR(32) NOT NULL,
-- ========== INFERENCE DETAILS ==========
-- The w2l template that was used (if set)
maybe_w2l_template_token VARCHAR(32) NOT NULL,
-- The inference result, if we're using them.
maybe_tts_inference_result_token VARCHAR(32) DEFAULT NULL,
-- ========== CREATOR DETAILS ==========
-- The person that created the template.
-- If the user wasn't logged in, this is null
maybe_creator_user_token VARCHAR(32) DEFAULT NULL,
-- Based on a cookie sent by the frontend.
-- We'll save this even if the user is logged in.
maybe_creator_anonymous_visitor_token VARCHAR(32) DEFAULT NULL,
-- For abuse tracking.
-- Wide enough for IPv4/6
creator_ip_address VARCHAR(40) NOT NULL,
-- (THIS MIGHT NOT BE USED)
-- NB: DO NOT SORT!
-- THIS MUST MATCH THE RESPECTIVE JOBS TABLE.
creator_set_visibility ENUM(
'public',
'hidden',
'private'
) NOT NULL DEFAULT 'public',
-- The synthetic id associated with this result.
-- These ids are incremented on a per-user basis to help users
-- sequence their own work. They serve no other purpose.
maybe_creator_synthetic_id BIGINT(20) DEFAULT NULL,
-- ========== BUCKET STORAGE ==========
-- Where the wav, spectrogram, and etc. are located.
public_bucket_video_path VARCHAR(255) NOT NULL,
-- ========== METADATA ==========
file_size_bytes INT(10) NOT NULL DEFAULT 0,
mime_type VARCHAR(32) NOT NULL DEFAULT '',
frame_width INT(10) NOT NULL DEFAULT 0,
frame_height INT(10) NOT NULL DEFAULT 0,
duration_millis INT(10) NOT NULL DEFAULT 0,
-- ========== MODERATION DETAILS ==========
-- The last moderator that made changes.
maybe_mod_user_token VARCHAR(32) DEFAULT NULL,
-- ========== TIMESTAMPS ==========
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- If this is removed by a mod or the creator.
user_deleted_at TIMESTAMP NULL,
mod_deleted_at TIMESTAMP NULL,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (token),
KEY fk_maybe_creator_user_token (maybe_creator_user_token),
KEY fk_maybe_creator_anonymous_visitor_token (maybe_creator_anonymous_visitor_token),
KEY fk_maybe_w2l_template_token (maybe_w2l_template_token),
KEY fk_maybe_tts_inference_result_token (maybe_tts_inference_result_token),
KEY fk_maybe_mod_user_token (maybe_mod_user_token),
KEY index_creator_ip_address (creator_ip_address),
KEY index_user_deleted_at(user_deleted_at),
KEY index_mod_deleted_at(mod_deleted_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,6 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
drop table badges;
drop table user_badges;
@@ -0,0 +1,42 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- Badges are rewards for loyalty
CREATE TABLE badges (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Effective "primary key"
slug VARCHAR(32) NOT NULL,
-- Description
title VARCHAR(255) NOT NULL,
description VARCHAR(512) NOT NULL,
image_url VARCHAR(512) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (slug)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
-- Join table
CREATE TABLE user_badges (
id BIGINT(20) NOT NULL AUTO_INCREMENT,
user_token VARCHAR(32) NOT NULL,
badge_slug VARCHAR(32) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- INDICES --
PRIMARY KEY (id),
KEY fk_user_token (user_token),
KEY fk_badge_slug (badge_slug)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,4 @@
# noinspection SqlResolveForFile
# noinspection SqlNoDataSourceInspectionForFile
drop table firehose_entries;
@@ -0,0 +1,37 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
CREATE TABLE firehose_entries (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Visible "primary key"
token VARCHAR(32) NOT NULL,
-- The type of the event
event_type VARCHAR(32) NOT NULL,
-- The target user
maybe_target_user_token VARCHAR(32) DEFAULT NULL,
-- The target "entity", which varies by event type.
-- eg. a model token or template token
maybe_target_entity_token VARCHAR(32) DEFAULT NULL,
-- If something was created, this might be the token (if we know it)
-- eg. an inference job's token
maybe_created_entity_token VARCHAR(32) DEFAULT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (token),
KEY fk_maybe_target_user_token (maybe_target_user_token),
KEY fk_maybe_target_entity_token (maybe_target_entity_token),
KEY fk_maybe_created_entity_token (maybe_created_entity_token),
KEY index_event_type (event_type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,5 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
drop table ip_address_bans;
@@ -0,0 +1,37 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
CREATE TABLE ip_address_bans (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- The IP address to ban
ip_address VARCHAR(40) NOT NULL,
-- Maybe the target user.
maybe_target_user_token VARCHAR(32) DEFAULT NULL,
-- Mod who created or last edited the ban.
mod_user_token VARCHAR(32) NOT NULL,
-- Optional notes about the ban.
mod_notes TEXT NOT NULL,
-- Moderator can set an expire time (optional).
-- If not set, the ban is indefinite
expires_at TIMESTAMP DEFAULT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- If deleted, the ban is removed.
deleted_at TIMESTAMP DEFAULT NULL,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (ip_address),
KEY fk_mod_user_token (mod_user_token),
KEY fk_maybe_target_user_token (maybe_target_user_token)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,5 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
drop table moderator_audit_log_entries;
@@ -0,0 +1,38 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
CREATE TABLE moderator_audit_log_entries (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Visible "primary key"
token VARCHAR(32) NOT NULL,
-- The type of the event (ban, delete, etc.)
event_type VARCHAR(32) NOT NULL,
-- The target user
maybe_target_user_token VARCHAR(32) DEFAULT NULL,
-- The target "entity", which varies by event type.
-- eg. a model token or template token
maybe_target_entity_token VARCHAR(32) DEFAULT NULL,
-- If an IP was banned, we record it here.
maybe_target_ip_address VARCHAR(40) DEFAULT NULL,
-- Optional mod-specified notes.
optional_notes TEXT DEFAULT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (token),
KEY fk_maybe_target_user_token (maybe_target_user_token),
KEY fk_maybe_target_entity_token (maybe_target_entity_token),
KEY index_event_type (event_type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,6 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
drop table tts_result_synthetic_ids;
drop table w2l_result_synthetic_ids;
@@ -0,0 +1,47 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- It gets unwieldy to show results without a numeric sequence.
-- We create a synthetic sequence for each user to make revisiting results easier.
CREATE TABLE tts_result_synthetic_ids (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Effective "primary key" (PUBLIC)
user_token VARCHAR(32) NOT NULL,
-- The next result id.
-- After we use an id, we increment this counter.
next_id BIGINT(20) NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (user_token)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
-- It gets unwieldy to show results without a numeric sequence.
-- We create a synthetic sequence for each user to make revisiting results easier.
CREATE TABLE w2l_result_synthetic_ids (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Effective "primary key" (PUBLIC)
user_token VARCHAR(32) NOT NULL,
-- The next result id.
-- After we use an id, we increment this counter.
next_id BIGINT(20) NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (user_token)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,12 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE tts_models
DROP COLUMN maybe_default_pretrained_vocoder;
ALTER TABLE tts_inference_jobs
DROP COLUMN maybe_override_pretrained_vocoder;
ALTER TABLE tts_results
DROP COLUMN maybe_pretrained_vocoder_used;
@@ -0,0 +1,21 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- TODO/FIXME: I originally created this with the wrong directory month.
-- I'll have to repair the migrations table.
-- version: 20210925010101 | run_on: 2021-08-25 03:01:15 (development)
-- UPDATE __diesel_schema_migrations SET version=20210825010101 WHERE version=20210925010101 LIMIT 1;
-- NB: Fixed in production on 2022-01-06
ALTER TABLE tts_models
ADD COLUMN maybe_default_pretrained_vocoder VARCHAR(64) DEFAULT NULL
AFTER text_preprocessing_algorithm;
ALTER TABLE tts_inference_jobs
ADD COLUMN maybe_override_pretrained_vocoder VARCHAR(64) DEFAULT NULL
AFTER model_token;
ALTER TABLE tts_results
ADD COLUMN maybe_pretrained_vocoder_used VARCHAR(64) DEFAULT NULL
AFTER model_token;
@@ -0,0 +1 @@
-- This file should undo anything in `up.sql`
@@ -0,0 +1,23 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- Add talknet to the tts_model_type.
ALTER TABLE tts_models
MODIFY COLUMN tts_model_type ENUM(
'not-set',
'tacotron2',
'glowtts',
'glowtts-vocodes',
'talknet'
) NOT NULL DEFAULT 'not-set';
ALTER TABLE tts_model_upload_jobs
MODIFY COLUMN tts_model_type ENUM(
'not-set',
'tacotron2',
'glowtts',
'glowtts-vocodes',
'talknet'
) NOT NULL DEFAULT 'not-set';
@@ -0,0 +1,15 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE tts_models
DROP COLUMN private_bucket_object_is_archive;
ALTER TABLE tts_models
DROP COLUMN has_self_contained_vocoder;
ALTER TABLE tts_models
DROP COLUMN has_self_contained_duration_model;
ALTER TABLE tts_models
DROP COLUMN has_self_contained_pitch_model;
@@ -0,0 +1,19 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE tts_models
ADD COLUMN private_bucket_object_is_archive BOOLEAN NOT NULL DEFAULT FALSE
AFTER private_bucket_object_name;
ALTER TABLE tts_models
ADD COLUMN has_self_contained_vocoder BOOLEAN NOT NULL DEFAULT FALSE
AFTER tts_model_type;
ALTER TABLE tts_models
ADD COLUMN has_self_contained_duration_model BOOLEAN NOT NULL DEFAULT FALSE
AFTER has_self_contained_vocoder;
ALTER TABLE tts_models
ADD COLUMN has_self_contained_pitch_model BOOLEAN NOT NULL DEFAULT FALSE
AFTER has_self_contained_duration_model;
@@ -0,0 +1,7 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
DROP TABLE model_categories;
DROP TABLE tts_category_assignments;
DROP TABLE w2l_category_assignments;
@@ -0,0 +1,174 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
CREATE TABLE model_categories (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Visible "primary key"
token VARCHAR(32) NOT NULL,
-- Idempotency token
-- This is so the frontend client doesn't submit duplicate requests.
-- Only used on create.
uuid_idempotency_token VARCHAR(36) NOT NULL,
-- ========== MODEL TYPE ==========
model_type ENUM(
'not_set',
'tts',
'w2l'
) NOT NULL DEFAULT 'not_set',
-- ========== CATEGORY TOPOLOGY ==========
-- Optional foreign key link to super category.
-- If absent, this is a top-level category or top-level super category.
maybe_super_category_token VARCHAR(32) DEFAULT NULL,
-- If this category can be applied directly to models.
can_directly_have_models BOOLEAN NOT NULL DEFAULT false,
-- If this category can have subcategories.
can_have_subcategories BOOLEAN NOT NULL DEFAULT false,
-- If only mods are allowed to tag (or untag) models with this.
can_only_mods_apply BOOLEAN NOT NULL DEFAULT false,
-- ========== CATEGORY NAME ==========
-- The name of the category, which might be complicated.
name VARCHAR(255) NOT NULL,
-- The name displayed in the dropdown (if it should be different), otherwise NULL.
-- eg. "Gender" (name) and "By Gender" (dropdown name)
maybe_dropdown_name VARCHAR(20) DEFAULT NULL,
-- ========== CREATOR DETAILS ==========
-- The person that created the category.
creator_user_token VARCHAR(32) NOT NULL,
-- For abuse tracking.
-- Wide enough for IPv4/6
creator_ip_address_creation VARCHAR(40) NOT NULL,
-- Technically we don't allow editing, but just in case.
creator_ip_address_last_update VARCHAR(40) NOT NULL,
-- ========== MODERATION DETAILS ==========
-- Mods have to approve of categories. They remain hidden and unusable until approved.
is_mod_approved BOOLEAN DEFAULT NULL,
-- If a moderator has comments.
maybe_mod_comments VARCHAR(255) DEFAULT NULL,
-- The last moderator that made changes.
maybe_mod_user_token VARCHAR(32) DEFAULT NULL,
-- ========== VECTOR CLOCK ==========
-- Incremented with every update.
version INT NOT NULL DEFAULT 0,
-- ========== TIMESTAMPS ==========
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- If the category is deleted and shouldn't be displayed.
-- Only mods can delete categories.
deleted_at TIMESTAMP NULL,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (token),
UNIQUE KEY (uuid_idempotency_token),
KEY fk_maybe_super_category_token (maybe_super_category_token),
KEY fk_creator_user_token (creator_user_token),
KEY index_model_type (model_type),
KEY index_can_directly_have_models (can_directly_have_models),
KEY index_can_have_subcategories (can_have_subcategories),
KEY index_is_mod_approved (is_mod_approved)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
-- Join table assigning models to zero or more categories.
CREATE TABLE tts_category_assignments (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
model_token VARCHAR(32) NOT NULL,
category_token VARCHAR(32) NOT NULL,
-- ========== USER DETAILS ==========
-- The person that created the assignment.
-- This can be the model owner or a moderator.
category_addition_user_token VARCHAR(32) DEFAULT NULL,
-- The person that removed the assignment.
-- This can be the model owner or a moderator.
category_removal_user_token VARCHAR(32) DEFAULT NULL,
-- For abuse tracking.
-- THIS CAN BE A MODERATOR FOR SIMPLICITY.
-- Wide enough for IPv4/6
ip_address_creation VARCHAR(40) NOT NULL,
ip_address_last_update VARCHAR(40) NOT NULL,
-- ========== TIMESTAMPS ==========
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (model_token, category_token),
KEY fk_model_token (model_token),
KEY fk_category_token (category_token),
KEY index_deleted_at (deleted_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
-- Join table assigning models to zero or more categories.
CREATE TABLE w2l_category_assignments (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
model_token VARCHAR(32) NOT NULL,
category_token VARCHAR(32) NOT NULL,
-- ========== USER DETAILS ==========
-- The person that created the assignment.
-- This can be the model owner or a moderator.
category_addition_user_token VARCHAR(32) DEFAULT NULL,
-- The person that removed the assignment.
-- This can be the model owner or a moderator.
category_removal_user_token VARCHAR(32) DEFAULT NULL,
-- For abuse tracking.
-- THIS CAN BE A MODERATOR FOR SIMPLICITY.
-- Wide enough for IPv4/6
ip_address_creation VARCHAR(40) NOT NULL,
ip_address_last_update VARCHAR(40) NOT NULL,
-- ========== TIMESTAMPS ==========
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (model_token, category_token),
KEY fk_model_token (model_token),
KEY fk_category_token (category_token),
KEY index_deleted_at (deleted_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,5 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
DROP TABLE twitch_oauth_tokens;
@@ -0,0 +1,264 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- **NOTE ABOUT DESIGN**:
-- This table contains _many_ OAuth tokens for any single user. (many users : many tokens)
-- An entire history of tokens is kept in the table.
-- Apart from "deleted_at", etc., these records should largely be IMMUTABLE.
CREATE TABLE twitch_oauth_tokens(
-- Not used for anything except replication.
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
-- Minted on first request and copied on each refresh.
-- This allows us to group access tokens all the way back up the
-- refresh chain.
oauth_refresh_grouping_token VARCHAR(32) NOT NULL,
-- ========== STORYTELLER/FAKEYOU USER ==========
-- Foreign key to user (Storyteller/FakeYou user)
-- If no user is associated (anonymous Twitch user), then this is null.
maybe_user_token VARCHAR(32) DEFAULT NULL,
-- ========== TWITCH USER ==========
-- The user ID / channel ID are the same
-- https://discuss.dev.twitch.tv/t/what-is-the-difference-between-the-stream--id-and-channel--id/4423
-- Several suggest this should be a string and not an integer
-- https://discuss.dev.twitch.tv/t/type-of-user-id-in-api-responses/10205
-- Yep, strings
-- https://discuss.dev.twitch.tv/t/bug-v5-api-returns--id-as-string-for-featured-channels/10310
twitch_user_id VARCHAR(64) NOT NULL,
-- Twitch usernames are between 4 and 25 characters.
-- This is returned in the oauth flow
twitch_username VARCHAR(32) NOT NULL,
-- Twitch username, but lowercase for lookup.
twitch_username_lowercase VARCHAR(32) NOT NULL,
-- ========== OAUTH TOKEN DETAILS ==========
-- The ever important OAuth access token.
access_token VARCHAR(128) NOT NULL,
-- If we can refresh the token, this is the thing to use.
maybe_refresh_token VARCHAR(128) DEFAULT NULL,
-- Should be "bearer". Included in the OAuth redemption.
token_type VARCHAR(32) DEFAULT NULL,
-- When the token expires from time of first grant.
-- (We don't update this field.)
-- Null if it does not expire or we were not informed
expires_in_seconds INT(10) UNSIGNED DEFAULT NULL,
-- Number of times this token has been refreshed
-- The original token creation will be "0", and each
-- subsequent time will bump by one. Mostly for debugging.
refresh_count INT(10) UNSIGNED DEFAULT 0,
-- ======================================================
-- ==================== OAUTH SCOPES ====================
-- ======================================================
-- TODO: Probably could have stored this in a bitfield. Ugh.
-- "analytics:read:extensions"
-- "View analytics data for the Twitch Extensions owned by the authenticated account."
has_analytics_read_extensions BOOLEAN NOT NULL DEFAULT FALSE,
-- scope: "analytics:read:games"
-- "View analytics data for the games owned by the authenticated account."
has_analytics_read_games BOOLEAN NOT NULL DEFAULT FALSE,
-- "bits:read"
-- "View Bits information for a channel."
has_bits_read BOOLEAN NOT NULL DEFAULT FALSE,
-- "channel:edit:commercial"
-- "Run commercials on a channel."
has_channel_edit_commercial BOOLEAN NOT NULL DEFAULT FALSE,
-- "channel:manage:broadcast"
-- "Manage a channel's broadcast configuration, including updating channel configuration and managing stream markers and stream tags."
has_channel_manage_broadcast BOOLEAN NOT NULL DEFAULT FALSE,
-- "channel:manage:extensions"
-- "Manage a channel's Extension configuration, including activating Extensions."
has_channel_manage_extensions BOOLEAN NOT NULL DEFAULT FALSE,
-- "channel:manage:polls"
-- "Manage a channel's polls."
has_channel_manage_polls BOOLEAN NOT NULL DEFAULT FALSE,
-- "channel:manage:predictions"
-- "Manage of channel's Channel Points Predictions"
has_channel_manage_predictions BOOLEAN NOT NULL DEFAULT FALSE,
-- "channel:manage:redemptions"
-- "Manage Channel Points custom rewards and their redemptions on a channel."
has_channel_manage_redemptions BOOLEAN NOT NULL DEFAULT FALSE,
-- "channel:manage:schedule"
-- "Manage a channel's stream schedule."
has_channel_manage_schedule BOOLEAN NOT NULL DEFAULT FALSE,
-- "channel:manage:videos"
-- "Manage a channel's videos, including deleting videos."
has_channel_manage_videos BOOLEAN NOT NULL DEFAULT FALSE,
-- "channel:moderate"
-- "Perform moderation actions in a channel. The user requesting the scope must be a moderator in the channel."
has_channel_moderate BOOLEAN NOT NULL DEFAULT FALSE,
-- "channel:read:editors"
-- "View a list of users with the editor role for a channel."
has_channel_read_editors BOOLEAN NOT NULL DEFAULT FALSE,
-- "channel:read:goals"
-- "View Creator Goals for a channel."
has_channel_read_goals BOOLEAN NOT NULL DEFAULT FALSE,
-- "channel:read:hype_train"
-- "View Hype Train information for a channel."
has_channel_read_hype_train BOOLEAN NOT NULL DEFAULT FALSE,
-- "channel:read:polls"
-- "View a channel's polls."
has_channel_read_polls BOOLEAN NOT NULL DEFAULT FALSE,
-- "channel:read:predictions"
-- "View a channel's Channel Points Predictions."
has_channel_read_predictions BOOLEAN NOT NULL DEFAULT FALSE,
-- "channel:read:redemptions"
-- "View Channel Points custom rewards and their redemptions on a channel."
-- eg. PubSub subscribe to `channel-points-channel-v1.<channel_id>` (channel points spends)
has_channel_read_redemptions BOOLEAN NOT NULL DEFAULT FALSE,
-- "channel:read:stream_key"
-- "View an authorized user's stream key."
has_channel_read_stream_key BOOLEAN NOT NULL DEFAULT FALSE,
-- "channel:read:subscriptions"
-- "View a list of all subscribers to a channel and check if a user is subscribed to a channel."
-- eg. Enumerate list of subscribers for a channel
-- eg. PubSub subscribe to `channel-subscribe-events-v1.<channel ID>` (subscribe, resubscribe, gift)
has_channel_read_subscriptions BOOLEAN NOT NULL DEFAULT FALSE,
-- "channel_subscriptions"
-- "\\[DEPRECATED\\] Read all subscribers to your channel."
has_channel_subscriptions BOOLEAN NOT NULL DEFAULT FALSE,
-- "chat:edit"
-- "Send live stream chat and rooms messages."
has_chat_edit BOOLEAN NOT NULL DEFAULT FALSE,
-- "chat:read"
-- "View live stream chat and rooms messages."
has_chat_read BOOLEAN NOT NULL DEFAULT FALSE,
-- "clips:edit"
-- "Manage Clips for a channel."
has_clips_edit BOOLEAN NOT NULL DEFAULT FALSE,
-- "moderation:read"
-- "View a channel's moderation data including Moderators, Bans, Timeouts, and Automod settings."
has_moderation_read BOOLEAN NOT NULL DEFAULT FALSE,
-- "moderator:manage:automod"
-- "Manage messages held for review by AutoMod in channels where you are a moderator."
has_moderator_manage_auto_mod BOOLEAN NOT NULL DEFAULT FALSE,
-- "user:edit"
-- "Manage a user object."
has_user_edit BOOLEAN NOT NULL DEFAULT FALSE,
-- "user:edit:broadcast"
-- "Edit your channel's broadcast configuration, including extension configuration. (This scope implies user:read:broadcast capability.)"
has_user_edit_broadcast BOOLEAN NOT NULL DEFAULT FALSE,
-- "user:edit:follows"
-- "Edit a user's follows."
has_user_edit_follows BOOLEAN NOT NULL DEFAULT FALSE,
-- "user:manage:blocked_users"
-- "Manage the block list of a user."
has_user_manage_blocked_users BOOLEAN NOT NULL DEFAULT FALSE,
-- "user:read:blocked_users"
-- "View the block list of a user."
has_user_read_blocked_users BOOLEAN NOT NULL DEFAULT FALSE,
-- "user:read:broadcast"
-- "View a user's broadcasting configuration, including Extension configurations."
has_user_read_broadcast BOOLEAN NOT NULL DEFAULT FALSE,
-- "user:read:email"
-- "Read an authorized user's email address."
has_user_read_email BOOLEAN NOT NULL DEFAULT FALSE,
-- "user:read:follows"
-- "View the list of channels a user follows."
-- eg. We can use this to see if a Twitch user follows us.
has_user_read_follows BOOLEAN NOT NULL DEFAULT FALSE,
-- "user:read:subscriptions"
-- "View if an authorized user is subscribed to specific channels."
has_user_read_subscriptions BOOLEAN NOT NULL DEFAULT FALSE,
-- "whispers:edit"
-- "Send whisper messages."
has_whispers_edit BOOLEAN NOT NULL DEFAULT FALSE,
-- "whispers:read"
-- "View your whisper messages."
has_whispers_read BOOLEAN NOT NULL DEFAULT FALSE,
-- ========== SECURITY ==========
-- For abuse tracking.
-- Wide enough for IPv4/6
ip_address_creation VARCHAR(40) DEFAULT NULL,
-- ========== VECTOR CLOCK ==========
-- Incremented with every update.
version INT UNSIGNED NOT NULL DEFAULT 0,
-- ========== TIMESTAMPS ==========
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- This is when the OAuth token will expire.
-- While we need to refresh before this time, Twitch recommends against eager
-- refreshes and instead wants the code to lazily refresh.
-- This also has no bearing on the "refresh_token".
-- We'll just use this for bookkeeping.
expires_at TIMESTAMP DEFAULT NULL,
-- If the OAuth token is deleted, we set this.
-- We'll need to set it for all of a user's OAuth tokens since this table stores many tokens.
user_deleted_at TIMESTAMP NULL,
mod_deleted_at TIMESTAMP NULL,
-- INDICES --
PRIMARY KEY (id),
KEY index_oauth_refresh_grouping_token (oauth_refresh_grouping_token),
KEY fk_maybe_user_token (maybe_user_token),
KEY index_twitch_user_id (twitch_user_id),
-- KEY index_twitch_username (twitch_username),
KEY index_twitch_username_lowercase (twitch_username_lowercase),
KEY index_expires_at (expires_at),
KEY index_user_deleted_at (user_deleted_at),
KEY index_mod_deleted_at (mod_deleted_at)
-- KEY index_has_bits_read (has_bits_read),
-- KEY index_creator_ip_address (creator_ip_address)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,5 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
DROP TABLE twitch_bits_events;
@@ -0,0 +1,44 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- This is primarily meant to be for analysis.
-- NB: See [_docs/database_schema.md] for details on Twitch-specifics.
CREATE TABLE twitch_bits_events(
-- Not used for anything except replication.
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
-- ========== USER / CHANNEL ==========
-- The user sending the bits.
-- We'll need to keep the name and lowercase for lookup.
sender_twitch_user_id VARCHAR(64) NOT NULL,
sender_twitch_username VARCHAR(32) NOT NULL,
sender_twitch_username_lowercase VARCHAR(32) NOT NULL,
-- The channel the bits were being sent to.
-- We technically only need the ID since we can JOIN, but we'll keep
-- the name for convenience.
destination_channel_id VARCHAR(64) NOT NULL,
destination_channel_name VARCHAR(32) NOT NULL,
-- ========== BITS DETAILS ==========
is_anonymous BOOLEAN NOT NULL DEFAULT FALSE,
-- The number of bits just spent
bits_used INT(10) UNSIGNED DEFAULT NULL,
-- The lifetime(?) number of bits spent by the user in this channel.
total_bits_used INT(10) UNSIGNED DEFAULT NULL,
chat_message TEXT NOT NULL,
-- INDICES --
PRIMARY KEY (id),
KEY index_sender_twitch_user_id (sender_twitch_user_id),
KEY index_sender_twitch_username_lowercase (sender_twitch_username_lowercase),
KEY index_destination_channel_id (destination_channel_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,5 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
DROP TABLE twitch_channel_point_events;
@@ -0,0 +1,57 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- This is primarily meant to be for analysis.
-- NB: See [_docs/database_schema.md] for details on Twitch-specifics.
CREATE TABLE twitch_channel_point_events (
-- Not used for anything except replication.
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
-- ========== USER / CHANNEL ==========
-- The user sending the bits.
-- We'll need to keep the name and lowercase for lookup.
sender_twitch_user_id VARCHAR(64) NOT NULL,
sender_twitch_username VARCHAR(32) NOT NULL,
sender_twitch_username_lowercase VARCHAR(32) NOT NULL,
-- The channel the bits were being sent to.
-- We technically only need the ID since we can JOIN, but we'll keep
-- the name for convenience.
destination_channel_id VARCHAR(64) NOT NULL,
destination_channel_name VARCHAR(32) NOT NULL,
-- ========== CHANNEL POINTS DETAILS ==========
-- This is the title used for the reward
-- We'll match on this to do TTS
title VARCHAR(256) NOT NULL,
-- This is the description used for the reward
prompt VARCHAR(256) NOT NULL,
-- This is the text that the user sends
-- This is optional, but we'll coerce null to empty
user_text_input TEXT NOT NULL,
-- A UUID for this instance of reward redemption
redemption_id VARCHAR(36) NOT NULL,
-- A UUID for the reward
reward_id VARCHAR(36) NOT NULL,
reward_cost INT(10) UNSIGNED DEFAULT NULL,
is_sub_only BOOLEAN NOT NULL DEFAULT FALSE,
max_per_stream INT(10) UNSIGNED DEFAULT NULL,
max_per_user_per_stream INT(10) UNSIGNED DEFAULT NULL,
-- INDICES --
PRIMARY KEY (id),
KEY index_sender_twitch_user_id (sender_twitch_user_id),
KEY index_sender_twitch_username_lowercase (sender_twitch_username_lowercase),
KEY index_destination_channel_id (destination_channel_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,10 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE tts_inference_jobs DROP COLUMN is_for_twitch;
ALTER TABLE tts_inference_jobs DROP COLUMN is_from_api;
ALTER TABLE tts_inference_jobs DROP COLUMN priority_level;
ALTER TABLE tts_results DROP COLUMN is_from_api;
ALTER TABLE tts_results DROP COLUMN is_for_twitch;
@@ -0,0 +1,23 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE tts_inference_jobs
ADD COLUMN is_from_api BOOLEAN NOT NULL DEFAULT FALSE
AFTER creator_set_visibility,
ADD COLUMN is_for_twitch BOOLEAN NOT NULL DEFAULT FALSE
AFTER is_from_api,
-- Priority *increases*, so a level of 2 will be higher than 1.
-- By default, all jobs from FakeYou have level 0.
-- Twitch TTS has level 1.
-- Paid Twitch and Paid API have level 2.
ADD COLUMN priority_level TINYINT UNSIGNED NOT NULL DEFAULT 0
AFTER status;
ALTER TABLE tts_results
ADD COLUMN is_from_api BOOLEAN NOT NULL DEFAULT FALSE
AFTER duration_millis,
ADD COLUMN is_for_twitch BOOLEAN NOT NULL DEFAULT FALSE
AFTER is_from_api;
@@ -0,0 +1,6 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE tts_models DROP COLUMN is_front_page_featured;
ALTER TABLE tts_models DROP COLUMN is_twitch_featured;
@@ -0,0 +1,10 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE tts_models
ADD COLUMN is_front_page_featured BOOLEAN NOT NULL DEFAULT FALSE
AFTER version_string,
ADD COLUMN is_twitch_featured BOOLEAN NOT NULL DEFAULT FALSE
AFTER is_front_page_featured;
@@ -0,0 +1,5 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
DROP TABLE twitch_event_rules;
@@ -0,0 +1,70 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- How to respond to different types of Twitch events.
-- Multiple rows per user
CREATE TABLE twitch_event_rules(
-- Not used for anything except replication.
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
-- Idempotency token from client
-- This is so the frontend client doesn't submit duplicate jobs.
uuid_idempotency_token VARCHAR(36) NOT NULL,
-- Effective "primary key" (PUBLIC)
token VARCHAR(32) NOT NULL,
-- Foreign key to user
-- This is a FakeYou/Storyteller account, *NOT* a twitch user id.
user_token VARCHAR(32) NOT NULL,
-- ========== EVENT MATCHING AND BEHAVIOR ==========
-- What type of event we'll be responding to.
event_category ENUM(
'bits',
'channel_points',
'chat_command'
) NOT NULL,
-- A JSON payload containing any predicates we wish to apply to the match.
-- eg. bits_value > 100
-- eg. channel_points_award_name == "Mario Voice"
event_match_predicate MEDIUMTEXT NOT NULL,
-- A JSON payload containing how we wish to respond to the event.
-- eg. tts M:model
event_response MEDIUMTEXT NOT NULL,
-- ========== RULE ORGANIZATION AND MANAGEMENT ==========
-- The user can rearrange the rules in the UI.
-- This will be the order they apply in if matched.
-- NB: There is nothing in MySQL to guarantee unique ordering.
user_specified_rule_order INT(10) UNSIGNED NOT NULL DEFAULT 0,
-- Whether or not the rule is enabled.
-- This is different than deleted. It still shows up in the UI.
rule_is_disabled BOOLEAN NOT NULL DEFAULT FALSE,
-- ========== ABUSE TRACKING, ETC. ==========
-- For abuse tracking.
-- Wide enough for IPv4/6
ip_address_creation VARCHAR(40) NOT NULL,
ip_address_last_update VARCHAR(40) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted_at TIMESTAMP DEFAULT NULL,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (uuid_idempotency_token),
UNIQUE KEY (token),
KEY fk_user_token (user_token),
KEY index_event_category (event_category),
KEY index_deleted_at (deleted_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,5 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
DROP TABLE twitch_general_settings;
@@ -0,0 +1,38 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- One row per user. Contains all of the general (non-repeated) configs.
CREATE TABLE twitch_general_settings (
-- Not used for anything except replication.
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
-- Foreign key to user
-- This is a FakeYou/Storyteller account, *NOT* a twitch user id.
user_token VARCHAR(32) NOT NULL,
-- Kill switch for the entire service.
storyteller_is_enabled BOOLEAN NOT NULL DEFAULT FALSE,
-- Kill switch for TTS.
tts_is_enabled BOOLEAN NOT NULL DEFAULT FALSE,
-- Kill switch for W2L.
w2l_is_enabled BOOLEAN NOT NULL DEFAULT FALSE,
-- Can set a fallback voice if a TTS voice goes missing.
maybe_fallback_tts_model_token VARCHAR(32) DEFAULT NULL,
-- For abuse tracking.
-- Wide enough for IPv4/6
ip_address_creation VARCHAR(40) NOT NULL,
ip_address_last_update VARCHAR(40) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (user_token)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,5 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
DROP TABLE api_tokens;
@@ -0,0 +1,47 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- We'll use application logic to kill any API keys beyond the most recent 5 "active" a user creates.
CREATE TABLE api_tokens (
-- Not used for anything except replication.
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
-- Idempotency token from client
-- This is so the frontend client doesn't submit duplicate jobs.
uuid_idempotency_token VARCHAR(36) NOT NULL,
-- This is a private internal reference to the API token.
-- Not shared with consumers / users.
-- This should be used as the foreign key for the token.
internal_token VARCHAR(32) NOT NULL,
-- This is the secret API token value given to consumers
api_token VARCHAR(32) NOT NULL,
-- Foreign key to user that owns the API token.
-- A user can have several.
user_token VARCHAR(32) NOT NULL,
-- A user-defined short description (optional)
maybe_short_description VARCHAR(32) DEFAULT NULL,
-- For abuse tracking.
-- Wide enough for IPv4/6
ip_address_creation VARCHAR(40) NOT NULL,
ip_address_last_update VARCHAR(40) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted_at TIMESTAMP DEFAULT NULL,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (uuid_idempotency_token),
UNIQUE KEY (internal_token),
UNIQUE KEY (api_token),
KEY fk_user_token (user_token),
KEY index_created_at (created_at),
KEY index_deleted_at (deleted_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,5 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
DROP TABLE voice_clone_requests;
@@ -0,0 +1,77 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
CREATE TABLE voice_clone_requests(
-- Not used for anything except replication.
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
-- Idempotency token from client
-- This is so the frontend client doesn't submit duplicate jobs.
uuid_idempotency_token VARCHAR(36) NOT NULL,
-- Effective "primary key" (PUBLIC)
token VARCHAR(32) NOT NULL,
-- ========== CONTACT ==========
-- Foreign key to user
-- This is a FakeYou/Storyteller account, *NOT* a twitch user id.
maybe_user_token VARCHAR(32) DEFAULT NULL,
email_address VARCHAR(255) NOT NULL,
discord_username VARCHAR(255) NOT NULL,
-- ========== REASON ==========
-- Visibility
is_for_private_use BOOLEAN NOT NULL DEFAULT FALSE,
is_for_public_use BOOLEAN NOT NULL DEFAULT FALSE,
-- How they'll use
is_for_studio BOOLEAN NOT NULL DEFAULT FALSE,
is_for_twitch_tts BOOLEAN NOT NULL DEFAULT FALSE,
is_for_api_use BOOLEAN NOT NULL DEFAULT FALSE,
is_for_music BOOLEAN NOT NULL DEFAULT FALSE,
is_for_games BOOLEAN NOT NULL DEFAULT FALSE,
is_for_other BOOLEAN NOT NULL DEFAULT FALSE,
optional_notes_on_use MEDIUMTEXT DEFAULT NULL,
-- Subject/Ownership
is_own_voice BOOLEAN NOT NULL DEFAULT FALSE,
is_third_party_voice BOOLEAN NOT NULL DEFAULT FALSE,
optional_notes_on_subject VARCHAR(255) DEFAULT NULL,
-- ========== RECORDING QUALITY / EQUIPMENT ==========
has_clean_audio_recordings BOOLEAN NOT NULL DEFAULT FALSE,
has_good_microphone BOOLEAN NOT NULL DEFAULT FALSE,
-- ========== COMMENTS ==========
optional_questions MEDIUMTEXT DEFAULT NULL,
optional_extra_comments MEDIUMTEXT DEFAULT NULL,
-- ========== BUSINESS END / INTERNAL USE ==========
internal_has_been_handled BOOLEAN NOT NULL DEFAULT FALSE,
internal_notes MEDIUMTEXT DEFAULT NULL,
-- ========== ABUSE TRACKING, ETC. ==========
-- For abuse tracking.
-- Wide enough for IPv4/6
ip_address_creation VARCHAR(40) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (uuid_idempotency_token),
UNIQUE KEY (token),
KEY fk_maybe_user_token (maybe_user_token)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,9 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE tts_models
DROP COLUMN ietf_language_tag;
ALTER TABLE tts_models
DROP COLUMN ietf_primary_language_subtag;
@@ -0,0 +1,13 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- The full IETF BCP47 language tag (eg. en, en-US, es-419, ja-JP, pt, etc.)
ALTER TABLE tts_models
ADD COLUMN ietf_language_tag VARCHAR(64) NOT NULL DEFAULT 'en'
AFTER maybe_default_pretrained_vocoder;
-- The IETF BCP47 language tag's primary language subtag (eg. "es-419" becomes "es")
ALTER TABLE tts_models
ADD COLUMN ietf_primary_language_subtag VARCHAR(12) NOT NULL DEFAULT 'en'
AFTER ietf_language_tag;
@@ -0,0 +1,6 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE tts_models
DROP COLUMN maybe_suggested_unique_bot_command;
@@ -0,0 +1,14 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- A bot command prefix, eg. 'sonic' that could be interpreted as '/sonic' by a TTS system.
-- These are unique strings and can only be applied to one model at a time.
-- Only moderators can set this, and it's only for the best voices.
ALTER TABLE tts_models
ADD COLUMN maybe_suggested_unique_bot_command VARCHAR(255) DEFAULT NULL
AFTER is_twitch_featured;
-- And the unique index.
ALTER TABLE tts_models
ADD UNIQUE INDEX unique_index_maybe_suggested_unique_bot_command (maybe_suggested_unique_bot_command);
@@ -0,0 +1,6 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE twitch_oauth_tokens
DROP COLUMN internal_token;
@@ -0,0 +1,12 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- In case we ever want to query these via an admin interface, it'd be nice to not use IDs
ALTER TABLE twitch_oauth_tokens
ADD COLUMN internal_token VARCHAR(32) NOT NULL
AFTER id;
-- And the unique index.
ALTER TABLE twitch_oauth_tokens
ADD UNIQUE INDEX unique_index_internal_token (internal_token);
@@ -0,0 +1,5 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
DROP TABLE twitch_event_match_logs;
@@ -0,0 +1,23 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- Log any event matching (bits, channel points, etc.)
CREATE TABLE twitch_event_match_logs(
-- Not used for anything except replication.
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
token VARCHAR(32) NOT NULL,
-- Type of event, eg.
event_type VARCHAR(32) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted_at TIMESTAMP DEFAULT NULL,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (token)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,120 @@
-- NB: This is a manually squashed view of all the CREATE and ALTER statements,
-- with comments attached to the fields for centralized documentation.
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
CREATE TABLE tts_inference_jobs (
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Effective "primary key" (PUBLIC)
-- This is so the in-progress results can be looked up by the UI.
token VARCHAR(32) NOT NULL,
-- Idempotency token from client
-- This is so the frontend client doesn't submit duplicate jobs.
uuid_idempotency_token VARCHAR(36) NOT NULL,
-- ========== SUCCESS CASE ==========
-- If the job completes successfully, this is the result token.
-- This is only populated on a successful result.
on_success_result_token VARCHAR(32) DEFAULT NULL,
-- ========== INFERENCE DETAILS ==========
-- The model to use.
-- This also determines which architecture we're using.
model_token VARCHAR(32) NOT NULL,
-- If set, use a different pretrained vocoder.
maybe_override_pretrained_vocoder VARCHAR(64) DEFAULT NULL,
-- The raw, unprocessed user input.
raw_inference_text TEXT NOT NULL,
-- ========== CREATOR DETAILS ==========
-- Foreign key to user
-- If no user is logged in, this is null.
maybe_creator_user_token VARCHAR(32) DEFAULT NULL,
-- Based on a cookie sent by the frontend.
-- We'll save this even if the user is logged in.
maybe_creator_anonymous_visitor_token VARCHAR(32) DEFAULT NULL,
-- For abuse tracking.
-- Wide enough for IPv4/6
creator_ip_address VARCHAR(40) NOT NULL,
-- (THIS MIGHT NOT BE USED)
-- NB: DO NOT SORT!
-- THIS MUST MATCH THE RESPECTIVE JOBS TABLE.
creator_set_visibility ENUM(
'public',
'hidden',
'private'
) NOT NULL DEFAULT 'public',
-- ========== SOURCE METADATA ==========
is_from_api BOOLEAN NOT NULL DEFAULT FALSE,
is_for_twitch BOOLEAN NOT NULL DEFAULT FALSE,
-- ========== JOB SYSTEM DETAILS ==========
-- Jobs begin as "pending", then transition to other states.
--
-- * Pending = job is ready to go
-- * Started = job is running
-- * Complete_Success = job is done (success)
-- * Complete_Failure = job is done (failure)
-- * Attempt_Failed = job failed but may retry.
-- * Dead = job failed permanently.
--
-- Pending -> Started -> Complete_Success
-- |-> Complete_Failure
-- \-> Attempt_Failed -> Started -> { Complete, Failed, Dead }
status ENUM(
'pending',
'started',
'complete_success',
'complete_failure',
'attempt_failed',
'dead') NOT NULL DEFAULT 'pending',
-- Priority *increases*, so a level of 2 will be higher than 1.
-- These are the level semantics currently:
-- - All jobs from anonymous FakeYou users have level 0.
-- - All jobs from logged in FakeYou users have level 1.
-- - All jobs from Twitch TTS (unpaid) have level 10 (ten).
-- - (There will be future levels for paid Twitch and social FakeYou rewards.)
priority_level TINYINT UNSIGNED NOT NULL DEFAULT 0,
-- We can track this against a "max_attempt_count"
attempt_count INT(3) NOT NULL DEFAULT 0,
-- If there is a failure, tell the user why.
failure_reason VARCHAR(512) DEFAULT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- Failed jobs will set a next attempt time.
-- Subsequent tries can increase the timeout.
-- Failures because of permissions require human intervention => [retry_at=null].
-- Failures because of invalid files are dead => [status=dead].
retry_at TIMESTAMP NULL,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (token),
UNIQUE KEY (uuid_idempotency_token),
KEY fk_on_success_result_token (on_success_result_token),
KEY fk_model_token (model_token),
KEY fk_maybe_creator_user_token (maybe_creator_user_token),
KEY index_status (status),
KEY index_creator_ip_address (creator_ip_address)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,120 @@
-- NB: This is a manually squashed view of all the CREATE and ALTER statements,
-- with comments attached to the fields for centralized documentation.
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- TTS MODELS
CREATE TABLE tts_model_upload_jobs (
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Effective "primary key" (PUBLIC)
-- This is so the in-progress results can be looked up by the UI.
token VARCHAR(32) NOT NULL,
-- Idempotency token
-- This is so the frontend client doesn't submit duplicate jobs.
uuid_idempotency_token VARCHAR(36) NOT NULL,
-- ========== SUCCESS CASE ==========
-- If the job completes successfully, this is the result token.
-- This is only populated on a successful result.
on_success_result_token VARCHAR(32) DEFAULT NULL,
-- ========== UPLOAD DETAILS ==========
-- The name of the voice
-- The "name" of the voice model, which might be complicated.
-- If maybe_subject_token (etc.) is set, then it's authoritative instead.
title VARCHAR(255) NOT NULL,
-- NB: DO NOT CHANGE ORDER; APPEND ONLY!
-- THIS MUST MATCH THE RESPECTIVE JOBS TABLE.
tts_model_type ENUM(
'not-set',
'tacotron2',
'glowtts',
'glowtts-vocodes',
'talknet'
) NOT NULL DEFAULT 'not-set',
-- Can be linked to a well-known voice
-- maybe_subject_token VARCHAR(32) DEFAULT NULL,
-- maybe_actor_subject_token VARCHAR(32) DEFAULT NULL,
-- If we need to download the file from Google Drive.
download_url VARCHAR(512) DEFAULT NULL,
-- NB: DO NOT SORT!
-- download_url_type ENUM(
-- 'google-drive',
-- 'web'
-- ) NOT NULL DEFAULT 'web',
-- ========== CREATOR DETAILS AND PREFERENCES ==========
-- Foreign key to user
creator_user_token VARCHAR(32) NOT NULL,
-- For abuse tracking.
-- Wide enough for IPv4/6
creator_ip_address VARCHAR(40) NOT NULL,
-- (THIS MIGHT NOT BE USED)
-- NB: DO NOT SORT!
-- THIS MUST MATCH THE RESPECTIVE JOBS TABLE.
creator_set_visibility ENUM(
'public',
'hidden',
'private'
) NOT NULL DEFAULT 'public',
-- ========== JOB SYSTEM DETAILS ==========
-- Jobs begin as "pending", then transition to other states.
--
-- * Pending = job is ready to go
-- * Started = job is running
-- * Complete_Success = job is done (success)
-- * Complete_Failure = job is done (failure)
-- * Attempt_Failed = job failed but may retry.
-- * Dead = job failed permanently.
--
-- Pending -> Started -> Complete_Success
-- |-> Complete_Failure
-- \-> Attempt_Failed -> Started -> { Complete, Failed, Dead }
status ENUM(
'pending',
'started',
'complete_success',
'complete_failure',
'attempt_failed',
'dead') NOT NULL DEFAULT 'pending',
-- We can track this against a "max_attempt_count"
attempt_count INT(3) NOT NULL DEFAULT 0,
-- If there is a failure, tell the user why.
failure_reason VARCHAR(512) DEFAULT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- Failed jobs will set a next attempt time.
-- Subsequent tries can increase the timeout.
-- Failures because of permissions require human intervention => [retry_at=null].
-- Failures because of invalid files are dead => [status=dead].
retry_at TIMESTAMP NULL,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (token),
UNIQUE KEY (uuid_idempotency_token),
KEY fk_on_success_result_token (on_success_result_token),
KEY fk_creator_user_token (creator_user_token),
KEY index_status (status),
KEY index_creator_ip_address (creator_ip_address)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,204 @@
-- NB: This is a manually squashed view of all the CREATE and ALTER statements,
-- with comments attached to the fields for centralized documentation.
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
CREATE TABLE tts_models (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Effective "primary key" (PUBLIC)
token VARCHAR(32) NOT NULL,
-- NB: DO NOT CHANGE ORDER; APPEND ONLY!
-- THIS MUST MATCH THE RESPECTIVE JOBS TABLE.
tts_model_type ENUM(
'not-set',
'tacotron2',
'glowtts',
'glowtts-vocodes',
'talknet'
) NOT NULL DEFAULT 'not-set',
-- For models distributed as archives/bundles (eg. TalkNet), we can pack
-- in a vocoder. Note that this vocoder does not have to be used.
has_self_contained_vocoder BOOLEAN NOT NULL DEFAULT FALSE,
-- For models distributed as archives/bundles (eg. TalkNet), we can pack
-- in a duration model.
has_self_contained_duration_model BOOLEAN NOT NULL DEFAULT FALSE,
-- For models distributed as archives/bundles (eg. TalkNet), we can pack
-- in a pitch model.
has_self_contained_pitch_model BOOLEAN NOT NULL DEFAULT FALSE,
-- NB: DO NOT CHANGE ORDER; APPEND ONLY!
-- How text should be handled.
text_preprocessing_algorithm ENUM(
'basic',
'arpabet'
) NOT NULL DEFAULT 'basic',
-- If set, use a different pretrained vocoder.
-- If not set, use the website default. (Currently HifiGan-SS)
maybe_default_pretrained_vocoder VARCHAR(64) DEFAULT NULL,
-- Optional Pointer to a newer version of the voice
-- If there's a newer version, we can disable this one.
-- maybe_updated_model_token VARCHAR(32) DEFAULT NULL,
-- We can set as a combination of ['username' + 'voice-name']
-- There can be public aliases for voices, eg. a voice's default model.
-- A user can change this.
-- As such, these should not be foreign keys.
-- updatable_slug VARCHAR(64) NOT NULL,
-- Can be linked to a well-known voice
-- maybe_subject_token VARCHAR(32) DEFAULT NULL,
-- maybe_actor_subject_token VARCHAR(32) DEFAULT NULL,
-- ========== UNSTRUCTURED METADATA ==========
-- The full IETF BCP47 language tag (eg. en, en-US, es-419, ja-JP, pt, etc.)
ietf_language_tag VARCHAR(64) NOT NULL DEFAULT 'en',
-- The IETF BCP47 language tag's primary language subtag (eg. "es-419" becomes "es")
ietf_primary_language_subtag VARCHAR(12) NOT NULL DEFAULT 'en',
-- The "name" of the voice model, which might be complicated.
-- If maybe_subject_token (etc.) is set, then it's authoritative instead.
title VARCHAR(255) NOT NULL,
-- The description of the model in markdown.
description_markdown TEXT NOT NULL,
-- Generated HTML (not user-editable).
description_rendered_html TEXT NOT NULL,
-- eg. Bill Clinton, Batman, or Goku.
voice_name VARCHAR(255) DEFAULT NULL,
-- Optional; eg. Sean Schemmel
actor_name VARCHAR(255) DEFAULT NULL,
-- Optional; eg. if the voice is "happy" or a singer "a-capella", etc.
characteristic VARCHAR(255) DEFAULT NULL,
-- Optional; eg. if you train the model more than once.
version_string VARCHAR(255) DEFAULT NULL,
-- Whether the voice features on the front of FakeYou or Storyteller Twitch TTS
is_front_page_featured BOOLEAN NOT NULL DEFAULT FALSE,
is_twitch_featured BOOLEAN NOT NULL DEFAULT FALSE,
-- A suggested bot command prefix, eg. 'sonic' that could be interpreted as '/sonic'
-- by a TTS system. These are unique strings that can only be applied to one model at
-- a time at most. Only moderators can set this, and it's only for the best voices.
maybe_suggested_unique_bot_command VARCHAR(255) DEFAULT NULL,
-- ========== IMMUTABLE PROVENANCE AND METADATA ==========
-- Where the file was originally downloaded (if it was downloaded)
original_download_url VARCHAR(512) DEFAULT NULL,
-- The filename that was used at upload time (if available)
original_filename VARCHAR(255) DEFAULT NULL,
-- File characteristics
file_size_bytes INT(10) NOT NULL DEFAULT 0,
-- ========== CREATOR DETAILS ==========
-- The person that created the template.
creator_user_token VARCHAR(32) NOT NULL,
-- For abuse tracking.
-- Wide enough for IPv4/6
creator_ip_address_creation VARCHAR(40) NOT NULL,
creator_ip_address_last_update VARCHAR(40) NOT NULL,
-- (THIS MIGHT NOT BE USED)
-- NB: DO NOT SORT!
-- THIS MUST MATCH THE RESPECTIVE JOBS TABLE.
creator_set_visibility ENUM(
'public',
'hidden',
'private'
) NOT NULL DEFAULT 'public',
-- ========== BUCKET STORAGE ==========
-- The pytorch model
-- For now, this will be a hash of the file contents.
-- NB: NOT UNIQUE! We can allow duplicate uploads.
private_bucket_hash CHAR(64) NOT NULL,
-- The "full url" version of the path
private_bucket_object_name VARCHAR(255) NOT NULL,
-- Whether the file is a zip archive (rather than a model file).
private_bucket_object_is_archive BOOLEAN NOT NULL DEFAULT FALSE,
-- ========== RATING AND RANKING ==========
-- Calculated average, on a scale of 0-100
-- Null with zero ratings.
-- calculated_average_score INT(3) DEFAULT NULL,
-- Other metrics
-- calculated_total_ratings_submitted_count INT(10) NOT NULL DEFAULT 0,
-- calculated_total_uses_count BIGINT(10) NOT NULL DEFAULT 0,
-- ========== MODERATION DETAILS ==========
-- Mods have to approve of tts models for them to show up in a public index.
-- Models can be used by the author (or other parties that know the URL) before
-- they're approved, but unapproved models won't show up in public indices.
-- This is independent of other visibility controls (is_mod_disabled, deleted_at,
-- and creator_set_visibility)
is_public_listing_approved BOOLEAN DEFAULT NULL,
-- Extremely popular models may be locked from deletion or modification by users.
is_locked_from_user_modification BOOLEAN NOT NULL DEFAULT FALSE,
-- In this case, a moderator disables it from being used for inference.
-- This also disables it for the creator.
-- Unlike deletion, it remains "visible" to those that have access.
is_locked_from_use BOOLEAN NOT NULL DEFAULT FALSE,
-- If a moderator has comments.
maybe_mod_comments VARCHAR(255) DEFAULT NULL,
-- The last moderator that made changes.
maybe_mod_user_token VARCHAR(32) DEFAULT NULL,
-- ========== VECTOR CLOCK ==========
-- Incremented with every update.
version INT NOT NULL DEFAULT 0,
-- ========== TIMESTAMPS ==========
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- If this is removed by a mod.
-- It completely disappears from the system.
user_deleted_at TIMESTAMP NULL,
mod_deleted_at TIMESTAMP NULL,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (token),
KEY fk_creator_user_token (creator_user_token),
KEY fk_maybe_mod_user_token (maybe_mod_user_token),
KEY index_creator_ip_address_creation (creator_ip_address_creation),
KEY index_creator_ip_address_last_update (creator_ip_address_last_update),
KEY index_creator_set_visibility (creator_set_visibility),
KEY index_private_bucket_hash (private_bucket_hash),
KEY index_is_public_listing_approved (is_public_listing_approved),
KEY index_is_locked_from_user_modification (is_locked_from_user_modification),
KEY index_is_locked_from_use (is_locked_from_use)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,105 @@
-- NB: This is a manually squashed view of all the CREATE and ALTER statements,
-- with comments attached to the fields for centralized documentation.
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
CREATE TABLE tts_results (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Effective "primary key" (PUBLIC)
token VARCHAR(32) NOT NULL,
-- ========== INFERENCE DETAILS ==========
-- The TTS model that was used
model_token VARCHAR(32) NOT NULL,
-- If set, specifies which pretrained vocoder was used.
maybe_pretrained_vocoder_used VARCHAR(64) DEFAULT NULL,
-- The original raw, unprocessed user input.
raw_inference_text TEXT NOT NULL,
-- SHA2 hash of the text [SHA2 = CHAR(64), SHA1 = CHAR(40), MD5 = CHAR(32)]
raw_inference_text_hash_sha2 CHAR(64) NOT NULL,
-- First pass at text normalization.
-- eg. 14th -> fourteenth, etc.
normalized_inference_text TEXT NOT NULL,
-- If the model uses arpabet, we'll save the arpabet we used.
maybe_arpabet_text TEXT DEFAULT NULL,
-- ========== CREATOR DETAILS ==========
-- The person that created the template.
-- If the user wasn't logged in, this is null
maybe_creator_user_token VARCHAR(32) DEFAULT NULL,
-- Based on a cookie sent by the frontend.
-- We'll save this even if the user is logged in.
maybe_creator_anonymous_visitor_token VARCHAR(32) DEFAULT NULL,
-- For abuse tracking.
-- Wide enough for IPv4/6
creator_ip_address VARCHAR(40) NOT NULL,
-- (THIS MIGHT NOT BE USED)
-- NB: DO NOT SORT!
-- THIS MUST MATCH THE RESPECTIVE JOBS TABLE.
creator_set_visibility ENUM(
'public',
'hidden',
'private'
) NOT NULL DEFAULT 'public',
-- The synthetic id associated with this result.
-- These ids are incremented on a per-user basis to help users
-- sequence their own work. They serve no other purpose.
maybe_creator_synthetic_id BIGINT(20) DEFAULT NULL,
-- ========== BUCKET STORAGE ==========
-- Where the wav, spectrogram, and etc. are located.
public_bucket_wav_audio_path VARCHAR(255) NOT NULL,
public_bucket_spectrogram_path VARCHAR(255) NOT NULL,
-- ========== METADATA ==========
file_size_bytes INT(10) NOT NULL DEFAULT 0,
duration_millis INT(10) NOT NULL DEFAULT 0,
-- ========== SOURCE METADATA ==========
is_from_api BOOLEAN NOT NULL DEFAULT FALSE,
is_for_twitch BOOLEAN NOT NULL DEFAULT FALSE,
-- ========== MODERATION DETAILS ==========
-- The last moderator that made changes.
maybe_mod_user_token VARCHAR(32) DEFAULT NULL,
-- ========== TIMESTAMPS ==========
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- If this is removed by a mod or the creator.
user_deleted_at TIMESTAMP NULL,
mod_deleted_at TIMESTAMP NULL,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (token),
KEY fk_model_token (model_token),
KEY fk_maybe_creator_user_token (maybe_creator_user_token),
KEY fk_maybe_creator_anonymous_visitor_token (maybe_creator_anonymous_visitor_token),
KEY fk_maybe_mod_user_token (maybe_mod_user_token),
KEY index_creator_ip_address (creator_ip_address),
KEY index_user_deleted_at(user_deleted_at),
KEY index_mod_deleted_at(mod_deleted_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
+75
View File
@@ -0,0 +1,75 @@
-- All logged in users with results in the last X days
select u.created_at from users as u
where u.token IN
(select distinct u.token from tts_inference_jobs as j join users u on u.token = j.maybe_creator_user_token where j.created_at > now() - interval 7 day)
order by u.created_at ASC;
-- All logged in users with results in the last X days that are older than Y
select u.created_at from users as u
where u.token IN
(select distinct u.token from tts_inference_jobs as j join users u on u.token = j.maybe_creator_user_token where j.created_at > now() - interval 30 day)
AND u.created_at < now() - interval 30 day
order by u.created_at ASC;
-- ====================== FEBRUARY COHORT ===================================
-- [1] All users created between X and Y
select count(*) from users
where created_at > now() - interval 60 day
and created_at < now() - interval 30 day;
-- [2] Active users from that cohort ^
select count(*) from users as u
where u.token IN
(select distinct u.token from tts_inference_jobs as j join users u on u.token = j.maybe_creator_user_token where j.created_at > now() - interval 30 day)
AND created_at > now() - interval 60 day
AND created_at < now() - interval 30 day;
-- ====================== JANUARY COHORT ===================================
-- [1] All users created between X and Y
select count(*) from users
where created_at > now() - interval 90 day
and created_at < now() - interval 60 day;
-- [2] Active users from that cohort ^
select count(*) from users as u
where u.token IN
(select distinct u.token from tts_inference_jobs as j join users u on u.token = j.maybe_creator_user_token where j.created_at > now() - interval 30 day)
AND created_at > now() - interval 90 day
AND created_at < now() - interval 60 day;
(select distinct u.token from tts_inference_jobs as j join users u on u.token = j.maybe_creator_user_token where j.created_at > now() - interval 30 day)
select j.creator_ip_address, count(*) as number
from tts_inference_jobs as j
where j.created_at > now() - interval 1 day
group by j.creator_ip_address
order by number desc
limit 100;
select count(*) from tts_inference_jobs
where created_at > now() - interval 1 day ;
select count(distinct creator_ip_address) from tts_inference_jobs
where created_at > now() - interval 1 day ;
select j.creator_ip_address, count(*) as number
from tts_inference_jobs as j
where j.created_at > now() - interval 1 day
group by j.creator_ip_address
order by number desc
limit 100;
+46
View File
@@ -0,0 +1,46 @@
#!/bin/bash
# This is complicated since we're now in a more complex Rust workspace with
# multiple projects and targets.
# NB(2022-01-31): For some reason, 'storyteller-web', despite importing the obs queries
# via the `database_queries` shared library, started to exclude these queries. It was
# working, but mysteriously stopped. I'm using this more complicated setup with jq-based
# json merge as a stop gap.
# NB: This format can be used without changing directory:
# SQLX_OFFLINE=true cargo sqlx prepare -- --bin storyteller-web --manifest-path crates/service/storyteller_web/Cargo.toml
build_shared_database_library() {
pushd crates/lib/database_queries
SQLX_OFFLINE=true cargo sqlx prepare
popd
}
build_storyteller_web_app() {
# NB: This imports the inference/upload job queries
# It should also import the shared database lib queries, but something(???) broke.
pushd crates/service/storyteller_web
SQLX_OFFLINE=true cargo sqlx prepare -- --bin storyteller-web # NB: Because multiple binary targets
popd
}
combine_sqlx_queries() {
# Merge multiple JSON files into a single dictionary.
# https://stackoverflow.com/a/24904276
jq -s '.[0] * .[1]' \
crates/lib/database_queries/sqlx-data.json \
crates/service/storyteller_web/sqlx-data.json \
> sqlx-data.json
}
cleanup_temp_files() {
rm crates/lib/database_queries/sqlx-data.json \
crates/service/storyteller_web/sqlx-data.json
}
build_shared_database_library
build_storyteller_web_app
combine_sqlx_queries
cleanup_temp_files
+13
View File
@@ -0,0 +1,13 @@
[package]
name = "config"
edition = "2018"
version = "0.0.1"
authors = [
"Brandon Thomas <bt@brand.io>",
"Brandon Thomas <echelon@gmail.com>",
]
publish = false
[lib]
name = "config"
path = "src/lib.rs"
+1
View File
@@ -0,0 +1 @@
pub mod shared_constants;
+20
View File
@@ -0,0 +1,20 @@
//! Shared constants that all binaries use.
//! Or at least that deserve extremely high visibility.
/// NB: `sqlx::query` is spammy and dumps all queries as "info"-level log lines.
/// NB: `hyper::proto::h1::io` is incredibly spammy and logs every chunk of bytes in very large files being downloaded.
pub const DEFAULT_RUST_LOG: &'static str = "debug,actix_web=info,sqlx::query=warn,hyper::proto::h1::io=warn,storyteller_web::threads::db_health_checker_thread::db_health_checker_thread=warn";
//const DEFAULT_RUST_LOG: &'static str = "debug,actix_web=info"; // but sometimes we want to debug
/// The default Redis connection string for use in development
pub const DEFAULT_REDIS_DATABASE_0_CONNECTION_STRING: &'static str = "redis://localhost/0";
/// The default Redis connection string for use in development
/// Database 1
pub const DEFAULT_REDIS_DATABASE_1_CONNECTION_STRING: &'static str = "redis://localhost/1";
/// The default MySql connection string for use in development
pub const DEFAULT_MYSQL_CONNECTION_STRING: &'static str = "mysql://storyteller:password@localhost/storyteller";
/// The number of results we return by default for paginated queries.
pub const DEFAULT_MYSQL_QUERY_RESULT_PAGE_SIZE: u16 = 25;
+21
View File
@@ -0,0 +1,21 @@
[package]
name = "container_common"
edition = "2018"
version = "0.0.1"
authors = [
"Brandon Thomas <bt@brand.io>",
"Brandon Thomas <echelon@gmail.com>",
]
publish = false
[lib]
name = "container_common"
path = "src/lib.rs"
[dependencies]
# External
anyhow = "1.0.40"
once_cell = "1.9.0"
rand = "0.8.3"
uuid = { version = "0.8.2", features = ["v4"] }
@@ -0,0 +1,2 @@
/// Easier to import than anyhow::Result.
pub type AnyhowResult<T> = anyhow::Result<T>;
View File
Whitespace-only changes.
@@ -0,0 +1 @@
pub mod random_from_vec;
@@ -0,0 +1,31 @@
use rand::seq::SliceRandom;
/// Return a random item from a vec.
pub fn random_from_vec<T>(vec: &Vec<T>) -> Option<&T> {
vec.choose(&mut rand::thread_rng())
}
#[cfg(test)]
mod tests {
use crate::collections::random_from_vec::random_from_vec;
#[test]
fn pulls_item_from_vec() {
let items = vec!["one", "two", "three"];
let selected = random_from_vec(&items).unwrap();
assert!(items.contains(selected));
let items = vec!["one", "two", "three"].into_iter()
.map(|s| s.to_string())
.collect();
let selected = random_from_vec(&items).unwrap();
assert!(items.contains(selected));
}
#[test]
fn behavior_on_empty() {
let items : Vec<String> = vec![];
let selected = random_from_vec(&items);
assert!(selected.is_none());
}
}
@@ -0,0 +1 @@
pub mod supported_languages_for_models;
@@ -0,0 +1,112 @@
use once_cell::sync::Lazy;
use std::collections::{HashSet, HashMap};
/// These are language tags we try to support.
static SUPPORTED_LANGUAGES_FOR_MODELS : Lazy<HashSet<String>> = Lazy::new(|| {
let language_tags = [
// ========== Technically, we only support these so far ==========
// English
"en",
"en-AU",
"en-CA",
"en-GB",
"en-US",
// Spanish
"es",
"es-419",
"es-AR",
"es-CL",
"es-CO",
"es-ES",
"es-MX",
"es-PE",
"es-US",
// Portuguese
"pt",
"pt-BR",
// ========== But these are on the horizon ==========
// French
"fr",
"fr-FR",
// German
"de",
"de-DE",
// Japanese
"ja",
"ja-JP",
// Misc
"id",
"id-ID",
"it",
"it-IT",
"ru",
"ru-RU",
"th-TH",
"tr",
"tr-TR",
"zh-CN",
"zh-HK",
];
language_tags.iter()
.map(|tag| tag.to_string())
.collect::<HashSet<String>>()
});
/// Convert lower case tags to canonical form
static SUPPORTED_LANGUAGES_FOR_MODELS_CANONICAL_MAP : Lazy<HashMap<String, String>> = Lazy::new(|| {
SUPPORTED_LANGUAGES_FOR_MODELS.iter()
.map(|tag| (tag.to_lowercase(), tag.clone()))
.collect::<HashMap<_,_>>()
});
/// Take a full IETF BCP 47 language tag and return the canonicalized form *IF* we support it
pub fn get_canonicalized_language_tag_for_model(language_tag: &str) -> Option<&'static str> {
let lowercase = language_tag.to_lowercase();
SUPPORTED_LANGUAGES_FOR_MODELS_CANONICAL_MAP.get(&lowercase).map(|v| v.as_str())
}
/// Take a full IETF BCP 47 language tag and return true *IF* we support it
/// It must be the correct case.
pub fn is_valid_language_for_models(language_tag: &str) -> bool {
SUPPORTED_LANGUAGES_FOR_MODELS.contains(language_tag)
}
#[cfg(test)]
mod tests {
use crate::i18n::supported_languages_for_models::{get_canonicalized_language_tag_for_model, is_valid_language_for_models};
#[test]
fn get_canonical_language_for_model_success() {
assert_eq!("en", get_canonicalized_language_tag_for_model("EN").unwrap());
assert_eq!("es-419", get_canonicalized_language_tag_for_model("ES-419").unwrap());
assert_eq!("en-US", get_canonicalized_language_tag_for_model("En-Us").unwrap());
}
#[test]
fn get_canonical_language_for_model_failure() {
assert!(get_canonicalized_language_tag_for_model("").is_none());
assert!(get_canonicalized_language_tag_for_model("en-JP").is_none());
assert!(get_canonicalized_language_tag_for_model("es-Lots-Of-Tags").is_none());
}
#[test]
fn test_valid_language_for_model() {
// Valid
assert!(is_valid_language_for_models("en"));
assert!(is_valid_language_for_models("es-419"));
assert!(is_valid_language_for_models("ja-JP"));
// Invalid due to case
assert!(!is_valid_language_for_models("EN"));
assert!(!is_valid_language_for_models("ES-419"));
assert!(!is_valid_language_for_models("JA-jp"));
// Invalid misc
assert!(!is_valid_language_for_models(""));
assert!(!is_valid_language_for_models("foo"));
assert!(!is_valid_language_for_models("en-FOO"));
}
}
+6
View File
@@ -0,0 +1,6 @@
pub mod anyhow_result;
pub mod collections;
pub mod i18n;
pub mod numerics;
pub mod thread;
pub mod token;
@@ -0,0 +1 @@
pub mod signed_to_unsigned;
@@ -0,0 +1,127 @@
// TODO: Use macros to define these (or use a better numeric crate)
/// Negative numbers coalesce to 0
pub fn i8_to_unsigned_zeroing_negatives(num: i8) -> u8 {
if num.is_negative() {
0
} else {
num.unsigned_abs()
}
}
/// Negative numbers coalesce to 0
pub fn i16_to_unsigned_zeroing_negatives(num: i16) -> u16 {
if num.is_negative() {
0
} else {
num.unsigned_abs()
}
}
/// Negative numbers coalesce to 0
pub fn i32_to_unsigned_zeroing_negatives(num: i32) -> u32 {
if num.is_negative() {
0
} else {
num.unsigned_abs()
}
}
/// Negative numbers coalesce to 0
pub fn i64_to_unsigned_zeroing_negatives(num: i64) -> u64 {
if num.is_negative() {
0
} else {
num.unsigned_abs()
}
}
/// Negative numbers coalesce to 0
pub fn i128_to_unsigned_zeroing_negatives(num: i128) -> u128 {
if num.is_negative() {
0
} else {
num.unsigned_abs()
}
}
/// Negative numbers coalesce to 0
pub fn isize_to_unsigned_zeroing_negatives(num: isize) -> usize {
if num.is_negative() {
0
} else {
num.unsigned_abs()
}
}
#[cfg(test)]
mod tests {
use crate::numerics::signed_to_unsigned::i8_to_unsigned_zeroing_negatives;
use crate::numerics::signed_to_unsigned::i16_to_unsigned_zeroing_negatives;
use crate::numerics::signed_to_unsigned::i32_to_unsigned_zeroing_negatives;
use crate::numerics::signed_to_unsigned::i64_to_unsigned_zeroing_negatives;
use crate::numerics::signed_to_unsigned::i128_to_unsigned_zeroing_negatives;
use crate::numerics::signed_to_unsigned::isize_to_unsigned_zeroing_negatives;
#[test]
fn i8_to_unsigned() {
assert_eq!(i8_to_unsigned_zeroing_negatives(-127), 0);
assert_eq!(i8_to_unsigned_zeroing_negatives(-1), 0);
assert_eq!(i8_to_unsigned_zeroing_negatives(0), 0);
assert_eq!(i8_to_unsigned_zeroing_negatives(1), 1);
assert_eq!(i8_to_unsigned_zeroing_negatives(100), 100);
assert_eq!(i8_to_unsigned_zeroing_negatives(127), 127);
}
#[test]
fn i16_to_unsigned() {
assert_eq!(i16_to_unsigned_zeroing_negatives(-127), 0);
assert_eq!(i16_to_unsigned_zeroing_negatives(-1), 0);
assert_eq!(i16_to_unsigned_zeroing_negatives(0), 0);
assert_eq!(i16_to_unsigned_zeroing_negatives(1), 1);
assert_eq!(i16_to_unsigned_zeroing_negatives(100), 100);
assert_eq!(i16_to_unsigned_zeroing_negatives(127), 127);
}
#[test]
fn i32_to_unsigned() {
assert_eq!(i32_to_unsigned_zeroing_negatives(-127), 0);
assert_eq!(i32_to_unsigned_zeroing_negatives(-1), 0);
assert_eq!(i32_to_unsigned_zeroing_negatives(0), 0);
assert_eq!(i32_to_unsigned_zeroing_negatives(1), 1);
assert_eq!(i32_to_unsigned_zeroing_negatives(100), 100);
assert_eq!(i32_to_unsigned_zeroing_negatives(127), 127);
}
#[test]
fn i64_to_unsigned() {
assert_eq!(i64_to_unsigned_zeroing_negatives(-127), 0);
assert_eq!(i64_to_unsigned_zeroing_negatives(-1), 0);
assert_eq!(i64_to_unsigned_zeroing_negatives(0), 0);
assert_eq!(i64_to_unsigned_zeroing_negatives(1), 1);
assert_eq!(i64_to_unsigned_zeroing_negatives(100), 100);
assert_eq!(i64_to_unsigned_zeroing_negatives(127), 127);
}
#[test]
fn i128_to_unsigned() {
assert_eq!(i128_to_unsigned_zeroing_negatives(-127), 0);
assert_eq!(i128_to_unsigned_zeroing_negatives(-1), 0);
assert_eq!(i128_to_unsigned_zeroing_negatives(0), 0);
assert_eq!(i128_to_unsigned_zeroing_negatives(1), 1);
assert_eq!(i128_to_unsigned_zeroing_negatives(100), 100);
assert_eq!(i128_to_unsigned_zeroing_negatives(127), 127);
}
#[test]
fn isize_to_unsigned() {
assert_eq!(isize_to_unsigned_zeroing_negatives(-127), 0);
assert_eq!(isize_to_unsigned_zeroing_negatives(-1), 0);
assert_eq!(isize_to_unsigned_zeroing_negatives(0), 0);
assert_eq!(isize_to_unsigned_zeroing_negatives(1), 1);
assert_eq!(isize_to_unsigned_zeroing_negatives(100), 100);
assert_eq!(isize_to_unsigned_zeroing_negatives(127), 127);
}
}
@@ -0,0 +1 @@
pub mod thread_id;
@@ -0,0 +1,59 @@
use crate::token::random_crockford_token::random_crockford_token;
/// Thread string identifiers
/// Used for leases, logging, etc.
/// This has nothing to do with actual OS thread names or IDs.
/// The `std::thread::ThreadId` type might be better for some cases.
#[derive(Eq, PartialEq, Debug, Clone)]
pub struct ThreadId {
id: String,
}
impl ThreadId {
pub fn random_id() -> Self {
Self {
id: random_crockford_token(10),
}
}
pub fn with_id(id: &str) -> Self {
Self {
id: id.to_string(),
}
}
pub fn get_id(&self) -> &str {
&self.id
}
}
#[cfg(test)]
mod tests {
use crate::thread::thread_id::ThreadId;
#[test]
fn equals() {
let a = ThreadId::with_id("foo");
let b = ThreadId::with_id("foo");
assert_eq!(a, b);
}
#[test]
fn not_equals() {
let a = ThreadId::with_id("foo");
let b = ThreadId::with_id("bar");
assert_ne!(a, b);
}
#[test]
fn manual_id() {
let t = ThreadId::with_id("foo");
assert_eq!("foo", t.get_id());
}
#[test]
fn random_id() {
let t = ThreadId::random_id();
assert_eq!(t.get_id().len(), 10);
}
}
@@ -0,0 +1,3 @@
pub mod random_crockford_token;
pub mod random_prefix_crockford_token;
pub mod random_uuid;
@@ -0,0 +1,30 @@
use rand::Rng;
// Crockford characters
const CROCKFORD_CHARSET: &[u8] = b"0123456789ABCDEFGHJKMNPQRSTVWXYZ";
pub fn random_crockford_token(length: usize) -> String {
let mut rng = rand::thread_rng();
let token: String = (0..length)
.map(|_| {
let idx = rng.gen_range(0..CROCKFORD_CHARSET.len());
CROCKFORD_CHARSET[idx] as char
})
.collect();
token
}
#[cfg(test)]
mod tests {
use crate::token::random_crockford_token::random_crockford_token;
#[test]
fn random_token_length() {
assert_eq!(random_crockford_token(0), "".to_string());
assert_eq!(random_crockford_token(1).len(), 1);
assert_eq!(random_crockford_token(10).len(), 10);
assert_eq!(random_crockford_token(32).len(), 32);
}
}
@@ -0,0 +1,98 @@
use anyhow::anyhow;
use crate::anyhow_result::AnyhowResult;
use rand::Rng;
// Crockford characters
const CROCKFORD_UPPERCASE_CHARSET: &[u8] = b"0123456789ABCDEFGHJKMNPQRSTVWXYZ";
const CROCKFORD_LOWERCASE_CHARSET: &[u8] = b"0123456789abcdefghjkmnpqrstvwxyz";
// Random part can't be any less than this.
const MIN_ENTROPY_LENGTH : usize = 8;
pub fn random_prefix_crockford_token(prefix: &str, length: usize, uppercase: bool) -> AnyhowResult<String> {
let mut prefix = prefix.trim().to_string();
if prefix.is_empty() {
return Err(anyhow!("prefix not set; use other crockford functions"));
}
if !prefix.ends_with(":") {
prefix = format!("{}:", prefix);
}
let entropy_length : usize = length.saturating_sub(prefix.len());
if entropy_length < MIN_ENTROPY_LENGTH {
return Err(anyhow!("not enough entropy in token"));
}
let mut rng = rand::thread_rng();
let charset = if uppercase { CROCKFORD_UPPERCASE_CHARSET } else { CROCKFORD_LOWERCASE_CHARSET };
let entropy_part: String = (0..entropy_length)
.map(|_| {
let idx = rng.gen_range(0..charset.len());
charset[idx] as char
})
.collect();
let token = format!("{}{}", prefix, entropy_part);
Ok(token)
}
#[cfg(test)]
mod tests {
use crate::token::random_prefix_crockford_token::random_prefix_crockford_token;
#[test]
fn no_prefix_error() {
assert!(random_prefix_crockford_token("", 0, false).is_err());
assert!(random_prefix_crockford_token("", 10, false).is_err());
assert!(random_prefix_crockford_token("", 32, false).is_err());
assert!(random_prefix_crockford_token("", 0, true).is_err());
assert!(random_prefix_crockford_token("", 10, true).is_err());
assert!(random_prefix_crockford_token("", 32, true).is_err());
}
#[test]
fn not_enough_entropy_error() {
assert!(random_prefix_crockford_token("foo", 0, false).is_err());
assert!(random_prefix_crockford_token("foo", 5, false).is_err());
assert!(random_prefix_crockford_token("foo", 11, false).is_err());
assert!(random_prefix_crockford_token("foo:", 11, false).is_err());
assert!(random_prefix_crockford_token("foo", 0, true).is_err());
assert!(random_prefix_crockford_token("foo", 5, true).is_err());
assert!(random_prefix_crockford_token("foo", 11, true).is_err());
assert!(random_prefix_crockford_token("foo:", 11, true).is_err());
}
#[test]
fn starts_with() {
assert!(random_prefix_crockford_token("w", 10, false).unwrap().starts_with("w:"));
assert!(random_prefix_crockford_token("w", 10, false).unwrap().starts_with("w:"));
assert!(random_prefix_crockford_token("prefix", 16, false).unwrap().starts_with("prefix:"));
assert!(random_prefix_crockford_token("prefix:", 16, false).unwrap().starts_with("prefix:"));
assert!(random_prefix_crockford_token("w", 10, true).unwrap().starts_with("w:"));
assert!(random_prefix_crockford_token("w", 10, true).unwrap().starts_with("w:"));
assert!(random_prefix_crockford_token("prefix", 16, true).unwrap().starts_with("prefix:"));
assert!(random_prefix_crockford_token("prefix:", 16, true).unwrap().starts_with("prefix:"));
}
#[test]
fn length() {
assert_eq!(random_prefix_crockford_token("w", 10, false).unwrap().len(), 10);
assert_eq!(random_prefix_crockford_token("w:", 10, false).unwrap().len(), 10);
assert_eq!(random_prefix_crockford_token("prefix", 16, false).unwrap().len(), 16);
assert_eq!(random_prefix_crockford_token("prefix:", 16, false).unwrap().len(), 16);
assert_eq!(random_prefix_crockford_token("asdf:", 32, false).unwrap().len(), 32);
assert_eq!(random_prefix_crockford_token("asdf", 32, false).unwrap().len(), 32);
assert_eq!(random_prefix_crockford_token("w", 10, true).unwrap().len(), 10);
assert_eq!(random_prefix_crockford_token("w:", 10, true).unwrap().len(), 10);
assert_eq!(random_prefix_crockford_token("prefix", 16, true).unwrap().len(), 16);
assert_eq!(random_prefix_crockford_token("prefix:", 16, true).unwrap().len(), 16);
assert_eq!(random_prefix_crockford_token("asdf:", 32, true).unwrap().len(), 32);
assert_eq!(random_prefix_crockford_token("asdf", 32, true).unwrap().len(), 32);
}
}
@@ -0,0 +1,9 @@
use uuid::Uuid;
pub fn generate_random_uuid() -> String {
let uuid = Uuid::new_v4();
let mut buffer = Uuid::encode_buffer();
let hyphenated_uuid = uuid.to_hyphenated()
.encode_lower(&mut buffer);
hyphenated_uuid.to_string()
}
+44
View File
@@ -0,0 +1,44 @@
[package]
name = "database_queries"
edition = "2018"
version = "0.0.1"
authors = [
"Brandon Thomas <bt@brand.io>",
"Brandon Thomas <echelon@gmail.com>",
]
publish = false
[lib]
name = "database_queries"
path = "src/lib.rs"
[dependencies]
# Internal
config = { path = "../config" }
container_common = { path = "../container_common" }
# External
anyhow = "1.0.40"
base64 = "0.13.0"
chrono = { version = "0.4.19", features = ["serde"] }
dotenv = "0.15.0"
log = "0.4.14"
# Needed to safety check some queries we can't use escaping for
once_cell = "1.9.0"
regex = "1.5.4"
# Serialization
serde = "1.0.125"
serde_derive = "1.0.125"
serde_json = "1.0.64"
toml = "0.5.8"
# MySql Database
# "offline" supports typesafe builds without a live database
# "rustls" doesn't appear to work with IP addresses, per:
# https://github.com/launchbadge/sqlx/issues/1095#issuecomment-798050828
#sqlx = { version = "0.4", features = [ "mysql", "runtime-tokio-native-tls", "offline", "chrono" ] }
sqlx = { version = "0.5", features = [ "mysql", "runtime-actix-native-tls", "offline", "chrono" ] }
@@ -0,0 +1,5 @@
/// This is simply a semantic / literate way of saying
/// an Optional should be required. There should be a
/// better way to actually enforce this, though.
pub (crate) type RequiredOption<T> = Option<T>;
@@ -0,0 +1,44 @@
//! These are columns where users can control the visibility of their data.
use anyhow::anyhow;
use container_common::anyhow_result::AnyhowResult;
/// To use this in a query, the query must have type annotations.
/// See: https://www.gitmemory.com/issue/launchbadge/sqlx/1241/847154375
/// eg. job_status as `job_status: crate::column_types::job_status::JobStatus`
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize, sqlx::Type)]
#[serde(rename_all = "snake_case")]
#[sqlx(rename_all = "snake_case")]
pub enum JobStatus {
Pending,
Started,
CompleteSuccess,
CompleteFailure,
AttemptFailed,
Dead,
}
impl JobStatus {
pub fn to_str(&self) -> &'static str {
match self {
Self::Pending => "pending",
Self::Started => "started",
Self::CompleteSuccess => "complete_success",
Self::CompleteFailure => "complete_failure",
Self::AttemptFailed => "attempt_failed",
Self::Dead => "dead",
}
}
pub fn from_str(job_status: &str) -> AnyhowResult<Self> {
match job_status {
"pending" => Ok(Self::Pending),
"started" => Ok(Self::Started),
"complete_success" => Ok(Self::CompleteSuccess),
"complete_failure" => Ok(Self::CompleteFailure),
"attempt_failed" => Ok(Self::AttemptFailed),
"dead" => Ok(Self::Dead),
_ => Err(anyhow!("invalid job_status: {:?}", job_status)),
}
}
}
@@ -0,0 +1,5 @@
pub mod job_status;
pub mod record_visibility;
pub mod tts_model_type;
pub mod twitch_event_category;
pub mod vocoder_type;
@@ -0,0 +1,35 @@
//! These are columns where users can control the visibility of their data.
use anyhow::anyhow;
use container_common::anyhow_result::AnyhowResult;
/// To use this in a query, the query must have type annotations.
/// See: https://www.gitmemory.com/issue/launchbadge/sqlx/1241/847154375
/// eg. preferred_tts_result_visibility as `preferred_tts_result_visibility: crate::column_types::record_visibility::RecordVisibility`
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize, sqlx::Type)]
#[serde(rename_all = "lowercase")]
#[sqlx(rename_all = "lowercase")]
pub enum RecordVisibility {
Public,
Hidden,
Private,
}
impl RecordVisibility {
pub fn to_str(&self) -> &'static str {
match self {
RecordVisibility::Public => "public",
RecordVisibility::Hidden => "hidden",
RecordVisibility::Private => "private",
}
}
pub fn from_str(record_visibility: &str) -> AnyhowResult<Self> {
match record_visibility {
"public" => Ok(RecordVisibility::Public),
"hidden" => Ok(RecordVisibility::Hidden),
"private" => Ok(RecordVisibility::Private),
_ => Err(anyhow!("invalid value: {:?}", record_visibility)),
}
}
}
@@ -0,0 +1,40 @@
//! These denote the TTS synthesizer model types
use anyhow::anyhow;
use container_common::anyhow_result::AnyhowResult;
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize, sqlx::Type)]
#[serde(rename_all = "lowercase")]
#[sqlx(rename_all = "lowercase")]
pub enum TtsModelType {
#[serde(rename = "tacotron2")]
Tacotron2,
#[serde(rename = "talknet")]
Talknet,
// Currently unused enum values:
//#[serde(rename = "not-set")]
//NotSet,
//#[serde(rename = "glowtts")]
//GlowTts,
//#[serde(rename = "glowtts-vocodes")]
//GlowTtsVocodes,
}
impl TtsModelType {
pub fn to_str(&self) -> &'static str {
match self {
TtsModelType::Tacotron2 => "tacotron2",
TtsModelType::Talknet => "talknet",
}
}
pub fn from_str(vocoder_type: &str) -> AnyhowResult<Self> {
match vocoder_type {
"tacotron2" => Ok(TtsModelType::Tacotron2),
"talknet" => Ok(TtsModelType::Talknet),
_ => Err(anyhow!("invalid value: {:?}", vocoder_type)),
}
}
}
@@ -0,0 +1,35 @@
//! These are columns where users can control the visibility of their data.
use anyhow::anyhow;
use container_common::anyhow_result::AnyhowResult;
/// To use this in a query, the query must have type annotations.
/// See: https://www.gitmemory.com/issue/launchbadge/sqlx/1241/847154375
/// eg. twitch_event_category as `twitch_event_category: crate::column_types::twitch_event_category::TwitchEventCategory`
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize, sqlx::Type)]
#[serde(rename_all = "snake_case")]
#[sqlx(rename_all = "snake_case")]
pub enum TwitchEventCategory {
Bits,
ChannelPoints,
ChatCommand,
}
impl TwitchEventCategory {
pub fn to_str(&self) -> &'static str {
match self {
Self::Bits => "bits",
Self::ChannelPoints => "channel_points",
Self::ChatCommand => "chat_command",
}
}
pub fn from_str(value: &str) -> AnyhowResult<Self> {
match value {
"bits" => Ok(Self::Bits),
"channel_points" => Ok(Self::ChannelPoints),
"chat_command" => Ok(Self::ChatCommand),
_ => Err(anyhow!("invalid value: {:?}", value)),
}
}
}
Loaded 100 of 1629 files, more files were not shown because too many files have changed in this diff. Show more