From d7eebd7bf6e2c2f7b8404fe2705920774d9fbc3c Mon Sep 17 00:00:00 2001 From: Gilles <43683714+corp-0@users.noreply.github.com> Date: Wed, 19 Jun 2024 06:25:51 -0400 Subject: [PATCH 1/6] chore: remove outdated migrations --- src/persistence/migrations/0001_initial.py | 29 ------------------- .../migrations/0002_character_last_updated.py | 18 ------------ 2 files changed, 47 deletions(-) delete mode 100644 src/persistence/migrations/0001_initial.py delete mode 100644 src/persistence/migrations/0002_character_last_updated.py diff --git a/src/persistence/migrations/0001_initial.py b/src/persistence/migrations/0001_initial.py deleted file mode 100644 index 1fb9dab..0000000 --- a/src/persistence/migrations/0001_initial.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 3.2.22 on 2023-11-11 22:10 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import persistence.validators - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('accounts', '0001_initial'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='Character', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('fork_compatibility', models.CharField(default='Unitystation', help_text='What fork is this character compatible with? This is a simple string, like "Unitystation" or "tg".', max_length=25)), - ('character_sheet_version', models.CharField(help_text='What character sheet version is this character compatible with? This should be semantically versioned, like "1.0.0" or "0.1.0".', max_length=10, validators=[persistence.validators.validate_semantic_version])), - ('data', models.JSONField(help_text='Unstructured character data in JSON format.', verbose_name='Character data')), - ('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - ), - ] diff --git a/src/persistence/migrations/0002_character_last_updated.py b/src/persistence/migrations/0002_character_last_updated.py deleted file mode 100644 index 6b47916..0000000 --- a/src/persistence/migrations/0002_character_last_updated.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.2.22 on 2023-11-11 22:53 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('persistence', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='character', - name='last_updated', - field=models.DateTimeField(auto_now=True, help_text='When was this character last updated? Useful for conciliation with the local cache of a character.'), - ), - ] From 1e891352e13f7970b29f2e5a61cb8ea6807c6825 Mon Sep 17 00:00:00 2001 From: Gilles <43683714+corp-0@users.noreply.github.com> Date: Wed, 19 Jun 2024 06:26:20 -0400 Subject: [PATCH 2/6] chore: add redis service for server status volatile data --- dev-compose.yml | 7 +++++++ docker-compose.yml | 5 +++++ example.env | 1 + src/central_command/settings.py | 13 +++++++++++++ 4 files changed, 26 insertions(+) diff --git a/dev-compose.yml b/dev-compose.yml index 03b6a8b..91b8ced 100644 --- a/dev-compose.yml +++ b/dev-compose.yml @@ -10,6 +10,13 @@ services: ports: - "5432:5432" + redis: + image: redis:7.2.5-alpine + ports: + - "6379:6379" + environment: + REDIS_PORT: 6379 + web: depends_on: - db diff --git a/docker-compose.yml b/docker-compose.yml index cdbacbe..08eeef1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,6 +8,11 @@ services: volumes: - db-data:/var/lib/postgresql/data + redis: + image: redis:7.2.5-alpine + environment: + REDIS_PORT: 6379 + web: image: unitystation/central-command:latest environment: diff --git a/example.env b/example.env index c30f9a8..97553ad 100644 --- a/example.env +++ b/example.env @@ -13,6 +13,7 @@ DB_ENGINE=django.db.backends.postgresql DB_NAME=postgres DB_HOST=db DB_PORT=5432 +REDIS_HOST=redis # Links to stuff WEBSITE_URL = http://localhost:8000 diff --git a/src/central_command/settings.py b/src/central_command/settings.py index 8fe78b7..dfa66ec 100644 --- a/src/central_command/settings.py +++ b/src/central_command/settings.py @@ -51,6 +51,7 @@ "post_office", "accounts", "persistence", + "server", "drf_spectacular", ] @@ -154,6 +155,18 @@ } } +REDIS_LOCATION = f"redis://{os.environ.get('REDIS_HOST')}:6379/1" + +CACHES = { + "default": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": REDIS_LOCATION, + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + }, + } +} + REST_FRAMEWORK = { "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", "DEFAULT_AUTHENTICATION_CLASSES": ["knox.auth.TokenAuthentication"], From 8abe166aaa08b33300d7f287d98b7396edfc6f44 Mon Sep 17 00:00:00 2001 From: Gilles <43683714+corp-0@users.noreply.github.com> Date: Wed, 19 Jun 2024 06:28:48 -0400 Subject: [PATCH 3/6] feat: wip definition of models for new server management --- src/server/__init__.py | 0 src/server/admin.py | 37 +++++ src/server/api/__init__.py | 0 src/server/api/urls.py | 1 + src/server/api/views.py | 0 src/server/apps.py | 6 + src/server/management/__init__.py | 0 src/server/management/commands/__init__.py | 0 .../commands/create_moderation_info.py | 13 ++ src/server/migrations/0001_initial.py | 59 +++++++ src/server/migrations/__init__.py | 0 src/server/models.py | 150 ++++++++++++++++++ 12 files changed, 266 insertions(+) create mode 100644 src/server/__init__.py create mode 100644 src/server/admin.py create mode 100644 src/server/api/__init__.py create mode 100644 src/server/api/urls.py create mode 100644 src/server/api/views.py create mode 100644 src/server/apps.py create mode 100644 src/server/management/__init__.py create mode 100644 src/server/management/commands/__init__.py create mode 100644 src/server/management/commands/create_moderation_info.py create mode 100644 src/server/migrations/0001_initial.py create mode 100644 src/server/migrations/__init__.py create mode 100644 src/server/models.py diff --git a/src/server/__init__.py b/src/server/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/server/admin.py b/src/server/admin.py new file mode 100644 index 0000000..e0d7b6f --- /dev/null +++ b/src/server/admin.py @@ -0,0 +1,37 @@ +from django.contrib import admin + +from .models import ( + AccountModerationInfo, + CodeScanInformation, + ServerAdmonition, + ServerInformation, +) + + +class ServerAdmonitionInline(admin.TabularInline): + model = ServerAdmonition + extra = 1 + raw_id_fields = ["server"] + fields = ["server", "created_at", "reason", "severity"] + readonly_fields = ["created_at"] + + +@admin.register(CodeScanInformation) +class CodeScanInformationAdmin(admin.ModelAdmin): + list_display = ["version"] + + +@admin.register(ServerInformation) +class ServerInformationAdmin(admin.ModelAdmin): + list_display = ["name", "owner", "is_18_plus", "is_delisted"] + search_fields = ["name", "owner__username"] + list_filter = ["is_18_plus", "is_delisted"] + raw_id_fields = ["owner", "code_scan_version"] + + +@admin.register(AccountModerationInfo) +class AccountModerationInfoAdmin(admin.ModelAdmin): + list_display = ["account", "can_create_servers", "can_list_servers"] + search_fields = ["account__username"] + list_filter = ["can_create_servers", "can_list_servers"] + raw_id_fields = ["account"] diff --git a/src/server/api/__init__.py b/src/server/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/server/api/urls.py b/src/server/api/urls.py new file mode 100644 index 0000000..ae94233 --- /dev/null +++ b/src/server/api/urls.py @@ -0,0 +1 @@ +app_name = "server" diff --git a/src/server/api/views.py b/src/server/api/views.py new file mode 100644 index 0000000..e69de29 diff --git a/src/server/apps.py b/src/server/apps.py new file mode 100644 index 0000000..40baeb8 --- /dev/null +++ b/src/server/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ServerConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "server" diff --git a/src/server/management/__init__.py b/src/server/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/server/management/commands/__init__.py b/src/server/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/server/management/commands/create_moderation_info.py b/src/server/management/commands/create_moderation_info.py new file mode 100644 index 0000000..4b8f1eb --- /dev/null +++ b/src/server/management/commands/create_moderation_info.py @@ -0,0 +1,13 @@ +from django.core.management.base import BaseCommand + +from accounts.models import Account +from server.models import AccountModerationInfo + + +class Command(BaseCommand): + help = "creates AccountModerationInfo for existing accounts" + + def handle(self, *args, **options): + for account in Account.objects.all(): + AccountModerationInfo.objects.get_or_create(account=account) + self.stdout.write(self.style.SUCCESS("Successfully created AccountModerationInfo for all accounts")) diff --git a/src/server/migrations/0001_initial.py b/src/server/migrations/0001_initial.py new file mode 100644 index 0000000..ef79813 --- /dev/null +++ b/src/server/migrations/0001_initial.py @@ -0,0 +1,59 @@ +# Generated by Django 3.2.24 on 2024-06-19 10:17 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='AccountModerationInfo', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('can_create_servers', models.BooleanField(default=True)), + ('can_list_servers', models.BooleanField(default=True)), + ('account', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='moderation_info', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='CodeScanInformation', + fields=[ + ('version', models.TextField(primary_key=True, serialize=False)), + ], + ), + migrations.CreateModel( + name='ServerInformation', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.TextField(help_text='Name this server uses to present itself in the servers list', max_length=50, verbose_name='Name')), + ('description', models.TextField(help_text='A brief description of what this server is about', max_length=200, verbose_name='Description')), + ('icon', models.URLField(blank=True, help_text='URL of the image this server uses to present itself in the servers list', verbose_name='Icon')), + ('rules', models.TextField(blank=True, help_text='The rules that players must follow on this server', verbose_name='Rules')), + ('motd', models.TextField(help_text='Message displayed to players when they join the server', verbose_name='Message of the Day (MOTD)')), + ('is_18_plus', models.BooleanField(default=False, help_text='Indicates if this server is intended for players aged 18 and above', verbose_name='18+')), + ('is_delisted', models.BooleanField(help_text='Indicates if this server is delisted from the servers list', verbose_name='Is Delisted')), + ('listing_key', models.TextField(blank=True, help_text='A unique key used for listing this server. Do not lose this key!', null=True, unique=True, verbose_name='Listing Key')), + ('code_scan_version', models.ForeignKey(help_text='What version should this build of the game be tested against in CodeScan', on_delete=django.db.models.deletion.PROTECT, to='server.codescaninformation', verbose_name='CodeScan Version')), + ('owner', models.ForeignKey(help_text='Who created and/or is responsible for this server', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Owner')), + ], + ), + migrations.CreateModel( + name='ServerAdmonition', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='When was this admonition created', verbose_name='Created at')), + ('reason', models.TextField(help_text='Why was this server warned?', verbose_name='Reason')), + ('severity', models.CharField(choices=[('low', 'Low'), ('medium', 'Medium'), ('high', 'High')], default='low', help_text='The severity level of the admonition', max_length=6)), + ('owner_moderation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='admonitions', to='server.accountmoderationinfo')), + ('server', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='admonitions', to='server.serverinformation')), + ], + ), + ] diff --git a/src/server/migrations/__init__.py b/src/server/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/server/models.py b/src/server/models.py new file mode 100644 index 0000000..7a9e198 --- /dev/null +++ b/src/server/models.py @@ -0,0 +1,150 @@ +import secrets +import string + +from django.db import models +from django.db.models.signals import pre_save +from django.dispatch import receiver + +from accounts.models import Account + + +class CodeScanInformation(models.Model): + version = models.TextField(primary_key=True) + + def __str__(self): + return self.version + + +def generate_listing_key(): + alphabet = string.ascii_letters + string.digits + "-_" + return "".join(secrets.choice(alphabet) for _ in range(30)) + + +class ServerInformation(models.Model): + owner = models.ForeignKey( + verbose_name="Owner", + to=Account, + on_delete=models.CASCADE, + help_text="Who created and/or is responsible for this server", + ) + name = models.TextField( + verbose_name="Name", + max_length=50, + help_text="Name this server uses to present itself in the servers list", + ) + description = models.TextField( + verbose_name="Description", + max_length=200, + help_text="A brief description of what this server is about", + ) + icon = models.URLField( + verbose_name="Icon", + blank=True, + help_text="URL of the image this server uses to present itself in the servers list", + ) + rules = models.TextField( + verbose_name="Rules", + blank=True, + help_text="The rules that players must follow on this server", + ) + motd = models.TextField( + verbose_name="Message of the Day (MOTD)", + help_text="Message displayed to players when they join the server", + ) + is_18_plus = models.BooleanField( + verbose_name="18+", + default=False, + help_text="Indicates if this server is intended for players aged 18 and above", + ) + code_scan_version = models.ForeignKey( + verbose_name="CodeScan Version", + to=CodeScanInformation, + on_delete=models.PROTECT, + help_text="What version should this build of the game be tested against in CodeScan", + ) + is_delisted = models.BooleanField( + verbose_name="Is Delisted", + help_text="Indicates if this server is delisted from the servers list", + ) + listing_key = models.TextField( + unique=True, + verbose_name="Listing Key", + null=True, + blank=True, + help_text="A unique key used for listing this server. Do not lose this key!", + ) + + def __str__(self): + return f"Server: {self.name} by: {self.owner.unique_identifier}" + + +@receiver(pre_save, sender=ServerInformation) +def set_listing_key(sender, instance: ServerInformation, **kwargs): + if not instance.listing_key: + unique_key_found = False + while not unique_key_found: + new_key = generate_listing_key() + if not ServerInformation.objects.filter(listing_key=new_key).exists(): + instance.listing_key = new_key + unique_key_found = True + + +# class ServerStatus(models.Model): +# server = models.ForeignKey( +# ServerInformation, related_name="status", on_delete=models.CASCADE +# ) +# is_passworded = models.BooleanField() +# fork_name = models.TextField() +# build_version = models.TextField() +# current_map = models.TextField() +# game_mode = models.TextField() +# ingame_time = models.TextField() +# round_time = models.TextField() +# player_count = models.PositiveSmallIntegerField() +# player_count_max = models.PositiveSmallIntegerField() +# ip = models.TextField() +# port = models.PositiveSmallIntegerField() +# windows_download = models.URLField() +# osx_download = models.URLField() +# linux_download = models.URLField() +# fps = models.PositiveSmallIntegerField() + + +class AccountModerationInfo(models.Model): + account = models.OneToOneField(to=Account, related_name="moderation_info", on_delete=models.CASCADE) + can_create_servers = models.BooleanField(default=True) + can_list_servers = models.BooleanField(default=True) + + def __str__(self): + return f"Moderation for {self.account.unique_identifier}" + + +class ServerAdmonition(models.Model): + SEVERITY_LEVELS = [ + ("low", "Low"), + ("medium", "Medium"), + ("high", "High"), + ] + + owner_moderation = models.ForeignKey(AccountModerationInfo, related_name="admonitions", on_delete=models.CASCADE) + server = models.ForeignKey( + ServerInformation, + related_name="admonitions", + on_delete=models.SET_NULL, + null=True, + ) + created_at = models.DateTimeField( + verbose_name="Created at", + auto_now_add=True, + help_text="When was this admonition created", + ) + reason = models.TextField(verbose_name="Reason", help_text="Why was this server warned?") + severity = models.CharField( + max_length=6, + choices=SEVERITY_LEVELS, + default="low", + help_text="The severity level of the admonition", + ) + + def __str__(self): + return f"[{self.get_severity_display()}]: {self.reason}" From f011620afad1e2a5b5c9fab59873acfc1c7bc2ba Mon Sep 17 00:00:00 2001 From: Gilles <43683714+corp-0@users.noreply.github.com> Date: Fri, 20 Sep 2024 03:27:17 -0300 Subject: [PATCH 4/6] feat: wip definition of models for new server management --- dev-compose.yml | 10 ++--- docker-compose.yml | 6 +-- example.env | 3 +- src/central_command/settings.py | 10 ++--- src/server/models.py | 71 +++++++++++++++++++++------------ 5 files changed, 56 insertions(+), 44 deletions(-) diff --git a/dev-compose.yml b/dev-compose.yml index 91b8ced..e98b6e0 100644 --- a/dev-compose.yml +++ b/dev-compose.yml @@ -10,12 +10,10 @@ services: ports: - "5432:5432" - redis: - image: redis:7.2.5-alpine + memcached: + image: memcached:1.6-alpine ports: - - "6379:6379" - environment: - REDIS_PORT: 6379 + - "11211:11211" web: depends_on: @@ -30,5 +28,3 @@ services: volumes: db-data: - static-volume: - media-volume: diff --git a/docker-compose.yml b/docker-compose.yml index 08eeef1..af753e4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,10 +8,8 @@ services: volumes: - db-data:/var/lib/postgresql/data - redis: - image: redis:7.2.5-alpine - environment: - REDIS_PORT: 6379 + memcached: + image: memcached:1.6-alpine web: image: unitystation/central-command:latest diff --git a/example.env b/example.env index 97553ad..922cdc1 100644 --- a/example.env +++ b/example.env @@ -13,7 +13,8 @@ DB_ENGINE=django.db.backends.postgresql DB_NAME=postgres DB_HOST=db DB_PORT=5432 -REDIS_HOST=redis +MEMCACHED_LOCATION=127.0.0.1 +MEMCACHED_PORT=11211 # Links to stuff WEBSITE_URL = http://localhost:8000 diff --git a/src/central_command/settings.py b/src/central_command/settings.py index dfa66ec..a03f986 100644 --- a/src/central_command/settings.py +++ b/src/central_command/settings.py @@ -155,15 +155,13 @@ } } -REDIS_LOCATION = f"redis://{os.environ.get('REDIS_HOST')}:6379/1" +MEMCACHED_HOST = os.environ.get("MEMCACHED_HOST", "127.0.0.1") +MEMCACHED_PORT = os.environ.get("MEMCACHED_PORT", 11211) CACHES = { "default": { - "BACKEND": "django_redis.cache.RedisCache", - "LOCATION": REDIS_LOCATION, - "OPTIONS": { - "CLIENT_CLASS": "django_redis.client.DefaultClient", - }, + "BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache", + "LOCATION": f"{MEMCACHED_HOST}:{MEMCACHED_PORT}", } } diff --git a/src/server/models.py b/src/server/models.py index 7a9e198..ff0f865 100644 --- a/src/server/models.py +++ b/src/server/models.py @@ -1,5 +1,4 @@ import secrets -import string from django.db import models from django.db.models.signals import pre_save @@ -9,6 +8,8 @@ class CodeScanInformation(models.Model): + """Information regarding the CodeScan that will be used to scan the code for this build after downloading on clients""" + version = models.TextField(primary_key=True) def __str__(self): @@ -16,23 +17,25 @@ def __str__(self): def generate_listing_key(): - alphabet = string.ascii_letters + string.digits + "-_" - return "".join(secrets.choice(alphabet) for _ in range(30)) + # 22 bytes produce 30 characters + return secrets.token_urlsafe(22) class ServerInformation(models.Model): + """Basic information that describes and identifies a server""" + owner = models.ForeignKey( verbose_name="Owner", to=Account, on_delete=models.CASCADE, help_text="Who created and/or is responsible for this server", ) - name = models.TextField( + name = models.CharField( verbose_name="Name", max_length=50, help_text="Name this server uses to present itself in the servers list", ) - description = models.TextField( + description = models.CharField( verbose_name="Description", max_length=200, help_text="A brief description of what this server is about", @@ -47,9 +50,10 @@ class ServerInformation(models.Model): blank=True, help_text="The rules that players must follow on this server", ) - motd = models.TextField( + motd = models.CharField( verbose_name="Message of the Day (MOTD)", help_text="Message displayed to players when they join the server", + max_length=255, ) is_18_plus = models.BooleanField( verbose_name="18+", @@ -66,11 +70,12 @@ class ServerInformation(models.Model): verbose_name="Is Delisted", help_text="Indicates if this server is delisted from the servers list", ) - listing_key = models.TextField( + listing_key = models.CharField( unique=True, verbose_name="Listing Key", null=True, blank=True, + max_length=30, help_text="A unique key used for listing this server. Do not lose this key!", ) @@ -78,6 +83,7 @@ def __str__(self): return f"Server: {self.name} by: {self.owner.unique_identifier}" +# Binds the save event of ServerInformation model to this function so that the key is generated every time a new ServerInformation is created @receiver(pre_save, sender=ServerInformation) def set_listing_key(sender, instance: ServerInformation, **kwargs): if not instance.listing_key: @@ -89,28 +95,39 @@ def set_listing_key(sender, instance: ServerInformation, **kwargs): unique_key_found = True -# class ServerStatus(models.Model): -# server = models.ForeignKey( -# ServerInformation, related_name="status", on_delete=models.CASCADE -# ) -# is_passworded = models.BooleanField() -# fork_name = models.TextField() -# build_version = models.TextField() -# current_map = models.TextField() -# game_mode = models.TextField() -# ingame_time = models.TextField() -# round_time = models.TextField() -# player_count = models.PositiveSmallIntegerField() -# player_count_max = models.PositiveSmallIntegerField() -# ip = models.TextField() -# port = models.PositiveSmallIntegerField() -# windows_download = models.URLField() -# osx_download = models.URLField() -# linux_download = models.URLField() -# fps = models.PositiveSmallIntegerField() +class ServerTag(models.Model): + """Represents an individual tag a server could have attached to it to make them easier to find by categories""" + + server = models.ForeignKey(to=ServerInformation, related_name="tags", on_delete=models.CASCADE) + name = models.TextField(verbose_name="Name", max_length=50) + + # class ServerStatus(models.Model): + # server = models.ForeignKey( + # ServerInformation, related_name="status", on_delete=models.CASCADE + # ) + # is_passworded = models.BooleanField() + # fork_name = models.TextField() + # build_version = models.TextField() + # current_map = models.TextField() + # game_mode = models.TextField() + # ingame_time = models.TextField() + # round_time = models.TextField() + # player_count = models.PositiveSmallIntegerField() + # player_count_max = models.PositiveSmallIntegerField() + # ip = models.TextField() + # port = models.PositiveSmallIntegerField() + # windows_download = models.URLField() + # osx_download = models.URLField() + # linux_download = models.URLField() + # fps = models.PositiveSmallIntegerField() + + def __str__(self) -> str: + return self.name class AccountModerationInfo(models.Model): + """Information an account has permanently attached to it for the purpose of moderating their behaviour on the hub""" + account = models.OneToOneField(to=Account, related_name="moderation_info", on_delete=models.CASCADE) can_create_servers = models.BooleanField(default=True) can_list_servers = models.BooleanField(default=True) @@ -120,6 +137,8 @@ def __str__(self): class ServerAdmonition(models.Model): + """Represents a warning or reprimand an account received for their misbehaviour or mismanagement of their server""" + SEVERITY_LEVELS = [ ("low", "Low"), ("medium", "Medium"), From d2c45304b50af9d5f799428d099eea57637cdaac Mon Sep 17 00:00:00 2001 From: Gilles <43683714+corp-0@users.noreply.github.com> Date: Sat, 19 Oct 2024 22:57:28 -0300 Subject: [PATCH 5/6] wip: add memcached to new pyproject --- pyproject.toml | 1 + uv.lock | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 4f40742..e254ce3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,7 @@ dependencies = [ "psycopg2-binary~=2.9.9", "python-dotenv~=0.19.2", "whitenoise~=6.6.0", + "pymemcache>=4.0.0", ] [build-system] diff --git a/uv.lock b/uv.lock index 9fdcec6..84e3e40 100644 --- a/uv.lock +++ b/uv.lock @@ -49,6 +49,7 @@ dependencies = [ { name = "drf-spectacular" }, { name = "gunicorn" }, { name = "psycopg2-binary" }, + { name = "pymemcache" }, { name = "python-dotenv" }, { name = "whitenoise" }, ] @@ -71,6 +72,7 @@ requires-dist = [ { name = "drf-spectacular", specifier = "~=0.27.1" }, { name = "gunicorn", specifier = "~=20.1.0" }, { name = "psycopg2-binary", specifier = "~=2.9.9" }, + { name = "pymemcache", specifier = ">=4.0.0" }, { name = "python-dotenv", specifier = "~=0.19.2" }, { name = "whitenoise", specifier = "~=6.6.0" }, ] @@ -479,6 +481,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, ] +[[package]] +name = "pymemcache" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/b6/4541b664aeaad025dfb8e851dcddf8e25ab22607e674dd2b562ea3e3586f/pymemcache-4.0.0.tar.gz", hash = "sha256:27bf9bd1bbc1e20f83633208620d56de50f14185055e49504f4f5e94e94aff94", size = 70176 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/ba/2f7b22d8135b51c4fefb041461f8431e1908778e6539ff5af6eeaaee367a/pymemcache-4.0.0-py2.py3-none-any.whl", hash = "sha256:f507bc20e0dc8d562f8df9d872107a278df049fa496805c1431b926f3ddd0eab", size = 60772 }, +] + [[package]] name = "python-dotenv" version = "0.19.2" From 4c5d82a15ab46c3d82366d0631101d3199885d81 Mon Sep 17 00:00:00 2001 From: Gilles <43683714+corp-0@users.noreply.github.com> Date: Sat, 19 Oct 2024 22:59:38 -0300 Subject: [PATCH 6/6] chore: migrations --- .../migrations/0002_auto_20241019_2259.py | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/server/migrations/0002_auto_20241019_2259.py diff --git a/src/server/migrations/0002_auto_20241019_2259.py b/src/server/migrations/0002_auto_20241019_2259.py new file mode 100644 index 0000000..097ef90 --- /dev/null +++ b/src/server/migrations/0002_auto_20241019_2259.py @@ -0,0 +1,42 @@ +# Generated by Django 3.2.25 on 2024-10-20 01:59 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('server', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='serverinformation', + name='description', + field=models.CharField(help_text='A brief description of what this server is about', max_length=200, verbose_name='Description'), + ), + migrations.AlterField( + model_name='serverinformation', + name='listing_key', + field=models.CharField(blank=True, help_text='A unique key used for listing this server. Do not lose this key!', max_length=30, null=True, unique=True, verbose_name='Listing Key'), + ), + migrations.AlterField( + model_name='serverinformation', + name='motd', + field=models.CharField(help_text='Message displayed to players when they join the server', max_length=255, verbose_name='Message of the Day (MOTD)'), + ), + migrations.AlterField( + model_name='serverinformation', + name='name', + field=models.CharField(help_text='Name this server uses to present itself in the servers list', max_length=50, verbose_name='Name'), + ), + migrations.CreateModel( + name='ServerTag', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.TextField(max_length=50, verbose_name='Name')), + ('server', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tags', to='server.serverinformation')), + ], + ), + ]