add check for username already taken
This commit is contained in:
parent
864758f296
commit
887b80aee8
|
@ -189,6 +189,21 @@ export class UserDbService {
|
|||
|
||||
// Listing
|
||||
|
||||
public async checkUsername(username: string): AsyncFailable<{
|
||||
available: boolean;
|
||||
}> {
|
||||
try {
|
||||
const found = await this.usersRepository.findOne({
|
||||
where: { username },
|
||||
select: ['id'],
|
||||
});
|
||||
|
||||
return { available: !found};
|
||||
} catch (e) {
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
}
|
||||
|
||||
public async findByUsername(
|
||||
username: string,
|
||||
// Also fetch fields that aren't normally sent to the client
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
import { Body, Controller, Get, Logger, Post } from '@nestjs/common';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Get, Logger,
|
||||
Post
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
UserCheckNameRequest,
|
||||
UserCheckNameResponse,
|
||||
UserLoginResponse,
|
||||
UserMePermissionsResponse,
|
||||
UserMeResponse,
|
||||
|
@ -56,6 +53,17 @@ export class UserController {
|
|||
return EUserBackend2EUser(user);
|
||||
}
|
||||
|
||||
@Post('checkname')
|
||||
@Returns(UserCheckNameResponse)
|
||||
@RequiredPermissions(Permission.UserRegister)
|
||||
async checkName(
|
||||
@Body() checkName: UserCheckNameRequest,
|
||||
): Promise<UserCheckNameResponse> {
|
||||
return ThrowIfFailed(
|
||||
await this.usersService.checkUsername(checkName.username),
|
||||
);
|
||||
}
|
||||
|
||||
@Get('me')
|
||||
@Returns(UserMeResponse)
|
||||
@RequiredPermissions(Permission.UserKeepLogin)
|
||||
|
@ -76,7 +84,9 @@ export class UserController {
|
|||
async refresh(
|
||||
@ReqUserID() userid: string,
|
||||
): Promise<UserMePermissionsResponse> {
|
||||
const permissions = ThrowIfFailed(await this.usersService.getPermissions(userid));
|
||||
const permissions = ThrowIfFailed(
|
||||
await this.usersService.getPermissions(userid),
|
||||
);
|
||||
|
||||
return { permissions };
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@ export const CreateUsernameError = (
|
|||
return 'Username is too long';
|
||||
case 'pattern':
|
||||
return 'Username can only contain letters and numbers';
|
||||
case 'unavailable':
|
||||
return 'Username is already taken';
|
||||
default:
|
||||
return 'Invalid username';
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import { Router } from '@angular/router';
|
|||
import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
|
||||
import { Permission } from 'picsur-shared/dist/dto/permissions.enum';
|
||||
import { HasFailed } from 'picsur-shared/dist/types';
|
||||
import { debounceTime } from 'rxjs';
|
||||
import { UserPassModel } from 'src/app/models/forms-dto/userpass.dto';
|
||||
import { PermissionService } from 'src/app/services/api/permission.service';
|
||||
import { UserService } from 'src/app/services/api/user.service';
|
||||
|
@ -36,6 +37,20 @@ export class RegisterComponent implements OnInit {
|
|||
history.replaceState(null, '');
|
||||
}
|
||||
|
||||
this.model.username.valueChanges
|
||||
.pipe(debounceTime(500))
|
||||
.subscribe(async (value) => {
|
||||
if (this.model.username.errors || value === null) return;
|
||||
|
||||
const result = await this.userService.checkNameIsAvailable(value);
|
||||
if (HasFailed(result))
|
||||
return this.errorService.showFailure(result, this.logger);
|
||||
|
||||
if (!result) {
|
||||
this.model.username.setErrors({ unavailable: true });
|
||||
}
|
||||
});
|
||||
|
||||
this.onPermissions();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import jwt_decode from 'jwt-decode';
|
||||
import {
|
||||
UserCheckNameRequest,
|
||||
UserCheckNameResponse,
|
||||
UserLoginRequest,
|
||||
UserLoginResponse,
|
||||
UserMeResponse,
|
||||
|
@ -9,7 +11,13 @@ import {
|
|||
} from 'picsur-shared/dist/dto/api/user.dto';
|
||||
import { JwtDataSchema } from 'picsur-shared/dist/dto/jwt.dto';
|
||||
import { EUser } from 'picsur-shared/dist/entities/user.entity';
|
||||
import { AsyncFailable, Fail, FT, HasFailed } from 'picsur-shared/dist/types';
|
||||
import {
|
||||
AsyncFailable,
|
||||
Fail,
|
||||
FT,
|
||||
HasFailed,
|
||||
Open
|
||||
} from 'picsur-shared/dist/types';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { Logger } from '../logger/logger.service';
|
||||
import { KeyService } from '../storage/key.service';
|
||||
|
@ -86,6 +94,20 @@ export class UserService {
|
|||
return user;
|
||||
}
|
||||
|
||||
public async checkNameIsAvailable(username: string): AsyncFailable<boolean> {
|
||||
return Open(
|
||||
await this.api.post(
|
||||
UserCheckNameRequest,
|
||||
UserCheckNameResponse,
|
||||
'/api/user/checkname',
|
||||
{
|
||||
username,
|
||||
},
|
||||
),
|
||||
'available',
|
||||
);
|
||||
}
|
||||
|
||||
public async register(
|
||||
username: string,
|
||||
password: string,
|
||||
|
|
|
@ -30,6 +30,21 @@ export class UserRegisterResponse extends createZodDto(
|
|||
UserRegisterResponseSchema,
|
||||
) {}
|
||||
|
||||
// UserCheckName
|
||||
export const UserCheckNameRequestSchema = z.object({
|
||||
username: IsUsername(),
|
||||
});
|
||||
export class UserCheckNameRequest extends createZodDto(
|
||||
UserCheckNameRequestSchema,
|
||||
) {}
|
||||
|
||||
export const UserCheckNameResponseSchema = z.object({
|
||||
available: z.boolean(),
|
||||
});
|
||||
export class UserCheckNameResponse extends createZodDto(
|
||||
UserCheckNameResponseSchema,
|
||||
) {}
|
||||
|
||||
// UserMe
|
||||
export const UserMeResponseSchema = z.object({
|
||||
user: EUserSchema,
|
||||
|
|
Loading…
Reference in a new issue