Skip to content

Commit

Permalink
allow team members create collections
Browse files Browse the repository at this point in the history
  • Loading branch information
imolorhe committed Apr 10, 2024
1 parent 2afc2c0 commit 35eec82
Show file tree
Hide file tree
Showing 22 changed files with 155 additions and 196 deletions.
4 changes: 2 additions & 2 deletions packages/altair-api/src/app-bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const bootstrapApp = async (app: INestApplication) => {
const swaggerConfig = configService.get<SwaggerConfig>('swagger');

// Swagger Api
if (swaggerConfig.enabled) {
if (swaggerConfig?.enabled) {
const options = new DocumentBuilder()
.setTitle(swaggerConfig.title || 'Altair')
.setDescription(swaggerConfig.description || 'The Altair API description')
Expand All @@ -50,7 +50,7 @@ export const bootstrapApp = async (app: INestApplication) => {
}

// Cors
if (corsConfig.enabled) {
if (corsConfig?.enabled) {
app.enableCors();
}

Expand Down
54 changes: 15 additions & 39 deletions packages/altair-api/src/auth/auth.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,8 @@ describe('AuthService', () => {
it(`should return a user object on successful login`, async () => {
// GIVEN
const userMock = mockUser();
jest
.spyOn(prismaService.user, 'findUnique')
.mockResolvedValueOnce(userMock);
jest
.spyOn(passwordService, 'validatePassword')
.mockResolvedValueOnce(true);
jest.spyOn(prismaService.user, 'findUnique').mockResolvedValueOnce(userMock);
jest.spyOn(passwordService, 'validatePassword').mockResolvedValueOnce(true);
jest.spyOn(jwtService, 'sign').mockReturnValue(tokenMock);

// WHEN
Expand All @@ -73,17 +69,13 @@ describe('AuthService', () => {
it(`should throw an error if the provided password is invalid`, () => {
// GIVEN
const userMock = mockUser();
jest
.spyOn(prismaService.user, 'findUnique')
.mockResolvedValueOnce(userMock);
jest
.spyOn(passwordService, 'validatePassword')
.mockResolvedValueOnce(false);
jest.spyOn(prismaService.user, 'findUnique').mockResolvedValueOnce(userMock);
jest.spyOn(passwordService, 'validatePassword').mockResolvedValueOnce(false);

// THEN
expect(
service.passwordLogin(userMock.email, passwordMock)
).rejects.toThrow(`Invalid password`);
expect(service.passwordLogin(userMock.email, passwordMock)).rejects.toThrow(
`Invalid password`
);
});
});

Expand All @@ -103,7 +95,7 @@ describe('AuthService', () => {

it(`should throw an error if the user object is missing from the request`, () => {
// THEN
expect(() => service.googleLogin(undefined)).toThrow(
expect(() => service.googleLogin(undefined as any)).toThrow(
'No user from google'
);
});
Expand All @@ -116,9 +108,7 @@ describe('AuthService', () => {
jest.spyOn(jwtService, 'decode').mockReturnValueOnce({
userId: 'e23b7b34-8996-45d3-9097-b14b8f451dbd',
});
jest
.spyOn(prismaService.user, 'findUnique')
.mockResolvedValueOnce(userMock);
jest.spyOn(prismaService.user, 'findUnique').mockResolvedValueOnce(userMock);

// WHEN
const user = await service.getUserFromToken(tokenMock);
Expand All @@ -129,14 +119,10 @@ describe('AuthService', () => {

it(`should throw an error if the token is invalid`, () => {
// GIVEN
jest
.spyOn(jwtService, 'decode')
.mockReturnValueOnce(`Couldn't decode token.`);
jest.spyOn(jwtService, 'decode').mockReturnValueOnce(`Couldn't decode token.`);

// THEN
expect(() => service.getUserFromToken(tokenMock)).toThrow(
'Invalid JWT token'
);
expect(() => service.getUserFromToken(tokenMock)).toThrow('Invalid JWT token');
});
});

Expand All @@ -153,9 +139,7 @@ describe('AuthService', () => {
it(`should return a user object on successful password change`, async () => {
// GIVEN
const userMock = mockUser();
jest
.spyOn(passwordService, 'validatePassword')
.mockResolvedValueOnce(true);
jest.spyOn(passwordService, 'validatePassword').mockResolvedValueOnce(true);
jest
.spyOn(passwordService, 'hashPassword')
.mockResolvedValue(
Expand All @@ -176,17 +160,11 @@ describe('AuthService', () => {

it(`should throw an error if the user password is invalid`, () => {
// GIVEN
jest
.spyOn(passwordService, 'validatePassword')
.mockResolvedValueOnce(false);
jest.spyOn(passwordService, 'validatePassword').mockResolvedValueOnce(false);

// THEN
expect(
service.changePassword(
mockUser().id,
passwordMock,
changePasswordInputMock
)
service.changePassword(mockUser().id, passwordMock, changePasswordInputMock)
).rejects.toThrow('Invalid password');
});
});
Expand Down Expand Up @@ -243,9 +221,7 @@ describe('AuthService', () => {
});

// THEN
expect(() => service.refreshToken(tokenMock)).toThrow(
UnauthorizedException
);
expect(() => service.refreshToken(tokenMock)).toThrow(UnauthorizedException);
});
});
});
3 changes: 1 addition & 2 deletions packages/altair-api/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { SecurityConfig } from 'src/common/config';
import { ChangePasswordInput } from './models/change-password.input';
import { PasswordService } from './password/password.service';
import { IToken } from '@altairgraphql/api-utils';
import { Request } from 'express';

@Injectable()
export class AuthService {
Expand Down Expand Up @@ -42,7 +41,7 @@ export class AuthService {
return this.getLoginResponse(user);
}

googleLogin(user: User) {
googleLogin(user?: User) {
if (!user) {
throw new BadRequestException('No user from google');
}
Expand Down
14 changes: 7 additions & 7 deletions packages/altair-api/src/auth/mocks/stripe-service.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ export function mockStripeCustomer(): Stripe.Customer {
export function mockSubscriptionItem(): Stripe.Response<Stripe.SubscriptionItem> {
return {
id: 'f7102dc9-4c0c-42b4-9a17-e2bd4af94d5a',
object: {},
billing_thresholds: {},
object: {} as any,
billing_thresholds: {} as any,
created: 1,
metadata: {},
plan: {},
price: {},
metadata: {} as any,
plan: {} as any,
price: {} as any,
subscription: 'my sub',
tax_rates: [],
lastResponse: {},
tax_rates: [] as any,
lastResponse: {} as any,
} as Stripe.Response<Stripe.SubscriptionItem>;
}

Expand Down
4 changes: 2 additions & 2 deletions packages/altair-api/src/auth/models/change-password.input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { IsNotEmpty, MinLength } from 'class-validator';
export class ChangePasswordInput {
@IsNotEmpty()
@MinLength(8)
oldPassword: string;
oldPassword!: string;

@IsNotEmpty()
@MinLength(8)
newPassword: string;
newPassword!: string;
}
4 changes: 2 additions & 2 deletions packages/altair-api/src/auth/models/login.input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { IsEmail, IsNotEmpty, MinLength } from 'class-validator';

export class LoginInput {
@IsEmail()
email: string;
email!: string;

@IsNotEmpty()
@MinLength(8)
password: string;
password!: string;
}
2 changes: 1 addition & 1 deletion packages/altair-api/src/auth/models/refresh-token.input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ import { IsJWT, IsNotEmpty } from 'class-validator';
export class RefreshTokenInput {
@IsNotEmpty()
@IsJWT()
token: string;
token!: string;
}
4 changes: 2 additions & 2 deletions packages/altair-api/src/auth/models/signup.input.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { IsEmail, IsNotEmpty, MinLength } from 'class-validator';
import { IsEmail } from 'class-validator';

export class SignupInput {
@IsEmail()
email: string;
email!: string;

// @IsNotEmpty()
// @MinLength(8)
Expand Down
64 changes: 20 additions & 44 deletions packages/altair-api/src/auth/user/user.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,7 @@ describe('UserService', () => {

it('should return the created user object', async () => {
// GIVEN
jest
.spyOn(prismaService.user, 'create')
.mockResolvedValueOnce(mockUser());
jest.spyOn(prismaService.user, 'create').mockResolvedValueOnce(mockUser());

// WHEN
const user = await service.createUser(payloadMock);
Expand Down Expand Up @@ -81,19 +79,15 @@ describe('UserService', () => {
.mockRejectedValueOnce(Error('Unexpected error'));

// THEN
expect(service.createUser(payloadMock)).rejects.toThrow(
'Unexpected error'
);
expect(service.createUser(payloadMock)).rejects.toThrow('Unexpected error');
});
});

describe('mustGetUser', () => {
it('should return a user object', async () => {
// GIVEN
const userMock = mockUser();
jest
.spyOn(prismaService.user, 'findUnique')
.mockResolvedValueOnce(userMock);
jest.spyOn(prismaService.user, 'findUnique').mockResolvedValueOnce(userMock);

// WHEN
const user = await service.mustGetUser(userMock.id);
Expand Down Expand Up @@ -132,9 +126,7 @@ describe('UserService', () => {
it('should return the the basic plan if no plan was found for the user', async () => {
// GIVEN
const user = mockUser();
jest
.spyOn(prismaService.userPlan, 'findUnique')
.mockResolvedValueOnce(null);
jest.spyOn(prismaService.userPlan, 'findUnique').mockResolvedValueOnce(null);
jest
.spyOn(prismaService.planConfig, 'findUnique')
.mockResolvedValueOnce(mockPlanConfig());
Expand All @@ -151,25 +143,19 @@ describe('UserService', () => {
it("should throw an error if the user doesn't have a customer ID associated on Stripe", () => {
// GIVEN
const userMock = mockUser();
userMock.stripeCustomerId = undefined;
jest
.spyOn(prismaService.user, 'findUnique')
.mockResolvedValueOnce(userMock);
userMock.stripeCustomerId = null;
jest.spyOn(prismaService.user, 'findUnique').mockResolvedValueOnce(userMock);

// THEN
expect(
service.updateSubscriptionQuantity(userMock.id, 1)
).rejects.toThrow(
expect(service.updateSubscriptionQuantity(userMock.id, 1)).rejects.toThrow(
'Cannot update subscription quantity since user (f7102dc9-4c0c-42b4-9a17-e2bd4af94d5a) does not have a stripe customer ID'
);
});

it('should return the updated subscription item', async () => {
// GIVEN
const userMock = mockUser();
jest
.spyOn(prismaService.user, 'findUnique')
.mockResolvedValueOnce(userMock);
jest.spyOn(prismaService.user, 'findUnique').mockResolvedValueOnce(userMock);
jest
.spyOn(stripeService, 'updateSubscriptionQuantity')
.mockResolvedValueOnce(mockSubscriptionItem());
Expand All @@ -189,9 +175,7 @@ describe('UserService', () => {
it('should return the stripe customer ID', () => {
// GIVEN
const userMock = mockUser();
jest
.spyOn(prismaService.user, 'findUnique')
.mockResolvedValueOnce(userMock);
jest.spyOn(prismaService.user, 'findUnique').mockResolvedValueOnce(userMock);

// THEN
expect(service.getStripeCustomerId(userMock.id)).resolves.toEqual(
Expand All @@ -204,12 +188,12 @@ describe('UserService', () => {
const userMock = mockUser();
jest
.spyOn(prismaService.user, 'findUnique')
.mockResolvedValueOnce({ ...userMock, stripeCustomerId: undefined });
.mockResolvedValueOnce({ ...userMock, stripeCustomerId: null });
jest
.spyOn(stripeService, 'connectOrCreateCustomer')
.mockResolvedValueOnce(mockStripeCustomer());
jest.spyOn(prismaService.user, 'update').mockImplementation(() => {
return null;
return null as any;
});

// THEN
Expand All @@ -224,9 +208,7 @@ describe('UserService', () => {
// GIVEN
const userMock = mockUser();
const mockUrl = 'https://myurl.com/123';
jest
.spyOn(prismaService.user, 'findUnique')
.mockResolvedValueOnce(userMock);
jest.spyOn(prismaService.user, 'findUnique').mockResolvedValueOnce(userMock);
jest.spyOn(stripeService, 'createBillingSession').mockResolvedValueOnce({
url: mockUrl,
} as Stripe.Response<Stripe.BillingPortal.Session>);
Expand All @@ -243,12 +225,12 @@ describe('UserService', () => {
it(`should return a user object`, () => {
// GIVEN
const userMock = mockUser();
jest
.spyOn(prismaService.user, 'findFirst')
.mockResolvedValueOnce(userMock);
jest.spyOn(prismaService.user, 'findFirst').mockResolvedValueOnce(userMock);

// WHEN
const user = service.getUserByStripeCustomerId(userMock.stripeCustomerId);
const user = service.getUserByStripeCustomerId(
userMock.stripeCustomerId ?? ''
);

// THEN
expect(user).resolves.toBeUser();
Expand All @@ -264,9 +246,7 @@ describe('UserService', () => {
jest
.spyOn(prismaService.userPlan, 'findUnique')
.mockResolvedValueOnce(mockUserPlan());
jest
.spyOn(prismaService.user, 'findUnique')
.mockResolvedValueOnce(userMock);
jest.spyOn(prismaService.user, 'findUnique').mockResolvedValueOnce(userMock);
jest
.spyOn(stripeService, 'getPlanInfoByRole')
.mockResolvedValueOnce(mockPlanInfo());
Expand All @@ -291,9 +271,7 @@ describe('UserService', () => {
jest
.spyOn(prismaService.userPlan, 'findUnique')
.mockResolvedValueOnce(userPlanMock);
jest
.spyOn(prismaService.user, 'findUnique')
.mockResolvedValueOnce(userMock);
jest.spyOn(prismaService.user, 'findUnique').mockResolvedValueOnce(userMock);
jest.spyOn(stripeService, 'createBillingSession').mockResolvedValueOnce({
url: proUrlMock,
} as Stripe.Response<Stripe.BillingPortal.Session>);
Expand All @@ -311,12 +289,10 @@ describe('UserService', () => {
jest
.spyOn(prismaService.userPlan, 'findUnique')
.mockResolvedValueOnce(mockUserPlan());
jest
.spyOn(prismaService.user, 'findUnique')
.mockResolvedValueOnce(userMock);
jest.spyOn(prismaService.user, 'findUnique').mockResolvedValueOnce(userMock);
jest
.spyOn(stripeService, 'getPlanInfoByRole')
.mockResolvedValueOnce(null);
.mockResolvedValueOnce(null as any);

// THEN
expect(service.getProPlanUrl(userMock.id)).rejects.toThrow(
Expand Down
2 changes: 1 addition & 1 deletion packages/altair-api/src/auth/user/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ export class UserService {

async getProPlanUrl(userId: string) {
const planConfig = await this.getPlanConfig(userId);
if (planConfig.id === PRO_PLAN_ID) {
if (planConfig?.id === PRO_PLAN_ID) {
console.warn(
'User is already on pro plan. Going to return billing url instead.'
);
Expand Down
2 changes: 1 addition & 1 deletion packages/altair-api/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ async function bootstrap() {

const configService = app.get(ConfigService);
const nestConfig = configService.get<NestConfig>('nest');
const port = process.env.PORT || nestConfig.port;
const port = process.env.PORT ?? nestConfig?.port ?? 3000;
console.log('Listening on port', port);
await app.listen(port);
}
Expand Down
Loading

0 comments on commit 35eec82

Please sign in to comment.