Skip to content

Commit

Permalink
feat: more robust characters model (#66)
Browse files Browse the repository at this point in the history
* chore: flush database

* refactor: move characters out of account model

* feat: new characters endpoints

* chore: migrations

* chore: update bruno api collection

* feat: endpoints

* chore: bruno

* feat: added last_updated field for conflict conciliation
  • Loading branch information
corp-0 authored Nov 13, 2023
1 parent 7a80b1d commit b04b093
Show file tree
Hide file tree
Showing 33 changed files with 411 additions and 185 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ post {

body:json {
{
"account_identifier": "' 'DROP TABLE USERS;",
"unique_identifiers": "' 'DROP TABLE USERS;",
"username": "My Name",
"password": "qweasd123",
"email": "[email protected]"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ post {

body:json {
{
"account_identifier": "myname",
"unique_identifier": "myname",
"username": "My Name",
"password": "qweasd123",
"email": "[email protected]"
Expand Down
File renamed without changes.
File renamed without changes.
26 changes: 26 additions & 0 deletions api-collection/Characters/Create.bru
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
meta {
name: Create
type: http
seq: 1
}

post {
url: {{baseUrl}}/persistence/characters/create
body: json
auth: none
}

headers {
Authorization: Token 4963885504960842e61c6cadb4a9df05647e2c7bf1cfe08ea8cff57ab37058ac
}

body:json {
{
"character_sheet_version": "1.0.0",
"fork_compatibility": "Not compatible",
"data": {
"name": "My Name",
"age": 31
}
}
}
15 changes: 15 additions & 0 deletions api-collection/Characters/Delete.bru
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
meta {
name: Delete
type: http
seq: 5
}

delete {
url: {{baseUrl}}/persistence/characters/7/delete
body: none
auth: none
}

headers {
Authorization: Token 4963885504960842e61c6cadb4a9df05647e2c7bf1cfe08ea8cff57ab37058ac
}
15 changes: 15 additions & 0 deletions api-collection/Characters/GetAll.bru
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
meta {
name: GetAll
type: http
seq: 3
}

get {
url: {{baseUrl}}/persistence/characters
body: none
auth: none
}

headers {
Authorization: Token 4963885504960842e61c6cadb4a9df05647e2c7bf1cfe08ea8cff57ab37058ac
}
15 changes: 15 additions & 0 deletions api-collection/Characters/GetCharacter.bru
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
meta {
name: GetCharacter
type: http
seq: 2
}

get {
url: {{baseUrl}}/persistence/characters/8
body: none
auth: none
}

headers {
Authorization: Token 4963885504960842e61c6cadb4a9df05647e2c7bf1cfe08ea8cff57ab37058ac
}
20 changes: 20 additions & 0 deletions api-collection/Characters/GetCompatible.bru
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
meta {
name: GetCompatible
type: http
seq: 3
}

get {
url: {{baseUrl}}/persistence/characters/compatible?fork_compatibility=Unitystation&character_sheet_version=1.0.0
body: none
auth: none
}

query {
fork_compatibility: Unitystation
character_sheet_version: 1.0.0
}

headers {
Authorization: Token 4963885504960842e61c6cadb4a9df05647e2c7bf1cfe08ea8cff57ab37058ac
}
27 changes: 27 additions & 0 deletions api-collection/Characters/Update.bru
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
meta {
name: Update
type: http
seq: 6
}

patch {
url: {{baseUrl}}/persistence/characters/8/update
body: json
auth: none
}

headers {
Authorization: Token 4963885504960842e61c6cadb4a9df05647e2c7bf1cfe08ea8cff57ab37058ac
}

body:json {
{
"id": 20,
"account": "AnotherAccount",
"fork_compatibility": "Not compatible",
"data": {
"age": 31,
"name": "Another name"
}
}
}
13 changes: 7 additions & 6 deletions src/accounts/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@
class AccountAdminView(admin.ModelAdmin):
list_display = (
"email",
"account_identifier",
"is_active",
"unique_identifier",
"username",
"is_verified",
"legacy_id",
"characters_data",
"is_authorized_server",
)
fieldsets = (
(
Expand All @@ -21,18 +20,20 @@ class AccountAdminView(admin.ModelAdmin):
"classes": ("wide",),
"fields": (
"email",
"account_identifier",
"unique_identifier",
"username",
"verification_token",
),
},
),
("Characters", {"classes": ("wide",), "fields": ("characters_data",)}),
(
"Authorization",
{
"classes": ("wide",),
"fields": ("is_active", "is_verified", "is_authorized_server"),
"fields": (
"is_active",
"is_verified",
),
},
),
("Legacy", {"classes": ("wide",), "fields": ("legacy_id",)}),
Expand Down
21 changes: 4 additions & 17 deletions src/accounts/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,17 @@ class PublicAccountDataSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fields = (
"account_identifier",
"unique_identifier",
"username",
"legacy_id",
"is_verified",
"is_authorized_server",
"characters_data",
)


class RegisterAccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fields = ("account_identifier", "username", "password", "email")
fields = ("unique_identifier", "username", "password", "email")
extra_kwargs = {"password": {"write_only": True}}

def create(self, validated_data):
Expand Down Expand Up @@ -68,23 +66,12 @@ def update(self, instance, validated_data):
return instance


class UpdateCharactersSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fields = ("characters_data",)

def update(self, instance, validated_data):
instance.characters_data = validated_data.get("characters_data", instance.characters_data)
instance.save()
return instance


class VerifyAccountSerializer(serializers.Serializer):
account_identifier = serializers.CharField()
unique_identifier = serializers.CharField()
verification_token = serializers.UUIDField()

def validate(self, data):
account = Account.objects.get(account_identifier=data["account_identifier"])
account = Account.objects.get(unique_identifier=data["unique_identifier"])

data_token = data["verification_token"]
account_token = account.verification_token
Expand Down
2 changes: 0 additions & 2 deletions src/accounts/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
RegisterAccountView,
RequestVerificationTokenView,
UpdateAccountView,
UpdateCharactersView,
VerifyAccountView,
)

Expand All @@ -23,7 +22,6 @@
),
path("register", RegisterAccountView.as_view(), name="register"),
path("update-account", UpdateAccountView.as_view(), name="update"),
path("update-characters", UpdateCharactersView.as_view(), name="update-characters"),
path("account", PublicAccountDataView.as_view(), name="public-data"),
path("account/<str:pk>", PublicAccountDataView.as_view(), name="public-data"),
path("logout", knox_views.LogoutView.as_view(), name="logout"),
Expand Down
32 changes: 2 additions & 30 deletions src/accounts/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
PublicAccountDataSerializer,
RegisterAccountSerializer,
UpdateAccountSerializer,
UpdateCharactersSerializer,
VerifyAccountSerializer,
)

Expand Down Expand Up @@ -140,33 +139,6 @@ def post(self, request):
return Response(serializer.data, status=status.HTTP_200_OK)


class UpdateCharactersView(GenericAPIView):
serializer_class = UpdateCharactersSerializer

def post(self, request):
try:
account = Account.objects.get(pk=request.user.pk)
if request.user != account:
raise PermissionDenied
except ObjectDoesNotExist:
return Response({"error": "Account does not exist."}, status=status.HTTP_404_NOT_FOUND)
except PermissionDenied:
return Response(
{"error": "You have no permission to do this action."},
status=status.HTTP_403_FORBIDDEN,
)

serializer = self.get_serializer(account, data=request.data)
try:
serializer.is_valid(raise_exception=True)
except ValidationError as e:
return Response(data={"error": str(e)}, status=e.status_code)
except Exception as e:
return Response(data={"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)


class RequestVerificationTokenView(GenericAPIView):
def get(self, *args, **kwargs):
verification_token = uuid4()
Expand All @@ -185,7 +157,7 @@ def get(self, *args, **kwargs):
account.save()
return Response(
{
"account_identifier": account.account_identifier,
"unique_identifier": account.unique_identifier,
"verification_token": verification_token,
},
status=status.HTTP_200_OK,
Expand All @@ -205,7 +177,7 @@ def post(self, request):
except Exception as e:
return Response(data={"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

account = Account.objects.get(account_identifier=serializer.data["account_identifier"])
account = Account.objects.get(unique_identifier=serializer.data["unique_identifier"])
public_data = PublicAccountDataSerializer(account).data

return Response(public_data, status=status.HTTP_200_OK)
9 changes: 3 additions & 6 deletions src/accounts/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
# Generated by Django 3.2.9 on 2022-02-07 03:55
# Generated by Django 3.2.22 on 2023-11-11 22:10

import accounts.validators
import django.contrib.auth.models
from django.db import migrations, models
import django.utils.timezone
import uuid


class Migration(migrations.Migration):
Expand All @@ -28,13 +27,11 @@ class Migration(migrations.Migration):
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('email', models.EmailField(help_text='Email address must be unique. It is used to login and confirm the account.', max_length=254, unique=True, verbose_name='Email address')),
('account_identifier', models.CharField(help_text="Account identifier is used to identify your account. This will be used for bans, job bans, etc and can't ever be changed", max_length=28, primary_key=True, serialize=False, validators=[accounts.validators.AccountNameValidator()], verbose_name='Account identifier')),
('unique_identifier', models.CharField(help_text="Unique identifier is used to identify your account. This will be used for bans, job bans, etc and can't ever be changed", max_length=28, primary_key=True, serialize=False, validators=[accounts.validators.AccountNameValidator()], verbose_name='Unique identifier')),
('username', models.CharField(help_text='Public username is used to identify your account publicly and shows in OOC. This can be changed at any time', max_length=28, validators=[accounts.validators.UsernameValidator()], verbose_name='Public username')),
('is_verified', models.BooleanField(default=False, help_text='Is this account verified to be who they claim to be? Are they famous?!', verbose_name='Verified')),
('legacy_id', models.CharField(blank=True, default='null', help_text="Legacy ID is used to identify your account in the old database. This is used for bans, job bans, etc and can't ever be changed", max_length=28, verbose_name='Legacy ID')),
('characters_data', models.JSONField(default=dict, help_text='Characters data is used to store all the characters associated with this account.', verbose_name='Characters data')),
('is_authorized_server', models.BooleanField(default=False, help_text='Can this account broadcast the server state to the server list api? Can this account write to persistence layer?', verbose_name='Authorized server')),
('verification_token', models.UUIDField(blank=True, default=uuid.UUID('05e74bff-c4b1-4452-ac03-b20805ac4fef'), verbose_name='Verification token')),
('verification_token', models.UUIDField(blank=True, null=True, verbose_name='Verification token')),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
],
Expand Down
19 changes: 0 additions & 19 deletions src/accounts/migrations/0002_alter_account_verification_token.py

This file was deleted.

19 changes: 0 additions & 19 deletions src/accounts/migrations/0003_alter_account_verification_token.py

This file was deleted.

19 changes: 0 additions & 19 deletions src/accounts/migrations/0004_alter_account_verification_token.py

This file was deleted.

Loading

0 comments on commit b04b093

Please sign in to comment.