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

fix: server date for weather datapoint upload same day #9

Merged
merged 9 commits into from
Jan 7, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 6 additions & 0 deletions .env.local.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Database
MONGO_URL=

# Firebase
MOBILE_CLIENT_ID=
JWT_SECRET=
2 changes: 0 additions & 2 deletions .env.sample

This file was deleted.

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ lerna-debug.log*
.env
.env.dev
.env.prod
.env.local
57 changes: 20 additions & 37 deletions src/modules/points/points.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,17 @@ import { PointsController } from './points.controller';
import { PointsService } from './points.service';
import { CreateWeatherDatumDto } from '../weather-data/dto/create-weather-datum.dto';
import { PointsConfigs } from './configs/points.config';
import { SessionService } from '../users/session/session.service';
import { AuthService } from '../auth/auth.service';
import { JwtService } from '@nestjs/jwt';
import { UsersService } from '../users/users.service';
import { OAuth2Client } from 'google-auth-library';
import { User, UserSchema } from '../users/entities/user.entity';

describe('PointsService', () => {
let pointsService: PointsService;

let mockUserModel = Model<User>;
let mockPointTrackerModel = Model<PointTracker>;
let mockPointTransactionModel = Model<PointTransaction>;
let mockLastProcessedEntryModel = Model<LastProcessedEntry>;
Expand All @@ -41,6 +48,7 @@ describe('PointsService', () => {
const uri = mongod.getUri();
mockMongoConnection = (await connect(uri)).connection;

mockUserModel = mockMongoConnection.model(User.name, UserSchema);
mockPointTrackerModel = mockMongoConnection.model(
PointTracker.name,
PointTrackerSchema,
Expand All @@ -63,6 +71,15 @@ describe('PointsService', () => {
controllers: [PointsController],
providers: [
PointsService,
SessionService,
AuthService,
JwtService,
UsersService,
OAuth2Client,
{
provide: getModelToken(User.name),
useValue: mockUserModel,
},
{
provide: getModelToken(PointTracker.name),
useValue: mockPointTrackerModel,
Expand Down Expand Up @@ -137,51 +154,17 @@ describe('PointsService', () => {
expect(result).toBe(0);
});

it('should return the correct points for a single future weather data point for a new date', async () => {
it('should return the correct points for a single future weather data point for a today', async () => {
const result = await pointsService.calculatePoints(
author_user_id,
123456789,
[{ timestamp: 123456790 }] as CreateWeatherDatumDto[],
123456789000,
[{ timestamp: new Date().getTime() }] as CreateWeatherDatumDto[],
session,
);

expect(result).toBe(
PointsConfigs.POINTS_PER_HOUR + PointsConfigs.POINTS_PER_DAY,
);
});

it('should return the correct points for multiple future weather data points for a single new date.', async () => {
const result = await pointsService.calculatePoints(
author_user_id,
123456789,
[
{ timestamp: 1704523621000 },
{ timestamp: 1704527221000 },
] as CreateWeatherDatumDto[],
session,
);

expect(result).toBe(
PointsConfigs.POINTS_PER_HOUR * 2 + PointsConfigs.POINTS_PER_DAY,
);
});

it('should return the correct points for multiple future weather data points for a multiple new dates.', async () => {
const result = await pointsService.calculatePoints(
author_user_id,
123456789,
[
{ timestamp: 1704192176000 },
{ timestamp: 1704192695000 },
{ timestamp: 1704192854000 },
{ timestamp: 1704332150000 },
] as CreateWeatherDatumDto[],
session,
);

expect(result).toBe(
PointsConfigs.POINTS_PER_HOUR * 2 + PointsConfigs.POINTS_PER_DAY * 2,
);
});
});
});
21 changes: 16 additions & 5 deletions src/modules/points/points.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ export class PointsService {
const currentUserPoints = await this.pointModel.findOne({ author_user_id });
const currentPoints = currentUserPoints ? currentUserPoints.amount : 0;

let updatedPoints = currentPoints;

// Check the type of transaction and validate accordingly.
if (
transactionType === PointTransactionTypes.REDEEM &&
Expand All @@ -139,7 +141,7 @@ export class PointsService {
throw new Error('Not enough points to redeem.');
} else if (transactionType === PointTransactionTypes.DEDUCT) {
// For DEDUCT type, adjust the 'reduceBy' value to not go below zero.
reduceBy = currentPoints < Math.abs(reduceBy) ? -currentPoints : reduceBy;
updatedPoints = Math.max(currentPoints - Math.abs(reduceBy), 0);
}

try {
Expand All @@ -149,8 +151,8 @@ export class PointsService {
author_user_id,
},
{
$inc: {
amount: reduceBy, // Increment negative.
$set: {
amount: updatedPoints,
},
last_point_calculated_timestamp: Date.now(),
},
Expand Down Expand Up @@ -339,6 +341,7 @@ export class PointsService {
// Get the existing point trackers from DB and populate local cache.
const existingTrackers = await this.pointTrackerModel.find(
{
author_user_id,
date: { $in: uniqueDates },
},
null,
Expand All @@ -356,7 +359,7 @@ export class PointsService {
for (const weatherDatum of newWeatherData) {
// Only consider future data.
if (weatherDatum.timestamp > lastProcessedTimestamp) {
const date = new Date(weatherDatum.timestamp);
const date = new Date(weatherDatum.timestamp); // UTC received.
date.setUTCHours(0, 0, 0, 0);
const dateISO = date.toISOString();
const hourOnly = new Date(weatherDatum.timestamp).getUTCHours();
Expand All @@ -371,7 +374,15 @@ export class PointsService {
localPointTrackers.set(dateISO, processedHours);

if (isNewDay) {
points += PointsConfigs.POINTS_PER_DAY;
// Check if the datapoint is uploaded within same day.
// Check if server date.
const serverDate = new Date();
serverDate.setUTCHours(0, 0, 0, 0);
const serverDateISO = serverDate.toISOString();

if (dateISO === serverDateISO) {
points += PointsConfigs.POINTS_PER_DAY;
}
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/modules/weather-data/entities/weather-datum.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ export class WeatherDatum {

@Prop()
percentage_light_intensity: number;

@Prop()
tvoc: number;
}

export type WeatherDatumDocument = WeatherDatum & Document;
Expand Down
19 changes: 14 additions & 5 deletions src/modules/weather-data/weather-data.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export class WeatherDataService {
const session = await this.mongoConnection.startSession();
session.startTransaction();
let insertedData = [];
let existingWeatherData = [];
try {
// (1) Commit weather data into the database.
try {
Expand All @@ -71,7 +72,7 @@ export class WeatherDataService {

// Filter out the weather data that already exists within the database with same timestamps.
// This is to prevent duplicate weather data.
const existingWeatherData = await this.weatherDatumModel
existingWeatherData = await this.weatherDatumModel
.find({
timestamp: {
$in: data.map((datum) => datum.timestamp),
Expand All @@ -80,11 +81,17 @@ export class WeatherDataService {
.exec();

// Remove the existing weather data from the data to be inserted.
// Also check timestamp of weather datapoint
const currentUtcTimestamp = new Date().getTime(); // Get current UTC timestamp

data = data.filter((datum) => {
const datumDate = new Date(datum.timestamp); // Convert timestamp to Date object
return !existingWeatherData.some(
(existingDatum) =>
existingDatum.timestamp.getTime() === datumDate.getTime(),
return (
datumDate.getTime() <= currentUtcTimestamp + 86400000 && // Check if the timestamp is not in the future
!existingWeatherData.some(
(existingDatum) =>
existingDatum.timestamp.getTime() === datumDate.getTime(),
)
);
});

Expand Down Expand Up @@ -134,7 +141,9 @@ export class WeatherDataService {
await session.commitTransaction();

// Return the _id, timestamp, created_at fields.
return insertedData.map((datum) => {
const responseData = [...insertedData, ...existingWeatherData];

return responseData.map((datum) => {
return {
_id: datum._id,
timestamp: datum.timestamp,
Expand Down
13 changes: 8 additions & 5 deletions src/modules/weather-stations/weather-stations.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,18 +68,21 @@ export class WeatherStationsController {
);

// Get points of the user of the weather station.
const pointsOfUser = await this.pointsService.findByUserId(
let pointsOfUser = null;
if (!weatherData) {
return {
weatherData,
pointsOfUser,
};
}
pointsOfUser = await this.pointsService.findByUserId(
weatherData.author_user_id,
);

return {
weatherData,
pointsOfUser,
};

// return this.weatherDataService.findLatestByWeatherStationId(
// weatherStationId,
// );
}

@Patch(':id')
Expand Down
Loading