Skip to content

Commit

Permalink
feat: replace Google Pub/Sub with Redis (#67)
Browse files Browse the repository at this point in the history
  • Loading branch information
hobroker authored Jun 3, 2022
1 parent 53cde85 commit 8a92107
Show file tree
Hide file tree
Showing 22 changed files with 360 additions and 933 deletions.
3 changes: 3 additions & 0 deletions .env.production
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ JWT_ACCESS_TOKEN_SECRET=
JWT_ACCESS_TOKEN_EXPIRATION_TIME=86400
JWT_REFRESH_TOKEN_SECRET=
JWT_REFRESH_TOKEN_EXPIRATION_TIME=108000

REDIS_HOST=localhost
REDIS_PORT=6379
9 changes: 9 additions & 0 deletions charts/redis.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
image:
repository: redis
tag: 6.2-alpine
pullPolicy: Always

service:
type: LoadBalancer
targetPort: 6379
port: 6379
1,016 changes: 245 additions & 771 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
}
},
"dependencies": {
"@axelspringer/graphql-google-pubsub": "^2.1.0",
"@nestjs/apollo": "^10.0.8",
"@nestjs/common": "8.4.4",
"@nestjs/config": "^2.0.0",
Expand All @@ -61,6 +60,7 @@
"googleapis": "^100.0.0",
"graphql": "^16.3.0",
"graphql-fields-list": "^2.2.4",
"graphql-redis-subscriptions": "^2.4.2",
"graphql-tools": "8.2.5",
"graphql-ws": "^5.8.2",
"luxon": "^2.3.2",
Expand Down
69 changes: 30 additions & 39 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,62 +13,53 @@ import { TmdbModule } from './modules/tmdb';
import { ShowModule } from './modules/show';
import { GoogleModule } from './modules/google';
import { HealthModule } from './modules/health';
import { AuthModule, AuthService } from './modules/auth';
import { AuthModule } from './modules/auth';
import { PreferenceModule } from './modules/preference';
import { WatchlistModule } from './modules/watchlist';
import { ReviewModule } from './modules/review';
import { StatsModule } from './modules/stats';
import { SearchModule } from './modules/search';
import { NotificationModule } from './modules/notification';
import { CORS_ORIGINS } from './app.constants';
import { getCookie } from './util/cookie';

@Module({
imports: [
ConfigModule.forRoot({
envFilePath: ['.env', '.env.production'],
}),
ConfigModule.forFeature(appConfig),
GraphQLModule.forRootAsync<ApolloDriverConfig>({
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
imports: [AuthModule],
inject: [AuthService],
useFactory: async (authService: AuthService) => ({
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
playground: false,
// installSubscriptionHandlers: true,
async context({ extra }) {
let user = null;
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
playground: false,
context: ({ req, extra, connectionParams }) => {
if (extra) {
return {
req: {
...extra.request,
headers: {
...extra.request.headers,
authorization: connectionParams.authorization,
},
},
};
}

if (extra) {
try {
const authToken = getCookie(
extra.request.headers.cookie,
'Authentication',
);

user = await authService.getUserFromJwtToken(authToken);
} catch (e) {
console.log('e', e);
}
}

return { user };
},
subscriptions: {
'graphql-ws': {
path: '/subscriptions',
},
},
plugins: [
ApolloServerPluginLandingPageLocalDefault(),
ApolloServerPluginInlineTrace(),
],
cors: {
credentials: 'include',
origin: CORS_ORIGINS,
return { req };
},
subscriptions: {
'graphql-ws': {
path: '/subscriptions',
},
}),
},
plugins: [
ApolloServerPluginLandingPageLocalDefault(),
ApolloServerPluginInlineTrace(),
],
cors: {
credentials: 'include',
origin: CORS_ORIGINS,
},
}),
HealthModule,
TmdbModule,
Expand Down
2 changes: 0 additions & 2 deletions src/modules/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
JwtRefreshTokenStrategy,
} from './strategies';
import { authConfig } from './auth.config';
import { AuthController } from './controllers';
import { AuthResolver } from './resolvers';

@Module({
Expand All @@ -28,6 +27,5 @@ import { AuthResolver } from './resolvers';
JwtStrategy,
JwtRefreshTokenStrategy,
],
controllers: [AuthController],
})
export class AuthModule {}
74 changes: 0 additions & 74 deletions src/modules/auth/controllers/auth.controller.ts

This file was deleted.

1 change: 0 additions & 1 deletion src/modules/auth/controllers/index.ts

This file was deleted.

1 change: 1 addition & 0 deletions src/modules/auth/entities/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Token } from './token';
11 changes: 11 additions & 0 deletions src/modules/auth/entities/token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import 'reflect-metadata';
import { Field, ObjectType } from '@nestjs/graphql';

@ObjectType()
export class Token {
@Field()
accessToken: string;

@Field()
refreshToken: string;
}
4 changes: 2 additions & 2 deletions src/modules/auth/resolvers/auth.resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ export class AuthResolver {
@Mutation(() => User)
@UseGuards(GraphqlJwtRefreshGuard)
async refresh(@Context() { req }: { req: RequestWithUser }) {
const accessTokenCookie = this.authService.getCookieWithJwtAccessToken(
const accessToken = this.authService.getCookieWithJwtAccessToken(
req.user.id,
);

req.res.setHeader('Set-Cookie', accessTokenCookie);
req.res.setHeader('Set-Cookie', accessToken.cookie);

return req.user;
}
Expand Down
8 changes: 6 additions & 2 deletions src/modules/auth/services/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,15 @@ export class AuthService {
secret: this.config.jwtAccessTokenSecret,
expiresIn: `${this.config.jwtAccessTokenExirationTime}s`,
});

return createCookie(AUTHENTICATION_COOKIE, {
const cookie = createCookie(AUTHENTICATION_COOKIE, {
value: token,
maxAge: this.config.jwtAccessTokenExirationTime,
});

return {
cookie,
token,
};
}

public getCookieWithJwtRefreshToken(userId: number) {
Expand Down
5 changes: 3 additions & 2 deletions src/modules/auth/strategies/jwt.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { ConfigService, ConfigType } from '@nestjs/config';
import { Request } from 'express';
import { UserService } from '../../user/services';
import { authConfig } from '../auth.config';
import { AUTHENTICATION_COOKIE } from '../auth.constants';

interface TokenPayload {
userId: number;
Expand All @@ -22,7 +21,9 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
) {
super({
jwtFromRequest: ExtractJwt.fromExtractors([
(request: Request) => request?.cookies?.[AUTHENTICATION_COOKIE],
(request: Request) => {
return request.headers.authorization;
},
]),
secretOrKey: config.jwtAccessTokenSecret,
});
Expand Down
14 changes: 8 additions & 6 deletions src/modules/google/resolvers/google.resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,25 @@ import { Request } from 'express';
import { Injectable } from '@nestjs/common';
import { User } from '../../user/entities';
import { GoogleService } from '../services';
import { Void } from '../../../util/void';
import { JoinWithGoogleInput } from './input';

@Injectable()
@Resolver(User)
export class GoogleResolver {
constructor(private readonly googleService: GoogleService) {}

@Mutation(() => User)
@Mutation(() => Void)
async joinWithGoogle(
@Args('input') input: JoinWithGoogleInput,
@Context() { req }: { req: Request },
) {
const { user, refreshTokenCookie, accessTokenCookie } =
await this.googleService.authenticate(input.token);
): Promise<Void> {
const { accessToken, refreshToken } = await this.googleService.authenticate(
input.token,
);

req.res.setHeader('Set-Cookie', [accessTokenCookie, refreshTokenCookie]);
req.res.setHeader('Set-Cookie', [accessToken.cookie, refreshToken.cookie]);

return user;
return {};
}
}
21 changes: 8 additions & 13 deletions src/modules/google/services/google.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,28 +60,23 @@ export class GoogleService {
}

private async getCookiesForUser(user: User) {
const accessTokenCookie = this.authService.getCookieWithJwtAccessToken(
user.id,
);
const { cookie: refreshTokenCookie, token: refreshToken } =
this.authService.getCookieWithJwtRefreshToken(user.id);
const accessToken = this.authService.getCookieWithJwtAccessToken(user.id);
const refreshToken = this.authService.getCookieWithJwtRefreshToken(user.id);

await this.userService.setCurrentRefreshToken(refreshToken, user.id);
await this.userService.setCurrentRefreshToken(refreshToken.token, user.id);

return {
accessTokenCookie,
refreshTokenCookie,
accessToken,
refreshToken,
};
}

private async handleRegisteredUser(user: User) {
const { accessTokenCookie, refreshTokenCookie } =
await this.getCookiesForUser(user);
const { accessToken, refreshToken } = await this.getCookiesForUser(user);

return {
accessTokenCookie,
refreshTokenCookie,
user,
accessToken,
refreshToken,
};
}

Expand Down
7 changes: 7 additions & 0 deletions src/modules/notification/notification.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { registerAs } from '@nestjs/config';
import { NOTIFICATION_MODULE_ID } from './notification.constants';

export const notificationConfig = registerAs(NOTIFICATION_MODULE_ID, () => ({
redisHost: process.env.REDIS_HOST,
redisPort: process.env.REDIS_PORT,
}));
1 change: 1 addition & 0 deletions src/modules/notification/notification.constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const NOTIFICATION_MODULE_ID = 'notification';
4 changes: 2 additions & 2 deletions src/modules/notification/notification.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import { PrismaModule } from '../prisma';
import { WatchlistModule } from '../watchlist';
import { TmdbModule } from '../tmdb';
import { GoogleModule } from '../google';
import { googleConfig } from '../google/google.config';
import * as resolvers from './resolvers';
import * as services from './services';
import { NotificationSchedulerService } from './services';
import { notificationConfig } from './notification.config';

@Module({
imports: [
ConfigModule.forFeature(googleConfig),
ConfigModule.forFeature(notificationConfig),
ScheduleModule.forRoot(),
PrismaModule,
WatchlistModule,
Expand Down
9 changes: 3 additions & 6 deletions src/modules/notification/resolvers/notification.resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,14 @@ export class NotificationResolver {
}

@Subscription(() => [Notification], {
filter({ data }, variables, { user }) {
const { userId } = JSON.parse(data);

filter({ userId }, variables, { req: { user } }) {
return userId === user.id;
},
resolve(this: NotificationResolver, { data }) {
const { notificationIds } = JSON.parse(data);

resolve(this: NotificationResolver, { notificationIds }) {
return this.notificationService.listNotificationsByIds(notificationIds);
},
})
@UseGuards(GraphqlJwtAuthGuard)
notificationsAdded() {
return this.notificationPubsubService.getAsyncIterator();
}
Expand Down
Loading

0 comments on commit 8a92107

Please sign in to comment.