From 58d3e0937b64e6b5cad0fad300306845ae0c124f Mon Sep 17 00:00:00 2001 From: Paradoxe Ngwasi Date: Thu, 11 Jan 2024 15:11:03 +0000 Subject: [PATCH 1/4] implement image-assets/upload API om NextJS api --- apps/web/app/[locale]/layout.tsx | 2 +- .../api/image-assets/upload/[folder]/route.ts | 34 +++++++++++++++++++ apps/web/app/api/user/[id]/route.ts | 4 +-- apps/web/app/helpers/cookies/index.ts | 4 +-- apps/web/app/hooks/features/useImageAssets.ts | 24 +++++-------- apps/web/app/interfaces/IDataResponse.ts | 2 ++ apps/web/app/services/client/api/invite.ts | 2 +- apps/web/app/services/client/axios.ts | 25 +++++++------- .../services/server/requests/image-assets.ts | 24 +++++++++++++ apps/web/lib/settings/profile-avatar.tsx | 5 ++- apps/web/lib/settings/team-avatar.tsx | 7 ++-- apps/web/next.config.js | 3 +- apps/web/package.json | 2 +- yarn.lock | 8 ++--- 14 files changed, 100 insertions(+), 46 deletions(-) create mode 100644 apps/web/app/api/image-assets/upload/[folder]/route.ts create mode 100644 apps/web/app/services/server/requests/image-assets.ts diff --git a/apps/web/app/[locale]/layout.tsx b/apps/web/app/[locale]/layout.tsx index c65051ceb..bafe18d98 100644 --- a/apps/web/app/[locale]/layout.tsx +++ b/apps/web/app/[locale]/layout.tsx @@ -50,7 +50,7 @@ const LocaleLayout = ({ children, params: { locale }, pageProps }: Props) => { const messages = require(`../../messages/${locale}.json`); console.log({ pageProps }); return ( - + {/* diff --git a/apps/web/app/api/image-assets/upload/[folder]/route.ts b/apps/web/app/api/image-assets/upload/[folder]/route.ts new file mode 100644 index 000000000..26181b486 --- /dev/null +++ b/apps/web/app/api/image-assets/upload/[folder]/route.ts @@ -0,0 +1,34 @@ +import { NextResponse } from 'next/server'; +import { authenticatedGuard } from '@app/services/server/guards/authenticated-guard-app'; +import { createImageAssetsRequest } from '@app/services/server/requests/image-assets'; +import { INextParams } from '@app/interfaces'; + +export const config = { + api: { + bodyParser: false + } +}; + +export async function POST(req: Request, { params }: INextParams) { + const res = new NextResponse(); + + const { $res, user, access_token, tenantId } = await authenticatedGuard(req, res); + + if (!user) return NextResponse.json({}, { status: 401 }); + + const folderParam = params.folder as string; + + const form = await req.formData(); + + const response = await createImageAssetsRequest( + { + tenantId: tenantId, + bearer_token: access_token, + folder: folderParam, + contentType: req.headers.get('Content-Type') as string + }, + form + ); + + return $res(response.data); +} diff --git a/apps/web/app/api/user/[id]/route.ts b/apps/web/app/api/user/[id]/route.ts index e0aab649a..5ae5258bb 100644 --- a/apps/web/app/api/user/[id]/route.ts +++ b/apps/web/app/api/user/[id]/route.ts @@ -4,7 +4,7 @@ import { getTaskCreator, updateUserAvatarRequest } from '@app/services/server/re import { deleteUserRequest } from '@app/services/server/requests/user'; import { NextResponse } from 'next/server'; -export async function GET(req: Request, { params }: { params: { id: string } }) { +export async function GET(req: Request, { params }: { params: { id: string } }) { const res = new NextResponse(); const { $res, user, access_token } = await authenticatedGuard(req, res); @@ -19,7 +19,7 @@ export async function GET(req: Request, { params }: { params: { id: string } }) ); } -export async function POST(req: Request) { +export async function PUT(req: Request) { const res = new NextResponse(); const { $res, user, access_token, tenantId } = await authenticatedGuard(req, res); diff --git a/apps/web/app/helpers/cookies/index.ts b/apps/web/app/helpers/cookies/index.ts index 701f09439..fe7f39975 100644 --- a/apps/web/app/helpers/cookies/index.ts +++ b/apps/web/app/helpers/cookies/index.ts @@ -214,9 +214,9 @@ export function setActiveUserIdCookie(userId: string, ctx?: NextCtx) { } export function getNoTeamPopupShowCookie(ctx?: NextCtx) { - return getCookie(NO_TEAM_POPUP_SHOW_COOKIE_NAME, { + return !!getCookie(NO_TEAM_POPUP_SHOW_COOKIE_NAME, { ...(ctx || {}) - }) as boolean; + }); } export function setNoTeamPopupShowCookie(show: boolean, ctx?: NextCtx) { return setCookie(NO_TEAM_POPUP_SHOW_COOKIE_NAME, show, { ...(ctx || {}) }); diff --git a/apps/web/app/hooks/features/useImageAssets.ts b/apps/web/app/hooks/features/useImageAssets.ts index 1b7e93d51..826cd9e12 100644 --- a/apps/web/app/hooks/features/useImageAssets.ts +++ b/apps/web/app/hooks/features/useImageAssets.ts @@ -2,8 +2,8 @@ import { getAccessTokenCookie } from '@app/helpers'; import { useCallback, useState } from 'react'; -import axios, { AxiosResponse } from 'axios'; -import { GAUZY_API_BASE_SERVER_URL } from '@app/constants'; +import { post } from '@app/services/client/axios'; +import { IImageAssets } from '@app/interfaces'; export function useImageAssets() { const [loading, setLoading] = useState(false); @@ -17,19 +17,13 @@ export function useImageAssets() { formData.append('organizationId', organizationId); setLoading(true); - return axios - .post(GAUZY_API_BASE_SERVER_URL.value + `/api/image-assets/upload/${folder}`, formData, { - headers: { - 'tenant-id': tenantId, - authorization: `Bearer ${bearer_token}` - } - }) - .then(async (res: AxiosResponse) => { - return res.data; - }) - .catch((e) => { - console.log(e); - }) + return post(`/image-assets/upload/${folder}`, formData, { + headers: { + 'tenant-id': tenantId, + Authorization: `Bearer ${bearer_token}` + } + }) + .then((res) => res.data) .finally(() => { setLoading(false); }); diff --git a/apps/web/app/interfaces/IDataResponse.ts b/apps/web/app/interfaces/IDataResponse.ts index 90d3154dd..d5eba6c4f 100644 --- a/apps/web/app/interfaces/IDataResponse.ts +++ b/apps/web/app/interfaces/IDataResponse.ts @@ -34,3 +34,5 @@ export interface ISuccessResponse { status: number; message: string; } + +export type INextParams = { params: Record }; diff --git a/apps/web/app/services/client/api/invite.ts b/apps/web/app/services/client/api/invite.ts index 4bac1c949..739122f92 100644 --- a/apps/web/app/services/client/api/invite.ts +++ b/apps/web/app/services/client/api/invite.ts @@ -39,7 +39,7 @@ export async function inviteByEmailsAPI(data: IIInviteRequest, tenantId: string) }; // for not direct call we need to adjust data to include name and email only - const fetchData = await post(endpoint, dataToInviteUser, true, { tenantId }); + const fetchData = await post(endpoint, dataToInviteUser, { tenantId }); return GAUZY_API_BASE_SERVER_URL.value ? fetchData.data : fetchData; } diff --git a/apps/web/app/services/client/axios.ts b/apps/web/app/services/client/axios.ts index 0d210b312..149965f99 100644 --- a/apps/web/app/services/client/axios.ts +++ b/apps/web/app/services/client/axios.ts @@ -1,7 +1,7 @@ /* eslint-disable no-mixed-spaces-and-tabs */ import { API_BASE_URL, DEFAULT_APP_PATH, GAUZY_API_BASE_SERVER_URL } from '@app/constants'; import { getAccessTokenCookie, getActiveTeamIdCookie } from '@app/helpers/cookies'; -import axios, { AxiosResponse } from 'axios'; +import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'; const api = axios.create({ baseURL: API_BASE_URL, @@ -92,25 +92,26 @@ function get( : api.get(endpoint); } -function post( - endpoint: string, - data: any, - isDirect: boolean, - extras?: { - tenantId: string; - } +function post( + url: string, + data?: any, + config?: AxiosRequestConfig & { tenantId?: string; directAPI?: boolean } ) { + const { directAPI = true } = config || {}; + let baseURL: string | undefined = GAUZY_API_BASE_SERVER_URL.value; baseURL = baseURL ? `${baseURL}/api` : undefined; - return isDirect && baseURL - ? apiDirect.post(endpoint, data, { + return baseURL && directAPI + ? apiDirect.post(url, data, { baseURL, + ...config, headers: { - ...(extras?.tenantId ? { 'tenant-id': extras?.tenantId } : {}) + ...(config?.tenantId ? { 'tenant-id': config?.tenantId } : {}), + ...config?.headers } }) - : api.post(endpoint, data); + : api.post(url, data); } export { get, post }; diff --git a/apps/web/app/services/server/requests/image-assets.ts b/apps/web/app/services/server/requests/image-assets.ts new file mode 100644 index 000000000..1ab6a7aeb --- /dev/null +++ b/apps/web/app/services/server/requests/image-assets.ts @@ -0,0 +1,24 @@ +import { IImageAssets } from '@app/interfaces'; +import { serverFetch } from '../fetch'; + +type Params = { + tenantId: string; + bearer_token: string; + folder: string; + contentType: string; +}; + +export function createImageAssetsRequest(params: Params, body: FormData) { + return serverFetch({ + path: '/image-assets/upload/' + params.folder, + method: 'POST', + body, + bearer_token: params.bearer_token, + init: { + headers: { + 'tenant-id': params.tenantId, + 'Content-Type': params.contentType + } + } + }); +} diff --git a/apps/web/lib/settings/profile-avatar.tsx b/apps/web/lib/settings/profile-avatar.tsx index 0867b69d2..5214d75bb 100644 --- a/apps/web/lib/settings/profile-avatar.tsx +++ b/apps/web/lib/settings/profile-avatar.tsx @@ -1,7 +1,6 @@ /* eslint-disable no-mixed-spaces-and-tabs */ import { imgTitle } from '@app/helpers'; import { useAuthenticateUser, useImageAssets, useSettings } from '@app/hooks'; -import { IImageAssets } from '@app/interfaces'; import { clsxm } from '@app/utils'; import { Avatar, Button } from 'lib/components'; import Image from 'next/image'; @@ -34,8 +33,8 @@ export const ProfileAvatar = () => { user.tenantId as string, user?.employee?.organizationId ) - .then((d: IImageAssets) => { - updateAvatar({ imageId: d.id, id: user.id }); + .then((image) => { + updateAvatar({ imageId: image.id, id: user.id }); }) .finally(() => { setAvatarBtn(false); diff --git a/apps/web/lib/settings/team-avatar.tsx b/apps/web/lib/settings/team-avatar.tsx index ca83edb4f..2e6f56e15 100644 --- a/apps/web/lib/settings/team-avatar.tsx +++ b/apps/web/lib/settings/team-avatar.tsx @@ -1,7 +1,6 @@ /* eslint-disable no-mixed-spaces-and-tabs */ import { imgTitle } from '@app/helpers'; import { useAuthenticateUser, useImageAssets, useOrganizationTeams } from '@app/hooks'; -import { IImageAssets } from '@app/interfaces'; import { clsxm } from '@app/utils'; import { Avatar, Button } from 'lib/components'; import { useTheme } from 'next-themes'; @@ -38,11 +37,11 @@ export const TeamAvatar = ({ disabled, bgColor }: { disabled: boolean; bgColor?: user.tenantId as string, user.employee.organizationId ) - .then((d: IImageAssets) => { + .then((image) => { updateOrganizationTeam(activeTeam, { ...activeTeam, - imageId: d.id, - image: d + imageId: image.id, + image: image }); }) .finally(() => { diff --git a/apps/web/next.config.js b/apps/web/next.config.js index 9abae01b6..d749b5e4b 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -27,7 +27,8 @@ const nextConfig = { 'cdn-icons-png.flaticon.com', // Remove this domain once the Backend Icons list is added 'api.gauzy.co', 'apistage.gauzy.co', - 'gauzy.s3.wasabisys.com' + 'gauzy.s3.wasabisys.com', + 'gauzystage.s3.wasabisys.com' ] }, // Optional build-time configuration options sentry: { diff --git a/apps/web/package.json b/apps/web/package.json index 32356dd5d..d1af861b1 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -49,7 +49,7 @@ "clsx": "^2.0.0", "cmdk": "^0.2.0", "cookie": "^0.5.0", - "cookies-next": "^2.1.1", + "cookies-next": "^4.1.0", "date-fns": "^2.30.0", "domhandler": "^5.0.3", "embla-carousel-react": "^8.0.0-rc11", diff --git a/yarn.lock b/yarn.lock index 66e832bdf..e873febcc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9874,10 +9874,10 @@ cookie@^0.4.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== -cookies-next@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/cookies-next/-/cookies-next-2.1.2.tgz#78fe2f3e7b68eb0e1c6682c9e1f4e94ce4d09904" - integrity sha512-czxcfqVaQlo0Q/3xMgp/2jpspsuLJrIm6D37wlmibP3DAcYT315c8UxQmDMohhAT/GRWpaHzpDEFANBjzTFQGg== +cookies-next@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cookies-next/-/cookies-next-4.1.0.tgz#1ecf2e4a65abe2ad3ffb3f4ad3d303ae008303c3" + integrity sha512-BREVc4TJT4NwXfyKjdjnYFXM6iRns+MYpCd34ClXuYqeisXnkPkbq7Ok9xaqi9mHmV6H2rwPE+p3EpMz4pF/kQ== dependencies: "@types/cookie" "^0.4.1" "@types/node" "^16.10.2" From 0da60774893b9837b7529277380960a3555b74b5 Mon Sep 17 00:00:00 2001 From: Paradoxe Ngwasi Date: Thu, 11 Jan 2024 15:11:37 +0000 Subject: [PATCH 2/4] fixed build error --- apps/web/app/api/image-assets/upload/[folder]/route.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/apps/web/app/api/image-assets/upload/[folder]/route.ts b/apps/web/app/api/image-assets/upload/[folder]/route.ts index 26181b486..c075fd57b 100644 --- a/apps/web/app/api/image-assets/upload/[folder]/route.ts +++ b/apps/web/app/api/image-assets/upload/[folder]/route.ts @@ -3,12 +3,6 @@ import { authenticatedGuard } from '@app/services/server/guards/authenticated-gu import { createImageAssetsRequest } from '@app/services/server/requests/image-assets'; import { INextParams } from '@app/interfaces'; -export const config = { - api: { - bodyParser: false - } -}; - export async function POST(req: Request, { params }: INextParams) { const res = new NextResponse(); From 803f0aca9954894e918938161e291e233dc0709d Mon Sep 17 00:00:00 2001 From: Paradoxe Ngwasi Date: Thu, 11 Jan 2024 15:14:51 +0000 Subject: [PATCH 3/4] fixed build error --- apps/web/app/services/client/api/invite.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/app/services/client/api/invite.ts b/apps/web/app/services/client/api/invite.ts index 739122f92..0850a4e31 100644 --- a/apps/web/app/services/client/api/invite.ts +++ b/apps/web/app/services/client/api/invite.ts @@ -39,7 +39,7 @@ export async function inviteByEmailsAPI(data: IIInviteRequest, tenantId: string) }; // for not direct call we need to adjust data to include name and email only - const fetchData = await post(endpoint, dataToInviteUser, { tenantId }); + const fetchData = await post(endpoint, dataToInviteUser, { tenantId }); return GAUZY_API_BASE_SERVER_URL.value ? fetchData.data : fetchData; } From c6b8d1cf7c06afaa896d403bad69fd64c334295b Mon Sep 17 00:00:00 2001 From: Paradoxe Ngwasi Date: Thu, 11 Jan 2024 15:46:33 +0000 Subject: [PATCH 4/4] implement post github integration on Frontend --- apps/web/app/helpers/cookies/index.ts | 4 +-- .../client/api/integrations/github.ts | 4 +-- apps/web/app/services/client/axios.ts | 26 ++++++++++++++++--- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/apps/web/app/helpers/cookies/index.ts b/apps/web/app/helpers/cookies/index.ts index fe7f39975..50521861c 100644 --- a/apps/web/app/helpers/cookies/index.ts +++ b/apps/web/app/helpers/cookies/index.ts @@ -181,7 +181,7 @@ export function setActiveProjectIdCookie(teamIds: string, ctx?: NextCtx) { } // Organization Id -export function getOrganizationIdCookie(ctx: NextCtx) { +export function getOrganizationIdCookie(ctx?: NextCtx) { return getCookie(ORGANIZATION_ID_COOKIE_NAME, { ...ctx }) as string; } @@ -190,7 +190,7 @@ export function setOrganizationIdCookie(orgId: string, ctx?: NextCtx) { } // Tenant Id -export function getTenantIdCookie(ctx: NextCtx) { +export function getTenantIdCookie(ctx?: NextCtx) { return getCookie(TENANT_ID_COOKIE_NAME, { ...ctx }) as string; } diff --git a/apps/web/app/services/client/api/integrations/github.ts b/apps/web/app/services/client/api/integrations/github.ts index 8d2777bf0..9e963e309 100644 --- a/apps/web/app/services/client/api/integrations/github.ts +++ b/apps/web/app/services/client/api/integrations/github.ts @@ -1,9 +1,9 @@ import { CreateResponse, IGithubMetadata, IGithubRepositories } from '@app/interfaces'; -import api from '../../axios'; +import api, { post } from '../../axios'; // TODO export function installGitHubIntegrationAPI(body: any) { - return api.post('/integration/github/install', body); + return post('/integration/github/install', body); } // TODO diff --git a/apps/web/app/services/client/axios.ts b/apps/web/app/services/client/axios.ts index 149965f99..ae054b77e 100644 --- a/apps/web/app/services/client/axios.ts +++ b/apps/web/app/services/client/axios.ts @@ -1,6 +1,11 @@ /* eslint-disable no-mixed-spaces-and-tabs */ import { API_BASE_URL, DEFAULT_APP_PATH, GAUZY_API_BASE_SERVER_URL } from '@app/constants'; -import { getAccessTokenCookie, getActiveTeamIdCookie } from '@app/helpers/cookies'; +import { + getAccessTokenCookie, + getActiveTeamIdCookie, + getOrganizationIdCookie, + getTenantIdCookie +} from '@app/helpers/cookies'; import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'; const api = axios.create({ @@ -94,20 +99,35 @@ function get( function post( url: string, - data?: any, + data?: Record | FormData, config?: AxiosRequestConfig & { tenantId?: string; directAPI?: boolean } ) { + const bearer_token = getAccessTokenCookie(); + const tenantId = getTenantIdCookie(); + const organizationId = getOrganizationIdCookie(); + const { directAPI = true } = config || {}; let baseURL: string | undefined = GAUZY_API_BASE_SERVER_URL.value; baseURL = baseURL ? `${baseURL}/api` : undefined; + if (baseURL && directAPI && data && !(data instanceof FormData)) { + if (!data.tenantId) { + data.tenantId = tenantId; + } + + if (!data.organizationId) { + data.organizationId = organizationId; + } + } + return baseURL && directAPI ? apiDirect.post(url, data, { baseURL, ...config, headers: { - ...(config?.tenantId ? { 'tenant-id': config?.tenantId } : {}), + Authorization: `Bearer ${bearer_token}`, + ...(config?.tenantId ? { 'tenant-id': config?.tenantId } : { 'tenant-id': tenantId }), ...config?.headers } })