Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
35cfd6d
Merge pull request #16 from akirachix/develop
Ndichu-shee Sep 12, 2025
b54479a
Update utils.py to handle Google credentials for Heroku and local
jacquelinenyinawabagesera Sep 13, 2025
834c364
Update utils.py to handle Google credentials for Heroku and local
jacquelinenyinawabagesera Sep 13, 2025
01c14d2
Update utils.py to handle Google credentials for Heroku and local
jacquelinenyinawabagesera Sep 13, 2025
fcff436
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 13, 2025
9e9210b
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 13, 2025
917c762
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 13, 2025
b2b92c3
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 13, 2025
195671c
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 13, 2025
67cc51a
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 13, 2025
8323276
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 13, 2025
9c47f68
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 13, 2025
1149f38
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 13, 2025
7f30de2
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 13, 2025
9dcf5c4
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 13, 2025
c0bf35e
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 13, 2025
9f4f06c
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 13, 2025
f0b85a8
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 13, 2025
922fd4d
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 14, 2025
2a018ae
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 14, 2025
a1539ba
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 14, 2025
198c80e
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 14, 2025
21b66a1
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 14, 2025
466e6b8
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 14, 2025
2e9c237
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 14, 2025
9b90454
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 14, 2025
9bdc149
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 14, 2025
c1236bb
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 14, 2025
d462a35
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 14, 2025
d47810a
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 14, 2025
10e78b9
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 14, 2025
9a6c133
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 14, 2025
24281e9
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 14, 2025
ada2885
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 14, 2025
fa75f31
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 14, 2025
0710956
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 14, 2025
224923c
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 14, 2025
b284fc1
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 14, 2025
585e5e8
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 14, 2025
2a7ff96
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 14, 2025
0d54e24
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 14, 2025
9046498
Update utils.py to handle Google credentials for Heroku and local
jacquelinenyinawabagesera Sep 14, 2025
d492b8f
Update utils.py to handle Google credentials for Heroku and local
jacquelinenyinawabagesera Sep 14, 2025
6f3851e
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 14, 2025
60cde5b
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 14, 2025
8cc4311
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 14, 2025
6daa356
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 14, 2025
575d9cb
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 14, 2025
15ab941
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 15, 2025
b676c79
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 15, 2025
75b0d75
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 15, 2025
d1d2cc5
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 15, 2025
138f186
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 15, 2025
196e0a7
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 15, 2025
ef6f4c6
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 15, 2025
f70ae15
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 15, 2025
62ebbd4
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 15, 2025
347ae22
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 15, 2025
46ad5e4
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 15, 2025
aa4056d
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 15, 2025
5858e25
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 15, 2025
4bbcd32
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 15, 2025
a87a353
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 15, 2025
7cbdb18
postman
jacquelinenyinawabagesera Sep 15, 2025
91b73d9
Added the credentials for google calendar
jacquelinenyinawabagesera Sep 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .github/workflows/postman.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# test
name: Automated API tests using Postman CLI
on:
push:
branches:
- main
- develop
- feature/test-workflows
pull_request:
branches:
- main
- develop
workflow_dispatch:
jobs:
automated-api-tests:
runs-on: macos-latest
env:
COL_UID: ${{secrets.COL_UID}}
ENV_UID: ${{secrets.ENV_UID}}
POSTMAN_API_KEY: ${{secrets.POSTMAN_API_KEY}}
steps:
- uses: actions/checkout@v4
- name: Install Postman CLI
run: |
curl -o- "https://dl-cli.pstmn.io/install/osx_64.sh" | sh
- name: Login to Postman CLI
run: postman login --with-api-key "$POSTMAN_API_KEY"
- name: Run API tests
run: |
mkdir -p reports
postman collection run "$COL_UID" -e "$ENV_UID" \
--reporters cli,junit \
--reporter-junit-export reports/junit.xml
- name: Upload JUnit report
uses: actions/upload-artifact@v4
with:
name: postman-junit
path: reports/junit.xml
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ staticfiles
credentials.json
saved_login.pickle
.env
token_1.pickle
vemv
vemv
media
2 changes: 1 addition & 1 deletion ai_reports/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 4.2.24 on 2025-09-12 12:11
# Generated by Django 5.2.6 on 2025-09-14 07:03

from django.db import migrations, models

Expand Down
5 changes: 3 additions & 2 deletions ai_reports/migrations/0002_initial.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Generated by Django 4.2.24 on 2025-09-12 12:11
# Generated by Django 5.2.6 on 2025-09-14 07:03

