Skip to content

Commit

Permalink
Change slug usage to encoded text (#261)
Browse files Browse the repository at this point in the history
* Chore remove slugify

* Feat change slug to encoding text

* Feat decoded workspace_slug to encoding back

* Feat Add validate title length in CreateModal

* Refactor remove duplicated error check

* Feat update validation for nickname and title length
  • Loading branch information
minai621 authored Aug 2, 2024
1 parent e184d24 commit 1d5e8e4
Show file tree
Hide file tree
Showing 9 changed files with 43 additions and 46 deletions.
11 changes: 1 addition & 10 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@
"passport-github": "^1.1.0",
"passport-jwt": "^4.0.1",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1",
"slugify": "^1.6.6"
"rxjs": "^7.8.1"
},
"devDependencies": {
"@nestjs/cli": "^10.0.0",
Expand Down
15 changes: 7 additions & 8 deletions backend/src/check/check.service.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { Injectable } from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";
import * as moment from "moment";
import { PrismaService } from "src/db/prisma.service";
import { CheckNameConflicReponse } from "./types/check-name-conflict-response.type";
import slugify from "slugify";
import { JwtPayload } from "src/utils/types/jwt.type";
import { CheckYorkieDto, YorkieMethod } from "./dto/check-yorkie.dto";
import { CheckNameConflicReponse } from "./types/check-name-conflict-response.type";
import { CheckYorkieResponse } from "./types/check-yorkie-response.type";
import { JwtService } from "@nestjs/jwt";
import { JwtPayload } from "src/utils/types/jwt.type";
import * as moment from "moment";

@Injectable()
export class CheckService {
Expand All @@ -16,15 +15,15 @@ export class CheckService {
) {}

async checkNameConflict(name: string): Promise<CheckNameConflicReponse> {
const slug = slugify(name, { lower: true });
const encodedText = encodeURIComponent(name);
const conflictUserList = await this.prismaService.user.findMany({
where: {
OR: [{ nickname: name }, { nickname: slug }],
OR: [{ nickname: name }, { nickname: encodedText }],
},
});
const conflictWorkspaceList = await this.prismaService.workspace.findMany({
where: {
OR: [{ title: name }, { title: slug }],
OR: [{ title: name }, { title: encodedText }],
},
});

Expand Down
4 changes: 3 additions & 1 deletion backend/src/users/dto/change-nickname.dto.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { ApiProperty } from "@nestjs/swagger";
import { MinLength } from "class-validator";

export class ChangeNicknameDto {
@ApiProperty({ type: String, description: "Nickname of user to update" })
@ApiProperty({ type: String, description: "Nickname of user to update", minLength: 2 })
@MinLength(2)
nickname: string;
}
9 changes: 4 additions & 5 deletions backend/src/users/users.service.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { ConflictException, Injectable } from "@nestjs/common";
import { User } from "@prisma/client";
import { PrismaService } from "src/db/prisma.service";
import { FindUserResponse } from "./types/find-user-response.type";
import { CheckService } from "src/check/check.service";
import slugify from "slugify";
import { PrismaService } from "src/db/prisma.service";
import { WorkspaceRoleConstants } from "src/utils/constants/auth-role";
import { FindUserResponse } from "./types/find-user-response.type";

@Injectable()
export class UsersService {
Expand Down Expand Up @@ -95,7 +94,7 @@ export class UsersService {
},
});

const slug = slugify(nickname, { lower: true });
const encodedText = encodeURIComponent(nickname);

if (!userWorkspaceList.length) {
const { id: workspaceId } = await this.prismaService.workspace.create({
Expand All @@ -104,7 +103,7 @@ export class UsersService {
},
data: {
title: nickname,
slug,
slug: encodedText,
},
});

Expand Down
4 changes: 3 additions & 1 deletion backend/src/workspaces/dto/create-workspace.dto.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { ApiProperty } from "@nestjs/swagger";
import { MinLength } from "class-validator";

export class CreateWorkspaceDto {
@ApiProperty({ description: "Title of project to create", type: String })
@ApiProperty({ description: "Title of project to create", type: String, minLength: 2 })
@MinLength(2)
title: string;
}
14 changes: 7 additions & 7 deletions backend/src/workspaces/workspaces.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import {
Query,
Req,
} from "@nestjs/common";
import { WorkspacesService } from "./workspaces.service";
import { CreateWorkspaceDto } from "./dto/create-workspace.dto";
import {
ApiBearerAuth,
ApiBody,
Expand All @@ -24,15 +22,17 @@ import {
ApiTags,
ApiUnauthorizedResponse,
} from "@nestjs/swagger";
import { HttpExceptionResponse } from "src/utils/types/http-exception-response.type";
import { AuthroizedRequest } from "src/utils/types/req.type";
import { CreateInvitationTokenDto } from "./dto/create-invitation-token.dto";
import { CreateWorkspaceDto } from "./dto/create-workspace.dto";
import { JoinWorkspaceDto } from "./dto/join-workspace.dto";
import { CreateInvitationTokenResponse } from "./types/create-inviation-token-response.type";
import { CreateWorkspaceResponse } from "./types/create-workspace-response.type";
import { FindWorkspaceResponse } from "./types/find-workspace-response.type";
import { HttpExceptionResponse } from "src/utils/types/http-exception-response.type";
import { FindWorkspacesResponse } from "./types/find-workspaces-response.type";
import { CreateInvitationTokenResponse } from "./types/create-inviation-token-response.type";
import { JoinWorkspaceDto } from "./dto/join-workspace.dto";
import { JoinWorkspaceResponse } from "./types/join-workspace-response.type";
import { CreateInvitationTokenDto } from "./dto/create-invitation-token.dto";
import { WorkspacesService } from "./workspaces.service";

@ApiTags("Workspaces")
@ApiBearerAuth()
Expand Down Expand Up @@ -68,7 +68,7 @@ export class WorkspacesController {
@Req() req: AuthroizedRequest,
@Param("workspace_slug") workspaceSlug: string
): Promise<FindWorkspaceResponse> {
return this.workspacesService.findOneBySlug(req.user.id, workspaceSlug);
return this.workspacesService.findOneBySlug(req.user.id, encodeURIComponent(workspaceSlug));
}

@Get("")
Expand Down
11 changes: 5 additions & 6 deletions backend/src/workspaces/workspaces.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@ import {
UnauthorizedException,
} from "@nestjs/common";
import { Prisma, Workspace } from "@prisma/client";
import * as moment from "moment";
import { CheckService } from "src/check/check.service";
import { PrismaService } from "src/db/prisma.service";
import { FindWorkspacesResponse } from "./types/find-workspaces-response.type";
import { CreateInvitationTokenResponse } from "./types/create-inviation-token-response.type";
import { WorkspaceRoleConstants } from "src/utils/constants/auth-role";
import slugify from "slugify";
import { generateRandomKey } from "src/utils/functions/random-string";
import * as moment from "moment";
import { CheckService } from "src/check/check.service";
import { CreateInvitationTokenResponse } from "./types/create-inviation-token-response.type";
import { FindWorkspacesResponse } from "./types/find-workspaces-response.type";

@Injectable()
export class WorkspacesService {
Expand All @@ -31,7 +30,7 @@ export class WorkspacesService {
const workspace = await this.prismaService.workspace.create({
data: {
title,
slug: slugify(title, { lower: true }),
slug: encodeURIComponent(title),
},
});

Expand Down
18 changes: 12 additions & 6 deletions frontend/src/components/modals/CreateModal.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import CloseIcon from "@mui/icons-material/Close";
import {
Button,
FormControl,
Expand All @@ -8,11 +9,10 @@ import {
Stack,
Typography,
} from "@mui/material";
import { FormContainer, TextFieldElement } from "react-hook-form-mui";
import CloseIcon from "@mui/icons-material/Close";
import { useMemo, useState } from "react";
import { useCheckNameConflictQuery } from "../../hooks/api/check";
import { FormContainer, TextFieldElement } from "react-hook-form-mui";
import { useDebounce } from "react-use";
import { useCheckNameConflictQuery } from "../../hooks/api/check";

interface CreateRequest {
title: string;
Expand All @@ -29,12 +29,16 @@ function CreateModal(props: CreateModalProps) {
const [nickname, setNickname] = useState("");
const [debouncedNickname, setDebouncedNickname] = useState("");
const { data: conflictResult } = useCheckNameConflictQuery(debouncedNickname);

const errorMessage = useMemo(() => {
if (nickname.length < 2) {
return "Title must be at least 2 characters";
}
if (conflictResult?.conflict) {
return "Already Exists";
}
return null;
}, [conflictResult?.conflict]);
}, [nickname.length, conflictResult?.conflict]);

useDebounce(
() => {
Expand All @@ -49,8 +53,10 @@ function CreateModal(props: CreateModalProps) {
};

const handleCreate = async (data: CreateRequest) => {
await onSuccess(data);
handleCloseModal();
if (data.title.length >= 2) {
await onSuccess(data);
handleCloseModal();
}
};

const handleNicknameChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
Expand Down

0 comments on commit 1d5e8e4

Please sign in to comment.