Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d29af1a
feat(db): Add database URL configuration
Shivansh-007 Aug 22, 2021
f08a118
deps: Add SQLAlchemy for db-client, limited to v1.4.23
Shivansh-007 Aug 26, 2021
e4a3773
deps: Add psycopg2-binary for SQLAlchemy
Shivansh-007 Aug 26, 2021
1253853
feat(db): Add init_db function to initalize the DB
Shivansh-007 Aug 26, 2021
1211746
feat(db): Add DB base class via as_declarative
Shivansh-007 Aug 26, 2021
73ec5d5
feat(db): Add discord modmail ticket model
Shivansh-007 Aug 26, 2021
7138f1c
feat(db): Add DB model representing message in ticket
Shivansh-007 Aug 26, 2021
34a5a90
feat(db): Add DB model representing attachment in a message
Shivansh-007 Aug 26, 2021
4bcd399
feat(db): Add DB model representing embed in a message
Shivansh-007 Aug 26, 2021
8082820
feat(db): Shift to asyncpg and drop psycopg
Shivansh-007 Aug 26, 2021
7bf651a
feat(db): Add Emojis and Modmail Configurations Model
Shivansh-007 Aug 26, 2021
b2abd9f
revert(db): Remove Embed model json validation
Shivansh-007 Aug 27, 2021
b48b470
fix(db): Don't map messages <-> tickets foreignkey
Shivansh-007 Aug 28, 2021
c87f8c8
feat(db): Add alembic for migrations managing
Shivansh-007 Aug 28, 2021
6bb69da
chore(flake8): Ignore S101
Shivansh-007 Aug 28, 2021
0220feb
feat(db): Add alembic migrations
Shivansh-007 Aug 28, 2021
a460abc
Merge branch 'main' into feat/db-client
Shivansh-007 Sep 1, 2021
0ca1ecc
docs(models): Document all model columns
Shivansh-007 Sep 2, 2021
b73ace5
feat(db): CRUD functions for models.Messages
Shivansh-007 Sep 2, 2021
4e73793
feat(db): CRUD functions for models.Threads
Shivansh-007 Sep 2, 2021
945af5d
feat(db): CRUD functions for models.Configurations
Shivansh-007 Sep 2, 2021
076579e
docs(contrib): Add PostgreSQL setup
Shivansh-007 Sep 2, 2021
0f32c2c
chore: Lint fixes
Shivansh-007 Sep 2, 2021
4616df6
Merge branch 'main' into feat/db-client
onerandomusername Sep 7, 2021
b2480f8
fix configuration so tests work again
onerandomusername Sep 7, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,37 @@ $ poetry install

This runs our register pre-commit hooks on every commit to automatically point out issues in code such as missing semicolons, trailing whitespace, and debug statements. By pointing these issues out before code review, this allows a code reviewer to focus on the architecture of a change while not wasting time with trivial style nitpicks.

### PostgreSQL setup

