Skip to content

Commit

Permalink
Merge pull request #482 from chaynHQ/develop
Browse files Browse the repository at this point in the history
Merge Develop onto Main
  • Loading branch information
annarhughes authored Jun 18, 2024
2 parents 54f9181 + 0881cba commit f50aeb9
Show file tree
Hide file tree
Showing 23 changed files with 308 additions and 53 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ To generate a new migration
yarn migration:generate
```

Add the new migration import into [typeorm.config.ts](./src/typeorm.config.ts)

To run (apply) migrations

```bash
Expand Down
4 changes: 4 additions & 0 deletions src/api/crisp/crisp-api.interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { EMAIL_REMINDERS_FREQUENCY } from '../../utils/constants';

export interface CrispProfileCustomFields {
signed_up_at?: string;
last_active_at?: string;
email_reminders_frequency?: EMAIL_REMINDERS_FREQUENCY;
language?: string;
marketing_permission?: boolean;
service_emails_permission?: boolean;
Expand Down
4 changes: 4 additions & 0 deletions src/api/mailchimp/mailchimp-api.interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { EMAIL_REMINDERS_FREQUENCY } from '../../utils/constants';

export enum MAILCHIMP_MERGE_FIELD_TYPES {
TEXT = 'text',
NUMBER = 'number',
Expand All @@ -15,6 +17,8 @@ export enum MAILCHIMP_MERGE_FIELD_TYPES {
export interface ListMemberCustomFields {
NAME?: string;
SIGNUPD?: string;
LACTIVED?: string;
REMINDFREQ?: EMAIL_REMINDERS_FREQUENCY;
PARTNERS?: string;
FEATTHER?: string;
FEATCHAT?: string;
Expand Down
1 change: 0 additions & 1 deletion src/api/mailchimp/mailchimp-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ export const batchCreateMailchimpProfiles = async (users: UserEntity[]) => {
console.log('Mailchimp batch response:', batchResponse);
}, 120000);
} catch (error) {
console.log(error);
throw new Error(`Batch create mailchimp profiles API call failed: ${error}`);
}
};
Expand Down
7 changes: 7 additions & 0 deletions src/entities/user.entity.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Column, Entity, Generated, OneToMany, OneToOne, PrimaryGeneratedColumn } from 'typeorm';
import { PartnerAccessEntity } from '../entities/partner-access.entity';
import { PartnerAdminEntity } from '../entities/partner-admin.entity';
import { EMAIL_REMINDERS_FREQUENCY } from '../utils/constants';
import { BaseBloomEntity } from './base.entity';
import { CourseUserEntity } from './course-user.entity';
import { EventLogEntity } from './event-log.entity';
Expand Down Expand Up @@ -30,12 +31,18 @@ export class UserEntity extends BaseBloomEntity {
@Column({ default: true })
serviceEmailsPermission: boolean; // service emails consent - mapped to mailchimp status field

@Column({ default: EMAIL_REMINDERS_FREQUENCY.NEVER })
emailRemindersFrequency: EMAIL_REMINDERS_FREQUENCY;

@Column({ type: Boolean, default: false })
isSuperAdmin: boolean;

@Column({ type: Boolean, default: true })
isActive: boolean;

@Column({ type: 'timestamptz', nullable: true })
lastActiveAt: Date; // set each time user record is fetched

@OneToMany(() => PartnerAccessEntity, (partnerAccess) => partnerAccess.user, { cascade: true })
partnerAccess: PartnerAccessEntity[];

Expand Down
1 change: 1 addition & 0 deletions src/logger/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export class Logger extends ConsoleLogger {
accessToken: rollbarToken,
captureUncaught: true,
captureUnhandledRejections: true,
captureIp: 'anonymize',
ignoredMessages: [...Object.values(FIREBASE_ERRORS)],
});
}
Expand Down
13 changes: 13 additions & 0 deletions src/migrations/1718300621138-bloom-backend.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class BloomBackend1718300621138 implements MigrationInterface {
name = 'BloomBackend1718300621138';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "user" ADD "lastActiveAt" TIMESTAMP WITH TIME ZONE`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "lastActiveAt"`);
}
}
14 changes: 14 additions & 0 deletions src/migrations/1718728423454-bloom-backend.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { MigrationInterface, QueryRunner } from "typeorm";

