Onboarding
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"compilerOptions": {
|
||||
"deleteOutDir": true
|
||||
"deleteOutDir": true,
|
||||
"webpack": true,
|
||||
"webpackConfigPath": "webpack.config.js"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,18 @@ import {
|
||||
Get,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
Patch,
|
||||
Post,
|
||||
Query,
|
||||
Req,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import type { AuthenticatedRequest } from './auth.types';
|
||||
import { AuthService } from './auth.service';
|
||||
import { LoginDto } from './dto/login.dto';
|
||||
import { RefreshTokenDto } from './dto/refresh-token.dto';
|
||||
import { RegisterDto } from './dto/register.dto';
|
||||
import { JwtAuthGuard } from './jwt-auth.guard';
|
||||
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
@@ -36,4 +41,22 @@ export class AuthController {
|
||||
refresh(@Body() refreshTokenDto: RefreshTokenDto) {
|
||||
return this.authService.refresh(refreshTokenDto);
|
||||
}
|
||||
|
||||
@Get('me')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
me(@Req() request: AuthenticatedRequest) {
|
||||
return this.authService.getPublicUser(request.user!.sub);
|
||||
}
|
||||
|
||||
@Patch('me/onboarding')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
updateOnboarding(
|
||||
@Req() request: AuthenticatedRequest,
|
||||
@Body() body: { completed?: boolean },
|
||||
) {
|
||||
return this.authService.updateOnboardingCompleted(
|
||||
request.user!.sub,
|
||||
body.completed === true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,6 +193,35 @@ export class AuthService {
|
||||
return user.name || user.email;
|
||||
}
|
||||
|
||||
async getPublicUser(userId: string): Promise<PublicUser> {
|
||||
const user = await this.usersRepository.findOne({
|
||||
where: { id: userId },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new UnauthorizedException('Authenticated user is required.');
|
||||
}
|
||||
|
||||
return this.toPublicUser(user);
|
||||
}
|
||||
|
||||
async updateOnboardingCompleted(
|
||||
userId: string,
|
||||
completed: boolean,
|
||||
): Promise<PublicUser> {
|
||||
const user = await this.usersRepository.findOne({
|
||||
where: { id: userId },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new UnauthorizedException('Authenticated user is required.');
|
||||
}
|
||||
|
||||
user.onboardingCompleted = completed;
|
||||
|
||||
return this.toPublicUser(await this.usersRepository.save(user));
|
||||
}
|
||||
|
||||
private normalizeEmail(email?: string): string {
|
||||
const normalizedEmail = email?.trim().toLowerCase();
|
||||
|
||||
@@ -328,6 +357,7 @@ export class AuthService {
|
||||
email: user.email,
|
||||
name: user.name ?? undefined,
|
||||
verified: user.verified,
|
||||
onboardingCompleted: user.onboardingCompleted === true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ export interface AuthUser {
|
||||
passwordHash: string;
|
||||
verificationToken?: string;
|
||||
verified: boolean;
|
||||
onboardingCompleted: boolean;
|
||||
}
|
||||
|
||||
export interface AuthTokens {
|
||||
@@ -34,4 +35,5 @@ export interface PublicUser {
|
||||
email: string;
|
||||
name?: string;
|
||||
verified: boolean;
|
||||
onboardingCompleted: boolean;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,9 @@ export class UserEntity {
|
||||
@Column({ type: 'boolean', default: false })
|
||||
verified!: boolean;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
onboardingCompleted!: boolean;
|
||||
|
||||
@CreateDateColumn({
|
||||
type: 'datetime',
|
||||
precision: 3,
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddUserOnboardingCompleted1781000000000
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'AddUserOnboardingCompleted1781000000000';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
if (!(await queryRunner.hasTable('users'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await queryRunner.hasColumn('users', 'onboardingCompleted'))) {
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE `users` ADD `onboardingCompleted` tinyint NOT NULL DEFAULT 0',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
if (
|
||||
(await queryRunner.hasTable('users')) &&
|
||||
(await queryRunner.hasColumn('users', 'onboardingCompleted'))
|
||||
) {
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE `users` DROP COLUMN `onboardingCompleted`',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class GeneratedMigration1781003163444 implements MigrationInterface {
|
||||
name = 'GeneratedMigration1781003163444'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE \`users\` ADD \`onboardingCompleted\` tinyint NOT NULL DEFAULT 0`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE \`users\` DROP COLUMN \`onboardingCompleted\``);
|
||||
}
|
||||
|
||||
}
|
||||
4
listify-api/webpack.config.js
Normal file
4
listify-api/webpack.config.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = (options) => ({
|
||||
...options,
|
||||
externals: [],
|
||||
});
|
||||
Reference in New Issue
Block a user