Skip to content
This repository has been archived by the owner on Oct 4, 2024. It is now read-only.

Commit

Permalink
Assign points to the user if he has met his goals (#246)
Browse files Browse the repository at this point in the history
  • Loading branch information
henrybrink authored Jun 18, 2024
1 parent 5540082 commit 5cd2eff
Show file tree
Hide file tree
Showing 14 changed files with 180 additions and 13 deletions.
4 changes: 3 additions & 1 deletion backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ DATABASE_URL=file:./database.dev.sqlite

EMAIL_HOST=
EMAIL_USER=
EMAIL_PASS=
EMAIL_PASS=

WEBCRON_TOKEN=
17 changes: 17 additions & 0 deletions backend/prisma/migrations/20240618115304_/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Points" (
"userId" TEXT NOT NULL,
"day" INTEGER NOT NULL,
"points" INTEGER NOT NULL,
"streak" INTEGER NOT NULL,
"goalReached" BOOLEAN NOT NULL DEFAULT false,

PRIMARY KEY ("userId", "day"),
CONSTRAINT "Points_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO "new_Points" ("day", "points", "streak", "userId") SELECT "day", "points", "streak", "userId" FROM "Points";
DROP TABLE "Points";
ALTER TABLE "new_Points" RENAME TO "Points";
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;
11 changes: 6 additions & 5 deletions backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,12 @@ model TaskLog {
}

model Points {
userId String
owner User @relation(fields: [userId], references: [id])
day Int
points Int
streak Int
userId String
owner User @relation(fields: [userId], references: [id])
day Int
points Int
streak Int
goalReached Boolean @default(false)
@@id([userId, day])
}
Expand Down
2 changes: 2 additions & 0 deletions backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { NotificationModule } from './notification/notification.module';
import { TaskModule } from './app/tasks/task.module';
import { StreakModule } from './app/streaks/streak.module';
import { GoalModule } from './app/goals/goal.module';
import { WebcronModule } from './app/webcron/webcron.module';

@Module({
imports: [
Expand All @@ -25,6 +26,7 @@ import { GoalModule } from './app/goals/goal.module';
TaskModule,
StreakModule,
GoalModule,
WebcronModule,
],
controllers: [AppController, UserController],
providers: [
Expand Down
1 change: 1 addition & 0 deletions backend/src/app/goals/goal.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import FitnessModule from '../../integration/fitness/fitness.module';
@Module({
imports: [PrismaModule, FitnessModule],
providers: [GoalService],
exports: [GoalService],
controllers: [GoalController],
})
export class GoalModule {}
3 changes: 2 additions & 1 deletion backend/src/app/streaks/streak.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { Module } from '@nestjs/common';
import { StreakService } from './streak.service';
import { PrismaModule } from '../../db/prisma.module';
import { StreakController } from './streak.controller';
import { GoalModule } from '../goals/goal.module';

@Module({
imports: [PrismaModule],
imports: [PrismaModule, GoalModule],
providers: [StreakService],
controllers: [StreakController],
exports: [StreakService],
Expand Down
5 changes: 4 additions & 1 deletion backend/src/app/streaks/streak.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ import { Points } from '@prisma/client';
import { TestConstants } from '../../../test/lib/constants';
import * as dayjs from 'dayjs';
import { Streak } from './streak.type';
import { GoalModule } from '../goals/goal.module';

describe('streak service testing', () => {
let cut: StreakService;
let streakRepository: DeepMockProxy<StreakRepository>;

beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [PrismaModule],
imports: [PrismaModule, GoalModule],
providers: [StreakService],
})
.overrideProvider(StreakRepository)
Expand Down Expand Up @@ -64,6 +65,7 @@ describe('streak service testing', () => {
day: yesterday,
},
],
dailyGoalsReached: false,
} as Streak);
});

Expand All @@ -81,6 +83,7 @@ describe('streak service testing', () => {
points: 0,
streak: 0,
history: [],
dailyGoalsReached: false,
} as Streak);
});

Expand Down
70 changes: 65 additions & 5 deletions backend/src/app/streaks/streak.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@ import { Injectable } from '@nestjs/common';
import { Streak, StreakHistory } from './streak.type';
import * as dayjs from 'dayjs';
import { StreakRepository } from '../../db/repositories/streak.repository';
import { GoalService } from '../goals/goal.service';
import { UserRepository } from '../../db/repositories/user.repository';