export class BloomBackend1718728423454 implements MigrationInterface {
name = 'BloomBackend1718728423454'

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "user" ADD "emailRemindersFrequency" character varying NOT NULL DEFAULT 'NEVER'`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "emailRemindersFrequency"`);
}

}
27 changes: 24 additions & 3 deletions src/partner-access/partner-access.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { sub } from 'date-fns';
import * as crispApi from 'src/api/crisp/crisp-api';
import * as mailchimpApi from 'src/api/mailchimp/mailchimp-api';
import { PartnerEntity } from 'src/entities/partner.entity';
import { GetUserDto } from 'src/user/dtos/get-user.dto';
import * as profileData from 'src/utils/serviceUserProfiles';
Expand Down Expand Up @@ -46,11 +47,12 @@ jest.mock('src/api/crisp/crisp-api', () => ({
getCrispProfileData: jest.fn(),
updateCrispProfileBase: jest.fn(),
updateCrispProfile: jest.fn(),
updateServiceUserProfilesPartnerAccess: jest.fn(),
}));

jest.mock('src/utils/serviceUserProfiles', () => ({
updateServiceUserProfilesPartnerAccess: jest.fn(),
jest.mock('src/api/mailchimp/mailchimp-api', () => ({
createMailchimpMergeField: jest.fn(),
createMailchimpProfile: jest.fn(),
updateMailchimpProfile: jest.fn(),
}));

describe('PartnerAccessService', () => {
Expand Down Expand Up @@ -143,6 +145,8 @@ describe('PartnerAccessService', () => {
});
// Mocks that the accesscode already exists
jest.spyOn(repo, 'findOne').mockResolvedValueOnce(mockPartnerAccessEntity);
// Observer on the service user profiles method
jest.spyOn(profileData, 'updateServiceUserProfilesPartnerAccess');

const partnerAccess = await service.assignPartnerAccess(mockUserEntity, '123456');

Expand Down Expand Up @@ -175,6 +179,23 @@ describe('PartnerAccessService', () => {
});
});

it('should assign partner access even if mailchimp profile api fails', async () => {
// Mocks that the accesscode already exists
jest.spyOn(repo, 'findOne').mockResolvedValueOnce(mockPartnerAccessEntity);

jest.spyOn(mailchimpApi, 'updateMailchimpProfile').mockImplementationOnce(async () => {
throw new Error('Test throw');
});

const partnerAccess = await service.assignPartnerAccess(mockUserEntity, '123456');

expect(partnerAccess).toEqual({
...mockPartnerAccessEntity,
userId: mockUserEntity.id,
activatedAt: partnerAccess.activatedAt,
});
});

it('should return an error when partner access code has already been used by another user account', async () => {
jest.spyOn(repo, 'findOne').mockResolvedValueOnce({
...mockPartnerAccessEntity,
Expand Down
3 changes: 3 additions & 0 deletions src/partner-admin/partner-admin-auth.guard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { DecodedIdToken } from 'firebase-admin/lib/auth/token-verifier';
import { AuthService } from 'src/auth/auth.service';
import { PartnerAdminEntity } from 'src/entities/partner-admin.entity';
import { UserEntity } from 'src/entities/user.entity';
import { EMAIL_REMINDERS_FREQUENCY } from 'src/utils/constants';
import { Repository } from 'typeorm';
import { createQueryBuilderMock } from '../../test/utils/mockUtils';
import { PartnerAdminAuthGuard } from './partner-admin-auth.guard';
Expand All @@ -17,11 +18,13 @@ const userEntity: UserEntity = {
name: 'name',
contactPermission: false,
serviceEmailsPermission: true,
emailRemindersFrequency: EMAIL_REMINDERS_FREQUENCY.TWO_MONTHS,
isSuperAdmin: false,
crispTokenId: '123',
partnerAccess: [],
partnerAdmin: { id: 'partnerAdminId', active: true, partner: {} } as PartnerAdminEntity,
isActive: true,
lastActiveAt: new Date(),
courseUser: [],
signUpLanguage: 'en',
subscriptionUser: [],
Expand Down
4 changes: 4 additions & 0 deletions src/typeorm.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ import { bloomBackend1696994943309 } from './migrations/1696994943309-bloom-back
import { bloomBackend1697818259254 } from './migrations/1697818259254-bloom-backend';
import { bloomBackend1698136145516 } from './migrations/1698136145516-bloom-backend';
import { bloomBackend1706174260018 } from './migrations/1706174260018-bloom-backend';
import { BloomBackend1718300621138 } from './migrations/1718300621138-bloom-backend';
import { BloomBackend1718728423454 } from './migrations/1718728423454-bloom-backend';

config();
const configService = new ConfigService();
Expand Down Expand Up @@ -108,6 +110,8 @@ export const dataSourceOptions = {
bloomBackend1697818259254,
bloomBackend1698136145516,
bloomBackend1706174260018,
BloomBackend1718300621138,
BloomBackend1718728423454,
],
subscribers: [],
ssl: isProduction,
Expand Down
6 changes: 6 additions & 0 deletions src/user/dtos/create-user.dto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsBoolean, IsDefined, IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator';
import { EMAIL_REMINDERS_FREQUENCY } from '../../utils/constants';

export class CreateUserDto {
@IsString()
Expand Down Expand Up @@ -40,6 +41,11 @@ export class CreateUserDto {
@ApiProperty({ type: Boolean })
serviceEmailsPermission: boolean;

@IsOptional()
@IsString()
@ApiProperty({ type: String })
emailRemindersFrequency: EMAIL_REMINDERS_FREQUENCY;

@IsOptional()
@IsString()
@ApiProperty({ type: String })
Expand Down
13 changes: 12 additions & 1 deletion src/user/dtos/update-user.dto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsBoolean, IsOptional, IsString } from 'class-validator';
import { IsBoolean, IsDate, IsOptional, IsString } from 'class-validator';
import { EMAIL_REMINDERS_FREQUENCY } from '../../utils/constants';

export class UpdateUserDto {
@IsString()
Expand All @@ -17,8 +18,18 @@ export class UpdateUserDto {
@ApiProperty({ type: Boolean })
serviceEmailsPermission: boolean;

@IsOptional()
@IsString()
@ApiProperty({ type: String })
emailRemindersFrequency: EMAIL_REMINDERS_FREQUENCY;

@IsString()
@IsOptional()
@ApiProperty({ type: String })
signUpLanguage: string;

@IsDate()
@IsOptional()
@ApiProperty({ type: 'date' })
lastActiveAt: Date;
}
6 changes: 4 additions & 2 deletions src/user/user.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,15 @@ export class UserController {
@Get('/me')
@UseGuards(FirebaseAuthGuard)
async getUserByFirebaseId(@Req() req: Request): Promise<GetUserDto> {
return req['user'];
const user = req['user'];
this.userService.updateUser({ lastActiveAt: new Date() }, user);
return user;
}

/**
* This POST endpoint deviates from REST patterns.
* Please use `getUserByFirebaseId` above which is a GET endpoint.
* Do not delete this until frontend usage is migrated.
* Safe to delete function below from July 2024 - allowing for caches to clear
*/
@ApiBearerAuth('access-token')
@ApiOperation({
Expand Down
4 changes: 4 additions & 0 deletions src/user/user.interface.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { EMAIL_REMINDERS_FREQUENCY } from '../utils/constants';

export interface IUser {
id: string;
createdAt: Date | string;
Expand All @@ -6,7 +8,9 @@ export interface IUser {
name: string;
email: string;
isActive: boolean;
lastActiveAt: Date | string;
crispTokenId: string;
isSuperAdmin: boolean;
signUpLanguage: string;
emailRemindersFrequency: EMAIL_REMINDERS_FREQUENCY;
}
Loading

0 comments on commit f50aeb9

Please sign in to comment.