diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index bbcbbe7..84a13eb 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -7,14 +7,16 @@ assignees: ''
---
-**Is your feature request related to a problem? Please describe.**
-A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+**Team:**
+
-**Describe the solution you'd like**
-A clear and concise description of what you want to happen.
+**Overview:**
+
+
+**Additional Instructions:**
+
+
+**Deadline:**
+
-**Describe alternatives you've considered**
-A clear and concise description of any alternative solutions or features you've considered.
-**Additional context**
-Add any other context or screenshots about the feature request here.
diff --git a/.github/workflows/prevent-main-pr.yml b/.github/workflows/prevent-main-pr.yml
new file mode 100644
index 0000000..03f0f52
--- /dev/null
+++ b/.github/workflows/prevent-main-pr.yml
@@ -0,0 +1,15 @@
+name: Prevent PR to Main
+
+on:
+ pull_request:
+ branches:
+ - main # This triggers the action when a PR is made to "main"
+
+jobs:
+ block-main-pr:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Fail PR if targeting main
+ run: |
+ echo "❌ Pull requests to 'main' are not allowed. Please target 'develop' instead."
+ exit 1
diff --git a/.idea/Dev-Track-App.iml b/.idea/Dev-Track-App.iml
index 7e902b9..f72ad73 100644
--- a/.idea/Dev-Track-App.iml
+++ b/.idea/Dev-Track-App.iml
@@ -30,6 +30,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml
index 935f4ca..d6245bd 100644
--- a/.idea/libraries/Dart_Packages.xml
+++ b/.idea/libraries/Dart_Packages.xml
@@ -37,10 +37,17 @@
+
+
+
+
+
+
+
-
+
@@ -51,6 +58,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -61,7 +82,49 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -75,7 +138,14 @@
-
+
+
+
+
+
+
+
+
@@ -103,7 +173,7 @@
-
+
@@ -114,6 +184,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -138,7 +264,7 @@
-
+
@@ -163,6 +289,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -173,21 +313,21 @@
-
+
-
+
-
+
@@ -212,10 +352,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
@@ -226,6 +408,13 @@
+
+
+
+
+
+
+
@@ -233,6 +422,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -292,35 +530,35 @@
-
+
-
+
-
+
-
+
-
+
@@ -341,7 +579,7 @@
-
+
@@ -362,14 +600,21 @@
-
+
+
+
+
+
+
+
+
-
+
@@ -381,31 +626,65 @@
-
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
-
+
+
+
-
-
-
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -413,18 +692,19 @@
-
-
-
-
-
+
+
+
+
+
-
+
-
-
+
+
+
diff --git a/.vscode/launch.json b/.vscode/launch.json
index d30496b..95464f9 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -16,7 +16,8 @@
],
"django": true,
"autoStartBrowser": false,
- "program": "${workspaceFolder}\\manage.py"
+ "justMyCode": false,
+ "program": "${workspaceFolder}/backend/myproject/manage.py"
}
]
}
\ No newline at end of file
diff --git a/backend/myproject/.env b/backend/myproject/.env
new file mode 100644
index 0000000..361fe0b
--- /dev/null
+++ b/backend/myproject/.env
@@ -0,0 +1,7 @@
+DB_NAME=postgres
+DB_USER=postgres.swzgdimtkynmkzftxjjv
+DB_PASSWORD=GOMxZBnMwtF5VPYk
+DB_HOST=aws-0-ap-south-1.pooler.supabase.com
+DB_PORT=6543 # Try 6543 if 5432 fails
+DATABASE_URL=postgres://postgres:GOMxZBnMwtF5VPYk@aws-0-ap-south-1.pooler.supabase.com:6543/postgres?sslmode=require
+DJANGO_ENV=test
diff --git a/backend/myproject/announcements/__pycache__/__init__.cpython-313.pyc b/backend/myproject/announcements/__pycache__/__init__.cpython-313.pyc
new file mode 100644
index 0000000..9a677e7
Binary files /dev/null and b/backend/myproject/announcements/__pycache__/__init__.cpython-313.pyc differ
diff --git a/backend/myproject/announcements/__pycache__/admin.cpython-313.pyc b/backend/myproject/announcements/__pycache__/admin.cpython-313.pyc
new file mode 100644
index 0000000..1b690e4
Binary files /dev/null and b/backend/myproject/announcements/__pycache__/admin.cpython-313.pyc differ
diff --git a/backend/myproject/announcements/__pycache__/api.cpython-313.pyc b/backend/myproject/announcements/__pycache__/api.cpython-313.pyc
new file mode 100644
index 0000000..421ff8e
Binary files /dev/null and b/backend/myproject/announcements/__pycache__/api.cpython-313.pyc differ
diff --git a/backend/myproject/announcements/__pycache__/apps.cpython-313.pyc b/backend/myproject/announcements/__pycache__/apps.cpython-313.pyc
new file mode 100644
index 0000000..65bb02b
Binary files /dev/null and b/backend/myproject/announcements/__pycache__/apps.cpython-313.pyc differ
diff --git a/backend/myproject/announcements/__pycache__/models.cpython-313.pyc b/backend/myproject/announcements/__pycache__/models.cpython-313.pyc
new file mode 100644
index 0000000..a47c39e
Binary files /dev/null and b/backend/myproject/announcements/__pycache__/models.cpython-313.pyc differ
diff --git a/backend/myproject/announcements/__pycache__/schemas.cpython-313.pyc b/backend/myproject/announcements/__pycache__/schemas.cpython-313.pyc
new file mode 100644
index 0000000..58eb928
Binary files /dev/null and b/backend/myproject/announcements/__pycache__/schemas.cpython-313.pyc differ
diff --git a/backend/myproject/announcements/__pycache__/services.cpython-313.pyc b/backend/myproject/announcements/__pycache__/services.cpython-313.pyc
new file mode 100644
index 0000000..112e0ed
Binary files /dev/null and b/backend/myproject/announcements/__pycache__/services.cpython-313.pyc differ
diff --git a/backend/myproject/announcements/api.py b/backend/myproject/announcements/api.py
new file mode 100644
index 0000000..2261c8b
--- /dev/null
+++ b/backend/myproject/announcements/api.py
@@ -0,0 +1,52 @@
+from django.shortcuts import get_object_or_404
+from django.middleware.csrf import get_token
+from ninja_extra import NinjaExtraAPI, ControllerBase, api_controller, route
+from .models import PostModel
+from . import schemas
+from .schemas import PostSchema
+from .services import AnnouncementService
+
+@api_controller("/posts", tags="Posts")
+class PostAPIController(ControllerBase):
+ def __init__(self) -> None:
+ self.post_service = AnnouncementService()
+
+ # API endpoint to create a new post (admin only).
+ @route.post("/add", url_name="Add Post")
+ def create_post(self, request, payload: schemas.PostCreateSchema):
+ admin_check = self.post_service._check_admin(request)
+ if admin_check:
+ return admin_check
+
+ post = self.post_service.create_post(request, payload)
+ return {
+ "message": "Post created successfully",
+ "post_id": post.id,
+ "csrf_token": get_token(request)
+ }
+
+ # API endpoint to list all posts.
+ @route.get("/", url_name="List Posts")
+ def list_posts(self, request):
+ return self.post_service.list_posts()
+
+ @route.put("/{post_id}", url_name="Update Post", response=PostSchema)
+ def update_post(self, request, post_id: int, payload: schemas.PostUpdateSchema):
+ self.post_service._check_admin(request)
+ post = get_object_or_404(PostModel, id=post_id)
+ post = self.post_service.update_post(post, payload)
+ return schemas.PostSchema.from_orm(post)
+
+ # API endpoint to delete a post (admin only).
+ @route.delete("/{post_id}", url_name="Delete Post")
+ def delete_post(self, request, post_id: int):
+ admin_check = self.post_service._check_admin(request)
+ if admin_check:
+ return admin_check
+
+ post = get_object_or_404(PostModel, id=post_id)
+ self.post_service.delete_post(post)
+ return {"message": "Post deleted successfully"}
+
+# Register the PostAPIController with your NinjaExtraAPI instance.
+api = NinjaExtraAPI(csrf=False)
diff --git a/backend/myproject/announcements/migrations/0001_initial.py b/backend/myproject/announcements/migrations/0001_initial.py
index a5c13e1..e7941e9 100644
--- a/backend/myproject/announcements/migrations/0001_initial.py
+++ b/backend/myproject/announcements/migrations/0001_initial.py
@@ -1,7 +1,5 @@
-# Generated by Django 5.0.7 on 2024-09-03 16:35
+# Generated by Django 5.1.6 on 2025-02-18 19:17
-import django.db.models.deletion
-from django.conf import settings
from django.db import migrations, models
@@ -10,7 +8,6 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
- migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
@@ -22,7 +19,6 @@ class Migration(migrations.Migration):
('created_at', models.DateTimeField(auto_now_add=True)),
('is_read', models.BooleanField(default=False)),
('notification_type', models.CharField(choices=[('EN', 'enrollment'), ('SC', 'scrum'), ('DE', 'deadline')], max_length=2)),
- ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]
diff --git a/backend/myproject/announcements/migrations/0002_initial.py b/backend/myproject/announcements/migrations/0002_initial.py
new file mode 100644
index 0000000..249959d
--- /dev/null
+++ b/backend/myproject/announcements/migrations/0002_initial.py
@@ -0,0 +1,23 @@
+# Generated by Django 5.1.6 on 2025-02-18 19:17
+
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ('announcements', '0001_initial'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='notificationmodel',
+ name='user',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
+ ),
+ ]
diff --git a/backend/myproject/announcements/migrations/0003_postmodel.py b/backend/myproject/announcements/migrations/0003_postmodel.py
new file mode 100644
index 0000000..93abcf5
--- /dev/null
+++ b/backend/myproject/announcements/migrations/0003_postmodel.py
@@ -0,0 +1,26 @@
+# Generated by Django 5.1.6 on 2025-02-20 04:53
+
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('announcements', '0002_initial'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='PostModel',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('title', models.CharField(max_length=100)),
+ ('description', models.TextField(blank=True, max_length=512)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ ]
diff --git a/backend/myproject/announcements/migrations/__pycache__/0001_initial.cpython-313.pyc b/backend/myproject/announcements/migrations/__pycache__/0001_initial.cpython-313.pyc
new file mode 100644
index 0000000..d238702
Binary files /dev/null and b/backend/myproject/announcements/migrations/__pycache__/0001_initial.cpython-313.pyc differ
diff --git a/backend/myproject/announcements/migrations/__pycache__/0002_initial.cpython-313.pyc b/backend/myproject/announcements/migrations/__pycache__/0002_initial.cpython-313.pyc
new file mode 100644
index 0000000..60fa7fa
Binary files /dev/null and b/backend/myproject/announcements/migrations/__pycache__/0002_initial.cpython-313.pyc differ
diff --git a/backend/myproject/announcements/migrations/__pycache__/0003_postmodel.cpython-313.pyc b/backend/myproject/announcements/migrations/__pycache__/0003_postmodel.cpython-313.pyc
new file mode 100644
index 0000000..a2df112
Binary files /dev/null and b/backend/myproject/announcements/migrations/__pycache__/0003_postmodel.cpython-313.pyc differ
diff --git a/backend/myproject/announcements/migrations/__pycache__/__init__.cpython-313.pyc b/backend/myproject/announcements/migrations/__pycache__/__init__.cpython-313.pyc
new file mode 100644
index 0000000..3abf1ca
Binary files /dev/null and b/backend/myproject/announcements/migrations/__pycache__/__init__.cpython-313.pyc differ
diff --git a/backend/myproject/announcements/models.py b/backend/myproject/announcements/models.py
index b896e38..40540c5 100644
--- a/backend/myproject/announcements/models.py
+++ b/backend/myproject/announcements/models.py
@@ -5,7 +5,7 @@
-class AnnouncementModel:
+class PostModel(models.Model):
title = models.CharField(max_length=100, blank=False, null=False)
description = models.TextField(max_length=512, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
diff --git a/backend/myproject/announcements/schemas.py b/backend/myproject/announcements/schemas.py
new file mode 100644
index 0000000..fa44630
--- /dev/null
+++ b/backend/myproject/announcements/schemas.py
@@ -0,0 +1,29 @@
+from ninja import Schema
+from typing import Optional
+from datetime import datetime
+
+
+class PostSchema(Schema):
+ id: int
+ title: str
+ description: str
+ created_at: datetime
+ created_by: int
+
+ @classmethod
+ def from_orm(cls, obj):
+ return cls(
+ id=obj.id,
+ title=obj.title,
+ description=obj.description,
+ created_at=obj.created_at,
+ created_by=obj.created_by.id,
+ )
+
+class PostCreateSchema(Schema):
+ title: str
+ description: Optional[str] = ""
+
+class PostUpdateSchema(Schema):
+ title: Optional[str] = None
+ description: Optional[str] = None
diff --git a/backend/myproject/announcements/services.py b/backend/myproject/announcements/services.py
index e69de29..bc3d35e 100644
--- a/backend/myproject/announcements/services.py
+++ b/backend/myproject/announcements/services.py
@@ -0,0 +1,42 @@
+from django.shortcuts import get_object_or_404
+from .models import PostModel
+from ninja.errors import HttpError
+
+class AnnouncementService:
+ def _check_admin(self, request):
+ if request.user.role != "admin":
+ raise HttpError(401, "Unauthorized: Admin access required.")
+ return None
+
+ def create_post(self, request, payload):
+ post = PostModel.objects.create(
+ title=payload.title,
+ description=payload.description,
+ created_by=request.user
+ )
+ return post
+
+ def list_posts(self):
+ posts = PostModel.objects.all().order_by("created_at")
+ result = [
+ {
+ "id": post.id,
+ "title": post.title,
+ "description": post.description,
+ "created_at": post.created_at.isoformat(),
+ "created_by": post.created_by.id,
+ }
+ for post in posts
+ ]
+ return result
+
+ def update_post(self, post, payload):
+ if payload.title is not None:
+ post.title = payload.title
+ if payload.description is not None:
+ post.description = payload.description
+ post.save()
+ return post
+
+ def delete_post(self, post):
+ post.delete()
diff --git a/backend/myproject/db.sqlite3 b/backend/myproject/db.sqlite3
index 9be5815..61ada48 100644
Binary files a/backend/myproject/db.sqlite3 and b/backend/myproject/db.sqlite3 differ
diff --git a/backend/myproject/manage.py b/backend/myproject/manage.py
index 222f06d..79b729e 100644
--- a/backend/myproject/manage.py
+++ b/backend/myproject/manage.py
@@ -2,11 +2,20 @@
"""Django's command-line utility for administrative tasks."""
import os
import sys
+import debugpy
+
# os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
+
+ # if "runserver" in sys.argv:
+ # import debugpy
+ # debugpy.listen(("0.0.0.0", 9000))
+ # print("Waiting for debugger attach...")
+ # debugpy.wait_for_client()
+
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
diff --git a/backend/myproject/members/__pycache__/__init__.cpython-313.pyc b/backend/myproject/members/__pycache__/__init__.cpython-313.pyc
new file mode 100644
index 0000000..cc45b8d
Binary files /dev/null and b/backend/myproject/members/__pycache__/__init__.cpython-313.pyc differ
diff --git a/backend/myproject/members/__pycache__/admin.cpython-313.pyc b/backend/myproject/members/__pycache__/admin.cpython-313.pyc
new file mode 100644
index 0000000..80ee687
Binary files /dev/null and b/backend/myproject/members/__pycache__/admin.cpython-313.pyc differ
diff --git a/backend/myproject/members/__pycache__/api.cpython-313.pyc b/backend/myproject/members/__pycache__/api.cpython-313.pyc
new file mode 100644
index 0000000..f8bd8b0
Binary files /dev/null and b/backend/myproject/members/__pycache__/api.cpython-313.pyc differ
diff --git a/backend/myproject/members/__pycache__/apps.cpython-313.pyc b/backend/myproject/members/__pycache__/apps.cpython-313.pyc
new file mode 100644
index 0000000..f544a45
Binary files /dev/null and b/backend/myproject/members/__pycache__/apps.cpython-313.pyc differ
diff --git a/backend/myproject/members/__pycache__/managers.cpython-313.pyc b/backend/myproject/members/__pycache__/managers.cpython-313.pyc
new file mode 100644
index 0000000..e017f28
Binary files /dev/null and b/backend/myproject/members/__pycache__/managers.cpython-313.pyc differ
diff --git a/backend/myproject/members/__pycache__/models.cpython-313.pyc b/backend/myproject/members/__pycache__/models.cpython-313.pyc
new file mode 100644
index 0000000..3b837cc
Binary files /dev/null and b/backend/myproject/members/__pycache__/models.cpython-313.pyc differ
diff --git a/backend/myproject/members/__pycache__/schemas.cpython-313.pyc b/backend/myproject/members/__pycache__/schemas.cpython-313.pyc
new file mode 100644
index 0000000..e792100
Binary files /dev/null and b/backend/myproject/members/__pycache__/schemas.cpython-313.pyc differ
diff --git a/backend/myproject/members/__pycache__/services.cpython-313.pyc b/backend/myproject/members/__pycache__/services.cpython-313.pyc
new file mode 100644
index 0000000..aa1053e
Binary files /dev/null and b/backend/myproject/members/__pycache__/services.cpython-313.pyc differ
diff --git a/backend/myproject/members/admin.py b/backend/myproject/members/admin.py
index 8c38f3f..a75af42 100644
--- a/backend/myproject/members/admin.py
+++ b/backend/myproject/members/admin.py
@@ -1,3 +1,9 @@
from django.contrib import admin
-# Register your models here.
+
+from .models import CustomUser
+
+@admin.register(CustomUser)
+class CustomUserAdmin(admin.ModelAdmin):
+ list_display = ("email", "first_name", "last_name", "srn", "is_active", "is_staff")
+ search_fields = ("email", "srn")
\ No newline at end of file
diff --git a/backend/myproject/members/api.py b/backend/myproject/members/api.py
index 5d3f0c3..61719fc 100644
--- a/backend/myproject/members/api.py
+++ b/backend/myproject/members/api.py
@@ -1,106 +1,58 @@
-from django.contrib.auth import authenticate, logout
-from .models import CustomUser as User
-from . import schemas
from django.http import JsonResponse
+from ninja_extra import NinjaExtraAPI, ControllerBase, api_controller, route
+from ninja import UploadedFile
from django.shortcuts import get_object_or_404
-from ninja_jwt.controller import NinjaJWTDefaultController
-from ninja_jwt.authentication import JWTAuth
-from ninja_jwt.tokens import RefreshToken
-from ninja_extra import NinjaExtraAPI, ControllerBase, api_controller, route
+from django.middleware.csrf import get_token
+from . import schemas
+from .models import CustomUser as User
+from .services import UserAuthService
from projects.api import ProjectsAPI
+from announcements.api import PostAPIController
+from registrations.api import ProjectApplicationAPI
-
-
-@api_controller("/user", tags="User Authentication", auth=JWTAuth())
+@api_controller("/user", tags="User Authentication")
class UserAuthAPI(ControllerBase):
+ def __init__(self) -> None:
+ self.auth_service = UserAuthService()
- #API call for user to login by giving username and password. JWT based session will be created after this.
- @route.post("/login", url_name="User login", auth=None)
+ # API call for user to login by giving username and password.
+ @route.post("/login", url_name="User login", auth=None, response=schemas.LoginResponseSchema)
def login_view(self, request, payload: schemas.SignInSchema):
- user = authenticate(request, username=payload.email, password=payload.password)
- if user is not None:
- refresh= RefreshToken.for_user(user)
- return {
- 'refresh': str(refresh),
- 'access': str(refresh.access_token)
- }
+ result = self.auth_service.login_user(request, payload.email, payload.password)
+ if result:
+ return result
return JsonResponse({"detail": "Invalid credentials"}, status=401)
-
- #API call made by a user to logout., auth=JWTAuth()
- @route.post("/logout", auth=None)
+ # API call made by a user to logout.
+ @route.post("/logout")
def logout_view(self, request):
- logout(request)
- return {"message": "Logged out"}
-
-
- #API call made by user to view their profile.
- @route.get("/user", auth=JWTAuth())
- def user(self, request):
- return {
- "username": request.user.username,
- "email": request.user.email,
- "github": request.user.github,
- "fname" : request.user.fname,
- }
-
-
- #API post request for a new user to create a new account.
- @route.post("/register", auth=None)
- def register(self, request, payload: schemas.RegisterSchema):
- try:
- user = User.objects.create_user(
- username=payload.email,
- email=payload.email,
- password=payload.password,
- first_name= payload.first_name,
- last_name= payload.last_name,
- )
- refresh = RefreshToken.for_user(user)
- return {
- "refresh": str(refresh),
- "access": str(refresh.access_token),
- }
- except Exception as e:
- return JsonResponse({"error": str(e)}, status=400)
-
-
-
- @route.put("/edit", auth=JWTAuth() ,response=schemas.RegisterSchema)
- def edit_profile(self, request, payload : schemas.RegisterSchema):
- user= get_object_or_404(User, srn=request.user.srn)
- for attr, value in payload.dict().items():
- setattr(user, attr, value)
- user.save()
+ return self.auth_service.logout_user(request)
+
+ # API call made by user to view their profile.
+ @route.get("/user", response=schemas.UserProfileResponseSchema)
+ def user_profile(self, request):
+ result = self.auth_service.get_user_profile(request)
+ if result:
+ return result
+ return JsonResponse({"detail": "Not logged in"}, status=401)
+
+ # API call made by user to edit their profile.
+ @route.put("/edit", response=schemas.RegisterSchema)
+ def edit_profile(self, request, payload: schemas.RegisterSchema):
+ if not request.user.is_authenticated:
+ return JsonResponse({"detail": "Authentication required"}, status=401)
+ user = self.auth_service.edit_user_profile(request, payload)
return user
-
-
-
+ # API call to import users using the management command.
+ @route.post("/import-users/", url_name="Import Users")
+ def import_users(self, request, file: UploadedFile):
+ return self.auth_service.import_users_from_file(file)
+# Register controllers with the NinjaExtraAPI instance.
api = NinjaExtraAPI(csrf=False)
-api.register_controllers(NinjaJWTDefaultController)
api.register_controllers(UserAuthAPI)
+api.register_controllers(PostAPIController)
api.register_controllers(ProjectsAPI)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+api.register_controllers(ProjectApplicationAPI)
diff --git a/backend/myproject/members/management/__init__.py b/backend/myproject/members/management/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/backend/myproject/members/management/__pycache__/__init__.cpython-313.pyc b/backend/myproject/members/management/__pycache__/__init__.cpython-313.pyc
new file mode 100644
index 0000000..0432021
Binary files /dev/null and b/backend/myproject/members/management/__pycache__/__init__.cpython-313.pyc differ
diff --git a/backend/myproject/members/management/commands/__init__.py b/backend/myproject/members/management/commands/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/backend/myproject/members/management/commands/__pycache__/__init__.cpython-313.pyc b/backend/myproject/members/management/commands/__pycache__/__init__.cpython-313.pyc
new file mode 100644
index 0000000..fa2910f
Binary files /dev/null and b/backend/myproject/members/management/commands/__pycache__/__init__.cpython-313.pyc differ
diff --git a/backend/myproject/members/management/commands/__pycache__/import_users.cpython-313.pyc b/backend/myproject/members/management/commands/__pycache__/import_users.cpython-313.pyc
new file mode 100644
index 0000000..72f3d5b
Binary files /dev/null and b/backend/myproject/members/management/commands/__pycache__/import_users.cpython-313.pyc differ
diff --git a/backend/myproject/members/management/commands/import_users.py b/backend/myproject/members/management/commands/import_users.py
new file mode 100644
index 0000000..7732ed3
--- /dev/null
+++ b/backend/myproject/members/management/commands/import_users.py
@@ -0,0 +1,81 @@
+import csv
+import os
+from django.core.management.base import BaseCommand
+from members.models import CustomUser
+from django.contrib.auth.hashers import make_password
+from django.utils.text import slugify
+
+class Command(BaseCommand):
+ help = "Import users from a CSV file and create them in the database"
+
+ def add_arguments(self, parser):
+ parser.add_argument("file_path", type=str, help="Path to the CSV file")
+
+ def generate_unique_username(self, email):
+ """Generate a unique username based on email."""
+ base_username = slugify(email.split('@')[0]) # Convert to a slug
+ username = base_username
+ counter = 1
+
+ while CustomUser.objects.filter(username=username).exists():
+ username = f"{base_username}{counter}"
+ counter += 1
+
+ return username
+
+ def handle(self, *args, **kwargs):
+ file_path = kwargs["file_path"]
+ users_created = 0
+ skipped_users = 0
+
+ try:
+ with open(file_path, newline='', encoding='utf-8') as csvfile:
+ reader = csv.DictReader(csvfile)
+
+ for row in reader:
+ email = row.get("email", "").strip()
+
+ # Check if user already exists
+ if CustomUser.objects.filter(email=email).exists():
+ print(f"Skipping {email} (User already exists)")
+ skipped_users += 1
+ continue
+
+ print(f"Creating user: {email}")
+
+ # Ensure unique username
+ username = row.get("username", "").strip()
+ if not username:
+ username = self.generate_unique_username(email)
+
+ # Create new user
+ user = CustomUser(
+ email=email,
+ username=username,
+ first_name=row.get("first_name", "").strip(),
+ last_name=row.get("last_name", "").strip(),
+ phone=row.get("phone", "").strip(),
+ srn=row.get("srn", "").strip(),
+ branch=row.get("branch", "").strip(),
+ semester=row.get("semester", "1").strip(),
+ github=row.get("github", "").strip(),
+ linkedin=row.get("linkedin", "").strip(),
+ role="student",
+ )
+
+ user.set_password("test@123") # Set default password
+
+ print(f"Saving user: {user.email}")
+ try:
+ user.save()
+ print(f"User {user.email} saved successfully!")
+ users_created += 1
+ self.stdout.write(self.style.SUCCESS(f"User {email} created successfully!"))
+ except Exception as e:
+ print(f"Error saving user {user.email}: {e}")
+
+ except FileNotFoundError:
+ self.stderr.write(self.style.ERROR(f"File {file_path} not found!"))
+ return
+
+ self.stdout.write(self.style.SUCCESS(f"Import completed: {users_created} new users, {skipped_users} skipped."))
diff --git a/backend/myproject/members/managers.py b/backend/myproject/members/managers.py
new file mode 100644
index 0000000..2c8ca01
--- /dev/null
+++ b/backend/myproject/members/managers.py
@@ -0,0 +1,15 @@
+from django.contrib.auth.models import BaseUserManager
+
+class CustomUserManager(BaseUserManager):
+ def create_user(self, email, password=None, **extra_fields):
+ if not email:
+ raise ValueError('The Email field must be set')
+ email = self.normalize_email(email)
+ user = self.model(email=email, **extra_fields)
+ user.set_password(password)
+ user.save(using=self._db)
+ return user
+
+ def create_superuser(self, email, password=None, **extra_fields):
+ extra_fields.setdefault('role', 'admin')
+ return self.create_user(email, password, **extra_fields)
diff --git a/backend/myproject/members/migrations/0001_initial.py b/backend/myproject/members/migrations/0001_initial.py
index 215912b..785f258 100644
--- a/backend/myproject/members/migrations/0001_initial.py
+++ b/backend/myproject/members/migrations/0001_initial.py
@@ -1,6 +1,5 @@
-# Generated by Django 5.0.7 on 2024-08-10 13:33
+# Generated by Django 5.1.6 on 2025-02-18 19:17
-import django.contrib.auth.models
import django.contrib.auth.validators
import django.utils.timezone
from django.db import migrations, models
@@ -11,10 +10,22 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
- ('auth', '0013_alter_user_email'),
+ ('auth', '0012_alter_user_first_name_max_length'),
]
operations = [
+ migrations.CreateModel(
+ name='FileModel',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=200, unique=True)),
+ ('type', models.CharField(choices=[('PD', 'pdf'), ('IM', 'image'), ('LI', 'link')], max_length=2)),
+ ('file', models.FileField(blank=True, null=True, upload_to='files/')),
+ ('image', models.ImageField(blank=True, null=True, upload_to='images/')),
+ ('url', models.URLField(blank=True, max_length=500, null=True)),
+ ('uploaded_at', models.DateTimeField(auto_now_add=True, db_index=True)),
+ ],
+ ),
migrations.CreateModel(
name='CustomUser',
fields=[
@@ -25,17 +36,19 @@ class Migration(migrations.Migration):
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
- ('email', models.EmailField(blank=True, max_length=254, unique=True, verbose_name='email address')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
- ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
- ('alt_email', models.EmailField(max_length=254, unique=True)),
+ ('email', models.EmailField(max_length=254, unique=True)),
('phone', models.CharField(max_length=10)),
- ('srn', models.CharField(max_length=8, unique=True)),
+ ('srn', models.CharField(max_length=8)),
('branch', models.CharField(max_length=50)),
('semester', models.CharField(default='1', max_length=2)),
- ('github', models.URLField(default='')),
- ('linkedin', models.URLField(default='')),
+ ('github', models.URLField(blank=True, default='')),
+ ('linkedin', models.URLField(blank=True, default='')),
+ ('is_active', models.BooleanField(default=True)),
+ ('otp_token', models.CharField(blank=True, max_length=6, null=True)),
+ ('otp_expiration', models.DateTimeField(blank=True, null=True)),
+ ('role', models.CharField(choices=[('admin', 'Admin'), ('student', 'Student')], default='student', max_length=10)),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
],
@@ -44,8 +57,5 @@ class Migration(migrations.Migration):
'verbose_name_plural': 'users',
'abstract': False,
},
- managers=[
- ('objects', django.contrib.auth.models.UserManager()),
- ],
),
]
diff --git a/backend/myproject/members/migrations/0002_alter_customuser_srn.py b/backend/myproject/members/migrations/0002_alter_customuser_srn.py
deleted file mode 100644
index 3306fd2..0000000
--- a/backend/myproject/members/migrations/0002_alter_customuser_srn.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Generated by Django 5.0.7 on 2024-08-12 05:54
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('members', '0001_initial'),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='customuser',
- name='srn',
- field=models.CharField(max_length=8),
- ),
- ]
diff --git a/backend/myproject/members/migrations/0005_filemodel_related_project_and_more.py b/backend/myproject/members/migrations/0002_initial.py
similarity index 91%
rename from backend/myproject/members/migrations/0005_filemodel_related_project_and_more.py
rename to backend/myproject/members/migrations/0002_initial.py
index 132d855..a31271d 100644
--- a/backend/myproject/members/migrations/0005_filemodel_related_project_and_more.py
+++ b/backend/myproject/members/migrations/0002_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 5.0.7 on 2024-09-03 16:35
+# Generated by Django 5.1.6 on 2025-02-18 19:17
import django.db.models.deletion
from django.conf import settings
@@ -7,8 +7,10 @@
class Migration(migrations.Migration):
+ initial = True
+
dependencies = [
- ('members', '0004_filemodel_alter_customuser_email_and_more'),
+ ('members', '0001_initial'),
('projects', '0001_initial'),
]
diff --git a/backend/myproject/members/migrations/0003_remove_customuser_alt_email.py b/backend/myproject/members/migrations/0003_remove_customuser_alt_email.py
deleted file mode 100644
index 7b9e239..0000000
--- a/backend/myproject/members/migrations/0003_remove_customuser_alt_email.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# Generated by Django 5.0.7 on 2024-08-12 06:09
-
-from django.db import migrations
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('members', '0002_alter_customuser_srn'),
- ]
-
- operations = [
- migrations.RemoveField(
- model_name='customuser',
- name='alt_email',
- ),
- ]
diff --git a/backend/myproject/members/migrations/0004_filemodel_alter_customuser_email_and_more.py b/backend/myproject/members/migrations/0004_filemodel_alter_customuser_email_and_more.py
deleted file mode 100644
index 78636b4..0000000
--- a/backend/myproject/members/migrations/0004_filemodel_alter_customuser_email_and_more.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# Generated by Django 5.0.7 on 2024-09-03 16:35
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('members', '0003_remove_customuser_alt_email'),
- ]
-
- operations = [
- migrations.CreateModel(
- name='FileModel',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('name', models.CharField(max_length=200, unique=True)),
- ('type', models.CharField(choices=[('PD', 'pdf'), ('IM', 'image'), ('LI', 'link')], max_length=2)),
- ('file', models.FileField(blank=True, null=True, upload_to='files/')),
- ('image', models.ImageField(blank=True, null=True, upload_to='images/')),
- ('url', models.URLField(blank=True, max_length=500, null=True)),
- ('uploaded_at', models.DateTimeField(auto_now_add=True, db_index=True)),
- ],
- ),
- migrations.AlterField(
- model_name='customuser',
- name='email',
- field=models.EmailField(max_length=254, unique=True),
- ),
- migrations.AlterField(
- model_name='customuser',
- name='github',
- field=models.URLField(blank=True, default=''),
- ),
- migrations.AlterField(
- model_name='customuser',
- name='linkedin',
- field=models.URLField(blank=True, default=''),
- ),
- ]
diff --git a/backend/myproject/members/migrations/__pycache__/0001_initial.cpython-313.pyc b/backend/myproject/members/migrations/__pycache__/0001_initial.cpython-313.pyc
new file mode 100644
index 0000000..0760654
Binary files /dev/null and b/backend/myproject/members/migrations/__pycache__/0001_initial.cpython-313.pyc differ
diff --git a/backend/myproject/members/migrations/__pycache__/0002_initial.cpython-313.pyc b/backend/myproject/members/migrations/__pycache__/0002_initial.cpython-313.pyc
new file mode 100644
index 0000000..d761016
Binary files /dev/null and b/backend/myproject/members/migrations/__pycache__/0002_initial.cpython-313.pyc differ
diff --git a/backend/myproject/members/migrations/__pycache__/__init__.cpython-313.pyc b/backend/myproject/members/migrations/__pycache__/__init__.cpython-313.pyc
new file mode 100644
index 0000000..79ba7a0
Binary files /dev/null and b/backend/myproject/members/migrations/__pycache__/__init__.cpython-313.pyc differ
diff --git a/backend/myproject/members/models.py b/backend/myproject/members/models.py
index 347b0ae..385524d 100644
--- a/backend/myproject/members/models.py
+++ b/backend/myproject/members/models.py
@@ -2,6 +2,7 @@
from django.contrib.auth.models import AbstractUser
from django.db import models
from pydantic import ValidationError
+from .managers import CustomUserManager
from django.core.exceptions import ValidationError
@@ -24,6 +25,18 @@ class CustomUser(AbstractUser):
semester = models.CharField(max_length=2, default='1')
github = models.URLField(default='', blank=True)
linkedin = models.URLField(default='', blank=True)
+ is_active = models.BooleanField(default=True)
+ otp_token = models.CharField(max_length=6, blank=True, null=True)
+ otp_expiration = models.DateTimeField(null=True, blank=True)
+
+
+
+ ROLE_CHOICES = [
+ ('admin', 'Admin'),
+ ('student', 'Student')
+ ]
+ role = models.CharField(max_length=10, choices=ROLE_CHOICES, default='student')
+ objects = CustomUserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username', 'first_name', 'last_name', 'phone', 'srn', 'branch', 'semester']
diff --git a/backend/myproject/members/schemas.py b/backend/myproject/members/schemas.py
index 9510f36..7686249 100644
--- a/backend/myproject/members/schemas.py
+++ b/backend/myproject/members/schemas.py
@@ -9,6 +9,8 @@ class SignInSchema(BaseModel):
email: str
password: str
+ model_config = {"arbitrary_types_allowed": True}
+
class RegisterSchema(ModelSchema):
class Meta:
@@ -19,4 +21,24 @@ class Meta:
'last_name',
'password',
'srn',
- ]
\ No newline at end of file
+ ]
+
+ model_config = {"arbitrary_types_allowed": True}
+
+class LoginResponseSchema(Schema):
+ message: str
+ role: str
+ csrf_token: str
+
+ model_config = {
+ "arbitrary_types_allowed": True,
+ }
+
+
+class UserProfileResponseSchema(Schema):
+ username: str
+ email: str
+ github: str
+ fname: str
+
+ model_config = {"arbitrary_types_allowed": True}
\ No newline at end of file
diff --git a/backend/myproject/members/services.py b/backend/myproject/members/services.py
index e69de29..15290e4 100644
--- a/backend/myproject/members/services.py
+++ b/backend/myproject/members/services.py
@@ -0,0 +1,56 @@
+import os
+from django.contrib.auth import authenticate, logout, login
+from django.core.management import call_command
+from django.shortcuts import get_object_or_404
+from django.http import JsonResponse
+from django.middleware.csrf import get_token
+from django.core.files.base import ContentFile
+from django.core.files.storage import default_storage
+from .models import CustomUser as User
+
+class UserAuthService:
+ def login_user(self, request, email, password):
+ user = authenticate(request, username=email, password=password)
+ if user is not None:
+ login(request, user)
+ return {
+ "message": "Login successful",
+ "role": user.role,
+ "csrf_token": get_token(request)
+ }
+ return None # caller can then return an error response
+
+ def logout_user(self, request):
+ logout(request)
+ return {"message": "Logged out successfully"}
+
+ def get_user_profile(self, request):
+ if request.user.is_authenticated:
+ return {
+ "username": request.user.username,
+ "email": request.user.email,
+ "github": request.user.github,
+ "first_name": request.user.first_name,
+ }
+ return None
+
+ def edit_user_profile(self, request, payload):
+ if not request.user.is_authenticated:
+ return None
+ user = get_object_or_404(User, srn=request.user.srn)
+ for attr, value in payload.dict().items():
+ setattr(user, attr, value)
+ user.save()
+ return user
+
+ def import_users_from_file(self, file):
+ file_path = default_storage.save(f"temp/{file.name}", ContentFile(file.read()))
+ try:
+ call_command("import_users", default_storage.path(file_path))
+ except Exception as e:
+ return {"error": str(e)}
+ finally:
+ full_path = default_storage.path(file_path)
+ if os.path.exists(full_path):
+ os.remove(full_path)
+ return {"message": "User import initiated successfully!"}
diff --git a/backend/myproject/myproject/__pycache__/__init__.cpython-313.pyc b/backend/myproject/myproject/__pycache__/__init__.cpython-313.pyc
new file mode 100644
index 0000000..19b8cc0
Binary files /dev/null and b/backend/myproject/myproject/__pycache__/__init__.cpython-313.pyc differ
diff --git a/backend/myproject/myproject/__pycache__/settings.cpython-313.pyc b/backend/myproject/myproject/__pycache__/settings.cpython-313.pyc
new file mode 100644
index 0000000..af82dc6
Binary files /dev/null and b/backend/myproject/myproject/__pycache__/settings.cpython-313.pyc differ
diff --git a/backend/myproject/myproject/__pycache__/urls.cpython-313.pyc b/backend/myproject/myproject/__pycache__/urls.cpython-313.pyc
new file mode 100644
index 0000000..3d732e1
Binary files /dev/null and b/backend/myproject/myproject/__pycache__/urls.cpython-313.pyc differ
diff --git a/backend/myproject/myproject/__pycache__/wsgi.cpython-313.pyc b/backend/myproject/myproject/__pycache__/wsgi.cpython-313.pyc
new file mode 100644
index 0000000..0ec85ce
Binary files /dev/null and b/backend/myproject/myproject/__pycache__/wsgi.cpython-313.pyc differ
diff --git a/backend/myproject/myproject/settings.py b/backend/myproject/myproject/settings.py
index eb8fb47..a91eeb5 100644
--- a/backend/myproject/myproject/settings.py
+++ b/backend/myproject/myproject/settings.py
@@ -11,6 +11,11 @@
"""
import os
from pathlib import Path
+
+from dotenv import load_dotenv
+
+load_dotenv()
+
# import django_heroku
# import dj_database_url
# import os
@@ -28,30 +33,14 @@
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
-ALLOWED_HOSTS = []
-
-
-from datetime import timedelta
-#Django-ninja jwt settings
-NINJA_JWT = {
- 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),
- 'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
- 'ROTATE_REFRESH_TOKENS': True,
- 'BLACKLIST_AFTER_ROTATION': True,
- 'ALGORITHM': 'HS256',
- 'SIGNING_KEY': SECRET_KEY,
- 'VERIFYING_KEY': None,
- 'AUTH_HEADER_TYPES': ('Bearer',),
- 'USER_ID_FIELD': 'id',
- 'USER_ID_CLAIM': 'user_id',
-}
+ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS", "127.0.0.1,localhost").split(",")
# Application definition
INSTALLED_APPS = [
- # 'django.contrib.admin',
+ 'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
@@ -60,13 +49,23 @@
'members',
'ninja',
'corsheaders',
- 'ninja_jwt',
'ninja_extra',
'projects',
'announcements',
'registrations',
+
]
+AUTHENTICATION_BACKENDS = [
+ "django.contrib.auth.backends.ModelBackend",
+]
+
+REST_FRAMEWORK = {
+ 'DEFAULT_AUTHENTICATION_CLASSES': [
+ 'rest_framework.authentication.SessionAuthentication',
+ ]
+}
+
MIDDLEWARE = [
'whitenoise.middleware.WhiteNoiseMiddleware',
"corsheaders.middleware.CorsMiddleware",
@@ -104,15 +103,29 @@
# Database
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
-DATABASES = {
- 'default': {
- 'ENGINE': 'django.db.backends.sqlite3',
- 'NAME': BASE_DIR / 'db.sqlite3',
+if os.getenv('DJANGO_ENV') == 'production':
+ DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.postgresql',
+ 'NAME': os.getenv('DB_NAME', 'postgres'),
+ 'USER': os.getenv('DB_USER', 'postgres'),
+ 'PASSWORD': os.getenv('DB_PASSWORD', ''),
+ 'HOST': os.getenv('DB_HOST', 'localhost'),
+ 'PORT': int(os.getenv('DB_PORT', 6543)), # Cast to integer
+ 'OPTIONS': {'sslmode': 'require'},
+ }
+ }
+else:
+ DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3',
+ 'NAME': BASE_DIR / 'db.sqlite3', # Use Pathlib for cross-platform support
+ }
}
-}
-# if 'DATABASE_URL' in os.environ:
-# DATABASES['default'] = dj_database_url.config(default=os.environ['DATABASE_URL'])
+import dj_database_url
+if os.getenv('DJANGO_ENV') == 'production' and 'DATABASE_URL' in os.environ:
+ DATABASES['default'] = dj_database_url.config(default=os.environ['DATABASE_URL'])
# Password validation
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
@@ -161,11 +174,32 @@
CORS_ALLOW_CREDENTIALS = True
+CSRF_COOKIE_NAME = "csrftoken"
+CSRF_COOKIE_HTTPONLY = False
CORS_ALLOWED_ORIGINS = ["http://localhost:8000"]
-CSRF_TRUSTED_ORIGINS = ['http://localhost:8000']
+CSRF_TRUSTED_ORIGINS = ["https://dev-track-app.onrender.com/", 'http://localhost:8000']
+CSRF_USE_SESSIONS = True
AUTH_USER_MODEL = 'members.CustomUser'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
-MEDIA_URL = '/media/'
\ No newline at end of file
+MEDIA_URL = '/media/'
+
+
+# database-backed sessions
+SESSION_ENGINE = "django.contrib.sessions.backends.db"
+
+# Name of the session cookie
+SESSION_COOKIE_NAME = "sessionid"
+
+# Ensure the session expires when the browser is closed
+SESSION_EXPIRE_AT_BROWSER_CLOSE = False
+
+# Set session age (e.g., 2 weeks)
+SESSION_COOKIE_AGE = 1209600 # 2 weeks in seconds
+
+# Secure session settings for production
+SESSION_COOKIE_SECURE = False # Set to True in production if using HTTPS
+SESSION_COOKIE_HTTPONLY = True
+SESSION_COOKIE_SAMESITE = "Lax" # Change to 'None' if working with cross-site requests
diff --git a/backend/myproject/myproject/urls.py b/backend/myproject/myproject/urls.py
index 91f8e51..7a08a29 100644
--- a/backend/myproject/myproject/urls.py
+++ b/backend/myproject/myproject/urls.py
@@ -6,6 +6,6 @@
urlpatterns = [
- # path('admin/', admin.site.urls),
+ path('admin/', admin.site.urls),
path("api/", api.urls),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
diff --git a/backend/myproject/projects/__pycache__/__init__.cpython-313.pyc b/backend/myproject/projects/__pycache__/__init__.cpython-313.pyc
new file mode 100644
index 0000000..74b2bae
Binary files /dev/null and b/backend/myproject/projects/__pycache__/__init__.cpython-313.pyc differ
diff --git a/backend/myproject/projects/__pycache__/admin.cpython-313.pyc b/backend/myproject/projects/__pycache__/admin.cpython-313.pyc
new file mode 100644
index 0000000..02e8454
Binary files /dev/null and b/backend/myproject/projects/__pycache__/admin.cpython-313.pyc differ
diff --git a/backend/myproject/projects/__pycache__/api.cpython-313.pyc b/backend/myproject/projects/__pycache__/api.cpython-313.pyc
new file mode 100644
index 0000000..0464f5b
Binary files /dev/null and b/backend/myproject/projects/__pycache__/api.cpython-313.pyc differ
diff --git a/backend/myproject/projects/__pycache__/apps.cpython-313.pyc b/backend/myproject/projects/__pycache__/apps.cpython-313.pyc
new file mode 100644
index 0000000..60ce407
Binary files /dev/null and b/backend/myproject/projects/__pycache__/apps.cpython-313.pyc differ
diff --git a/backend/myproject/projects/__pycache__/models.cpython-313.pyc b/backend/myproject/projects/__pycache__/models.cpython-313.pyc
new file mode 100644
index 0000000..f15e2d6
Binary files /dev/null and b/backend/myproject/projects/__pycache__/models.cpython-313.pyc differ
diff --git a/backend/myproject/projects/__pycache__/schemas.cpython-313.pyc b/backend/myproject/projects/__pycache__/schemas.cpython-313.pyc
new file mode 100644
index 0000000..7fcbd29
Binary files /dev/null and b/backend/myproject/projects/__pycache__/schemas.cpython-313.pyc differ
diff --git a/backend/myproject/projects/__pycache__/services.cpython-313.pyc b/backend/myproject/projects/__pycache__/services.cpython-313.pyc
new file mode 100644
index 0000000..5f503d2
Binary files /dev/null and b/backend/myproject/projects/__pycache__/services.cpython-313.pyc differ
diff --git a/backend/myproject/projects/api.py b/backend/myproject/projects/api.py
index 5b569f4..48f87a9 100644
--- a/backend/myproject/projects/api.py
+++ b/backend/myproject/projects/api.py
@@ -40,33 +40,39 @@ def list_domain(self, request):
return domain_list
except Exception as e:
raise HttpError(400, str(e))
+
+ #API call to create a new project cycle.
+ @route.post("/cycle/create", url_name="Create project cycle")
+ def create_project_cycle(self, request, payload:schemas.CreateProjectCycleSchema):
+ return self.project_service.create_project_cycle(request, payload)
- #API call to create a new project
- @route.post("/create", url_name="Create project")
- def create_project(self, payload:schemas.CreateProjectSchema, file: UploadedFile = File(...)):
- response = self.project_service.create_project(payload,file)
- if 'error' in response:
- raise HttpError(400, response['error'])
- return {"message": "Project created successfully", "project_id": response['project_id']}
+ # #API call to create a new project
+ # @route.post("/create", url_name="Create project")
+ # def create_project(self, payload:schemas.CreateProjectSchema, file: UploadedFile = File(...)):
+ # response = self.project_service.create_project(payload,file)
+ # if 'error' in response:
+ # raise HttpError(400, response['error'])
+ # return {"message": "Project created successfully", "project_id": response['project_id']}
- #Api to list all the names of the projects.
- @route.get("/list", url_name="List Projects", response=list[ListProjectSchema])
- def list_projects(self,request):
- try:
- projects = ProjectModel.objects.all()
- project_list = [
- ListProjectSchema(
- name=project.name,
- domain=project.domain.name,
- created_at=project.created_at.isoformat()
- )
- for project in projects
- ]
- return project_list
- except Exception as e:
- raise HttpError(400, str(e))
+
+ # #Api to list all the names of the projects.
+ # @route.get("/list", url_name="List Projects", response=list[ListProjectSchema])
+ # def list_projects(self,request):
+ # try:
+ # projects = ProjectModel.objects.all()
+ # project_list = [
+ # ListProjectSchema(
+ # name=project.name,
+ # domain=project.domain.name,
+ # created_at=project.created_at.isoformat()
+ # )
+ # for project in projects
+ # ]
+ # return project_list
+ # except Exception as e:
+ # raise HttpError(400, str(e))
diff --git a/backend/myproject/projects/migrations/0001_initial.py b/backend/myproject/projects/migrations/0001_initial.py
index 58a0239..0792814 100644
--- a/backend/myproject/projects/migrations/0001_initial.py
+++ b/backend/myproject/projects/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 5.0.7 on 2024-09-03 16:35
+# Generated by Django 5.1.6 on 2025-02-18 19:17
import django.db.models.deletion
from django.conf import settings
@@ -10,7 +10,7 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
- ('members', '0004_filemodel_alter_customuser_email_and_more'),
+ ('members', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
diff --git a/backend/myproject/projects/migrations/0002_alter_projectmodel_description_and_more.py b/backend/myproject/projects/migrations/0002_alter_projectmodel_description_and_more.py
deleted file mode 100644
index 4b86281..0000000
--- a/backend/myproject/projects/migrations/0002_alter_projectmodel_description_and_more.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# Generated by Django 5.0.7 on 2024-09-03 18:34
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('projects', '0001_initial'),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='projectmodel',
- name='description',
- field=models.TextField(blank=True, max_length=256),
- ),
- migrations.AlterField(
- model_name='projectmodel',
- name='status',
- field=models.CharField(choices=[('ON', 'ongoing'), ('PA', 'past')], default='PA', max_length=100),
- ),
- ]
diff --git a/backend/myproject/projects/migrations/0003_alter_projectmodel_description_and_more.py b/backend/myproject/projects/migrations/0003_alter_projectmodel_description_and_more.py
deleted file mode 100644
index 4812d2b..0000000
--- a/backend/myproject/projects/migrations/0003_alter_projectmodel_description_and_more.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# Generated by Django 5.0.7 on 2024-09-04 03:01
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('projects', '0002_alter_projectmodel_description_and_more'),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='projectmodel',
- name='description',
- field=models.TextField(max_length=256),
- ),
- migrations.AlterField(
- model_name='projectmodel',
- name='status',
- field=models.CharField(choices=[('ON', 'ongoing'), ('PA', 'past')], max_length=100),
- ),
- ]
diff --git a/backend/myproject/projects/migrations/__pycache__/0001_initial.cpython-313.pyc b/backend/myproject/projects/migrations/__pycache__/0001_initial.cpython-313.pyc
new file mode 100644
index 0000000..36f6290
Binary files /dev/null and b/backend/myproject/projects/migrations/__pycache__/0001_initial.cpython-313.pyc differ
diff --git a/backend/myproject/projects/migrations/__pycache__/__init__.cpython-313.pyc b/backend/myproject/projects/migrations/__pycache__/__init__.cpython-313.pyc
new file mode 100644
index 0000000..f2000c1
Binary files /dev/null and b/backend/myproject/projects/migrations/__pycache__/__init__.cpython-313.pyc differ
diff --git a/backend/myproject/projects/schemas.py b/backend/myproject/projects/schemas.py
index 314f299..2ae75dc 100644
--- a/backend/myproject/projects/schemas.py
+++ b/backend/myproject/projects/schemas.py
@@ -1,5 +1,5 @@
from ninja import ModelSchema
-from pydantic import BaseModel
+from pydantic import BaseModel, Field
from ninja import Schema
from projects.models import ProjectModel, DomainModel
@@ -27,3 +27,10 @@ class CreateProjectSchema(Schema):
youtube_link : str
+class CreateProjectCycleSchema(Schema):
+ cycle_name : str
+ start_date : str
+ end_date : str
+ is_active : bool = Field(default=False)
+
+
diff --git a/backend/myproject/projects/services.py b/backend/myproject/projects/services.py
index d4673d0..104a566 100644
--- a/backend/myproject/projects/services.py
+++ b/backend/myproject/projects/services.py
@@ -3,7 +3,8 @@
from .models import ProjectModel
from .schemas import CreateProjectSchema, ListProjectSchema, CreateDomainSchema, ListDomainSchema
from members.models import FileModel
-from projects.models import DomainModel
+from projects.models import DomainModel, ProjectCycleModel
+from projects.schemas import CreateProjectCycleSchema, CreateDomainSchema
class ProjectService:
@@ -40,6 +41,19 @@ def create_project(self, details : CreateProjectSchema, pdf_file):
return {"error": str(e)}
+ def create_project_cycle(self, payload : CreateProjectCycleSchema):
+ try:
+ response = ProjectCycleModel.objects.create(
+ cycle_name = payload.cycle_name,
+ start_date = payload.start_date, #Notes for frontend : Dates will be in the format :
+ end_date = payload.end_date, #ISO 8601 format : YYYY-MM-DD
+ is_active = payload.is_active
+ )
+ return {"Success":"Project cycle has been created"}
+ except Exception as e:
+ return {"error": str(e)}
+
+
class DomainService:
#Service to create a new domain
diff --git a/backend/myproject/registrations/__pycache__/__init__.cpython-313.pyc b/backend/myproject/registrations/__pycache__/__init__.cpython-313.pyc
new file mode 100644
index 0000000..a913c18
Binary files /dev/null and b/backend/myproject/registrations/__pycache__/__init__.cpython-313.pyc differ
diff --git a/backend/myproject/registrations/__pycache__/admin.cpython-313.pyc b/backend/myproject/registrations/__pycache__/admin.cpython-313.pyc
new file mode 100644
index 0000000..c7c69e8
Binary files /dev/null and b/backend/myproject/registrations/__pycache__/admin.cpython-313.pyc differ
diff --git a/backend/myproject/registrations/__pycache__/api.cpython-313.pyc b/backend/myproject/registrations/__pycache__/api.cpython-313.pyc
new file mode 100644
index 0000000..f0b7ae1
Binary files /dev/null and b/backend/myproject/registrations/__pycache__/api.cpython-313.pyc differ
diff --git a/backend/myproject/registrations/__pycache__/apps.cpython-313.pyc b/backend/myproject/registrations/__pycache__/apps.cpython-313.pyc
new file mode 100644
index 0000000..8780349
Binary files /dev/null and b/backend/myproject/registrations/__pycache__/apps.cpython-313.pyc differ
diff --git a/backend/myproject/registrations/__pycache__/models.cpython-313.pyc b/backend/myproject/registrations/__pycache__/models.cpython-313.pyc
new file mode 100644
index 0000000..f89f7d3
Binary files /dev/null and b/backend/myproject/registrations/__pycache__/models.cpython-313.pyc differ
diff --git a/backend/myproject/registrations/__pycache__/schemas.cpython-313.pyc b/backend/myproject/registrations/__pycache__/schemas.cpython-313.pyc
new file mode 100644
index 0000000..255a20a
Binary files /dev/null and b/backend/myproject/registrations/__pycache__/schemas.cpython-313.pyc differ
diff --git a/backend/myproject/registrations/__pycache__/services.cpython-313.pyc b/backend/myproject/registrations/__pycache__/services.cpython-313.pyc
new file mode 100644
index 0000000..c24f3e6
Binary files /dev/null and b/backend/myproject/registrations/__pycache__/services.cpython-313.pyc differ
diff --git a/backend/myproject/registrations/api.py b/backend/myproject/registrations/api.py
index e69de29..b1fdd21 100644
--- a/backend/myproject/registrations/api.py
+++ b/backend/myproject/registrations/api.py
@@ -0,0 +1,15 @@
+from ninja import Router
+from ninja_extra import ControllerBase, api_controller, route
+from .schemas import ProjectApplicationSchema
+from .services import ProjectApplicationService
+
+@api_controller("/applications", tags=["Project Applications"])
+class ProjectApplicationAPI(ControllerBase):
+
+ def __init__(self) -> None:
+ self.application_service = ProjectApplicationService()
+
+ @route.post("/enroll", url_name="Enroll in Project")
+ def enroll_now(self, request, payload: ProjectApplicationSchema):
+ response = self.application_service.enroll_user(request, payload)
+ return response
diff --git a/backend/myproject/registrations/migrations/0001_initial.py b/backend/myproject/registrations/migrations/0001_initial.py
index 946453b..c0177b3 100644
--- a/backend/myproject/registrations/migrations/0001_initial.py
+++ b/backend/myproject/registrations/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 5.0.7 on 2024-09-03 16:35
+# Generated by Django 5.1.6 on 2025-02-18 19:17
import django.db.models.deletion
from django.conf import settings
diff --git a/backend/myproject/registrations/migrations/__pycache__/0001_initial.cpython-313.pyc b/backend/myproject/registrations/migrations/__pycache__/0001_initial.cpython-313.pyc
new file mode 100644
index 0000000..3d57f41
Binary files /dev/null and b/backend/myproject/registrations/migrations/__pycache__/0001_initial.cpython-313.pyc differ
diff --git a/backend/myproject/registrations/migrations/__pycache__/__init__.cpython-313.pyc b/backend/myproject/registrations/migrations/__pycache__/__init__.cpython-313.pyc
new file mode 100644
index 0000000..9c72f47
Binary files /dev/null and b/backend/myproject/registrations/migrations/__pycache__/__init__.cpython-313.pyc differ
diff --git a/backend/myproject/registrations/schemas.py b/backend/myproject/registrations/schemas.py
new file mode 100644
index 0000000..6d2df50
--- /dev/null
+++ b/backend/myproject/registrations/schemas.py
@@ -0,0 +1,6 @@
+from ninja import Schema
+from typing import Optional
+
+class ProjectApplicationSchema(Schema):
+ first_preference_id: int
+ second_preference_id: int
diff --git a/backend/myproject/registrations/services.py b/backend/myproject/registrations/services.py
index e69de29..ff9d00a 100644
--- a/backend/myproject/registrations/services.py
+++ b/backend/myproject/registrations/services.py
@@ -0,0 +1,44 @@
+from django.shortcuts import get_object_or_404
+from projects.models import DomainModel, ProjectCycleModel
+from .models import ProjectApplicationModel
+from ninja.errors import HttpError
+
+class ProjectApplicationService:
+ def enroll_user(self, request, payload):
+ # Check if user is authenticated
+ if not request.user.is_authenticated:
+ raise HttpError(400, "Authentication required")
+
+ user = request.user
+
+ # Retrieve the current active project cycle
+ cycle = ProjectCycleModel.objects.filter(is_active=True).first()
+ if not cycle:
+ raise HttpError(400, "No active project cycle found.")
+
+ if payload.first_preference_id == payload.second_preference_id:
+ raise HttpError(400, "First and second preference cannot be the same.")
+
+ # Retrieve the domain choices
+ #Instructions for frontend :
+ # Send the domain id in the payload,
+ # But in the ui, show the domain name to the user
+ # This way, the user will see the domain name, but the backend will receive the domain id
+ first_pref = get_object_or_404(DomainModel, id=payload.first_preference_id)
+ second_pref = get_object_or_404(DomainModel, id=payload.second_preference_id)
+
+ # Create or get the application
+ application, created = ProjectApplicationModel.objects.get_or_create(
+ user=user,
+ cycle=cycle,
+ defaults={
+ "first_preference": first_pref,
+ "second_preference": second_pref,
+ "is_selected": False
+ }
+ )
+
+ if not created:
+ return {"message": "You have already applied for this cycle."}
+
+ return {"message": "Application submitted successfully, hang tight!"}
diff --git a/backend/myproject/requirements.txt b/backend/myproject/requirements.txt
index beccf13..bd1795b 100644
--- a/backend/myproject/requirements.txt
+++ b/backend/myproject/requirements.txt
@@ -1,58 +1,29 @@
annotated-types==0.7.0
asgiref==3.8.1
-asttokens==2.4.1
-cffi==1.16.0
-colorama==0.4.6
-comm==0.2.2
+cffi==1.17.1
contextlib2==21.6.0
-cryptography==43.0.0
-debugpy==1.8.1
-decorator==5.1.1
-dj-database-url==2.2.0
-Django==5.0.7
-django-cors-headers==4.4.0
-django-enumfields==2.1.1
-django-heroku==0.3.1
-django-ninja==1.2.2
-django-ninja-extra==0.21.2
-django-ninja-jwt==5.3.2
-django-phonenumber-field==8.0.0
+cryptography==44.0.1
+debugpy==1.8.12
+dj-database-url==2.3.0
+Django==5.1.6
+django-cors-headers==4.7.0
+django-ninja==1.3.0
+django-ninja-extra==0.22.3
+django-ninja-jwt==5.3.5
djangorestframework==3.15.2
-djangorestframework-simplejwt==5.3.1
-executing==2.0.1
-gunicorn==22.0.0
+djangorestframework_simplejwt==5.4.0
+gunicorn==23.0.0
injector==0.22.0
-ipykernel==6.29.4
-ipython==8.24.0
-jedi==0.19.1
-jupyter_client==8.6.1
-jupyter_core==5.7.2
-matplotlib-inline==0.1.7
-nest-asyncio==1.6.0
-packaging==24.0
-parso==0.8.4
-phonenumberslite==8.13.42
-platformdirs==4.2.2
-prompt-toolkit==3.0.43
-psutil==5.9.8
-psycopg2==2.9.9
-pure-eval==0.2.2
+packaging==24.2
+pillow==11.1.0
+psycopg2==2.9.10
+psycopg2-binary==2.9.10
pycparser==2.22
-pydantic==2.8.2
-pydantic_core==2.20.1
-Pygments==2.18.0
-PyJWT==2.9.0
-python-dateutil==2.9.0.post0
-pywin32==306
-pyzmq==26.0.3
-setuptools==69.5.1
-six==1.16.0
-sqlparse==0.5.1
-stack-data==0.6.3
-tornado==6.4
-traitlets==5.14.3
+pydantic==2.10.6
+pydantic_core==2.27.2
+PyJWT==2.10.1
+python-dotenv==1.0.1
+sqlparse==0.5.3
typing_extensions==4.12.2
-tzdata==2024.1
-wcwidth==0.2.13
-wheel==0.43.0
-whitenoise==6.7.0
+tzdata==2025.1
+whitenoise==6.9.0
diff --git a/frontend/android/app/build.gradle b/frontend/android/app/build.gradle
index 896fb11..7ff43b9 100644
--- a/frontend/android/app/build.gradle
+++ b/frontend/android/app/build.gradle
@@ -46,7 +46,7 @@ android {
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdkVersion flutter.minSdkVersion
- targetSdkVersion flutter.targetSdkVersion
+ targetSdkVersion (flutter.targetSdkVersion - 1)
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
@@ -64,4 +64,7 @@ flutter {
source '../..'
}
-dependencies {}
+dependencies {
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.0"
+ implementation 'com.android.support:multidex:1.0.3'
+}
diff --git a/frontend/android/app/src/main/AndroidManifest.xml b/frontend/android/app/src/main/AndroidManifest.xml
index 6f2f4c8..949d141 100644
--- a/frontend/android/app/src/main/AndroidManifest.xml
+++ b/frontend/android/app/src/main/AndroidManifest.xml
@@ -44,4 +44,12 @@
+
+
+
+
+
+
+
+
diff --git a/frontend/android/build.gradle b/frontend/android/build.gradle
index bc157bd..ceaf90e 100644
--- a/frontend/android/build.gradle
+++ b/frontend/android/build.gradle
@@ -1,3 +1,13 @@
+buildscript {
+ repositories {
+ google()
+ mavenCentral()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:8.1.0'
+ }
+}
+
allprojects {
repositories {
google()
diff --git a/frontend/android/gradle.properties b/frontend/android/gradle.properties
index 598d13f..06c53e1 100644
--- a/frontend/android/gradle.properties
+++ b/frontend/android/gradle.properties
@@ -1,3 +1,5 @@
org.gradle.jvmargs=-Xmx4G
android.useAndroidX=true
android.enableJetifier=true
+android.useAndroidX=true
+android.enableJetifier=true
\ No newline at end of file
diff --git a/frontend/android/gradle/wrapper/gradle-wrapper.properties b/frontend/android/gradle/wrapper/gradle-wrapper.properties
index e1ca574..b115ac1 100644
--- a/frontend/android/gradle/wrapper/gradle-wrapper.properties
+++ b/frontend/android/gradle/wrapper/gradle-wrapper.properties
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip
+distributionUrl=https://services.gradle.org/distributions/gradle-8.12.1-bin.zip
diff --git a/frontend/android/settings.gradle b/frontend/android/settings.gradle
index 1d6d19b..8a87a89 100644
--- a/frontend/android/settings.gradle
+++ b/frontend/android/settings.gradle
@@ -19,8 +19,8 @@ pluginManagement {
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
- id "com.android.application" version "7.3.0" apply false
- id "org.jetbrains.kotlin.android" version "1.7.10" apply false
+ id "com.android.application" version "8.1.0" apply false
+ id "org.jetbrains.kotlin.android" version "2.1.10" apply false
}
include ":app"
diff --git a/frontend/ios/Runner/Info.plist b/frontend/ios/Runner/Info.plist
index 992d529..8cb0658 100644
--- a/frontend/ios/Runner/Info.plist
+++ b/frontend/ios/Runner/Info.plist
@@ -51,5 +51,9 @@
Camera Usage
NSMicrophoneUsageDescription
Microphone Usage
+ LSApplicationQueriesSchemes
+
+ https
+
diff --git a/frontend/lib/To-Be-Discarded/bottomNav.dart b/frontend/lib/To-Be-Discarded/bottomNav.dart
new file mode 100644
index 0000000..a31884f
--- /dev/null
+++ b/frontend/lib/To-Be-Discarded/bottomNav.dart
@@ -0,0 +1,70 @@
+// import 'package:flutter/material.dart';
+// //Testing change
+// import 'package:dev_track_app/pages/domain_pages/domain.dart';
+// import 'package:dev_track_app/pages/home.dart';
+
+// class BottomNav extends StatefulWidget {
+// const BottomNav({super.key});
+
+// @override
+// State createState() => _BottomNavState();
+// }
+
+// class _BottomNavState extends State {
+// int _currentIndex = 0;
+// final List pages = [
+// const HomePag(),
+// // const ScrumPage(),
+// const DomainPage(),
+// // const ProgressPage(),
+// // const ProfilePage(),
+// ];
+// @override
+// Widget build(BuildContext context) {
+// return MaterialApp(
+// home: Scaffold(
+// body: pages[_currentIndex],
+// bottomNavigationBar: BottomNavigationBar(
+// currentIndex: _currentIndex,
+// type: BottomNavigationBarType.fixed,
+// backgroundColor: Colors.black,
+// selectedItemColor:
+// Colors.green, // Set the selected icon color to green
+// unselectedItemColor: Color.fromARGB(255, 2, 155, 73),
+// iconSize: 35, // Set the unselected icon color to green
+// items: const [
+// BottomNavigationBarItem(
+// icon: Icon(Icons.home),
+// label: 'Feed',
+// ),
+// BottomNavigationBarItem(
+// icon: Icon(Icons.supervisor_account),
+// label: 'Scrum',
+// ),
+// BottomNavigationBarItem(
+// icon: Icon(Icons.apps),
+// label: 'Domain',
+// ),
+// BottomNavigationBarItem(
+// icon: Icon(Icons.access_time),
+// label: 'Progress',
+// ),
+// BottomNavigationBarItem(
+// icon: Icon(Icons.person_rounded),
+// label: 'Profile',
+// ),
+// ],
+// onTap: (index) {
+// setState(() {
+// _currentIndex = index;
+// });
+// },
+// ),
+// ),
+// );
+// }
+// }
+
+// void main() {
+// runApp(const BottomNav());
+// }
diff --git a/frontend/lib/api/api.dart b/frontend/lib/api/api.dart
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/lib/api/login_api.dart b/frontend/lib/api/login_api.dart
new file mode 100644
index 0000000..a718c68
--- /dev/null
+++ b/frontend/lib/api/login_api.dart
@@ -0,0 +1,32 @@
+import 'dart:convert';
+
+import 'package:http/http.dart' as http;
+
+import '../models/user_model.dart';
+// static const String baseUrl = "https://dev-track-app.onrender.com";
+// static const String loginEndpoint = "$baseUrl/user/login";
+
+class AuthService {
+ final String baseUrl = "https://dev-track-app.onrender.com/api/user/login";
+
+ Future login(String email, String password) async {
+ try {
+ final response = await http.post(
+ Uri.parse(baseUrl),
+ headers: {'Content-Type': 'application/json'},
+ body: jsonEncode({"email": email, "password": password}),
+ );
+ print("Response Status Code: ${response.statusCode}");
+ print("Response Body: ${response.body}");
+
+ if (response.statusCode == 200) {
+ final data = jsonDecode(response.body);
+ return UserModel.fromJson(data);
+ } else {
+ throw Exception('Failed to login ${response.body}');
+ }
+ } catch (e) {
+ throw Exception('Error: $e');
+ }
+ }
+}
diff --git a/frontend/lib/components/bottomNav.dart b/frontend/lib/components/bottomNav.dart
deleted file mode 100644
index b38a23b..0000000
--- a/frontend/lib/components/bottomNav.dart
+++ /dev/null
@@ -1,70 +0,0 @@
-import 'package:flutter/material.dart';
-
-import 'package:dev_track_app/pages/domain.dart';
-import 'package:dev_track_app/pages/home.dart';
-
-class BottomNav extends StatefulWidget {
- const BottomNav({super.key});
-
- @override
- State createState() => _BottomNavState();
-}
-
-class _BottomNavState extends State {
- int _currentIndex = 0;
- final List pages = [
- const HomePag(),
- // const ScrumPage(),
- const DomainPage(),
- // const ProgressPage(),
- // const ProfilePage(),
- ];
- @override
- Widget build(BuildContext context) {
- return MaterialApp(
- home: Scaffold(
- body: pages[_currentIndex],
- bottomNavigationBar: BottomNavigationBar(
- currentIndex: _currentIndex,
- type: BottomNavigationBarType.fixed,
- backgroundColor: Colors.black,
- selectedItemColor:
- Colors.green, // Set the selected icon color to green
- unselectedItemColor: Color.fromARGB(255, 2, 155, 73),
- iconSize: 35, // Set the unselected icon color to green
- items: const [
- BottomNavigationBarItem(
- icon: Icon(Icons.home),
- label: 'Feed',
- ),
- BottomNavigationBarItem(
- icon: Icon(Icons.supervisor_account),
- label: 'Scrum',
- ),
- BottomNavigationBarItem(
- icon: Icon(Icons.apps),
- label: 'Domain',
- ),
- BottomNavigationBarItem(
- icon: Icon(Icons.access_time),
- label: 'Progress',
- ),
- BottomNavigationBarItem(
- icon: Icon(Icons.person_rounded),
- label: 'Profile',
- ),
- ],
- onTap: (index) {
- setState(() {
- _currentIndex = index;
- });
- },
- ),
- ),
- );
- }
-}
-
-void main() {
- runApp(const BottomNav());
-}
diff --git a/frontend/lib/logic/logic.dart b/frontend/lib/logic/logic.dart
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/lib/logic/tracker_logic.dart b/frontend/lib/logic/tracker_logic.dart
new file mode 100644
index 0000000..26ec9ca
--- /dev/null
+++ b/frontend/lib/logic/tracker_logic.dart
@@ -0,0 +1,31 @@
+import 'package:flutter/material.dart';
+
+class ProgressTrackerLogic {
+ /// Number of members to track
+ final int numberOfMembers;
+
+ /// Which panels are expanded
+ late List isExpandedList;
+
+ /// Text controllers for each member
+ late List progressControllers;
+
+ ProgressTrackerLogic({this.numberOfMembers = 4}) {
+ isExpandedList = List.generate(numberOfMembers, (_) => false);
+ progressControllers =
+ List.generate(numberOfMembers, (_) => TextEditingController());
+ }
+
+ /// Toggle expansion for a specific member index
+ void toggleExpanded(int index) {
+ isExpandedList[index] = !isExpandedList[index];
+ }
+
+ /// Dispose of controllers to avoid memory leaks
+ void dispose() {
+ for (var controller in progressControllers) {
+ controller.dispose();
+ }
+ }
+}
+
diff --git a/frontend/lib/main.dart b/frontend/lib/main.dart
index b8a282e..f95bc9f 100644
--- a/frontend/lib/main.dart
+++ b/frontend/lib/main.dart
@@ -1,9 +1,22 @@
+import 'package:dev_track_app/pages/common_pages/login_page.dart';
import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
-import 'package:dev_track_app/pages/home.dart';
+import 'models/login_view_model.dart';
+
+// void main() {
+// runApp(const MyApp());
+// }
void main() {
- runApp(const MyApp());
+ runApp(
+ MultiProvider(
+ providers: [
+ ChangeNotifierProvider(create: (_) => LoginViewModel()),
+ ],
+ child: const MyApp(),
+ ),
+ );
}
class MyApp extends StatelessWidget {
@@ -17,7 +30,7 @@ class MyApp extends StatelessWidget {
primarySwatch: Colors.green,
),
debugShowCheckedModeBanner: false,
- home: const HomePag(),
+ home: LoginPage(),
);
}
}
diff --git a/frontend/lib/models/domain_model.dart b/frontend/lib/models/domain_model.dart
new file mode 100644
index 0000000..37de0f3
--- /dev/null
+++ b/frontend/lib/models/domain_model.dart
@@ -0,0 +1,8 @@
+// final List