Skip to content

Commit

Permalink
improve: replace orgId with slug for readability (#205)
Browse files Browse the repository at this point in the history
* feat: add slug for org

* change command name from camelCase to snake-case, use lowercase for organization's name, limit organization's name

* query org in project layout

* save orgSlug and orgId into store instead of getting from useParams

---------

Co-authored-by: hudy9x <[email protected]>
  • Loading branch information
namnn9x and hudy9x authored Jul 10, 2024
1 parent 8069518 commit 2c000ec
Show file tree
Hide file tree
Showing 148 changed files with 12,966 additions and 789 deletions.
12,714 changes: 12,219 additions & 495 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
"react-pdf": "^7.7.1",
"read-excel-file": "^5.6.1",
"resend": "^1.0.0",
"slugify": "^1.6.6",
"tippy.js": "^6.3.7",
"tslib": "^2.3.0",
"winston": "^3.12.0",
Expand Down
20 changes: 18 additions & 2 deletions packages/be-gateway/src/routes/organization/index.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import {
mdOrgGetOwned,
mdOrgMemAdd,
mdOrgMemGetByUid,
mdOrgUpdate
mdOrgUpdate,
mdOrgGetOneBySlug,
generateSlug,
} from '@shared/models'
import {
BaseController,
Expand Down Expand Up @@ -39,6 +41,14 @@ export class OrganizationController extends BaseController {
return result
}

@Get('/query/slug')
async getOrgBySlug() {
const { slug } = this.req.query as { slug: string }
const result = await mdOrgGetOneBySlug(slug)

return result
}

@Get('')
async getOrgByUid() {
const req = this.req as AuthRequest
Expand Down Expand Up @@ -89,9 +99,12 @@ export class OrganizationController extends BaseController {
throw new Error('REACHED_MAX_ORGANIZATION')
}

const slug = generateSlug(body.name)

const result = await mdOrgAdd({
name: body.name,
desc: body.desc,
slug,
maxStorageSize: MAX_STORAGE_SIZE,
cover: body.cover,
avatar: null,
Expand All @@ -118,7 +131,9 @@ export class OrganizationController extends BaseController {

return result
} catch (error) {
console.log('create org error', error)
if (error.code === 'P2002') {
throw new Error('DUPLICATE_ORGANIZATION')
}

throw new InternalServerException()
}
Expand All @@ -138,6 +153,7 @@ export class OrganizationController extends BaseController {

const result = await mdOrgUpdate(body.id, {
name: body.name,
slug: generateSlug(body.name),
desc: body.desc,
cover: body.cover,
avatar: null,
Expand Down
17 changes: 17 additions & 0 deletions packages/shared-models/src/lib/organization.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Organization, OrganizationMembers } from '@prisma/client';
import { orgMemberModel, orgModel } from './_prisma';
import slugify from 'slugify';

export const mdOrgGetOne = async (orgId: string | string[]) => {
return orgModel.findFirst({
Expand All @@ -11,6 +12,14 @@ export const mdOrgGetOne = async (orgId: string | string[]) => {
});
};

export const mdOrgGetOneBySlug = async (slug: string) => {
return orgModel.findUnique({
where: {
slug,
}
});
};

export const mdOrgGet = async (projectId: string | string[]) => {
return orgModel.findMany({
where: {
Expand Down Expand Up @@ -64,3 +73,11 @@ export const mdOrgMemAddMany = async (data: Omit<OrganizationMembers, 'id'>[]) =
data: data
});
};

export const generateSlug = (name: string) => {
return slugify(name, {
replacement: '-',
lower: true,
trim: true
})
}
1 change: 1 addition & 0 deletions packages/shared-models/src/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ model Favorites {
model Organization {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String @unique
slug String @unique
cover String?
avatar String?
maxStorageSize Int?
Expand Down
9 changes: 6 additions & 3 deletions packages/shared-models/src/prisma/seed.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { PrismaClient } from '@prisma/client'
import { createAdminUser, getOrgOwner } from './seeder/user'
import { createOrganization } from './seeder/organization'
import { createOrganization, updateAllSlug } from './seeder/organization'
import { createProject } from './seeder/project'
import { generateIconName, generateOrgName, generateProjectName } from './dummy'
import { runTest } from './seeder/test'
Expand Down Expand Up @@ -71,11 +71,14 @@ password: ${process.env.DEFAULT_PWD || '123123123'}
case 'starter':
createStarterData()
break;

case 'update-slug':
updateAllSlug().then(() => {
console.log('Update all organization successfully')
})
break
case 'daily-stats':
await generateDailyData()
break;

case 'test':
await runTest()
break;
Expand Down
43 changes: 41 additions & 2 deletions packages/shared-models/src/prisma/seeder/organization.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import { InvitationStatus, OrganizationRole } from "@prisma/client"
import { InvitationStatus, Organization, OrganizationRole } from "@prisma/client"
import { pmClient } from "../../lib/_prisma"
import { generateSlug } from "../../lib"

const MAX_STORAGE_SIZE = 100 * 1024 * 1024 // 100Mb

export const createOrganization = async (body: {
name: string,
desc?: string,
cover?: string
cover?: string,
uid: string
}) => {
try {
const result = await pmClient.organization.create({
data: {
name: body.name,
desc: body.desc,
slug: generateSlug(body.name),
maxStorageSize: MAX_STORAGE_SIZE,
cover: body.cover,
avatar: null,
Expand Down Expand Up @@ -46,3 +48,40 @@ export const createOrganization = async (body: {
}

}

export const updateAllSlug = async () => {
const orgs = await pmClient.organization.findMany({
select: {
id: true,
name: true
}
})

const promises = []
for (const org of orgs) {
const { id, name } = org
const slug = generateSlug(name.toLowerCase())

console.log('slug', slug)

promises.push(updateOrganization(id, { name, slug }))
}

await Promise.allSettled(promises)
}

export const updateOrganization = async (id: string, data: Partial<Organization>) => {
try {
await pmClient.organization.update({
data: data,
where: {
id,
},
})

console.log('update organization succesfully')
} catch (error) {
console.log('update organization error', error)
return null
}
}
36 changes: 0 additions & 36 deletions packages/ui-app/app/[orgID]/layout.tsx

This file was deleted.

35 changes: 0 additions & 35 deletions packages/ui-app/app/[orgID]/meeting/MeetingRoomList.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ function ProjectSidebarContainer() {

return (
<aside className={classes.join(' ')}>
{/* <RootSidebar /> */}
<nav className={`secondary-sidebar`}>
<div onClick={() => {
setLocalCache('COMPACT_MENU', isCompactMode ? '0' : '1')
Expand Down
File renamed without changes.
76 changes: 76 additions & 0 deletions packages/ui-app/app/[orgName]/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
'use client'
import HamburgerMenu from '@/components/HamburgerMenu'
import ProjectSidebar from './ProjectSidebar'
import { useOrgMemberGet } from '@/services/organizationMember'
import EventUserProjectUpdate from '@/features/Events/EventUserProjectUpdate'
import { useOrgIdBySlug } from '@/hooks/useOrgIdBySlug'
import { Loading } from '@shared/ui'
import { ReactNode, useEffect } from 'react'
import { useGlobalDataFetch } from '@/features/GlobalData/useGlobalDataFetch'
import { useGlobalDataStore } from '@/store/global'
import { setLocalCache } from '@shared/libs'

// NOTE: do not move these following function inside ProjectLayout
// cuz it causes a re-render to the entire component
// why ? because it contains useParams inside, and this will triggered as url updated
function PrefetchOrgData() {
useOrgMemberGet()
return <></>
}

function OrgDetailContent({ children }: { children: ReactNode }) {
const { orgId } = useGlobalDataStore()

console.log('run in <OrgDetailContent/>', orgId)
if (!orgId) {
return <Loading className='h-screen w-screen items-center justify-center' title='Fetching organization data ...' />
}

return <>
<PrefetchOrgData />
<EventUserProjectUpdate />
<ProjectSidebar />
<main
className="main-content w-full"
style={{ width: 'calc(100% - 251px)' }}>
<HamburgerMenu />
{children}
</main>
</>
}

// This will clear the global data as the org page unmount
function OrgDetailClearGlobalData() {
const { setOrgId } = useGlobalDataStore()

useEffect(() => {
return () => {
console.log('Clear data inside Global data store !')
setOrgId('')

setLocalCache('ORG_ID', '')
setLocalCache('ORG_SLUG', '')
}
}, [])
return <></>
}

// This component used for fetching global data
function OrgDetailFetchGlobalData() {
useGlobalDataFetch()
return <></>
}

export default function ProjectLayout({
children
}: {
children: React.ReactNode
}) {
return (
<>
<OrgDetailFetchGlobalData />
<OrgDetailClearGlobalData />
<OrgDetailContent>{children}</OrgDetailContent>
</>
)
}
Loading

0 comments on commit 2c000ec

Please sign in to comment.