Skip to content

Commit

Permalink
(BE) Generate slug for workspace and document (#78)
Browse files Browse the repository at this point in the history
* Update database schem

* Change database & API for slug

* Change API Spec for slug

* Change parameter name for slug
  • Loading branch information
devleejb authored Jan 22, 2024
1 parent 4922605 commit c585c73
Show file tree
Hide file tree
Showing 18 changed files with 117 additions and 51 deletions.
52 changes: 30 additions & 22 deletions backend/package-lock.json

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

3 changes: 2 additions & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
"passport-github": "^1.1.0",
"passport-jwt": "^4.0.1",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1"
"rxjs": "^7.8.1",
"slugify": "^1.6.6"
},
"devDependencies": {
"@nestjs/cli": "^10.0.0",
Expand Down
2 changes: 2 additions & 0 deletions backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ model UserWorkspace {
model Workspace {
id String @id @default(auto()) @map("_id") @db.ObjectId
title String
slug String
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
documentList Document[]
Expand All @@ -50,6 +51,7 @@ model Document {
id String @id @default(auto()) @map("_id") @db.ObjectId
yorkieDocumentId String @map("yorkie_document_id")
title String
slug String
content String?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
Expand Down
2 changes: 2 additions & 0 deletions backend/src/documents/types/document-domain.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export class DocumentDomain {
yorkieDocumentId: string;
@ApiProperty({ type: String, description: "Title of the document" })
title: string;
@ApiProperty({ type: String, description: "Slug of the document" })
slug: string;
@ApiProperty({ type: String, description: "Content of the document", required: false })
content?: string;
@ApiProperty({ type: Date, description: "Created date of the document" })
Expand Down
4 changes: 2 additions & 2 deletions backend/src/users/types/user-domain.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ export class UserDomain {
id: string;
@ApiProperty({ type: String, description: "Nickname of user" })
nickname: string;
@ApiProperty({ type: String, description: "Last worksace ID of user" })
lastWorkspaceId: string;
@ApiProperty({ type: String, description: "Last worksace slug of user" })
lastWorkspaceSlug: string;
@ApiProperty({ type: Date, description: "Created date of user" })
createdAt: Date;
@ApiProperty({ type: Date, description: "Updated date of user" })
Expand Down
25 changes: 22 additions & 3 deletions backend/src/users/users.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { User } from "@prisma/client";
import { PrismaService } from "src/db/prisma.service";
import { FindUserResponse } from "./types/find-user-response.type";
import { WorkspaceRoleConstants } from "src/utils/constants/auth-role";
import slugify from "slugify";

@Injectable()
export class UsersService {
Expand All @@ -11,7 +12,11 @@ export class UsersService {
async findOne(userId: string): Promise<FindUserResponse> {
const foundUserWorkspace = await this.prismaService.userWorkspace.findFirst({
select: {
workspaceId: true,
workspace: {
select: {
slug: true,
},
},
},
where: {
userId,
Expand All @@ -35,7 +40,7 @@ export class UsersService {

return {
...foundUser,
lastWorkspaceId: foundUserWorkspace.workspaceId,
lastWorkspaceSlug: foundUserWorkspace.workspace.slug,
};
}

Expand Down Expand Up @@ -63,9 +68,23 @@ export class UsersService {
},
});

const title = `${user.nickname}'s Workspace`;
let slug = slugify(title);

const duplicatedWorkspaceList = await this.prismaService.workspace.findMany({
where: {
slug,
},
});

if (duplicatedWorkspaceList.length) {
slug += `-${duplicatedWorkspaceList.length + 1}`;
}

const workspace = await this.prismaService.workspace.create({
data: {
title: `${user.nickname}'s Workspace`,
title,
slug,
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import { CreateWorkspaceDocumentShareTokenDto } from "./dto/create-workspace-doc
export class WorkspaceDocumentsController {
constructor(private workspaceDocumentsService: WorkspaceDocumentsService) {}

@Get(":document_id")
@Get(":document_slug")
@ApiOperation({
summary: "Retrieve a Document in the Workspace",
description: "If the user has the access permissions, return a document.",
Expand All @@ -50,9 +50,9 @@ export class WorkspaceDocumentsController {
async findOne(
@Req() req: AuthroizedRequest,
@Param("workspace_id") workspaceId: string,
@Param("document_id") documentId: string
@Param("document_slug") documentSlug: string
): Promise<FindWorkspaceDocumentResponse> {
return this.workspaceDocumentsService.findOne(req.user.id, workspaceId, documentId);
return this.workspaceDocumentsService.findOneBySlug(req.user.id, workspaceId, documentSlug);
}

@Get("")
Expand Down
20 changes: 17 additions & 3 deletions backend/src/workspace-documents/workspace-documents.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { FindWorkspaceDocumentsResponse } from "./types/find-workspace-documents
import { JwtService } from "@nestjs/jwt";
import { CreateWorkspaceDocumentShareTokenResponse } from "./types/create-workspace-document-share-token-response.type";
import { ShareRole } from "src/utils/types/share-role.type";
import slugify from "slugify";

@Injectable()
export class WorkspaceDocumentsService {
Expand All @@ -25,16 +26,29 @@ export class WorkspaceDocumentsService {
throw new NotFoundException();
}

let slug = slugify(title);

const duplicatedDocumentList = await this.prismaService.document.findMany({
where: {
slug,
},
});

if (duplicatedDocumentList.length) {
slug += `-${duplicatedDocumentList.length + 1}`;
}

return this.prismaService.document.create({
data: {
title,
slug,
workspaceId,
yorkieDocumentId: Math.random().toString(36).substring(7),
},
});
}

async findOne(userId: string, workspaceId: string, documentId: string) {
async findOneBySlug(userId: string, workspaceId: string, documentSlug: string) {
try {
await this.prismaService.userWorkspace.findFirstOrThrow({
where: {
Expand All @@ -43,9 +57,9 @@ export class WorkspaceDocumentsService {
},
});

return this.prismaService.document.findUniqueOrThrow({
return this.prismaService.document.findFirstOrThrow({
where: {
id: documentId,
slug: documentSlug,
},
});
} catch (e) {
Expand Down
2 changes: 2 additions & 0 deletions backend/src/workspaces/types/workspace-domain.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export class WorkspaceDomain {
id: string;
@ApiProperty({ type: String, description: "Title of the workspace" })
title: string;
@ApiProperty({ type: String, description: "Slug of the workspace" })
slug: string;
@ApiProperty({ type: Date, description: "Created date of the workspace" })
createdAt: Date;
@ApiProperty({ type: Date, description: "Updated date of the workspace" })
Expand Down
6 changes: 3 additions & 3 deletions backend/src/workspaces/workspaces.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export class WorkspacesController {
return this.workspacesService.create(req.user.id, createWorkspaceDto.title);
}

@Get(":id")
@Get(":workspace_slug")
@ApiOperation({
summary: "Retrieve a Workspace",
description: "If the user has the access permissions, return a workspace.",
Expand All @@ -65,9 +65,9 @@ export class WorkspacesController {
})
async findOne(
@Req() req: AuthroizedRequest,
@Param("id") workspaceId: string
@Param("workspace_slug") workspaceSlug: string
): Promise<FindWorkspaceResponse> {
return this.workspacesService.findOne(req.user.id, workspaceId);
return this.workspacesService.findOneBySlug(req.user.id, workspaceSlug);
}

@Get("")
Expand Down
28 changes: 22 additions & 6 deletions backend/src/workspaces/workspaces.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { JwtService } from "@nestjs/jwt";
import { CreateInvitationTokenResponse } from "./types/create-inviation-token-response.type";
import { InvitationTokenPayload } from "./types/inviation-token-payload.type";
import { WorkspaceRoleConstants } from "src/utils/constants/auth-role";
import slugify from "slugify";

@Injectable()
export class WorkspacesService {
Expand All @@ -15,9 +16,22 @@ export class WorkspacesService {
) {}

async create(userId: string, title: string): Promise<Workspace> {
let slug = slugify(title);

const duplicatedWorkspaceList = await this.prismaService.workspace.findMany({
where: {
slug,
},
});

if (duplicatedWorkspaceList.length) {
slug += `-${duplicatedWorkspaceList.length + 1}`;
}

const workspace = await this.prismaService.workspace.create({
data: {
title,
slug,
},
});

Expand All @@ -32,20 +46,22 @@ export class WorkspacesService {
return workspace;
}

async findOne(userId: string, workspaceId: string) {
async findOneBySlug(userId: string, workspaceSlug: string) {
try {
await this.prismaService.userWorkspace.findFirstOrThrow({
const foundWorkspace = await this.prismaService.workspace.findFirstOrThrow({
where: {
userId,
workspaceId,
slug: workspaceSlug,
},
});

return this.prismaService.workspace.findUniqueOrThrow({
await this.prismaService.userWorkspace.findFirstOrThrow({
where: {
id: workspaceId,
userId,
workspaceId: foundWorkspace.id,
},
});

return foundWorkspace;
} catch (e) {
throw new NotFoundException();
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/common/GuestRoute.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const GuestRoute = (props: RejectLoggedInRouteProps) => {
if (isLoggedIn) {
return (
<Navigate
to={`/workspace/${userStore.data?.lastWorkspaceId}`}
to={`/workspace/${userStore.data?.lastWorkspaceSlug}`}
state={{ from: location }}
replace
/>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/drawers/WorkspaceDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const DRAWER_WIDTH = 240;
function WorkspaceDrawer() {
const params = useParams();
const userStore = useSelector(selectUser);
const { data: workspace } = useGetWorkspaceQuery(params.workspaceId);
const { data: workspace } = useGetWorkspaceQuery(params.workspaceSlug);
const [profileAnchorEl, setProfileAnchorEl] = useState<(EventTarget & Element) | null>(null);
const [workspaceListAnchorEl, setWorkspaceListAnchorEl] = useState<
(EventTarget & Element) | null
Expand Down
Loading

0 comments on commit c585c73

Please sign in to comment.