From 95a48860cff8155374cde3e5874e7d42e23bbb8e Mon Sep 17 00:00:00 2001 From: sina_sajjadi Date: Sun, 18 Oct 2026 16:42:13 +0330 Subject: [PATCH] feat: add profiles app, dynamic forms views, serializers, and openapi schema --- apps/account/admin/user.py | 29 +- apps/dynamic_forms/permissions.py | 39 + .../serializers/form_serializers.py | 9 +- apps/dynamic_forms/tests/test_phase9_forms.py | 156 + apps/dynamic_forms/views/form_views.py | 51 +- apps/profiles/admin.py | 34 +- .../commands/seed_quick_access_links.py | 89 + .../migrations/0003_quickaccesslink.py | 38 + apps/profiles/models/__init__.py | 3 + apps/profiles/models/quick_access_link.py | 83 + apps/profiles/permissions.py | 8 +- apps/profiles/serializers/__init__.py | 5 + .../serializers/institution_serializers.py | 58 + .../profiles/tests/test_quick_access_links.py | 176 + apps/profiles/urls.py | 9 + apps/profiles/views/__init__.py | 9 + apps/profiles/views/institution_views.py | 181 +- dump.rdb | Bin 89 -> 521 bytes openapi.json | 15588 ++++++++++++++++ schema.yml | 1850 +- 20 files changed, 18271 insertions(+), 144 deletions(-) create mode 100644 apps/dynamic_forms/permissions.py create mode 100644 apps/profiles/management/commands/seed_quick_access_links.py create mode 100644 apps/profiles/migrations/0003_quickaccesslink.py create mode 100644 apps/profiles/models/quick_access_link.py create mode 100644 apps/profiles/tests/test_quick_access_links.py create mode 100644 openapi.json diff --git a/apps/account/admin/user.py b/apps/account/admin/user.py index 5c17ac0..4474774 100644 --- a/apps/account/admin/user.py +++ b/apps/account/admin/user.py @@ -69,6 +69,11 @@ class UserAdminChangeForm(UserChangeForm): required=False, label=_("Role & Permissions Overview") ) + can_create_forms = forms.BooleanField( + required=False, + label=_("Can Create Dynamic Forms"), + help_text=_("Toggles permission for creating dynamic forms & surveys for this user.") + ) class Meta(UserChangeForm.Meta): model = User @@ -79,17 +84,30 @@ class UserAdminChangeForm(UserChangeForm): self.fields['email'].required = True if 'custom_permissions' in self.fields: self.fields['custom_permissions'].widget.instance = self.instance + if 'can_create_forms' in self.fields and self.instance and self.instance.pk: + self.fields['can_create_forms'].initial = self.instance.has_perm_flag('can_create_forms') def clean_custom_permissions(self): val = self.cleaned_data.get('custom_permissions') if isinstance(val, str): try: - return json.loads(val) if val.strip() else {} + val = json.loads(val) if val.strip() else {} except Exception: - return {} - elif isinstance(val, dict): - return val - return {} + val = {} + elif not isinstance(val, dict): + val = {} + return val + + def save(self, commit=True): + user = super().save(commit=False) + can_create = self.cleaned_data.get('can_create_forms') + perms = dict(user.custom_permissions or {}) + perms['can_create_forms'] = bool(can_create) + user.custom_permissions = perms + if commit: + user.save() + self.save_m2m() + return user class UserAdmin(ModelAdmin, BaseUserAdmin): @@ -158,6 +176,7 @@ class UserAdmin(ModelAdmin, BaseUserAdmin): "classes": ["tab"], "fields": ( ("is_active", "is_staff", "is_superuser"), + ("can_create_forms",), ("two_factor_enabled", "two_factor_method"), "custom_permissions", ("role", "user_type"), diff --git a/apps/dynamic_forms/permissions.py b/apps/dynamic_forms/permissions.py new file mode 100644 index 0000000..62061c9 --- /dev/null +++ b/apps/dynamic_forms/permissions.py @@ -0,0 +1,39 @@ +from rest_framework.permissions import BasePermission +from django.utils.translation import gettext_lazy as _ + + +class CanCreateDynamicForm(BasePermission): + """ + Permission to create dynamic forms: + 1. User must be authenticated and active. + 2. Platform staff, superusers, or regional admins are permitted. + 3. Regular users must have an associated institution AND + the 'can_create_forms' permission flag (toggled via role or user custom_permissions in panel admin). + """ + message = _("Only users affiliated with an institution and granted form creation permissions can create dynamic forms.") + + def has_permission(self, request, view): + user = request.user + if not user or not user.is_authenticated or not user.is_active: + return False + + if user.is_staff or getattr(user, 'is_super_admin', False) or getattr(user, 'is_regional_admin', False) or user.is_superuser: + return True + + # Check if user has an associated institution + has_institution = bool( + (hasattr(user, 'institution_memberships') and user.institution_memberships.exists()) or + (hasattr(user, 'created_institutions') and user.created_institutions.exists()) or + bool(getattr(user, 'institution_name', None)) + ) + if not has_institution: + self.message = _("Only users affiliated with an institution can create dynamic forms.") + return False + + # Check togglable permission flag from panel admin (Role or custom_permissions) + has_permission_flag = user.has_perm_flag('can_create_forms') if hasattr(user, 'has_perm_flag') else False + if not has_permission_flag: + self.message = _("You do not have permission to create dynamic forms. Please contact an administrator.") + return False + + return True diff --git a/apps/dynamic_forms/serializers/form_serializers.py b/apps/dynamic_forms/serializers/form_serializers.py index dcdfa67..a8e118f 100644 --- a/apps/dynamic_forms/serializers/form_serializers.py +++ b/apps/dynamic_forms/serializers/form_serializers.py @@ -4,24 +4,31 @@ from apps.dynamic_forms.models import FormDefinition, FormResponse, WorkflowStat class FormDefinitionListSerializer(serializers.ModelSerializer): responses_count = serializers.IntegerField(source='responses.count', read_only=True) + creator_name = serializers.CharField(source='created_by.get_full_name', read_only=True) class Meta: model = FormDefinition fields = [ 'id', 'title', 'slug', 'description', 'is_active', 'is_public', 'target_role', + 'created_by', 'creator_name', 'responses_count', 'created_at', ] + read_only_fields = ['id', 'created_by', 'created_at'] class FormDefinitionDetailSerializer(serializers.ModelSerializer): + creator_name = serializers.CharField(source='created_by.get_full_name', read_only=True) + class Meta: model = FormDefinition fields = [ 'id', 'title', 'slug', 'description', 'schema', 'is_active', 'is_public', - 'target_role', 'created_at', 'updated_at', + 'target_role', 'created_by', 'creator_name', + 'created_at', 'updated_at', ] + read_only_fields = ['id', 'created_by', 'created_at', 'updated_at'] class FormResponseSerializer(serializers.ModelSerializer): diff --git a/apps/dynamic_forms/tests/test_phase9_forms.py b/apps/dynamic_forms/tests/test_phase9_forms.py index 7c67bd9..01a2dae 100644 --- a/apps/dynamic_forms/tests/test_phase9_forms.py +++ b/apps/dynamic_forms/tests/test_phase9_forms.py @@ -92,3 +92,159 @@ class Phase9DynamicFormsAPITests(TestCase): self.assertEqual(review_res.status_code, status.HTTP_200_OK) self.assertEqual(review_res.data["workflow_status"], WorkflowStatus.APPROVED) self.assertEqual(review_res.data["admin_notes"], review_payload["admin_notes"]) + + def test_form_list_filtering_by_user(self): + """ + Verify GET /api/v1/forms/?my_forms=true: + - Authenticated user sees only their own forms. + - Unauthenticated request returns 401. + - Default list returns active forms with created_by and creator_name. + """ + user_a = User.objects.create_user( + email="user_a@shiahub.org", + password="Password123!", + fullname="User A", + user_type=User.UserType.CLIENT, + ) + user_b = User.objects.create_user( + email="user_b@shiahub.org", + password="Password123!", + fullname="User B", + user_type=User.UserType.CLIENT, + ) + + form_a1 = FormDefinition.objects.create( + title="Form A1", + slug="form-a1", + schema=[], + is_active=True, + created_by=user_a, + ) + form_a2 = FormDefinition.objects.create( + title="Form A2", + slug="form-a2", + schema=[], + is_active=False, + created_by=user_a, + ) + form_b1 = FormDefinition.objects.create( + title="Form B1", + slug="form-b1", + schema=[], + is_active=True, + created_by=user_b, + ) + + # 1. Unauthenticated request with my_forms=true should return 401 + self.client.logout() + res_unauth = self.client.get("/api/v1/forms/?my_forms=true") + self.assertEqual(res_unauth.status_code, status.HTTP_401_UNAUTHORIZED) + + # 2. Authenticated as user_a with my_forms=true + self.client.force_authenticate(user=user_a) + res_a = self.client.get("/api/v1/forms/?my_forms=true") + self.assertEqual(res_a.status_code, status.HTTP_200_OK) + self.assertEqual(len(res_a.data), 2) + titles_a = [item["title"] for item in res_a.data] + self.assertIn("Form A1", titles_a) + self.assertIn("Form A2", titles_a) + self.assertEqual(res_a.data[0]["created_by"], user_a.id) + self.assertEqual(res_a.data[0]["creator_name"], "User A") + + # 3. Authenticated as user_b with my_forms=true + self.client.force_authenticate(user=user_b) + res_b = self.client.get("/api/v1/forms/?my_forms=true") + self.assertEqual(res_b.status_code, status.HTTP_200_OK) + self.assertEqual(len(res_b.data), 1) + self.assertEqual(res_b.data[0]["title"], "Form B1") + self.assertEqual(res_b.data[0]["created_by"], user_b.id) + self.assertEqual(res_b.data[0]["creator_name"], "User B") + + # 4. Standard listing without my_forms returns all active forms + res_all = self.client.get("/api/v1/forms/") + self.assertEqual(res_all.status_code, status.HTTP_200_OK) + # Should include self.form_def, form_a1, form_b1 (form_a2 is inactive) + active_titles = [item["title"] for item in res_all.data] + self.assertIn("Form A1", active_titles) + self.assertIn("Form B1", active_titles) + self.assertNotIn("Form A2", active_titles) + + def test_form_creation_permissions(self): + """ + Verify POST /api/v1/forms/ permission constraints: + - Unauthenticated -> 401 + - User without institution -> 403 + - User with institution but without can_create_forms -> 403 + - User with institution AND can_create_forms toggled -> 201 + - Staff / Admin user -> 201 + """ + # 1. Unauthenticated -> 401 + self.client.logout() + payload = { + "title": "Unauthenticated Form", + "slug": "unauth-form", + "schema": [], + } + res = self.client.post("/api/v1/forms/", payload, format="json") + self.assertEqual(res.status_code, status.HTTP_401_UNAUTHORIZED) + + # 2. Client without institution -> 403 + no_inst_user = User.objects.create_user( + email="no_inst@shiahub.org", + password="Password123!", + fullname="No Institution User", + user_type=User.UserType.CLIENT, + ) + self.client.force_authenticate(user=no_inst_user) + payload = { + "title": "No Inst Form", + "slug": "no-inst-form", + "schema": [], + } + res = self.client.post("/api/v1/forms/", payload, format="json") + self.assertEqual(res.status_code, status.HTTP_403_FORBIDDEN) + self.assertIn("institution", str(res.data.get("detail", "")).lower()) + + # 3. User with institution name but can_create_forms=False -> 403 + inst_user = User.objects.create_user( + email="inst_user@shiahub.org", + password="Password123!", + fullname="Institution User", + user_type=User.UserType.CLIENT, + institution_name="Al-Mustafa University", + custom_permissions={"can_create_forms": False}, + ) + self.client.force_authenticate(user=inst_user) + payload = { + "title": "Disallowed Form", + "slug": "disallowed-form", + "schema": [], + } + res = self.client.post("/api/v1/forms/", payload, format="json") + self.assertEqual(res.status_code, status.HTTP_403_FORBIDDEN) + + # 4. User with institution AND can_create_forms=True -> 201 + inst_user.custom_permissions["can_create_forms"] = True + inst_user.save() + payload = { + "title": "Permitted Institution Form", + "slug": "permitted-inst-form", + "schema": [ + {"id": "proposal", "label": "Proposal Text", "type": "textarea", "required": True} + ], + } + res = self.client.post("/api/v1/forms/", payload, format="json") + self.assertEqual(res.status_code, status.HTTP_201_CREATED) + self.assertEqual(res.data["created_by"], inst_user.id) + self.assertEqual(res.data["creator_name"], "Institution User") + + # 5. Staff user -> 201 + self.client.force_authenticate(user=self.admin_user) + admin_payload = { + "title": "Admin Created Form", + "slug": "admin-created-form", + "schema": [], + } + res = self.client.post("/api/v1/forms/", admin_payload, format="json") + self.assertEqual(res.status_code, status.HTTP_201_CREATED) + self.assertEqual(res.data["created_by"], self.admin_user.id) diff --git a/apps/dynamic_forms/views/form_views.py b/apps/dynamic_forms/views/form_views.py index dd673ff..75521f2 100644 --- a/apps/dynamic_forms/views/form_views.py +++ b/apps/dynamic_forms/views/form_views.py @@ -8,8 +8,10 @@ from rest_framework.generics import GenericAPIView from rest_framework.permissions import AllowAny, IsAuthenticated from rest_framework.response import Response from rest_framework.parsers import MultiPartParser, FormParser, JSONParser -from drf_spectacular.utils import extend_schema, OpenApiResponse +from drf_spectacular.utils import extend_schema, OpenApiResponse, OpenApiParameter +from drf_spectacular.types import OpenApiTypes +from apps.dynamic_forms.permissions import CanCreateDynamicForm from apps.dynamic_forms.models import FormDefinition, FormResponse, WorkflowStatus from apps.dynamic_forms.serializers import ( FormDefinitionListSerializer, @@ -36,31 +38,62 @@ class FormDefinitionListCreateView(GenericAPIView): queryset = FormDefinition.objects.filter(is_active=True) @extend_schema( - summary="List active dynamic forms", - description="Retrieves available custom dynamic forms and survey workflows.", + summary="List dynamic forms", + description="Retrieves available forms. Supports filtering to only user's own forms via `my_forms=true`.", + parameters=[ + OpenApiParameter( + name="my_forms", + type=OpenApiTypes.BOOL, + location=OpenApiParameter.QUERY, + required=False, + description="If true, returns only forms created by the authenticated user." + ), + ], responses={200: FormDefinitionListSerializer(many=True)}, tags=["Dynamic Forms & Surveys"], ) def get(self, request, *args, **kwargs): - qs = FormDefinition.objects.all() - if not (request.user and request.user.is_authenticated and request.user.is_staff): - qs = qs.filter(is_active=True) + is_my_forms = request.query_params.get('my_forms', '').lower() in ('true', '1') + + if is_my_forms: + if not request.user or not request.user.is_authenticated: + return Response( + {'detail': _("Authentication required to view personal forms.")}, + status=status.HTTP_401_UNAUTHORIZED + ) + qs = FormDefinition.objects.filter(created_by=request.user) + else: + qs = FormDefinition.objects.all() + if not (request.user and request.user.is_authenticated and request.user.is_staff): + qs = qs.filter(is_active=True) + serializer = self.get_serializer(qs, many=True) return Response(serializer.data, status=status.HTTP_200_OK) @extend_schema( summary="Create new dynamic form schema", - description="Defines a new custom form, questions, validation rules, and schema (Staff/Admin only).", + description="Defines a new custom form, questions, validation rules, and schema (Staff or affiliated Institution users with permission).", request=FormDefinitionDetailSerializer, responses={ 201: FormDefinitionDetailSerializer, + 401: OpenApiResponse(description="Authentication required"), 403: OpenApiResponse(description="Permission denied"), }, tags=["Dynamic Forms & Surveys"], ) def post(self, request, *args, **kwargs): - if not request.user or not request.user.is_authenticated or not request.user.is_staff: - return Response({'detail': _("Only platform administrators can create form schemas.")}, status=status.HTTP_403_FORBIDDEN) + if not request.user or not request.user.is_authenticated: + return Response( + {'detail': _("Authentication required to create forms.")}, + status=status.HTTP_401_UNAUTHORIZED + ) + + perm = CanCreateDynamicForm() + if not perm.has_permission(request, self): + return Response( + {'detail': getattr(perm, 'message', _("Only users affiliated with an institution and granted form creation permissions can create dynamic forms."))}, + status=status.HTTP_403_FORBIDDEN + ) serializer = FormDefinitionDetailSerializer(data=request.data) serializer.is_valid(raise_exception=True) diff --git a/apps/profiles/admin.py b/apps/profiles/admin.py index cf382d1..9f0dfa3 100644 --- a/apps/profiles/admin.py +++ b/apps/profiles/admin.py @@ -10,6 +10,7 @@ from apps.profiles.models import ( InstitutionTimeline, InstitutionMedia, InstitutionFollower, + QuickAccessLink, ) from utils.admin import project_admin_site @@ -59,6 +60,12 @@ class InstitutionMemberInline(TabularInline): readonly_fields = ('joined_at',) +class QuickAccessLinkInline(TabularInline): + model = QuickAccessLink + extra = 0 + fields = ('order', 'title', 'url', 'category', 'icon', 'color', 'is_live', 'is_external', 'is_active') + + class InstitutionTimelineInline(StackedInline): model = InstitutionTimeline extra = 0 @@ -91,7 +98,7 @@ class InstitutionAdmin(ModelAdmin): search_fields = ('name', 'region__name', 'city', 'country', 'description', 'email', 'phone') prepopulated_fields = {'slug': ('name',)} readonly_fields = ('follower_count', 'created_at', 'updated_at') - inlines = [InstitutionMemberInline, InstitutionTimelineInline, InstitutionMediaInline] + inlines = [InstitutionMemberInline, QuickAccessLinkInline, InstitutionTimelineInline, InstitutionMediaInline] actions = ['approve_institutions', 'reject_institutions', 'toggle_featured'] def get_queryset(self, request): @@ -218,3 +225,28 @@ class InstitutionFollowerAdmin(ModelAdmin): list_display_links = ('user', 'institution') search_fields = ('user__email', 'user__fullname', 'institution__name') readonly_fields = ('created_at',) + + +@admin.register(QuickAccessLink, site=project_admin_site) +class QuickAccessLinkAdmin(ModelAdmin): + list_display = ( + 'title', + 'institution', + 'category', + 'icon', + 'color_badge', + 'is_live', + 'is_external', + 'order', + 'is_active', + 'created_at', + ) + list_display_links = ('title', 'institution') + list_filter = ('is_active', 'is_live', 'color', 'category', 'institution') + search_fields = ('title', 'url', 'description', 'category', 'institution__name') + readonly_fields = ('created_at', 'updated_at') + + @display(description=_('Color')) + def color_badge(self, obj): + return obj.get_color_display() + diff --git a/apps/profiles/management/commands/seed_quick_access_links.py b/apps/profiles/management/commands/seed_quick_access_links.py new file mode 100644 index 0000000..957bd1c --- /dev/null +++ b/apps/profiles/management/commands/seed_quick_access_links.py @@ -0,0 +1,89 @@ +from django.core.management.base import BaseCommand +from apps.profiles.models import Institution, QuickAccessLink + +PRESETS = [ + { + "title": "زیارت مجازی روضه منوره", + "url": "https://tv.razavi.ir/virtual-tour", + "description": "نمای سه‌بعدی و ۳۶۰ درجه از حرم مطهر رضوی، روضه منوره و صحن‌های متبرکه", + "icon": "landmark", + "color": "emerald", + "category": "زیارت مجازی", + "is_live": False, + "is_external": True, + "order": 1, + }, + { + "title": "پخش زنده حرم مطهر رضوی", + "url": "https://tv.razavi.ir/live", + "description": "پخش مستقیم و ۲۴ ساعته از صحن انقلاب، گوهرشاد و ضریح مطهر", + "icon": "video", + "color": "rose", + "category": "پخش زنده", + "is_live": True, + "is_external": True, + "order": 2, + }, + { + "title": "کتابخانه دیجیتال آستان قدس", + "url": "https://digital.razavi.ir", + "description": "دسترسی و جستجو در صدها هزار نسخه خطی، اسناد تاریخی و کتب معارف", + "icon": "bookOpen", + "color": "blue", + "category": "کتابخانه دیجیتال", + "is_live": False, + "is_external": True, + "order": 3, + }, + { + "title": "مرکز تولیدات چندرسانه‌ای", + "url": "https://media.razavi.ir", + "description": "آرشیو تواشیح، نماهنگ‌ها، سخنرانی‌های مذهبی، پادکست‌ها و مستندات چندزبانه", + "icon": "film", + "color": "purple", + "category": "چندرسانه‌ای", + "is_live": False, + "is_external": True, + "order": 4, + }, +] + + +class Command(BaseCommand): + help = "Seed canonical quick access links for primary institution (e.g. Astan Quds / ID 1)" + + def add_arguments(self, parser): + parser.add_argument('--institution-id', type=int, default=1, help='Institution ID to attach links to') + + def handle(self, *args, **options): + inst_id = options.get('institution_id', 1) + institution = Institution.objects.filter(id=inst_id).first() or Institution.objects.first() + if not institution: + self.stdout.write(self.style.WARNING("No institution found to seed quick access links.")) + return + + created_count = 0 + for item in PRESETS: + link, created = QuickAccessLink.objects.update_or_create( + institution=institution, + title=item['title'], + defaults={ + 'url': item['url'], + 'description': item['description'], + 'icon': item['icon'], + 'color': item['color'], + 'category': item['category'], + 'is_live': item['is_live'], + 'is_external': item['is_external'], + 'order': item['order'], + 'is_active': True, + } + ) + if created: + created_count += 1 + + self.stdout.write( + self.style.SUCCESS( + f"Successfully seeded canonical quick access links for '{institution.name}' (ID: {institution.id})." + ) + ) diff --git a/apps/profiles/migrations/0003_quickaccesslink.py b/apps/profiles/migrations/0003_quickaccesslink.py new file mode 100644 index 0000000..d82b0bb --- /dev/null +++ b/apps/profiles/migrations/0003_quickaccesslink.py @@ -0,0 +1,38 @@ +# Generated by Django 4.2.30 on 2026-10-18 12:18 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0002_region_and_institution_region'), + ] + + operations = [ + migrations.CreateModel( + name='QuickAccessLink', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=255, verbose_name='عنوان خدمت')), + ('url', models.CharField(max_length=500, verbose_name='نشانی اینترنتی یا مسیر')), + ('description', models.CharField(blank=True, default='', max_length=500, verbose_name='توضیح مختصر')), + ('icon', models.CharField(default='link', max_length=50, verbose_name='شناسه آیکون')), + ('color', models.CharField(choices=[('emerald', 'سبز رضوی (Emerald)'), ('rose', 'سرخ زنده (Rose)'), ('blue', 'آبی دانش (Blue)'), ('purple', 'بنفش چندرسانه\u200cای (Purple)'), ('amber', 'طلایی ویژه (Amber)'), ('teal', 'فیروزه\u200cای (Teal)')], default='emerald', max_length=20, verbose_name='طیف رنگی')), + ('category', models.CharField(blank=True, default='', max_length=100, verbose_name='برچسب یا دسته\u200cبندی')), + ('is_live', models.BooleanField(default=False, verbose_name='نشانگر پخش زنده')), + ('is_external', models.BooleanField(default=True, verbose_name='باز شدن در برگه جدید')), + ('order', models.PositiveIntegerField(default=0, verbose_name='ترتیب نمایش')), + ('is_active', models.BooleanField(default=True, verbose_name='فعال بودن')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='تاریخ ایجاد')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='آخرین بروزرسانی')), + ('institution', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='quick_access_links', to='profiles.institution', verbose_name='مؤسسه مربوطه')), + ], + options={ + 'verbose_name': 'لینک دسترسی سریع', + 'verbose_name_plural': 'لینک\u200cهای دسترسی سریع', + 'ordering': ['order', 'id'], + }, + ), + ] diff --git a/apps/profiles/models/__init__.py b/apps/profiles/models/__init__.py index bbb7d20..7ac2d25 100644 --- a/apps/profiles/models/__init__.py +++ b/apps/profiles/models/__init__.py @@ -6,6 +6,7 @@ from .institution import ( InstitutionMedia, InstitutionFollower, ) +from .quick_access_link import QuickAccessLink __all__ = [ 'Region', @@ -15,4 +16,6 @@ __all__ = [ 'InstitutionTimeline', 'InstitutionMedia', 'InstitutionFollower', + 'QuickAccessLink', ] + diff --git a/apps/profiles/models/quick_access_link.py b/apps/profiles/models/quick_access_link.py new file mode 100644 index 0000000..7a6dc97 --- /dev/null +++ b/apps/profiles/models/quick_access_link.py @@ -0,0 +1,83 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ + + +class QuickAccessLink(models.Model): + class ColorChoice(models.TextChoices): + EMERALD = "emerald", _("سبز رضوی (Emerald)") + ROSE = "rose", _("سرخ زنده (Rose)") + BLUE = "blue", _("آبی دانش (Blue)") + PURPLE = "purple", _("بنفش چندرسانه‌ای (Purple)") + AMBER = "amber", _("طلایی ویژه (Amber)") + TEAL = "teal", _("فیروزه‌ای (Teal)") + + institution = models.ForeignKey( + "profiles.Institution", + on_delete=models.CASCADE, + related_name="quick_access_links", + verbose_name=_("مؤسسه مربوطه"), + ) + title = models.CharField( + max_length=255, + verbose_name=_("عنوان خدمت") + ) + url = models.CharField( + max_length=500, + verbose_name=_("نشانی اینترنتی یا مسیر") + ) + description = models.CharField( + max_length=500, + blank=True, + default="", + verbose_name=_("توضیح مختصر") + ) + icon = models.CharField( + max_length=50, + default="link", + verbose_name=_("شناسه آیکون") + ) + color = models.CharField( + max_length=20, + choices=ColorChoice.choices, + default=ColorChoice.EMERALD, + verbose_name=_("طیف رنگی"), + ) + category = models.CharField( + max_length=100, + blank=True, + default="", + verbose_name=_("برچسب یا دسته‌بندی") + ) + is_live = models.BooleanField( + default=False, + verbose_name=_("نشانگر پخش زنده") + ) + is_external = models.BooleanField( + default=True, + verbose_name=_("باز شدن در برگه جدید") + ) + order = models.PositiveIntegerField( + default=0, + verbose_name=_("ترتیب نمایش") + ) + is_active = models.BooleanField( + default=True, + verbose_name=_("فعال بودن") + ) + + created_at = models.DateTimeField( + auto_now_add=True, + verbose_name=_("تاریخ ایجاد") + ) + updated_at = models.DateTimeField( + auto_now=True, + verbose_name=_("آخرین بروزرسانی") + ) + + class Meta: + verbose_name = _("لینک دسترسی سریع") + verbose_name_plural = _("لینک‌های دسترسی سریع") + ordering = ["order", "id"] + + def __str__(self): + return f"{self.title} ({self.institution.name})" diff --git a/apps/profiles/permissions.py b/apps/profiles/permissions.py index f15c223..71586cb 100644 --- a/apps/profiles/permissions.py +++ b/apps/profiles/permissions.py @@ -19,8 +19,8 @@ class IsInstitutionAdminOrReadOnly(BasePermission): if not request.user or not request.user.is_authenticated or not request.user.is_active: return False - # Super admin has global write authority - if getattr(request.user, 'is_super_admin', False) or request.user.is_superuser: + # Super admin or staff has global write authority + if getattr(request.user, 'is_super_admin', False) or request.user.is_superuser or request.user.is_staff: return True # Check if obj is Institution or a related model with an institution foreign key @@ -28,6 +28,10 @@ class IsInstitutionAdminOrReadOnly(BasePermission): if not institution: return False + user_inst_id = getattr(request.user, 'institution_id', None) + if user_inst_id and user_inst_id == institution.id: + return True + return institution.is_admin(request.user) diff --git a/apps/profiles/serializers/__init__.py b/apps/profiles/serializers/__init__.py index 155cd9c..f4a10a1 100644 --- a/apps/profiles/serializers/__init__.py +++ b/apps/profiles/serializers/__init__.py @@ -9,6 +9,8 @@ from .institution_serializers import ( InstitutionDetailSerializer, InstitutionCreateUpdateSerializer, FollowResponseSerializer, + QuickAccessLinkSerializer, + QuickAccessLinkReorderSerializer, ) __all__ = [ @@ -22,5 +24,8 @@ __all__ = [ 'InstitutionDetailSerializer', 'InstitutionCreateUpdateSerializer', 'FollowResponseSerializer', + 'QuickAccessLinkSerializer', + 'QuickAccessLinkReorderSerializer', ] + diff --git a/apps/profiles/serializers/institution_serializers.py b/apps/profiles/serializers/institution_serializers.py index 25970d6..f2409cd 100644 --- a/apps/profiles/serializers/institution_serializers.py +++ b/apps/profiles/serializers/institution_serializers.py @@ -1,4 +1,5 @@ from django.contrib.auth import get_user_model +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from drf_spectacular.utils import extend_schema_field from django.db.models import Q @@ -9,6 +10,7 @@ from apps.profiles.models import ( InstitutionTimeline, InstitutionMedia, InstitutionFollower, + QuickAccessLink, ) User = get_user_model() @@ -74,6 +76,45 @@ class InstitutionMediaSerializer(serializers.ModelSerializer): read_only_fields = ['id', 'institution', 'uploaded_at', 'media_type_display'] +class QuickAccessLinkSerializer(serializers.ModelSerializer): + institution_id = serializers.IntegerField(source='institution.id', read_only=True) + + class Meta: + model = QuickAccessLink + fields = [ + 'id', + 'institution', + 'institution_id', + 'title', + 'url', + 'description', + 'icon', + 'color', + 'category', + 'is_live', + 'is_external', + 'order', + 'is_active', + 'created_at', + 'updated_at', + ] + read_only_fields = ['id', 'institution', 'institution_id', 'created_at', 'updated_at'] + + def validate_url(self, value): + val = value.strip() + if not val.startswith(('http://', 'https://', '/', '#')): + raise serializers.ValidationError(_("URL must start with http://, https:// or /")) + return val + + +class QuickAccessLinkReorderSerializer(serializers.Serializer): + order = serializers.ListField( + child=serializers.IntegerField(), + required=True, + help_text="Ordered list of QuickAccessLink IDs." + ) + + class MemberUserMiniSerializer(serializers.ModelSerializer): class Meta: model = User @@ -189,6 +230,7 @@ class InstitutionDetailSerializer(serializers.ModelSerializer): region_name = serializers.CharField(source='region.name', read_only=True) timeline_entries = InstitutionTimelineSerializer(many=True, read_only=True) media_items = InstitutionMediaSerializer(many=True, read_only=True) + quick_access_links = serializers.SerializerMethodField() projects = serializers.SerializerMethodField() posts = serializers.SerializerMethodField() members_count = serializers.SerializerMethodField() @@ -235,6 +277,7 @@ class InstitutionDetailSerializer(serializers.ModelSerializer): 'user_role', 'timeline_entries', 'media_items', + 'quick_access_links', 'projects', 'posts', 'created_at', @@ -257,12 +300,27 @@ class InstitutionDetailSerializer(serializers.ModelSerializer): 'user_role', 'timeline_entries', 'media_items', + 'quick_access_links', 'projects', 'posts', 'created_at', 'updated_at', ] + @extend_schema_field(QuickAccessLinkSerializer(many=True)) + def get_quick_access_links(self, obj): + links = obj.quick_access_links.all() + request = self.context.get('request') + is_privileged = False + if request and request.user and request.user.is_authenticated: + if request.user.is_staff or getattr(request.user, 'is_super_admin', False) or request.user.is_superuser: + is_privileged = True + elif hasattr(obj, 'is_admin') and obj.is_admin(request.user): + is_privileged = True + if not is_privileged: + links = links.filter(is_active=True) + return QuickAccessLinkSerializer(links.order_by('order', 'id'), many=True, context=self.context).data + @extend_schema_field(serializers.IntegerField) def get_members_count(self, obj) -> int: return obj.members.count() diff --git a/apps/profiles/tests/test_quick_access_links.py b/apps/profiles/tests/test_quick_access_links.py new file mode 100644 index 0000000..ad78b85 --- /dev/null +++ b/apps/profiles/tests/test_quick_access_links.py @@ -0,0 +1,176 @@ +from django.test import TestCase +from django.contrib.auth import get_user_model +from rest_framework.test import APIClient +from rest_framework import status + +from apps.profiles.models import Region, Institution, InstitutionMember, QuickAccessLink + +User = get_user_model() + + +class QuickAccessLinkAPITests(TestCase): + def setUp(self): + self.client = APIClient() + + self.region = Region.objects.create(name="Middle East", code="ME", slug="middle-east") + + self.admin_user = User.objects.create_user( + email="admin@razavi.ir", + password="SecurePassword123!", + fullname="Admin User", + user_type=User.UserType.INSTITUTION_ADMIN, + is_staff=True, + ) + + self.other_user = User.objects.create_user( + email="other@example.com", + password="SecurePassword123!", + fullname="Regular User", + user_type=User.UserType.CLIENT, + ) + + self.institution = Institution.objects.create( + name="Astan Quds Razavi", + slug="astan-quds", + region=self.region, + country="Iran", + city="Mashhad", + created_by=self.admin_user, + ) + + InstitutionMember.objects.create( + institution=self.institution, + user=self.admin_user, + role=InstitutionMember.MemberRole.ADMIN, + ) + + self.link1 = QuickAccessLink.objects.create( + institution=self.institution, + title="زیارت مجازی روضه منوره", + url="https://tv.razavi.ir/virtual-tour", + description="نمای سه‌بعدی و ۳۶۰ درجه از حرم مطهر رضوی", + icon="landmark", + color=QuickAccessLink.ColorChoice.EMERALD, + category="زیارت مجازی", + is_live=False, + is_external=True, + order=1, + is_active=True, + ) + + self.link2 = QuickAccessLink.objects.create( + institution=self.institution, + title="پخش زنده حرم مطهر رضوی", + url="https://tv.razavi.ir/live", + description="پخش مستقیم و ۲۴ ساعته", + icon="video", + color=QuickAccessLink.ColorChoice.ROSE, + category="پخش زنده", + is_live=True, + is_external=True, + order=2, + is_active=False, # Inactive link to test filtering + ) + + def test_embedded_quick_links_in_institution_detail(self): + # Public user should only see active link + response = self.client.get(f"/api/v1/profiles/{self.institution.id}/") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIn("quick_access_links", response.data) + links = response.data["quick_access_links"] + self.assertEqual(len(links), 1) + self.assertEqual(links[0]["id"], self.link1.id) + + # Admin user should see all links + self.client.force_authenticate(user=self.admin_user) + response = self.client.get(f"/api/v1/profiles/{self.institution.id}/") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data["quick_access_links"]), 2) + + def test_list_quick_access_links_public_and_admin(self): + # Public user + response = self.client.get(f"/api/v1/profiles/{self.institution.slug}/quick-links/") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + self.assertEqual(response.data[0]["title"], self.link1.title) + + # Admin user + self.client.force_authenticate(user=self.admin_user) + response = self.client.get(f"/api/v1/profiles/{self.institution.id}/quick-links/") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 2) + + def test_create_quick_access_link_permission(self): + new_data = { + "title": "کتابخانه دیجیتال", + "url": "https://digital.razavi.ir", + "description": "دسترسی به کتب", + "icon": "bookOpen", + "color": "blue", + "category": "کتابخانه", + "order": 3, + "is_live": False, + "is_external": True, + } + + # Unauthenticated create should fail (401) + response = self.client.post(f"/api/v1/profiles/{self.institution.id}/quick-links/", new_data, format="json") + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + # Unauthorized user create should fail (403) + self.client.force_authenticate(user=self.other_user) + response = self.client.post(f"/api/v1/profiles/{self.institution.id}/quick-links/", new_data, format="json") + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + # Admin create should succeed (201) + self.client.force_authenticate(user=self.admin_user) + response = self.client.post(f"/api/v1/profiles/{self.institution.slug}/quick-links/", new_data, format="json") + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(response.data["title"], "کتابخانه دیجیتال") + self.assertEqual(response.data["institution_id"], self.institution.id) + + def test_url_validation(self): + self.client.force_authenticate(user=self.admin_user) + invalid_data = { + "title": "Invalid Link", + "url": "javascript:alert('xss')", + } + response = self.client.post(f"/api/v1/profiles/{self.institution.id}/quick-links/", invalid_data, format="json") + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + error_fields = [e.get('field') for e in response.data.get('errors', [])] if isinstance(response.data, dict) and 'errors' in response.data else response.data + self.assertIn("url", error_fields) + + def test_retrieve_update_and_delete_quick_link(self): + # Retrieve detail + response = self.client.get(f"/api/v1/profiles/{self.institution.id}/quick-links/{self.link1.id}/") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data["id"], self.link1.id) + + # Update link (patch) + self.client.force_authenticate(user=self.admin_user) + patch_response = self.client.patch( + f"/api/v1/profiles/{self.institution.id}/quick-links/{self.link1.id}/", + {"title": "تور مجازی ۳۶۰ درجه به روز رسانی شده"}, + format="json", + ) + self.assertEqual(patch_response.status_code, status.HTTP_200_OK) + self.assertEqual(patch_response.data["title"], "تور مجازی ۳۶۰ درجه به روز رسانی شده") + + # Delete link + del_response = self.client.delete(f"/api/v1/profiles/{self.institution.id}/quick-links/{self.link1.id}/") + self.assertEqual(del_response.status_code, status.HTTP_204_NO_CONTENT) + self.assertFalse(QuickAccessLink.objects.filter(id=self.link1.id).exists()) + + def test_reorder_quick_links(self): + self.client.force_authenticate(user=self.admin_user) + reorder_data = {"order": [self.link2.id, self.link1.id]} + response = self.client.post( + f"/api/v1/profiles/{self.institution.id}/quick-links/reorder/", + reorder_data, + format="json", + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.link1.refresh_from_db() + self.link2.refresh_from_db() + self.assertEqual(self.link2.order, 0) + self.assertEqual(self.link1.order, 1) diff --git a/apps/profiles/urls.py b/apps/profiles/urls.py index 6b901f7..4096283 100644 --- a/apps/profiles/urls.py +++ b/apps/profiles/urls.py @@ -11,6 +11,9 @@ from apps.profiles.views import ( InstitutionFollowToggleView, InstitutionMembersView, InstitutionMemberDetailView, + QuickAccessLinkListCreateView, + QuickAccessLinkDetailView, + QuickAccessLinkReorderView, ) urlpatterns = [ @@ -22,6 +25,11 @@ urlpatterns = [ path('', InstitutionListCreateView.as_view(), name='institution_list_create'), path('/', InstitutionDetailView.as_view(), name='institution_detail'), + # Quick Access Links to Services (F-CMS-04) + path('/quick-links/', QuickAccessLinkListCreateView.as_view(), name='institution_quick_links'), + path('/quick-links//', QuickAccessLinkDetailView.as_view(), name='institution_quick_link_detail'), + path('/quick-links/reorder/', QuickAccessLinkReorderView.as_view(), name='institution_quick_links_reorder'), + # Historical Timeline Milestones path('/timeline/', InstitutionTimelineView.as_view(), name='institution_timeline'), path('/timeline//', InstitutionTimelineDetailView.as_view(), name='institution_timeline_detail'), @@ -37,3 +45,4 @@ urlpatterns = [ path('/members/', InstitutionMembersView.as_view(), name='institution_members'), path('/members//', InstitutionMemberDetailView.as_view(), name='institution_member_detail'), ] + diff --git a/apps/profiles/views/__init__.py b/apps/profiles/views/__init__.py index a522619..ee579e6 100644 --- a/apps/profiles/views/__init__.py +++ b/apps/profiles/views/__init__.py @@ -10,6 +10,10 @@ from .institution_views import ( InstitutionFollowToggleView, InstitutionMembersView, InstitutionMemberDetailView, + QuickAccessLinkListCreateView, + QuickAccessLinkDetailView, + QuickAccessLinkReorderView, + QuickAccessLinkViewSet, ) __all__ = [ @@ -24,5 +28,10 @@ __all__ = [ 'InstitutionFollowToggleView', 'InstitutionMembersView', 'InstitutionMemberDetailView', + 'QuickAccessLinkListCreateView', + 'QuickAccessLinkDetailView', + 'QuickAccessLinkReorderView', + 'QuickAccessLinkViewSet', ] + diff --git a/apps/profiles/views/institution_views.py b/apps/profiles/views/institution_views.py index ca18f6e..9aab423 100644 --- a/apps/profiles/views/institution_views.py +++ b/apps/profiles/views/institution_views.py @@ -3,7 +3,7 @@ from django.contrib.auth import get_user_model from django.db.models import Q, F from django.shortcuts import get_object_or_404 from django.utils.translation import gettext_lazy as _ -from rest_framework import status +from rest_framework import status, viewsets from rest_framework.generics import GenericAPIView from rest_framework.parsers import MultiPartParser, FormParser, JSONParser from rest_framework.permissions import AllowAny, IsAuthenticated @@ -17,6 +17,7 @@ from apps.profiles.models import ( InstitutionTimeline, InstitutionMedia, InstitutionFollower, + QuickAccessLink, ) from apps.profiles.permissions import ( IsInstitutionAdminOrReadOnly, @@ -34,6 +35,8 @@ from apps.profiles.serializers import ( InstitutionMemberSerializer, InstitutionMemberAddSerializer, FollowResponseSerializer, + QuickAccessLinkSerializer, + QuickAccessLinkReorderSerializer, ) from utils.pagination import StandardResultsSetPagination, InstitutionPagination @@ -589,3 +592,179 @@ class InstitutionMemberDetailView(GenericAPIView): member.delete() return Response(status=status.HTTP_204_NO_CONTENT) + + +class QuickAccessLinkListCreateView(GenericAPIView): + serializer_class = QuickAccessLinkSerializer + queryset = QuickAccessLink.objects.all() + + def get_permissions(self): + if self.request.method == 'POST': + return [IsAuthenticated(), IsInstitutionAdminOrReadOnly()] + return [AllowAny()] + + @extend_schema( + summary="List quick access links", + description="Returns administrative quick access service links (virtual tour, live broadcast, digital library, etc.) for an institution.", + responses={200: QuickAccessLinkSerializer(many=True)}, + tags=["Institutions & Profiles"], + ) + def get(self, request, pk_or_slug, *args, **kwargs): + institution = get_institution_by_id_or_slug(pk_or_slug) + links = institution.quick_access_links.all().order_by('order', 'id') + is_admin_user = ( + request.user and request.user.is_authenticated and + (request.user.is_staff or request.user.is_superuser or getattr(request.user, 'is_super_admin', False) or institution.is_admin(request.user)) + ) + if not is_admin_user: + links = links.filter(is_active=True) + serializer = QuickAccessLinkSerializer(links, many=True, context={'request': request}) + return Response(serializer.data, status=status.HTTP_200_OK) + + @extend_schema( + summary="Create quick access link", + description="Creates a new quick access service link for an institution.", + request=QuickAccessLinkSerializer, + responses={201: QuickAccessLinkSerializer}, + tags=["Institutions & Profiles"], + ) + def post(self, request, pk_or_slug, *args, **kwargs): + institution = get_institution_by_id_or_slug(pk_or_slug) + self.check_object_permissions(request, institution) + + serializer = QuickAccessLinkSerializer(data=request.data, context={'request': request}) + serializer.is_valid(raise_exception=True) + link = serializer.save(institution=institution) + return Response(QuickAccessLinkSerializer(link, context={'request': request}).data, status=status.HTTP_201_CREATED) + + +class QuickAccessLinkDetailView(GenericAPIView): + serializer_class = QuickAccessLinkSerializer + queryset = QuickAccessLink.objects.all() + + def get_permissions(self): + if self.request.method in ['PUT', 'PATCH', 'DELETE']: + return [IsAuthenticated(), IsInstitutionAdminOrReadOnly()] + return [AllowAny()] + + @extend_schema( + summary="Retrieve quick access link", + description="Gets details of a specific quick access service link.", + responses={200: QuickAccessLinkSerializer}, + tags=["Institutions & Profiles"], + ) + def get(self, request, pk_or_slug, link_id, *args, **kwargs): + institution = get_institution_by_id_or_slug(pk_or_slug) + link = get_object_or_404(QuickAccessLink, id=link_id, institution=institution) + serializer = QuickAccessLinkSerializer(link, context={'request': request}) + return Response(serializer.data, status=status.HTTP_200_OK) + + @extend_schema( + summary="Update quick access link", + description="Fully updates an existing quick access service link.", + request=QuickAccessLinkSerializer, + responses={200: QuickAccessLinkSerializer}, + tags=["Institutions & Profiles"], + ) + def put(self, request, pk_or_slug, link_id, *args, **kwargs): + institution = get_institution_by_id_or_slug(pk_or_slug) + link = get_object_or_404(QuickAccessLink, id=link_id, institution=institution) + self.check_object_permissions(request, link) + + serializer = QuickAccessLinkSerializer(link, data=request.data, partial=False, context={'request': request}) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response(serializer.data, status=status.HTTP_200_OK) + + @extend_schema( + summary="Partial update quick access link", + description="Partially updates an existing quick access service link.", + request=QuickAccessLinkSerializer, + responses={200: QuickAccessLinkSerializer}, + tags=["Institutions & Profiles"], + ) + def patch(self, request, pk_or_slug, link_id, *args, **kwargs): + institution = get_institution_by_id_or_slug(pk_or_slug) + link = get_object_or_404(QuickAccessLink, id=link_id, institution=institution) + self.check_object_permissions(request, link) + + serializer = QuickAccessLinkSerializer(link, data=request.data, partial=True, context={'request': request}) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response(serializer.data, status=status.HTTP_200_OK) + + @extend_schema( + summary="Delete quick access link", + description="Removes a quick access service link from an institution.", + responses={204: OpenApiResponse(description="Link deleted")}, + tags=["Institutions & Profiles"], + ) + def delete(self, request, pk_or_slug, link_id, *args, **kwargs): + institution = get_institution_by_id_or_slug(pk_or_slug) + link = get_object_or_404(QuickAccessLink, id=link_id, institution=institution) + self.check_object_permissions(request, link) + + link.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + + +class QuickAccessLinkReorderView(GenericAPIView): + serializer_class = QuickAccessLinkReorderSerializer + permission_classes = [IsAuthenticated, IsInstitutionAdminOrReadOnly] + queryset = QuickAccessLink.objects.all() + + @extend_schema( + summary="Reorder quick access links", + description="Reorders the display sequence of quick access links. Accepts either a list of IDs in order `{\"order\": [1, 2, 3]}` or an array of objects `[{\"id\": 1, \"order\": 0}, ...]`.", + request=QuickAccessLinkReorderSerializer, + responses={200: QuickAccessLinkSerializer(many=True)}, + tags=["Institutions & Profiles"], + ) + def post(self, request, pk_or_slug, *args, **kwargs): + institution = get_institution_by_id_or_slug(pk_or_slug) + self.check_object_permissions(request, institution) + + data = request.data + if isinstance(data, list): + for item in data: + if isinstance(item, dict) and 'id' in item and 'order' in item: + QuickAccessLink.objects.filter(id=item['id'], institution=institution).update(order=item['order']) + elif isinstance(data, dict): + if 'order' in data and isinstance(data['order'], list): + for idx, link_id in enumerate(data['order']): + QuickAccessLink.objects.filter(id=link_id, institution=institution).update(order=idx) + elif 'items' in data and isinstance(data['items'], list): + for item in data['items']: + if isinstance(item, dict) and 'id' in item and 'order' in item: + QuickAccessLink.objects.filter(id=item['id'], institution=institution).update(order=item['order']) + + links = institution.quick_access_links.all().order_by('order', 'id') + return Response(QuickAccessLinkSerializer(links, many=True, context={'request': request}).data, status=status.HTTP_200_OK) + + +class QuickAccessLinkViewSet(viewsets.ModelViewSet): + serializer_class = QuickAccessLinkSerializer + permission_classes = [IsInstitutionAdminOrReadOnly] + queryset = QuickAccessLink.objects.all() + + def get_queryset(self): + inst_lookup = self.kwargs.get("institution_pk") or self.kwargs.get("pk_or_slug") + qs = QuickAccessLink.objects.all() + if inst_lookup: + if str(inst_lookup).isdigit(): + qs = qs.filter(institution_id=int(inst_lookup)) + else: + qs = qs.filter(institution__slug=inst_lookup) + if not (self.request.user and self.request.user.is_authenticated and (self.request.user.is_staff or self.request.user.is_superuser)): + qs = qs.filter(is_active=True) + return qs.order_by('order', 'id') + + def perform_create(self, serializer): + inst_lookup = self.kwargs.get("institution_pk") or self.kwargs.get("pk_or_slug") + institution = get_institution_by_id_or_slug(inst_lookup) if inst_lookup else None + if institution: + self.check_object_permissions(self.request, institution) + serializer.save(institution=institution) + else: + serializer.save() + diff --git a/dump.rdb b/dump.rdb index 29f76252a321c63011513d64744be0fe674ff11d..81c8ebe96fc2818575ec0fa5f6c84eaa498c79c7 100644 GIT binary patch literal 521 zcmWG?b@2=~FfcUz#aWb^l3A=5xUrsf`!3*=__#gUkwrkj*loO*!aAH#2E=0DTdowQuY$iTp0msnboVP%w- zXqB9in3I#5m!4|nZ64&BRBqy)TUe}{UgZ+*=#lOkmXR1*pqrIm?3x~LlHrz-=&NS9D xF*h@_Fa|ku24--AeIE}rnv~!as#Z$QPe}!Zi?O8{5GmFEKcwvISNH!#6af9(tik{Q delta 46 zcmeBViJV}l(VufV>la69acYWgZffqKFYCA&esLt`r|Bjo7N;Iy_;21bWoGJ!FOLAr C(-mj{ diff --git a/openapi.json b/openapi.json new file mode 100644 index 0000000..67e54db --- /dev/null +++ b/openapi.json @@ -0,0 +1,15588 @@ +openapi: 3.0.3 +info: + title: API سامانه تعاملات بین‌الملل + version: 2.0.0 + description: مستندات نقاط پایانی سامانه تعاملات بین‌الملل آستان قدس رضوی (فاز دوم) +paths: + /api/v1/account/admin/directory-users/: + get: + operationId: v1_account_admin_directory_users_list + description: |- + Separate, dedicated endpoint for accounts/users list page. + Filters users who have an email, are not deleted (deleted_at is null). + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + tags: + - v1 + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedAdminUserList' + description: '' + post: + operationId: v1_account_admin_directory_users_create + description: |- + Separate, dedicated endpoint for accounts/users list page. + Filters users who have an email, are not deleted (deleted_at is null). + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AdminUserRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/AdminUserRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/AdminUserRequest' + required: true + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/AdminUser' + description: '' + /api/v1/account/admin/directory-users/{id}/: + get: + operationId: v1_account_admin_directory_users_retrieve + description: |- + Separate, dedicated endpoint for accounts/users list page. + Filters users who have an email, are not deleted (deleted_at is null). + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this User. + required: true + tags: + - v1 + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/AdminUser' + description: '' + put: + operationId: v1_account_admin_directory_users_update + description: |- + Separate, dedicated endpoint for accounts/users list page. + Filters users who have an email, are not deleted (deleted_at is null). + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this User. + required: true + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AdminUserRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/AdminUserRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/AdminUserRequest' + required: true + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/AdminUser' + description: '' + patch: + operationId: v1_account_admin_directory_users_partial_update + description: |- + Separate, dedicated endpoint for accounts/users list page. + Filters users who have an email, are not deleted (deleted_at is null). + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this User. + required: true + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedAdminUserRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedAdminUserRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedAdminUserRequest' + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/AdminUser' + description: '' + delete: + operationId: v1_account_admin_directory_users_destroy + description: |- + Separate, dedicated endpoint for accounts/users list page. + Filters users who have an email, are not deleted (deleted_at is null). + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this User. + required: true + tags: + - v1 + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '204': + description: No response body + /api/v1/account/admin/directory-users/export_excel/: + get: + operationId: v1_account_admin_directory_users_export_excel_retrieve + description: |- + Export users to an Excel (.xlsx) file. + Accepts user_ids in POST body for selective bulk export, + or uses current queryset filters if no specific IDs are provided. + tags: + - v1 + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/AdminUser' + description: '' + post: + operationId: v1_account_admin_directory_users_export_excel_create + description: |- + Export users to an Excel (.xlsx) file. + Accepts user_ids in POST body for selective bulk export, + or uses current queryset filters if no specific IDs are provided. + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AdminUserRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/AdminUserRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/AdminUserRequest' + required: true + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/AdminUser' + description: '' + /api/v1/account/admin/login/: + post: + operationId: v1_account_admin_login_create + description: Login specifically for Admin Panel users. Returns auth token on + success. + summary: Admin panel login + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UserLoginRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/UserLoginRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/UserLoginRequest' + required: true + security: + - jwtAuth: [] + - {} + responses: + '201': + description: Returns token and admin user info. + /api/v1/account/admin/notification-templates/: + get: + operationId: v1_account_admin_notification_templates_list + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + tags: + - v1 + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedNotificationTemplateList' + description: '' + post: + operationId: v1_account_admin_notification_templates_create + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/NotificationTemplateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/NotificationTemplateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/NotificationTemplateRequest' + required: true + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/NotificationTemplate' + description: '' + /api/v1/account/admin/notification-templates/{id}/: + get: + operationId: v1_account_admin_notification_templates_retrieve + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this Notification Template. + required: true + tags: + - v1 + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/NotificationTemplate' + description: '' + put: + operationId: v1_account_admin_notification_templates_update + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this Notification Template. + required: true + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/NotificationTemplateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/NotificationTemplateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/NotificationTemplateRequest' + required: true + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/NotificationTemplate' + description: '' + patch: + operationId: v1_account_admin_notification_templates_partial_update + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this Notification Template. + required: true + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedNotificationTemplateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedNotificationTemplateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedNotificationTemplateRequest' + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/NotificationTemplate' + description: '' + delete: + operationId: v1_account_admin_notification_templates_destroy + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this Notification Template. + required: true + tags: + - v1 + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '204': + description: No response body + /api/v1/account/admin/notifications/: + get: + operationId: v1_account_admin_notifications_list + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + tags: + - v1 + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedAdminNotificationList' + description: '' + post: + operationId: v1_account_admin_notifications_create + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AdminNotificationRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/AdminNotificationRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/AdminNotificationRequest' + required: true + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/AdminNotification' + description: '' + /api/v1/account/admin/notifications/{id}/: + get: + operationId: v1_account_admin_notifications_retrieve + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this Notification. + required: true + tags: + - v1 + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/AdminNotification' + description: '' + put: + operationId: v1_account_admin_notifications_update + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this Notification. + required: true + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AdminNotificationRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/AdminNotificationRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/AdminNotificationRequest' + required: true + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/AdminNotification' + description: '' + patch: + operationId: v1_account_admin_notifications_partial_update + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this Notification. + required: true + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedAdminNotificationRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedAdminNotificationRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedAdminNotificationRequest' + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/AdminNotification' + description: '' + delete: + operationId: v1_account_admin_notifications_destroy + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this Notification. + required: true + tags: + - v1 + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '204': + description: No response body + /api/v1/account/admin/users/: + get: + operationId: v1_account_admin_users_list + description: |- + Admin-only endpoint for full CRUD operations on Users. + Includes searching, filtering, and pagination. + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + tags: + - v1 + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedAdminUserList' + description: '' + post: + operationId: v1_account_admin_users_create + description: |- + Admin-only endpoint for full CRUD operations on Users. + Includes searching, filtering, and pagination. + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AdminUserRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/AdminUserRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/AdminUserRequest' + required: true + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/AdminUser' + description: '' + /api/v1/account/admin/users/{id}/: + get: + operationId: v1_account_admin_users_retrieve + description: |- + Admin-only endpoint for full CRUD operations on Users. + Includes searching, filtering, and pagination. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this User. + required: true + tags: + - v1 + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/AdminUser' + description: '' + put: + operationId: v1_account_admin_users_update + description: |- + Admin-only endpoint for full CRUD operations on Users. + Includes searching, filtering, and pagination. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this User. + required: true + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AdminUserRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/AdminUserRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/AdminUserRequest' + required: true + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/AdminUser' + description: '' + patch: + operationId: v1_account_admin_users_partial_update + description: |- + Admin-only endpoint for full CRUD operations on Users. + Includes searching, filtering, and pagination. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this User. + required: true + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedAdminUserRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedAdminUserRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedAdminUserRequest' + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/AdminUser' + description: '' + delete: + operationId: v1_account_admin_users_destroy + description: |- + Admin-only endpoint for full CRUD operations on Users. + Includes searching, filtering, and pagination. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this User. + required: true + tags: + - v1 + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '204': + description: No response body + /api/v1/account/admin/users/export_excel/: + get: + operationId: v1_account_admin_users_export_excel_retrieve + description: |- + Export users to an Excel (.xlsx) file. + Accepts user_ids in POST body for selective bulk export, + or uses current queryset filters if no specific IDs are provided. + tags: + - v1 + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/AdminUser' + description: '' + post: + operationId: v1_account_admin_users_export_excel_create + description: |- + Export users to an Excel (.xlsx) file. + Accepts user_ids in POST body for selective bulk export, + or uses current queryset filters if no specific IDs are provided. + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AdminUserRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/AdminUserRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/AdminUserRequest' + required: true + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/AdminUser' + description: '' + /api/v1/account/exchange-token/: + post: + operationId: v1_account_exchange_token_create + description: Exchange a one-time temporary token (issued by mobile app) for + a real auth token and user info. + summary: Exchange temporary token for user authentication + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ExchangeTokenRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ExchangeTokenRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/ExchangeTokenRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + description: Token exchanged successfully. Returns auth token and user details. + '400': + description: Invalid request — token not provided or malformed. + '404': + description: Token not found, expired, or user does not exist. + /api/v1/account/guest/: + post: + operationId: v1_account_guest_create + description: Create a guest user account with device information. + summary: Create guest user account + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UserGuestRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/UserGuestRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/UserGuestRequest' + security: + - jwtAuth: [] + - {} + responses: + '200': + description: Returns auth token for the guest user. + /api/v1/account/location-update/: + post: + operationId: v1_account_location_update_create + description: Records client coordinates and IP address for session security. + summary: Record location update + tags: + - Location + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/LocationHistoryRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/LocationHistoryRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/LocationHistoryRequest' + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/LocationHistory' + description: '' + /api/v1/account/login/: + post: + operationId: v1_account_login_create + description: "\n# \U0001F408 Scenario\n\U0001F6E0️ ورود به حساب کاربری\n\nکاربر + با وارد کردن ایمیل و رمز عبور خود به سیستم وارد می‌شود. اگر اعتبارنامه‌ها + معتبر باشند، توکن احراز هویت برای دسترسی به دیگر بخش‌های سیستم بازگردانده + می‌شود.\n\n---\n\n## \U0001F680 درخواست API\n\n### URL:\n```\nPOST /api/login/\n```\n\n### + Header:\n| کلید | مقدار |\n|---------------|---------------------------------|\n| + Content-Type | application/json |\n| Authorization | Optional + (برای این endpoint نیاز نیست) |\n\n### Body:\n```json\n{\n \"email\": \"johndoe@example.com\",\n + \ \"password\": \"strongpassword\",\n \"fcm\": \"fcm_token_optional\",\n + \ \"device_id\": \"device_id_optional\"\n}\n```\n\n---\n\n## \U0001F4CA + پاسخ‌ها\n\n| کد وضعیت | توضیحات |\n|---------------|-----------------------------------------------------------|\n| + `201` | موفقیت‌آمیز - کاربر با موفقیت وارد شد و توکن احراز هویت بازگردانده + شد. |\n| `400` | درخواست نادرست - مشکلات مربوط به داده‌های ارسالی. + \ |\n| `404` | کاربر یافت نشد. |\n| + `500` | مشکل موقتی در سرور. |\n\n---\n\n## + \U0001F4C4 نمونه پاسخ موفقیت‌آمیز\n\n```json\n{\n \"id\": 1,\n \"fullname\": + \"John Doe\",\n \"email\": \"johndoe@example.com\",\n \"token\": \"abc123def456\",\n + \ \"avatar\": \"https://example.com/avatar.jpg\"\n}\n```\n\n---\n\n## \U0001F4C4 + نمونه پاسخ خطا\n\n### ورود ناموفق (اطلاعات اشتباه):\n```json\n{\n \"status\": + \"error\",\n \"code\": \"invalid_credentials\",\n \"status_code\": 400,\n + \ \"message\": \"Unable to log in with provided credentials.\"\n}\n```\n\n### + کاربر یافت نشد:\n```json\n{\n \"status\": \"error\",\n \"code\": \"not_found\",\n + \ \"status_code\": 404,\n \"message\": \"User not found.\"\n}\n```\n\n### + مشکل موقتی در سرور:\n```json\n{\n \"status\": \"error\",\n \"code\": + \"service_unavailable\",\n \"status_code\": 500,\n \"message\": \"Service + temporarily unavailable.\"\n}\n```\n\n---\n\n## \U0001F4A1 نکات مهم:\n1. **رمز + عبور:**\n - رمز عبور باید صحیح و مطابق با آنچه کاربر هنگام ثبت‌نام ارائه + کرده است، باشد.\n2. **توکن احراز هویت:**\n - پس از ورود موفقیت‌آمیز، توکن + احراز هویت به کاربر بازگردانده می‌شود که برای دسترسی به دیگر بخش‌های سیستم + نیاز است.\n3. **اطلاعات دستگاه:**\n - `fcm` و `device_id` به عنوان اطلاعات + اختیاری برای شناسایی دستگاه ارسال می‌شوند.\n\n---\n\n## \U0001F527 توضیحات + فنی:\n\n### فرآیند ورود به حساب کاربری:\n1. کاربر ایمیل و رمز عبور خود را + وارد می‌کند.\n2. سیستم سعی می‌کند کاربر را با استفاده از اعتبارنامه‌های ارائه + شده احراز هویت کند.\n3. اگر کاربر یافت شود و اعتبارنامه‌ها صحیح باشند، یک + توکن احراز هویت ایجاد شده و به کاربر بازگردانده می‌شود.\n4. اگر اعتبارنامه + نادرست باشند، پیام خطا برگردانده می‌شود.\n\n### ولیدیشن‌ها:\n- **ایمیل و رمز + عبور:**\n - بررسی می‌شود که ایمیل و رمز عبور وارد شده معتبر باشند.\n - اگر + کاربر با این ایمیل و رمز عبور یافت نشود، پیام خطای مناسب برگردانده می‌شود.\n\n---\n\n## + \U0001F4C4 نمونه درخواست:\n\n### درخواست کامل:\n```json\n{\n \"email\": + \"janedoe@example.com\",\n \"password\": \"mypassword\",\n \"fcm\": + \"fcm_token_example\",\n \"device_id\": \"device_id_example\"\n}\n```\n\n### + پاسخ موفق:\n```json\n{\n \"id\": 2,\n \"fullname\": \"Jane Doe\",\n + \ \"email\": \"janedoe@example.com\",\n \"token\": \"xyz987uvw654\",\n + \ \"avatar\": null\n}\n```\n" + summary: User login + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UserLoginRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/UserLoginRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/UserLoginRequest' + required: true + security: + - jwtAuth: [] + - {} + responses: + '201': + description: Returns token and user info. + /api/v1/account/notif/: + get: + operationId: v1_account_notif_list + description: Retrieve a paginated list of notifications for the authenticated + user. + summary: Retrieve user notifications + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + tags: + - Notifications + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedNotificationList' + description: '' + /api/v1/account/notif/read/: + post: + operationId: v1_account_notif_read_create + description: Mark all notifications as read for the authenticated user. + summary: Mark all notifications as read + tags: + - Notifications + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + description: All notifications marked as read + /api/v1/account/notif/send/: + post: + operationId: v1_account_notif_send_create + description: Dispatch an in-app notification to a specific user. + summary: Send notification to user + tags: + - Notifications + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/SendNotificationRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/SendNotificationRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/SendNotificationRequest' + required: true + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + description: Notification dispatched. + '404': + description: User not found. + /api/v1/account/profile/: + get: + operationId: v1_account_profile_retrieve + tags: + - v1 + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/UserProfile' + description: '' + /api/v1/account/profile/delete/: + delete: + operationId: v1_account_profile_delete_destroy + description: Soft deletes the current authenticated user account and revokes + active tokens. + summary: Delete current user account + tags: + - Account + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '204': + description: Account successfully deleted + '404': + description: User does not exist + /api/v1/account/profile/update/: + put: + operationId: v1_account_profile_update_update + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UserProfileRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/UserProfileRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/UserProfileRequest' + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/UserProfile' + description: '' + patch: + operationId: v1_account_profile_update_partial_update + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedUserProfileRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedUserProfileRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedUserProfileRequest' + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/UserProfile' + description: '' + /api/v1/account/recover/: + post: + operationId: v1_account_recover_create + description: "\n# \U0001F408 Scenario\n\U0001F6E0️ بازیابی رمز عبور\n\nکاربر + با وارد کردن ایمیل خود، درخواست بازیابی رمز عبور می‌دهد. \nیک کد تأیید به + ایمیل کاربر ارسال می‌شود تا کاربر بتواند رمز عبور خود را بازیابی کند.\nسپس + کاربر باید به صفحه وریفای ریدایرکت شود\nو بعد از تایید وریفای با توکن داده + شده \nبه صفحه ریست پسورد ریدایرکت میشود تا پسور جدیدی را ست کند\n\n---\n\n## + \U0001F680 درخواست API\n\n### URL:\n```\nPOST /api/recover-password/\n```\n\n### + Header:\n| کلید | مقدار |\n|---------------|---------------------------------|\n| + Content-Type | application/json |\n| Authorization | Optional + (برای این endpoint نیاز نیست) |\n\n### Body:\n```json\n{\n \"email\": \"johndoe@example.com\"\n}\n```\n\n---\n\n## + \U0001F4CA پاسخ‌ها\n\n| کد وضعیت | توضیحات |\n|---------------|-----------------------------------------------------------|\n| + `202` | موفقیت‌آمیز - کد بازیابی رمز عبور به ایمیل کاربر ارسال شد. + \ |\n| `400` | درخواست نادرست - مشکلات مربوط به داده‌های ارسالی. + \ |\n| `404` | کاربر یافت نشد. |\n| + `500` | مشکل موقتی در سرور. |\n\n---\n\n## + \U0001F4C4 نمونه پاسخ موفقیت‌آمیز\n\n```json\n{\n \"id\": 1,\n \"fullname\": + \"John Doe\",\n \"phone_number\": \"1234567890\",\n \"email\": \"johndoe@example.com\",\n + \ \"avatar\": null,\n \"message\": \"Forgot password code sent\"\n}\n```\n\n---\n\n## + \U0001F4C4 نمونه پاسخ خطا\n\n### کاربر یافت نشد:\n```json\n{\n \"status\": + \"error\",\n \"code\": \"not_found\",\n \"status_code\": 404,\n \"message\": + \"User not found.\"\n}\n```\n\n### مشکل موقتی در سرور:\n```json\n{\n \"status\": + \"error\",\n \"code\": \"service_unavailable\",\n \"status_code\": 500,\n + \ \"message\": \"Service temporarily unavailable.\"\n}\n```\n\n---\n\n## + \U0001F4A1 نکات مهم:\n1. **کد بازیابی رمز عبور:**\n - کد تأیید به ایمیل + کاربر ارسال می‌شود و باید در مرحله بعدی برای بازیابی رمز عبور استفاده شود.\n2. + **امنیت:**\n - کد بازیابی رمز عبور فقط برای مدت محدود اعتبار دارد و بعد + از آن منقضی می‌شود.\n\n---\n\n## \U0001F527 توضیحات فنی:\n\n### فرآیند بازیابی + رمز عبور:\n1. کاربر ایمیل خود را وارد می‌کند.\n2. سیستم بررسی می‌کند که آیا + کاربری با این ایمیل وجود دارد یا خیر.\n3. اگر کاربر یافت شود، یک کد تأیید + بازیابی رمز عبور به ایمیل کاربر ارسال می‌شود.\n4. کاربر باید این کد را در + مرحله بعدی برای تنظیم رمز عبور جدید وارد کند.\n\n### ولیدیشن‌ها:\n- **ایمیل:**\n + \ - بررسی می‌شود که ایمیل وارد شده معتبر باشد.\n - اگر کاربری با این ایمیل + یافت نشود، پیام خطای مناسب برگردانده می‌شود.\n\n---\n\n## \U0001F4C4 نمونه + درخواست:\n\n### درخواست کامل:\n```json\n{\n \"email\": \"janedoe@example.com\"\n}\n```\n\n### + پاسخ موفق:\n```json\n{\n \"id\": 2,\n \"fullname\": \"Jane Doe\",\n + \ \"phone_number\": \"0987654321\",\n \"email\": \"janedoe@example.com\",\n + \ \"avatar\": null,\n \"message\": \"Forgot password code sent\"\n}\n```\n" + summary: Request password recovery + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UserRecoverPasswordRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/UserRecoverPasswordRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/UserRecoverPasswordRequest' + required: true + security: + - jwtAuth: [] + - {} + responses: + '202': + description: Recovery code sent to email. + /api/v1/account/region-info/: + get: + operationId: v1_account_region_info_retrieve + description: Returns basic client region, browser, and network info. + summary: Get client region info + tags: + - Location + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/RegionInfo' + description: '' + /api/v1/account/register/: + post: + operationId: v1_account_register_create + description: "\n# \U0001F408 Scenario\nثبت نام کاربر\n\nکاربر با وارد کردن اطلاعات + مورد نیاز شامل نام کامل، ایمیل، رمز عبور و تأیید رمز عبور درخواست ثبت‌نام + ارسال می‌کند. پس از ثبت موفق، یک کد تأیید به ایمیل ارسال می‌شود که برای تکمیل + ثبت‌نام مورد نیاز است.\n\n---\n\n## \U0001F680 درخواست API\n\n### URL:\n```\nPOST + /api/register/\n```\n\n### Header:\n| کلید | مقدار |\n|---------------|---------------------------------|\n| + Content-Type | application/json |\n| Authorization | Optional + (برای این endpoint نیاز نیست) |\n\n### Body:\n```json\n{\n \"fullname\": + \"John Doe\",\n \"email\": \"johndoe@example.com\",\n \"password\": + \"strongpassword\",\n \"password_confirmation\": \"strongpassword\",\n + \ \"fcm\": \"fcm_token_optional\",\n \"device_id\": \"device_id_optional\"\n}\n```\n\n---\n\n## + \U0001F4CA پاسخ‌ها\n\n| کد وضعیت | توضیحات |\n|---------------|-----------------------------------------------------------|\n| + `202` | موفقیت‌آمیز - کد تأیید به ایمیل کاربر ارسال شد. |\n| + `400` | درخواست نادرست - مشکلات مربوط به داده‌های ارسالی. |\n| + `409` | ایمیل قبلاً ثبت شده است. |\n| + `404` | کاربر یا منبع یافت نشد. |\n| + `410` | کد تأیید منقضی شده است. |\n| + `500` | مشکل موقتی در سرور. |\n\n---\n\n## + \U0001F4C4 نمونه پاسخ موفقیت‌آمیز\n\n```json\n{\n \"user\": {\n \"id\": + 1,\n \"fullname\": \"John Doe\",\n \"email\": \"johndoe@example.com\"\n + \ },\n \"message\": \"The otp code was sent to the user's email\"\n}\n```\n\n---\n\n## + \U0001F4C4 نمونه پاسخ خطا\n\n### ایمیل تکراری:\n```json\n{\n \"status\": + \"error\",\n \"code\": \"validation_error\",\n \"status_code\": 409,\n + \ \"message\": \"There were validation errors.\",\n \"errors\": [\n {\n + \ \"field\": \"email\",\n \"message\": \"This email is + already registered.\"\n }\n ]\n}\n```\n\n### رمز عبور و تأیید رمز + عبور برابر نیستند:\n```json\n{\n \"status\": \"error\",\n \"code\": + \"validation_error\",\n \"status_code\": 400,\n \"message\": \"There + were validation errors.\",\n \"errors\": [\n {\n \"field\": + \"password_confirmation\",\n \"message\": \"Passwords do not match.\"\n + \ }\n ]\n}\n```\n\n### رمز عبور کوتاه‌تر از 8 کاراکتر است:\n```json\n{\n + \ \"status\": \"error\",\n \"code\": \"validation_error\",\n \"status_code\": + 400,\n \"message\": \"There were validation errors.\",\n \"errors\": + [\n {\n \"field\": \"password\",\n \"message\": + \"Password must be at least 8 characters long.\"\n }\n ]\n}\n```\n\n### + درخواست نامعتبر (فیلدهای اجباری):\n```json\n{\n \"status\": \"error\",\n + \ \"code\": \"validation_error\",\n \"status_code\": 400,\n \"message\": + \"There were validation errors.\",\n \"errors\": [\n {\n \"field\": + \"fullname\",\n \"message\": \"This field is required.\"\n },\n + \ {\n \"field\": \"email\",\n \"message\": \"This + field is required.\"\n },\n {\n \"field\": \"password\",\n + \ \"message\": \"This field is required.\"\n },\n {\n + \ \"field\": \"password_confirmation\",\n \"message\": + \"This field is required.\"\n }\n ]\n}\n```\n\n### کاربر یافت نشد:\n```json\n{\n + \ \"status\": \"error\",\n \"code\": \"not_found\",\n \"status_code\": + 404,\n \"message\": \"The requested resource was not found.\"\n}\n```\n\n### + کد تأیید منقضی شده است:\n```json\n{\n \"status\": \"error\",\n \"code\": + \"expired_code\",\n \"status_code\": 410,\n \"message\": \"The verification + code has expired.\"\n}\n```\n\n### مشکل موقتی در سرور:\n```json\n{\n \"status\": + \"error\",\n \"code\": \"service_unavailable\",\n \"status_code\": 500,\n + \ \"message\": \"Service temporarily unavailable.\"\n}\n```\n\n---\n\n## + \U0001F4A1 نکات مهم:\n1. **رمز عبور:**\n - باید حداقل 8 کاراکتر باشد.\n + \ - رمز عبور و تأیید رمز عبور (`password_confirmation`) باید یکسان باشند.\n2. + **ایمیل:**\n - باید یک آدرس ایمیل معتبر باشد.\n - ایمیل‌های تکراری مجاز + نیستند.\n3. **کد OTP:**\n - کد تأیید به ایمیل ارسال می‌شود و برای وریفای + کاربر استفاده می‌شود.\n4. **فیلدهای اختیاری:**\n - `fcm` و `device_id` در + صورت نیاز می‌توانند ارسال شوند اما اجباری نیستند.\n\n---\n\n### ولیدیشن‌ها:\n- + **ایمیل:**\n - بررسی می‌شود که در سیستم موجود نباشد.\n - اگر موجود باشد، + پیام خطای زیر برگردانده می‌شود:\n ```json\n {\n \"status\": \"error\",\n + \ \"code\": \"validation_error\",\n \"status_code\": 409,\n \"message\": + \"There were validation errors.\",\n \"errors\": [\n {\n + \ \"field\": \"email\",\n \"message\": \"This + email is already registered.\"\n }\n ]\n }\n ```\n- + **رمز عبور:**\n - بررسی می‌شود که حداقل 8 کاراکتر باشد:\n ```json\n {\n + \ \"status\": \"error\",\n \"code\": \"validation_error\",\n + \ \"status_code\": 400,\n \"message\": \"There were validation + errors.\",\n \"errors\": [\n {\n \"field\": + \"password\",\n \"message\": \"Password must be at least 8 + characters long.\"\n }\n ]\n }\n ```\n - بررسی می‌شود + که با `password_confirmation` یکسان باشد:\n ```json\n {\n \"status\": + \"error\",\n \"code\": \"validation_error\",\n \"status_code\": + 400,\n \"message\": \"There were validation errors.\",\n \"errors\": + [\n {\n \"field\": \"password_confirmation\",\n + \ \"message\": \"Passwords do not match.\"\n }\n + \ ]\n }\n ```\n\n---\n\n## \U0001F4C4 نمونه درخواست:\n\n### درخواست + کامل:\n```json\n{\n \"fullname\": \"Jane Doe\",\n \"email\": \"janedoe@example.com\",\n + \ \"password\": \"securepassword\",\n \"password_confirmation\": \"securepassword\",\n + \ \"fcm\": \"fcm_token_example\",\n \"device_id\": \"device_id_example\"\n}\n```\n\n### + پاسخ موفق:\n```json\n{\n \"user\": {\n \"id\": 2,\n \"fullname\": + \"Jane Doe\",\n \"email\": \"janedoe@example.com\"\n },\n \"message\": + \"The otp code was sent to the user's email\"\n}\n```\n" + summary: Register new user + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UserRegisterRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/UserRegisterRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/UserRegisterRequest' + required: true + security: + - jwtAuth: [] + - {} + responses: + '202': + description: OTP code sent to user email. + /api/v1/account/reset/: + post: + operationId: v1_account_reset_create + description: "\n# \U0001F408 Scenario\n\U0001F6E0️ تنظیم مجدد رمز عبور\n\nکاربر + پس از تأیید کد بازیابی رمز عبور، می‌تواند رمز عبور جدید خود را تنظیم کند. + برای این کار، کاربر باید رمز عبور جدید و تأیید آن را وارد کند.\n\nبعد از ریکاور + و وریفای\nبه این صفحه برای ریست میآید\nکه باید با همان توکنی که در وریفای + دریافت کرده است را درخواست کند\n\n(نکته بعد از ریست پسورد توکن ذخیره شده حذف + شود و کاربر باید با رمز عبور جدیدی که ست کرده است مجددا لاگین را انجام دهد)\n---\n\n## + \U0001F680 درخواست API\n\n### URL:\n```\nPOST /api/reset-password/\n```\n\n### + Header:\n| کلید | مقدار |\n|---------------|---------------------------------|\n| + Content-Type | application/json |\n| Authorization | Bearer + <توکن احراز هویت> |\n\n### Body:\n```json\n{\n \"password\": + \"newstrongpassword\",\n \"password_confirmation\": \"newstrongpassword\"\n}\n```\n\n---\n\n## + \U0001F4CA پاسخ‌ها\n\n| کد وضعیت | توضیحات |\n|---------------|-----------------------------------------------------------|\n| + `200` | موفقیت‌آمیز - رمز عبور با موفقیت تغییر یافت. |\n| + `400` | درخواست نادرست - مشکلات مربوط به داده‌های ارسالی. |\n| + `401` | عدم احراز هویت - کاربر وارد نشده است یا توکن نامعتبر است. + \ |\n| `500` | مشکل موقتی در سرور. |\n\n---\n\n## + \U0001F4C4 نمونه پاسخ موفقیت‌آمیز\n\n```json\n{\n \"message\": \"Your password + has been changed successfully.\"\n}\n```\n\n---\n\n## \U0001F4C4 نمونه پاسخ + خطا\n\n### رمز عبور و تأیید رمز عبور برابر نیستند:\n```json\n{\n \"status\": + \"error\",\n \"code\": \"validation_error\",\n \"status_code\": 400,\n + \ \"message\": \"Passwords do not match.\"\n}\n```\n\n### رمز عبور کوتاه‌تر + از 8 کاراکتر است:\n```json\n{\n \"status\": \"error\",\n \"code\": \"validation_error\",\n + \ \"status_code\": 400,\n \"message\": \"Password must be at least 8 + characters long.\"\n}\n```\n\n### عدم احراز هویت:\n```json\n{\n \"status\": + \"error\",\n \"code\": \"unauthorized\",\n \"status_code\": 401,\n \"message\": + \"Authentication credentials were not provided or are invalid.\"\n}\n```\n\n### + مشکل موقتی در سرور:\n```json\n{\n \"status\": \"error\",\n \"code\": + \"service_unavailable\",\n \"status_code\": 500,\n \"message\": \"Service + temporarily unavailable.\"\n}\n```\n\n---\n\n## \U0001F4A1 نکات مهم:\n1. **رمز + عبور جدید:**\n - باید حداقل 8 کاراکتر باشد و تأیید رمز عبور (`password_confirmation`) + باید با رمز عبور اصلی یکسان باشد.\n2. **امنیت:**\n - کاربر باید توکن احراز + هویت معتبر برای تنظیم مجدد رمز عبور ارائه دهد.\n3. **توکن احراز هویت:**\n + \ - فقط کاربران احراز هویت شده می‌توانند رمز عبور خود را تغییر دهند.\n\n---\n\n## + \U0001F527 توضیحات فنی:\n\n### فرآیند تنظیم مجدد رمز عبور:\n1. کاربر باید + ابتدا کد بازیابی رمز عبور را تأیید کند.\n2. پس از تأیید موفقیت‌آمیز، کاربر + با استفاده از توکن احراز هویت، رمز عبور جدید و تأیید آن را وارد می‌کند.\n3. + اگر داده‌ها معتبر باشند، رمز عبور جدید برای کاربر تنظیم می‌شود.\n4. اگر داده‌ها + نادرست باشند، پیام خطای مناسب به کاربر بازگردانده می‌شود.\n\n### ولیدیشن‌ها:\n- + **رمز عبور:**\n - بررسی می‌شود که رمز عبور حداقل 8 کاراکتر باشد.\n - بررسی + می‌شود که رمز عبور و تأیید آن یکسان باشند.\n\n---\n\n## \U0001F4C4 نمونه درخواست:\n\n### + درخواست کامل:\n```json\n{\n \"password\": \"mynewpassword\",\n \"password_confirmation\": + \"mynewpassword\"\n}\n```\n\n### پاسخ موفق:\n```json\n{\n \"message\": + \"Your password has been changed successfully.\"\n}\n```\n" + summary: Reset user password + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UserResetPasswordRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/UserResetPasswordRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/UserResetPasswordRequest' + required: true + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + description: Password changed successfully. + /api/v1/account/update-fcm/: + post: + operationId: v1_account_update_fcm_create + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UserFCMRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/UserFCMRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/UserFCMRequest' + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/UserFCM' + description: '' + /api/v1/account/verify/: + post: + operationId: v1_account_verify_create + description: "\n# \U0001F408 Scenario\n\U0001F4C5️ تأیید حساب کاربری با کد تأیید\n\nکاربر + پس از ثبت‌نام، باید با استفاده از کد تأییدی که به ایمیل او ارسال شده است،\nحساب + کاربری خود را تأیید کند. در این مرحله، کاربر ایمیل و کد تأیید خود را ارسال + می‌کند.\n\n---\n\n## \U0001F680 درخواست API\n\n### URL:\n```\nPOST /api/verify/\n```\n\n### + Header:\n| کلید | مقدار |\n|---------------|---------------------------------|\n| + Content-Type | application/json |\n| Authorization | Optional + (برای این endpoint نیاز نیست) |\n\n### Body:\n```json\n{\n \"email\": \"johndoe@example.com\",\n + \ \"code\": \"12345\"\n}\n```\n\n---\n\n## \U0001F4CA پاسخ‌ها\n\n| کد وضعیت + \ | توضیحات |\n|---------------|-----------------------------------------------------------|\n| + `201` | موفقیت‌آمیز - کاربر تأیید شد و توکن احراز هویت بازگردانده + شد. |\n| `400` | درخواست نادرست - مشکلات مربوط به داده‌های ارسالی. + \ |\n| `404` | کاربر یا کد تأیید یافت نشد. |\n| + `410` | کد تأیید منقضی شده است. |\n| + `500` | مشکل موقتی در سرور. |\n\n---\n\n## + \U0001F4C4 نمونه پاسخ موفقیت‌آمیز\n\n```json\n{\n \"token\": \"abc123def456\",\n + \ \"user_id\": 1,\n \"phone_number\": \"1234567890\",\n \"email\": + \"johndoe@example.com\",\n \"fullname\": \"John Doe\",\n \"avatar\": + null\n}\n```\n\n---\n\n## \U0001F4C4 نمونه پاسخ خطا\n\n### کد تأیید نادرست:\n```json\n{\n + \ \"status\": \"error\",\n \"code\": \"invalid_verification_code\",\n + \ \"status_code\": 400,\n \"message\": \"The verification code is invalid.\"\n}\n```\n\n### + کد تأیید منقضی شده است:\n```json\n{\n \"status\": \"error\",\n \"code\": + \"expired_code\",\n \"status_code\": 410,\n \"message\": \"The verification + code has expired.\"\n}\n```\n\n### کاربر یا کد تأیید یافت نشد:\n```json\n{\n + \ \"status\": \"error\",\n \"code\": \"not_found\",\n \"status_code\": + 404,\n \"message\": \"Verification data not found or expired.\"\n}\n```\n\n### + مشکل موقتی در سرور:\n```json\n{\n \"status\": \"error\",\n \"code\": + \"service_unavailable\",\n \"status_code\": 500,\n \"message\": \"Service + temporarily unavailable.\"\n}\n```\n\n---\n\n## \U0001F4A1 نکات مهم:\n1. **کد + تأیید:**\n - کد تأیید باید دقیقاً با کدی که به ایمیل کاربر ارسال شده مطابقت + داشته باشد.\n - کد تأیید فقط برای یک مدت محدود اعتبار دارد.\n2. **خطاها:**\n + \ - اگر کد تأیید نادرست باشد، پیام مناسب بازگردانده می‌شود.\n - اگر کد + تأیید منقضی شده باشد، کاربر باید درخواست کد جدید کند.\n3. **توکن احراز هویت:**\n + \ - پس از تأیید موفقیت‌آمیز، توکن احراز هویت به کاربر بازگردانده می‌شود که + برای دسترسی به دیگر بخش‌های سیستم نیاز است.\n\n---\n\n### ولیدیشن‌ها:\n- **کد + تأیید:**\n - باید حداکثر 5 کاراکتر باشد.\n - اگر کد معتبر نباشد یا منقضی + شده باشد، پیام خطای مناسب برگردانده می‌شود.\n\n---\n\n## \U0001F4C4 نمونه + درخواست:\n\n### درخواست کامل:\n```json\n{\n \"email\": \"janedoe@example.com\",\n + \ \"code\": \"67890\"\n}\n```\n\n### پاسخ موفق:\n```json\n{\n \"token\": + \"xyz987uvw654\",\n \"user_id\": 2,\n \"phone_number\": \"0987654321\",\n + \ \"email\": \"janedoe@example.com\",\n \"fullname\": \"Jane Doe\",\n + \ \"avatar\": null\n}\n```\n" + summary: Verify OTP and activate user + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UserVerifyRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/UserVerifyRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/UserVerifyRequest' + required: true + security: + - jwtAuth: [] + - {} + responses: + '201': + description: Returns token and user info. + /api/v1/account/web/register/: + post: + operationId: v1_account_web_register_create + description: Web registration with password and confirmation. + summary: Register web user with password + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/WebUserRegisterRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/WebUserRegisterRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/WebUserRegisterRequest' + required: true + security: + - jwtAuth: [] + - {} + responses: + '202': + description: OTP code sent to user email. + /api/v1/analytics/dashboard/: + get: + operationId: v1_analytics_dashboard_retrieve + description: Aggregates platform KPIs across international institutions, collaborative + projects, LMS academy, official ticketing, campaigns, and field diplomacy + reports. + summary: Executive KPIs & Global Analytics Dashboard + tags: + - Executive Analytics & KPIs + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/ExecutiveDashboard' + description: '' + /api/v1/auth/2fa/backup-codes/regenerate/: + post: + operationId: v1_auth_2fa_backup_codes_regenerate_create + description: Generates 8 fresh single-use 8-character recovery codes and invalidates + any previously generated codes. + summary: Regenerate 2FA backup recovery codes + tags: + - Authentication & Profile + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TwoFactorBackupCodesRegenerateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TwoFactorBackupCodesRegenerateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/TwoFactorBackupCodesRegenerateRequest' + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + description: New backup codes generated + /api/v1/auth/2fa/disable/: + post: + operationId: v1_auth_2fa_disable_create + description: Disables 2FA unless an administrator has enforced mandatory 2FA + (`require_2fa = True`) for the user or their Role. + summary: Disable 2FA for the current user + tags: + - Authentication & Profile + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TwoFactorDisableRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TwoFactorDisableRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/TwoFactorDisableRequest' + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + description: 2FA disabled + '403': + description: 2FA is enforced by administrator and cannot be disabled + /api/v1/auth/2fa/resend/: + post: + operationId: v1_auth_2fa_resend_create + description: Generates and sends a fresh 6-digit Email OTP code for the active + `temp_token` challenge. + summary: Send or resend 2FA Email OTP for an active login challenge + tags: + - Authentication & Profile + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TwoFactorResendEmailRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TwoFactorResendEmailRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/TwoFactorResendEmailRequest' + required: true + security: + - jwtAuth: [] + - {} + responses: + '200': + description: Email OTP sent + '400': + description: Invalid or expired challenge + /api/v1/auth/2fa/setup/confirm/: + post: + operationId: v1_auth_2fa_setup_confirm_create + description: Verifies the 6-digit code from the Authenticator app or Email, + activates 2FA, and returns 8 one-time backup recovery codes. + summary: Confirm 2FA setup and generate backup recovery codes + tags: + - Authentication & Profile + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TwoFactorSetupConfirmRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TwoFactorSetupConfirmRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/TwoFactorSetupConfirmRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + description: 2FA confirmed and enabled + '400': + description: Invalid or expired setup verification code + /api/v1/auth/2fa/setup/initiate/: + post: + operationId: v1_auth_2fa_setup_initiate_create + description: For `method='totp'`, generates a fresh Base32 TOTP secret, otpauth + URI, and QR code Data URL. For `method='email'`, sends a 6-digit confirmation + OTP to the user's email. + summary: Initiate 2FA setup (TOTP QR code or Email OTP verification) + tags: + - Authentication & Profile + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TwoFactorSetupInitiateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TwoFactorSetupInitiateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/TwoFactorSetupInitiateRequest' + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + description: Setup initiated + /api/v1/auth/2fa/status/: + get: + operationId: v1_auth_2fa_status_retrieve + description: Returns whether 2FA is enabled, whether it is forced by an administrator, + the active method, and remaining backup codes. + summary: Get current user's 2FA status and admin enforcement state + tags: + - Authentication & Profile + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + description: Current 2FA status + /api/v1/auth/2fa/toggle/: + post: + operationId: v1_auth_2fa_toggle_create + description: Allows the authenticated user to toggle Two-Factor Authentication + (2FA) on or off for themselves. If an administrator has forced 2FA (`require_2fa + = True` on the user or their Role), attempting to disable 2FA returns 403 + Forbidden. + summary: Toggle 2FA on or off for the current user + tags: + - Authentication & Profile + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TwoFactorToggleRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TwoFactorToggleRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/TwoFactorToggleRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + description: 2FA status updated + '403': + description: Cannot disable 2FA when enforced by administrator + /api/v1/auth/2fa/verify/: + post: + operationId: v1_auth_2fa_verify_create + description: Completes the second step of login by verifying the 6-digit OTP + code (from Email or Authenticator App) or an 8-character Backup Recovery Code + against the ephemeral `temp_token` stored in Redis. + summary: Verify 2FA login challenge and issue JWT tokens + tags: + - Authentication & Profile + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TwoFactorVerifyRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TwoFactorVerifyRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/TwoFactorVerifyRequest' + required: true + security: + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/RegisterResponse' + description: '' + '400': + description: Invalid or expired 2FA code / challenge + '429': + description: Maximum verification attempts exceeded + /api/v1/auth/documents/: + get: + operationId: v1_auth_documents_list + description: Retrieves a list of verification documents uploaded by the current + user or managed by admin. + summary: List user verification documents + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + tags: + - Authentication & Profile + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedVerificationDocumentList' + description: '' + post: + operationId: v1_auth_documents_create + description: Uploads an identity card, passport, institutional license, or recommendation + letter for account verification. + summary: Upload verification document + tags: + - Authentication & Profile + requestBody: + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/VerificationDocumentUploadRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/VerificationDocumentUploadRequest' + application/json: + schema: + $ref: '#/components/schemas/VerificationDocumentUploadRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/VerificationDocument' + description: '' + '400': + description: Invalid document upload data + /api/v1/auth/me/: + get: + operationId: v1_auth_me_retrieve + description: Returns detailed profile data, role hierarchy, and verification + status for authenticated user. + summary: Get current user details and permissions + tags: + - Authentication & Profile + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/UserMe' + description: '' + patch: + operationId: v1_auth_me_partial_update + description: Partially updates user profile attributes (name, phone, bio, languages, + skills, country, city, avatar). + summary: Update current user details + tags: + - Authentication & Profile + requestBody: + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedUserMeUpdateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedUserMeUpdateRequest' + application/json: + schema: + $ref: '#/components/schemas/PatchedUserMeUpdateRequest' + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/UserMe' + description: '' + /api/v1/auth/recover-password/: + post: + operationId: v1_auth_recover_password_create + description: Generates and dispatches a password recovery token to the specified + user email. + summary: Request password recovery token + tags: + - Authentication & Profile + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PasswordRecoverRequestRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PasswordRecoverRequestRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PasswordRecoverRequestRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + description: Reset instructions sent if email exists + /api/v1/auth/register/: + post: + operationId: v1_auth_register_create + description: Creates a new account and immediately issues JWT access and refresh + tokens. + summary: Register new user or representative + tags: + - Authentication & Profile + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RegisterRequestRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/RegisterRequestRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/RegisterRequestRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/RegisterResponse' + description: '' + '400': + description: Validation error + /api/v1/auth/reset-password/: + post: + operationId: v1_auth_reset_password_create + description: Resets the account password given a valid verification token. + summary: Reset user password using token + tags: + - Authentication & Profile + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PasswordResetRequestRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PasswordResetRequestRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PasswordResetRequestRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + description: Password reset successfully + '400': + description: Invalid or expired token + /api/v1/auth/token/: + post: + operationId: v1_auth_token_create + description: 'Authenticates user with email/username and password. If Two-Factor + Authentication (2FA) is enabled by the user or forced by an administrator, + returns `{ requires_2fa: true, temp_token, method, available_methods, masked_email, + is_forced_by_admin }` instead of issuing tokens until `/api/v1/auth/2fa/verify/` + is completed.' + summary: Obtain JWT token pair or 2FA challenge + tags: + - Authentication & Profile + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CustomTokenObtainPairRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/CustomTokenObtainPairRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/CustomTokenObtainPairRequest' + required: true + security: + - jwtAuth: [] + responses: + '200': + description: No response body + /api/v1/auth/token/refresh/: + post: + operationId: v1_auth_token_refresh_create + description: |- + Takes a refresh type JSON web token and returns an access type JSON web + token if the refresh token is valid. + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TokenRefreshRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TokenRefreshRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/TokenRefreshRequest' + required: true + security: + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TokenRefresh' + description: '' + /api/v1/auth/token/verify/: + post: + operationId: v1_auth_token_verify_create + description: |- + Takes a token and indicates if it is valid. This view provides no + information about a token's fitness for a particular use. + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TokenVerifyRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TokenVerifyRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/TokenVerifyRequest' + required: true + security: + - jwtAuth: [] + responses: + '200': + description: No response body + /api/v1/chat/centrifugo-token/: + get: + operationId: v1_chat_centrifugo_token_retrieve + description: Generates an HMAC-SHA256 connection JWT for client WebSocket authentication + with Centrifugo v5. + summary: Obtain Centrifugo real-time WebSocket connection token + tags: + - Real-Time Messaging & Chat + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CentrifugoTokenResponse' + description: '' + /api/v1/chat/rooms/: + get: + operationId: v1_chat_rooms_list + description: Returns all active direct and group conversations for the authenticated + user with unread counts. + summary: List active conversations & direct chats + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + tags: + - Real-Time Messaging & Chat + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedChatRoomListList' + description: '' + post: + operationId: v1_chat_rooms_create + description: Creates a new direct chat or multi-institution group room. Reuses + existing direct chat if already active. + summary: Start conversation with user or institution + tags: + - Real-Time Messaging & Chat + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ChatRoomCreateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ChatRoomCreateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/ChatRoomCreateRequest' + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/ChatRoomDetail' + description: '' + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/ChatRoomDetail' + description: '' + '400': + description: Validation error + /api/v1/chat/rooms/{id}/: + get: + operationId: v1_chat_rooms_retrieve + description: Retrieves metadata, participants, and channel name for a conversation. + summary: Get chat room details & metadata + parameters: + - in: path + name: id + schema: + type: integer + required: true + tags: + - Real-Time Messaging & Chat + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/ChatRoomDetail' + description: '' + '403': + description: Not a participant in this conversation + '404': + description: Room not found + /api/v1/chat/rooms/{id}/messages/: + get: + operationId: v1_chat_rooms_messages_list + description: Returns paginated message history for a conversation and marks + messages as read. + summary: Get message history for chat room + parameters: + - in: path + name: id + schema: + type: integer + required: true + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + tags: + - Real-Time Messaging & Chat + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedChatMessageList' + description: '' + post: + operationId: v1_chat_rooms_messages_create + description: Persists message to database and broadcasts real-time WebSocket + event via Centrifugo. + summary: Send message in conversation + parameters: + - in: path + name: id + schema: + type: integer + required: true + tags: + - Real-Time Messaging & Chat + requestBody: + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/ChatMessageCreateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ChatMessageCreateRequest' + application/json: + schema: + $ref: '#/components/schemas/ChatMessageCreateRequest' + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/ChatMessage' + description: '' + '400': + description: Validation error + '403': + description: Permission denied + /api/v1/chat/rooms/{id}/read/: + post: + operationId: v1_chat_rooms_read_create + description: Updates read receipts for all messages in the room and emits read + event. + summary: Mark all conversation messages as read + parameters: + - in: path + name: id + schema: + type: integer + required: true + tags: + - Real-Time Messaging & Chat + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + description: Messages marked as read + /api/v1/cms/categories/: + get: + operationId: v1_cms_categories_list + description: Returns content categories with optional language filtering. + summary: List post categories + parameters: + - in: query + name: language + schema: + type: string + description: Filter categories by language code (fa, ar, en, ur, fr) + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + tags: + - Content Management System (CMS) + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedPostCategoryList' + description: '' + /api/v1/cms/media/: + get: + operationId: v1_cms_media_list + description: Retrieves uploaded photos, videos, and documents. + summary: List media library assets + parameters: + - in: query + name: media_type + schema: + type: string + description: Filter by media type (image, video, audio, document) + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + tags: + - Content Management System (CMS) + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedMediaAssetList' + description: '' + post: + operationId: v1_cms_media_create + description: Uploads a new photo, document, or audio file to the media library. + summary: Upload media asset + tags: + - Content Management System (CMS) + requestBody: + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/MediaAssetRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/MediaAssetRequest' + application/json: + schema: + $ref: '#/components/schemas/MediaAssetRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/MediaAsset' + description: '' + /api/v1/cms/posts/: + get: + operationId: v1_cms_posts_list + description: Returns a paginated list of published news, articles, and interviews + with multilingual filtering. + summary: List multilingual posts & articles + parameters: + - in: query + name: author_id + schema: + type: integer + description: Filter by author user ID + - in: query + name: author_type + schema: + type: string + description: Filter by author type (user, institution) + - in: query + name: category + schema: + type: string + description: Filter by category ID or slug + - in: query + name: institution_id + schema: + type: integer + description: Filter by associated institution ID + - in: query + name: is_featured + schema: + type: boolean + description: Filter by featured status + - in: query + name: language + schema: + type: string + description: Filter by language code (fa, ar, en, ur, fr) + - in: query + name: my_posts + schema: + type: boolean + description: Filter by current authenticated user posts + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - in: query + name: post_type + schema: + type: string + description: Filter by type (news, article, report, interview) + - in: query + name: search + schema: + type: string + description: Search text in title, excerpt, and content + - in: query + name: status + schema: + type: string + description: Filter by status (staff/admin only) + - in: query + name: tag + schema: + type: string + description: Filter by tag string + tags: + - Content Management System (CMS) + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedPostListList' + description: '' + post: + operationId: v1_cms_posts_create + description: Creates a new article, news announcement, or report. + summary: Publish or draft new post + tags: + - Content Management System (CMS) + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PostCreateUpdateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PostCreateUpdateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PostCreateUpdateRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/PostDetail' + description: '' + '400': + description: Validation error + '401': + description: Authentication required + /api/v1/cms/posts/{pk_or_slug}/: + get: + operationId: v1_cms_posts_retrieve + description: Retrieves full article content and increments the view counter. + summary: Get full post details + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Content Management System (CMS) + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PostDetail' + description: '' + '404': + description: Post not found + put: + operationId: v1_cms_posts_update + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PostDetailRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PostDetailRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PostDetailRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PostDetail' + description: '' + patch: + operationId: v1_cms_posts_partial_update + description: Updates an existing post. If post is published and editor rights + are lacking, submits a zero-downtime staged revision. + summary: Update post + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Content Management System (CMS) + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedPostCreateUpdateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedPostCreateUpdateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedPostCreateUpdateRequest' + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PostDetail' + description: '' + '202': + description: Revision staged for editorial review + '403': + description: Permission denied + delete: + operationId: v1_cms_posts_destroy + description: Removes a post from the platform. + summary: Delete post + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Content Management System (CMS) + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '204': + description: Post deleted successfully + '403': + description: Permission denied + /api/v1/cms/posts/{pk_or_slug}/approve/: + post: + operationId: v1_cms_posts_approve_create + description: Approves a pending post and immediately publishes it. Requires + direct publishing capability for the associated institution. + summary: Approve and publish post + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Content Management System (CMS) + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PostDetailRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PostDetailRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PostDetailRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PostDetail' + description: '' + '400': + description: Post cannot be approved + '403': + description: Permission denied + '404': + description: Post not found + /api/v1/cms/posts/{pk_or_slug}/comments/: + get: + operationId: v1_cms_posts_comments_list + description: Retrieves approved reader comments for a post. + summary: List post comments + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Content Management System (CMS) + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedPostCommentList' + description: '' + post: + operationId: v1_cms_posts_comments_create + description: Submits a comment on an article. + summary: Submit post comment + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Content Management System (CMS) + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PostCommentCreateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PostCommentCreateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PostCommentCreateRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/PostComment' + description: '' + '400': + description: Validation error + /api/v1/cms/posts/{pk_or_slug}/like/: + post: + operationId: v1_cms_posts_like_create + description: Likes or unlikes an article for the authenticated user and updates + the like counter. + summary: Toggle like on post + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Content Management System (CMS) + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PostLikeResponseRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PostLikeResponseRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PostLikeResponseRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PostLikeResponse' + description: '' + /api/v1/cms/posts/{pk_or_slug}/reject/: + post: + operationId: v1_cms_posts_reject_create + description: Rejects a pending post. Requires direct publishing capability for + the associated institution and a mandatory explanation reason. + summary: Reject post with reason + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Content Management System (CMS) + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PostRejectRequestRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PostRejectRequestRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PostRejectRequestRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PostDetail' + description: '' + '400': + description: Rejection reason is missing + '403': + description: Permission denied + '404': + description: Post not found + /api/v1/cms/posts/{pk_or_slug}/revisions/: + get: + operationId: v1_cms_posts_revisions_list + description: Lists all historical and pending revisions for this post. Permitted + for the author or institution editors. + summary: List revisions for a post + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Content Management System (CMS) + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedPostRevisionList' + description: '' + '403': + description: Permission denied + '404': + description: Post not found + delete: + operationId: v1_cms_posts_revisions_destroy + description: Allows the post author to withdraw or delete their pending revision + before editorial review. + summary: Discard pending revision + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Content Management System (CMS) + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '204': + description: Pending revision discarded + '400': + description: No pending revision to discard + '403': + description: Permission denied + /api/v1/cms/posts/{pk_or_slug}/revisions/approve/: + post: + operationId: v1_cms_posts_revisions_approve_create + description: Approves a pending post revision and merges it live into the published + post with zero downtime. Requires direct publishing capability for the institution. + summary: Approve and merge staged post revision + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Content Management System (CMS) + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PostDetailRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PostDetailRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PostDetailRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PostRevisionApproveResponse' + description: '' + '400': + description: No pending revision found + '403': + description: Permission denied + '404': + description: Post not found + /api/v1/cms/posts/{pk_or_slug}/revisions/reject/: + post: + operationId: v1_cms_posts_revisions_reject_create + description: Rejects a pending post revision without affecting the live published + article. Requires direct publishing capability and an explanation reason. + summary: Reject staged post revision with explanation + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Content Management System (CMS) + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PostRevisionRejectRequestRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PostRevisionRejectRequestRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PostRevisionRejectRequestRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PostRevisionRejectResponse' + description: '' + '400': + description: Rejection reason missing or no pending revision + '403': + description: Permission denied + '404': + description: Post not found + /api/v1/cms/tags/: + get: + operationId: v1_cms_tags_list + description: Returns distinct content tags used across posts. + summary: List CMS tags + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + tags: + - Content Management System (CMS) + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedPostTagList' + description: '' + /api/v1/contact-us/: + post: + operationId: v1_contact_us_create + description: Submit a user contact or support inquiry + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/SupportMessageCreateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/SupportMessageCreateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/SupportMessageCreateRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/SupportMessageCreate' + description: '' + /api/v1/diplomacy/reports/: + get: + operationId: v1_diplomacy_reports_list + description: Retrieves periodic reports submitted by regional mission representatives + (Module 6.5). + summary: List field representative activity reports + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - in: query + name: region_hub + schema: + type: string + description: Filter by regional hub name + - in: query + name: status + schema: + type: string + description: Filter by status (submitted, under_review, approved, returned) + tags: + - Diplomacy & Field Reports + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedRepresentativeReportListList' + description: '' + post: + operationId: v1_diplomacy_reports_create + description: Submits a field audit, visits summary, and achievements report + for headquarters review. + summary: Submit periodic field activity report + tags: + - Diplomacy & Field Reports + requestBody: + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/RepresentativeReportCreateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/RepresentativeReportCreateRequest' + application/json: + schema: + $ref: '#/components/schemas/RepresentativeReportCreateRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/RepresentativeReportDetail' + description: '' + '400': + description: Validation error + /api/v1/diplomacy/reports/{id}/: + get: + operationId: v1_diplomacy_reports_retrieve + description: Returns full report contents, achievements, challenges, attachment, + and headquarters feedback. + summary: Retrieve representative report details + parameters: + - in: path + name: id + schema: + type: integer + required: true + tags: + - Diplomacy & Field Reports + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/RepresentativeReportDetail' + description: '' + '403': + description: Permission denied + '404': + description: Report not found + /api/v1/diplomacy/reports/{id}/review/: + patch: + operationId: v1_diplomacy_reports_review_partial_update + description: Approves or returns report with headquarters feedback directives + (Staff/Admin only). + summary: Review and give directives on field report + parameters: + - in: path + name: id + schema: + type: integer + required: true + tags: + - Diplomacy & Field Reports + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedRepresentativeReportReviewRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedRepresentativeReportReviewRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedRepresentativeReportReviewRequest' + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/RepresentativeReportDetail' + description: '' + '403': + description: Permission denied + /api/v1/donations/campaigns/: + get: + operationId: v1_donations_campaigns_list + description: Retrieves charitable campaigns with target, raised amounts, progress + percentages, and filters. + summary: List active donation appeals & campaigns + parameters: + - in: query + name: category + schema: + type: string + description: Filter by campaign category + - in: query + name: featured + schema: + type: boolean + description: Filter featured campaigns + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - in: query + name: priority + schema: + type: string + description: Filter by priority (urgent, high, medium) + - in: query + name: search + schema: + type: string + description: Search in campaign title or description + tags: + - Donations & Financial Campaigns + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedDonationCampaignListList' + description: '' + /api/v1/donations/campaigns/{pk_or_slug}/: + get: + operationId: v1_donations_campaigns_retrieve + description: Returns detailed campaign goal, beneficiary center, progress rate, + and recent donors. + summary: Retrieve campaign details & recent contributions + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Donations & Financial Campaigns + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DonationCampaignDetail' + description: '' + '404': + description: Campaign not found + /api/v1/donations/campaigns/{pk_or_slug}/contribute/: + post: + operationId: v1_donations_campaigns_contribute_create + description: Processes a donation contribution, persists transaction, and increments + campaign accumulated funds. + summary: Make a charitable contribution to campaign + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Donations & Financial Campaigns + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DonationContributeRequestRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/DonationContributeRequestRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/DonationContributeRequestRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/DonationTransaction' + description: '' + '400': + description: Validation error + '404': + description: Campaign not found + /api/v1/events/: + get: + operationId: v1_events_list + description: Filter events by category, date range, institution organizer, location + type, and search keywords. + summary: List events and programs + parameters: + - in: query + name: category + schema: + type: string + description: Event category filter + - in: query + name: end_date + schema: + type: string + format: date + description: Filter to date (YYYY-MM-DD) + - in: query + name: is_featured + schema: + type: boolean + description: Filter featured events + - in: query + name: language + schema: + type: string + description: Event language (fa, ar, en, ur, fr) + - in: query + name: location_type + schema: + type: string + description: in_person, online, or hybrid + - in: query + name: organizer + schema: + type: integer + description: Organizer institution ID + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - in: query + name: search + schema: + type: string + description: Search in title, description, or speaker + - in: query + name: start_date + schema: + type: string + format: date + description: Filter from date (YYYY-MM-DD) + tags: + - Events + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEventListList' + description: '' + post: + operationId: v1_events_create + description: Create an event on behalf of an institution where the user is an + admin/editor. + summary: Create a new event + tags: + - Events + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/EventCreateUpdateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/EventCreateUpdateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/EventCreateUpdateRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/EventDetail' + description: '' + /api/v1/events/{id_or_slug}/: + get: + operationId: v1_events_retrieve + description: Get full event details, speaker bio, registration status for current + user, and venue/meeting info. + summary: Retrieve event details + parameters: + - in: path + name: id_or_slug + schema: + type: string + required: true + tags: + - Events + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/EventDetail' + description: '' + put: + operationId: v1_events_update + parameters: + - in: path + name: id_or_slug + schema: + type: string + required: true + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/EventCreateUpdateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/EventCreateUpdateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/EventCreateUpdateRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/EventCreateUpdate' + description: '' + patch: + operationId: v1_events_partial_update + description: Update event metadata (requires organizer admin/editor permission). + summary: Update event + parameters: + - in: path + name: id_or_slug + schema: + type: string + required: true + tags: + - Events + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedEventCreateUpdateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedEventCreateUpdateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedEventCreateUpdateRequest' + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/EventDetail' + description: '' + delete: + operationId: v1_events_destroy + description: Delete or deactivate an event. + summary: Delete event + parameters: + - in: path + name: id_or_slug + schema: + type: string + required: true + tags: + - Events + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '204': + description: No response body + /api/v1/events/{id_or_slug}/cancel-registration/: + post: + operationId: v1_events_cancel_registration_create + description: Cancel registration for the current authenticated user. + summary: Cancel event registration + parameters: + - in: path + name: id_or_slug + schema: + type: string + required: true + tags: + - Events + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/EventDetail' + description: '' + /api/v1/events/{id_or_slug}/register/: + post: + operationId: v1_events_register_create + description: Register the current authenticated user for this event. + summary: Register for an event + parameters: + - in: path + name: id_or_slug + schema: + type: string + required: true + tags: + - Events + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/EventRegisterInputRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/EventRegisterInputRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/EventRegisterInputRequest' + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/EventDetail' + description: '' + /api/v1/events/{id_or_slug}/registrations/: + get: + operationId: v1_events_registrations_list + description: View registered participants for an event (organizer admin/editor + or staff only). + summary: List event attendees + parameters: + - in: path + name: id_or_slug + schema: + type: string + required: true + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + tags: + - Events + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEventRegistrationList' + description: '' + /api/v1/events/calendar/: + get: + operationId: v1_events_calendar_list + description: Get streamlined event entries for calendar components filtered + by month/year. + summary: Calendar view feed + parameters: + - in: query + name: category + schema: + type: string + description: Category filter + - in: query + name: month + schema: + type: integer + description: Calendar month (1-12) + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - in: query + name: year + schema: + type: integer + description: Calendar year (e.g. 2026) + tags: + - Events + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedEventCalendarItemList' + description: '' + /api/v1/forms/: + get: + operationId: v1_forms_list + description: Retrieves available forms. Supports filtering to only user's own + forms via `my_forms=true`. + summary: List dynamic forms + parameters: + - in: query + name: my_forms + schema: + type: boolean + description: If true, returns only forms created by the authenticated user. + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + tags: + - Dynamic Forms & Surveys + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedFormDefinitionListList' + description: '' + post: + operationId: v1_forms_create + description: Defines a new custom form, questions, validation rules, and schema + (Staff or affiliated Institution users with permission). + summary: Create new dynamic form schema + tags: + - Dynamic Forms & Surveys + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/FormDefinitionDetailRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/FormDefinitionDetailRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/FormDefinitionDetailRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/FormDefinitionDetail' + description: '' + '401': + description: Authentication required + '403': + description: Permission denied + /api/v1/forms/{pk_or_slug}/: + get: + operationId: v1_forms_retrieve + description: Retrieves the detailed form definition, instructions, and fields + JSON schema. + summary: Get form schema definition + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Dynamic Forms & Surveys + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/FormDefinitionDetail' + description: '' + '404': + description: Form not found + /api/v1/forms/{pk_or_slug}/responses/: + post: + operationId: v1_forms_responses_create + description: Records user answers to custom dynamic form questions with optional + file attachments. + summary: Submit dynamic form responses + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Dynamic Forms & Surveys + requestBody: + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/FormResponseSubmitRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/FormResponseSubmitRequest' + application/json: + schema: + $ref: '#/components/schemas/FormResponseSubmitRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/FormResponse' + description: '' + '400': + description: Invalid response data + /api/v1/forms/{pk_or_slug}/submissions/: + get: + operationId: v1_forms_submissions_list + description: Retrieves submissions for a form. Admins see all submissions; users + see their own. + summary: List submitted form responses + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Dynamic Forms & Surveys + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedFormResponseList' + description: '' + /api/v1/forms/responses/{response_id}/review/: + patch: + operationId: v1_forms_responses_review_partial_update + description: Updates workflow status (under_review, approved, rejected) and + adds admin notes (Staff only). + summary: Review and update form response workflow state + parameters: + - in: path + name: response_id + schema: + type: integer + required: true + tags: + - Dynamic Forms & Surveys + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedFormResponseReviewRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedFormResponseReviewRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedFormResponseReviewRequest' + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/FormResponse' + description: '' + /api/v1/health/: + get: + operationId: v1_health_retrieve + description: Returns system status, current timestamp, and API version + summary: Check API server health status + tags: + - v1 + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/HealthCheckResponse' + description: '' + /api/v1/lms/certificates/{code}/: + get: + operationId: v1_lms_certificates_retrieve + description: Public endpoint for verifying credentials by certificate code or + UUID. Does not require login. + summary: Verify digital certificate authenticity (Public / QR) + parameters: + - in: path + name: code + schema: + type: string + required: true + tags: + - LMS - Online Academy + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PublicCertificateVerify' + description: '' + '404': + description: Certificate code not found + /api/v1/lms/courses/: + get: + operationId: v1_lms_courses_list + description: Retrieves a list of published courses with category, language, + level, and keyword filtering. + summary: List online academy courses + parameters: + - in: query + name: category + schema: + type: string + description: Filter by course category (e.g. razavi_teachings, cultural_diplomacy) + - in: query + name: featured + schema: + type: boolean + description: Filter only featured courses + - in: query + name: language + schema: + type: string + description: Filter by language code (fa, ar, en, ur, fr) + - in: query + name: level + schema: + type: string + description: Filter by difficulty level (introductory, intermediate, advanced) + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - in: query + name: search + schema: + type: string + description: Keyword search in title, instructor, or description + tags: + - LMS - Online Academy + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedCourseListList' + description: '' + post: + operationId: v1_lms_courses_create + description: Creates a new LMS course. Creator is automatically set as course + author. + summary: Create a new course + tags: + - LMS - Online Academy + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CourseCreateUpdateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/CourseCreateUpdateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/CourseCreateUpdateRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/CourseDetail' + description: '' + '400': + description: Validation error + '403': + description: Permission denied + /api/v1/lms/courses/{pk_or_slug}/: + get: + operationId: v1_lms_courses_retrieve + description: Returns full course information, curriculum modules, lessons list, + instructor details, and authenticated user enrollment status. + summary: Retrieve course syllabus and details + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - LMS - Online Academy + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CourseDetail' + description: '' + '404': + description: Course not found + patch: + operationId: v1_lms_courses_partial_update + description: Partially updates course information. Restricted to instructor + or platform administrator. + summary: Update course metadata + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - LMS - Online Academy + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedCourseCreateUpdateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedCourseCreateUpdateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedCourseCreateUpdateRequest' + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CourseDetail' + description: '' + '403': + description: Permission denied + delete: + operationId: v1_lms_courses_destroy + description: Deletes a course and its modules. Restricted to instructor or platform + administrator. + summary: Delete a course + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - LMS - Online Academy + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '204': + description: Course deleted + /api/v1/lms/courses/{pk_or_slug}/enroll/: + post: + operationId: v1_lms_courses_enroll_create + description: Enrolls the authenticated user into the specified course. Returns + enrollment status. + summary: Enroll current user in course + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - LMS - Online Academy + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CourseDetailRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/CourseDetailRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/CourseDetailRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + description: Already enrolled + '201': + description: Successfully enrolled + '404': + description: Course not found + /api/v1/lms/courses/{pk_or_slug}/progress/: + get: + operationId: v1_lms_courses_progress_retrieve + description: Returns completion percentage, completed lessons tally, and certificate + code if finished. + summary: Get user progress in course + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - LMS - Online Academy + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CourseProgress' + description: '' + '404': + description: Course or enrollment not found + /api/v1/lms/lessons/{id}/: + get: + operationId: v1_lms_lessons_retrieve + description: Retrieves lesson video link, markdown content, slides attachment, + and quiz questions. + summary: Get lesson details and content + parameters: + - in: path + name: id + schema: + type: integer + required: true + tags: + - LMS - Online Academy + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/LessonDetail' + description: '' + '404': + description: Lesson not found + /api/v1/lms/lessons/{id}/complete/: + post: + operationId: v1_lms_lessons_complete_create + description: Marks a reading or video lesson as complete. Automatically recalculates + course progress and issues certificate if 100%. + summary: Mark lesson as complete + parameters: + - in: path + name: id + schema: + type: integer + required: true + tags: + - LMS - Online Academy + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/LessonCompleteResponseRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/LessonCompleteResponseRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/LessonCompleteResponseRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/LessonCompleteResponse' + description: '' + '404': + description: Lesson not found + /api/v1/lms/lessons/{id}/submit-quiz/: + post: + operationId: v1_lms_lessons_submit_quiz_create + description: Evaluates user quiz responses. If score >= 70%, marks lesson complete, + recalculates progress, and issues certificate if 100%. + summary: Submit quiz answers and evaluate score + parameters: + - in: path + name: id + schema: + type: integer + required: true + tags: + - LMS - Online Academy + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/QuizSubmitRequestRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/QuizSubmitRequestRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/QuizSubmitRequestRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/QuizResult' + description: '' + '400': + description: Invalid submission + '404': + description: Lesson not found + /api/v1/lms/my-certificates/: + get: + operationId: v1_lms_my_certificates_list + description: Retrieves a list of all official digital certificates earned by + the authenticated user across completed academy courses. + summary: List user earned digital certificates + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + tags: + - LMS - Online Academy + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedDigitalCertificateList' + description: '' + /api/v1/map/clusters/: + get: + operationId: v1_map_clusters_list + description: Returns aggregated institution counts and density breakdown per + country and region. + summary: Regional density & geographic cluster overview + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + tags: + - Smart Geo-Atlas + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedRegionalDensityList' + description: '' + /api/v1/map/institutions/: + get: + operationId: v1_map_institutions_list + description: Returns geographic markers or aggregated clusters for institutions + within the current map viewport. + summary: Query map markers with bounding box & clustering + parameters: + - in: query + name: city + schema: + type: string + description: Filter by city + - in: query + name: cluster + schema: + type: boolean + description: Whether to cluster nearby pins (default true) + - in: query + name: country + schema: + type: string + description: Filter by country + - in: query + name: east + schema: + type: number + format: double + description: Easternmost longitude of viewport + - in: query + name: is_featured + schema: + type: boolean + description: Filter by featured status + - in: query + name: north + schema: + type: number + format: double + description: Northernmost latitude of viewport + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - in: query + name: search + schema: + type: string + description: Search text query + - in: query + name: south + schema: + type: number + format: double + description: Southernmost latitude of viewport + - in: query + name: tag + schema: + type: string + description: Filter by tag string + - in: query + name: type + schema: + type: string + description: Filter by institution type + - in: query + name: west + schema: + type: number + format: double + description: Westernmost longitude of viewport + - in: query + name: zoom + schema: + type: integer + description: Current map zoom level (1 to 20, default 10) + tags: + - Smart Geo-Atlas + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedMapClusterItemList' + description: '' + /api/v1/map/stats/: + get: + operationId: v1_map_stats_retrieve + description: Returns total institutions, countries covered, cities count, and + classification distributions. + summary: Get global atlas statistics + tags: + - Smart Geo-Atlas + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/MapStats' + description: '' + /api/v1/meetings/: + get: + operationId: v1_meetings_list + description: List bilateral video meetings involving the authenticated user's + affiliated institutions. + summary: List scheduled & pending meetings + parameters: + - in: query + name: end_date + schema: + type: string + format: date + description: Filter meetings to date (YYYY-MM-DD) + - in: query + name: institution_id + schema: + type: integer + description: Filter meetings by participating institution ID + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - in: query + name: search + schema: + type: string + description: Search in title or agenda + - in: query + name: start_date + schema: + type: string + format: date + description: Filter meetings from date (YYYY-MM-DD) + - in: query + name: status + schema: + type: string + description: Meeting status (pending, scheduled, completed, cancelled, declined) + tags: + - Meetings + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedMeetingListList' + description: '' + post: + operationId: v1_meetings_create + description: Schedule/propose a bilateral meeting between institutions (requires + institution admin/editor role). + summary: Propose a new video meeting + tags: + - Meetings + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/MeetingCreateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/MeetingCreateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/MeetingCreateRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/MeetingDetail' + description: '' + /api/v1/meetings/{id}/: + get: + operationId: v1_meetings_retrieve + description: Get full meeting details including video link, agenda, and participants. + summary: Retrieve meeting details + parameters: + - in: path + name: id + schema: + type: integer + required: true + tags: + - Meetings + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/MeetingDetail' + description: '' + put: + operationId: v1_meetings_update + parameters: + - in: path + name: id + schema: + type: integer + required: true + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/MeetingUpdateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/MeetingUpdateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/MeetingUpdateRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/MeetingUpdate' + description: '' + patch: + operationId: v1_meetings_partial_update + description: Accept, reschedule, decline (requires rejection_reason or admin_notes + explanation), complete, or update meeting room URL and agenda notes. + summary: Update or respond to meeting request + parameters: + - in: path + name: id + schema: + type: integer + required: true + tags: + - Meetings + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedMeetingUpdateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedMeetingUpdateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedMeetingUpdateRequest' + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/MeetingDetail' + description: '' + delete: + operationId: v1_meetings_destroy + description: Cancel or delete a meeting request. + summary: Cancel / delete meeting + parameters: + - in: path + name: id + schema: + type: integer + required: true + tags: + - Meetings + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '204': + description: No response body + /api/v1/profiles/: + get: + operationId: v1_profiles_list + description: Returns a paginated list of mosques, cultural centers, and institutes + with filtering. + summary: List active institutions + parameters: + - in: query + name: city + schema: + type: string + description: Filter by city name + - in: query + name: country + schema: + type: string + description: Filter by country name + - in: query + name: is_featured + schema: + type: boolean + description: Filter by featured status + - in: query + name: managed_only + schema: + type: boolean + description: Filter to return only institutions within caller's managed regions + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - in: query + name: region + schema: + type: string + description: Filter by region ID, slug, or name + - in: query + name: search + schema: + type: string + description: Search query across name, city, country, region, description + - in: query + name: tag + schema: + type: string + description: Filter by tag string + - in: query + name: type + schema: + type: string + description: Filter by institution type (mosque, cultural_center, etc.) + - in: query + name: verification_status + schema: + type: string + description: Filter by verification status (pending, approved, rejected) + tags: + - Institutions & Profiles + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedInstitutionListList' + description: '' + post: + operationId: v1_profiles_create + description: Registers a new institution profile and designates the creator + as primary Administrator. + summary: Register a new institution + tags: + - Institutions & Profiles + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/InstitutionCreateUpdateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/InstitutionCreateUpdateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/InstitutionCreateUpdateRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/InstitutionDetail' + description: '' + '400': + description: Validation error + '401': + description: Authentication required + /api/v1/profiles/{pk_or_slug}/: + get: + operationId: v1_profiles_retrieve + description: Retrieves complete institution information, historical timeline + milestones, and media gallery. + summary: Get full institution profile + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Institutions & Profiles + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/InstitutionDetail' + description: '' + '404': + description: Institution not found + patch: + operationId: v1_profiles_partial_update + description: Updates details of an institution. Requires Admin or Editor role + on the institution. + summary: Update institution profile + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Institutions & Profiles + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedInstitutionCreateUpdateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedInstitutionCreateUpdateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedInstitutionCreateUpdateRequest' + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/InstitutionDetail' + description: '' + '403': + description: Permission denied + delete: + operationId: v1_profiles_destroy + description: Deactivates an institution. Permitted for institution administrator + or platform administrator. + summary: Deactivate / Delete institution + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Institutions & Profiles + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '204': + description: Institution successfully deactivated + '403': + description: Permission denied + /api/v1/profiles/{pk_or_slug}/follow/: + post: + operationId: v1_profiles_follow_create + description: Follows the specified institution to receive updates and news. + summary: Follow institution + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Institutions & Profiles + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/FollowResponseRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/FollowResponseRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/FollowResponseRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/FollowResponse' + description: '' + delete: + operationId: v1_profiles_follow_destroy + description: Unfollows the specified institution. + summary: Unfollow institution + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Institutions & Profiles + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/FollowResponse' + description: '' + /api/v1/profiles/{pk_or_slug}/media/: + get: + operationId: v1_profiles_media_list + description: Returns uploaded photos, videos, and documents for an institution. + summary: List media gallery assets + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Institutions & Profiles + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedInstitutionMediaList' + description: '' + post: + operationId: v1_profiles_media_create + description: Uploads a photo, video, or publication to the institution media + gallery. + summary: Upload media to gallery + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Institutions & Profiles + requestBody: + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/InstitutionMediaRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/InstitutionMediaRequest' + application/json: + schema: + $ref: '#/components/schemas/InstitutionMediaRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/InstitutionMedia' + description: '' + /api/v1/profiles/{pk_or_slug}/media/{media_id}/: + delete: + operationId: v1_profiles_media_destroy + description: Removes an asset from the institution media gallery. + summary: Delete media item + parameters: + - in: path + name: media_id + schema: + type: integer + required: true + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Institutions & Profiles + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '204': + description: Media deleted + /api/v1/profiles/{pk_or_slug}/members/: + get: + operationId: v1_profiles_members_list + description: Retrieves administrative and editorial team members of the institution. + summary: List institution team members + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Institutions & Profiles + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedInstitutionMemberList' + description: '' + post: + operationId: v1_profiles_members_create + description: Adds or updates a member role (admin, editor, viewer) for an existing + registered user by email. + summary: Delegate role / Add team member + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Institutions & Profiles + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/InstitutionMemberAddRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/InstitutionMemberAddRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/InstitutionMemberAddRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/InstitutionMember' + description: '' + '400': + description: User not found or invalid payload + '403': + description: Only institution administrators can delegate roles + /api/v1/profiles/{pk_or_slug}/members/{member_id}/: + delete: + operationId: v1_profiles_members_destroy + description: Revokes institution membership and delegated role for a user. + summary: Remove team member + parameters: + - in: path + name: member_id + schema: + type: integer + required: true + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Institutions & Profiles + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '204': + description: Member removed + /api/v1/profiles/{pk_or_slug}/quick-links/: + get: + operationId: v1_profiles_quick_links_list + description: Returns administrative quick access service links (virtual tour, + live broadcast, digital library, etc.) for an institution. + summary: List quick access links + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Institutions & Profiles + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedQuickAccessLinkList' + description: '' + post: + operationId: v1_profiles_quick_links_create + description: Creates a new quick access service link for an institution. + summary: Create quick access link + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Institutions & Profiles + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/QuickAccessLinkRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/QuickAccessLinkRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/QuickAccessLinkRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/QuickAccessLink' + description: '' + /api/v1/profiles/{pk_or_slug}/quick-links/{link_id}/: + get: + operationId: v1_profiles_quick_links_retrieve + description: Gets details of a specific quick access service link. + summary: Retrieve quick access link + parameters: + - in: path + name: link_id + schema: + type: integer + required: true + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Institutions & Profiles + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/QuickAccessLink' + description: '' + put: + operationId: v1_profiles_quick_links_update + description: Fully updates an existing quick access service link. + summary: Update quick access link + parameters: + - in: path + name: link_id + schema: + type: integer + required: true + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Institutions & Profiles + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/QuickAccessLinkRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/QuickAccessLinkRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/QuickAccessLinkRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/QuickAccessLink' + description: '' + patch: + operationId: v1_profiles_quick_links_partial_update + description: Partially updates an existing quick access service link. + summary: Partial update quick access link + parameters: + - in: path + name: link_id + schema: + type: integer + required: true + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Institutions & Profiles + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedQuickAccessLinkRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedQuickAccessLinkRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedQuickAccessLinkRequest' + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/QuickAccessLink' + description: '' + delete: + operationId: v1_profiles_quick_links_destroy + description: Removes a quick access service link from an institution. + summary: Delete quick access link + parameters: + - in: path + name: link_id + schema: + type: integer + required: true + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Institutions & Profiles + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '204': + description: Link deleted + /api/v1/profiles/{pk_or_slug}/quick-links/reorder/: + post: + operationId: v1_profiles_quick_links_reorder_create + description: 'Reorders the display sequence of quick access links. Accepts either + a list of IDs in order `{"order": [1, 2, 3]}` or an array of objects `[{"id": + 1, "order": 0}, ...]`.' + summary: Reorder quick access links + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Institutions & Profiles + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/QuickAccessLinkReorderRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/QuickAccessLinkReorderRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/QuickAccessLinkReorderRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedQuickAccessLinkList' + description: '' + /api/v1/profiles/{pk_or_slug}/timeline/: + get: + operationId: v1_profiles_timeline_list + description: Returns historical milestone entries for an institution. + summary: List timeline milestones + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Institutions & Profiles + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedInstitutionTimelineList' + description: '' + post: + operationId: v1_profiles_timeline_create + description: Creates a new milestone in the institution historical timeline. + summary: Add timeline milestone + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Institutions & Profiles + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/InstitutionTimelineRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/InstitutionTimelineRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/InstitutionTimelineRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/InstitutionTimeline' + description: '' + /api/v1/profiles/{pk_or_slug}/timeline/{entry_id}/: + delete: + operationId: v1_profiles_timeline_destroy + description: Removes a timeline entry from the institution profile. + summary: Delete timeline milestone + parameters: + - in: path + name: entry_id + schema: + type: integer + required: true + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Institutions & Profiles + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '204': + description: Entry deleted + /api/v1/profiles/regions/: + get: + operationId: v1_profiles_regions_list + description: Returns list of available regions for dropdowns and filtering. + summary: List all regions + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - in: query + name: search + schema: + type: string + description: Search query across region name, code, description + tags: + - Institutions & Profiles + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedRegionList' + description: '' + post: + operationId: v1_profiles_regions_create + description: Creates a new region (Admin or Staff required). + summary: Create a new region + tags: + - Institutions & Profiles + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RegionRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/RegionRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/RegionRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Region' + description: '' + /api/v1/profiles/regions/{pk_or_slug}/: + get: + operationId: v1_profiles_regions_retrieve + description: Retrieves a region by ID or slug. + summary: Get region details + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Institutions & Profiles + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Region' + description: '' + patch: + operationId: v1_profiles_regions_partial_update + description: Updates region details (Admin/Staff only). + summary: Update region + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Institutions & Profiles + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedRegionRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedRegionRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedRegionRequest' + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Region' + description: '' + delete: + operationId: v1_profiles_regions_destroy + description: Deletes a region (Admin/Staff only). Cannot delete if institutions + are linked. + summary: Delete region + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Institutions & Profiles + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '204': + description: Region deleted + /api/v1/projects/: + get: + operationId: v1_projects_list + description: Returns a paginated list of collaborative projects with filtering + by institution, category, and status. + summary: List collaborative projects + parameters: + - in: query + name: category + schema: + type: string + description: Filter by category name + - in: query + name: institution_id + schema: + type: integer + description: Filter by owner or collaborating institution ID + - in: query + name: owner_institution_id + schema: + type: integer + description: Filter specifically by owner institution ID + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - in: query + name: search + schema: + type: string + description: Search query across title and description + - in: query + name: status + schema: + type: string + description: Filter by project status (active, completed, on_hold, planning) + tags: + - Collaborative Projects & Kanban + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedProjectListList' + description: '' + post: + operationId: v1_projects_create + description: Creates a new multi-institution collaborative project with default + Kanban columns. + summary: Create collaborative project + tags: + - Collaborative Projects & Kanban + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectCreateUpdateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ProjectCreateUpdateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/ProjectCreateUpdateRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectDetail' + description: '' + '400': + description: Validation error + '401': + description: Authentication required + /api/v1/projects/{pk_or_slug}/: + get: + operationId: v1_projects_retrieve + description: Retrieves full project details, collaborating institutions, and + Kanban boards. + summary: Get project details + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Collaborative Projects & Kanban + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectDetail' + description: '' + '404': + description: Project not found + patch: + operationId: v1_projects_partial_update + description: Updates project status, progress percentage, or details. Permitted + for owner and collaborating editors. + summary: Update project + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Collaborative Projects & Kanban + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedProjectCreateUpdateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedProjectCreateUpdateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedProjectCreateUpdateRequest' + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectDetail' + description: '' + '403': + description: Permission denied + delete: + operationId: v1_projects_destroy + description: Removes a collaborative project and associated Kanban boards. + summary: Delete project + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Collaborative Projects & Kanban + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '204': + description: Project deleted successfully + '403': + description: Permission denied + /api/v1/projects/{pk_or_slug}/documents/: + get: + operationId: v1_projects_documents_list + description: Returns uploaded agreements, contracts, and progress reports for + a project. + summary: List project documents & MOUs + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Collaborative Projects & Kanban + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedProjectDocumentList' + description: '' + post: + operationId: v1_projects_documents_create + description: Uploads an agreement, contract, or milestone report. + summary: Upload project document + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Collaborative Projects & Kanban + requestBody: + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/ProjectDocumentRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ProjectDocumentRequest' + application/json: + schema: + $ref: '#/components/schemas/ProjectDocumentRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectDocument' + description: '' + '400': + description: Validation error + '403': + description: Permission denied + /api/v1/projects/{pk_or_slug}/documents/{doc_id}/: + delete: + operationId: v1_projects_documents_destroy + description: Removes a document attachment from the project. + summary: Delete project document + parameters: + - in: path + name: doc_id + schema: + type: integer + required: true + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Collaborative Projects & Kanban + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '204': + description: Document removed successfully + '403': + description: Permission denied + /api/v1/projects/{pk_or_slug}/kanban/: + get: + operationId: v1_projects_kanban_list + description: Returns hierarchical Kanban columns with nested task lists for + drag-and-drop board interfaces. + summary: Get project Kanban board hierarchy + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Collaborative Projects & Kanban + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedKanbanColumnList' + description: '' + /api/v1/projects/{pk_or_slug}/tasks/: + post: + operationId: v1_projects_tasks_create + description: Adds a new task under a specific column of the project. + summary: Create Kanban task + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Collaborative Projects & Kanban + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/KanbanTaskCreateUpdateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/KanbanTaskCreateUpdateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/KanbanTaskCreateUpdateRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/KanbanTask' + description: '' + '400': + description: Validation error + '403': + description: Permission denied + /api/v1/projects/{pk_or_slug}/tasks/{task_id}/: + patch: + operationId: v1_projects_tasks_partial_update + description: Updates task attributes, priority, assignee, or moves task to another + column / reorders within column. + summary: Update or move Kanban task + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + - in: path + name: task_id + schema: + type: integer + required: true + tags: + - Collaborative Projects & Kanban + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedKanbanTaskCreateUpdateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedKanbanTaskCreateUpdateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedKanbanTaskCreateUpdateRequest' + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/KanbanTask' + description: '' + '400': + description: Validation error + '403': + description: Permission denied + delete: + operationId: v1_projects_tasks_destroy + description: Removes a task from the project Kanban board. + summary: Delete Kanban task + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + - in: path + name: task_id + schema: + type: integer + required: true + tags: + - Collaborative Projects & Kanban + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '204': + description: Task deleted successfully + '403': + description: Permission denied + /api/v1/search/: + get: + operationId: v1_search_retrieve + description: Search simultaneously across institutions, collaborative projects, + LMS academy courses, CMS articles/news, and international events with relevance + ranking and facet filtering. + summary: Unified Multi-Entity Search + parameters: + - in: query + name: limit + schema: + type: integer + description: 'Maximum number of search results to return (default: 20, max: + 100)' + - in: query + name: offset + schema: + type: integer + description: 'Pagination offset (default: 0)' + - in: query + name: q + schema: + type: string + description: Search query string / keywords (Persian, Arabic, English, Urdu, + etc.) + - in: query + name: type + schema: + type: string + description: 'Comma-separated entity types to include: institution, project, + course, post, event' + tags: + - Unified Search + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/UnifiedSearchResponse' + description: '' + /api/v1/settings/about-us/: + get: + operationId: v1_settings_about_us_retrieve + tags: + - v1 + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/AboutUs' + description: '' + put: + operationId: v1_settings_about_us_update + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AboutUsRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/AboutUsRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/AboutUsRequest' + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/AboutUs' + description: '' + /api/v1/settings/about-us-dobodi/: + get: + operationId: v1_settings_about_us_dobodi_retrieve + tags: + - v1 + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/AboutUsDobodi' + description: '' + put: + operationId: v1_settings_about_us_dobodi_update + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AboutUsDobodiRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/AboutUsDobodiRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/AboutUsDobodiRequest' + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/AboutUsDobodi' + description: '' + /api/v1/settings/card/: + get: + operationId: v1_settings_card_retrieve + tags: + - v1 + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Card' + description: '' + put: + operationId: v1_settings_card_update + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CardRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/CardRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/CardRequest' + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Card' + description: '' + /api/v1/settings/faq-course/: + get: + operationId: v1_settings_faq_course_retrieve + tags: + - v1 + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/FAQItem' + description: '' + /api/v1/settings/faq-general/: + get: + operationId: v1_settings_faq_general_retrieve + tags: + - v1 + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/FAQItem' + description: '' + put: + operationId: v1_settings_faq_general_update + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/FAQItemRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/FAQItemRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/FAQItemRequest' + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/FAQItem' + description: '' + /api/v1/settings/support/: + get: + operationId: v1_settings_support_retrieve + tags: + - v1 + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Support' + description: '' + put: + operationId: v1_settings_support_update + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/SupportRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/SupportRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/SupportRequest' + security: + - tokenAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Support' + description: '' + /api/v1/tickets/: + get: + operationId: v1_tickets_list + description: Retrieves a list of official inquiry tickets submitted by the user + or their institution. + summary: List user and institution tickets + parameters: + - in: query + name: category + schema: + type: string + description: Filter by category (cultural_affairs, religious_inquiry, etc.) + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - in: query + name: priority + schema: + type: string + description: Filter by priority (urgent, high, medium, low) + - in: query + name: search + schema: + type: string + description: Search in subject or tracking code + - in: query + name: status + schema: + type: string + description: Filter by status (open, in_progress, answered, closed) + tags: + - Ticketing & Official Inquiries + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedTicketListList' + description: '' + post: + operationId: v1_tickets_create + description: Creates a new official inquiry ticket with initial message content + and optional file attachment. + summary: Submit an official ticket inquiry + tags: + - Ticketing & Official Inquiries + requestBody: + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/TicketCreateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TicketCreateRequest' + application/json: + schema: + $ref: '#/components/schemas/TicketCreateRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/TicketDetail' + description: '' + '400': + description: Validation error + /api/v1/tickets/{pk_or_code}/: + get: + operationId: v1_tickets_retrieve + description: Returns full ticket details including all dialogue messages and + attachments. + summary: Retrieve ticket thread and responses + parameters: + - in: path + name: pk_or_code + schema: + type: string + required: true + tags: + - Ticketing & Official Inquiries + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TicketDetail' + description: '' + '403': + description: Permission denied + '404': + description: Ticket not found + patch: + operationId: v1_tickets_partial_update + description: Updates ticket priority, category, or assigned expert. + summary: Update ticket status or priority (Staff/Owner) + parameters: + - in: path + name: pk_or_code + schema: + type: string + required: true + tags: + - Ticketing & Official Inquiries + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedTicketListRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedTicketListRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedTicketListRequest' + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TicketDetail' + description: '' + /api/v1/tickets/{pk_or_code}/close/: + post: + operationId: v1_tickets_close_create + description: Marks a ticket as closed and records the resolution timestamp. + summary: Close ticket inquiry + parameters: + - in: path + name: pk_or_code + schema: + type: string + required: true + tags: + - Ticketing & Official Inquiries + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TicketDetailRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TicketDetailRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/TicketDetailRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TicketDetail' + description: '' + /api/v1/tickets/{pk_or_code}/rate/: + post: + operationId: v1_tickets_rate_create + description: Records 1 to 5 star rating and optional user feedback upon inquiry + completion. + summary: Submit satisfaction rating for ticket + parameters: + - in: path + name: pk_or_code + schema: + type: string + required: true + tags: + - Ticketing & Official Inquiries + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TicketRateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TicketRateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/TicketRateRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TicketDetail' + description: '' + '400': + description: Validation error + /api/v1/tickets/{pk_or_code}/reply/: + post: + operationId: v1_tickets_reply_create + description: Appends a new message or official response to an existing ticket + thread. + summary: Reply to ticket inquiry + parameters: + - in: path + name: pk_or_code + schema: + type: string + required: true + tags: + - Ticketing & Official Inquiries + requestBody: + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/TicketMessageCreateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TicketMessageCreateRequest' + application/json: + schema: + $ref: '#/components/schemas/TicketMessageCreateRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/TicketMessage' + description: '' + '400': + description: Invalid reply + '403': + description: Permission denied + /api/v1/version/: + get: + operationId: v1_version_retrieve + description: Returns latest active mobile/web application version details + summary: Get active application version + tags: + - v1 + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/AppVersion' + description: '' + '404': + description: No active version found +components: + schemas: + AboutUs: + type: object + properties: + content: + type: string + default: '' + AboutUsDobodi: + type: object + properties: + arabic_text: + type: string + default: '' + translated_text: + type: string + default: '' + title: + type: string + default: '' + content: + type: string + default: '' + AboutUsDobodiRequest: + type: object + properties: + arabic_text: + type: string + default: '' + translated_text: + type: string + default: '' + title: + type: string + default: '' + content: + type: string + default: '' + AboutUsRequest: + type: object + properties: + content: + type: string + default: '' + AdminNotification: + type: object + properties: + id: + type: integer + readOnly: true + title: + type: string + maxLength: 255 + message: + type: string + maxLength: 1024 + is_read: + type: boolean + notification_type: + type: string + nullable: true + maxLength: 50 + action: + type: string + maxLength: 50 + navigate_to: + type: string + nullable: true + maxLength: 255 + created_at: + type: string + format: date-time + readOnly: true + updated_at: + type: string + format: date-time + readOnly: true + user_fullname: + type: string + readOnly: true + user_email: + type: string + readOnly: true + user_id: + type: integer + readOnly: true + required: + - created_at + - id + - message + - title + - updated_at + - user_email + - user_fullname + - user_id + AdminNotificationRequest: + type: object + properties: + title: + type: string + minLength: 1 + maxLength: 255 + message: + type: string + minLength: 1 + maxLength: 1024 + is_read: + type: boolean + notification_type: + type: string + nullable: true + maxLength: 50 + action: + type: string + minLength: 1 + maxLength: 50 + navigate_to: + type: string + nullable: true + maxLength: 255 + required: + - message + - title + AdminUser: + type: object + properties: + id: + type: integer + readOnly: true + fullname: + type: string + nullable: true + title: Full Name + description: Full name of the user. + maxLength: 255 + email: + type: string + format: email + phone_number: + type: string + nullable: true + description: e.g., +1 555 1234567 + maxLength: 128 + avatar: + type: string + nullable: true + gender: + nullable: true + oneOf: + - $ref: '#/components/schemas/GenderEnum' + - $ref: '#/components/schemas/BlankEnum' + - $ref: '#/components/schemas/NullEnum' + birthdate: + type: string + format: date + nullable: true + info: + type: string + nullable: true + title: Bio / Info + skill: + type: string + nullable: true + title: Skill / Role + maxLength: 512 + city: + type: string + nullable: true + maxLength: 255 + country: + type: string + nullable: true + maxLength: 255 + device_id: + type: string + readOnly: true + device_os: + type: string + readOnly: true + user_agent: + type: string + readOnly: true + client_ip: + type: string + readOnly: true + fcm: + type: string + nullable: true + title: FCM Token + maxLength: 512 + user_type: + $ref: '#/components/schemas/UserTypeEnum' + is_active: + type: boolean + title: Active + description: Designates whether this user should be treated as active. + is_staff: + type: boolean + is_superuser: + type: boolean + title: Superuser status + description: Designates that this user has all permissions without explicitly + assigning them. + two_factor_enabled: + type: boolean + two_factor_method: + allOf: + - $ref: '#/components/schemas/TwoFactorMethodEnum' + title: Preferred 2FA Method + require_2fa: + type: boolean + default: false + custom_permissions: + title: Custom Permission Overrides + description: 'Dictionary of permission overrides: {permission_name: True/False}' + date_joined: + type: string + format: date-time + readOnly: true + last_login: + type: string + format: date-time + readOnly: true + nullable: true + auth_token: + type: string + readOnly: true + plain_password: + type: string + readOnly: true + required: + - auth_token + - client_ip + - date_joined + - device_id + - device_os + - email + - id + - last_login + - plain_password + - user_agent + AdminUserRequest: + type: object + properties: + fullname: + type: string + nullable: true + title: Full Name + description: Full name of the user. + maxLength: 255 + email: + type: string + format: email + minLength: 1 + phone_number: + type: string + nullable: true + description: e.g., +1 555 1234567 + maxLength: 128 + password: + type: string + writeOnly: true + minLength: 1 + avatar: + type: string + nullable: true + minLength: 1 + gender: + nullable: true + oneOf: + - $ref: '#/components/schemas/GenderEnum' + - $ref: '#/components/schemas/BlankEnum' + - $ref: '#/components/schemas/NullEnum' + birthdate: + type: string + format: date + nullable: true + info: + type: string + nullable: true + title: Bio / Info + skill: + type: string + nullable: true + title: Skill / Role + maxLength: 512 + city: + type: string + nullable: true + maxLength: 255 + country: + type: string + nullable: true + maxLength: 255 + fcm: + type: string + nullable: true + title: FCM Token + maxLength: 512 + user_type: + $ref: '#/components/schemas/UserTypeEnum' + is_active: + type: boolean + title: Active + description: Designates whether this user should be treated as active. + is_staff: + type: boolean + is_superuser: + type: boolean + title: Superuser status + description: Designates that this user has all permissions without explicitly + assigning them. + two_factor_enabled: + type: boolean + two_factor_method: + allOf: + - $ref: '#/components/schemas/TwoFactorMethodEnum' + title: Preferred 2FA Method + require_2fa: + type: boolean + default: false + custom_permissions: + title: Custom Permission Overrides + description: 'Dictionary of permission overrides: {permission_name: True/False}' + required: + - email + AppTypeEnum: + enum: + - google_play + - app_store + - direct + type: string + description: |- + * `google_play` - Google Play + * `app_store` - Apple App Store + * `direct` - Direct Download + AppVersion: + type: object + properties: + id: + type: integer + readOnly: true + version: + type: string + description: Application version in format X.Y.Z (e.g., 1.0.0) + pattern: ^\d+\.\d+\.\d+$ + maxLength: 20 + apk_file: + type: string + format: uri + nullable: true + readOnly: true + description: + type: string + description: Release notes and changes for this version + app_type: + allOf: + - $ref: '#/components/schemas/AppTypeEnum' + title: App Distribution Platform + downloads_count: + type: integer + is_active: + type: boolean + title: Active + description: Is this version currently active? + created_at: + type: string + format: date-time + readOnly: true + updated_at: + type: string + format: date-time + readOnly: true + required: + - apk_file + - created_at + - id + - updated_at + - version + AuthorTypeEnum: + enum: + - user + - institution + type: string + description: |- + * `user` - Individual User + * `institution` - Institution + BlankEnum: + enum: + - '' + CMSCommentStatusEnum: + enum: + - pending + - approved + - spam + type: string + description: |- + * `pending` - Pending Moderation + * `approved` - Approved + * `spam` - Spam / Rejected + CMSPostStatusEnum: + enum: + - draft + - pending_review + - published + - scheduled + - rejected + - archived + type: string + description: |- + * `draft` - Draft + * `pending_review` - Pending Review + * `published` - Published + * `scheduled` - Scheduled + * `rejected` - Rejected + * `archived` - Archived + Card: + type: object + properties: + card_number: + type: string + default: '' + card_name: + type: string + default: '' + whatsapp_number: + type: string + default: '' + CardRequest: + type: object + properties: + card_number: + type: string + default: '' + card_name: + type: string + default: '' + whatsapp_number: + type: string + default: '' + CentrifugoTokenResponse: + type: object + properties: + token: + type: string + description: Signed HMAC-SHA256 JWT for Centrifugo WebSocket connection + ws_url: + type: string + description: WebSocket endpoint URL for frontend client + user_id: + type: integer + description: Authenticated user ID + expires_in: + type: integer + description: Token lifetime in seconds + required: + - expires_in + - token + - user_id + - ws_url + ChatInstitutionMini: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + readOnly: true + title: Institution Name + description: Official name of the mosque, center, or institute. + slug: + type: string + readOnly: true + nullable: true + title: Slug / URL Identifier + pattern: ^[-\w]+$ + type: + allOf: + - $ref: '#/components/schemas/InstitutionTypeEnum' + readOnly: true + title: Institution Type + type_display: + type: string + readOnly: true + avatar: + type: string + format: uri + nullable: true + readOnly: true + title: Logo / Avatar + city: + type: string + readOnly: true + country: + type: string + readOnly: true + required: + - avatar + - city + - country + - id + - name + - slug + - type + - type_display + ChatMessage: + type: object + properties: + id: + type: integer + readOnly: true + room: + type: integer + readOnly: true + title: Chat Room + sender: + allOf: + - $ref: '#/components/schemas/ChatUserMini' + readOnly: true + sender_institution: + allOf: + - $ref: '#/components/schemas/ChatInstitutionMini' + readOnly: true + content: + type: string + readOnly: true + nullable: true + title: Message Text + attachment: + type: string + format: uri + nullable: true + readOnly: true + title: Attachment File + attachment_name: + type: string + readOnly: true + nullable: true + title: Attachment Filename + is_read_by_me: + type: boolean + readOnly: true + read_by_count: + type: integer + readOnly: true + created_at: + type: string + format: date-time + readOnly: true + title: Timestamp + required: + - attachment + - attachment_name + - content + - created_at + - id + - is_read_by_me + - read_by_count + - room + - sender + - sender_institution + ChatMessageCreateRequest: + type: object + properties: + content: + type: string + attachment: + type: string + format: binary + nullable: true + sender_institution_id: + type: integer + nullable: true + ChatRoomCreateRequest: + type: object + properties: + room_type: + allOf: + - $ref: '#/components/schemas/RoomTypeEnum' + default: direct + title: + type: string + maxLength: 255 + target_user_id: + type: integer + nullable: true + target_institution_id: + type: integer + nullable: true + participant_user_ids: + type: array + items: + type: integer + participant_institution_ids: + type: array + items: + type: integer + initial_message: + type: string + ChatRoomDetail: + type: object + properties: + id: + type: integer + readOnly: true + room_type: + allOf: + - $ref: '#/components/schemas/RoomTypeEnum' + readOnly: true + title: + type: string + readOnly: true + nullable: true + title: Conversation Title + display_title: + type: string + readOnly: true + participant_users: + type: array + items: + $ref: '#/components/schemas/ChatUserMini' + readOnly: true + participant_institutions: + type: array + items: + $ref: '#/components/schemas/ChatInstitutionMini' + readOnly: true + unread_count: + type: integer + readOnly: true + centrifugo_channel: + type: string + readOnly: true + created_at: + type: string + format: date-time + readOnly: true + updated_at: + type: string + format: date-time + readOnly: true + title: Last Activity + required: + - centrifugo_channel + - created_at + - display_title + - id + - participant_institutions + - participant_users + - room_type + - title + - unread_count + - updated_at + ChatRoomList: + type: object + properties: + id: + type: integer + readOnly: true + room_type: + allOf: + - $ref: '#/components/schemas/RoomTypeEnum' + readOnly: true + title: + type: string + readOnly: true + nullable: true + title: Conversation Title + display_title: + type: string + readOnly: true + participant_users: + type: array + items: + $ref: '#/components/schemas/ChatUserMini' + readOnly: true + participant_institutions: + type: array + items: + $ref: '#/components/schemas/ChatInstitutionMini' + readOnly: true + last_message: + allOf: + - $ref: '#/components/schemas/ChatMessage' + nullable: true + readOnly: true + unread_count: + type: integer + readOnly: true + centrifugo_channel: + type: string + readOnly: true + updated_at: + type: string + format: date-time + readOnly: true + title: Last Activity + required: + - centrifugo_channel + - display_title + - id + - last_message + - participant_institutions + - participant_users + - room_type + - title + - unread_count + - updated_at + ChatUserMini: + type: object + properties: + id: + type: integer + readOnly: true + email: + type: string + format: email + readOnly: true + nullable: true + title: Email Address + description: User primary email address. + fullname: + type: string + readOnly: true + nullable: true + title: Full Name + description: Full name of the user. + avatar: + type: string + format: uri + nullable: true + readOnly: true + user_type: + allOf: + - $ref: '#/components/schemas/UserTypeEnum' + readOnly: true + required: + - avatar + - email + - fullname + - id + - user_type + ColorEnum: + enum: + - emerald + - rose + - blue + - purple + - amber + - teal + type: string + description: |- + * `emerald` - سبز رضوی (Emerald) + * `rose` - سرخ زنده (Rose) + * `blue` - آبی دانش (Blue) + * `purple` - بنفش چندرسانه‌ای (Purple) + * `amber` - طلایی ویژه (Amber) + * `teal` - فیروزه‌ای (Teal) + ContentLanguageEnum: + enum: + - fa + - ar + - en + - ur + - fr + type: string + description: |- + * `fa` - Persian + * `ar` - Arabic + * `en` - English + * `ur` - Urdu + * `fr` - French + CourseCreateUpdateRequest: + type: object + properties: + title: + type: string + minLength: 1 + title: Course Title + maxLength: 255 + category: + $ref: '#/components/schemas/LMSCourseCategoryEnum' + language: + $ref: '#/components/schemas/ContentLanguageEnum' + level: + allOf: + - $ref: '#/components/schemas/LMSCourseLevelEnum' + title: Difficulty Level + cover_image: + type: string + format: binary + nullable: true + instructor_name: + type: string + minLength: 1 + maxLength: 255 + instructor_title: + type: string + nullable: true + title: Instructor Title / Specialty + maxLength: 255 + instructor_avatar: + type: string + format: binary + nullable: true + institution: + type: integer + nullable: true + title: Partner Institution / Center + organization_name: + type: string + nullable: true + title: Organization / Academy Name + maxLength: 255 + description: + type: string + minLength: 1 + title: Course Description & Syllabus Summary + duration_hours: + type: number + format: double + title: Duration (Hours) + is_published: + type: boolean + is_featured: + type: boolean + required: + - description + - instructor_name + - title + CourseDetail: + type: object + properties: + id: + type: integer + readOnly: true + title: + type: string + title: Course Title + maxLength: 255 + slug: + nullable: true + title: Slug / URL Identifier + oneOf: + - type: string + pattern: ^[-\w]+$ + maxLength: 280 + - type: string + maxLength: 0 + category: + $ref: '#/components/schemas/LMSCourseCategoryEnum' + language: + $ref: '#/components/schemas/ContentLanguageEnum' + level: + allOf: + - $ref: '#/components/schemas/LMSCourseLevelEnum' + title: Difficulty Level + cover_image: + type: string + format: uri + nullable: true + instructor_name: + type: string + maxLength: 255 + instructor_title: + type: string + nullable: true + title: Instructor Title / Specialty + maxLength: 255 + instructor_avatar: + type: string + format: uri + nullable: true + institution: + type: integer + nullable: true + title: Partner Institution / Center + organization_name: + type: string + nullable: true + title: Organization / Academy Name + maxLength: 255 + description: + type: string + title: Course Description & Syllabus Summary + duration_hours: + type: number + format: double + title: Duration (Hours) + rating: + type: number + format: double + title: Average Rating + reviews_count: + type: integer + is_published: + type: boolean + is_featured: + type: boolean + modules: + type: array + items: + $ref: '#/components/schemas/CourseModule' + readOnly: true + total_lessons: + type: integer + readOnly: true + total_students: + type: integer + readOnly: true + is_enrolled: + type: boolean + readOnly: true + progress_percentage: + type: integer + readOnly: true + certificate_code: + type: string + readOnly: true + created_at: + type: string + format: date-time + readOnly: true + updated_at: + type: string + format: date-time + readOnly: true + required: + - certificate_code + - created_at + - description + - id + - instructor_name + - is_enrolled + - modules + - progress_percentage + - title + - total_lessons + - total_students + - updated_at + CourseDetailRequest: + type: object + properties: + title: + type: string + minLength: 1 + title: Course Title + maxLength: 255 + slug: + nullable: true + title: Slug / URL Identifier + oneOf: + - type: string + pattern: ^[-\w]+$ + maxLength: 280 + - type: string + maxLength: 0 + category: + $ref: '#/components/schemas/LMSCourseCategoryEnum' + language: + $ref: '#/components/schemas/ContentLanguageEnum' + level: + allOf: + - $ref: '#/components/schemas/LMSCourseLevelEnum' + title: Difficulty Level + cover_image: + type: string + format: binary + nullable: true + instructor_name: + type: string + minLength: 1 + maxLength: 255 + instructor_title: + type: string + nullable: true + title: Instructor Title / Specialty + maxLength: 255 + instructor_avatar: + type: string + format: binary + nullable: true + institution: + type: integer + nullable: true + title: Partner Institution / Center + organization_name: + type: string + nullable: true + title: Organization / Academy Name + maxLength: 255 + description: + type: string + minLength: 1 + title: Course Description & Syllabus Summary + duration_hours: + type: number + format: double + title: Duration (Hours) + rating: + type: number + format: double + title: Average Rating + reviews_count: + type: integer + is_published: + type: boolean + is_featured: + type: boolean + required: + - description + - instructor_name + - title + CourseList: + type: object + properties: + id: + type: integer + readOnly: true + title: + type: string + title: Course Title + maxLength: 255 + slug: + nullable: true + title: Slug / URL Identifier + oneOf: + - type: string + pattern: ^[-\w]+$ + maxLength: 280 + - type: string + maxLength: 0 + category: + $ref: '#/components/schemas/LMSCourseCategoryEnum' + language: + $ref: '#/components/schemas/ContentLanguageEnum' + level: + allOf: + - $ref: '#/components/schemas/LMSCourseLevelEnum' + title: Difficulty Level + cover_image: + type: string + format: uri + nullable: true + instructor_name: + type: string + maxLength: 255 + instructor_title: + type: string + nullable: true + title: Instructor Title / Specialty + maxLength: 255 + instructor_avatar: + type: string + format: uri + nullable: true + organization_name: + type: string + nullable: true + title: Organization / Academy Name + maxLength: 255 + duration_hours: + type: number + format: double + title: Duration (Hours) + rating: + type: number + format: double + title: Average Rating + reviews_count: + type: integer + is_featured: + type: boolean + total_lessons: + type: integer + readOnly: true + total_students: + type: integer + readOnly: true + is_enrolled: + type: boolean + readOnly: true + progress_percentage: + type: integer + readOnly: true + created_at: + type: string + format: date-time + readOnly: true + required: + - created_at + - id + - instructor_name + - is_enrolled + - progress_percentage + - title + - total_lessons + - total_students + CourseModule: + type: object + properties: + id: + type: integer + readOnly: true + title: + type: string + title: Module Title + maxLength: 255 + description: + type: string + nullable: true + title: Module Description + order: + type: integer + title: Display Order + lessons: + type: array + items: + $ref: '#/components/schemas/LessonList' + readOnly: true + required: + - id + - lessons + - title + CourseModuleRequest: + type: object + properties: + title: + type: string + minLength: 1 + title: Module Title + maxLength: 255 + description: + type: string + nullable: true + title: Module Description + order: + type: integer + title: Display Order + required: + - title + CourseProgress: + type: object + properties: + course_id: + type: integer + course_title: + type: string + progress_percentage: + type: integer + completed_lessons_count: + type: integer + total_lessons_count: + type: integer + is_completed: + type: boolean + certificate_code: + type: string + nullable: true + required: + - certificate_code + - completed_lessons_count + - course_id + - course_title + - is_completed + - progress_percentage + - total_lessons_count + CustomTokenObtainPairRequest: + type: object + description: |- + JWT token obtain serializer that accepts either 'username' or 'email' as the credential. + Enforces Two-Factor Authentication (2FA) when user.is_2fa_required() is True. + properties: + email: + type: string + writeOnly: true + minLength: 1 + password: + type: string + writeOnly: true + minLength: 1 + username: + type: string + writeOnly: true + minLength: 1 + required: + - password + DeviceOsEnum: + enum: + - android + - apple + - web + type: string + description: |- + * `android` - Android + * `apple` - Apple iOS + * `web` - Web + DigitalCertificate: + type: object + properties: + id: + type: integer + readOnly: true + certificate_code: + type: string + title: Verification Code + description: e.g. CERT-RAZAVI-2026-AB12CD + maxLength: 100 + uuid: + type: string + format: uuid + readOnly: true + title: Unique UUID + recipient_name: + type: string + readOnly: true + course_title: + type: string + readOnly: true + course_slug: + type: string + readOnly: true + organization: + type: string + readOnly: true + issue_date: + type: string + format: date + final_score: + type: number + format: double + title: Final Score (%) + certificate_file: + type: string + format: uri + nullable: true + title: Certificate PDF Document + is_revoked: + type: boolean + created_at: + type: string + format: date-time + readOnly: true + required: + - certificate_code + - course_slug + - course_title + - created_at + - id + - organization + - recipient_name + - uuid + DiplomacyAnalytics: + type: object + properties: + total_reports_submitted: + type: integer + approved_reports_count: + type: integer + total_centers_visited: + type: integer + required: + - approved_reports_count + - total_centers_visited + - total_reports_submitted + DiplomacyReportStatusEnum: + enum: + - submitted + - under_review + - approved + - returned + type: string + description: |- + * `submitted` - Submitted + * `under_review` - Under Review + * `approved` - Approved by Headquarters + * `returned` - Returned for Revision + DocTypeEnum: + enum: + - mou + - report + - contract + - other + type: string + description: |- + * `mou` - MOU / Bilateral Agreement + * `report` - Progress / Milestone Report + * `contract` - Contract / Agreement + * `other` - Other Document + DocumentTypeEnum: + enum: + - national_id + - institution_license + - recommendation_letter + - student_card + - other + type: string + description: |- + * `national_id` - National ID / Passport + * `institution_license` - Institution Registration / License + * `recommendation_letter` - Recommendation Letter + * `student_card` - Student / Member Card + * `other` - Other Document + DonationCampaignCategoryEnum: + enum: + - mosque_construction + - humanitarian_relief + - pilgrim_support + - cultural_publications + - educational_scholarships + - general + type: string + description: |- + * `mosque_construction` - Mosque & Center Construction / Renovation + * `humanitarian_relief` - Humanitarian & Disaster Relief + * `pilgrim_support` - Imam Reza (AS) Pilgrim Services + * `cultural_publications` - Quran & Islamic Publications Translation + * `educational_scholarships` - Seminary & Academic Scholarships + * `general` - General Waqf & Endowment + DonationCampaignDetail: + type: object + properties: + id: + type: integer + readOnly: true + title: + type: string + title: Campaign Title + maxLength: 255 + slug: + nullable: true + title: Slug / URL Identifier + oneOf: + - type: string + pattern: ^[-\w]+$ + maxLength: 280 + - type: string + maxLength: 0 + description: + type: string + title: Campaign Description & Objectives + target_amount: + type: string + format: decimal + pattern: ^-?\d{0,12}(?:\.\d{0,2})?$ + raised_amount: + type: string + format: decimal + pattern: ^-?\d{0,12}(?:\.\d{0,2})?$ + title: Accumulated Raised Amount + currency: + type: string + title: Currency (e.g. USD, EUR, IRR, AED) + maxLength: 10 + category: + $ref: '#/components/schemas/DonationCampaignCategoryEnum' + priority: + $ref: '#/components/schemas/DonationCampaignPriorityEnum' + cover_image: + type: string + format: uri + nullable: true + title: Campaign Image + institution: + type: integer + nullable: true + title: Beneficiary Institution / Mosque + institution_name: + type: string + readOnly: true + organization_name: + type: string + nullable: true + title: Managing Foundation Name + maxLength: 255 + deadline: + type: string + format: date + nullable: true + title: Campaign Deadline + progress_percentage: + type: integer + readOnly: true + donors_count: + type: integer + readOnly: true + is_featured: + type: boolean + recent_donations: + type: array + items: + $ref: '#/components/schemas/DonationTransaction' + readOnly: true + created_at: + type: string + format: date-time + readOnly: true + updated_at: + type: string + format: date-time + readOnly: true + required: + - created_at + - description + - donors_count + - id + - institution_name + - progress_percentage + - recent_donations + - target_amount + - title + - updated_at + DonationCampaignList: + type: object + properties: + id: + type: integer + readOnly: true + title: + type: string + title: Campaign Title + maxLength: 255 + slug: + nullable: true + title: Slug / URL Identifier + oneOf: + - type: string + pattern: ^[-\w]+$ + maxLength: 280 + - type: string + maxLength: 0 + description: + type: string + title: Campaign Description & Objectives + target_amount: + type: string + format: decimal + pattern: ^-?\d{0,12}(?:\.\d{0,2})?$ + raised_amount: + type: string + format: decimal + pattern: ^-?\d{0,12}(?:\.\d{0,2})?$ + title: Accumulated Raised Amount + currency: + type: string + title: Currency (e.g. USD, EUR, IRR, AED) + maxLength: 10 + category: + $ref: '#/components/schemas/DonationCampaignCategoryEnum' + priority: + $ref: '#/components/schemas/DonationCampaignPriorityEnum' + cover_image: + type: string + format: uri + nullable: true + title: Campaign Image + institution: + type: integer + nullable: true + title: Beneficiary Institution / Mosque + institution_name: + type: string + readOnly: true + organization_name: + type: string + nullable: true + title: Managing Foundation Name + maxLength: 255 + deadline: + type: string + format: date + nullable: true + title: Campaign Deadline + progress_percentage: + type: integer + readOnly: true + donors_count: + type: integer + readOnly: true + is_featured: + type: boolean + created_at: + type: string + format: date-time + readOnly: true + required: + - created_at + - description + - donors_count + - id + - institution_name + - progress_percentage + - target_amount + - title + DonationCampaignPriorityEnum: + enum: + - urgent + - high + - medium + type: string + description: |- + * `urgent` - Urgent Appeal + * `high` - High Priority + * `medium` - Medium Priority + DonationContributeRequestRequest: + type: object + properties: + amount: + type: string + format: decimal + pattern: ^-?\d{0,10}(?:\.\d{0,2})?$ + donor_name: + type: string + default: Anonymous Donor + donor_email: + nullable: true + oneOf: + - type: string + format: email + - type: string + maxLength: 0 + required: + - amount + DonationTransaction: + type: object + properties: + id: + type: integer + readOnly: true + transaction_reference: + type: string + readOnly: true + description: e.g. DON-RAZAVI-2026-XYZ123 + campaign: + type: integer + campaign_title: + type: string + readOnly: true + donor_name: + type: string + title: Donor Display Name + maxLength: 255 + amount: + type: string + format: decimal + pattern: ^-?\d{0,10}(?:\.\d{0,2})?$ + title: Donation Amount + currency: + type: string + maxLength: 10 + payment_status: + allOf: + - $ref: '#/components/schemas/PaymentStatusEnum' + readOnly: true + created_at: + type: string + format: date-time + readOnly: true + title: Donated At + required: + - amount + - campaign + - campaign_title + - created_at + - id + - payment_status + - transaction_reference + DonationsAnalytics: + type: object + properties: + active_campaigns_count: + type: integer + total_funds_raised_usd: + type: number + format: double + required: + - active_campaigns_count + - total_funds_raised_usd + EventCalendarItem: + type: object + properties: + id: + type: integer + readOnly: true + title: + type: string + title: Event Title + maxLength: 255 + slug: + oneOf: + - type: string + maxLength: 280 + pattern: ^[-a-zA-Z0-9_]+$ + - type: string + maxLength: 0 + category: + $ref: '#/components/schemas/EventCategoryEnum' + event_date: + type: string + format: date + start_time: + type: string + format: time + end_time: + type: string + format: time + nullable: true + location_type: + $ref: '#/components/schemas/LocationTypeEnum' + organizer_name: + type: string + readOnly: true + is_featured: + type: boolean + required: + - event_date + - id + - organizer_name + - start_time + - title + EventCategoryEnum: + enum: + - religious + - educational + - cultural + - counseling + - conference + - workshop + type: string + description: |- + * `religious` - Religious + * `educational` - Educational + * `cultural` - Cultural + * `counseling` - Counseling + * `conference` - Conference + * `workshop` - Workshop + EventCreateUpdate: + type: object + properties: + id: + type: integer + readOnly: true + title: + type: string + title: Event Title + maxLength: 255 + description: + type: string + title: Event Description + category: + $ref: '#/components/schemas/EventCategoryEnum' + event_date: + type: string + format: date + start_time: + type: string + format: time + end_time: + type: string + format: time + nullable: true + speaker_name: + type: string + maxLength: 255 + speaker_title: + type: string + title: Speaker Title / Affiliation + maxLength: 255 + speaker_avatar: + type: string + format: uri + nullable: true + location_type: + $ref: '#/components/schemas/LocationTypeEnum' + venue_address: + type: string + maxLength: 500 + online_meeting_url: + title: Online Meeting Link + oneOf: + - type: string + format: uri + maxLength: 200 + - type: string + maxLength: 0 + cover_image: + type: string + format: uri + nullable: true + capacity: + type: integer + title: Capacity Limit + is_active: + type: boolean + is_featured: + type: boolean + language: + $ref: '#/components/schemas/ContentLanguageEnum' + tags: {} + required: + - description + - event_date + - id + - start_time + - title + EventCreateUpdateRequest: + type: object + properties: + title: + type: string + minLength: 1 + title: Event Title + maxLength: 255 + description: + type: string + minLength: 1 + title: Event Description + organizer_id: + type: integer + writeOnly: true + category: + $ref: '#/components/schemas/EventCategoryEnum' + event_date: + type: string + format: date + start_time: + type: string + format: time + end_time: + type: string + format: time + nullable: true + speaker_name: + type: string + maxLength: 255 + speaker_title: + type: string + title: Speaker Title / Affiliation + maxLength: 255 + speaker_avatar: + type: string + format: binary + nullable: true + location_type: + $ref: '#/components/schemas/LocationTypeEnum' + venue_address: + type: string + maxLength: 500 + online_meeting_url: + title: Online Meeting Link + oneOf: + - type: string + format: uri + maxLength: 200 + - type: string + maxLength: 0 + cover_image: + type: string + format: binary + nullable: true + capacity: + type: integer + title: Capacity Limit + is_active: + type: boolean + is_featured: + type: boolean + language: + $ref: '#/components/schemas/ContentLanguageEnum' + tags: {} + required: + - description + - event_date + - organizer_id + - start_time + - title + EventDetail: + type: object + properties: + id: + type: integer + readOnly: true + title: + type: string + title: Event Title + maxLength: 255 + slug: + oneOf: + - type: string + maxLength: 280 + pattern: ^[-a-zA-Z0-9_]+$ + - type: string + maxLength: 0 + description: + type: string + title: Event Description + category: + $ref: '#/components/schemas/EventCategoryEnum' + event_date: + type: string + format: date + start_time: + type: string + format: time + end_time: + type: string + format: time + nullable: true + speaker_name: + type: string + maxLength: 255 + speaker_title: + type: string + title: Speaker Title / Affiliation + maxLength: 255 + speaker_avatar: + type: string + format: uri + nullable: true + location_type: + $ref: '#/components/schemas/LocationTypeEnum' + venue_address: + type: string + maxLength: 500 + online_meeting_url: + title: Online Meeting Link + oneOf: + - type: string + format: uri + maxLength: 200 + - type: string + maxLength: 0 + cover_image: + type: string + format: uri + nullable: true + capacity: + type: integer + title: Capacity Limit + registration_count: + type: integer + title: Current Registrations + is_active: + type: boolean + is_featured: + type: boolean + language: + $ref: '#/components/schemas/ContentLanguageEnum' + tags: {} + organizer: + allOf: + - $ref: '#/components/schemas/EventOrganizerMinimal' + readOnly: true + is_registered: + type: boolean + readOnly: true + my_registration: + type: object + additionalProperties: {} + nullable: true + readOnly: true + created_at: + type: string + format: date-time + readOnly: true + updated_at: + type: string + format: date-time + readOnly: true + required: + - created_at + - description + - event_date + - id + - is_registered + - my_registration + - organizer + - start_time + - title + - updated_at + EventList: + type: object + properties: + id: + type: integer + readOnly: true + title: + type: string + title: Event Title + maxLength: 255 + slug: + oneOf: + - type: string + maxLength: 280 + pattern: ^[-a-zA-Z0-9_]+$ + - type: string + maxLength: 0 + category: + $ref: '#/components/schemas/EventCategoryEnum' + event_date: + type: string + format: date + start_time: + type: string + format: time + end_time: + type: string + format: time + nullable: true + speaker_name: + type: string + maxLength: 255 + speaker_title: + type: string + title: Speaker Title / Affiliation + maxLength: 255 + speaker_avatar: + type: string + format: uri + nullable: true + location_type: + $ref: '#/components/schemas/LocationTypeEnum' + venue_address: + type: string + maxLength: 500 + cover_image: + type: string + format: uri + nullable: true + capacity: + type: integer + title: Capacity Limit + registration_count: + type: integer + title: Current Registrations + is_active: + type: boolean + is_featured: + type: boolean + language: + $ref: '#/components/schemas/ContentLanguageEnum' + tags: {} + organizer: + allOf: + - $ref: '#/components/schemas/EventOrganizerMinimal' + readOnly: true + created_at: + type: string + format: date-time + readOnly: true + required: + - created_at + - event_date + - id + - organizer + - start_time + - title + EventOrganizerMinimal: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + title: Institution Name + description: Official name of the mosque, center, or institute. + maxLength: 255 + slug: + nullable: true + title: Slug / URL Identifier + oneOf: + - type: string + pattern: ^[-\w]+$ + maxLength: 255 + - type: string + maxLength: 0 + type: + allOf: + - $ref: '#/components/schemas/InstitutionTypeEnum' + title: Institution Type + city: + type: string + maxLength: 255 + country: + type: string + maxLength: 255 + avatar: + type: string + format: uri + nullable: true + title: Logo / Avatar + verification_status: + $ref: '#/components/schemas/VerificationStatusEnum' + required: + - city + - country + - id + - name + EventParticipantUser: + type: object + properties: + id: + type: integer + readOnly: true + email: + nullable: true + title: Email Address + description: User primary email address. + oneOf: + - type: string + format: email + maxLength: 254 + - type: string + maxLength: 0 + fullname: + type: string + nullable: true + title: Full Name + description: Full name of the user. + maxLength: 255 + avatar: + type: string + format: uri + nullable: true + required: + - id + EventRegisterInputRequest: + type: object + properties: + notes: + type: string + default: '' + EventRegistration: + type: object + properties: + id: + type: integer + readOnly: true + event: + type: integer + readOnly: true + user: + allOf: + - $ref: '#/components/schemas/EventParticipantUser' + readOnly: true + status: + $ref: '#/components/schemas/EventRegistrationStatusEnum' + notes: + type: string + title: Participant Notes + registered_at: + type: string + format: date-time + readOnly: true + required: + - event + - id + - registered_at + - user + EventRegistrationStatusEnum: + enum: + - registered + - attended + - cancelled + type: string + description: |- + * `registered` - Registered + * `attended` - Attended + * `cancelled` - Cancelled + ExchangeTokenRequest: + type: object + properties: + temp_token: + type: string + minLength: 1 + maxLength: 128 + required: + - temp_token + ExecutiveDashboard: + type: object + properties: + institutions: + $ref: '#/components/schemas/InstitutionsAnalytics' + projects: + $ref: '#/components/schemas/ProjectsAnalytics' + lms: + $ref: '#/components/schemas/LMSAnalytics' + ticketing: + $ref: '#/components/schemas/TicketingAnalytics' + donations: + $ref: '#/components/schemas/DonationsAnalytics' + diplomacy: + $ref: '#/components/schemas/DiplomacyAnalytics' + required: + - diplomacy + - donations + - institutions + - lms + - projects + - ticketing + FAQItem: + type: object + properties: + question: + type: string + default: '' + answer: + type: string + default: '' + FAQItemRequest: + type: object + properties: + question: + type: string + default: '' + answer: + type: string + default: '' + FollowResponse: + type: object + properties: + is_following: + type: boolean + follower_count: + type: integer + message: + type: string + required: + - follower_count + - is_following + - message + FollowResponseRequest: + type: object + properties: + is_following: + type: boolean + follower_count: + type: integer + message: + type: string + minLength: 1 + required: + - follower_count + - is_following + - message + FormDefinitionDetail: + type: object + properties: + id: + type: integer + readOnly: true + title: + type: string + title: Form Title + maxLength: 255 + slug: + nullable: true + title: Slug / URL Identifier + oneOf: + - type: string + pattern: ^[-\w]+$ + maxLength: 280 + - type: string + maxLength: 0 + description: + type: string + nullable: true + title: Instructions & Description + schema: + title: Form Fields Schema (JSON) + description: 'Array of field definitions: [{"id": "applicant_name", "label": + "Full Name", "type": "text", "required": true}]' + is_active: + type: boolean + is_public: + type: boolean + title: Is Publicly Accessible (No Login Required) + target_role: + type: string + nullable: true + title: Target User Role Restriction + maxLength: 50 + created_by: + type: integer + readOnly: true + nullable: true + creator_name: + type: string + readOnly: true + created_at: + type: string + format: date-time + readOnly: true + updated_at: + type: string + format: date-time + readOnly: true + required: + - created_at + - created_by + - creator_name + - id + - title + - updated_at + FormDefinitionDetailRequest: + type: object + properties: + title: + type: string + minLength: 1 + title: Form Title + maxLength: 255 + slug: + nullable: true + title: Slug / URL Identifier + oneOf: + - type: string + pattern: ^[-\w]+$ + maxLength: 280 + - type: string + maxLength: 0 + description: + type: string + nullable: true + title: Instructions & Description + schema: + title: Form Fields Schema (JSON) + description: 'Array of field definitions: [{"id": "applicant_name", "label": + "Full Name", "type": "text", "required": true}]' + is_active: + type: boolean + is_public: + type: boolean + title: Is Publicly Accessible (No Login Required) + target_role: + type: string + nullable: true + title: Target User Role Restriction + maxLength: 50 + required: + - title + FormDefinitionList: + type: object + properties: + id: + type: integer + readOnly: true + title: + type: string + title: Form Title + maxLength: 255 + slug: + nullable: true + title: Slug / URL Identifier + oneOf: + - type: string + pattern: ^[-\w]+$ + maxLength: 280 + - type: string + maxLength: 0 + description: + type: string + nullable: true + title: Instructions & Description + is_active: + type: boolean + is_public: + type: boolean + title: Is Publicly Accessible (No Login Required) + target_role: + type: string + nullable: true + title: Target User Role Restriction + maxLength: 50 + created_by: + type: integer + readOnly: true + nullable: true + creator_name: + type: string + readOnly: true + responses_count: + type: integer + readOnly: true + created_at: + type: string + format: date-time + readOnly: true + required: + - created_at + - created_by + - creator_name + - id + - responses_count + - title + FormResponse: + type: object + properties: + id: + type: integer + readOnly: true + form: + type: integer + form_title: + type: string + readOnly: true + user: + type: integer + readOnly: true + nullable: true + title: Submitter + submitter_name: + type: string + readOnly: true + answers: + title: Submitted Form Answers + attachment: + type: string + format: uri + nullable: true + title: Uploaded Attachment + workflow_status: + allOf: + - $ref: '#/components/schemas/WorkflowStatusEnum' + readOnly: true + admin_notes: + type: string + readOnly: true + nullable: true + title: Reviewer / Admin Notes + reviewed_by: + type: integer + readOnly: true + nullable: true + reviewer_name: + type: string + readOnly: true + created_at: + type: string + format: date-time + readOnly: true + title: Submitted At + updated_at: + type: string + format: date-time + readOnly: true + required: + - admin_notes + - created_at + - form + - form_title + - id + - reviewed_by + - reviewer_name + - submitter_name + - updated_at + - user + - workflow_status + FormResponseSubmitRequest: + type: object + properties: + answers: + description: Dictionary of question IDs and answers + attachment: + type: string + format: binary + nullable: true + required: + - answers + GenderEnum: + enum: + - male + - female + - other + type: string + description: |- + * `male` - Male + * `female` - Female + * `other` - Other + HealthCheckResponse: + type: object + properties: + status: + type: string + default: healthy + timestamp: + type: string + format: date-time + version: + type: string + default: 1.0.0 + required: + - timestamp + InstitutionCreateUpdateRequest: + type: object + properties: + name: + type: string + minLength: 1 + title: Institution Name + description: Official name of the mosque, center, or institute. + maxLength: 255 + type: + allOf: + - $ref: '#/components/schemas/InstitutionTypeEnum' + title: Institution Type + region: + type: integer + description: Region ID this institution belongs to (required). + country: + type: string + minLength: 1 + maxLength: 255 + city: + type: string + minLength: 1 + maxLength: 255 + address: + type: string + nullable: true + title: Full Address + latitude: + type: number + format: double + nullable: true + longitude: + type: number + format: double + nullable: true + description: + type: string + nullable: true + title: Description & Background + website: + nullable: true + oneOf: + - type: string + format: uri + maxLength: 500 + - type: string + maxLength: 0 + email: + nullable: true + title: Official Email + oneOf: + - type: string + format: email + maxLength: 254 + - type: string + maxLength: 0 + phone: + type: string + nullable: true + title: Official Phone + maxLength: 50 + established_year: + type: integer + nullable: true + title: Established Year (CE) + cover_image: + type: string + format: binary + nullable: true + avatar: + type: string + format: binary + nullable: true + title: Logo / Avatar + social_media: + title: Social Media Links + description: 'Dictionary with keys: facebook, twitter, instagram, linkedin, + youtube, telegram' + working_hours: + title: Working Hours / Prayer Times Schedule + tags: + title: Tags & Specializations + is_featured: + type: boolean + required: + - city + - country + - name + - region + InstitutionDetail: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + title: Institution Name + description: Official name of the mosque, center, or institute. + maxLength: 255 + slug: + type: string + readOnly: true + nullable: true + title: Slug / URL Identifier + pattern: ^[-\w]+$ + type: + allOf: + - $ref: '#/components/schemas/InstitutionTypeEnum' + title: Institution Type + type_display: + type: string + readOnly: true + region: + allOf: + - $ref: '#/components/schemas/Region' + readOnly: true + region_id: + type: integer + readOnly: true + region_name: + type: string + readOnly: true + country: + type: string + maxLength: 255 + city: + type: string + maxLength: 255 + address: + type: string + nullable: true + title: Full Address + latitude: + type: number + format: double + nullable: true + longitude: + type: number + format: double + nullable: true + description: + type: string + nullable: true + title: Description & Background + website: + nullable: true + oneOf: + - type: string + format: uri + maxLength: 500 + - type: string + maxLength: 0 + email: + nullable: true + title: Official Email + oneOf: + - type: string + format: email + maxLength: 254 + - type: string + maxLength: 0 + phone: + type: string + nullable: true + title: Official Phone + maxLength: 50 + established_year: + type: integer + nullable: true + title: Established Year (CE) + cover_image: + type: string + nullable: true + readOnly: true + avatar: + type: string + nullable: true + readOnly: true + social_media: + title: Social Media Links + description: 'Dictionary with keys: facebook, twitter, instagram, linkedin, + youtube, telegram' + working_hours: + title: Working Hours / Prayer Times Schedule + tags: + title: Tags & Specializations + verification_status: + allOf: + - $ref: '#/components/schemas/VerificationStatusEnum' + readOnly: true + verification_status_display: + type: string + readOnly: true + follower_count: + type: integer + readOnly: true + title: Followers Count + is_featured: + type: boolean + readOnly: true + is_active: + type: boolean + readOnly: true + created_by_email: + type: string + format: email + readOnly: true + nullable: true + members_count: + type: integer + readOnly: true + is_following: + type: boolean + readOnly: true + user_role: + type: string + nullable: true + readOnly: true + timeline_entries: + type: array + items: + $ref: '#/components/schemas/InstitutionTimeline' + readOnly: true + media_items: + type: array + items: + $ref: '#/components/schemas/InstitutionMedia' + readOnly: true + quick_access_links: + type: array + items: + $ref: '#/components/schemas/QuickAccessLink' + readOnly: true + projects: + type: array + items: {} + readOnly: true + posts: + type: array + items: {} + readOnly: true + created_at: + type: string + format: date-time + readOnly: true + updated_at: + type: string + format: date-time + readOnly: true + required: + - avatar + - city + - country + - cover_image + - created_at + - created_by_email + - follower_count + - id + - is_active + - is_featured + - is_following + - media_items + - members_count + - name + - posts + - projects + - quick_access_links + - region + - region_id + - region_name + - slug + - timeline_entries + - type_display + - updated_at + - user_role + - verification_status + - verification_status_display + InstitutionList: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + readOnly: true + title: Institution Name + description: Official name of the mosque, center, or institute. + slug: + type: string + readOnly: true + nullable: true + title: Slug / URL Identifier + pattern: ^[-\w]+$ + type: + allOf: + - $ref: '#/components/schemas/InstitutionTypeEnum' + readOnly: true + title: Institution Type + type_display: + type: string + readOnly: true + region: + allOf: + - $ref: '#/components/schemas/RegionMini' + readOnly: true + region_id: + type: integer + readOnly: true + region_name: + type: string + readOnly: true + country: + type: string + readOnly: true + city: + type: string + readOnly: true + address: + type: string + readOnly: true + nullable: true + title: Full Address + latitude: + type: number + format: double + readOnly: true + nullable: true + longitude: + type: number + format: double + readOnly: true + nullable: true + avatar: + type: string + nullable: true + readOnly: true + cover_image: + type: string + nullable: true + readOnly: true + verification_status: + allOf: + - $ref: '#/components/schemas/VerificationStatusEnum' + readOnly: true + verification_status_display: + type: string + readOnly: true + follower_count: + type: integer + readOnly: true + title: Followers Count + is_featured: + type: boolean + readOnly: true + tags: + readOnly: true + title: Tags & Specializations + is_following: + type: boolean + readOnly: true + created_at: + type: string + format: date-time + readOnly: true + required: + - address + - avatar + - city + - country + - cover_image + - created_at + - follower_count + - id + - is_featured + - is_following + - latitude + - longitude + - name + - region + - region_id + - region_name + - slug + - tags + - type + - type_display + - verification_status + - verification_status_display + InstitutionMedia: + type: object + properties: + id: + type: integer + readOnly: true + institution: + type: integer + readOnly: true + file: + type: string + format: uri + title: Media File + media_type: + $ref: '#/components/schemas/InstitutionMediaMediaTypeEnum' + media_type_display: + type: string + readOnly: true + title: + type: string + nullable: true + title: Media Title + maxLength: 255 + caption: + type: string + nullable: true + title: Caption / Notes + uploaded_at: + type: string + format: date-time + readOnly: true + required: + - file + - id + - institution + - media_type_display + - uploaded_at + InstitutionMediaMediaTypeEnum: + enum: + - image + - video + - document + type: string + description: |- + * `image` - Image + * `video` - Video + * `document` - Document / Publication + InstitutionMediaRequest: + type: object + properties: + file: + type: string + format: binary + title: Media File + media_type: + $ref: '#/components/schemas/InstitutionMediaMediaTypeEnum' + title: + type: string + nullable: true + title: Media Title + maxLength: 255 + caption: + type: string + nullable: true + title: Caption / Notes + required: + - file + InstitutionMeetingMinimal: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + title: Institution Name + description: Official name of the mosque, center, or institute. + maxLength: 255 + slug: + nullable: true + title: Slug / URL Identifier + oneOf: + - type: string + pattern: ^[-\w]+$ + maxLength: 255 + - type: string + maxLength: 0 + type: + allOf: + - $ref: '#/components/schemas/InstitutionTypeEnum' + title: Institution Type + avatar: + type: string + format: uri + nullable: true + title: Logo / Avatar + city: + type: string + maxLength: 255 + country: + type: string + maxLength: 255 + required: + - city + - country + - id + - name + InstitutionMember: + type: object + properties: + id: + type: integer + readOnly: true + institution: + type: integer + readOnly: true + user: + allOf: + - $ref: '#/components/schemas/MemberUserMini' + readOnly: true + role: + $ref: '#/components/schemas/RoleEnum' + role_display: + type: string + readOnly: true + title: + type: string + nullable: true + title: Organizational Title + description: e.g., Director, Media Manager, Cultural Affairs Officer + maxLength: 150 + joined_at: + type: string + format: date-time + readOnly: true + required: + - id + - institution + - joined_at + - role_display + - user + InstitutionMemberAddRequest: + type: object + properties: + email: + type: string + format: email + minLength: 1 + role: + allOf: + - $ref: '#/components/schemas/RoleEnum' + default: viewer + title: + type: string + maxLength: 150 + required: + - email + InstitutionTimeline: + type: object + properties: + id: + type: integer + readOnly: true + institution: + type: integer + readOnly: true + year: + type: string + title: Year / Period + description: e.g. 1995, 1416 AH, or 2020-Present + maxLength: 50 + title: + type: string + title: Milestone Title + maxLength: 255 + description: + type: string + nullable: true + title: Milestone Description + order: + type: integer + title: Display Order + created_at: + type: string + format: date-time + readOnly: true + required: + - created_at + - id + - institution + - title + - year + InstitutionTimelineRequest: + type: object + properties: + year: + type: string + minLength: 1 + title: Year / Period + description: e.g. 1995, 1416 AH, or 2020-Present + maxLength: 50 + title: + type: string + minLength: 1 + title: Milestone Title + maxLength: 255 + description: + type: string + nullable: true + title: Milestone Description + order: + type: integer + title: Display Order + required: + - title + - year + InstitutionTypeEnum: + enum: + - mosque + - hussainiya + - cultural_center + - library + - institute + - charity + - other + type: string + description: |- + * `mosque` - Mosque + * `hussainiya` - Hussainiya / Islamic Center + * `cultural_center` - Cultural Center + * `library` - Islamic Library + * `institute` - Seminary & Institute + * `charity` - Charity & Foundation + * `other` - Other Organization + InstitutionsAnalytics: + type: object + properties: + total_institutions: + type: integer + verified_institutions: + type: integer + countries_count: + type: integer + by_type: + type: object + additionalProperties: + type: integer + top_countries: + type: array + items: + type: object + additionalProperties: {} + required: + - by_type + - countries_count + - top_countries + - total_institutions + - verified_institutions + KanbanColumn: + type: object + properties: + id: + type: integer + readOnly: true + project: + type: integer + readOnly: true + name: + type: string + title: Column Name + maxLength: 100 + code: + type: string + title: Column Code Identifier + maxLength: 50 + order: + type: integer + title: Display Order + color: + type: string + nullable: true + title: Badge / Header Color + maxLength: 20 + tasks: + type: array + items: + $ref: '#/components/schemas/KanbanTask' + readOnly: true + required: + - id + - name + - project + - tasks + KanbanTask: + type: object + properties: + id: + type: integer + readOnly: true + project: + type: integer + readOnly: true + column: + type: integer + title: Kanban Column + column_name: + type: string + readOnly: true + column_code: + type: string + readOnly: true + title: + type: string + title: Task Title + maxLength: 255 + description: + type: string + nullable: true + title: Task Description + priority: + $ref: '#/components/schemas/TicketPriorityEnum' + priority_display: + type: string + readOnly: true + assignee: + allOf: + - $ref: '#/components/schemas/ProjectUserMini' + readOnly: true + due_date: + type: string + format: date + nullable: true + labels: + title: Task Labels + order: + type: integer + title: Display Order within Column + created_at: + type: string + format: date-time + readOnly: true + updated_at: + type: string + format: date-time + readOnly: true + required: + - assignee + - column + - column_code + - column_name + - created_at + - id + - priority_display + - project + - title + - updated_at + KanbanTaskCreateUpdateRequest: + type: object + properties: + column: + type: integer + title: Kanban Column + title: + type: string + minLength: 1 + title: Task Title + maxLength: 255 + description: + type: string + nullable: true + title: Task Description + priority: + $ref: '#/components/schemas/TicketPriorityEnum' + assignee: + type: integer + nullable: true + title: Assignee User + due_date: + type: string + format: date + nullable: true + labels: + title: Task Labels + order: + type: integer + title: Display Order within Column + required: + - column + - title + LMSAnalytics: + type: object + properties: + total_courses: + type: integer + total_enrollments: + type: integer + total_certificates_issued: + type: integer + required: + - total_certificates_issued + - total_courses + - total_enrollments + LMSCourseCategoryEnum: + enum: + - razavi_teachings + - cultural_diplomacy + - center_management + - international_media + - specialized_languages + - general + type: string + description: |- + * `razavi_teachings` - Razavi Teachings & Lifestyle + * `cultural_diplomacy` - Cultural Diplomacy & Interfaith + * `center_management` - Center & Mosque Management + * `international_media` - International Media & Content + * `specialized_languages` - Specialized Languages & Translation + * `general` - General Religious Studies + LMSCourseLevelEnum: + enum: + - introductory + - intermediate + - advanced + type: string + description: |- + * `introductory` - Introductory + * `intermediate` - Intermediate + * `advanced` - Advanced + LMSLessonTypeEnum: + enum: + - video + - reading + - quiz + - assignment + type: string + description: |- + * `video` - Video Lesson + * `reading` - Reading & Text + * `quiz` - Interactive Quiz + * `assignment` - Practical Assignment + LessonCompleteResponse: + type: object + properties: + message: + type: string + lesson_id: + type: integer + is_completed: + type: boolean + course_progress_percentage: + type: integer + certificate_code: + type: string + nullable: true + required: + - course_progress_percentage + - is_completed + - lesson_id + - message + LessonCompleteResponseRequest: + type: object + properties: + message: + type: string + minLength: 1 + lesson_id: + type: integer + is_completed: + type: boolean + course_progress_percentage: + type: integer + certificate_code: + type: string + nullable: true + minLength: 1 + required: + - course_progress_percentage + - is_completed + - lesson_id + - message + LessonDetail: + type: object + properties: + id: + type: integer + readOnly: true + title: + type: string + title: Lesson Title + maxLength: 255 + slug: + nullable: true + oneOf: + - type: string + pattern: ^[-\w]+$ + maxLength: 280 + - type: string + maxLength: 0 + lesson_type: + $ref: '#/components/schemas/LMSLessonTypeEnum' + duration_minutes: + type: integer + title: Duration (Minutes) + video_url: + nullable: true + title: Video URL (YouTube, Vimeo, HLS, MP4) + oneOf: + - type: string + format: uri + maxLength: 500 + - type: string + maxLength: 0 + content: + type: string + nullable: true + title: Lesson Content / Notes (Markdown) + slides_attachment: + type: string + format: uri + nullable: true + title: Slides / PDF Handout + order: + type: integer + title: Display Order + is_preview: + type: boolean + title: Is Free Preview (Public) + quiz_questions: + type: array + items: + $ref: '#/components/schemas/QuizQuestionStudent' + readOnly: true + is_completed: + type: boolean + readOnly: true + quiz_score: + type: number + format: float + readOnly: true + required: + - id + - is_completed + - quiz_questions + - quiz_score + - title + LessonList: + type: object + properties: + id: + type: integer + readOnly: true + title: + type: string + title: Lesson Title + maxLength: 255 + slug: + nullable: true + oneOf: + - type: string + pattern: ^[-\w]+$ + maxLength: 280 + - type: string + maxLength: 0 + lesson_type: + $ref: '#/components/schemas/LMSLessonTypeEnum' + duration_minutes: + type: integer + title: Duration (Minutes) + order: + type: integer + title: Display Order + is_preview: + type: boolean + title: Is Free Preview (Public) + is_completed: + type: boolean + readOnly: true + required: + - id + - is_completed + - title + LessonListRequest: + type: object + properties: + title: + type: string + minLength: 1 + title: Lesson Title + maxLength: 255 + slug: + nullable: true + oneOf: + - type: string + pattern: ^[-\w]+$ + maxLength: 280 + - type: string + maxLength: 0 + lesson_type: + $ref: '#/components/schemas/LMSLessonTypeEnum' + duration_minutes: + type: integer + title: Duration (Minutes) + order: + type: integer + title: Display Order + is_preview: + type: boolean + title: Is Free Preview (Public) + required: + - title + LocationHistory: + type: object + properties: + id: + type: integer + readOnly: true + lat: + type: number + format: double + nullable: true + title: Latitude + lon: + type: number + format: double + nullable: true + title: Longitude + country: + type: string + nullable: true + maxLength: 255 + city: + type: string + nullable: true + maxLength: 255 + selected_manually: + type: boolean + ip: + type: string + nullable: true + title: IP Address + maxLength: 255 + timezone: + type: string + nullable: true + maxLength: 60 + required: + - id + LocationHistoryRequest: + type: object + properties: + lat: + type: number + format: double + nullable: true + title: Latitude + lon: + type: number + format: double + nullable: true + title: Longitude + country: + type: string + nullable: true + maxLength: 255 + city: + type: string + nullable: true + maxLength: 255 + selected_manually: + type: boolean + ip: + type: string + nullable: true + title: IP Address + maxLength: 255 + timezone: + type: string + nullable: true + maxLength: 60 + LocationTypeEnum: + enum: + - in_person + - online + - hybrid + type: string + description: |- + * `in_person` - In Person + * `online` - Online + * `hybrid` - Hybrid + MapClusterItem: + type: object + properties: + is_cluster: + type: boolean + id: + type: integer + nullable: true + name: + type: string + nullable: true + slug: + type: string + nullable: true + type: + type: string + nullable: true + type_display: + type: string + nullable: true + city: + type: string + nullable: true + country: + type: string + nullable: true + avatar: + type: string + nullable: true + cover_image: + type: string + nullable: true + is_featured: + type: boolean + verification_status: + type: string + nullable: true + follower_count: + type: integer + cluster_id: + type: string + count: + type: integer + lat: + type: number + format: double + lng: + type: number + format: double + type_breakdown: + type: object + additionalProperties: {} + preview_institutions: + type: array + items: {} + required: + - is_cluster + - lat + - lng + MapStats: + type: object + properties: + total_institutions: + type: integer + total_countries: + type: integer + total_cities: + type: integer + type_distribution: + type: object + additionalProperties: {} + verified_count: + type: integer + featured_count: + type: integer + required: + - featured_count + - total_cities + - total_countries + - total_institutions + - type_distribution + - verified_count + MediaAsset: + type: object + properties: + id: + type: integer + readOnly: true + file: + type: string + format: uri + title: Media File + title: + type: string + nullable: true + title: Title / Description + maxLength: 255 + media_type: + $ref: '#/components/schemas/MediaAssetMediaTypeEnum' + media_type_display: + type: string + readOnly: true + file_size: + type: integer + readOnly: true + title: File Size (Bytes) + dimensions: + type: string + nullable: true + title: Dimensions (WxH) + maxLength: 50 + uploaded_by_email: + type: string + format: email + readOnly: true + nullable: true + created_at: + type: string + format: date-time + readOnly: true + required: + - created_at + - file + - file_size + - id + - media_type_display + - uploaded_by_email + MediaAssetMediaTypeEnum: + enum: + - image + - video + - audio + - document + type: string + description: |- + * `image` - Image + * `video` - Video + * `audio` - Audio + * `document` - Document / PDF + MediaAssetRequest: + type: object + properties: + file: + type: string + format: binary + title: Media File + title: + type: string + nullable: true + title: Title / Description + maxLength: 255 + media_type: + $ref: '#/components/schemas/MediaAssetMediaTypeEnum' + dimensions: + type: string + nullable: true + title: Dimensions (WxH) + maxLength: 50 + required: + - file + MeetingCreateRequest: + type: object + properties: + title: + type: string + minLength: 1 + title: Meeting Title / Subject + maxLength: 255 + requester_institution_id: + type: integer + writeOnly: true + recipient_institution_id: + type: integer + writeOnly: true + meeting_date: + type: string + format: date + title: Proposed / Confirmed Date + meeting_time: + type: string + format: time + title: Proposed / Confirmed Time + duration_minutes: + type: integer + title: Duration (Minutes) + meeting_url: + title: Video Meeting Link + oneOf: + - type: string + format: uri + maxLength: 500 + - type: string + maxLength: 0 + agenda: + type: string + title: Meeting Agenda & Topics + required: + - meeting_date + - meeting_time + - recipient_institution_id + - requester_institution_id + - title + MeetingDetail: + type: object + properties: + id: + type: integer + readOnly: true + title: + type: string + title: Meeting Title / Subject + maxLength: 255 + requester_institution: + allOf: + - $ref: '#/components/schemas/InstitutionMeetingMinimal' + readOnly: true + recipient_institution: + allOf: + - $ref: '#/components/schemas/InstitutionMeetingMinimal' + readOnly: true + created_by: + allOf: + - $ref: '#/components/schemas/MeetingUser' + readOnly: true + status: + $ref: '#/components/schemas/MeetingStatusEnum' + meeting_date: + type: string + format: date + title: Proposed / Confirmed Date + meeting_time: + type: string + format: time + title: Proposed / Confirmed Time + duration_minutes: + type: integer + title: Duration (Minutes) + meeting_url: + title: Video Meeting Link + oneOf: + - type: string + format: uri + maxLength: 500 + - type: string + maxLength: 0 + agenda: + type: string + title: Meeting Agenda & Topics + admin_notes: + type: string + title: Internal / Admin Notes + rejection_reason: + type: string + title: Rejection / Decline Reason + created_at: + type: string + format: date-time + readOnly: true + updated_at: + type: string + format: date-time + readOnly: true + required: + - created_at + - created_by + - id + - meeting_date + - meeting_time + - recipient_institution + - requester_institution + - title + - updated_at + MeetingList: + type: object + properties: + id: + type: integer + readOnly: true + title: + type: string + title: Meeting Title / Subject + maxLength: 255 + requester_institution: + allOf: + - $ref: '#/components/schemas/InstitutionMeetingMinimal' + readOnly: true + recipient_institution: + allOf: + - $ref: '#/components/schemas/InstitutionMeetingMinimal' + readOnly: true + created_by: + allOf: + - $ref: '#/components/schemas/MeetingUser' + readOnly: true + status: + $ref: '#/components/schemas/MeetingStatusEnum' + meeting_date: + type: string + format: date + title: Proposed / Confirmed Date + meeting_time: + type: string + format: time + title: Proposed / Confirmed Time + duration_minutes: + type: integer + title: Duration (Minutes) + meeting_url: + title: Video Meeting Link + oneOf: + - type: string + format: uri + maxLength: 500 + - type: string + maxLength: 0 + rejection_reason: + type: string + title: Rejection / Decline Reason + created_at: + type: string + format: date-time + readOnly: true + required: + - created_at + - created_by + - id + - meeting_date + - meeting_time + - recipient_institution + - requester_institution + - title + MeetingStatusEnum: + enum: + - pending + - scheduled + - completed + - cancelled + - declined + type: string + description: |- + * `pending` - Pending + * `scheduled` - Scheduled + * `completed` - Completed + * `cancelled` - Cancelled + * `declined` - Declined + MeetingUpdate: + type: object + properties: + status: + $ref: '#/components/schemas/MeetingStatusEnum' + meeting_date: + type: string + format: date + title: Proposed / Confirmed Date + meeting_time: + type: string + format: time + title: Proposed / Confirmed Time + duration_minutes: + type: integer + title: Duration (Minutes) + meeting_url: + title: Video Meeting Link + oneOf: + - type: string + format: uri + maxLength: 500 + - type: string + maxLength: 0 + agenda: + type: string + title: Meeting Agenda & Topics + admin_notes: + type: string + title: Internal / Admin Notes + rejection_reason: + type: string + description: Required explanation when rejecting/declining a meeting request. + required: + - meeting_date + - meeting_time + MeetingUpdateRequest: + type: object + properties: + status: + $ref: '#/components/schemas/MeetingStatusEnum' + meeting_date: + type: string + format: date + title: Proposed / Confirmed Date + meeting_time: + type: string + format: time + title: Proposed / Confirmed Time + duration_minutes: + type: integer + title: Duration (Minutes) + meeting_url: + title: Video Meeting Link + oneOf: + - type: string + format: uri + maxLength: 500 + - type: string + maxLength: 0 + agenda: + type: string + title: Meeting Agenda & Topics + admin_notes: + type: string + title: Internal / Admin Notes + rejection_reason: + type: string + description: Required explanation when rejecting/declining a meeting request. + required: + - meeting_date + - meeting_time + MeetingUser: + type: object + properties: + id: + type: integer + readOnly: true + email: + nullable: true + title: Email Address + description: User primary email address. + oneOf: + - type: string + format: email + maxLength: 254 + - type: string + maxLength: 0 + fullname: + type: string + nullable: true + title: Full Name + description: Full name of the user. + maxLength: 255 + avatar: + type: string + format: uri + nullable: true + required: + - id + MemberUserMini: + type: object + properties: + id: + type: integer + readOnly: true + email: + type: string + format: email + readOnly: true + nullable: true + title: Email Address + description: User primary email address. + fullname: + type: string + readOnly: true + nullable: true + title: Full Name + description: Full name of the user. + avatar: + type: string + format: uri + nullable: true + readOnly: true + phone_number: + type: string + readOnly: true + nullable: true + description: e.g., +1 555 1234567 + required: + - avatar + - email + - fullname + - id + - phone_number + Method4fbEnum: + enum: + - email + - totp + type: string + description: |- + * `email` - Email OTP + * `totp` - Authenticator App (TOTP) + Notification: + type: object + properties: + id: + type: integer + readOnly: true + title: + type: string + maxLength: 255 + message: + type: string + maxLength: 1024 + is_read: + type: boolean + notification_type: + type: string + nullable: true + maxLength: 50 + action: + type: string + maxLength: 50 + navigate_to: + type: string + nullable: true + maxLength: 255 + created_at: + type: string + format: date-time + readOnly: true + updated_at: + type: string + format: date-time + readOnly: true + required: + - created_at + - id + - message + - title + - updated_at + NotificationTemplate: + type: object + properties: + id: + type: integer + readOnly: true + notification_type: + type: string + maxLength: 50 + name: + type: string + maxLength: 100 + is_active: + type: boolean + title: + type: string + maxLength: 255 + body: + type: string + maxLength: 1024 + placeholders_info: + type: string + nullable: true + title: Allowed Placeholders Description + maxLength: 255 + required: + - id + - name + - notification_type + NotificationTemplateRequest: + type: object + properties: + notification_type: + type: string + minLength: 1 + maxLength: 50 + name: + type: string + minLength: 1 + maxLength: 100 + is_active: + type: boolean + title: + type: string + minLength: 1 + maxLength: 255 + body: + type: string + minLength: 1 + maxLength: 1024 + placeholders_info: + type: string + nullable: true + title: Allowed Placeholders Description + maxLength: 255 + required: + - name + - notification_type + NullEnum: + enum: + - null + PaginatedAdminNotificationList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/AdminNotification' + PaginatedAdminUserList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/AdminUser' + PaginatedChatMessageList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/ChatMessage' + PaginatedChatRoomListList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/ChatRoomList' + PaginatedCourseListList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/CourseList' + PaginatedDigitalCertificateList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/DigitalCertificate' + PaginatedDonationCampaignListList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/DonationCampaignList' + PaginatedEventCalendarItemList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/EventCalendarItem' + PaginatedEventListList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/EventList' + PaginatedEventRegistrationList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/EventRegistration' + PaginatedFormDefinitionListList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/FormDefinitionList' + PaginatedFormResponseList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/FormResponse' + PaginatedInstitutionListList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/InstitutionList' + PaginatedInstitutionMediaList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/InstitutionMedia' + PaginatedInstitutionMemberList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/InstitutionMember' + PaginatedInstitutionTimelineList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/InstitutionTimeline' + PaginatedKanbanColumnList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/KanbanColumn' + PaginatedMapClusterItemList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/MapClusterItem' + PaginatedMediaAssetList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/MediaAsset' + PaginatedMeetingListList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/MeetingList' + PaginatedNotificationList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/Notification' + PaginatedNotificationTemplateList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/NotificationTemplate' + PaginatedPostCategoryList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/PostCategory' + PaginatedPostCommentList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/PostComment' + PaginatedPostListList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/PostList' + PaginatedPostRevisionList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/PostRevision' + PaginatedPostTagList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/PostTag' + PaginatedProjectDocumentList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/ProjectDocument' + PaginatedProjectListList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/ProjectList' + PaginatedQuickAccessLinkList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/QuickAccessLink' + PaginatedRegionList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/Region' + PaginatedRegionalDensityList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/RegionalDensity' + PaginatedRepresentativeReportListList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/RepresentativeReportList' + PaginatedTicketListList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/TicketList' + PaginatedVerificationDocumentList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/VerificationDocument' + PasswordRecoverRequestRequest: + type: object + properties: + email: + type: string + format: email + minLength: 1 + required: + - email + PasswordResetRequestRequest: + type: object + properties: + email: + type: string + format: email + minLength: 1 + token: + type: string + minLength: 1 + new_password: + type: string + minLength: 6 + required: + - email + - new_password + - token + PatchedAdminNotificationRequest: + type: object + properties: + title: + type: string + minLength: 1 + maxLength: 255 + message: + type: string + minLength: 1 + maxLength: 1024 + is_read: + type: boolean + notification_type: + type: string + nullable: true + maxLength: 50 + action: + type: string + minLength: 1 + maxLength: 50 + navigate_to: + type: string + nullable: true + maxLength: 255 + PatchedAdminUserRequest: + type: object + properties: + fullname: + type: string + nullable: true + title: Full Name + description: Full name of the user. + maxLength: 255 + email: + type: string + format: email + minLength: 1 + phone_number: + type: string + nullable: true + description: e.g., +1 555 1234567 + maxLength: 128 + password: + type: string + writeOnly: true + minLength: 1 + avatar: + type: string + nullable: true + minLength: 1 + gender: + nullable: true + oneOf: + - $ref: '#/components/schemas/GenderEnum' + - $ref: '#/components/schemas/BlankEnum' + - $ref: '#/components/schemas/NullEnum' + birthdate: + type: string + format: date + nullable: true + info: + type: string + nullable: true + title: Bio / Info + skill: + type: string + nullable: true + title: Skill / Role + maxLength: 512 + city: + type: string + nullable: true + maxLength: 255 + country: + type: string + nullable: true + maxLength: 255 + fcm: + type: string + nullable: true + title: FCM Token + maxLength: 512 + user_type: + $ref: '#/components/schemas/UserTypeEnum' + is_active: + type: boolean + title: Active + description: Designates whether this user should be treated as active. + is_staff: + type: boolean + is_superuser: + type: boolean + title: Superuser status + description: Designates that this user has all permissions without explicitly + assigning them. + two_factor_enabled: + type: boolean + two_factor_method: + allOf: + - $ref: '#/components/schemas/TwoFactorMethodEnum' + title: Preferred 2FA Method + require_2fa: + type: boolean + default: false + custom_permissions: + title: Custom Permission Overrides + description: 'Dictionary of permission overrides: {permission_name: True/False}' + PatchedCourseCreateUpdateRequest: + type: object + properties: + title: + type: string + minLength: 1 + title: Course Title + maxLength: 255 + category: + $ref: '#/components/schemas/LMSCourseCategoryEnum' + language: + $ref: '#/components/schemas/ContentLanguageEnum' + level: + allOf: + - $ref: '#/components/schemas/LMSCourseLevelEnum' + title: Difficulty Level + cover_image: + type: string + format: binary + nullable: true + instructor_name: + type: string + minLength: 1 + maxLength: 255 + instructor_title: + type: string + nullable: true + title: Instructor Title / Specialty + maxLength: 255 + instructor_avatar: + type: string + format: binary + nullable: true + institution: + type: integer + nullable: true + title: Partner Institution / Center + organization_name: + type: string + nullable: true + title: Organization / Academy Name + maxLength: 255 + description: + type: string + minLength: 1 + title: Course Description & Syllabus Summary + duration_hours: + type: number + format: double + title: Duration (Hours) + is_published: + type: boolean + is_featured: + type: boolean + PatchedEventCreateUpdateRequest: + type: object + properties: + title: + type: string + minLength: 1 + title: Event Title + maxLength: 255 + description: + type: string + minLength: 1 + title: Event Description + organizer_id: + type: integer + writeOnly: true + category: + $ref: '#/components/schemas/EventCategoryEnum' + event_date: + type: string + format: date + start_time: + type: string + format: time + end_time: + type: string + format: time + nullable: true + speaker_name: + type: string + maxLength: 255 + speaker_title: + type: string + title: Speaker Title / Affiliation + maxLength: 255 + speaker_avatar: + type: string + format: binary + nullable: true + location_type: + $ref: '#/components/schemas/LocationTypeEnum' + venue_address: + type: string + maxLength: 500 + online_meeting_url: + title: Online Meeting Link + oneOf: + - type: string + format: uri + maxLength: 200 + - type: string + maxLength: 0 + cover_image: + type: string + format: binary + nullable: true + capacity: + type: integer + title: Capacity Limit + is_active: + type: boolean + is_featured: + type: boolean + language: + $ref: '#/components/schemas/ContentLanguageEnum' + tags: {} + PatchedFormResponseReviewRequest: + type: object + properties: + workflow_status: + $ref: '#/components/schemas/WorkflowStatusEnum' + admin_notes: + type: string + PatchedInstitutionCreateUpdateRequest: + type: object + properties: + name: + type: string + minLength: 1 + title: Institution Name + description: Official name of the mosque, center, or institute. + maxLength: 255 + type: + allOf: + - $ref: '#/components/schemas/InstitutionTypeEnum' + title: Institution Type + region: + type: integer + description: Region ID this institution belongs to (required). + country: + type: string + minLength: 1 + maxLength: 255 + city: + type: string + minLength: 1 + maxLength: 255 + address: + type: string + nullable: true + title: Full Address + latitude: + type: number + format: double + nullable: true + longitude: + type: number + format: double + nullable: true + description: + type: string + nullable: true + title: Description & Background + website: + nullable: true + oneOf: + - type: string + format: uri + maxLength: 500 + - type: string + maxLength: 0 + email: + nullable: true + title: Official Email + oneOf: + - type: string + format: email + maxLength: 254 + - type: string + maxLength: 0 + phone: + type: string + nullable: true + title: Official Phone + maxLength: 50 + established_year: + type: integer + nullable: true + title: Established Year (CE) + cover_image: + type: string + format: binary + nullable: true + avatar: + type: string + format: binary + nullable: true + title: Logo / Avatar + social_media: + title: Social Media Links + description: 'Dictionary with keys: facebook, twitter, instagram, linkedin, + youtube, telegram' + working_hours: + title: Working Hours / Prayer Times Schedule + tags: + title: Tags & Specializations + is_featured: + type: boolean + PatchedKanbanTaskCreateUpdateRequest: + type: object + properties: + column: + type: integer + title: Kanban Column + title: + type: string + minLength: 1 + title: Task Title + maxLength: 255 + description: + type: string + nullable: true + title: Task Description + priority: + $ref: '#/components/schemas/TicketPriorityEnum' + assignee: + type: integer + nullable: true + title: Assignee User + due_date: + type: string + format: date + nullable: true + labels: + title: Task Labels + order: + type: integer + title: Display Order within Column + PatchedMeetingUpdateRequest: + type: object + properties: + status: + $ref: '#/components/schemas/MeetingStatusEnum' + meeting_date: + type: string + format: date + title: Proposed / Confirmed Date + meeting_time: + type: string + format: time + title: Proposed / Confirmed Time + duration_minutes: + type: integer + title: Duration (Minutes) + meeting_url: + title: Video Meeting Link + oneOf: + - type: string + format: uri + maxLength: 500 + - type: string + maxLength: 0 + agenda: + type: string + title: Meeting Agenda & Topics + admin_notes: + type: string + title: Internal / Admin Notes + rejection_reason: + type: string + description: Required explanation when rejecting/declining a meeting request. + PatchedNotificationTemplateRequest: + type: object + properties: + notification_type: + type: string + minLength: 1 + maxLength: 50 + name: + type: string + minLength: 1 + maxLength: 100 + is_active: + type: boolean + title: + type: string + minLength: 1 + maxLength: 255 + body: + type: string + minLength: 1 + maxLength: 1024 + placeholders_info: + type: string + nullable: true + title: Allowed Placeholders Description + maxLength: 255 + PatchedPostCreateUpdateRequest: + type: object + properties: + title: + type: string + minLength: 1 + title: Post Title + maxLength: 255 + post_type: + $ref: '#/components/schemas/PostTypeEnum' + author_type: + allOf: + - $ref: '#/components/schemas/AuthorTypeEnum' + description: |- + Indicates whether the post is authored as an individual user or on behalf of an institution. + + * `user` - Individual User + * `institution` - Institution + category: + type: integer + nullable: true + language: + $ref: '#/components/schemas/ContentLanguageEnum' + status: + allOf: + - $ref: '#/components/schemas/CMSPostStatusEnum' + title: Publication Status + rejection_reason: + type: string + description: Reason provided by editor when rejecting a post. + institution: + type: integer + nullable: true + title: Associated Institution + description: Institution on whose behalf the post is published if author_type + is institution. + author_role_label: + type: string + nullable: true + description: e.g. Senior Researcher, Center Director, Diplomatic Envoy + maxLength: 150 + author_custom_avatar: + type: string + format: binary + nullable: true + title: Custom Author Avatar + featured_image: + type: string + format: binary + nullable: true + title: Featured Cover Image + excerpt: + type: string + nullable: true + title: Excerpt / Summary + description: Brief summary for search results and cards. + content: + type: string + minLength: 1 + title: Rich Text / Markdown Content + tags: {} + is_featured: + type: boolean + publish_date: + type: string + format: date-time + PatchedProjectCreateUpdateRequest: + type: object + properties: + title: + type: string + minLength: 1 + title: Project Title + maxLength: 255 + description: + type: string + nullable: true + title: Project Description & Objectives + owner_institution: + type: integer + title: Lead / Owner Institution + collaborating_institutions: + type: array + items: + type: integer + title: Collaborating Institutions + category: + type: string + nullable: true + title: Project Category + description: e.g. Cultural Diplomacy, Educational Academy, Interfaith Dialogue, + Humanitarian Relief + maxLength: 100 + status: + allOf: + - $ref: '#/components/schemas/ProjectStatusEnum' + title: Project Status + progress_percentage: + type: integer + title: Progress Percentage (0-100) + budget: + type: string + nullable: true + title: Estimated Budget + description: e.g. $50,000 or €35,000 + maxLength: 100 + team_size: + type: integer + cover_image: + type: string + format: binary + nullable: true + start_date: + type: string + format: date + nullable: true + estimated_end_date: + type: string + format: date + nullable: true + actual_end_date: + type: string + format: date + nullable: true + title: Actual Completion Date + PatchedQuickAccessLinkRequest: + type: object + properties: + title: + type: string + minLength: 1 + title: عنوان خدمت + maxLength: 255 + url: + type: string + minLength: 1 + title: نشانی اینترنتی یا مسیر + maxLength: 500 + description: + type: string + title: توضیح مختصر + maxLength: 500 + icon: + type: string + minLength: 1 + title: شناسه آیکون + maxLength: 50 + color: + allOf: + - $ref: '#/components/schemas/ColorEnum' + title: طیف رنگی + category: + type: string + title: برچسب یا دسته‌بندی + maxLength: 100 + is_live: + type: boolean + title: نشانگر پخش زنده + is_external: + type: boolean + title: باز شدن در برگه جدید + order: + type: integer + title: ترتیب نمایش + is_active: + type: boolean + title: فعال بودن + PatchedRegionRequest: + type: object + properties: + name: + type: string + minLength: 1 + title: Region Name + description: Name of the geographical or administrative region (e.g. Middle + East, Europe). + maxLength: 255 + code: + type: string + nullable: true + title: Region Code + description: Short code such as MENA, EU, NA, etc. + maxLength: 50 + description: + type: string + nullable: true + is_active: + type: boolean + PatchedRepresentativeReportReviewRequest: + type: object + properties: + status: + $ref: '#/components/schemas/DiplomacyReportStatusEnum' + feedback_notes: + type: string + PatchedTicketListRequest: + type: object + properties: + tracking_code: + type: string + description: e.g. TK-RAZAVI-2026-AB12CD + maxLength: 100 + subject: + type: string + minLength: 1 + title: Subject / Title + maxLength: 255 + category: + $ref: '#/components/schemas/TicketCategoryEnum' + priority: + $ref: '#/components/schemas/TicketPriorityEnum' + status: + $ref: '#/components/schemas/TicketStatusEnum' + institution: + type: integer + nullable: true + title: Affiliated Institution + satisfaction_rating: + type: integer + nullable: true + title: Satisfaction Rating (1-5 Stars) + closed_at: + type: string + format: date-time + nullable: true + PatchedUserMeUpdateRequest: + type: object + properties: + fullname: + type: string + nullable: true + title: Full Name + description: Full name of the user. + maxLength: 255 + first_name: + type: string + last_name: + type: string + name: + type: string + writeOnly: true + phone_number: + type: string + nullable: true + description: e.g., +1 555 1234567 + maxLength: 128 + phone: + type: string + writeOnly: true + avatar: + type: string + format: binary + nullable: true + bio: + type: string + nullable: true + languages: {} + preferred_language: + type: string + writeOnly: true + skills: {} + expertise: + type: array + items: + type: string + minLength: 1 + writeOnly: true + title: + type: string + rank: + type: string + institution: + type: string + writeOnly: true + institution_name: + type: string + website: + type: string + address: + type: string + country: + type: string + nullable: true + maxLength: 255 + city: + type: string + nullable: true + maxLength: 255 + gender: + nullable: true + oneOf: + - $ref: '#/components/schemas/GenderEnum' + - $ref: '#/components/schemas/BlankEnum' + - $ref: '#/components/schemas/NullEnum' + birthdate: + type: string + format: date + nullable: true + two_factor_enabled: + type: boolean + two_factor_method: + $ref: '#/components/schemas/TwoFactorMethodEnum' + PatchedUserProfileRequest: + type: object + properties: + fcm: + type: string + minLength: 1 + description: Firebase Cloud Messaging token. + first_name: + type: string + last_name: + type: string + fullname: + type: string + avatar: + type: string + nullable: true + minLength: 1 + phone_number: + type: string + nullable: true + description: e.g., +1 555 1234567 + maxLength: 128 + password: + type: string + writeOnly: true + minLength: 1 + city: + type: string + nullable: true + maxLength: 255 + country: + type: string + nullable: true + maxLength: 255 + birthdate: + type: string + format: date + nullable: true + gender: + allOf: + - $ref: '#/components/schemas/GenderEnum' + description: |- + Select the user's gender. + + * `male` - Male + * `female` - Female + * `other` - Other + PaymentStatusEnum: + enum: + - completed + - pending + - failed + type: string + description: |- + * `completed` - Completed + * `pending` - Pending + * `failed` - Failed + PostAuthorDisplay: + type: object + properties: + type: + type: string + description: 'Author type: ''user'' or ''institution''' + id: + type: integer + nullable: true + description: ID of the user or institution + name: + type: string + description: Name of the author or institution + slug: + type: string + nullable: true + avatar: + type: string + nullable: true + role_label: + type: string + nullable: true + submitted_by: + type: object + additionalProperties: {} + nullable: true + required: + - id + - name + - type + PostAuthorMini: + type: object + properties: + id: + type: integer + readOnly: true + email: + type: string + format: email + readOnly: true + nullable: true + title: Email Address + description: User primary email address. + fullname: + type: string + readOnly: true + nullable: true + title: Full Name + description: Full name of the user. + avatar: + type: string + format: uri + nullable: true + readOnly: true + required: + - avatar + - email + - fullname + - id + PostCategory: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + title: Category Name + maxLength: 255 + slug: + type: string + readOnly: true + title: Slug / URL Identifier + pattern: ^[-\w]+$ + language: + $ref: '#/components/schemas/PostCategoryLanguageEnum' + description: + type: string + nullable: true + title: Category Description + created_at: + type: string + format: date-time + readOnly: true + posts_count: + type: integer + readOnly: true + required: + - created_at + - id + - name + - posts_count + - slug + PostCategoryLanguageEnum: + enum: + - fa + - en + - ar + - ur + - ru + type: string + description: |- + * `fa` - Persian + * `en` - English + * `ar` - Arabic + * `ur` - Urdu + * `ru` - Russian + PostCategoryRequest: + type: object + properties: + name: + type: string + minLength: 1 + title: Category Name + maxLength: 255 + language: + $ref: '#/components/schemas/PostCategoryLanguageEnum' + description: + type: string + nullable: true + title: Category Description + required: + - name + PostComment: + type: object + properties: + id: + type: integer + readOnly: true + post: + type: integer + readOnly: true + user: + allOf: + - $ref: '#/components/schemas/PostAuthorMini' + readOnly: true + author_name: + type: string + nullable: true + maxLength: 255 + author_email: + nullable: true + oneOf: + - type: string + format: email + maxLength: 254 + - type: string + maxLength: 0 + content: + type: string + title: Comment Content + status: + allOf: + - $ref: '#/components/schemas/CMSCommentStatusEnum' + readOnly: true + title: Moderation Status + status_display: + type: string + readOnly: true + created_at: + type: string + format: date-time + readOnly: true + required: + - content + - created_at + - id + - post + - status + - status_display + - user + PostCommentCreateRequest: + type: object + properties: + content: + type: string + minLength: 1 + author_name: + type: string + maxLength: 255 + author_email: + oneOf: + - type: string + format: email + - type: string + maxLength: 0 + required: + - content + PostCreateUpdateRequest: + type: object + properties: + title: + type: string + minLength: 1 + title: Post Title + maxLength: 255 + post_type: + $ref: '#/components/schemas/PostTypeEnum' + author_type: + allOf: + - $ref: '#/components/schemas/AuthorTypeEnum' + description: |- + Indicates whether the post is authored as an individual user or on behalf of an institution. + + * `user` - Individual User + * `institution` - Institution + category: + type: integer + nullable: true + language: + $ref: '#/components/schemas/ContentLanguageEnum' + status: + allOf: + - $ref: '#/components/schemas/CMSPostStatusEnum' + title: Publication Status + rejection_reason: + type: string + description: Reason provided by editor when rejecting a post. + institution: + type: integer + nullable: true + title: Associated Institution + description: Institution on whose behalf the post is published if author_type + is institution. + author_role_label: + type: string + nullable: true + description: e.g. Senior Researcher, Center Director, Diplomatic Envoy + maxLength: 150 + author_custom_avatar: + type: string + format: binary + nullable: true + title: Custom Author Avatar + featured_image: + type: string + format: binary + nullable: true + title: Featured Cover Image + excerpt: + type: string + nullable: true + title: Excerpt / Summary + description: Brief summary for search results and cards. + content: + type: string + minLength: 1 + title: Rich Text / Markdown Content + tags: {} + is_featured: + type: boolean + publish_date: + type: string + format: date-time + required: + - content + - title + PostDetail: + type: object + properties: + id: + type: integer + readOnly: true + title: + type: string + title: Post Title + maxLength: 255 + slug: + type: string + readOnly: true + nullable: true + title: Slug / URL Identifier + pattern: ^[-\w]+$ + post_type: + $ref: '#/components/schemas/PostTypeEnum' + post_type_display: + type: string + readOnly: true + author_type: + allOf: + - $ref: '#/components/schemas/AuthorTypeEnum' + description: |- + Indicates whether the post is authored as an individual user or on behalf of an institution. + + * `user` - Individual User + * `institution` - Institution + author_type_display: + type: string + readOnly: true + author_display: + allOf: + - $ref: '#/components/schemas/PostAuthorDisplay' + readOnly: true + category: + allOf: + - $ref: '#/components/schemas/PostCategory' + readOnly: true + language: + $ref: '#/components/schemas/ContentLanguageEnum' + status: + allOf: + - $ref: '#/components/schemas/CMSPostStatusEnum' + title: Publication Status + rejection_reason: + type: string + description: Reason provided by editor when rejecting a post. + author: + allOf: + - $ref: '#/components/schemas/PostAuthorMini' + readOnly: true + institution: + allOf: + - $ref: '#/components/schemas/PostInstitutionMini' + readOnly: true + author_role_label: + type: string + nullable: true + description: e.g. Senior Researcher, Center Director, Diplomatic Envoy + maxLength: 150 + author_custom_avatar: + type: string + format: uri + nullable: true + title: Custom Author Avatar + featured_image: + type: string + format: uri + nullable: true + title: Featured Cover Image + excerpt: + type: string + nullable: true + title: Excerpt / Summary + description: Brief summary for search results and cards. + content: + type: string + title: Rich Text / Markdown Content + tags: {} + reading_time_minutes: + type: integer + readOnly: true + title: Reading Time (Minutes) + views_count: + type: integer + readOnly: true + likes_count: + type: integer + readOnly: true + comments_count: + type: integer + readOnly: true + is_featured: + type: boolean + publish_date: + type: string + format: date-time + is_liked: + type: boolean + readOnly: true + has_pending_revision: + type: boolean + readOnly: true + pending_revision: + allOf: + - $ref: '#/components/schemas/PostRevision' + readOnly: true + comments: + type: array + items: + $ref: '#/components/schemas/PostComment' + readOnly: true + reviewed_by: + allOf: + - $ref: '#/components/schemas/PostAuthorMini' + readOnly: true + reviewed_at: + type: string + format: date-time + readOnly: true + nullable: true + created_at: + type: string + format: date-time + readOnly: true + updated_at: + type: string + format: date-time + readOnly: true + required: + - author + - author_display + - author_type_display + - category + - comments + - comments_count + - content + - created_at + - has_pending_revision + - id + - institution + - is_liked + - likes_count + - pending_revision + - post_type_display + - reading_time_minutes + - reviewed_at + - reviewed_by + - slug + - title + - updated_at + - views_count + PostDetailRequest: + type: object + properties: + title: + type: string + minLength: 1 + title: Post Title + maxLength: 255 + post_type: + $ref: '#/components/schemas/PostTypeEnum' + author_type: + allOf: + - $ref: '#/components/schemas/AuthorTypeEnum' + description: |- + Indicates whether the post is authored as an individual user or on behalf of an institution. + + * `user` - Individual User + * `institution` - Institution + language: + $ref: '#/components/schemas/ContentLanguageEnum' + status: + allOf: + - $ref: '#/components/schemas/CMSPostStatusEnum' + title: Publication Status + rejection_reason: + type: string + description: Reason provided by editor when rejecting a post. + author_role_label: + type: string + nullable: true + description: e.g. Senior Researcher, Center Director, Diplomatic Envoy + maxLength: 150 + author_custom_avatar: + type: string + format: binary + nullable: true + title: Custom Author Avatar + featured_image: + type: string + format: binary + nullable: true + title: Featured Cover Image + excerpt: + type: string + nullable: true + title: Excerpt / Summary + description: Brief summary for search results and cards. + content: + type: string + minLength: 1 + title: Rich Text / Markdown Content + tags: {} + is_featured: + type: boolean + publish_date: + type: string + format: date-time + required: + - content + - title + PostInstitutionMini: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + readOnly: true + title: Institution Name + description: Official name of the mosque, center, or institute. + slug: + type: string + readOnly: true + nullable: true + title: Slug / URL Identifier + pattern: ^[-\w]+$ + type: + allOf: + - $ref: '#/components/schemas/InstitutionTypeEnum' + readOnly: true + title: Institution Type + type_display: + type: string + readOnly: true + avatar: + type: string + format: uri + nullable: true + readOnly: true + title: Logo / Avatar + city: + type: string + readOnly: true + country: + type: string + readOnly: true + required: + - avatar + - city + - country + - id + - name + - slug + - type + - type_display + PostLikeResponse: + type: object + properties: + is_liked: + type: boolean + likes_count: + type: integer + message: + type: string + required: + - is_liked + - likes_count + - message + PostLikeResponseRequest: + type: object + properties: + is_liked: + type: boolean + likes_count: + type: integer + message: + type: string + minLength: 1 + required: + - is_liked + - likes_count + - message + PostList: + type: object + properties: + id: + type: integer + readOnly: true + title: + type: string + readOnly: true + title: Post Title + slug: + type: string + readOnly: true + nullable: true + title: Slug / URL Identifier + pattern: ^[-\w]+$ + post_type: + allOf: + - $ref: '#/components/schemas/PostTypeEnum' + readOnly: true + post_type_display: + type: string + readOnly: true + author_type: + allOf: + - $ref: '#/components/schemas/AuthorTypeEnum' + readOnly: true + description: |- + Indicates whether the post is authored as an individual user or on behalf of an institution. + + * `user` - Individual User + * `institution` - Institution + author_type_display: + type: string + readOnly: true + author_display: + allOf: + - $ref: '#/components/schemas/PostAuthorDisplay' + readOnly: true + category: + type: integer + readOnly: true + nullable: true + category_name: + type: string + readOnly: true + nullable: true + language: + allOf: + - $ref: '#/components/schemas/ContentLanguageEnum' + readOnly: true + status: + allOf: + - $ref: '#/components/schemas/CMSPostStatusEnum' + readOnly: true + title: Publication Status + rejection_reason: + type: string + readOnly: true + description: Reason provided by editor when rejecting a post. + author: + allOf: + - $ref: '#/components/schemas/PostAuthorMini' + readOnly: true + institution: + allOf: + - $ref: '#/components/schemas/PostInstitutionMini' + readOnly: true + author_role_label: + type: string + readOnly: true + nullable: true + description: e.g. Senior Researcher, Center Director, Diplomatic Envoy + author_custom_avatar: + type: string + format: uri + nullable: true + readOnly: true + title: Custom Author Avatar + featured_image: + type: string + format: uri + nullable: true + readOnly: true + title: Featured Cover Image + excerpt: + type: string + readOnly: true + nullable: true + title: Excerpt / Summary + description: Brief summary for search results and cards. + tags: + readOnly: true + reading_time_minutes: + type: integer + readOnly: true + title: Reading Time (Minutes) + views_count: + type: integer + readOnly: true + likes_count: + type: integer + readOnly: true + comments_count: + type: integer + readOnly: true + is_featured: + type: boolean + readOnly: true + publish_date: + type: string + format: date-time + readOnly: true + is_liked: + type: boolean + readOnly: true + has_pending_revision: + type: boolean + readOnly: true + reviewed_at: + type: string + format: date-time + readOnly: true + nullable: true + created_at: + type: string + format: date-time + readOnly: true + required: + - author + - author_custom_avatar + - author_display + - author_role_label + - author_type + - author_type_display + - category + - category_name + - comments_count + - created_at + - excerpt + - featured_image + - has_pending_revision + - id + - institution + - is_featured + - is_liked + - language + - likes_count + - post_type + - post_type_display + - publish_date + - reading_time_minutes + - rejection_reason + - reviewed_at + - slug + - status + - tags + - title + - views_count + PostRejectRequestRequest: + type: object + properties: + reason: + type: string + minLength: 1 + description: Explanation for rejection + required: + - reason + PostRevision: + type: object + properties: + id: + type: integer + readOnly: true + post: + type: integer + readOnly: true + title: Original Post + author: + allOf: + - $ref: '#/components/schemas/PostAuthorMini' + readOnly: true + title: + type: string + readOnly: true + title: Revised Title + excerpt: + type: string + readOnly: true + title: Revised Excerpt + content: + type: string + readOnly: true + title: Revised Content + tags: + readOnly: true + title: Revised Tags + featured_image: + type: string + format: uri + nullable: true + readOnly: true + title: Revised Featured Image + category: + type: integer + readOnly: true + nullable: true + title: Revised Category + category_name: + type: string + readOnly: true + nullable: true + status: + allOf: + - $ref: '#/components/schemas/PostRevisionStatusEnum' + readOnly: true + title: Revision Status + status_display: + type: string + readOnly: true + rejection_reason: + type: string + readOnly: true + reviewed_by: + allOf: + - $ref: '#/components/schemas/PostAuthorMini' + readOnly: true + reviewed_at: + type: string + format: date-time + readOnly: true + nullable: true + created_at: + type: string + format: date-time + readOnly: true + title: Submitted At + updated_at: + type: string + format: date-time + readOnly: true + required: + - author + - category + - category_name + - content + - created_at + - excerpt + - featured_image + - id + - post + - rejection_reason + - reviewed_at + - reviewed_by + - status + - status_display + - tags + - title + - updated_at + PostRevisionApproveResponse: + type: object + properties: + detail: + type: string + post: + $ref: '#/components/schemas/PostDetail' + revision: + $ref: '#/components/schemas/PostRevision' + required: + - detail + - post + - revision + PostRevisionRejectRequestRequest: + type: object + properties: + reason: + type: string + minLength: 1 + description: Explanation for rejection + required: + - reason + PostRevisionRejectResponse: + type: object + properties: + detail: + type: string + post: + $ref: '#/components/schemas/PostDetail' + revision: + $ref: '#/components/schemas/PostRevision' + required: + - detail + - post + - revision + PostRevisionStatusEnum: + enum: + - pending + - approved + - rejected + type: string + description: |- + * `pending` - Pending Review + * `approved` - Approved & Merged + * `rejected` - Rejected + PostTag: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + title: Tag Name + maxLength: 100 + slug: + type: string + readOnly: true + pattern: ^[-\w]+$ + required: + - id + - name + - slug + PostTypeEnum: + enum: + - news + - article + - report + - interview + type: string + description: |- + * `news` - News & Announcements + * `article` - Analytical Article + * `report` - Field & Diplomatic Report + * `interview` - Expert Interview + ProjectCreateUpdateRequest: + type: object + properties: + title: + type: string + minLength: 1 + title: Project Title + maxLength: 255 + description: + type: string + nullable: true + title: Project Description & Objectives + owner_institution: + type: integer + title: Lead / Owner Institution + collaborating_institutions: + type: array + items: + type: integer + title: Collaborating Institutions + category: + type: string + nullable: true + title: Project Category + description: e.g. Cultural Diplomacy, Educational Academy, Interfaith Dialogue, + Humanitarian Relief + maxLength: 100 + status: + allOf: + - $ref: '#/components/schemas/ProjectStatusEnum' + title: Project Status + progress_percentage: + type: integer + title: Progress Percentage (0-100) + budget: + type: string + nullable: true + title: Estimated Budget + description: e.g. $50,000 or €35,000 + maxLength: 100 + team_size: + type: integer + cover_image: + type: string + format: binary + nullable: true + start_date: + type: string + format: date + nullable: true + estimated_end_date: + type: string + format: date + nullable: true + actual_end_date: + type: string + format: date + nullable: true + title: Actual Completion Date + required: + - owner_institution + - title + ProjectDetail: + type: object + properties: + id: + type: integer + readOnly: true + title: + type: string + title: Project Title + maxLength: 255 + slug: + type: string + readOnly: true + nullable: true + title: Slug / URL Identifier + pattern: ^[-\w]+$ + description: + type: string + nullable: true + title: Project Description & Objectives + owner_institution: + allOf: + - $ref: '#/components/schemas/ProjectInstitutionMini' + readOnly: true + collaborating_institutions: + type: array + items: + $ref: '#/components/schemas/ProjectInstitutionMini' + readOnly: true + category: + type: string + nullable: true + title: Project Category + description: e.g. Cultural Diplomacy, Educational Academy, Interfaith Dialogue, + Humanitarian Relief + maxLength: 100 + status: + allOf: + - $ref: '#/components/schemas/ProjectStatusEnum' + title: Project Status + status_display: + type: string + readOnly: true + progress_percentage: + type: integer + title: Progress Percentage (0-100) + budget: + type: string + nullable: true + title: Estimated Budget + description: e.g. $50,000 or €35,000 + maxLength: 100 + team_size: + type: integer + cover_image: + type: string + format: uri + nullable: true + start_date: + type: string + format: date + nullable: true + estimated_end_date: + type: string + format: date + nullable: true + actual_end_date: + type: string + format: date + nullable: true + title: Actual Completion Date + columns: + type: array + items: + $ref: '#/components/schemas/KanbanColumn' + readOnly: true + documents: + type: array + items: + $ref: '#/components/schemas/ProjectDocument' + readOnly: true + can_edit: + type: boolean + readOnly: true + created_by_email: + type: string + format: email + readOnly: true + nullable: true + created_at: + type: string + format: date-time + readOnly: true + updated_at: + type: string + format: date-time + readOnly: true + required: + - can_edit + - collaborating_institutions + - columns + - created_at + - created_by_email + - documents + - id + - owner_institution + - slug + - status_display + - title + - updated_at + ProjectDocument: + type: object + properties: + id: + type: integer + readOnly: true + project: + type: integer + readOnly: true + title: + type: string + title: Document Title + maxLength: 255 + file: + type: string + format: uri + title: Document File + doc_type: + allOf: + - $ref: '#/components/schemas/DocTypeEnum' + title: Document Type + doc_type_display: + type: string + readOnly: true + uploaded_by_email: + type: string + format: email + readOnly: true + nullable: true + uploaded_at: + type: string + format: date-time + readOnly: true + required: + - doc_type_display + - file + - id + - project + - title + - uploaded_at + - uploaded_by_email + ProjectDocumentRequest: + type: object + properties: + title: + type: string + minLength: 1 + title: Document Title + maxLength: 255 + file: + type: string + format: binary + title: Document File + doc_type: + allOf: + - $ref: '#/components/schemas/DocTypeEnum' + title: Document Type + required: + - file + - title + ProjectInstitutionMini: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + readOnly: true + title: Institution Name + description: Official name of the mosque, center, or institute. + slug: + type: string + readOnly: true + nullable: true + title: Slug / URL Identifier + pattern: ^[-\w]+$ + type: + allOf: + - $ref: '#/components/schemas/InstitutionTypeEnum' + readOnly: true + title: Institution Type + type_display: + type: string + readOnly: true + avatar: + type: string + format: uri + nullable: true + readOnly: true + title: Logo / Avatar + city: + type: string + readOnly: true + country: + type: string + readOnly: true + required: + - avatar + - city + - country + - id + - name + - slug + - type + - type_display + ProjectList: + type: object + properties: + id: + type: integer + readOnly: true + title: + type: string + readOnly: true + title: Project Title + slug: + type: string + readOnly: true + nullable: true + title: Slug / URL Identifier + pattern: ^[-\w]+$ + owner_institution: + allOf: + - $ref: '#/components/schemas/ProjectInstitutionMini' + readOnly: true + collaborating_institutions_count: + type: integer + readOnly: true + category: + type: string + readOnly: true + nullable: true + title: Project Category + description: e.g. Cultural Diplomacy, Educational Academy, Interfaith Dialogue, + Humanitarian Relief + status: + allOf: + - $ref: '#/components/schemas/ProjectStatusEnum' + readOnly: true + title: Project Status + status_display: + type: string + readOnly: true + progress_percentage: + type: integer + readOnly: true + title: Progress Percentage (0-100) + budget: + type: string + readOnly: true + nullable: true + title: Estimated Budget + description: e.g. $50,000 or €35,000 + team_size: + type: integer + readOnly: true + cover_image: + type: string + format: uri + nullable: true + readOnly: true + start_date: + type: string + format: date + readOnly: true + nullable: true + estimated_end_date: + type: string + format: date + readOnly: true + nullable: true + tasks_count: + type: integer + readOnly: true + completed_tasks_count: + type: integer + readOnly: true + created_at: + type: string + format: date-time + readOnly: true + required: + - budget + - category + - collaborating_institutions_count + - completed_tasks_count + - cover_image + - created_at + - estimated_end_date + - id + - owner_institution + - progress_percentage + - slug + - start_date + - status + - status_display + - tasks_count + - team_size + - title + ProjectStatusEnum: + enum: + - planning + - active + - on_hold + - completed + type: string + description: |- + * `planning` - Planning + * `active` - Active / In Progress + * `on_hold` - On Hold + * `completed` - Completed + ProjectUserMini: + type: object + properties: + id: + type: integer + readOnly: true + email: + type: string + format: email + readOnly: true + nullable: true + title: Email Address + description: User primary email address. + fullname: + type: string + readOnly: true + nullable: true + title: Full Name + description: Full name of the user. + avatar: + type: string + format: uri + nullable: true + readOnly: true + required: + - avatar + - email + - fullname + - id + ProjectsAnalytics: + type: object + properties: + total_projects: + type: integer + active_projects: + type: integer + completed_projects: + type: integer + required: + - active_projects + - completed_projects + - total_projects + PublicCertificateVerify: + type: object + properties: + certificate_code: + type: string + uuid: + type: string + format: uuid + recipient_name: + type: string + course_title: + type: string + organization: + type: string + nullable: true + issue_date: + type: string + format: date + final_score: + type: number + format: double + is_valid: + type: boolean + required: + - certificate_code + - course_title + - final_score + - is_valid + - issue_date + - organization + - recipient_name + - uuid + QuickAccessLink: + type: object + properties: + id: + type: integer + readOnly: true + institution: + type: integer + readOnly: true + title: مؤسسه مربوطه + institution_id: + type: integer + readOnly: true + title: + type: string + title: عنوان خدمت + maxLength: 255 + url: + type: string + title: نشانی اینترنتی یا مسیر + maxLength: 500 + description: + type: string + title: توضیح مختصر + maxLength: 500 + icon: + type: string + title: شناسه آیکون + maxLength: 50 + color: + allOf: + - $ref: '#/components/schemas/ColorEnum' + title: طیف رنگی + category: + type: string + title: برچسب یا دسته‌بندی + maxLength: 100 + is_live: + type: boolean + title: نشانگر پخش زنده + is_external: + type: boolean + title: باز شدن در برگه جدید + order: + type: integer + title: ترتیب نمایش + is_active: + type: boolean + title: فعال بودن + created_at: + type: string + format: date-time + readOnly: true + title: تاریخ ایجاد + updated_at: + type: string + format: date-time + readOnly: true + title: آخرین بروزرسانی + required: + - created_at + - id + - institution + - institution_id + - title + - updated_at + - url + QuickAccessLinkReorderRequest: + type: object + properties: + order: + type: array + items: + type: integer + description: Ordered list of QuickAccessLink IDs. + required: + - order + QuickAccessLinkRequest: + type: object + properties: + title: + type: string + minLength: 1 + title: عنوان خدمت + maxLength: 255 + url: + type: string + minLength: 1 + title: نشانی اینترنتی یا مسیر + maxLength: 500 + description: + type: string + title: توضیح مختصر + maxLength: 500 + icon: + type: string + minLength: 1 + title: شناسه آیکون + maxLength: 50 + color: + allOf: + - $ref: '#/components/schemas/ColorEnum' + title: طیف رنگی + category: + type: string + title: برچسب یا دسته‌بندی + maxLength: 100 + is_live: + type: boolean + title: نشانگر پخش زنده + is_external: + type: boolean + title: باز شدن در برگه جدید + order: + type: integer + title: ترتیب نمایش + is_active: + type: boolean + title: فعال بودن + required: + - title + - url + QuizQuestionResultItem: + type: object + properties: + question_id: + type: integer + is_correct: + type: boolean + selected_option: + type: integer + correct_option: + type: integer + explanation: + type: string + nullable: true + required: + - correct_option + - explanation + - is_correct + - question_id + - selected_option + QuizQuestionStudent: + type: object + properties: + id: + type: integer + readOnly: true + question_text: + type: string + options: + title: Options Array + description: 'List of multiple-choice strings: ["Option A", "Option B", + "Option C", "Option D"]' + order: + type: integer + title: Display Order + required: + - id + - question_text + QuizResult: + type: object + properties: + score: + type: number + format: double + description: Percentage score achieved (0-100) + passed: + type: boolean + description: True if score >= 70% + total_questions: + type: integer + correct_count: + type: integer + question_results: + type: array + items: + $ref: '#/components/schemas/QuizQuestionResultItem' + is_lesson_completed: + type: boolean + course_progress_percentage: + type: integer + certificate_code: + type: string + nullable: true + required: + - correct_count + - course_progress_percentage + - is_lesson_completed + - passed + - question_results + - score + - total_questions + QuizSubmitRequestRequest: + type: object + properties: + answers: + type: array + items: + $ref: '#/components/schemas/SingleQuizAnswerRequest' + required: + - answers + Region: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + title: Region Name + description: Name of the geographical or administrative region (e.g. Middle + East, Europe). + maxLength: 255 + slug: + type: string + readOnly: true + nullable: true + title: Slug / URL Identifier + pattern: ^[-\w]+$ + code: + type: string + nullable: true + title: Region Code + description: Short code such as MENA, EU, NA, etc. + maxLength: 50 + description: + type: string + nullable: true + is_active: + type: boolean + institutions_count: + type: integer + readOnly: true + created_at: + type: string + format: date-time + readOnly: true + updated_at: + type: string + format: date-time + readOnly: true + required: + - created_at + - id + - institutions_count + - name + - slug + - updated_at + RegionInfo: + type: object + properties: + ip: + type: string + nullable: true + browser: + type: string + nullable: true + user_agent: + type: string + required: + - browser + - ip + - user_agent + RegionMini: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + readOnly: true + title: Region Name + description: Name of the geographical or administrative region (e.g. Middle + East, Europe). + slug: + type: string + readOnly: true + nullable: true + title: Slug / URL Identifier + pattern: ^[-\w]+$ + code: + type: string + readOnly: true + nullable: true + title: Region Code + description: Short code such as MENA, EU, NA, etc. + required: + - code + - id + - name + - slug + RegionRequest: + type: object + properties: + name: + type: string + minLength: 1 + title: Region Name + description: Name of the geographical or administrative region (e.g. Middle + East, Europe). + maxLength: 255 + code: + type: string + nullable: true + title: Region Code + description: Short code such as MENA, EU, NA, etc. + maxLength: 50 + description: + type: string + nullable: true + is_active: + type: boolean + required: + - name + RegionalDensity: + type: object + properties: + country: + type: string + total_institutions: + type: integer + mosques_count: + type: integer + hussainiyas_count: + type: integer + cultural_centers_count: + type: integer + libraries_count: + type: integer + institutes_count: + type: integer + charities_count: + type: integer + total_followers: + type: integer + center_lat: + type: number + format: double + nullable: true + center_lng: + type: number + format: double + nullable: true + required: + - center_lat + - center_lng + - charities_count + - country + - cultural_centers_count + - hussainiyas_count + - institutes_count + - libraries_count + - mosques_count + - total_followers + - total_institutions + RegisterRequestRequest: + type: object + properties: + email: + type: string + format: email + minLength: 1 + password: + type: string + writeOnly: true + minLength: 6 + first_name: + type: string + maxLength: 150 + last_name: + type: string + maxLength: 150 + fullname: + type: string + maxLength: 255 + phone_number: + type: string + nullable: true + user_type: + allOf: + - $ref: '#/components/schemas/UserTypeEnum' + default: client + languages: + type: array + items: + type: string + minLength: 1 + maxLength: 10 + skills: + type: array + items: + type: string + minLength: 1 + maxLength: 100 + country: + type: string + maxLength: 255 + city: + type: string + maxLength: 255 + required: + - email + - password + RegisterResponse: + type: object + properties: + user: + $ref: '#/components/schemas/UserMe' + access: + type: string + refresh: + type: string + message: + type: string + required: + - access + - message + - refresh + - user + RepresentativeReportCreateRequest: + type: object + properties: + title: + type: string + minLength: 1 + title: Report Title + maxLength: 255 + region_hub: + type: string + minLength: 1 + title: Regional Hub / Mission Zone + description: e.g. East Africa Regional Hub, South Asia Bureau, European + Cultural Mission + maxLength: 150 + period: + type: string + minLength: 1 + title: Reporting Period + description: e.g. Q1 2026, Muharram 1448 AH, March 2026 + maxLength: 100 + activities_summary: + type: string + minLength: 1 + title: Summary of Activities & Negotiations + centers_visited_count: + type: integer + title: Number of Mosques / Centers Visited + key_achievements: + type: string + nullable: true + title: Key Achievements & Milestones + challenges_and_needs: + type: string + nullable: true + title: Challenges, Risks & Logistical Needs + attachment: + type: string + format: binary + nullable: true + title: Official Report Document (PDF / Word) + required: + - activities_summary + - period + - region_hub + - title + RepresentativeReportDetail: + type: object + properties: + id: + type: integer + readOnly: true + title: + type: string + title: Report Title + maxLength: 255 + representative: + type: integer + readOnly: true + title: Field Representative + representative_name: + type: string + readOnly: true + region_hub: + type: string + title: Regional Hub / Mission Zone + description: e.g. East Africa Regional Hub, South Asia Bureau, European + Cultural Mission + maxLength: 150 + period: + type: string + title: Reporting Period + description: e.g. Q1 2026, Muharram 1448 AH, March 2026 + maxLength: 100 + activities_summary: + type: string + title: Summary of Activities & Negotiations + centers_visited_count: + type: integer + title: Number of Mosques / Centers Visited + key_achievements: + type: string + nullable: true + title: Key Achievements & Milestones + challenges_and_needs: + type: string + nullable: true + title: Challenges, Risks & Logistical Needs + attachment: + type: string + format: uri + nullable: true + title: Official Report Document (PDF / Word) + status: + allOf: + - $ref: '#/components/schemas/DiplomacyReportStatusEnum' + readOnly: true + title: Review Status + feedback_notes: + type: string + readOnly: true + nullable: true + title: Headquarters Feedback & Directives + reviewed_by: + type: integer + readOnly: true + nullable: true + title: Reviewed By Officer + reviewer_name: + type: string + readOnly: true + created_at: + type: string + format: date-time + readOnly: true + title: Submitted At + updated_at: + type: string + format: date-time + readOnly: true + required: + - activities_summary + - created_at + - feedback_notes + - id + - period + - region_hub + - representative + - representative_name + - reviewed_by + - reviewer_name + - status + - title + - updated_at + RepresentativeReportList: + type: object + properties: + id: + type: integer + readOnly: true + title: + type: string + title: Report Title + maxLength: 255 + representative_name: + type: string + readOnly: true + representative_email: + type: string + format: email + readOnly: true + region_hub: + type: string + title: Regional Hub / Mission Zone + description: e.g. East Africa Regional Hub, South Asia Bureau, European + Cultural Mission + maxLength: 150 + period: + type: string + title: Reporting Period + description: e.g. Q1 2026, Muharram 1448 AH, March 2026 + maxLength: 100 + centers_visited_count: + type: integer + title: Number of Mosques / Centers Visited + status: + allOf: + - $ref: '#/components/schemas/DiplomacyReportStatusEnum' + title: Review Status + created_at: + type: string + format: date-time + readOnly: true + title: Submitted At + required: + - created_at + - id + - period + - region_hub + - representative_email + - representative_name + - title + RoleEnum: + enum: + - admin + - editor + - viewer + type: string + description: |- + * `admin` - Administrator + * `editor` - Content & Project Editor + * `viewer` - Viewer / Member + RoomTypeEnum: + enum: + - direct + - group + type: string + description: |- + * `direct` - Direct Message + * `group` - Group Conversation + SearchEntityTypeEnum: + enum: + - institution + - project + - course + - post + - event + type: string + description: |- + * `institution` - Institution + * `project` - Project + * `course` - Course + * `post` - Post + * `event` - Event + SearchResultItem: + type: object + properties: + id: + type: string + description: Entity unique identifier + type: + allOf: + - $ref: '#/components/schemas/SearchEntityTypeEnum' + description: |- + Type of the entity found + + * `institution` - Institution + * `project` - Project + * `course` - Course + * `post` - Post + * `event` - Event + title: + type: string + description: Title or primary name + description: + type: string + description: Summary or excerpt + url_path: + type: string + description: Frontend navigation route + image: + type: string + nullable: true + description: Cover or avatar image URL + badge: + type: string + description: Contextual badge / label + metadata: + type: object + additionalProperties: {} + description: Additional metadata attributes + relevance_score: + type: number + format: double + default: 1.0 + description: Relevance ranking score + required: + - id + - title + - type + - url_path + SearchTypeCount: + type: object + properties: + institutions: + type: integer + default: 0 + projects: + type: integer + default: 0 + courses: + type: integer + default: 0 + posts: + type: integer + default: 0 + events: + type: integer + default: 0 + SendNotificationRequest: + type: object + properties: + user_id: + type: integer + description: Target User ID + title: + type: string + minLength: 1 + description: Notification title + maxLength: 255 + body: + type: string + minLength: 1 + description: Notification body + data: + type: object + additionalProperties: {} + description: Extra payload data + required: + - body + - title + - user_id + SingleQuizAnswerRequest: + type: object + properties: + question_id: + type: integer + selected_option: + type: integer + required: + - question_id + - selected_option + Support: + type: object + properties: + telegram_number: + type: string + default: '' + whatsapp_number: + type: string + default: '' + office_hours: + type: string + default: '' + SupportMessageCreate: + type: object + properties: + id: + type: integer + readOnly: true + sender_name: + type: string + maxLength: 255 + sender_email: + type: string + format: email + maxLength: 254 + subject: + type: string + maxLength: 255 + sender_phone: + type: string + nullable: true + maxLength: 128 + message: + type: string + created_at: + type: string + format: date-time + readOnly: true + required: + - created_at + - id + - message + - sender_email + - sender_name + - subject + SupportMessageCreateRequest: + type: object + properties: + sender_name: + type: string + minLength: 1 + maxLength: 255 + sender_email: + type: string + format: email + minLength: 1 + maxLength: 254 + subject: + type: string + minLength: 1 + maxLength: 255 + sender_phone: + type: string + nullable: true + maxLength: 128 + message: + type: string + minLength: 1 + required: + - message + - sender_email + - sender_name + - subject + SupportRequest: + type: object + properties: + telegram_number: + type: string + default: '' + whatsapp_number: + type: string + default: '' + office_hours: + type: string + default: '' + TicketCategoryEnum: + enum: + - cultural_affairs + - religious_inquiry + - inter_center_cooperation + - financial_legal + - technical_support + - general + type: string + description: |- + * `cultural_affairs` - Cultural & International Affairs + * `religious_inquiry` - Religious & Theological Inquiry + * `inter_center_cooperation` - Inter-Center Cooperation & Partnerships + * `financial_legal` - Financial & Legal Affairs + * `technical_support` - Technical & Portal Support + * `general` - General Inquiry + TicketCreateRequest: + type: object + properties: + subject: + type: string + minLength: 1 + title: Subject / Title + maxLength: 255 + category: + $ref: '#/components/schemas/TicketCategoryEnum' + priority: + $ref: '#/components/schemas/TicketPriorityEnum' + institution: + type: integer + nullable: true + title: Affiliated Institution + content: + type: string + writeOnly: true + minLength: 1 + description: Initial message description + attachment: + type: string + format: binary + writeOnly: true + nullable: true + required: + - content + - subject + TicketDetail: + type: object + properties: + id: + type: integer + readOnly: true + tracking_code: + type: string + description: e.g. TK-RAZAVI-2026-AB12CD + maxLength: 100 + subject: + type: string + title: Subject / Title + maxLength: 255 + category: + $ref: '#/components/schemas/TicketCategoryEnum' + priority: + $ref: '#/components/schemas/TicketPriorityEnum' + status: + $ref: '#/components/schemas/TicketStatusEnum' + submitter_name: + type: string + readOnly: true + institution: + type: integer + nullable: true + title: Affiliated Institution + institution_name: + type: string + readOnly: true + assigned_expert: + type: integer + nullable: true + title: Assigned Expert / Officer + expert_name: + type: string + readOnly: true + satisfaction_rating: + type: integer + nullable: true + title: Satisfaction Rating (1-5 Stars) + feedback_comment: + type: string + nullable: true + title: User Feedback Notes + messages: + type: array + items: + $ref: '#/components/schemas/TicketMessage' + readOnly: true + created_at: + type: string + format: date-time + readOnly: true + updated_at: + type: string + format: date-time + readOnly: true + closed_at: + type: string + format: date-time + nullable: true + required: + - created_at + - expert_name + - id + - institution_name + - messages + - subject + - submitter_name + - updated_at + TicketDetailRequest: + type: object + properties: + tracking_code: + type: string + description: e.g. TK-RAZAVI-2026-AB12CD + maxLength: 100 + subject: + type: string + minLength: 1 + title: Subject / Title + maxLength: 255 + category: + $ref: '#/components/schemas/TicketCategoryEnum' + priority: + $ref: '#/components/schemas/TicketPriorityEnum' + status: + $ref: '#/components/schemas/TicketStatusEnum' + institution: + type: integer + nullable: true + title: Affiliated Institution + assigned_expert: + type: integer + nullable: true + title: Assigned Expert / Officer + satisfaction_rating: + type: integer + nullable: true + title: Satisfaction Rating (1-5 Stars) + feedback_comment: + type: string + nullable: true + title: User Feedback Notes + closed_at: + type: string + format: date-time + nullable: true + required: + - subject + TicketList: + type: object + properties: + id: + type: integer + readOnly: true + tracking_code: + type: string + description: e.g. TK-RAZAVI-2026-AB12CD + maxLength: 100 + subject: + type: string + title: Subject / Title + maxLength: 255 + category: + $ref: '#/components/schemas/TicketCategoryEnum' + priority: + $ref: '#/components/schemas/TicketPriorityEnum' + status: + $ref: '#/components/schemas/TicketStatusEnum' + submitter_name: + type: string + readOnly: true + institution: + type: integer + nullable: true + title: Affiliated Institution + institution_name: + type: string + readOnly: true + messages_count: + type: integer + readOnly: true + satisfaction_rating: + type: integer + nullable: true + title: Satisfaction Rating (1-5 Stars) + created_at: + type: string + format: date-time + readOnly: true + updated_at: + type: string + format: date-time + readOnly: true + closed_at: + type: string + format: date-time + nullable: true + required: + - created_at + - id + - institution_name + - messages_count + - subject + - submitter_name + - updated_at + TicketMessage: + type: object + properties: + id: + type: integer + readOnly: true + sender_id: + type: integer + readOnly: true + sender_name: + type: string + readOnly: true + is_official_response: + type: boolean + readOnly: true + title: Is Official Response from Astan / Expert + department_label: + type: string + title: Department / Authority Label + maxLength: 255 + content: + type: string + title: Message Content + attachment: + type: string + format: uri + nullable: true + created_at: + type: string + format: date-time + readOnly: true + required: + - content + - created_at + - id + - is_official_response + - sender_id + - sender_name + TicketMessageCreateRequest: + type: object + properties: + content: + type: string + minLength: 1 + title: Message Content + attachment: + type: string + format: binary + nullable: true + department_label: + type: string + title: Department / Authority Label + maxLength: 255 + required: + - content + TicketMessageRequest: + type: object + properties: + department_label: + type: string + title: Department / Authority Label + maxLength: 255 + content: + type: string + minLength: 1 + title: Message Content + attachment: + type: string + format: binary + nullable: true + required: + - content + TicketPriorityEnum: + enum: + - low + - medium + - high + - urgent + type: string + description: |- + * `low` - Low + * `medium` - Medium + * `high` - High + * `urgent` - Urgent + TicketRateRequest: + type: object + properties: + rating: + type: integer + maximum: 5 + minimum: 1 + description: 1 to 5 stars + feedback: + type: string + required: + - rating + TicketStatusEnum: + enum: + - open + - in_progress + - answered + - closed + type: string + description: |- + * `open` - Open + * `in_progress` - In Progress + * `answered` - Official Response Provided + * `closed` - Closed + TicketingAnalytics: + type: object + properties: + total_tickets: + type: integer + open_tickets: + type: integer + answered_or_closed: + type: integer + average_satisfaction_rating: + type: number + format: double + nullable: true + required: + - answered_or_closed + - average_satisfaction_rating + - open_tickets + - total_tickets + TokenRefresh: + type: object + properties: + access: + type: string + readOnly: true + refresh: + type: string + required: + - access + - refresh + TokenRefreshRequest: + type: object + properties: + refresh: + type: string + minLength: 1 + required: + - refresh + TokenVerifyRequest: + type: object + properties: + token: + type: string + writeOnly: true + minLength: 1 + required: + - token + TwoFactorBackupCodesRegenerateRequest: + type: object + properties: + password: + type: string + writeOnly: true + description: Optional current password confirmation. + TwoFactorDisableRequest: + type: object + properties: + password: + type: string + writeOnly: true + description: Optional current password confirmation. + TwoFactorMethodEnum: + enum: + - email + - totp + type: string + description: |- + * `email` - Email OTP + * `totp` - Authenticator App (TOTP) + TwoFactorResendEmailRequest: + type: object + properties: + temp_token: + type: string + minLength: 1 + description: Ephemeral 2FA challenge token. + required: + - temp_token + TwoFactorSetupConfirmRequest: + type: object + properties: + method: + allOf: + - $ref: '#/components/schemas/Method4fbEnum' + default: totp + description: |- + 2FA method being confirmed ('totp' or 'email'). + + * `email` - Email OTP + * `totp` - Authenticator App (TOTP) + code: + type: string + minLength: 6 + description: 6-digit verification code from Authenticator app or Email. + maxLength: 8 + required: + - code + TwoFactorSetupInitiateRequest: + type: object + properties: + method: + allOf: + - $ref: '#/components/schemas/Method4fbEnum' + default: totp + description: |- + 2FA method to set up ('totp' or 'email'). + + * `email` - Email OTP + * `totp` - Authenticator App (TOTP) + TwoFactorToggleRequest: + type: object + properties: + enabled: + type: boolean + description: Set True to enable 2FA or False to disable 2FA. + method: + allOf: + - $ref: '#/components/schemas/Method4fbEnum' + description: |- + Optional preferred 2FA method ('email' or 'totp'). + + * `email` - Email OTP + * `totp` - Authenticator App (TOTP) + required: + - enabled + TwoFactorVerifyMethodEnum: + enum: + - email + - totp + - backup + - backup_code + type: string + description: |- + * `email` - Email OTP + * `totp` - Authenticator App (TOTP) + * `backup` - Backup Recovery Code + * `backup_code` - Backup Recovery Code + TwoFactorVerifyRequest: + type: object + properties: + temp_token: + type: string + minLength: 1 + description: Ephemeral 2FA challenge token returned by /api/v1/auth/token/. + code: + type: string + minLength: 6 + description: 6-digit OTP code (Email or TOTP) or 8-character backup recovery + code. + maxLength: 16 + method: + allOf: + - $ref: '#/components/schemas/TwoFactorVerifyMethodEnum' + description: |- + Verification method used. Defaults to the challenge's active method. + + * `email` - Email OTP + * `totp` - Authenticator App (TOTP) + * `backup` - Backup Recovery Code + * `backup_code` - Backup Recovery Code + required: + - code + - temp_token + UnifiedSearchResponse: + type: object + properties: + query: + type: string + description: Original search query string + total_results: + type: integer + description: Total number of matched records across categories + by_type: + allOf: + - $ref: '#/components/schemas/SearchTypeCount' + description: Breakdown count of matched records by entity type + results: + type: array + items: + $ref: '#/components/schemas/SearchResultItem' + description: List of matched search results ordered by relevance + required: + - by_type + - query + - results + - total_results + UserFCM: + type: object + properties: + fcm: + type: string + nullable: true + title: FCM Token + maxLength: 512 + UserFCMRequest: + type: object + properties: + fcm: + type: string + nullable: true + title: FCM Token + maxLength: 512 + UserGuestRequest: + type: object + properties: + device_id: + type: string + minLength: 1 + fcm: + type: string + minLength: 1 + device_os: + $ref: '#/components/schemas/DeviceOsEnum' + lat: + type: string + nullable: true + maxLength: 255 + lon: + type: string + nullable: true + maxLength: 255 + timezone: + type: string + nullable: true + UserLoginRequest: + type: object + properties: + email: + type: string + format: email + writeOnly: true + minLength: 1 + password: + type: string + minLength: 1 + fcm: + type: string + minLength: 1 + device_id: + type: string + minLength: 1 + timezone: + type: string + nullable: true + required: + - email + - password + UserMe: + type: object + properties: + id: + type: integer + readOnly: true + email: + type: string + format: email + readOnly: true + nullable: true + title: Email Address + description: User primary email address. + username: + type: string + readOnly: true + nullable: true + fullname: + type: string + nullable: true + title: Full Name + description: Full name of the user. + maxLength: 255 + first_name: + type: string + readOnly: true + last_name: + type: string + readOnly: true + name: + type: string + readOnly: true + phone_number: + type: string + nullable: true + description: e.g., +1 555 1234567 + maxLength: 128 + phone: + type: string + readOnly: true + user_type: + allOf: + - $ref: '#/components/schemas/UserTypeEnum' + readOnly: true + avatar: + type: string + format: uri + nullable: true + bio: + type: string + nullable: true + languages: {} + preferred_language: + type: string + readOnly: true + skills: {} + title: + type: string + readOnly: true + rank: + type: string + readOnly: true + position: + type: string + readOnly: true + institution: + type: string + readOnly: true + institution_id: + type: integer + nullable: true + readOnly: true + website: + type: string + readOnly: true + address: + type: string + readOnly: true + country: + type: string + nullable: true + maxLength: 255 + city: + type: string + nullable: true + maxLength: 255 + gender: + nullable: true + oneOf: + - $ref: '#/components/schemas/GenderEnum' + - $ref: '#/components/schemas/BlankEnum' + - $ref: '#/components/schemas/NullEnum' + birthdate: + type: string + format: date + nullable: true + date_joined: + type: string + format: date-time + readOnly: true + is_active: + type: boolean + readOnly: true + title: Active + description: Designates whether this user should be treated as active. + is_staff: + type: boolean + readOnly: true + role: + type: string + readOnly: true + roles: + type: array + items: {} + readOnly: true + permissions: + type: array + items: {} + description: |- + Returns active permission capability flags where boolean value is True, + dynamically derived from the user's assigned database Role and custom overrides. + readOnly: true + permission_flags: + type: object + additionalProperties: {} + description: Returns full boolean flags dictionary dynamically resolved + from Role and custom_permissions. + readOnly: true + managed_regions: + type: array + items: {} + readOnly: true + regional_permissions: + title: Regional Permission Overrides + description: List of permission names that are restricted to the user's + managed regions (e.g. ['can_manage_institutions', 'can_manage_tickets']). + readOnly: true + verification_status: + type: string + readOnly: true + is_verified: + type: boolean + readOnly: true + two_factor_enabled: + type: boolean + readOnly: true + two_factor_method: + type: string + readOnly: true + is_2fa_forced_by_admin: + type: boolean + readOnly: true + require_2fa: + type: boolean + readOnly: true + has_totp_configured: + type: boolean + readOnly: true + backup_codes_count: + type: integer + readOnly: true + required: + - address + - backup_codes_count + - date_joined + - email + - first_name + - has_totp_configured + - id + - institution + - institution_id + - is_2fa_forced_by_admin + - is_active + - is_staff + - is_verified + - last_name + - managed_regions + - name + - permission_flags + - permissions + - phone + - position + - preferred_language + - rank + - regional_permissions + - require_2fa + - role + - roles + - title + - two_factor_enabled + - two_factor_method + - user_type + - username + - verification_status + - website + UserProfile: + type: object + properties: + id: + type: integer + readOnly: true + device_id: + type: string + readOnly: true + nullable: true + fcm: + type: string + description: Firebase Cloud Messaging token. + first_name: + type: string + last_name: + type: string + fullname: + type: string + slug: + type: string + readOnly: true + nullable: true + pattern: ^[-a-zA-Z0-9_]+$ + avatar: + type: string + nullable: true + email: + type: string + format: email + readOnly: true + nullable: true + title: Email Address + description: User primary email address. + phone_number: + type: string + nullable: true + description: e.g., +1 555 1234567 + maxLength: 128 + info: + type: string + readOnly: true + nullable: true + title: Bio / Info + skill: + type: string + readOnly: true + nullable: true + title: Skill / Role + city: + type: string + nullable: true + maxLength: 255 + country: + type: string + nullable: true + maxLength: 255 + birthdate: + type: string + format: date + nullable: true + gender: + allOf: + - $ref: '#/components/schemas/GenderEnum' + description: |- + Select the user's gender. + + * `male` - Male + * `female` - Female + * `other` - Other + saved_location: + type: object + additionalProperties: {} + readOnly: true + required: + - device_id + - email + - id + - info + - saved_location + - skill + - slug + UserProfileRequest: + type: object + properties: + fcm: + type: string + minLength: 1 + description: Firebase Cloud Messaging token. + first_name: + type: string + last_name: + type: string + fullname: + type: string + avatar: + type: string + nullable: true + minLength: 1 + phone_number: + type: string + nullable: true + description: e.g., +1 555 1234567 + maxLength: 128 + password: + type: string + writeOnly: true + minLength: 1 + city: + type: string + nullable: true + maxLength: 255 + country: + type: string + nullable: true + maxLength: 255 + birthdate: + type: string + format: date + nullable: true + gender: + allOf: + - $ref: '#/components/schemas/GenderEnum' + description: |- + Select the user's gender. + + * `male` - Male + * `female` - Female + * `other` - Other + UserRecoverPasswordRequest: + type: object + description: |- + Validates that an email is provided and is in a valid format + without checking for database uniqueness. + properties: + email: + type: string + format: email + minLength: 1 + required: + - email + UserRegisterRequest: + type: object + properties: + first_name: + type: string + last_name: + type: string + fullname: + type: string + nullable: true + title: Full Name + description: Full name of the user. + maxLength: 255 + email: + type: string + format: email + minLength: 1 + password: + type: string + writeOnly: true + minLength: 1 + fcm: + type: string + nullable: true + device_id: + type: string + writeOnly: true + nullable: true + required: + - email + UserResetPasswordRequest: + type: object + properties: + password: + type: string + writeOnly: true + minLength: 1 + required: + - password + UserTypeEnum: + enum: + - client + - student + - professor + - consultant + - admin + - super_admin + - regional_admin + - institution_admin + - editor + - viewer + type: string + description: |- + * `client` - Client / User + * `student` - Student / Member + * `professor` - Professor / Scholar + * `consultant` - Consultant + * `admin` - Admin + * `super_admin` - Super Admin + * `regional_admin` - Regional Admin + * `institution_admin` - Institution Admin + * `editor` - Editor + * `viewer` - Viewer + UserVerifyRequest: + type: object + properties: + code: + type: string + minLength: 1 + maxLength: 6 + email: + type: string + format: email + minLength: 1 + device_id: + type: string + minLength: 1 + maxLength: 255 + required: + - code + - email + VerificationDocument: + type: object + properties: + id: + type: integer + readOnly: true + user: + type: integer + readOnly: true + document_type: + $ref: '#/components/schemas/DocumentTypeEnum' + document_type_display: + type: string + readOnly: true + title: + type: string + nullable: true + title: Document Title + maxLength: 255 + document_file: + type: string + format: uri + status: + allOf: + - $ref: '#/components/schemas/VerificationDocumentStatusEnum' + readOnly: true + title: Verification Status + status_display: + type: string + readOnly: true + admin_notes: + type: string + readOnly: true + nullable: true + title: Admin Notes / Feedback + uploaded_at: + type: string + format: date-time + readOnly: true + reviewed_at: + type: string + format: date-time + readOnly: true + nullable: true + required: + - admin_notes + - document_file + - document_type_display + - id + - reviewed_at + - status + - status_display + - uploaded_at + - user + VerificationDocumentStatusEnum: + enum: + - pending + - verified + - rejected + type: string + description: |- + * `pending` - Pending Review + * `verified` - Verified + * `rejected` - Rejected + VerificationDocumentUploadRequest: + type: object + properties: + document_type: + allOf: + - $ref: '#/components/schemas/DocumentTypeEnum' + default: national_id + title: + type: string + maxLength: 255 + document_file: + type: string + format: binary + required: + - document_file + VerificationStatusEnum: + enum: + - pending + - approved + - rejected + type: string + description: |- + * `pending` - Pending Review + * `approved` - Approved & Verified + * `rejected` - Rejected + WebUserRegisterRequest: + type: object + properties: + fullname: + type: string + nullable: true + title: Full Name + description: Full name of the user. + maxLength: 255 + email: + type: string + format: email + minLength: 1 + password: + type: string + writeOnly: true + minLength: 1 + fcm: + type: string + nullable: true + required: + - email + - fullname + - password + WorkflowStatusEnum: + enum: + - submitted + - under_review + - approved + - rejected + type: string + description: |- + * `submitted` - Submitted + * `under_review` - Under Review + * `approved` - Approved + * `rejected` - Rejected + securitySchemes: + cookieAuth: + type: apiKey + in: cookie + name: sessionid + jwtAuth: + type: http + scheme: bearer + bearerFormat: JWT + tokenAuth: + type: apiKey + in: header + name: Authorization + description: Token-based authentication with required prefix "Token" diff --git a/schema.yml b/schema.yml index 51bf956..67e54db 100644 --- a/schema.yml +++ b/schema.yml @@ -1490,6 +1490,231 @@ paths: schema: $ref: '#/components/schemas/ExecutiveDashboard' description: '' + /api/v1/auth/2fa/backup-codes/regenerate/: + post: + operationId: v1_auth_2fa_backup_codes_regenerate_create + description: Generates 8 fresh single-use 8-character recovery codes and invalidates + any previously generated codes. + summary: Regenerate 2FA backup recovery codes + tags: + - Authentication & Profile + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TwoFactorBackupCodesRegenerateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TwoFactorBackupCodesRegenerateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/TwoFactorBackupCodesRegenerateRequest' + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + description: New backup codes generated + /api/v1/auth/2fa/disable/: + post: + operationId: v1_auth_2fa_disable_create + description: Disables 2FA unless an administrator has enforced mandatory 2FA + (`require_2fa = True`) for the user or their Role. + summary: Disable 2FA for the current user + tags: + - Authentication & Profile + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TwoFactorDisableRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TwoFactorDisableRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/TwoFactorDisableRequest' + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + description: 2FA disabled + '403': + description: 2FA is enforced by administrator and cannot be disabled + /api/v1/auth/2fa/resend/: + post: + operationId: v1_auth_2fa_resend_create + description: Generates and sends a fresh 6-digit Email OTP code for the active + `temp_token` challenge. + summary: Send or resend 2FA Email OTP for an active login challenge + tags: + - Authentication & Profile + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TwoFactorResendEmailRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TwoFactorResendEmailRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/TwoFactorResendEmailRequest' + required: true + security: + - jwtAuth: [] + - {} + responses: + '200': + description: Email OTP sent + '400': + description: Invalid or expired challenge + /api/v1/auth/2fa/setup/confirm/: + post: + operationId: v1_auth_2fa_setup_confirm_create + description: Verifies the 6-digit code from the Authenticator app or Email, + activates 2FA, and returns 8 one-time backup recovery codes. + summary: Confirm 2FA setup and generate backup recovery codes + tags: + - Authentication & Profile + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TwoFactorSetupConfirmRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TwoFactorSetupConfirmRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/TwoFactorSetupConfirmRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + description: 2FA confirmed and enabled + '400': + description: Invalid or expired setup verification code + /api/v1/auth/2fa/setup/initiate/: + post: + operationId: v1_auth_2fa_setup_initiate_create + description: For `method='totp'`, generates a fresh Base32 TOTP secret, otpauth + URI, and QR code Data URL. For `method='email'`, sends a 6-digit confirmation + OTP to the user's email. + summary: Initiate 2FA setup (TOTP QR code or Email OTP verification) + tags: + - Authentication & Profile + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TwoFactorSetupInitiateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TwoFactorSetupInitiateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/TwoFactorSetupInitiateRequest' + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + description: Setup initiated + /api/v1/auth/2fa/status/: + get: + operationId: v1_auth_2fa_status_retrieve + description: Returns whether 2FA is enabled, whether it is forced by an administrator, + the active method, and remaining backup codes. + summary: Get current user's 2FA status and admin enforcement state + tags: + - Authentication & Profile + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + description: Current 2FA status + /api/v1/auth/2fa/toggle/: + post: + operationId: v1_auth_2fa_toggle_create + description: Allows the authenticated user to toggle Two-Factor Authentication + (2FA) on or off for themselves. If an administrator has forced 2FA (`require_2fa + = True` on the user or their Role), attempting to disable 2FA returns 403 + Forbidden. + summary: Toggle 2FA on or off for the current user + tags: + - Authentication & Profile + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TwoFactorToggleRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TwoFactorToggleRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/TwoFactorToggleRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + description: 2FA status updated + '403': + description: Cannot disable 2FA when enforced by administrator + /api/v1/auth/2fa/verify/: + post: + operationId: v1_auth_2fa_verify_create + description: Completes the second step of login by verifying the 6-digit OTP + code (from Email or Authenticator App) or an 8-character Backup Recovery Code + against the ephemeral `temp_token` stored in Redis. + summary: Verify 2FA login challenge and issue JWT tokens + tags: + - Authentication & Profile + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TwoFactorVerifyRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/TwoFactorVerifyRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/TwoFactorVerifyRequest' + required: true + security: + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/RegisterResponse' + description: '' + '400': + description: Invalid or expired 2FA code / challenge + '429': + description: Maximum verification attempts exceeded /api/v1/auth/documents/: get: operationId: v1_auth_documents_list @@ -1703,10 +1928,14 @@ paths: /api/v1/auth/token/: post: operationId: v1_auth_token_create - description: Takes a set of user credentials (either 'username' or 'email' + - password) and returns access & refresh token pair. + description: 'Authenticates user with email/username and password. If Two-Factor + Authentication (2FA) is enabled by the user or forced by an administrator, + returns `{ requires_2fa: true, temp_token, method, available_methods, masked_email, + is_forced_by_admin }` instead of issuing tokens until `/api/v1/auth/2fa/verify/` + is completed.' + summary: Obtain JWT token pair or 2FA challenge tags: - - v1 + - Authentication & Profile requestBody: content: application/json: @@ -2116,6 +2345,11 @@ paths: schema: type: integer description: Filter by author user ID + - in: query + name: author_type + schema: + type: string + description: Filter by author type (user, institution) - in: query name: category schema: @@ -2250,10 +2484,44 @@ paths: description: '' '404': description: Post not found + put: + operationId: v1_cms_posts_update + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - v1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PostDetailRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PostDetailRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PostDetailRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PostDetail' + description: '' patch: operationId: v1_cms_posts_partial_update - description: Updates an existing post. Permitted for original author, institution - editors, or platform admins. + description: Updates an existing post. If post is published and editor rights + are lacking, submits a zero-downtime staged revision. summary: Update post parameters: - in: path @@ -2286,6 +2554,8 @@ paths: schema: $ref: '#/components/schemas/PostDetail' description: '' + '202': + description: Revision staged for editorial review '403': description: Permission denied delete: @@ -2310,6 +2580,50 @@ paths: description: Post deleted successfully '403': description: Permission denied + /api/v1/cms/posts/{pk_or_slug}/approve/: + post: + operationId: v1_cms_posts_approve_create + description: Approves a pending post and immediately publishes it. Requires + direct publishing capability for the associated institution. + summary: Approve and publish post + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Content Management System (CMS) + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PostDetailRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PostDetailRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PostDetailRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PostDetail' + description: '' + '400': + description: Post cannot be approved + '403': + description: Permission denied + '404': + description: Post not found /api/v1/cms/posts/{pk_or_slug}/comments/: get: operationId: v1_cms_posts_comments_list @@ -2425,45 +2739,244 @@ paths: schema: $ref: '#/components/schemas/PostLikeResponse' description: '' - /api/v1/cms/tags/: - get: - operationId: v1_cms_tags_list - description: Returns distinct content tags used across posts. - summary: List CMS tags + /api/v1/cms/posts/{pk_or_slug}/reject/: + post: + operationId: v1_cms_posts_reject_create + description: Rejects a pending post. Requires direct publishing capability for + the associated institution and a mandatory explanation reason. + summary: Reject post with reason parameters: - - name: page - required: false - in: query - description: A page number within the paginated result set. - schema: - type: integer - - name: page_size - required: false - in: query - description: Number of results to return per page. + - in: path + name: pk_or_slug schema: - type: integer + type: string + required: true tags: - Content Management System (CMS) + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PostRejectRequestRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PostRejectRequestRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PostRejectRequestRequest' + required: true security: - jwtAuth: [] - tokenAuth: [] - cookieAuth: [] - jwtAuth: [] - - {} responses: '200': content: application/json: schema: - $ref: '#/components/schemas/PaginatedPostTagList' + $ref: '#/components/schemas/PostDetail' description: '' - /api/v1/contact-us/: - post: - operationId: v1_contact_us_create - description: Submit a user contact or support inquiry - tags: - - v1 + '400': + description: Rejection reason is missing + '403': + description: Permission denied + '404': + description: Post not found + /api/v1/cms/posts/{pk_or_slug}/revisions/: + get: + operationId: v1_cms_posts_revisions_list + description: Lists all historical and pending revisions for this post. Permitted + for the author or institution editors. + summary: List revisions for a post + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Content Management System (CMS) + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedPostRevisionList' + description: '' + '403': + description: Permission denied + '404': + description: Post not found + delete: + operationId: v1_cms_posts_revisions_destroy + description: Allows the post author to withdraw or delete their pending revision + before editorial review. + summary: Discard pending revision + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Content Management System (CMS) + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '204': + description: Pending revision discarded + '400': + description: No pending revision to discard + '403': + description: Permission denied + /api/v1/cms/posts/{pk_or_slug}/revisions/approve/: + post: + operationId: v1_cms_posts_revisions_approve_create + description: Approves a pending post revision and merges it live into the published + post with zero downtime. Requires direct publishing capability for the institution. + summary: Approve and merge staged post revision + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Content Management System (CMS) + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PostDetailRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PostDetailRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PostDetailRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PostRevisionApproveResponse' + description: '' + '400': + description: No pending revision found + '403': + description: Permission denied + '404': + description: Post not found + /api/v1/cms/posts/{pk_or_slug}/revisions/reject/: + post: + operationId: v1_cms_posts_revisions_reject_create + description: Rejects a pending post revision without affecting the live published + article. Requires direct publishing capability and an explanation reason. + summary: Reject staged post revision with explanation + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Content Management System (CMS) + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PostRevisionRejectRequestRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PostRevisionRejectRequestRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PostRevisionRejectRequestRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PostRevisionRejectResponse' + description: '' + '400': + description: Rejection reason missing or no pending revision + '403': + description: Permission denied + '404': + description: Post not found + /api/v1/cms/tags/: + get: + operationId: v1_cms_tags_list + description: Returns distinct content tags used across posts. + summary: List CMS tags + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + tags: + - Content Management System (CMS) + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedPostTagList' + description: '' + /api/v1/contact-us/: + post: + operationId: v1_contact_us_create + description: Submit a user contact or support inquiry + tags: + - v1 requestBody: content: application/json: @@ -3133,9 +3646,15 @@ paths: /api/v1/forms/: get: operationId: v1_forms_list - description: Retrieves available custom dynamic forms and survey workflows. - summary: List active dynamic forms + description: Retrieves available forms. Supports filtering to only user's own + forms via `my_forms=true`. + summary: List dynamic forms parameters: + - in: query + name: my_forms + schema: + type: boolean + description: If true, returns only forms created by the authenticated user. - name: page required: false in: query @@ -3166,7 +3685,7 @@ paths: post: operationId: v1_forms_create description: Defines a new custom form, questions, validation rules, and schema - (Staff/Admin only). + (Staff or affiliated Institution users with permission). summary: Create new dynamic form schema tags: - Dynamic Forms & Surveys @@ -3195,6 +3714,8 @@ paths: schema: $ref: '#/components/schemas/FormDefinitionDetail' description: '' + '401': + description: Authentication required '403': description: Permission denied /api/v1/forms/{pk_or_slug}/: @@ -4616,11 +5137,12 @@ paths: responses: '204': description: Member removed - /api/v1/profiles/{pk_or_slug}/timeline/: + /api/v1/profiles/{pk_or_slug}/quick-links/: get: - operationId: v1_profiles_timeline_list - description: Returns historical milestone entries for an institution. - summary: List timeline milestones + operationId: v1_profiles_quick_links_list + description: Returns administrative quick access service links (virtual tour, + live broadcast, digital library, etc.) for an institution. + summary: List quick access links parameters: - name: page required: false @@ -4652,12 +5174,12 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/PaginatedInstitutionTimelineList' + $ref: '#/components/schemas/PaginatedQuickAccessLinkList' description: '' post: - operationId: v1_profiles_timeline_create - description: Creates a new milestone in the institution historical timeline. - summary: Add timeline milestone + operationId: v1_profiles_quick_links_create + description: Creates a new quick access service link for an institution. + summary: Create quick access link parameters: - in: path name: pk_or_slug @@ -4670,13 +5192,13 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/InstitutionTimelineRequest' + $ref: '#/components/schemas/QuickAccessLinkRequest' application/x-www-form-urlencoded: schema: - $ref: '#/components/schemas/InstitutionTimelineRequest' + $ref: '#/components/schemas/QuickAccessLinkRequest' multipart/form-data: schema: - $ref: '#/components/schemas/InstitutionTimelineRequest' + $ref: '#/components/schemas/QuickAccessLinkRequest' required: true security: - jwtAuth: [] @@ -4688,16 +5210,16 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/InstitutionTimeline' + $ref: '#/components/schemas/QuickAccessLink' description: '' - /api/v1/profiles/{pk_or_slug}/timeline/{entry_id}/: - delete: - operationId: v1_profiles_timeline_destroy - description: Removes a timeline entry from the institution profile. - summary: Delete timeline milestone + /api/v1/profiles/{pk_or_slug}/quick-links/{link_id}/: + get: + operationId: v1_profiles_quick_links_retrieve + description: Gets details of a specific quick access service link. + summary: Retrieve quick access link parameters: - in: path - name: entry_id + name: link_id schema: type: integer required: true @@ -4713,72 +5235,334 @@ paths: - tokenAuth: [] - cookieAuth: [] - jwtAuth: [] + - {} responses: - '204': - description: Entry deleted - /api/v1/profiles/regions/: - get: - operationId: v1_profiles_regions_list - description: Returns list of available regions for dropdowns and filtering. - summary: List all regions + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/QuickAccessLink' + description: '' + put: + operationId: v1_profiles_quick_links_update + description: Fully updates an existing quick access service link. + summary: Update quick access link parameters: - - name: page - required: false - in: query - description: A page number within the paginated result set. - schema: - type: integer - - name: page_size - required: false - in: query - description: Number of results to return per page. + - in: path + name: link_id schema: type: integer - - in: query - name: search + required: true + - in: path + name: pk_or_slug schema: type: string - description: Search query across region name, code, description + required: true tags: - Institutions & Profiles + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/QuickAccessLinkRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/QuickAccessLinkRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/QuickAccessLinkRequest' + required: true security: - jwtAuth: [] - tokenAuth: [] - cookieAuth: [] - jwtAuth: [] - - {} responses: '200': content: application/json: schema: - $ref: '#/components/schemas/PaginatedRegionList' + $ref: '#/components/schemas/QuickAccessLink' description: '' - post: - operationId: v1_profiles_regions_create - description: Creates a new region (Admin or Staff required). - summary: Create a new region + patch: + operationId: v1_profiles_quick_links_partial_update + description: Partially updates an existing quick access service link. + summary: Partial update quick access link + parameters: + - in: path + name: link_id + schema: + type: integer + required: true + - in: path + name: pk_or_slug + schema: + type: string + required: true tags: - Institutions & Profiles requestBody: content: application/json: schema: - $ref: '#/components/schemas/RegionRequest' + $ref: '#/components/schemas/PatchedQuickAccessLinkRequest' application/x-www-form-urlencoded: schema: - $ref: '#/components/schemas/RegionRequest' + $ref: '#/components/schemas/PatchedQuickAccessLinkRequest' multipart/form-data: schema: - $ref: '#/components/schemas/RegionRequest' - required: true + $ref: '#/components/schemas/PatchedQuickAccessLinkRequest' security: - jwtAuth: [] - tokenAuth: [] - cookieAuth: [] - jwtAuth: [] responses: - '201': + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/QuickAccessLink' + description: '' + delete: + operationId: v1_profiles_quick_links_destroy + description: Removes a quick access service link from an institution. + summary: Delete quick access link + parameters: + - in: path + name: link_id + schema: + type: integer + required: true + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Institutions & Profiles + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '204': + description: Link deleted + /api/v1/profiles/{pk_or_slug}/quick-links/reorder/: + post: + operationId: v1_profiles_quick_links_reorder_create + description: 'Reorders the display sequence of quick access links. Accepts either + a list of IDs in order `{"order": [1, 2, 3]}` or an array of objects `[{"id": + 1, "order": 0}, ...]`.' + summary: Reorder quick access links + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Institutions & Profiles + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/QuickAccessLinkReorderRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/QuickAccessLinkReorderRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/QuickAccessLinkReorderRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedQuickAccessLinkList' + description: '' + /api/v1/profiles/{pk_or_slug}/timeline/: + get: + operationId: v1_profiles_timeline_list + description: Returns historical milestone entries for an institution. + summary: List timeline milestones + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Institutions & Profiles + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedInstitutionTimelineList' + description: '' + post: + operationId: v1_profiles_timeline_create + description: Creates a new milestone in the institution historical timeline. + summary: Add timeline milestone + parameters: + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Institutions & Profiles + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/InstitutionTimelineRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/InstitutionTimelineRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/InstitutionTimelineRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/InstitutionTimeline' + description: '' + /api/v1/profiles/{pk_or_slug}/timeline/{entry_id}/: + delete: + operationId: v1_profiles_timeline_destroy + description: Removes a timeline entry from the institution profile. + summary: Delete timeline milestone + parameters: + - in: path + name: entry_id + schema: + type: integer + required: true + - in: path + name: pk_or_slug + schema: + type: string + required: true + tags: + - Institutions & Profiles + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '204': + description: Entry deleted + /api/v1/profiles/regions/: + get: + operationId: v1_profiles_regions_list + description: Returns list of available regions for dropdowns and filtering. + summary: List all regions + parameters: + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - in: query + name: search + schema: + type: string + description: Search query across region name, code, description + tags: + - Institutions & Profiles + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedRegionList' + description: '' + post: + operationId: v1_profiles_regions_create + description: Creates a new region (Admin or Staff required). + summary: Create a new region + tags: + - Institutions & Profiles + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RegionRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/RegionRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/RegionRequest' + required: true + security: + - jwtAuth: [] + - tokenAuth: [] + - cookieAuth: [] + - jwtAuth: [] + responses: + '201': content: application/json: schema: @@ -6068,6 +6852,18 @@ components: title: Superuser status description: Designates that this user has all permissions without explicitly assigning them. + two_factor_enabled: + type: boolean + two_factor_method: + allOf: + - $ref: '#/components/schemas/TwoFactorMethodEnum' + title: Preferred 2FA Method + require_2fa: + type: boolean + default: false + custom_permissions: + title: Custom Permission Overrides + description: 'Dictionary of permission overrides: {permission_name: True/False}' date_joined: type: string format: date-time @@ -6165,6 +6961,18 @@ components: title: Superuser status description: Designates that this user has all permissions without explicitly assigning them. + two_factor_enabled: + type: boolean + two_factor_method: + allOf: + - $ref: '#/components/schemas/TwoFactorMethodEnum' + title: Preferred 2FA Method + require_2fa: + type: boolean + default: false + custom_permissions: + title: Custom Permission Overrides + description: 'Dictionary of permission overrides: {permission_name: True/False}' required: - email AppTypeEnum: @@ -6220,6 +7028,14 @@ components: - id - updated_at - version + AuthorTypeEnum: + enum: + - user + - institution + type: string + description: |- + * `user` - Individual User + * `institution` - Institution BlankEnum: enum: - '' @@ -6236,14 +7052,18 @@ components: CMSPostStatusEnum: enum: - draft - - review + - pending_review - published + - scheduled + - rejected - archived type: string description: |- * `draft` - Draft - * `review` - Under Review + * `pending_review` - Pending Review * `published` - Published + * `scheduled` - Scheduled + * `rejected` - Rejected * `archived` - Archived Card: type: object @@ -6572,6 +7392,22 @@ components: - fullname - id - user_type + ColorEnum: + enum: + - emerald + - rose + - blue + - purple + - amber + - teal + type: string + description: |- + * `emerald` - سبز رضوی (Emerald) + * `rose` - سرخ زنده (Rose) + * `blue` - آبی دانش (Blue) + * `purple` - بنفش چندرسانه‌ای (Purple) + * `amber` - طلایی ویژه (Amber) + * `teal` - فیروزه‌ای (Teal) ContentLanguageEnum: enum: - fa @@ -6983,8 +7819,9 @@ components: - total_lessons_count CustomTokenObtainPairRequest: type: object - description: JWT token obtain serializer that accepts either 'username' or 'email' - as the credential. + description: |- + JWT token obtain serializer that accepts either 'username' or 'email' as the credential. + Enforces Two-Factor Authentication (2FA) when user.is_2fa_required() is True. properties: email: type: string @@ -7996,6 +8833,13 @@ components: nullable: true title: Target User Role Restriction maxLength: 50 + created_by: + type: integer + readOnly: true + nullable: true + creator_name: + type: string + readOnly: true created_at: type: string format: date-time @@ -8006,6 +8850,8 @@ components: readOnly: true required: - created_at + - created_by + - creator_name - id - title - updated_at @@ -8079,7 +8925,14 @@ components: nullable: true title: Target User Role Restriction maxLength: 50 - responses_count: + created_by: + type: integer + readOnly: true + nullable: true + creator_name: + type: string + readOnly: true + responses_count: type: integer readOnly: true created_at: @@ -8088,6 +8941,8 @@ components: readOnly: true required: - created_at + - created_by + - creator_name - id - responses_count - title @@ -8419,6 +9274,11 @@ components: items: $ref: '#/components/schemas/InstitutionMedia' readOnly: true + quick_access_links: + type: array + items: + $ref: '#/components/schemas/QuickAccessLink' + readOnly: true projects: type: array items: {} @@ -8452,6 +9312,7 @@ components: - name - posts - projects + - quick_access_links - region - region_id - region_name @@ -9742,6 +10603,14 @@ components: - fullname - id - phone_number + Method4fbEnum: + enum: + - email + - totp + type: string + description: |- + * `email` - Email OTP + * `totp` - Authenticator App (TOTP) Notification: type: object properties: @@ -10417,6 +11286,29 @@ components: type: array items: $ref: '#/components/schemas/PostList' + PaginatedPostRevisionList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/PostRevision' PaginatedPostTagList: type: object required: @@ -10486,6 +11378,29 @@ components: type: array items: $ref: '#/components/schemas/ProjectList' + PaginatedQuickAccessLinkList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/QuickAccessLink' PaginatedRegionList: type: object required: @@ -10723,6 +11638,18 @@ components: title: Superuser status description: Designates that this user has all permissions without explicitly assigning them. + two_factor_enabled: + type: boolean + two_factor_method: + allOf: + - $ref: '#/components/schemas/TwoFactorMethodEnum' + title: Preferred 2FA Method + require_2fa: + type: boolean + default: false + custom_permissions: + title: Custom Permission Overrides + description: 'Dictionary of permission overrides: {permission_name: True/False}' PatchedCourseCreateUpdateRequest: type: object properties: @@ -11033,6 +11960,14 @@ components: maxLength: 255 post_type: $ref: '#/components/schemas/PostTypeEnum' + author_type: + allOf: + - $ref: '#/components/schemas/AuthorTypeEnum' + description: |- + Indicates whether the post is authored as an individual user or on behalf of an institution. + + * `user` - Individual User + * `institution` - Institution category: type: integer nullable: true @@ -11042,10 +11977,15 @@ components: allOf: - $ref: '#/components/schemas/CMSPostStatusEnum' title: Publication Status + rejection_reason: + type: string + description: Reason provided by editor when rejecting a post. institution: type: integer nullable: true title: Associated Institution + description: Institution on whose behalf the post is published if author_type + is institution. author_role_label: type: string nullable: true @@ -11135,6 +12075,48 @@ components: format: date nullable: true title: Actual Completion Date + PatchedQuickAccessLinkRequest: + type: object + properties: + title: + type: string + minLength: 1 + title: عنوان خدمت + maxLength: 255 + url: + type: string + minLength: 1 + title: نشانی اینترنتی یا مسیر + maxLength: 500 + description: + type: string + title: توضیح مختصر + maxLength: 500 + icon: + type: string + minLength: 1 + title: شناسه آیکون + maxLength: 50 + color: + allOf: + - $ref: '#/components/schemas/ColorEnum' + title: طیف رنگی + category: + type: string + title: برچسب یا دسته‌بندی + maxLength: 100 + is_live: + type: boolean + title: نشانگر پخش زنده + is_external: + type: boolean + title: باز شدن در برگه جدید + order: + type: integer + title: ترتیب نمایش + is_active: + type: boolean + title: فعال بودن PatchedRegionRequest: type: object properties: @@ -11266,6 +12248,10 @@ components: type: string format: date nullable: true + two_factor_enabled: + type: boolean + two_factor_method: + $ref: '#/components/schemas/TwoFactorMethodEnum' PatchedUserProfileRequest: type: object properties: @@ -11323,6 +12309,36 @@ components: * `completed` - Completed * `pending` - Pending * `failed` - Failed + PostAuthorDisplay: + type: object + properties: + type: + type: string + description: 'Author type: ''user'' or ''institution''' + id: + type: integer + nullable: true + description: ID of the user or institution + name: + type: string + description: Name of the author or institution + slug: + type: string + nullable: true + avatar: + type: string + nullable: true + role_label: + type: string + nullable: true + submitted_by: + type: object + additionalProperties: {} + nullable: true + required: + - id + - name + - type PostAuthorMini: type: object properties: @@ -11400,6 +12416,22 @@ components: * `ar` - Arabic * `ur` - Urdu * `ru` - Russian + PostCategoryRequest: + type: object + properties: + name: + type: string + minLength: 1 + title: Category Name + maxLength: 255 + language: + $ref: '#/components/schemas/PostCategoryLanguageEnum' + description: + type: string + nullable: true + title: Category Description + required: + - name PostComment: type: object properties: @@ -11475,6 +12507,14 @@ components: maxLength: 255 post_type: $ref: '#/components/schemas/PostTypeEnum' + author_type: + allOf: + - $ref: '#/components/schemas/AuthorTypeEnum' + description: |- + Indicates whether the post is authored as an individual user or on behalf of an institution. + + * `user` - Individual User + * `institution` - Institution category: type: integer nullable: true @@ -11484,10 +12524,15 @@ components: allOf: - $ref: '#/components/schemas/CMSPostStatusEnum' title: Publication Status + rejection_reason: + type: string + description: Reason provided by editor when rejecting a post. institution: type: integer nullable: true title: Associated Institution + description: Institution on whose behalf the post is published if author_type + is institution. author_role_label: type: string nullable: true @@ -11542,6 +12587,21 @@ components: post_type_display: type: string readOnly: true + author_type: + allOf: + - $ref: '#/components/schemas/AuthorTypeEnum' + description: |- + Indicates whether the post is authored as an individual user or on behalf of an institution. + + * `user` - Individual User + * `institution` - Institution + author_type_display: + type: string + readOnly: true + author_display: + allOf: + - $ref: '#/components/schemas/PostAuthorDisplay' + readOnly: true category: allOf: - $ref: '#/components/schemas/PostCategory' @@ -11552,6 +12612,9 @@ components: allOf: - $ref: '#/components/schemas/CMSPostStatusEnum' title: Publication Status + rejection_reason: + type: string + description: Reason provided by editor when rejecting a post. author: allOf: - $ref: '#/components/schemas/PostAuthorMini' @@ -11605,11 +12668,27 @@ components: is_liked: type: boolean readOnly: true + has_pending_revision: + type: boolean + readOnly: true + pending_revision: + allOf: + - $ref: '#/components/schemas/PostRevision' + readOnly: true comments: type: array items: $ref: '#/components/schemas/PostComment' readOnly: true + reviewed_by: + allOf: + - $ref: '#/components/schemas/PostAuthorMini' + readOnly: true + reviewed_at: + type: string + format: date-time + readOnly: true + nullable: true created_at: type: string format: date-time @@ -11620,21 +12699,87 @@ components: readOnly: true required: - author + - author_display + - author_type_display - category - comments - comments_count - content - created_at + - has_pending_revision - id - institution - is_liked - likes_count + - pending_revision - post_type_display - reading_time_minutes + - reviewed_at + - reviewed_by - slug - title - updated_at - views_count + PostDetailRequest: + type: object + properties: + title: + type: string + minLength: 1 + title: Post Title + maxLength: 255 + post_type: + $ref: '#/components/schemas/PostTypeEnum' + author_type: + allOf: + - $ref: '#/components/schemas/AuthorTypeEnum' + description: |- + Indicates whether the post is authored as an individual user or on behalf of an institution. + + * `user` - Individual User + * `institution` - Institution + language: + $ref: '#/components/schemas/ContentLanguageEnum' + status: + allOf: + - $ref: '#/components/schemas/CMSPostStatusEnum' + title: Publication Status + rejection_reason: + type: string + description: Reason provided by editor when rejecting a post. + author_role_label: + type: string + nullable: true + description: e.g. Senior Researcher, Center Director, Diplomatic Envoy + maxLength: 150 + author_custom_avatar: + type: string + format: binary + nullable: true + title: Custom Author Avatar + featured_image: + type: string + format: binary + nullable: true + title: Featured Cover Image + excerpt: + type: string + nullable: true + title: Excerpt / Summary + description: Brief summary for search results and cards. + content: + type: string + minLength: 1 + title: Rich Text / Markdown Content + tags: {} + is_featured: + type: boolean + publish_date: + type: string + format: date-time + required: + - content + - title PostInstitutionMini: type: object properties: @@ -11731,6 +12876,22 @@ components: post_type_display: type: string readOnly: true + author_type: + allOf: + - $ref: '#/components/schemas/AuthorTypeEnum' + readOnly: true + description: |- + Indicates whether the post is authored as an individual user or on behalf of an institution. + + * `user` - Individual User + * `institution` - Institution + author_type_display: + type: string + readOnly: true + author_display: + allOf: + - $ref: '#/components/schemas/PostAuthorDisplay' + readOnly: true category: type: integer readOnly: true @@ -11748,6 +12909,10 @@ components: - $ref: '#/components/schemas/CMSPostStatusEnum' readOnly: true title: Publication Status + rejection_reason: + type: string + readOnly: true + description: Reason provided by editor when rejecting a post. author: allOf: - $ref: '#/components/schemas/PostAuthorMini' @@ -11763,76 +12928,235 @@ components: description: e.g. Senior Researcher, Center Director, Diplomatic Envoy author_custom_avatar: type: string - format: uri - nullable: true + format: uri + nullable: true + readOnly: true + title: Custom Author Avatar + featured_image: + type: string + format: uri + nullable: true + readOnly: true + title: Featured Cover Image + excerpt: + type: string + readOnly: true + nullable: true + title: Excerpt / Summary + description: Brief summary for search results and cards. + tags: + readOnly: true + reading_time_minutes: + type: integer + readOnly: true + title: Reading Time (Minutes) + views_count: + type: integer + readOnly: true + likes_count: + type: integer + readOnly: true + comments_count: + type: integer + readOnly: true + is_featured: + type: boolean + readOnly: true + publish_date: + type: string + format: date-time + readOnly: true + is_liked: + type: boolean + readOnly: true + has_pending_revision: + type: boolean + readOnly: true + reviewed_at: + type: string + format: date-time + readOnly: true + nullable: true + created_at: + type: string + format: date-time + readOnly: true + required: + - author + - author_custom_avatar + - author_display + - author_role_label + - author_type + - author_type_display + - category + - category_name + - comments_count + - created_at + - excerpt + - featured_image + - has_pending_revision + - id + - institution + - is_featured + - is_liked + - language + - likes_count + - post_type + - post_type_display + - publish_date + - reading_time_minutes + - rejection_reason + - reviewed_at + - slug + - status + - tags + - title + - views_count + PostRejectRequestRequest: + type: object + properties: + reason: + type: string + minLength: 1 + description: Explanation for rejection + required: + - reason + PostRevision: + type: object + properties: + id: + type: integer + readOnly: true + post: + type: integer + readOnly: true + title: Original Post + author: + allOf: + - $ref: '#/components/schemas/PostAuthorMini' + readOnly: true + title: + type: string + readOnly: true + title: Revised Title + excerpt: + type: string + readOnly: true + title: Revised Excerpt + content: + type: string readOnly: true - title: Custom Author Avatar + title: Revised Content + tags: + readOnly: true + title: Revised Tags featured_image: type: string format: uri nullable: true readOnly: true - title: Featured Cover Image - excerpt: - type: string + title: Revised Featured Image + category: + type: integer readOnly: true nullable: true - title: Excerpt / Summary - description: Brief summary for search results and cards. - tags: - readOnly: true - reading_time_minutes: - type: integer + title: Revised Category + category_name: + type: string readOnly: true - title: Reading Time (Minutes) - views_count: - type: integer + nullable: true + status: + allOf: + - $ref: '#/components/schemas/PostRevisionStatusEnum' readOnly: true - likes_count: - type: integer + title: Revision Status + status_display: + type: string readOnly: true - comments_count: - type: integer + rejection_reason: + type: string readOnly: true - is_featured: - type: boolean + reviewed_by: + allOf: + - $ref: '#/components/schemas/PostAuthorMini' readOnly: true - publish_date: + reviewed_at: type: string format: date-time readOnly: true - is_liked: - type: boolean - readOnly: true + nullable: true created_at: type: string format: date-time readOnly: true + title: Submitted At + updated_at: + type: string + format: date-time + readOnly: true required: - author - - author_custom_avatar - - author_role_label - category - category_name - - comments_count + - content - created_at - excerpt - featured_image - id - - institution - - is_featured - - is_liked - - language - - likes_count - - post_type - - post_type_display - - publish_date - - reading_time_minutes - - slug + - post + - rejection_reason + - reviewed_at + - reviewed_by - status + - status_display - tags - title - - views_count + - updated_at + PostRevisionApproveResponse: + type: object + properties: + detail: + type: string + post: + $ref: '#/components/schemas/PostDetail' + revision: + $ref: '#/components/schemas/PostRevision' + required: + - detail + - post + - revision + PostRevisionRejectRequestRequest: + type: object + properties: + reason: + type: string + minLength: 1 + description: Explanation for rejection + required: + - reason + PostRevisionRejectResponse: + type: object + properties: + detail: + type: string + post: + $ref: '#/components/schemas/PostDetail' + revision: + $ref: '#/components/schemas/PostRevision' + required: + - detail + - post + - revision + PostRevisionStatusEnum: + enum: + - pending + - approved + - rejected + type: string + description: |- + * `pending` - Pending Review + * `approved` - Approved & Merged + * `rejected` - Rejected PostTag: type: object properties: @@ -12321,6 +13645,128 @@ components: - organization - recipient_name - uuid + QuickAccessLink: + type: object + properties: + id: + type: integer + readOnly: true + institution: + type: integer + readOnly: true + title: مؤسسه مربوطه + institution_id: + type: integer + readOnly: true + title: + type: string + title: عنوان خدمت + maxLength: 255 + url: + type: string + title: نشانی اینترنتی یا مسیر + maxLength: 500 + description: + type: string + title: توضیح مختصر + maxLength: 500 + icon: + type: string + title: شناسه آیکون + maxLength: 50 + color: + allOf: + - $ref: '#/components/schemas/ColorEnum' + title: طیف رنگی + category: + type: string + title: برچسب یا دسته‌بندی + maxLength: 100 + is_live: + type: boolean + title: نشانگر پخش زنده + is_external: + type: boolean + title: باز شدن در برگه جدید + order: + type: integer + title: ترتیب نمایش + is_active: + type: boolean + title: فعال بودن + created_at: + type: string + format: date-time + readOnly: true + title: تاریخ ایجاد + updated_at: + type: string + format: date-time + readOnly: true + title: آخرین بروزرسانی + required: + - created_at + - id + - institution + - institution_id + - title + - updated_at + - url + QuickAccessLinkReorderRequest: + type: object + properties: + order: + type: array + items: + type: integer + description: Ordered list of QuickAccessLink IDs. + required: + - order + QuickAccessLinkRequest: + type: object + properties: + title: + type: string + minLength: 1 + title: عنوان خدمت + maxLength: 255 + url: + type: string + minLength: 1 + title: نشانی اینترنتی یا مسیر + maxLength: 500 + description: + type: string + title: توضیح مختصر + maxLength: 500 + icon: + type: string + minLength: 1 + title: شناسه آیکون + maxLength: 50 + color: + allOf: + - $ref: '#/components/schemas/ColorEnum' + title: طیف رنگی + category: + type: string + title: برچسب یا دسته‌بندی + maxLength: 100 + is_live: + type: boolean + title: نشانگر پخش زنده + is_external: + type: boolean + title: باز شدن در برگه جدید + order: + type: integer + title: ترتیب نمایش + is_active: + type: boolean + title: فعال بودن + required: + - title + - url QuizQuestionResultItem: type: object properties: @@ -13384,6 +14830,122 @@ components: minLength: 1 required: - token + TwoFactorBackupCodesRegenerateRequest: + type: object + properties: + password: + type: string + writeOnly: true + description: Optional current password confirmation. + TwoFactorDisableRequest: + type: object + properties: + password: + type: string + writeOnly: true + description: Optional current password confirmation. + TwoFactorMethodEnum: + enum: + - email + - totp + type: string + description: |- + * `email` - Email OTP + * `totp` - Authenticator App (TOTP) + TwoFactorResendEmailRequest: + type: object + properties: + temp_token: + type: string + minLength: 1 + description: Ephemeral 2FA challenge token. + required: + - temp_token + TwoFactorSetupConfirmRequest: + type: object + properties: + method: + allOf: + - $ref: '#/components/schemas/Method4fbEnum' + default: totp + description: |- + 2FA method being confirmed ('totp' or 'email'). + + * `email` - Email OTP + * `totp` - Authenticator App (TOTP) + code: + type: string + minLength: 6 + description: 6-digit verification code from Authenticator app or Email. + maxLength: 8 + required: + - code + TwoFactorSetupInitiateRequest: + type: object + properties: + method: + allOf: + - $ref: '#/components/schemas/Method4fbEnum' + default: totp + description: |- + 2FA method to set up ('totp' or 'email'). + + * `email` - Email OTP + * `totp` - Authenticator App (TOTP) + TwoFactorToggleRequest: + type: object + properties: + enabled: + type: boolean + description: Set True to enable 2FA or False to disable 2FA. + method: + allOf: + - $ref: '#/components/schemas/Method4fbEnum' + description: |- + Optional preferred 2FA method ('email' or 'totp'). + + * `email` - Email OTP + * `totp` - Authenticator App (TOTP) + required: + - enabled + TwoFactorVerifyMethodEnum: + enum: + - email + - totp + - backup + - backup_code + type: string + description: |- + * `email` - Email OTP + * `totp` - Authenticator App (TOTP) + * `backup` - Backup Recovery Code + * `backup_code` - Backup Recovery Code + TwoFactorVerifyRequest: + type: object + properties: + temp_token: + type: string + minLength: 1 + description: Ephemeral 2FA challenge token returned by /api/v1/auth/token/. + code: + type: string + minLength: 6 + description: 6-digit OTP code (Email or TOTP) or 8-character backup recovery + code. + maxLength: 16 + method: + allOf: + - $ref: '#/components/schemas/TwoFactorVerifyMethodEnum' + description: |- + Verification method used. Defaults to the challenge's active method. + + * `email` - Email OTP + * `totp` - Authenticator App (TOTP) + * `backup` - Backup Recovery Code + * `backup_code` - Backup Recovery Code + required: + - code + - temp_token UnifiedSearchResponse: type: object properties: @@ -13576,6 +15138,9 @@ components: is_staff: type: boolean readOnly: true + role: + type: string + readOnly: true roles: type: array items: {} @@ -13583,6 +15148,15 @@ components: permissions: type: array items: {} + description: |- + Returns active permission capability flags where boolean value is True, + dynamically derived from the user's assigned database Role and custom overrides. + readOnly: true + permission_flags: + type: object + additionalProperties: {} + description: Returns full boolean flags dictionary dynamically resolved + from Role and custom_permissions. readOnly: true managed_regions: type: array @@ -13599,28 +15173,54 @@ components: is_verified: type: boolean readOnly: true + two_factor_enabled: + type: boolean + readOnly: true + two_factor_method: + type: string + readOnly: true + is_2fa_forced_by_admin: + type: boolean + readOnly: true + require_2fa: + type: boolean + readOnly: true + has_totp_configured: + type: boolean + readOnly: true + backup_codes_count: + type: integer + readOnly: true required: - address + - backup_codes_count - date_joined - email - first_name + - has_totp_configured - id - institution - institution_id + - is_2fa_forced_by_admin - is_active - is_staff - is_verified - last_name - managed_regions - name + - permission_flags - permissions - phone - position - preferred_language - rank - regional_permissions + - require_2fa + - role - roles - title + - two_factor_enabled + - two_factor_method - user_type - username - verification_status