Install PostgreSQL according to its [documentation](https://www.postgresql.org/download/).

Enter psql, a terminal-based front-end to PostgreSQL:

<div class="termy">

```console
$ psql -qd postgres
```

Run the following queries to create the user and database:

```psql
CREATE USER voting WITH SUPERUSER PASSWORD 'modmail';
CREATE DATABASE modmail WITH OWNER modmail;
```

Finally, enter `/q` to exit psql.

Once the Database is started, you need run migrations to init tables and columns which can be ran through:

<div class="termy">

```console
$ poetry run alembic upgrade heads

---> 100%
```


### Set up modmail config

Expand Down Expand Up @@ -182,7 +213,12 @@ $ poetry install
!!!note
The entire file name is literally `.env`

5. Open the file with any text editor and write the bot token to the files in this format: `TOKEN="my_token"`.
5. Open the file with any text editor and write the bot token and the database URL to the files in this format:
* `TOKEN="my_token"`.
* `SQLALCHEMY_DATABASE_URI=postgresql+asyncpg://modmail:modmail@localhost:5432/modmail`

!!! note
If you configured PostgreSQL in a different manner or you are not hosting it locally, then you will need to determine the correct host and port yourself. The user, password, and database name should all still be pysite unless you deviated from the setup instructions in the previous section.

### Run The Project

Expand Down
1 change: 1 addition & 0 deletions modmail/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,4 @@
logging.getLogger("websockets").setLevel(logging.ERROR)
# Set asyncio logging back to the default of INFO even if asyncio's debug mode is enabled.
logging.getLogger("asyncio").setLevel(logging.INFO)
logging.getLogger("sqlalchemy").setLevel(logging.INFO)
4 changes: 4 additions & 0 deletions modmail/__main__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import asyncio
import logging

from modmail.bot import ModmailBot
Expand All @@ -21,6 +22,9 @@ def main() -> None:
"""Run the bot."""
patch_embed()
bot = ModmailBot()
# Check if the database is alive before running the bot
loop = asyncio.get_event_loop()
loop.run_until_complete(bot.init_db())
bot.run(bot.config.bot.token)


Expand Down
81 changes: 81 additions & 0 deletions modmail/alembic.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# A generic, single database configuration.

[alembic]
# path to migration scripts
script_location = alembic

# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s

# sys.path path, will be prepended to sys.path if present.
# defaults to the current working directory.
prepend_sys_path = .

# timezone to use when rendering the date
# within the migration file as well as the filename.
# string value is passed to dateutil.tz.gettz()
# leave blank for localtime
# timezone =

# max length of characters to apply to the
# "slug" field
# truncate_slug_length = 40

# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false

# set to 'true' to allow .pyc and .pyo files without
# a source .py file to be detected as revisions in the
# versions/ directory
# sourceless = false

# version location specification; this defaults
# to alembic/versions. When using multiple version
# directories, initial revisions must be specified with --version-path
# version_locations = %(here)s/bar %(here)s/bat alembic/versions

# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8

[post_write_hooks]
hooks = black
black.type = console_scripts
black.entrypoint = black
black.options = -l 110 REVISION_SCRIPT_FILENAME

# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic

[handlers]
keys = console

[formatters]
keys = generic

[logger_root]
level = WARN
handlers = console
qualname =

[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine

[logger_alembic]
level = INFO
handlers =
qualname = alembic

[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic

[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
1 change: 1 addition & 0 deletions modmail/alembic/README
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Generic single-database configuration with an async dbapi.
103 changes: 103 additions & 0 deletions modmail/alembic/env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import asyncio
import inspect
import os
import sys
from logging.config import fileConfig

from alembic import context
from sqlalchemy import engine_from_config, pool
from sqlalchemy.ext.asyncio import AsyncConnection, AsyncEngine


current_dir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
parent_parent_dir = os.path.dirname(os.path.dirname(current_dir))
sys.path.insert(0, parent_parent_dir)

from modmail.config import CONFIG # noqa: I201, E402
from modmail.models.base import Base # noqa: I201, E402


# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config

# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)

# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
target_metadata = Base.metadata


# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.


def get_url() -> str:
"""Get database connection url."""
return CONFIG.bot.sqlalchemy_database_uri


def run_migrations_offline() -> None:
"""
Run migrations in 'offline' mode.

This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.

Calls to context.execute() here emit the given string to the
script output.
"""
url = get_url()
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)

with context.begin_transaction():
context.run_migrations()


def do_run_migrations(connection: AsyncConnection) -> None:
"""Run migrations."""
context.configure(connection=connection, target_metadata=target_metadata)

with context.begin_transaction():
context.run_migrations()


async def run_migrations_online() -> None:
"""
Run migrations in 'online' mode.

In this scenario we need to create an Engine
and associate a connection with the context.
"""
configuration = config.get_section(config.config_ini_section)
configuration["sqlalchemy.url"] = get_url()
connectable = AsyncEngine(
engine_from_config(
configuration,
prefix="sqlalchemy.",
poolclass=pool.NullPool,
future=True,
)
)

async with connectable.connect() as connection:
await connection.run_sync(do_run_migrations)


if context.is_offline_mode():
run_migrations_offline()
else:
asyncio.run(run_migrations_online())
24 changes: 24 additions & 0 deletions modmail/alembic/script.py.mako
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""${message}

Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}

"""
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}

# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}


def upgrade():
${upgrades if upgrades else "pass"}


def downgrade():
${downgrades if downgrades else "pass"}
33 changes: 33 additions & 0 deletions modmail/alembic/versions/500f4263ffe9_add_tickets_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""Add tickets table

Revision ID: 500f4263ffe9
Revises: e6b20ed26bc2
Create Date: 2021-08-28 17:01:39.611060

"""
import sqlalchemy as sa
from alembic import op


# revision identifiers, used by Alembic.
revision = "500f4263ffe9"
down_revision = "e6b20ed26bc2"
branch_labels = None
depends_on = None


def upgrade():
op.create_table(
"tickets",
sa.Column("id", sa.BigInteger(), nullable=False),
sa.Column("server_id", sa.BigInteger(), nullable=False),
sa.Column("thread_id", sa.BigInteger(), nullable=False),
sa.Column("creater_id", sa.BigInteger(), nullable=False),
sa.Column("creating_message_id", sa.BigInteger(), nullable=False),
sa.Column("creating_channel_id", sa.BigInteger(), nullable=False),
sa.PrimaryKeyConstraint("id"),
)


def downgrade():
op.drop_table("tickets")
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""Add configurations table

Revision ID: 72459556f7b9
Revises: 7b384834cca4
Create Date: 2021-08-28 17:02:36.726948

"""
import sqlalchemy as sa
from alembic import op


# revision identifiers, used by Alembic.
revision = "72459556f7b9"
down_revision = "7b384834cca4"
branch_labels = None
depends_on = None


def upgrade():
op.create_table(
"configurations",
sa.Column("target_id", sa.BigInteger(), nullable=False),
sa.Column("config_key", sa.String(), nullable=False),
sa.Column("config_value", sa.String(), nullable=False),
sa.PrimaryKeyConstraint("target_id"),
)


def downgrade():
op.drop_table("configurations")
31 changes: 31 additions & 0 deletions modmail/alembic/versions/7b384834cca4_add_emojis_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""Add emojis table

Revision ID: 7b384834cca4
Revises: 8d52443a026e
Create Date: 2021-08-28 17:02:30.226745

"""
import sqlalchemy as sa
from alembic import op


# revision identifiers, used by Alembic.
revision = "7b384834cca4"
down_revision = "8d52443a026e"
branch_labels = None
depends_on = None


def upgrade():
op.create_table(
"emojis",
sa.Column("id", sa.BigInteger(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
sa.Column("url", sa.String(), nullable=False),
sa.Column("animated", sa.Boolean(), nullable=False),
sa.PrimaryKeyConstraint("id"),
)


def downgrade():
op.drop_table("emojis")
Loading