init CTFd source
This commit is contained in:
275
migrations/1_2_0_upgrade_2_0_0.py
Normal file
275
migrations/1_2_0_upgrade_2_0_0.py
Normal file
@@ -0,0 +1,275 @@
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import dataset
|
||||
from sqlalchemy_utils import drop_database
|
||||
|
||||
from CTFd import config, create_app
|
||||
from CTFd.utils import string_types
|
||||
|
||||
# This is important to allow access to the CTFd application factory
|
||||
sys.path.append(os.getcwd())
|
||||
|
||||
|
||||
def cast_bool(value):
|
||||
if value and value.isdigit():
|
||||
return int(value)
|
||||
elif value and isinstance(value, string_types):
|
||||
if value.lower() == "true":
|
||||
return True
|
||||
elif value.lower() == "false":
|
||||
return False
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("/*\\ Migrating your database to 2.0.0 can potentially lose data./*\\")
|
||||
print(
|
||||
"""/*\\ Please be sure to back up all data by:
|
||||
* creating a CTFd export
|
||||
* creating a dump of your actual database
|
||||
* and backing up the CTFd source code directory"""
|
||||
)
|
||||
print("/*\\ CTFd maintainers are not responsible for any data loss! /*\\")
|
||||
if input("Run database migrations (Y/N)").lower().strip() == "y":
|
||||
pass
|
||||
else:
|
||||
print("/*\\ Aborting database migrations... /*\\")
|
||||
print("/*\\ Exiting... /*\\")
|
||||
exit(1)
|
||||
|
||||
db_url = config.Config.SQLALCHEMY_DATABASE_URI
|
||||
old_data = {}
|
||||
|
||||
old_conn = dataset.connect(config.Config.SQLALCHEMY_DATABASE_URI)
|
||||
tables = old_conn.tables
|
||||
for table in tables:
|
||||
old_data[table] = old_conn[table].all()
|
||||
|
||||
if "alembic_version" in old_data:
|
||||
old_data.pop("alembic_version")
|
||||
|
||||
print("Current Tables:")
|
||||
for table in old_data.keys():
|
||||
print("\t", table)
|
||||
|
||||
old_conn.executable.close()
|
||||
|
||||
print("DROPPING DATABASE")
|
||||
drop_database(db_url)
|
||||
|
||||
app = create_app()
|
||||
|
||||
new_conn = dataset.connect(config.Config.SQLALCHEMY_DATABASE_URI)
|
||||
|
||||
print("MIGRATING Challenges")
|
||||
for challenge in old_data["challenges"]:
|
||||
hidden = challenge.pop("hidden")
|
||||
challenge["state"] = "hidden" if hidden else "visible"
|
||||
new_conn["challenges"].insert(dict(challenge))
|
||||
del old_data["challenges"]
|
||||
|
||||
print("MIGRATING Teams")
|
||||
for team in old_data["teams"]:
|
||||
admin = team.pop("admin")
|
||||
team["type"] = "admin" if admin else "user"
|
||||
team["hidden"] = bool(team.pop("banned"))
|
||||
team["banned"] = False
|
||||
team["verified"] = bool(team.pop("verified"))
|
||||
new_conn["users"].insert(dict(team))
|
||||
del old_data["teams"]
|
||||
|
||||
print("MIGRATING Pages")
|
||||
for page in old_data["pages"]:
|
||||
page["content"] = page.pop("html")
|
||||
new_conn["pages"].insert(dict(page))
|
||||
del old_data["pages"]
|
||||
|
||||
print("MIGRATING Keys")
|
||||
for key in old_data["keys"]:
|
||||
key["challenge_id"] = key.pop("chal")
|
||||
key["content"] = key.pop("flag")
|
||||
new_conn["flags"].insert(dict(key))
|
||||
del old_data["keys"]
|
||||
|
||||
print("MIGRATING Tags")
|
||||
for tag in old_data["tags"]:
|
||||
tag["challenge_id"] = tag.pop("chal")
|
||||
tag["value"] = tag.pop("tag")
|
||||
new_conn["tags"].insert(dict(tag))
|
||||
del old_data["tags"]
|
||||
|
||||
print("MIGRATING Files")
|
||||
for f in old_data["files"]:
|
||||
challenge_id = f.pop("chal")
|
||||
if challenge_id:
|
||||
f["challenge_id"] = challenge_id
|
||||
f["type"] = "challenge"
|
||||
else:
|
||||
f["page_id"] = None
|
||||
f["type"] = "page"
|
||||
new_conn["files"].insert(dict(f))
|
||||
del old_data["files"]
|
||||
|
||||
print("MIGRATING Hints")
|
||||
for hint in old_data["hints"]:
|
||||
hint["type"] = "standard"
|
||||
hint["challenge_id"] = hint.pop("chal")
|
||||
hint["content"] = hint.pop("hint")
|
||||
new_conn["hints"].insert(dict(hint))
|
||||
del old_data["hints"]
|
||||
|
||||
print("MIGRATING Unlocks")
|
||||
for unlock in old_data["unlocks"]:
|
||||
unlock["user_id"] = unlock.pop(
|
||||
"teamid"
|
||||
) # This is intentional as previous CTFds are effectively in user mode
|
||||
unlock["target"] = unlock.pop("itemid")
|
||||
unlock["type"] = unlock.pop("model")
|
||||
new_conn["unlocks"].insert(dict(unlock))
|
||||
del old_data["unlocks"]
|
||||
|
||||
print("MIGRATING Awards")
|
||||
for award in old_data["awards"]:
|
||||
award["user_id"] = award.pop(
|
||||
"teamid"
|
||||
) # This is intentional as previous CTFds are effectively in user mode
|
||||
new_conn["awards"].insert(dict(award))
|
||||
del old_data["awards"]
|
||||
|
||||
submissions = []
|
||||
for solve in old_data["solves"]:
|
||||
solve.pop("id") # ID of a solve doesn't really matter
|
||||
solve["challenge_id"] = solve.pop("chalid")
|
||||
solve["user_id"] = solve.pop("teamid")
|
||||
solve["provided"] = solve.pop("flag")
|
||||
solve["type"] = "correct"
|
||||
solve["model"] = "solve"
|
||||
submissions.append(solve)
|
||||
|
||||
for wrong_key in old_data["wrong_keys"]:
|
||||
wrong_key.pop("id") # ID of a fail doesn't really matter.
|
||||
wrong_key["challenge_id"] = wrong_key.pop("chalid")
|
||||
wrong_key["user_id"] = wrong_key.pop("teamid")
|
||||
wrong_key["provided"] = wrong_key.pop("flag")
|
||||
wrong_key["type"] = "incorrect"
|
||||
wrong_key["model"] = "wrong_key"
|
||||
submissions.append(wrong_key)
|
||||
|
||||
submissions = sorted(submissions, key=lambda k: k["date"])
|
||||
print("MIGRATING Solves & WrongKeys")
|
||||
for submission in submissions:
|
||||
model = submission.pop("model")
|
||||
if model == "solve":
|
||||
new_id = new_conn["submissions"].insert(dict(submission))
|
||||
submission["id"] = new_id
|
||||
new_conn["solves"].insert(dict(submission))
|
||||
elif model == "wrong_key":
|
||||
new_conn["submissions"].insert(dict(submission))
|
||||
del old_data["solves"]
|
||||
del old_data["wrong_keys"]
|
||||
|
||||
print("MIGRATING Tracking")
|
||||
for tracking in old_data["tracking"]:
|
||||
tracking["user_id"] = tracking.pop("team")
|
||||
new_conn["tracking"].insert(dict(tracking))
|
||||
del old_data["tracking"]
|
||||
|
||||
print("MIGRATING Config")
|
||||
banned = ["ctf_version"]
|
||||
workshop_mode = None
|
||||
hide_scores = None
|
||||
prevent_registration = None
|
||||
view_challenges_unregistered = None
|
||||
view_scoreboard_if_authed = None
|
||||
|
||||
challenge_visibility = "private"
|
||||
registration_visibility = "public"
|
||||
score_visibility = "public"
|
||||
account_visibility = "public"
|
||||
for c in old_data["config"]:
|
||||
c.pop("id")
|
||||
|
||||
if c["key"] == "workshop_mode":
|
||||
workshop_mode = cast_bool(c["value"])
|
||||
elif c["key"] == "hide_scores":
|
||||
hide_scores = cast_bool(c["value"])
|
||||
elif c["key"] == "prevent_registration":
|
||||
prevent_registration = cast_bool(c["value"])
|
||||
elif c["key"] == "view_challenges_unregistered":
|
||||
view_challenges_unregistered = cast_bool(c["value"])
|
||||
elif c["key"] == "view_scoreboard_if_authed":
|
||||
view_scoreboard_if_authed = cast_bool(c["value"])
|
||||
|
||||
if c["key"] not in banned:
|
||||
new_conn["config"].insert(dict(c))
|
||||
|
||||
if workshop_mode:
|
||||
score_visibility = "admins"
|
||||
account_visibility = "admins"
|
||||
|
||||
if hide_scores:
|
||||
score_visibility = "hidden"
|
||||
|
||||
if prevent_registration:
|
||||
registration_visibility = "private"
|
||||
|
||||
if view_challenges_unregistered:
|
||||
challenge_visibility = "public"
|
||||
|
||||
if view_scoreboard_if_authed:
|
||||
score_visibility = "private"
|
||||
|
||||
new_conn["config"].insert({"key": "user_mode", "value": "users"})
|
||||
new_conn["config"].insert(
|
||||
{"key": "challenge_visibility", "value": challenge_visibility}
|
||||
)
|
||||
new_conn["config"].insert(
|
||||
{"key": "registration_visibility", "value": registration_visibility}
|
||||
)
|
||||
new_conn["config"].insert({"key": "score_visibility", "value": score_visibility})
|
||||
new_conn["config"].insert(
|
||||
{"key": "account_visibility", "value": account_visibility}
|
||||
)
|
||||
del old_data["config"]
|
||||
|
||||
manual = []
|
||||
not_created = []
|
||||
print("MIGRATING extra tables")
|
||||
for table in old_data.keys():
|
||||
print("MIGRATING", table)
|
||||
new_conn.create_table(table, primary_id=False)
|
||||
data = old_data[table]
|
||||
|
||||
ran = False
|
||||
for row in data:
|
||||
new_conn[table].insert(dict(row))
|
||||
ran = True
|
||||
else: # We finished inserting
|
||||
if ran:
|
||||
manual.append(table)
|
||||
|
||||
if ran is False:
|
||||
not_created.append(table)
|
||||
|
||||
print("Migration completed.")
|
||||
print(
|
||||
"The following tables require manual setting of primary keys and manual inspection"
|
||||
)
|
||||
for table in manual:
|
||||
print("\t", table)
|
||||
|
||||
print(
|
||||
"For example you can use the following commands if you know that the PRIMARY KEY for the table is `id`:"
|
||||
)
|
||||
for table in manual:
|
||||
print("\t", "ALTER TABLE `{table}` ADD PRIMARY KEY(id)".format(table=table))
|
||||
|
||||
print(
|
||||
"The following tables were not created because they were empty and must be manually recreated (e.g. app.db.create_all()"
|
||||
)
|
||||
for table in not_created:
|
||||
print("\t", table)
|
||||
45
migrations/alembic.ini
Normal file
45
migrations/alembic.ini
Normal file
@@ -0,0 +1,45 @@
|
||||
# A generic, single database configuration.
|
||||
|
||||
[alembic]
|
||||
# template used to generate migration files
|
||||
# file_template = %%(rev)s_%%(slug)s
|
||||
|
||||
# set to 'true' to run the environment during
|
||||
# the 'revision' command, regardless of autogenerate
|
||||
# revision_environment = false
|
||||
|
||||
|
||||
# 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
|
||||
96
migrations/env.py
Normal file
96
migrations/env.py
Normal file
@@ -0,0 +1,96 @@
|
||||
from __future__ import with_statement # noqa: I001
|
||||
|
||||
import logging
|
||||
from logging.config import fileConfig
|
||||
|
||||
from sqlalchemy import engine_from_config
|
||||
from sqlalchemy import pool
|
||||
|
||||
from alembic import context
|
||||
|
||||
# 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, disable_existing_loggers=False)
|
||||
logger = logging.getLogger("alembic.env")
|
||||
|
||||
# add your model's MetaData object here
|
||||
# for 'autogenerate' support
|
||||
# from myapp import mymodel
|
||||
# target_metadata = mymodel.Base.metadata
|
||||
from flask import current_app
|
||||
|
||||
config.set_main_option(
|
||||
"sqlalchemy.url",
|
||||
str(current_app.extensions["migrate"].db.engine.url).replace("%", "%%"),
|
||||
)
|
||||
target_metadata = current_app.extensions["migrate"].db.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 run_migrations_offline():
|
||||
"""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 = config.get_main_option("sqlalchemy.url")
|
||||
context.configure(url=url, target_metadata=target_metadata, literal_binds=True)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
def run_migrations_online():
|
||||
"""Run migrations in 'online' mode.
|
||||
|
||||
In this scenario we need to create an Engine
|
||||
and associate a connection with the context.
|
||||
|
||||
"""
|
||||
|
||||
# this callback is used to prevent an auto-migration from being generated
|
||||
# when there are no changes to the schema
|
||||
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
|
||||
def process_revision_directives(context, revision, directives):
|
||||
if getattr(config.cmd_opts, "autogenerate", False):
|
||||
script = directives[0]
|
||||
if script.upgrade_ops.is_empty():
|
||||
directives[:] = []
|
||||
logger.info("No changes in schema detected.")
|
||||
|
||||
connectable = engine_from_config(
|
||||
config.get_section(config.config_ini_section),
|
||||
prefix="sqlalchemy.",
|
||||
poolclass=pool.NullPool,
|
||||
)
|
||||
|
||||
with connectable.connect() as connection:
|
||||
context.configure(
|
||||
connection=connection,
|
||||
target_metadata=target_metadata,
|
||||
process_revision_directives=process_revision_directives,
|
||||
**current_app.extensions["migrate"].configure_args
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
run_migrations_online()
|
||||
24
migrations/script.py.mako
Normal file
24
migrations/script.py.mako
Normal file
@@ -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"}
|
||||
47
migrations/versions/0366ba6575ca_add_table_for_comments.py
Normal file
47
migrations/versions/0366ba6575ca_add_table_for_comments.py
Normal file
@@ -0,0 +1,47 @@
|
||||
"""Add table for comments
|
||||
|
||||
Revision ID: 0366ba6575ca
|
||||
Revises: 1093835a1051
|
||||
Create Date: 2020-08-14 00:46:54.161120
|
||||
|
||||
"""
|
||||
from alembic import op # noqa: I001
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "0366ba6575ca"
|
||||
down_revision = "1093835a1051"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table(
|
||||
"comments",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("type", sa.String(length=80), nullable=True),
|
||||
sa.Column("content", sa.Text(), nullable=True),
|
||||
sa.Column("date", sa.DateTime(), nullable=True),
|
||||
sa.Column("author_id", sa.Integer(), nullable=True),
|
||||
sa.Column("challenge_id", sa.Integer(), nullable=True),
|
||||
sa.Column("user_id", sa.Integer(), nullable=True),
|
||||
sa.Column("team_id", sa.Integer(), nullable=True),
|
||||
sa.Column("page_id", sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(["author_id"], ["users.id"], ondelete="CASCADE"),
|
||||
sa.ForeignKeyConstraint(
|
||||
["challenge_id"], ["challenges.id"], ondelete="CASCADE"
|
||||
),
|
||||
sa.ForeignKeyConstraint(["page_id"], ["pages.id"], ondelete="CASCADE"),
|
||||
sa.ForeignKeyConstraint(["team_id"], ["teams.id"], ondelete="CASCADE"),
|
||||
sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table("comments")
|
||||
# ### end Alembic commands ###
|
||||
28
migrations/versions/07dfbe5e1edc_add_format_to_pages.py
Normal file
28
migrations/versions/07dfbe5e1edc_add_format_to_pages.py
Normal file
@@ -0,0 +1,28 @@
|
||||
"""Add format to Pages
|
||||
|
||||
Revision ID: 07dfbe5e1edc
|
||||
Revises: 75e8ab9a0014
|
||||
Create Date: 2021-06-15 19:57:37.410152
|
||||
|
||||
"""
|
||||
from alembic import op # noqa: I001
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "07dfbe5e1edc"
|
||||
down_revision = "75e8ab9a0014"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column("pages", sa.Column("format", sa.String(length=80), nullable=True))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column("pages", "format")
|
||||
# ### end Alembic commands ###
|
||||
34
migrations/versions/080d29b15cd3_add_tokens_table.py
Normal file
34
migrations/versions/080d29b15cd3_add_tokens_table.py
Normal file
@@ -0,0 +1,34 @@
|
||||
"""Add Tokens table to store user access tokens
|
||||
|
||||
Revision ID: 080d29b15cd3
|
||||
Revises: b295b033364d
|
||||
Create Date: 2019-11-03 18:21:04.827015
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "080d29b15cd3"
|
||||
down_revision = "b295b033364d"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.create_table(
|
||||
"tokens",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("type", sa.String(length=32), nullable=True),
|
||||
sa.Column("user_id", sa.Integer(), nullable=True),
|
||||
sa.Column("created", sa.DateTime(), nullable=True),
|
||||
sa.Column("expiration", sa.DateTime(), nullable=True),
|
||||
sa.Column("value", sa.String(length=128), nullable=True),
|
||||
sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("value"),
|
||||
)
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_table("tokens")
|
||||
@@ -0,0 +1,23 @@
|
||||
"""Add language column to Users table
|
||||
|
||||
Revision ID: 0def790057c1
|
||||
Revises: 46a278193a94
|
||||
Create Date: 2023-04-19 00:56:54.592584
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "0def790057c1"
|
||||
down_revision = "46a278193a94"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column("users", sa.Column("language", sa.String(length=32), nullable=True))
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_column("users", "language")
|
||||
@@ -0,0 +1,70 @@
|
||||
"""Add default email templates
|
||||
|
||||
Revision ID: 1093835a1051
|
||||
Revises: a03403986a32
|
||||
Create Date: 2020-02-15 01:32:10.959373
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
from sqlalchemy.sql import column, table
|
||||
|
||||
from CTFd.models import db
|
||||
from CTFd.utils.email import (
|
||||
DEFAULT_PASSWORD_RESET_BODY,
|
||||
DEFAULT_PASSWORD_RESET_SUBJECT,
|
||||
DEFAULT_SUCCESSFUL_REGISTRATION_EMAIL_BODY,
|
||||
DEFAULT_SUCCESSFUL_REGISTRATION_EMAIL_SUBJECT,
|
||||
DEFAULT_USER_CREATION_EMAIL_BODY,
|
||||
DEFAULT_USER_CREATION_EMAIL_SUBJECT,
|
||||
DEFAULT_VERIFICATION_EMAIL_BODY,
|
||||
DEFAULT_VERIFICATION_EMAIL_SUBJECT,
|
||||
)
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "1093835a1051"
|
||||
down_revision = "a03403986a32"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
configs_table = table(
|
||||
"config", column("id", db.Integer), column("key", db.Text), column("value", db.Text)
|
||||
)
|
||||
|
||||
|
||||
def get_config(key):
|
||||
connection = op.get_bind()
|
||||
return connection.execute(
|
||||
configs_table.select().where(configs_table.c.key == key).limit(1)
|
||||
).fetchone()
|
||||
|
||||
|
||||
def set_config(key, value):
|
||||
connection = op.get_bind()
|
||||
connection.execute(configs_table.insert().values(key=key, value=value))
|
||||
|
||||
|
||||
def upgrade():
|
||||
# Only run if this instance already been setup before
|
||||
if bool(get_config("setup")) is True:
|
||||
for k, v in [
|
||||
("password_reset_body", DEFAULT_PASSWORD_RESET_BODY),
|
||||
("password_reset_subject", DEFAULT_PASSWORD_RESET_SUBJECT),
|
||||
(
|
||||
"successful_registration_email_body",
|
||||
DEFAULT_SUCCESSFUL_REGISTRATION_EMAIL_BODY,
|
||||
),
|
||||
(
|
||||
"successful_registration_email_subject",
|
||||
DEFAULT_SUCCESSFUL_REGISTRATION_EMAIL_SUBJECT,
|
||||
),
|
||||
("user_creation_email_body", DEFAULT_USER_CREATION_EMAIL_BODY),
|
||||
("user_creation_email_subject", DEFAULT_USER_CREATION_EMAIL_SUBJECT),
|
||||
("verification_email_body", DEFAULT_VERIFICATION_EMAIL_BODY),
|
||||
("verification_email_subject", DEFAULT_VERIFICATION_EMAIL_SUBJECT),
|
||||
]:
|
||||
if get_config(k) is None:
|
||||
set_config(k, v)
|
||||
|
||||
|
||||
def downgrade():
|
||||
pass
|
||||
@@ -0,0 +1,45 @@
|
||||
"""Convert rating values to votes
|
||||
|
||||
Revision ID: 24ad6790bc3c
|
||||
Revises: 55623b100da8
|
||||
Create Date: 2025-09-03 05:19:54.151054
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "24ad6790bc3c"
|
||||
down_revision = "55623b100da8"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# Convert 5-star ratings to upvote/downvote system
|
||||
# Values 1-2 become -1 (downvote)
|
||||
# Values 3-5 become 1 (upvote)
|
||||
|
||||
connection = op.get_bind()
|
||||
|
||||
# Update ratings with values 1 or 2 to -1 (downvote)
|
||||
connection.execute(sa.text("UPDATE ratings SET value = -1 WHERE value IN (1, 2)"))
|
||||
|
||||
# Update ratings with values 3, 4, or 5 to 1 (upvote)
|
||||
connection.execute(sa.text("UPDATE ratings SET value = 1 WHERE value IN (3, 4, 5)"))
|
||||
|
||||
|
||||
def downgrade():
|
||||
# This downgrade is not reversible since we lose the original 5-star granularity
|
||||
# We can only convert back to a simplified 5-star system
|
||||
# -1 (downvote) becomes 1 (1 star)
|
||||
# 1 (upvote) becomes 5 (5 stars)
|
||||
|
||||
connection = op.get_bind()
|
||||
|
||||
# Convert downvotes (-1) to 1-star ratings
|
||||
connection.execute(sa.text("UPDATE ratings SET value = 1 WHERE value = -1"))
|
||||
|
||||
# Convert upvotes (1) to 5-star ratings
|
||||
connection.execute(sa.text("UPDATE ratings SET value = 5 WHERE value = 1"))
|
||||
42
migrations/versions/364b4efa1686_add_ratings_table.py
Normal file
42
migrations/versions/364b4efa1686_add_ratings_table.py
Normal file
@@ -0,0 +1,42 @@
|
||||
"""Add ratings table
|
||||
|
||||
Revision ID: 364b4efa1686
|
||||
Revises: 662d728ad7da
|
||||
Create Date: 2025-08-17 07:07:41.484323
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "364b4efa1686"
|
||||
down_revision = "662d728ad7da"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table(
|
||||
"ratings",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("user_id", sa.Integer(), nullable=True),
|
||||
sa.Column("challenge_id", sa.Integer(), nullable=True),
|
||||
sa.Column("value", sa.Integer(), nullable=True),
|
||||
sa.Column("review", sa.String(length=2000), nullable=True),
|
||||
sa.Column("date", sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["challenge_id"], ["challenges.id"], ondelete="CASCADE"
|
||||
),
|
||||
sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("user_id", "challenge_id"),
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table("ratings")
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,46 @@
|
||||
"""Enable millisecond precision in MySQL datetime
|
||||
|
||||
Revision ID: 46a278193a94
|
||||
Revises: 4d3c1b59d011
|
||||
Create Date: 2022-11-01 23:27:44.620893
|
||||
|
||||
"""
|
||||
from alembic import op # noqa: I001
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "46a278193a94"
|
||||
down_revision = "4d3c1b59d011"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
url = str(bind.engine.url)
|
||||
if url.startswith("mysql"):
|
||||
get_columns = "SELECT `TABLE_NAME`, `COLUMN_NAME` FROM `information_schema`.`COLUMNS` WHERE `table_schema`=DATABASE() AND `DATA_TYPE`='datetime' AND `COLUMN_TYPE`='datetime';"
|
||||
conn = op.get_bind()
|
||||
columns = conn.execute(get_columns).fetchall()
|
||||
for table_name, column_name in columns:
|
||||
op.alter_column(
|
||||
table_name=table_name,
|
||||
column_name=column_name,
|
||||
type_=mysql.DATETIME(fsp=6),
|
||||
)
|
||||
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
url = str(bind.engine.url)
|
||||
if url.startswith("mysql"):
|
||||
get_columns = "SELECT `TABLE_NAME`, `COLUMN_NAME` FROM `information_schema`.`COLUMNS` WHERE `table_schema`=DATABASE() AND `DATA_TYPE`='datetime' AND `COLUMN_TYPE`='datetime(6)';"
|
||||
conn = op.get_bind()
|
||||
columns = conn.execute(get_columns).fetchall()
|
||||
for table_name, column_name in columns:
|
||||
op.alter_column(
|
||||
table_name=table_name,
|
||||
column_name=column_name,
|
||||
type_=mysql.DATETIME(fsp=0),
|
||||
)
|
||||
@@ -0,0 +1,32 @@
|
||||
"""Add next_id to Challenges table
|
||||
|
||||
Revision ID: 4d3c1b59d011
|
||||
Revises: 6012fe8de495
|
||||
Create Date: 2022-04-07 03:53:27.554190
|
||||
|
||||
"""
|
||||
from alembic import op # noqa: I001
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "4d3c1b59d011"
|
||||
down_revision = "6012fe8de495"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column("challenges", sa.Column("next_id", sa.Integer(), nullable=True))
|
||||
op.create_foreign_key(
|
||||
None, "challenges", "challenges", ["next_id"], ["id"], ondelete="SET NULL"
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_constraint(None, "challenges", type_="foreignkey")
|
||||
op.drop_column("challenges", "next_id")
|
||||
# ### end Alembic commands ###
|
||||
32
migrations/versions/4e4d5a9ea000_add_type_to_awards.py
Normal file
32
migrations/versions/4e4d5a9ea000_add_type_to_awards.py
Normal file
@@ -0,0 +1,32 @@
|
||||
"""Add type to awards
|
||||
|
||||
Revision ID: 4e4d5a9ea000
|
||||
Revises: 8369118943a1
|
||||
Create Date: 2019-04-07 19:37:17.872128
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "4e4d5a9ea000"
|
||||
down_revision = "8369118943a1"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column(
|
||||
"awards",
|
||||
sa.Column(
|
||||
"type", sa.String(length=80), nullable=True, server_default="standard"
|
||||
),
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column("awards", "type")
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,24 @@
|
||||
"""Add attribution to Challenges
|
||||
|
||||
Revision ID: 4fe3eeed9a9d
|
||||
Revises: a02c5bf43407
|
||||
Create Date: 2024-09-07 01:02:28.997761
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "4fe3eeed9a9d"
|
||||
down_revision = "a02c5bf43407"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column("challenges", sa.Column("attribution", sa.Text(), nullable=True))
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_column("challenges", "attribution")
|
||||
@@ -0,0 +1,28 @@
|
||||
"""Add target column to Tracking
|
||||
|
||||
Revision ID: 55623b100da8
|
||||
Revises: 5c98d9253f56
|
||||
Create Date: 2025-08-26 23:30:29.170546
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "55623b100da8"
|
||||
down_revision = "5c98d9253f56"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column("tracking", sa.Column("target", sa.Integer(), nullable=True))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column("tracking", "target")
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,23 @@
|
||||
"""Add sha1sum field to Files
|
||||
|
||||
Revision ID: 5c4996aeb2cb
|
||||
Revises: 9e6f6578ca84
|
||||
Create Date: 2024-01-07 13:09:08.843903
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "5c4996aeb2cb"
|
||||
down_revision = "9e6f6578ca84"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column("files", sa.Column("sha1sum", sa.String(length=40), nullable=True))
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_column("files", "sha1sum")
|
||||
119
migrations/versions/5c98d9253f56_rename_core_beta_to_core.py
Normal file
119
migrations/versions/5c98d9253f56_rename_core_beta_to_core.py
Normal file
@@ -0,0 +1,119 @@
|
||||
"""Rename core-beta to core
|
||||
|
||||
Revision ID: 5c98d9253f56
|
||||
Revises: 364b4efa1686
|
||||
Create Date: 2025-08-24 16:23:18.795064
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
from sqlalchemy.sql import column, table
|
||||
|
||||
from CTFd.models import db
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "5c98d9253f56"
|
||||
down_revision = "364b4efa1686"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
configs_table = table(
|
||||
"config", column("id", db.Integer), column("key", db.Text), column("value", db.Text)
|
||||
)
|
||||
|
||||
pages_table = table("pages", column("id", db.Integer), column("content", db.Text))
|
||||
|
||||
|
||||
def upgrade():
|
||||
connection = op.get_bind()
|
||||
|
||||
# Define theme transformations: old_theme -> new_theme
|
||||
theme_transformations = {
|
||||
"core-beta": "core",
|
||||
"hacker-beta-theme": "hacker-theme",
|
||||
"learning-beta-theme": "learning-theme",
|
||||
"learning": "learning-theme",
|
||||
}
|
||||
|
||||
# Find the ctf_theme config entry
|
||||
theme_config = connection.execute(
|
||||
configs_table.select().where(configs_table.c.key == "ctf_theme")
|
||||
).fetchone()
|
||||
|
||||
# Update ctf_theme config
|
||||
if (
|
||||
theme_config
|
||||
and theme_config.value
|
||||
and theme_config.value in theme_transformations
|
||||
):
|
||||
new_value = theme_transformations[theme_config.value]
|
||||
connection.execute(
|
||||
configs_table.update()
|
||||
.where(configs_table.c.id == theme_config.id)
|
||||
.values(value=new_value)
|
||||
)
|
||||
|
||||
# Update pages content for all theme transformations
|
||||
for old_theme, new_theme in theme_transformations.items():
|
||||
old_path = f"themes/{old_theme}/static"
|
||||
new_path = f"themes/{new_theme}/static"
|
||||
|
||||
pages_with_old_theme = connection.execute(
|
||||
pages_table.select().where(pages_table.c.content.like(f"%{old_path}%"))
|
||||
).fetchall()
|
||||
|
||||
for page in pages_with_old_theme:
|
||||
if page.content:
|
||||
new_content = page.content.replace(old_path, new_path)
|
||||
connection.execute(
|
||||
pages_table.update()
|
||||
.where(pages_table.c.id == page.id)
|
||||
.values(content=new_content)
|
||||
)
|
||||
|
||||
|
||||
def downgrade():
|
||||
connection = op.get_bind()
|
||||
|
||||
# Define reverse theme transformations: new_theme -> old_theme
|
||||
reverse_theme_transformations = {
|
||||
"core": "core-beta",
|
||||
"hacker-theme": "hacker-beta-theme",
|
||||
"learning-theme": "learning-beta-theme",
|
||||
}
|
||||
|
||||
# Find the ctf_theme config entry
|
||||
theme_config = connection.execute(
|
||||
configs_table.select().where(configs_table.c.key == "ctf_theme")
|
||||
).fetchone()
|
||||
|
||||
# Restore ctf_theme config
|
||||
if (
|
||||
theme_config
|
||||
and theme_config.value
|
||||
and theme_config.value in reverse_theme_transformations
|
||||
):
|
||||
new_value = reverse_theme_transformations[theme_config.value]
|
||||
connection.execute(
|
||||
configs_table.update()
|
||||
.where(configs_table.c.id == theme_config.id)
|
||||
.values(value=new_value)
|
||||
)
|
||||
|
||||
# Restore pages content for all theme transformations
|
||||
for new_theme, old_theme in reverse_theme_transformations.items():
|
||||
new_path = f"themes/{new_theme}/static"
|
||||
old_path = f"themes/{old_theme}/static"
|
||||
|
||||
pages_with_new_theme = connection.execute(
|
||||
pages_table.select().where(pages_table.c.content.like(f"%{new_path}%"))
|
||||
).fetchall()
|
||||
|
||||
for page in pages_with_new_theme:
|
||||
if page.content and old_path not in page.content:
|
||||
new_content = page.content.replace(new_path, old_path)
|
||||
connection.execute(
|
||||
pages_table.update()
|
||||
.where(pages_table.c.id == page.id)
|
||||
.values(content=new_content)
|
||||
)
|
||||
@@ -0,0 +1,28 @@
|
||||
"""Add connection_info column to Challenges
|
||||
|
||||
Revision ID: 6012fe8de495
|
||||
Revises: ef87d69ec29a
|
||||
Create Date: 2021-07-30 03:50:54.219124
|
||||
|
||||
"""
|
||||
from alembic import op # noqa: I001
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "6012fe8de495"
|
||||
down_revision = "ef87d69ec29a"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column("challenges", sa.Column("connection_info", sa.Text(), nullable=True))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column("challenges", "connection_info")
|
||||
# ### end Alembic commands ###
|
||||
43
migrations/versions/62bf576b2cd3_add_solutions_table.py
Normal file
43
migrations/versions/62bf576b2cd3_add_solutions_table.py
Normal file
@@ -0,0 +1,43 @@
|
||||
"""Add Solutions table
|
||||
|
||||
Revision ID: 62bf576b2cd3
|
||||
Revises: a49ad66aa0f1
|
||||
Create Date: 2025-07-28 20:04:45.082529
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "62bf576b2cd3"
|
||||
down_revision = "a49ad66aa0f1"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table(
|
||||
"solutions",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("challenge_id", sa.Integer(), nullable=True),
|
||||
sa.Column("content", sa.Text(), nullable=True),
|
||||
sa.Column("state", sa.String(length=80), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["challenge_id"], ["challenges.id"], ondelete="CASCADE"
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("challenge_id"),
|
||||
)
|
||||
op.add_column("files", sa.Column("solution_id", sa.Integer(), nullable=True))
|
||||
op.create_foreign_key(None, "files", "solutions", ["solution_id"], ["id"])
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_constraint(None, "files", type_="foreignkey")
|
||||
op.drop_column("files", "solution_id")
|
||||
op.drop_table("solutions")
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,28 @@
|
||||
"""Add change_password to Users
|
||||
|
||||
Revision ID: 662d728ad7da
|
||||
Revises: f73a96c97449
|
||||
Create Date: 2025-08-11 16:38:48.891702
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "662d728ad7da"
|
||||
down_revision = "f73a96c97449"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column("users", sa.Column("change_password", sa.Boolean(), nullable=True))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column("users", "change_password")
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,36 @@
|
||||
"""Add dynamic scoring columns to Challenges table
|
||||
|
||||
Revision ID: 67ebab6de598
|
||||
Revises: 24ad6790bc3c
|
||||
Create Date: 2025-09-11 08:57:33.156731
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "67ebab6de598"
|
||||
down_revision = "24ad6790bc3c"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column("challenges", sa.Column("initial", sa.Integer(), nullable=True))
|
||||
op.add_column("challenges", sa.Column("minimum", sa.Integer(), nullable=True))
|
||||
op.add_column("challenges", sa.Column("decay", sa.Integer(), nullable=True))
|
||||
op.add_column(
|
||||
"challenges", sa.Column("function", sa.String(length=32), nullable=True)
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column("challenges", "function")
|
||||
op.drop_column("challenges", "decay")
|
||||
op.drop_column("challenges", "minimum")
|
||||
op.drop_column("challenges", "initial")
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,53 @@
|
||||
"""Add Fields and FieldEntries tables
|
||||
|
||||
Revision ID: 75e8ab9a0014
|
||||
Revises: 0366ba6575ca
|
||||
Create Date: 2020-08-19 00:36:17.579497
|
||||
|
||||
"""
|
||||
from alembic import op # noqa: I001
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "75e8ab9a0014"
|
||||
down_revision = "0366ba6575ca"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table(
|
||||
"fields",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("name", sa.Text(), nullable=True),
|
||||
sa.Column("type", sa.String(length=80), nullable=True),
|
||||
sa.Column("field_type", sa.String(length=80), nullable=True),
|
||||
sa.Column("description", sa.Text(), nullable=True),
|
||||
sa.Column("required", sa.Boolean(), nullable=True),
|
||||
sa.Column("public", sa.Boolean(), nullable=True),
|
||||
sa.Column("editable", sa.Boolean(), nullable=True),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_table(
|
||||
"field_entries",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("type", sa.String(length=80), nullable=True),
|
||||
sa.Column("value", sa.JSON(), nullable=True),
|
||||
sa.Column("field_id", sa.Integer(), nullable=True),
|
||||
sa.Column("user_id", sa.Integer(), nullable=True),
|
||||
sa.Column("team_id", sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(["field_id"], ["fields.id"], ondelete="CASCADE"),
|
||||
sa.ForeignKeyConstraint(["team_id"], ["teams.id"], ondelete="CASCADE"),
|
||||
sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table("field_entries")
|
||||
op.drop_table("fields")
|
||||
# ### end Alembic commands ###
|
||||
249
migrations/versions/8369118943a1_initial_revision.py
Normal file
249
migrations/versions/8369118943a1_initial_revision.py
Normal file
@@ -0,0 +1,249 @@
|
||||
"""Initial Revision
|
||||
|
||||
Revision ID: 8369118943a1
|
||||
Revises:
|
||||
Create Date: 2018-11-05 01:06:24.495010
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "8369118943a1"
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table(
|
||||
"challenges",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("name", sa.String(length=80), nullable=True),
|
||||
sa.Column("description", sa.Text(), nullable=True),
|
||||
sa.Column("max_attempts", sa.Integer(), nullable=True),
|
||||
sa.Column("value", sa.Integer(), nullable=True),
|
||||
sa.Column("category", sa.String(length=80), nullable=True),
|
||||
sa.Column("type", sa.String(length=80), nullable=True),
|
||||
sa.Column("state", sa.String(length=80), nullable=False),
|
||||
sa.Column("requirements", sa.JSON(), nullable=True),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_table(
|
||||
"config",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("key", sa.Text(), nullable=True),
|
||||
sa.Column("value", sa.Text(), nullable=True),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_table(
|
||||
"pages",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("title", sa.String(length=80), nullable=True),
|
||||
sa.Column("route", sa.String(length=128), nullable=True),
|
||||
sa.Column("content", sa.Text(), nullable=True),
|
||||
sa.Column("draft", sa.Boolean(), nullable=True),
|
||||
sa.Column("hidden", sa.Boolean(), nullable=True),
|
||||
sa.Column("auth_required", sa.Boolean(), nullable=True),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("route"),
|
||||
)
|
||||
op.create_table(
|
||||
"teams",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("oauth_id", sa.Integer(), nullable=True),
|
||||
sa.Column("name", sa.String(length=128), nullable=True),
|
||||
sa.Column("email", sa.String(length=128), nullable=True),
|
||||
sa.Column("password", sa.String(length=128), nullable=True),
|
||||
sa.Column("secret", sa.String(length=128), nullable=True),
|
||||
sa.Column("website", sa.String(length=128), nullable=True),
|
||||
sa.Column("affiliation", sa.String(length=128), nullable=True),
|
||||
sa.Column("country", sa.String(length=32), nullable=True),
|
||||
sa.Column("bracket", sa.String(length=32), nullable=True),
|
||||
sa.Column("hidden", sa.Boolean(), nullable=True),
|
||||
sa.Column("banned", sa.Boolean(), nullable=True),
|
||||
sa.Column("created", sa.DateTime(), nullable=True),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("email"),
|
||||
sa.UniqueConstraint("id", "oauth_id"),
|
||||
sa.UniqueConstraint("oauth_id"),
|
||||
)
|
||||
op.create_table(
|
||||
"dynamic_challenge",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("initial", sa.Integer(), nullable=True),
|
||||
sa.Column("minimum", sa.Integer(), nullable=True),
|
||||
sa.Column("decay", sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(["id"], ["challenges.id"]),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_table(
|
||||
"files",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("type", sa.String(length=80), nullable=True),
|
||||
sa.Column("location", sa.Text(), nullable=True),
|
||||
sa.Column("challenge_id", sa.Integer(), nullable=True),
|
||||
sa.Column("page_id", sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(["challenge_id"], ["challenges.id"]),
|
||||
sa.ForeignKeyConstraint(["page_id"], ["pages.id"]),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_table(
|
||||
"flags",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("challenge_id", sa.Integer(), nullable=True),
|
||||
sa.Column("type", sa.String(length=80), nullable=True),
|
||||
sa.Column("content", sa.Text(), nullable=True),
|
||||
sa.Column("data", sa.Text(), nullable=True),
|
||||
sa.ForeignKeyConstraint(["challenge_id"], ["challenges.id"]),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_table(
|
||||
"hints",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("type", sa.String(length=80), nullable=True),
|
||||
sa.Column("challenge_id", sa.Integer(), nullable=True),
|
||||
sa.Column("content", sa.Text(), nullable=True),
|
||||
sa.Column("cost", sa.Integer(), nullable=True),
|
||||
sa.Column("requirements", sa.JSON(), nullable=True),
|
||||
sa.ForeignKeyConstraint(["challenge_id"], ["challenges.id"]),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_table(
|
||||
"tags",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("challenge_id", sa.Integer(), nullable=True),
|
||||
sa.Column("value", sa.String(length=80), nullable=True),
|
||||
sa.ForeignKeyConstraint(["challenge_id"], ["challenges.id"]),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_table(
|
||||
"users",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("oauth_id", sa.Integer(), nullable=True),
|
||||
sa.Column("name", sa.String(length=128), nullable=True),
|
||||
sa.Column("password", sa.String(length=128), nullable=True),
|
||||
sa.Column("email", sa.String(length=128), nullable=True),
|
||||
sa.Column("type", sa.String(length=80), nullable=True),
|
||||
sa.Column("secret", sa.String(length=128), nullable=True),
|
||||
sa.Column("website", sa.String(length=128), nullable=True),
|
||||
sa.Column("affiliation", sa.String(length=128), nullable=True),
|
||||
sa.Column("country", sa.String(length=32), nullable=True),
|
||||
sa.Column("bracket", sa.String(length=32), nullable=True),
|
||||
sa.Column("hidden", sa.Boolean(), nullable=True),
|
||||
sa.Column("banned", sa.Boolean(), nullable=True),
|
||||
sa.Column("verified", sa.Boolean(), nullable=True),
|
||||
sa.Column("team_id", sa.Integer(), nullable=True),
|
||||
sa.Column("created", sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(["team_id"], ["teams.id"]),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("email"),
|
||||
sa.UniqueConstraint("id", "oauth_id"),
|
||||
sa.UniqueConstraint("oauth_id"),
|
||||
)
|
||||
op.create_table(
|
||||
"awards",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("user_id", sa.Integer(), nullable=True),
|
||||
sa.Column("team_id", sa.Integer(), nullable=True),
|
||||
sa.Column("name", sa.String(length=80), nullable=True),
|
||||
sa.Column("description", sa.Text(), nullable=True),
|
||||
sa.Column("date", sa.DateTime(), nullable=True),
|
||||
sa.Column("value", sa.Integer(), nullable=True),
|
||||
sa.Column("category", sa.String(length=80), nullable=True),
|
||||
sa.Column("icon", sa.Text(), nullable=True),
|
||||
sa.Column("requirements", sa.JSON(), nullable=True),
|
||||
sa.ForeignKeyConstraint(["team_id"], ["teams.id"]),
|
||||
sa.ForeignKeyConstraint(["user_id"], ["users.id"]),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_table(
|
||||
"notifications",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("title", sa.Text(), nullable=True),
|
||||
sa.Column("content", sa.Text(), nullable=True),
|
||||
sa.Column("date", sa.DateTime(), nullable=True),
|
||||
sa.Column("user_id", sa.Integer(), nullable=True),
|
||||
sa.Column("team_id", sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(["team_id"], ["teams.id"]),
|
||||
sa.ForeignKeyConstraint(["user_id"], ["users.id"]),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_table(
|
||||
"submissions",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("challenge_id", sa.Integer(), nullable=True),
|
||||
sa.Column("user_id", sa.Integer(), nullable=True),
|
||||
sa.Column("team_id", sa.Integer(), nullable=True),
|
||||
sa.Column("ip", sa.String(length=46), nullable=True),
|
||||
sa.Column("provided", sa.Text(), nullable=True),
|
||||
sa.Column("type", sa.String(length=32), nullable=True),
|
||||
sa.Column("date", sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["challenge_id"], ["challenges.id"], ondelete="CASCADE"
|
||||
),
|
||||
sa.ForeignKeyConstraint(["team_id"], ["teams.id"], ondelete="CASCADE"),
|
||||
sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_table(
|
||||
"tracking",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("type", sa.String(length=32), nullable=True),
|
||||
sa.Column("ip", sa.String(length=46), nullable=True),
|
||||
sa.Column("user_id", sa.Integer(), nullable=True),
|
||||
sa.Column("date", sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(["user_id"], ["users.id"]),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_table(
|
||||
"unlocks",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("user_id", sa.Integer(), nullable=True),
|
||||
sa.Column("team_id", sa.Integer(), nullable=True),
|
||||
sa.Column("target", sa.Integer(), nullable=True),
|
||||
sa.Column("date", sa.DateTime(), nullable=True),
|
||||
sa.Column("type", sa.String(length=32), nullable=True),
|
||||
sa.ForeignKeyConstraint(["team_id"], ["teams.id"]),
|
||||
sa.ForeignKeyConstraint(["user_id"], ["users.id"]),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_table(
|
||||
"solves",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("challenge_id", sa.Integer(), nullable=True),
|
||||
sa.Column("user_id", sa.Integer(), nullable=True),
|
||||
sa.Column("team_id", sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["challenge_id"], ["challenges.id"], ondelete="CASCADE"
|
||||
),
|
||||
sa.ForeignKeyConstraint(["id"], ["submissions.id"], ondelete="CASCADE"),
|
||||
sa.ForeignKeyConstraint(["team_id"], ["teams.id"], ondelete="CASCADE"),
|
||||
sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("challenge_id", "team_id"),
|
||||
sa.UniqueConstraint("challenge_id", "user_id"),
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table("solves")
|
||||
op.drop_table("unlocks")
|
||||
op.drop_table("tracking")
|
||||
op.drop_table("submissions")
|
||||
op.drop_table("notifications")
|
||||
op.drop_table("awards")
|
||||
op.drop_table("users")
|
||||
op.drop_table("tags")
|
||||
op.drop_table("hints")
|
||||
op.drop_table("flags")
|
||||
op.drop_table("files")
|
||||
op.drop_table("dynamic_challenge")
|
||||
op.drop_table("teams")
|
||||
op.drop_table("pages")
|
||||
op.drop_table("config")
|
||||
op.drop_table("challenges")
|
||||
# ### end Alembic commands ###
|
||||
51
migrations/versions/9889b8c53673_add_brackets_table.py
Normal file
51
migrations/versions/9889b8c53673_add_brackets_table.py
Normal file
@@ -0,0 +1,51 @@
|
||||
"""Add Brackets table
|
||||
|
||||
Revision ID: 9889b8c53673
|
||||
Revises: 5c4996aeb2cb
|
||||
Create Date: 2024-01-25 03:17:52.734753
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "9889b8c53673"
|
||||
down_revision = "5c4996aeb2cb"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.create_table(
|
||||
"brackets",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("name", sa.String(length=255), nullable=True),
|
||||
sa.Column("description", sa.Text(), nullable=True),
|
||||
sa.Column("type", sa.String(length=80), nullable=True),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.add_column("teams", sa.Column("bracket_id", sa.Integer(), nullable=True))
|
||||
op.create_foreign_key(
|
||||
None, "teams", "brackets", ["bracket_id"], ["id"], ondelete="SET NULL"
|
||||
)
|
||||
op.drop_column("teams", "bracket")
|
||||
op.add_column("users", sa.Column("bracket_id", sa.Integer(), nullable=True))
|
||||
op.create_foreign_key(
|
||||
None, "users", "brackets", ["bracket_id"], ["id"], ondelete="SET NULL"
|
||||
)
|
||||
op.drop_column("users", "bracket")
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.add_column(
|
||||
"users", sa.Column("bracket", mysql.VARCHAR(length=32), nullable=True)
|
||||
)
|
||||
op.drop_constraint(None, "users", type_="foreignkey")
|
||||
op.drop_column("users", "bracket_id")
|
||||
op.add_column(
|
||||
"teams", sa.Column("bracket", mysql.VARCHAR(length=32), nullable=True)
|
||||
)
|
||||
op.drop_constraint(None, "teams", type_="foreignkey")
|
||||
op.drop_column("teams", "bracket_id")
|
||||
op.drop_table("brackets")
|
||||
@@ -0,0 +1,23 @@
|
||||
"""Add description column to tokens table
|
||||
|
||||
Revision ID: 9e6f6578ca84
|
||||
Revises: 0def790057c1
|
||||
Create Date: 2023-06-21 23:22:34.179636
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "9e6f6578ca84"
|
||||
down_revision = "0def790057c1"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column("tokens", sa.Column("description", sa.Text(), nullable=True))
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_column("tokens", "description")
|
||||
26
migrations/versions/a02c5bf43407_add_link_target_to_pages.py
Normal file
26
migrations/versions/a02c5bf43407_add_link_target_to_pages.py
Normal file
@@ -0,0 +1,26 @@
|
||||
"""Add link_target to Pages
|
||||
|
||||
Revision ID: a02c5bf43407
|
||||
Revises: 9889b8c53673
|
||||
Create Date: 2024-02-01 13:11:53.076825
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "a02c5bf43407"
|
||||
down_revision = "9889b8c53673"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column(
|
||||
"pages", sa.Column("link_target", sa.String(length=80), nullable=True)
|
||||
)
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_column("pages", "link_target")
|
||||
@@ -0,0 +1,46 @@
|
||||
"""add theme code injections to configs
|
||||
|
||||
Revision ID: a03403986a32
|
||||
Revises: 080d29b15cd3
|
||||
Create Date: 2020-02-13 01:10:16.430424
|
||||
|
||||
"""
|
||||
from alembic import op # noqa: I001
|
||||
from sqlalchemy.sql import column, table
|
||||
|
||||
from CTFd.models import db
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "a03403986a32"
|
||||
down_revision = "080d29b15cd3"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
configs_table = table(
|
||||
"config", column("id", db.Integer), column("key", db.Text), column("value", db.Text)
|
||||
)
|
||||
|
||||
|
||||
def upgrade():
|
||||
connection = op.get_bind()
|
||||
css = connection.execute(
|
||||
configs_table.select().where(configs_table.c.key == "css").limit(1)
|
||||
).fetchone()
|
||||
|
||||
if css and css.value:
|
||||
new_css = "<style>\n" + css.value + "\n</style>"
|
||||
config = connection.execute(
|
||||
configs_table.select().where(configs_table.c.key == "theme_header").limit(1)
|
||||
).fetchone()
|
||||
if config:
|
||||
# Do not overwrite existing theme_header value
|
||||
pass
|
||||
else:
|
||||
connection.execute(
|
||||
configs_table.insert().values(key="theme_header", value=new_css)
|
||||
)
|
||||
|
||||
|
||||
def downgrade():
|
||||
pass
|
||||
23
migrations/versions/a49ad66aa0f1_add_title_to_hint.py
Normal file
23
migrations/versions/a49ad66aa0f1_add_title_to_hint.py
Normal file
@@ -0,0 +1,23 @@
|
||||
"""add title to hint
|
||||
|
||||
Revision ID: a49ad66aa0f1
|
||||
Revises: 4fe3eeed9a9d
|
||||
Create Date: 2025-02-10 14:45:00.933880
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "a49ad66aa0f1"
|
||||
down_revision = "4fe3eeed9a9d"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column("hints", sa.Column("title", sa.String(length=80), nullable=True))
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_column("hints", "title")
|
||||
@@ -0,0 +1,298 @@
|
||||
"""Add ondelete cascade to foreign keys
|
||||
|
||||
Revision ID: b295b033364d
|
||||
Revises: b5551cd26764
|
||||
Create Date: 2019-05-03 19:26:57.746887
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "b295b033364d"
|
||||
down_revision = "b5551cd26764"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
url = str(bind.engine.url)
|
||||
if url.startswith("mysql"):
|
||||
op.drop_constraint("awards_ibfk_1", "awards", type_="foreignkey")
|
||||
op.drop_constraint("awards_ibfk_2", "awards", type_="foreignkey")
|
||||
op.create_foreign_key(
|
||||
"awards_ibfk_1", "awards", "teams", ["team_id"], ["id"], ondelete="CASCADE"
|
||||
)
|
||||
op.create_foreign_key(
|
||||
"awards_ibfk_2", "awards", "users", ["user_id"], ["id"], ondelete="CASCADE"
|
||||
)
|
||||
|
||||
op.drop_constraint("files_ibfk_1", "files", type_="foreignkey")
|
||||
op.create_foreign_key(
|
||||
"files_ibfk_1",
|
||||
"files",
|
||||
"challenges",
|
||||
["challenge_id"],
|
||||
["id"],
|
||||
ondelete="CASCADE",
|
||||
)
|
||||
|
||||
op.drop_constraint("flags_ibfk_1", "flags", type_="foreignkey")
|
||||
op.create_foreign_key(
|
||||
"flags_ibfk_1",
|
||||
"flags",
|
||||
"challenges",
|
||||
["challenge_id"],
|
||||
["id"],
|
||||
ondelete="CASCADE",
|
||||
)
|
||||
|
||||
op.drop_constraint("hints_ibfk_1", "hints", type_="foreignkey")
|
||||
op.create_foreign_key(
|
||||
"hints_ibfk_1",
|
||||
"hints",
|
||||
"challenges",
|
||||
["challenge_id"],
|
||||
["id"],
|
||||
ondelete="CASCADE",
|
||||
)
|
||||
|
||||
op.drop_constraint("tags_ibfk_1", "tags", type_="foreignkey")
|
||||
op.create_foreign_key(
|
||||
"tags_ibfk_1",
|
||||
"tags",
|
||||
"challenges",
|
||||
["challenge_id"],
|
||||
["id"],
|
||||
ondelete="CASCADE",
|
||||
)
|
||||
|
||||
op.drop_constraint("team_captain_id", "teams", type_="foreignkey")
|
||||
op.create_foreign_key(
|
||||
"team_captain_id",
|
||||
"teams",
|
||||
"users",
|
||||
["captain_id"],
|
||||
["id"],
|
||||
ondelete="SET NULL",
|
||||
)
|
||||
|
||||
op.drop_constraint("tracking_ibfk_1", "tracking", type_="foreignkey")
|
||||
op.create_foreign_key(
|
||||
"tracking_ibfk_1",
|
||||
"tracking",
|
||||
"users",
|
||||
["user_id"],
|
||||
["id"],
|
||||
ondelete="CASCADE",
|
||||
)
|
||||
|
||||
op.drop_constraint("unlocks_ibfk_1", "unlocks", type_="foreignkey")
|
||||
op.drop_constraint("unlocks_ibfk_2", "unlocks", type_="foreignkey")
|
||||
op.create_foreign_key(
|
||||
"unlocks_ibfk_1",
|
||||
"unlocks",
|
||||
"teams",
|
||||
["team_id"],
|
||||
["id"],
|
||||
ondelete="CASCADE",
|
||||
)
|
||||
op.create_foreign_key(
|
||||
"unlocks_ibfk_2",
|
||||
"unlocks",
|
||||
"users",
|
||||
["user_id"],
|
||||
["id"],
|
||||
ondelete="CASCADE",
|
||||
)
|
||||
elif url.startswith("postgres"):
|
||||
op.drop_constraint("awards_team_id_fkey", "awards", type_="foreignkey")
|
||||
op.drop_constraint("awards_user_id_fkey", "awards", type_="foreignkey")
|
||||
op.create_foreign_key(
|
||||
"awards_team_id_fkey",
|
||||
"awards",
|
||||
"teams",
|
||||
["team_id"],
|
||||
["id"],
|
||||
ondelete="CASCADE",
|
||||
)
|
||||
op.create_foreign_key(
|
||||
"awards_user_id_fkey",
|
||||
"awards",
|
||||
"users",
|
||||
["user_id"],
|
||||
["id"],
|
||||
ondelete="CASCADE",
|
||||
)
|
||||
|
||||
op.drop_constraint("files_challenge_id_fkey", "files", type_="foreignkey")
|
||||
op.create_foreign_key(
|
||||
"files_challenge_id_fkey",
|
||||
"files",
|
||||
"challenges",
|
||||
["challenge_id"],
|
||||
["id"],
|
||||
ondelete="CASCADE",
|
||||
)
|
||||
|
||||
op.drop_constraint("flags_challenge_id_fkey", "flags", type_="foreignkey")
|
||||
op.create_foreign_key(
|
||||
"flags_challenge_id_fkey",
|
||||
"flags",
|
||||
"challenges",
|
||||
["challenge_id"],
|
||||
["id"],
|
||||
ondelete="CASCADE",
|
||||
)
|
||||
|
||||
op.drop_constraint("hints_challenge_id_fkey", "hints", type_="foreignkey")
|
||||
op.create_foreign_key(
|
||||
"hints_challenge_id_fkey",
|
||||
"hints",
|
||||
"challenges",
|
||||
["challenge_id"],
|
||||
["id"],
|
||||
ondelete="CASCADE",
|
||||
)
|
||||
|
||||
op.drop_constraint("tags_challenge_id_fkey", "tags", type_="foreignkey")
|
||||
op.create_foreign_key(
|
||||
"tags_challenge_id_fkey",
|
||||
"tags",
|
||||
"challenges",
|
||||
["challenge_id"],
|
||||
["id"],
|
||||
ondelete="CASCADE",
|
||||
)
|
||||
|
||||
op.drop_constraint("team_captain_id", "teams", type_="foreignkey")
|
||||
op.create_foreign_key(
|
||||
"team_captain_id",
|
||||
"teams",
|
||||
"users",
|
||||
["captain_id"],
|
||||
["id"],
|
||||
ondelete="SET NULL",
|
||||
)
|
||||
|
||||
op.drop_constraint("tracking_user_id_fkey", "tracking", type_="foreignkey")
|
||||
op.create_foreign_key(
|
||||
"tracking_user_id_fkey",
|
||||
"tracking",
|
||||
"users",
|
||||
["user_id"],
|
||||
["id"],
|
||||
ondelete="CASCADE",
|
||||
)
|
||||
|
||||
op.drop_constraint("unlocks_team_id_fkey", "unlocks", type_="foreignkey")
|
||||
op.drop_constraint("unlocks_user_id_fkey", "unlocks", type_="foreignkey")
|
||||
op.create_foreign_key(
|
||||
"unlocks_team_id_fkey",
|
||||
"unlocks",
|
||||
"teams",
|
||||
["team_id"],
|
||||
["id"],
|
||||
ondelete="CASCADE",
|
||||
)
|
||||
op.create_foreign_key(
|
||||
"unlocks_user_id_fkey",
|
||||
"unlocks",
|
||||
"users",
|
||||
["user_id"],
|
||||
["id"],
|
||||
ondelete="CASCADE",
|
||||
)
|
||||
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
url = str(bind.engine.url)
|
||||
if url.startswith("mysql"):
|
||||
op.drop_constraint("unlocks_ibfk_1", "unlocks", type_="foreignkey")
|
||||
op.drop_constraint("unlocks_ibfk_2", "unlocks", type_="foreignkey")
|
||||
op.create_foreign_key("unlocks_ibfk_1", "unlocks", "teams", ["team_id"], ["id"])
|
||||
op.create_foreign_key("unlocks_ibfk_2", "unlocks", "users", ["user_id"], ["id"])
|
||||
|
||||
op.drop_constraint("tracking_ibfk_1", "tracking", type_="foreignkey")
|
||||
op.create_foreign_key(
|
||||
"tracking_ibfk_1", "tracking", "users", ["user_id"], ["id"]
|
||||
)
|
||||
|
||||
op.drop_constraint("team_captain_id", "teams", type_="foreignkey")
|
||||
op.create_foreign_key(
|
||||
"team_captain_id", "teams", "users", ["captain_id"], ["id"]
|
||||
)
|
||||
|
||||
op.drop_constraint("tags_ibfk_1", "tags", type_="foreignkey")
|
||||
op.create_foreign_key(
|
||||
"tags_ibfk_1", "tags", "challenges", ["challenge_id"], ["id"]
|
||||
)
|
||||
|
||||
op.drop_constraint("hints_ibfk_1", "hints", type_="foreignkey")
|
||||
op.create_foreign_key(
|
||||
"hints_ibfk_1", "hints", "challenges", ["challenge_id"], ["id"]
|
||||
)
|
||||
|
||||
op.drop_constraint("flags_ibfk_1", "flags", type_="foreignkey")
|
||||
op.create_foreign_key(
|
||||
"flags_ibfk_1", "flags", "challenges", ["challenge_id"], ["id"]
|
||||
)
|
||||
|
||||
op.drop_constraint("files_ibfk_1", "files", type_="foreignkey")
|
||||
op.create_foreign_key(
|
||||
"files_ibfk_1", "files", "challenges", ["challenge_id"], ["id"]
|
||||
)
|
||||
|
||||
op.drop_constraint("awards_ibfk_1", "awards", type_="foreignkey")
|
||||
op.drop_constraint("awards_ibfk_2", "awards", type_="foreignkey")
|
||||
op.create_foreign_key("awards_ibfk_1", "awards", "teams", ["team_id"], ["id"])
|
||||
op.create_foreign_key("awards_ibfk_2", "awards", "users", ["user_id"], ["id"])
|
||||
elif url.startswith("postgres"):
|
||||
op.drop_constraint("unlocks_team_id_fkey", "unlocks", type_="foreignkey")
|
||||
op.drop_constraint("unlocks_user_id_fkey", "unlocks", type_="foreignkey")
|
||||
op.create_foreign_key(
|
||||
"unlocks_team_id_fkey", "unlocks", "teams", ["team_id"], ["id"]
|
||||
)
|
||||
op.create_foreign_key(
|
||||
"unlocks_user_id_fkey", "unlocks", "users", ["user_id"], ["id"]
|
||||
)
|
||||
|
||||
op.drop_constraint("tracking_user_id_fkey", "tracking", type_="foreignkey")
|
||||
op.create_foreign_key(
|
||||
"tracking_user_id_fkey", "tracking", "users", ["user_id"], ["id"]
|
||||
)
|
||||
|
||||
op.drop_constraint("team_captain_id", "teams", type_="foreignkey")
|
||||
op.create_foreign_key(
|
||||
"team_captain_id", "teams", "users", ["captain_id"], ["id"]
|
||||
)
|
||||
|
||||
op.drop_constraint("tags_challenge_id_fkey", "tags", type_="foreignkey")
|
||||
op.create_foreign_key(
|
||||
"tags_challenge_id_fkey", "tags", "challenges", ["challenge_id"], ["id"]
|
||||
)
|
||||
|
||||
op.drop_constraint("hints_challenge_id_fkey", "hints", type_="foreignkey")
|
||||
op.create_foreign_key(
|
||||
"hints_challenge_id_fkey", "hints", "challenges", ["challenge_id"], ["id"]
|
||||
)
|
||||
|
||||
op.drop_constraint("flags_challenge_id_fkey", "flags", type_="foreignkey")
|
||||
op.create_foreign_key(
|
||||
"flags_challenge_id_fkey", "flags", "challenges", ["challenge_id"], ["id"]
|
||||
)
|
||||
|
||||
op.drop_constraint("files_challenge_id_fkey", "files", type_="foreignkey")
|
||||
op.create_foreign_key(
|
||||
"files_challenge_id_fkey", "files", "challenges", ["challenge_id"], ["id"]
|
||||
)
|
||||
|
||||
op.drop_constraint("awards_team_id_fkey", "awards", type_="foreignkey")
|
||||
op.drop_constraint("awards_user_id_fkey", "awards", type_="foreignkey")
|
||||
op.create_foreign_key(
|
||||
"awards_team_id_fkey", "awards", "teams", ["team_id"], ["id"]
|
||||
)
|
||||
op.create_foreign_key(
|
||||
"awards_user_id_fkey", "awards", "users", ["user_id"], ["id"]
|
||||
)
|
||||
@@ -0,0 +1,57 @@
|
||||
"""Add captain column to Teams
|
||||
|
||||
Revision ID: b5551cd26764
|
||||
Revises: 4e4d5a9ea000
|
||||
Create Date: 2019-04-12 00:29:08.021141
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
from sqlalchemy.sql import column, table
|
||||
|
||||
from CTFd.models import db
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "b5551cd26764"
|
||||
down_revision = "4e4d5a9ea000"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
teams_table = table("teams", column("id", db.Integer), column("captain_id", db.Integer))
|
||||
|
||||
users_table = table("users", column("id", db.Integer), column("team_id", db.Integer))
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column("teams", sa.Column("captain_id", sa.Integer(), nullable=True))
|
||||
|
||||
bind = op.get_bind()
|
||||
url = str(bind.engine.url)
|
||||
if url.startswith("sqlite") is False:
|
||||
op.create_foreign_key(
|
||||
"team_captain_id", "teams", "users", ["captain_id"], ["id"]
|
||||
)
|
||||
|
||||
connection = op.get_bind()
|
||||
for team in connection.execute(teams_table.select()):
|
||||
users = connection.execute(
|
||||
users_table.select()
|
||||
.where(users_table.c.team_id == team.id)
|
||||
.order_by(users_table.c.id)
|
||||
.limit(1)
|
||||
)
|
||||
for user in users:
|
||||
connection.execute(
|
||||
teams_table.update()
|
||||
.where(teams_table.c.id == team.id)
|
||||
.values(captain_id=user.id)
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_constraint("team_captain_id", "teams", type_="foreignkey")
|
||||
op.drop_column("teams", "captain_id")
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,46 @@
|
||||
"""Add topics and challenge_topics tables
|
||||
|
||||
Revision ID: ef87d69ec29a
|
||||
Revises: 07dfbe5e1edc
|
||||
Create Date: 2021-07-29 23:22:39.345426
|
||||
|
||||
"""
|
||||
from alembic import op # noqa: I001
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "ef87d69ec29a"
|
||||
down_revision = "07dfbe5e1edc"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table(
|
||||
"topics",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("value", sa.String(length=255), nullable=True),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("value"),
|
||||
)
|
||||
op.create_table(
|
||||
"challenge_topics",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("challenge_id", sa.Integer(), nullable=True),
|
||||
sa.Column("topic_id", sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["challenge_id"], ["challenges.id"], ondelete="CASCADE"
|
||||
),
|
||||
sa.ForeignKeyConstraint(["topic_id"], ["topics.id"], ondelete="CASCADE"),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table("challenge_topics")
|
||||
op.drop_table("topics")
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,30 @@
|
||||
"""Add logic column to Challenges
|
||||
|
||||
Revision ID: f73a96c97449
|
||||
Revises: 62bf576b2cd3
|
||||
Create Date: 2025-08-08 21:49:23.417694
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "f73a96c97449"
|
||||
down_revision = "62bf576b2cd3"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column(
|
||||
"challenges", sa.Column("logic", sa.String(length=80), nullable=False)
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column("challenges", "logic")
|
||||
# ### end Alembic commands ###
|
||||
Reference in New Issue
Block a user