from django.db import migrations, models
import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):
Expand All @@ -18,6 +18,7 @@ class Migration(migrations.Migration):
model_name="aireport",
name="conversation_id",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="interviewConversation.interviewconversation",
Expand Down
11 changes: 9 additions & 2 deletions ai_reports/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@
from django.utils import timezone
from interviewConversation.models import InterviewConversation


class AIReport(models.Model):
report_id = models.AutoField(primary_key=True)
conversation_id = models.ForeignKey(InterviewConversation, on_delete=models.CASCADE, null=True)
conversation_id = models.ForeignKey(
InterviewConversation,
on_delete=models.CASCADE,
null=True,
blank=True
)
skill_match_score = models.DecimalField(
max_digits=10,
decimal_places=2,
Expand All @@ -28,4 +34,5 @@ class AIReport(models.Model):


def __str__(self):
return f"AI Report #{self.report_id} for Conversation {self.conversation_id}"
convo_id = self.conversation_id.conversation_id if self.conversation_id else 'None'
return f"AI Report #{self.report_id} for Conversation {convo_id}"
90 changes: 57 additions & 33 deletions api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ def get_file_type_display(self, obj):
else:
return "File"


class InterviewConversationSerializer(serializers.ModelSerializer):
class Meta:
model = InterviewConversation
Expand All @@ -60,27 +59,25 @@ class InterviewSerializer(serializers.ModelSerializer):
recruiter_email = serializers.CharField(source='recruiter.email', read_only=True)
is_upcoming = serializers.BooleanField(read_only=True)
end_time = serializers.DateTimeField(read_only=True)

class Meta:
model = Interview
fields = [
'id',
'interview_id',
'candidate', 'recruiter',
'candidate_name', 'candidate_email',
'recruiter_name', 'recruiter_email',
'title', 'description',
'scheduled_at', 'duration', 'end_time',
'interview_link', 'required_preparation',
'status',
'google_event_id', 'google_calendar_link', 'send_calendar_invite',
'is_upcoming',
'created_at', 'updated_at', 'completed_at'
'status', 'google_event_id', 'google_calendar_link', 'send_calendar_invite',
'is_upcoming', 'created_at', 'updated_at', 'completed_at'
]
read_only_fields = [
'candidate_id', 'candidate_name', 'candidate_email',
'candidate_name', 'candidate_email',
'recruiter_name', 'recruiter_email',
'end_time', 'is_upcoming', 'google_event_id',
'google_calendar_link', 'created_at', 'updated_at',
'end_time', 'is_upcoming', 'google_event_id',
'google_calendar_link', 'created_at', 'updated_at',
'completed_at'
]

Expand Down Expand Up @@ -114,17 +111,33 @@ class InterviewCreateSerializer(InterviewSerializer):

class Meta(InterviewSerializer.Meta):
read_only_fields = InterviewSerializer.Meta.read_only_fields + [
'status', 'result', 'google_event_id', 'google_calendar_link'
'status', 'result'
]

def validate(self, data):
"""Additional validation for creation"""
data = super().validate(data)

if self.instance is None and 'candidate' not in data:
raise serializers.ValidationError({
'candidate': 'This field is required when creating an interview.'
})

if self.instance is None and 'recruiter' not in data:
raise serializers.ValidationError({
'recruiter': 'This field is required when creating an interview.'
})

if self.instance is None:
data['status'] = 'draft'

return data

def create(self, validated_data):
if 'recruiter' not in validated_data:
validated_data['recruiter'] = self.context['request'].user

return super().create(validated_data)

class InterviewUpdateSerializer(InterviewSerializer):
"""Serializer specifically for updating interviews"""
Expand Down Expand Up @@ -174,7 +187,7 @@ class InterviewCalendarSerializer(serializers.ModelSerializer):
class Meta:
model = Interview
fields = [
'id', 'title', 'start', 'end', 'candidate_name',
'interview_id', 'title', 'start', 'end', 'candidate_name',
'job_title', 'interview_type', 'status', 'interview_link'
]

Expand Down Expand Up @@ -214,21 +227,32 @@ class Meta:

class JobSerializer(serializers.ModelSerializer):
company_name = serializers.CharField(source='company.company_name', read_only=True)
company_id = serializers.IntegerField(source='company.company_id', read_only=True)

class Meta:
model = Job
fields = [
'job_id', 'company', 'company_name', 'odoo_job_id', 'job_title',
'job_description', 'generated_job_summary', 'state', 'posted_at',
'expired_at', 'created_at', 'updated_at'
'job_id',
'company',
'company_name',
'company_id',
'job_title',
'job_description',
'generated_job_summary',
'state',
'posted_at',
'expired_at',
'created_at'
]
read_only_fields = ['job_id', 'created_at', 'updated_at']

extra_kwargs = {
'company': {'required': True},
'expired_at': {'required': False}
}

class CandidateSerializer(serializers.ModelSerializer):
job_title = serializers.CharField(source='job.job_title', read_only=True)
company_name = serializers.CharField(source='job.company.company_name', read_only=True)

class Meta:
model = Candidate
fields = [
Expand All @@ -240,6 +264,7 @@ class Meta:
read_only_fields = ['candidate_id', 'created_at', 'updated_at']



class RecruiterSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)

Expand Down Expand Up @@ -272,23 +297,22 @@ def to_representation(self, instance):
return representation

class CompanySerializer(serializers.ModelSerializer):
recruiter_email = serializers.CharField(source='recruiter.email', read_only=True)
recruiter_id = serializers.IntegerField(source='recruiter.id', read_only=True)
class Meta:
model = Company
fields = ['company_id', 'company_name', 'created_at']
read_only_fields = ['company_id', 'created_at']

def validate_company_name(self, value):
"""Ensure company name is unique for this recruiter"""
request = self.context.get('request')
if request and request.user.is_authenticated:
if Company.objects.filter(
recruiter=request.user,
company_name__iexact=value
).exists():
raise serializers.ValidationError(
"You already have a company with this name."
)
return value
fields = [
'company_id',
'company_name',
'recruiter',
'recruiter_email',
'recruiter_id',
'odoo_credentials',
'is_active',
'created_at',
'updated_at'
]
read_only_fields = ['recruiter', 'created_at', 'updated_at']

class AIReportSerializer(serializers.ModelSerializer):
class Meta:
Expand Down
12 changes: 10 additions & 2 deletions api/urls.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

from rest_framework.routers import DefaultRouter
from .views import InterviewConversationViewSet, JobViewSet, CandidateViewSet, AIReportViewSet, InterviewViewSet
from django.urls import path,include
Expand Down Expand Up @@ -30,12 +29,21 @@
path('sync/all-data/', views.sync_all_data, name='sync_all_data'),
path('jobs/<int:job_id>/candidates/', views.get_candidates_by_job, name='get_candidates_by_job'),
path('sync/jobs/company/<int:company_id>/', views.sync_jobs_for_company, name='sync_jobs_for_company'),
path('sync/jobs/handle-duplicates/', views.sync_jobs_handle_duplicates, name='sync_jobs_handle_duplicates'),
path('sync/candidates/job/<int:job_id>/', views.sync_candidates_for_job, name='sync_candidates_for_job'),
path('sync/candidates/company/<int:company_id>/', views.sync_candidates_for_company, name='sync_candidates_for_company'),
path('sync/candidates/all/', views.sync_all_candidates, name='sync_all_candidates'),
path('companies/<int:company_id>/jobs/', views.get_jobs_by_company, name='get_jobs_by_company'),
path('candidates/<int:candidate_id>/attachments/', views.get_candidate_attachments, name='get_candidate_attachments'),
path('sync/jobs/user/', views.sync_jobs_for_user, name='sync_jobs_for_user'),
path('candidates/<int:candidate_id>/attachments/download/<int:attachment_id>/', views.download_candidate_attachment, name='download_candidate_attachment'),
path('sync/candidates/<int:candidate_id>/attachments/', views.sync_candidate_attachments, name='sync_candidate_attachments'),
path('interviews/create/', views.create_interview, name='create-interview'),
path('interviews/<int:interview_id>/create-calendar-event/', views.create_interview_event, name='create-calendar-event'),
path('interviews/<int:interview_id>/analytics/', views.get_interview_analytics, name='get-interview-analytics'),
]
path('auth/google/initiate/', views.google_auth_initiate, name='google_auth_initiate'),
path('auth/google/callback/', views.google_auth_callback, name='google_auth_callback'),
path('api/auth/google/callback/', views.google_auth_callback, name='api_google_auth_callback'),
path('update-profile/', views.update_profile, name='update-profile'),
path('delete-account/', views.delete_account, name='delete-account'),
]
Loading
Loading