@Injectable()
export class StreakService {
constructor(private repository: StreakRepository) {}
constructor(
private repository: StreakRepository,
private goalService: GoalService,
private userRepository: UserRepository,
) {}

/**
* Returns the streak information for a specific user
Expand All @@ -22,6 +28,7 @@ export class StreakService {
// Same applies to the streak value
let streak = 0;
let isStreak = false;
let dailyGoalsReached = false;

if (streakEntries.length > 0) {
// Verify that the streak is still active
Expand All @@ -31,6 +38,7 @@ export class StreakService {
isStreak = true;
points = streakEntries[0].points;
streak = streakEntries[0].streak;
dailyGoalsReached = streakEntries[0].goalReached == true;
}
}

Expand All @@ -46,6 +54,7 @@ export class StreakService {
} as StreakHistory;
})
: [],
dailyGoalsReached: dailyGoalsReached,
};
}

Expand All @@ -55,7 +64,11 @@ export class StreakService {
* @param user
* @param points
*/
public async addPoints(user: string, points: number) {
public async addPoints(
user: string,
points: number,
dailyGoalReached = false,
) {
const history = await this.repository.getStreakHistory(user, 0, 1);

if (history.length == 0) {
Expand All @@ -67,11 +80,22 @@ export class StreakService {
const yesterday = parseInt(dayjs().subtract(1, 'day').format('YYMMDD'));

if (history[0].day == today) {
this.repository.updatePoints(user, today, history[0].points + points);
this.repository.updatePoints(
user,
today,
history[0].points + points,
dailyGoalReached,
);
} else if (history[0].day == yesterday) {
this.repository.createStreak(user, points, history[0].streak);
this.repository.createStreak(
user,
today,
points,
history[0].streak,
dailyGoalReached,
);
} else {
this.createStreak(user, points, 0);
this.createStreak(user, points);
}
}

Expand All @@ -90,4 +114,40 @@ export class StreakService {
streak,
);
}

/**
* Verifies if a single user has met at least one of his goals
*
* @param user ID of the user
*/
private async verifyDailyGoalsForUser(user: string) {
// Verify that the daily goals have not been met yet.
const streak = await this.getStreakOf(user);

if (streak.dailyGoalsReached == true) {
return;
}

const goals = await this.goalService.getGoalsForUser(user);

if (goals.length > 0) {
for (const goal of goals) {
if (goal.value > goal.target) {
await this.addPoints(user, 10, true);
}
}
}
}

/**
* Verifies the goals for all users
*
*/
public async verifyGoalsOfUsers() {
const users = await this.userRepository.findAllEnabledUsers();

for (const user of users) {
await this.verifyDailyGoalsForUser(user.id);
}
}
}
1 change: 1 addition & 0 deletions backend/src/app/streaks/streak.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ export type Streak = {
points: number;
streak: number;
history: StreakHistory[];
dailyGoalsReached?: boolean;
};
40 changes: 40 additions & 0 deletions backend/src/app/webcron/webcron.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Controller, Get, Param, Res } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Response } from 'express';
import { StreakService } from '../streaks/streak.service';
import { NestRequest } from '../../types/request.type';

@Controller('/webcron')
export class WebcronController {
constructor(
private configService: ConfigService,
private streakService: StreakService,
) {}

@Get('/execute/:token')
public async execute(
@Res() request: NestRequest,
@Param('token') token: string,
@Res() response: Response,
) {
// This endpoint can be only called when a token was set
const actualToken = this.configService.get<string>('WEBCRON_TOKEN');

if (!actualToken || actualToken == '' || actualToken == ' ') {
return response.status(403).json({ error: 'Unauthorized' });
}

if (!token || actualToken != token) {
return response.status(403).json({ error: 'Unauthorized' });
}

// If the token was verified, execute the desired functions
setTimeout(() => {
try {
this.streakService.verifyGoalsOfUsers();
} catch (e) {}
}, 0);

response.status(204).json({});
}
}
17 changes: 17 additions & 0 deletions backend/src/app/webcron/webcron.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Module } from '@nestjs/common';
import { WebcronController } from './webcron.controller';
import { StreakModule } from '../streaks/streak.module';
import { ConfigModule } from '@nestjs/config';
import configuration from '../../config/configuration';

@Module({
imports: [
StreakModule,
ConfigModule.forRoot({
load: [configuration],
}),
],
exports: [],
controllers: [WebcronController],
})
export class WebcronModule {}
6 changes: 6 additions & 0 deletions backend/src/db/repositories/streak.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,15 @@ export class StreakRepository {
day: number,
points: number,
streak = 0,
dailyGoalReached = false,
): Promise<Points> {
return await this.prisma.points.create({
data: {
userId: user,
points,
day,
streak,
goalReached: dailyGoalReached,
},
});
}
Expand All @@ -67,6 +69,7 @@ export class StreakRepository {
user: string,
day: number,
points: number,
dailyGoalReached = false,
): Promise<Points> {
return await this.prisma.points.update({
where: {
Expand All @@ -77,6 +80,9 @@ export class StreakRepository {
},
data: {
points,
// There is only the option to set this, but not to unset this, so that the goalReached
// is not deleted, when a task log is verified.
goalReached: dailyGoalReached ? dailyGoalReached : undefined,
},
});
}
Expand Down
13 changes: 13 additions & 0 deletions backend/src/db/repositories/user.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,17 @@ export class UserRepository {
where,
});
}

/**
* Returns all enabled users
*
* @returns All enabled users
*/
public async findAllEnabledUsers(): Promise<User[]> {
return await this.prisma.user.findMany({
where: {
enabled: true,
},
});
}
}
3 changes: 3 additions & 0 deletions backend/test/lib/database.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,21 @@ export const PointEntries = {
day: parseInt(dayjs().subtract(1, 'day').format('YYMMDD')),
points: 10,
streak: 0,
goalReached: false,
} as Points,
streakTwoDaysAgo: {
userId: Users.exampleUser.id,
day: parseInt(dayjs().subtract(2, 'days').format('YYMMDD')),
points: 10,
streak: 0,
goalReached: false,
} as Points,
noStreakToday: {
userId: Users.exampleUser.id,
day: parseInt(dayjs().format('YYMMDD')),
points: 10,
streak: 0,
goalReached: false,
},
};

Expand Down

0 comments on commit 5cd2eff

Please sign in to comment.