Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Task #218152 Created the cron job and function to calculate the distnace between camp and attendance location #1152

Open
wants to merge 3 commits into
base: release-2.8.4
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
1 change: 1 addition & 0 deletions src/src/attendances/attendances.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export class AttendancesService {
'status',
'lat',
'long',
'camp_to_attendance_location_distance',
];

constructor(
Expand Down
45 changes: 45 additions & 0 deletions src/src/camp/camp.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2575,9 +2575,53 @@ export class CampService {
}

async markCampAttendance(body: any, req: any, resp: any) {
const gqlQuery = {
query: `query MyQuery {
response : camp_days_activities_tracker(where: {id: {_eq: ${body.context_id}}}) {
camp{
property_id
properties{
lat
long
}
}
}
}`,
};
const result = await this.hasuraServiceFromServices.getData(gqlQuery);

const campLat = result?.data?.response?.[0]?.camp?.properties?.lat;
const campLong = result?.data?.response?.[0]?.camp?.properties?.long;

const sql = `SELECT CASE WHEN COALESCE(NULLIF('${campLat}', ''), 'null') = 'null'
OR COALESCE(NULLIF('${body.lat}', ''), 'null') = 'null'
OR COALESCE(NULLIF('${campLong}', ''), 'null') = 'null'
OR COALESCE(NULLIF('${body.long}', ''), 'null') = 'null' THEN
0::numeric
ELSE round( cast( 6371 * 2 * asin( sqrt( power(sin(radians(COALESCE(NULLIF('${campLat}', '0')::DOUBLE PRECISION, 0) - COALESCE(NULLIF('${body.lat}', '0')::DOUBLE PRECISION, 0)) / 2), 2) + cos(radians(COALESCE(NULLIF('${campLat}', '0')::DOUBLE PRECISION, 0))) * cos(radians(COALESCE(NULLIF('${campLat}', '0')::DOUBLE PRECISION, 0))) * power(sin(radians(COALESCE(NULLIF('${campLong}', '0')::DOUBLE PRECISION, 0) - COALESCE(NULLIF('${body.long}', '0')::DOUBLE PRECISION, 0)) / 2), 2) ) ) AS numeric ), 2 )
END`;

let calculatedDistance;
try {
const sqlResult = (
await this.hasuraServiceFromServices.executeRawSql(sql)
).result;
YoginiTayade marked this conversation as resolved.
Show resolved Hide resolved
const formattedData =
this.hasuraServiceFromServices.getFormattedData(sqlResult);
calculatedDistance = formattedData[0].round;
YoginiTayade marked this conversation as resolved.
Show resolved Hide resolved
} catch (error) {
console.log('Error occurred in calculating distance ');
return resp.json({
status: 500,
message: 'CAMP_ATTENDANCE_ERROR',
data: error,
});
}

const camp_attendance_body = {
...body,
context: 'camp_days_activities_tracker',
camp_to_attendance_location_distance: calculatedDistance,
};

const response = await this.attendancesService.createAttendance(
Expand All @@ -2594,6 +2638,7 @@ export class CampService {
'created_by',
'updated_by',
'context',
'camp_to_attendance_location_distance',
],
);

Expand Down
2 changes: 2 additions & 0 deletions src/src/cron/cron.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { FaAttendanceProcessingCron } from './faAttendanceProcessing.cron';
import { FaFaceIndexingCron } from './faFaceIndexing.cron';
import { FaUserIndexingCron } from './faUserIndexing.cron';
import { PrepareCertificateHtmlCron } from './prepareCertificateHtml.cron';
import { DistanceCalculation } from './distanceCalculation.cron';
@Module({
imports: [
AwsRekognitionModule,
Expand All @@ -23,6 +24,7 @@ import { PrepareCertificateHtmlCron } from './prepareCertificateHtml.cron';
PrepareCertificateHtmlCron,
CampEndCron,
Method,
DistanceCalculation,
],
})
export class CronModule {}
146 changes: 146 additions & 0 deletions src/src/cron/distanceCalculation.cron.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { Injectable } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import { HasuraService } from 'src/services/hasura/hasura.service';

@Injectable()
export class DistanceCalculation {
constructor(private hasuraServiceFromService: HasuraService) {}
private async haversineFormula(
campLat: any,
campLong: any,
attendanceLat: any,
attendanceLong: any,
Comment on lines +9 to +12
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using more specific types for latitude and longitude parameters.

Using any type for latitude and longitude parameters reduces type safety. It's better to use number type for these parameters to ensure that the values being passed are of the expected type.

- campLat: any,
- campLong: any,
- attendanceLat: any,
- attendanceLong: any,
+ campLat: number,
+ campLong: number,
+ attendanceLat: number,
+ attendanceLong: number,

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
campLat: any,
campLong: any,
attendanceLat: any,
attendanceLong: any,
campLat: number,
campLong: number,
attendanceLat: number,
attendanceLong: number,

) {
const sql = `SELECT CASE WHEN COALESCE(NULLIF('${campLat}', ''), 'null') = 'null'
OR COALESCE(NULLIF('${attendanceLat}', ''), 'null') = 'null'
OR COALESCE(NULLIF('${campLong}', ''), 'null') = 'null'
OR COALESCE(NULLIF('${attendanceLong}', ''), 'null') = 'null' THEN
0::numeric
ELSE round( cast( 6371 * 2 * asin( sqrt( power(sin(radians(COALESCE(NULLIF('${campLat}', '0')::DOUBLE PRECISION, 0) - COALESCE(NULLIF('${attendanceLat}', '0')::DOUBLE PRECISION, 0)) / 2), 2) + cos(radians(COALESCE(NULLIF('${campLat}', '0')::DOUBLE PRECISION, 0))) * cos(radians(COALESCE(NULLIF('${campLat}', '0')::DOUBLE PRECISION, 0))) * power(sin(radians(COALESCE(NULLIF('${campLong}', '0')::DOUBLE PRECISION, 0) - COALESCE(NULLIF('${attendanceLong}', '0')::DOUBLE PRECISION, 0)) / 2), 2) ) ) AS numeric ), 2 )
Comment on lines +14 to +19
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure SQL queries are safe from injection vulnerabilities.

The current implementation of the SQL query in haversineFormula method might be vulnerable to SQL injection if not properly sanitized. Consider using parameterized queries to enhance security.

END`;
try {
const response = (
await this.hasuraServiceFromService.executeRawSql(sql)
).result;
const formattedData =
this.hasuraServiceFromService.getFormattedData(response);
return formattedData[0].round;
} catch {
return [];
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider refactoring haversineFormula to use application logic instead of SQL for distance calculation.

- private async haversineFormula(campLat: any, campLong: any, attendanceLat: any, attendanceLong: any) {
-   const sql = `SELECT CASE WHEN COALESCE(NULLIF('${campLat}', ''), 'null') = 'null'
-     OR     COALESCE(NULLIF('${attendanceLat}', ''), 'null') = 'null'
-     OR     COALESCE(NULLIF('${campLong}', ''), 'null') = 'null'
-     OR     COALESCE(NULLIF('${attendanceLong}', ''), 'null') = 'null' THEN
-     0::numeric
-     ELSE round( cast( 6371 * 2 * asin( sqrt( power(sin(radians(COALESCE(NULLIF('${campLat}', '0')::DOUBLE PRECISION, 0) - COALESCE(NULLIF('${attendanceLat}', '0')::DOUBLE PRECISION, 0)) / 2), 2) + cos(radians(COALESCE(NULLIF('${campLat}', '0')::DOUBLE PRECISION, 0))) * cos(radians(COALESCE(NULLIF('${campLat}', '0')::DOUBLE PRECISION, 0))) * power(sin(radians(COALESCE(NULLIF('${campLong}', '0')::DOUBLE PRECISION, 0) - COALESCE(NULLIF('${attendanceLong}', '0')::DOUBLE PRECISION, 0)) / 2), 2) ) ) AS numeric ), 2 )
-     END`;
-   try {
-     const response = (await this.hasuraServiceFromService.executeRawSql(sql)).result;
-     const formattedData = this.hasuraServiceFromService.getFormattedData(response);
-     return formattedData[0].round;
-   } catch {
-     return [];
-   }
- }
+ private haversineFormula(campLat: number, campLong: number, attendanceLat: number, attendanceLong: number): number {
+   const R = 6371; // Radius of the Earth in km
+   const dLat = (attendanceLat - campLat) * (Math.PI / 180);
+   const dLon = (attendanceLong - campLong) * (Math.PI / 180);
+   const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
+             Math.cos(campLat * (Math.PI / 180)) * Math.cos(attendanceLat * (Math.PI / 180)) *
+             Math.sin(dLon / 2) * Math.sin(dLon / 2);
+   const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+   return R * c; // Distance in km
+ }

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
private async haversineFormula(
campLat: any,
campLong: any,
attendanceLat: any,
attendanceLong: any,
) {
const sql = `SELECT CASE WHEN COALESCE(NULLIF('${campLat}', ''), 'null') = 'null'
OR COALESCE(NULLIF('${attendanceLat}', ''), 'null') = 'null'
OR COALESCE(NULLIF('${campLong}', ''), 'null') = 'null'
OR COALESCE(NULLIF('${attendanceLong}', ''), 'null') = 'null' THEN
0::numeric
ELSE round( cast( 6371 * 2 * asin( sqrt( power(sin(radians(COALESCE(NULLIF('${campLat}', '0')::DOUBLE PRECISION, 0) - COALESCE(NULLIF('${attendanceLat}', '0')::DOUBLE PRECISION, 0)) / 2), 2) + cos(radians(COALESCE(NULLIF('${campLat}', '0')::DOUBLE PRECISION, 0))) * cos(radians(COALESCE(NULLIF('${campLat}', '0')::DOUBLE PRECISION, 0))) * power(sin(radians(COALESCE(NULLIF('${campLong}', '0')::DOUBLE PRECISION, 0) - COALESCE(NULLIF('${attendanceLong}', '0')::DOUBLE PRECISION, 0)) / 2), 2) ) ) AS numeric ), 2 )
END`;
try {
const response = (
await this.hasuraServiceFromService.executeRawSql(sql)
).result;
const formattedData =
this.hasuraServiceFromService.getFormattedData(response);
return formattedData[0].round;
} catch {
return [];
}
private haversineFormula(campLat: number, campLong: number, attendanceLat: number, attendanceLong: number): number {
const R = 6371; // Radius of the Earth in km
const dLat = (attendanceLat - campLat) * (Math.PI / 180);
const dLon = (attendanceLong - campLong) * (Math.PI / 180);
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(campLat * (Math.PI / 180)) * Math.cos(attendanceLat * (Math.PI / 180)) *
Math.sin(dLon / 2) * Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c; // Distance in km
}

}
private async fetchData(limit: number) {
const gqlQuery = {
query: `query MyQuery {
result : camp_days_activities_tracker(where: {attendances: {context: {_eq: "camp_days_activities_tracker"}, camp_to_attendance_location_distance: {_is_null: true}}}, limit: ${limit}){
attendances{
id
user_id
lat
long
}
camp{
properties{
id
lat
long
}
}
}
}

`,
};

try {
const result = await this.hasuraServiceFromService.getData(
gqlQuery,
);

const data = result?.data?.result;

if (data) {
return data;
} else {
return [];
}
} catch (error) {
return [];
}
}

//cron runs for each hour's 30th minute
@Cron('30 * * * *')
private async processData() {
const user = await this.fetchData(10);

try {
for (const userData of user) {
const campData = userData.camp?.properties;
const attendanceData = userData?.attendances?.[0];
const attendanceId = attendanceData.id;
console.log('ATTENDANCEDATA ', attendanceData);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

keep console log only if neccessary otherwise remove it

console.log('CAMPDATA ', campData);

if (!campData || !attendanceData) {
console.log('SKIP - Missing campData or attendanceData');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here instead of console log is return statement expected?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are skippin the records so we cannot return anything there

continue;
}
const campLat = parseFloat(campData.lat);
const campLong = parseFloat(campData.long);
const attendanceLat = parseFloat(attendanceData.lat);
const attendanceLong = parseFloat(attendanceData.long);
console.log(
'campLat ',
campLat,
'campLong ',
campLong,
'attendanceLat ',
attendanceLat,
'attendanceLong ',
attendanceLong,
);
if (
isNaN(campLat) ||
isNaN(campLong) ||
isNaN(attendanceLat) ||
isNaN(attendanceLong)
) {
console.log('SKIP - Invalid coordinates');
continue;
}

const calculatedDistance = await this.haversineFormula(
campLat,
campLong,
attendanceLat,
attendanceLong,
);
console.log(
'Distance between attendance and camp location ',
calculatedDistance,
);
const updateQuery = {
query: `mutation MyMutation {
update_attendance(where: {id: {_eq: ${attendanceId}}}, _set: {camp_to_attendance_location_distance: ${calculatedDistance}}) {
returning {
id
camp_to_attendance_location_distance
}
}
}

`,
};
console.log(updateQuery.query);

const updatedResult =
await this.hasuraServiceFromService.getData(updateQuery);
console.log(JSON.stringify(updatedResult));
}
} catch (error) {
console.log('Error occurred while updating ', error);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enhance error handling by logging more detailed information.

When logging errors, it's beneficial to log more detailed information about the error to aid in debugging. Consider logging the error message along with the stack trace.

- console.log('Error occurred while updating ', error);
+ console.error('Error occurred while updating: ', error.message, error.stack);

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
console.log('Error occurred while updating ', error);
console.error('Error occurred while updating: ', error.message, error.stack);

return [];
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The processData method is well-implemented. Consider enhancing error logging for better traceability.

- console.log('Error occurred while updating ', error);
+ console.error('Error occurred while updating: ', error.message);

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
//cron runs for each hour's 30th minute
@Cron('30 * * * *')
private async processData() {
const user = await this.fetchData(10);
try {
for (const userData of user) {
const campData = userData.camp?.properties;
const attendanceData = userData?.attendances?.[0];
const attendanceId = attendanceData.id;
console.log('ATTENDANCEDATA ', attendanceData);
console.log('CAMPDATA ', campData);
if (!campData || !attendanceData) {
console.log('SKIP - Missing campData or attendanceData');
continue;
}
const campLat = parseFloat(campData.lat);
const campLong = parseFloat(campData.long);
const attendanceLat = parseFloat(attendanceData.lat);
const attendanceLong = parseFloat(attendanceData.long);
console.log(
'campLat ',
campLat,
'campLong ',
campLong,
'attendanceLat ',
attendanceLat,
'attendanceLong ',
attendanceLong,
);
if (
isNaN(campLat) ||
isNaN(campLong) ||
isNaN(attendanceLat) ||
isNaN(attendanceLong)
) {
console.log('SKIP - Invalid coordinates');
continue;
}
const calculatedDistance = await this.haversineFormula(
campLat,
campLong,
attendanceLat,
attendanceLong,
);
console.log(
'Distance between attendance and camp location ',
calculatedDistance,
);
const updateQuery = {
query: `mutation MyMutation {
update_attendance(where: {id: {_eq: ${attendanceId}}}, _set: {camp_to_attendance_location_distance: ${calculatedDistance}}) {
returning {
id
camp_to_attendance_location_distance
}
}
}
`,
};
console.log(updateQuery.query);
const updatedResult =
await this.hasuraServiceFromService.getData(updateQuery);
console.log(JSON.stringify(updatedResult));
}
} catch (error) {
console.log('Error occurred while updating ', error);
return [];
}
//cron runs for each hour's 30th minute
@Cron('30 * * * *')
private async processData() {
const user = await this.fetchData(10);
try {
for (const userData of user) {
const campData = userData.camp?.properties;
const attendanceData = userData?.attendances?.[0];
const attendanceId = attendanceData.id;
console.log('ATTENDANCEDATA ', attendanceData);
console.log('CAMPDATA ', campData);
if (!campData || !attendanceData) {
console.log('SKIP - Missing campData or attendanceData');
continue;
}
const campLat = parseFloat(campData.lat);
const campLong = parseFloat(campData.long);
const attendanceLat = parseFloat(attendanceData.lat);
const attendanceLong = parseFloat(attendanceData.long);
console.log(
'campLat ',
campLat,
'campLong ',
campLong,
'attendanceLat ',
attendanceLat,
'attendanceLong ',
attendanceLong,
);
if (
isNaN(campLat) ||
isNaN(campLong) ||
isNaN(attendanceLat) ||
isNaN(attendanceLong)
) {
console.log('SKIP - Invalid coordinates');
continue;
}
const calculatedDistance = await this.haversineFormula(
campLat,
campLong,
attendanceLat,
attendanceLong,
);
console.log(
'Distance between attendance and camp location ',
calculatedDistance,
);
const updateQuery = {
query: `mutation MyMutation {
update_attendance(where: {id: {_eq: ${attendanceId}}}, _set: {camp_to_attendance_location_distance: ${calculatedDistance}}) {
returning {
id
camp_to_attendance_location_distance
}
}
}
`,
};
console.log(updateQuery.query);
const updatedResult =
await this.hasuraServiceFromService.getData(updateQuery);
console.log(JSON.stringify(updatedResult));
}
} catch (error) {
console.error('Error occurred while updating: ', error.message);
return [];
}

}
}