diff --git a/config/api_urls.py b/config/api_urls.py
new file mode 100644
index 0000000..0cbfb39
--- /dev/null
+++ b/config/api_urls.py
@@ -0,0 +1,15 @@
+from django.conf.urls import include
+from django.urls import re_path
+from rest_framework import routers
+
+from grids import views as grids_views
+
+router = routers.DefaultRouter()
+router.register(r"levels", grids_views.LevelViewSet, basename="level")
+router.register(r"grids", grids_views.GridViewSet, basename="grid")
+router.register(r"words", grids_views.WordViewSet, basename="word")
+router.register(r"placement", grids_views.PlacementViewSet, basename="placement")
+
+
+v1_pattern = router.urls
+urlpatterns = [re_path(r"^v1/", include((v1_pattern, "v1"), namespace="v1"))]
diff --git a/config/settings.py b/config/settings.py
index 7b08d08..583e479 100644
--- a/config/settings.py
+++ b/config/settings.py
@@ -50,6 +50,7 @@ INSTALLED_APPS = [
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
+ "corsheaders.middleware.CorsMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
@@ -132,3 +133,19 @@ STATIC_URL = "static/"
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
+
+
+# Origins
+if DEBUG:
+ CORS_ALLOW_ALL_ORIGINS = True
+
+
+# Rest framework
+REST_FRAMEWORK = {
+ # "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",),
+ "DEFAULT_RENDERER_CLASSES": ("rest_framework.renderers.JSONRenderer",),
+ "DEFAULT_PARSER_CLASSES": (
+ "rest_framework.parsers.JSONParser",
+ "rest_framework.parsers.FormParser",
+ ),
+}
diff --git a/config/urls.py b/config/urls.py
index ea6928c..c4391eb 100644
--- a/config/urls.py
+++ b/config/urls.py
@@ -15,9 +15,10 @@ Including another URLconf
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
-from django.urls import path, include
+from django.urls import path, include, re_path
urlpatterns = [
path("admin/", admin.site.urls),
path("users/", include("users.urls", namespace="users")),
+ re_path(r"^api/", include(("config.api_urls", "api"), namespace="api")),
]
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 18d1cb9..7825a44 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -8,8 +8,10 @@
"name": "frontend",
"version": "0.0.0",
"dependencies": {
+ "@fortawesome/fontawesome-free": "^6.4.2",
"@sveltejs/adapter-auto": "^2.1.0",
- "@sveltejs/kit": "^1.24.1"
+ "@sveltejs/kit": "^1.24.1",
+ "axios": "^1.5.0"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^2.4.2",
@@ -378,6 +380,15 @@
"node": ">=12"
}
},
+ "node_modules/@fortawesome/fontawesome-free": {
+ "version": "6.4.2",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.4.2.tgz",
+ "integrity": "sha512-m5cPn3e2+FDCOgi1mz0RexTUvvQibBebOUlUlW0+YrMjDTPkiJ6VTKukA1GRsvRw+12KyJndNjj0O4AgTxm2Pg==",
+ "hasInstallScript": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
@@ -606,6 +617,11 @@
"dequal": "^2.0.3"
}
},
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+ },
"node_modules/autoprefixer": {
"version": "10.4.15",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.15.tgz",
@@ -643,6 +659,16 @@
"postcss": "^8.1.0"
}
},
+ "node_modules/axios": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.0.tgz",
+ "integrity": "sha512-D4DdjDo5CY50Qms0qGQTTw6Q44jl7zRwY7bthds06pUGfChBCTcQs+N743eFWGEd6pRTMd6A+I87aWyFV5wiZQ==",
+ "dependencies": {
+ "follow-redirects": "^1.15.0",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
"node_modules/axobject-query": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz",
@@ -817,6 +843,17 @@
"periscopic": "^3.1.0"
}
},
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/commander": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
@@ -888,6 +925,14 @@
"node": ">=0.10.0"
}
},
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
@@ -1029,6 +1074,38 @@
"node": ">=8"
}
},
+ "node_modules/follow-redirects": {
+ "version": "1.15.2",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
+ "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/fraction.js": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.6.tgz",
@@ -1316,6 +1393,25 @@
"node": ">=10.0.0"
}
},
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/min-indent": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
@@ -1660,6 +1756,11 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"dev": true
},
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+ },
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 37950ed..01ae778 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -22,7 +22,9 @@
"vite": "^4.4.5"
},
"dependencies": {
+ "@fortawesome/fontawesome-free": "^6.4.2",
"@sveltejs/adapter-auto": "^2.1.0",
- "@sveltejs/kit": "^1.24.1"
+ "@sveltejs/kit": "^1.24.1",
+ "axios": "^1.5.0"
}
}
diff --git a/frontend/src/lib/LevelTag.svelte b/frontend/src/lib/LevelTag.svelte
new file mode 100644
index 0000000..a10a3c6
--- /dev/null
+++ b/frontend/src/lib/LevelTag.svelte
@@ -0,0 +1,25 @@
+
+
+{level.levelString}
diff --git a/frontend/src/lib/NavLink.svelte b/frontend/src/lib/NavLink.svelte
new file mode 100644
index 0000000..ae242bc
--- /dev/null
+++ b/frontend/src/lib/NavLink.svelte
@@ -0,0 +1,9 @@
+
+
+
+ {title}
+
diff --git a/frontend/src/lib/WordForm.svelte b/frontend/src/lib/WordForm.svelte
new file mode 100644
index 0000000..45e4116
--- /dev/null
+++ b/frontend/src/lib/WordForm.svelte
@@ -0,0 +1,14 @@
+
+
+
+
+
+{word}
+{definition}
diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts
new file mode 100644
index 0000000..88eacd5
--- /dev/null
+++ b/frontend/src/lib/api.ts
@@ -0,0 +1,8 @@
+import axios from "axios";
+import { PUBLIC_API_ENDPOINT } from "$env/static/public";
+
+const axiosAPI = axios.create({
+ baseURL: PUBLIC_API_ENDPOINT
+});
+
+export default axiosAPI;
\ No newline at end of file
diff --git a/frontend/src/lib/case.ts b/frontend/src/lib/case.ts
new file mode 100644
index 0000000..5745aa9
--- /dev/null
+++ b/frontend/src/lib/case.ts
@@ -0,0 +1,59 @@
+// camelCase to snake_case
+export function camelToSnakeCase(o: any): any {
+ if (o === null || typeof o != "object") {
+ return o;
+ }
+
+ if (Array.isArray(o)) {
+ return o.map(camelToSnakeCase);
+ }
+
+ const build: { [key: string]: string } = {};
+ for (const key in o) {
+ const newKey = toSnake(key);
+
+ let value = o[key];
+
+ if (typeof value === "object") {
+ value = camelToSnakeCase(value);
+ }
+
+ build[newKey] = value;
+ }
+ return build;
+}
+
+// snake_case to camelCase
+export function snakeToCamelCase(o: any): any {
+ if (o === null || typeof o != "object") {
+ return o;
+ }
+
+ if (Array.isArray(o)) {
+ return o.map(snakeToCamelCase);
+ }
+
+ const build: { [key: string]: string } = {};
+ for (const key in o) {
+ const newKey = toCamel(key);
+
+ let value = o[key];
+
+ if (typeof value === "object") {
+ value = snakeToCamelCase(value);
+ }
+
+ build[newKey] = value;
+ }
+ return build;
+}
+
+function toCamel(s: string): string {
+ return s.replace(/([-_][a-z])/gi, ($1) => {
+ return $1.toUpperCase().replace("-", "").replace("_", "");
+ });
+}
+
+function toSnake(s: string): string {
+ return s.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
+}
\ No newline at end of file
diff --git a/frontend/src/lib/types.ts b/frontend/src/lib/types.ts
new file mode 100644
index 0000000..60311e5
--- /dev/null
+++ b/frontend/src/lib/types.ts
@@ -0,0 +1,13 @@
+
+export interface Level {
+ id: number;
+ levelNumber: number;
+ levelString: string;
+}
+
+export interface Word {
+ id: number;
+ word: string;
+ definition: string;
+ level: Level;
+}
\ No newline at end of file
diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte
index 0e19760..37bc047 100644
--- a/frontend/src/routes/+layout.svelte
+++ b/frontend/src/routes/+layout.svelte
@@ -1,6 +1,9 @@
@@ -13,7 +16,9 @@
diff --git a/frontend/src/routes/kaeliou/+page.svelte b/frontend/src/routes/kaeliou/+page.svelte
new file mode 100644
index 0000000..08caf9f
--- /dev/null
+++ b/frontend/src/routes/kaeliou/+page.svelte
@@ -0,0 +1,7 @@
+
+
+
+ {$page.url.pathname}
+
diff --git a/frontend/src/routes/ma_chaeliou/+page.svelte b/frontend/src/routes/ma_chaeliou/+page.svelte
new file mode 100644
index 0000000..08caf9f
--- /dev/null
+++ b/frontend/src/routes/ma_chaeliou/+page.svelte
@@ -0,0 +1,7 @@
+
+
+
+ {$page.url.pathname}
+
diff --git a/frontend/src/routes/ma_geriou/+page.svelte b/frontend/src/routes/ma_geriou/+page.svelte
new file mode 100644
index 0000000..bd050d9
--- /dev/null
+++ b/frontend/src/routes/ma_geriou/+page.svelte
@@ -0,0 +1,81 @@
+
+
+
+ {#await wordResponse}
+ …
+ {:then}
+
+
Live
+
Ger
+
Termenadur
+
+ {#if !isFormNewShown}
+
+
+
+ {:else}
+
+ {/if}
+
+ {#each words as word}
+
+ {/each}
+
+ {/await}
+
diff --git a/grids/admin.py b/grids/admin.py
index 8c38f3f..845c3de 100644
--- a/grids/admin.py
+++ b/grids/admin.py
@@ -1,3 +1,7 @@
from django.contrib import admin
+from .models import Word, Grid, Placement, Level
-# Register your models here.
+admin.site.register(Level)
+admin.site.register(Word)
+admin.site.register(Grid)
+admin.site.register(Placement)
diff --git a/grids/migrations/0001_initial.py b/grids/migrations/0001_initial.py
new file mode 100644
index 0000000..0776a1a
--- /dev/null
+++ b/grids/migrations/0001_initial.py
@@ -0,0 +1,114 @@
+# Generated by Django 4.2.5 on 2023-09-12 12:51
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+ initial = True
+
+ dependencies = []
+
+ operations = [
+ migrations.CreateModel(
+ name="Grid",
+ fields=[
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ],
+ ),
+ migrations.CreateModel(
+ name="Level",
+ fields=[
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("level_numer", models.IntegerField()),
+ ("string_number", models.CharField(max_length=50)),
+ ],
+ ),
+ migrations.CreateModel(
+ name="Word",
+ fields=[
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("word", models.CharField(max_length=50)),
+ ("definition", models.CharField(max_length=255)),
+ (
+ "level",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="words",
+ to="grids.level",
+ ),
+ ),
+ ],
+ ),
+ migrations.CreateModel(
+ name="Placement",
+ fields=[
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("coordinates_first_letter", models.CharField(max_length=10)),
+ (
+ "direction",
+ models.SmallIntegerField(
+ choices=[(1, "Right"), (2, "Bottom")], default=1
+ ),
+ ),
+ (
+ "grid",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE, to="grids.grid"
+ ),
+ ),
+ (
+ "word",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE, to="grids.word"
+ ),
+ ),
+ ],
+ ),
+ migrations.AddField(
+ model_name="grid",
+ name="level",
+ field=models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="grids",
+ to="grids.level",
+ ),
+ ),
+ migrations.AddField(
+ model_name="grid",
+ name="words",
+ field=models.ManyToManyField(through="grids.Placement", to="grids.word"),
+ ),
+ ]
diff --git a/grids/migrations/0002_rename_level_numer_level_level_number.py b/grids/migrations/0002_rename_level_numer_level_level_number.py
new file mode 100644
index 0000000..32098fb
--- /dev/null
+++ b/grids/migrations/0002_rename_level_numer_level_level_number.py
@@ -0,0 +1,17 @@
+# Generated by Django 4.2.5 on 2023-09-12 12:56
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("grids", "0001_initial"),
+ ]
+
+ operations = [
+ migrations.RenameField(
+ model_name="level",
+ old_name="level_numer",
+ new_name="level_number",
+ ),
+ ]
diff --git a/grids/migrations/0003_rename_string_number_level_level_string.py b/grids/migrations/0003_rename_string_number_level_level_string.py
new file mode 100644
index 0000000..e1c13b6
--- /dev/null
+++ b/grids/migrations/0003_rename_string_number_level_level_string.py
@@ -0,0 +1,17 @@
+# Generated by Django 4.2.5 on 2023-09-12 12:56
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("grids", "0002_rename_level_numer_level_level_number"),
+ ]
+
+ operations = [
+ migrations.RenameField(
+ model_name="level",
+ old_name="string_number",
+ new_name="level_string",
+ ),
+ ]
diff --git a/grids/models.py b/grids/models.py
index 71a8362..4cc5232 100644
--- a/grids/models.py
+++ b/grids/models.py
@@ -1,3 +1,32 @@
from django.db import models
-# Create your models here.
+
+class Level(models.Model):
+ level_number = models.IntegerField()
+ level_string = models.CharField(max_length=50)
+
+
+class Word(models.Model):
+ word = models.CharField(max_length=50)
+ definition = models.CharField(max_length=255)
+ level = models.ForeignKey(Level, on_delete=models.CASCADE, related_name="words")
+
+
+class Grid(models.Model):
+ level = models.ForeignKey(Level, on_delete=models.CASCADE, related_name="grids")
+ words = models.ManyToManyField(Word, through="Placement")
+
+
+class Direction:
+ RIGHT = 1
+ BOTTOM = 2
+
+
+class Placement(models.Model):
+ word = models.ForeignKey(Word, on_delete=models.CASCADE)
+ grid = models.ForeignKey(Grid, on_delete=models.CASCADE)
+ coordinates_first_letter = models.CharField(max_length=10)
+ direction = models.SmallIntegerField(
+ default=Direction.RIGHT,
+ choices=[(Direction.RIGHT, "Right"), (Direction.BOTTOM, "Bottom")],
+ )
diff --git a/grids/serializers.py b/grids/serializers.py
new file mode 100644
index 0000000..e039bf0
--- /dev/null
+++ b/grids/serializers.py
@@ -0,0 +1,28 @@
+from rest_framework import serializers
+from .models import Word, Grid, Placement, Level
+
+
+class LevelSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = Level
+ fields = ["id", "level_number", "level_string"]
+
+
+class WordSerializer(serializers.ModelSerializer):
+ level = LevelSerializer(many=False, read_only=True)
+
+ class Meta:
+ model = Word
+ fields = ["id", "word", "definition", "level"]
+
+
+class GridSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = Grid
+ fields = ["id", "words"]
+
+
+class PlacementSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = Placement
+ fields = ["id", "word", "grid", "coordinates_first_letter", "direction"]
diff --git a/grids/views.py b/grids/views.py
index 91ea44a..1ba2f0f 100644
--- a/grids/views.py
+++ b/grids/views.py
@@ -1,3 +1,33 @@
from django.shortcuts import render
+from rest_framework import pagination, status, viewsets
+from rest_framework.response import Response
-# Create your views here.
+from .models import Word, Grid, Placement, Level
+
+from .serializers import (
+ WordSerializer,
+ GridSerializer,
+ PlacementSerializer,
+ LevelSerializer,
+)
+
+
+class LevelViewSet(viewsets.ModelViewSet):
+ queryset = Level.objects.all()
+ serializer_class = LevelSerializer
+ ordering_fields = ["level_number"]
+
+
+class WordViewSet(viewsets.ModelViewSet):
+ queryset = Word.objects.all()
+ serializer_class = WordSerializer
+
+
+class GridViewSet(viewsets.ModelViewSet):
+ queryset = Grid.objects.all()
+ serializer_class = GridSerializer
+
+
+class PlacementViewSet(viewsets.ModelViewSet):
+ queryset = Placement.objects.all()
+ serializer_class = PlacementSerializer
diff --git a/poetry.lock b/poetry.lock
index 8edb67a..ee80b25 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -34,6 +34,20 @@ tzdata = {version = "*", markers = "sys_platform == \"win32\""}
argon2 = ["argon2-cffi (>=19.1.0)"]
bcrypt = ["bcrypt"]
+[[package]]
+name = "django-cors-headers"
+version = "4.2.0"
+description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "django_cors_headers-4.2.0-py3-none-any.whl", hash = "sha256:9ada212b0e2efd4a5e339360ffc869cb21ac5605e810afe69f7308e577ea5bde"},
+ {file = "django_cors_headers-4.2.0.tar.gz", hash = "sha256:f9749c6410fe738278bc2b6ef17f05195bc7b251693c035752d8257026af024f"},
+]
+
+[package.dependencies]
+Django = ">=3.2"
+
[[package]]
name = "django-environ"
version = "0.11.2"
@@ -106,4 +120,4 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = "^3.11"
-content-hash = "d592e1d1d4d9e64b28486f934462658ca397646908507648190a2711704eb769"
+content-hash = "892a80c189815eb735e7fc36932e4bb9f534ba248bfa6e6af37ca882fec04f14"
diff --git a/pyproject.toml b/pyproject.toml
index bb8364b..1809310 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -11,6 +11,7 @@ python = "^3.11"
django = "^4.2.5"
django-environ = "^0.11.2"
djangorestframework = "^3.14.0"
+django-cors-headers = "^4.2.0"
[build-system]