Browse Source

fix: add get custom url path and check availability api

Ramesh Mane 6 days ago
parent
commit
3812a77bb0
  1. 38
      packages/nc-gui/components/dlg/share-and-collaborate/SharePage.vue
  2. 44
      packages/nocodb/src/controllers/custom-urls.controller.ts
  3. 32
      packages/nocodb/src/models/CustomUrl.ts
  4. 34
      packages/nocodb/src/models/View.ts
  5. 106
      packages/nocodb/src/schema/swagger-v2.json
  6. 14
      packages/nocodb/src/schema/swagger.json
  7. 20
      packages/nocodb/src/services/custom-urls.service.ts
  8. 6
      packages/nocodb/src/services/views.service.ts

38
packages/nc-gui/components/dlg/share-and-collaborate/SharePage.vue

@ -108,11 +108,11 @@ const isOpenCustomUrl = computed(() => {
}) })
const customUrl = computed({ const customUrl = computed({
get: () => (isOpenCustomUrl.value ? activeView.value?.custom_path ?? '' : ''), get: () => (isOpenCustomUrl.value ? activeView.value?.custom_url_path ?? '' : ''),
set: async (value) => { set: async (value) => {
if (!activeView.value) return if (!activeView.value) return
activeView.value = { ...(activeView.value as any), custom_path: isOpenCustomUrl.value ? value : null } activeView.value = { ...(activeView.value as any), custom_url_path: isOpenCustomUrl.value ? value : null }
}, },
}) })
@ -124,9 +124,9 @@ const toggleCustomUrl = async () => {
isUpdating.value.password = true isUpdating.value.password = true
try { try {
if (passwordProtected.value) { if (passwordProtected.value) {
activeView.value = { ...(activeView.value as any), custom_path: null } activeView.value = { ...(activeView.value as any), custom_url_path: null }
} else { } else {
activeView.value = { ...(activeView.value as any), custom_path: '' } activeView.value = { ...(activeView.value as any), custom_url_path: '' }
} }
await updateSharedView() await updateSharedView()
@ -251,6 +251,17 @@ function sharedViewUrl() {
}` }`
} }
const dashboardUrl1 = computed(() => {
// get base url for workspace
const baseUrl = getBaseUrl(workspaceStore.activeWorkspaceId)
if (baseUrl) {
return `${baseUrl}${appInfo.value?.dashboardPath}`
}
return dashboardUrl.value
})
const toggleViewShare = async () => { const toggleViewShare = async () => {
if (!activeView.value?.id) return if (!activeView.value?.id) return
@ -317,7 +328,7 @@ async function updateSharedView() {
await $api.dbViewShare.update(activeView.value.id!, { await $api.dbViewShare.update(activeView.value.id!, {
meta, meta,
password: activeView.value.password, password: activeView.value.password,
...(activeView.value?.custom_path ? { custom_path: activeView.value?.custom_path } : {}), custom_url_path: activeView.value?.custom_url_path ?? null,
}) })
} catch (e: any) { } catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e)) message.error(await extractSdkResponseErrorMsg(e))
@ -337,8 +348,6 @@ const updateSharedViewWithDebounce = useDebounceFn(
async function savePreFilledMode() { async function savePreFilledMode() {
await updateSharedView() await updateSharedView()
} }
watchEffect(() => {})
</script> </script>
<template> <template>
@ -374,15 +383,26 @@ watchEffect(() => {})
/> />
</div> </div>
<Transition mode="out-in" name="layout"> <Transition mode="out-in" name="layout">
<div v-if="isOpenCustomUrl" class="flex gap-2 mt-2 w-2/3"> <div
v-if="isOpenCustomUrl"
class="flex items-center mt-2 pl-2 pr-1 w-full border-1 rounded-lg focus-within:(border-1 border-nc-border-brand shadow-selected)"
>
<div class="text-nc-content-gray-muted">{{ dashboardUrl1 }}#/shared/</div>
<a-input <a-input
v-model:value="customUrl" v-model:value="customUrl"
placeholder="Enter custom url" placeholder="Enter custom url"
class="!rounded-lg !py-1 !bg-white" class="!rounded-lg !py-1 h-8"
data-testid="nc-modal-share-view__custom-url" data-testid="nc-modal-share-view__custom-url"
size="small" size="small"
:bordered="false"
autocomplete="off"
@update:value="updateSharedViewWithDebounce" @update:value="updateSharedViewWithDebounce"
/> />
<div>
<NcButton size="xs" @click.stop="updateSharedView">
{{ $t('general.save') }}
</NcButton>
</div>
</div> </div>
</Transition> </Transition>
</div> </div>

44
packages/nocodb/src/controllers/custom-urls.controller.ts

@ -0,0 +1,44 @@
import {
Body,
Controller,
Get,
HttpCode,
Param,
Post,
UseGuards,
} from '@nestjs/common';
import { GlobalGuard } from '~/guards/global/global.guard';
import { MetaApiLimiterGuard } from '~/guards/meta-api-limiter.guard';
import { TenantContext } from '~/decorators/tenant-context.decorator';
import { NcContext } from '~/interface/config';
import { CustomUrlsService } from 'src/services/custom-urls.service';
@Controller()
@UseGuards(MetaApiLimiterGuard, GlobalGuard)
export class CustomUrlsController {
constructor(protected readonly customUrlsService: CustomUrlsService) {}
@Get([
'/api/v1/db/meta/custom-url/:customPath',
'/api/v2/meta/custom-url/:customPath',
])
async getOriginalPath(
@TenantContext() context: NcContext,
@Param('customPath') customPath: string,
) {
await this.customUrlsService.getOriginalPath(context, customPath);
}
@Post(['/api/v1/db/meta/custom-url/check-path', '/api/v2/meta/check-path'])
@HttpCode(200)
async checkAvailability(
@TenantContext() context: NcContext,
@Body()
body: {
id?: string;
custom_path?: string;
},
) {
await this.customUrlsService.checkAvailability(context, body);
}
}

32
packages/nocodb/src/models/CustomUrl.ts

@ -39,7 +39,7 @@ export default class CustomUrl {
return customUrl && new CustomUrl(customUrl); return customUrl && new CustomUrl(customUrl);
} }
public static async getByCustomPath( public static async getOriginUrlByCustomPath(
_context: NcContext, _context: NcContext,
customPath: string, customPath: string,
ncMeta = Noco.ncMeta, ncMeta = Noco.ncMeta,
@ -64,6 +64,8 @@ export default class CustomUrl {
} }
} }
console.log('custom url', customUrl);
return customUrl && new CustomUrl(customUrl); return customUrl && new CustomUrl(customUrl);
} }
@ -134,6 +136,34 @@ export default class CustomUrl {
); );
} }
public static async checkAvailability(
_context: NcContext,
params: Pick<CustomUrl, 'id' | 'custom_path'>,
ncMeta = Noco.ncMeta,
) {
const condition = extractProps(params, ['custom_path']);
const customUrlList = await ncMeta.metaList2(
RootScopes.ROOT,
RootScopes.ROOT,
MetaTable.CUSTOM_URLS,
{
condition,
xcCondition: params.id
? {
_not: {
id: {
neq: params.id,
},
},
}
: null,
},
);
return !!customUrlList.length;
}
static async delete( static async delete(
_context: NcContext, _context: NcContext,
customUrl: Pick<CustomUrl, 'id' | 'view_id'>, customUrl: Pick<CustomUrl, 'id' | 'view_id'>,

34
packages/nocodb/src/models/View.ts

@ -103,6 +103,7 @@ export default class View implements ViewType {
show_system_fields?: boolean; show_system_fields?: boolean;
meta?: any; meta?: any;
fk_custom_url_id?: string; fk_custom_url_id?: string;
custom_url_path?: string;
constructor(data: View) { constructor(data: View) {
Object.assign(this, data); Object.assign(this, data);
@ -128,6 +129,15 @@ export default class View implements ViewType {
); );
if (view) { if (view) {
view.meta = parseMetaProp(view); view.meta = parseMetaProp(view);
if (view.fk_custom_url_id) {
const customUrl = await CustomUrl.get(context, {
id: view.fk_custom_url_id,
});
view.custom_url_path = customUrl.custom_path || null;
}
await NocoCache.set(`${CacheScope.VIEW}:${view.id}`, view); await NocoCache.set(`${CacheScope.VIEW}:${view.id}`, view);
} }
} }
@ -170,6 +180,14 @@ export default class View implements ViewType {
); );
if (view) { if (view) {
if (view.fk_custom_url_id) {
const customUrl = await CustomUrl.get(context, {
id: view.fk_custom_url_id,
});
view.custom_url_path = customUrl.custom_path || null;
}
await NocoCache.set( await NocoCache.set(
`${CacheScope.VIEW}:${fk_model_id}:${view.id}`, `${CacheScope.VIEW}:${fk_model_id}:${view.id}`,
view, view,
@ -210,6 +228,14 @@ export default class View implements ViewType {
); );
if (view) { if (view) {
view.meta = parseMetaProp(view); view.meta = parseMetaProp(view);
if (view.fk_custom_url_id) {
const customUrl = await CustomUrl.get(context, {
id: view.fk_custom_url_id,
});
view.custom_url_path = customUrl.custom_path || null;
}
await NocoCache.set(`${CacheScope.VIEW}:${fk_model_id}:default`, view); await NocoCache.set(`${CacheScope.VIEW}:${fk_model_id}:default`, view);
} }
} }
@ -240,6 +266,14 @@ export default class View implements ViewType {
); );
for (const view of viewsList) { for (const view of viewsList) {
view.meta = parseMetaProp(view); view.meta = parseMetaProp(view);
if (view.fk_custom_url_id) {
const customUrl = await CustomUrl.get(context, {
id: view.fk_custom_url_id,
});
view.custom_url_path = customUrl.custom_path || null;
}
} }
await NocoCache.setList(CacheScope.VIEW, [modelId], viewsList); await NocoCache.setList(CacheScope.VIEW, [modelId], viewsList);
} }

106
packages/nocodb/src/schema/swagger-v2.json

@ -12970,6 +12970,93 @@
"$ref": "#/components/parameters/xc-token" "$ref": "#/components/parameters/xc-token"
} }
] ]
},
"/api/v2/meta/custom-url/{customPath}": {
"get": {
"summary": "Get original path from custom url path",
"operationId": "get-original-path",
"description": "Get original path from custom url path",
"tags": [
"CustomUrl", "Internal"
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {},
"examples": {
"Example 1": {
"value": "nc/form/957b3e5b-c7fe-4805-bdb2-e4048e974f62"
}
}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest"
}
}
},
"parameters": [
{
"schema": {
"type": "string"
},
"name": "customPath",
"in": "path",
"required": true,
"description": "Custom url path"
},
{
"$ref": "#/components/parameters/xc-token"
}
]
},
"/api/v2/meta/custom-url/check-path": {
"get": {
"summary": "Check custom url path availability",
"operationId": "check-availability",
"description": "Check custom url path availability",
"tags": [
"CustomUrl", "Internal"
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"status": {
"custom_path": "string"
}
}
}
}
}
},
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {},
"examples": {
"Example 1": {
"value": true
}
}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest"
}
}
}
} }
}, },
"components": { "components": {
@ -19841,7 +19928,8 @@
"examples": [ "examples": [
{ {
"meta": {}, "meta": {},
"password": "123456789" "password": "123456789",
"custom_url_path": "feedback-form"
} }
], ],
"title": "Shared View Request Model", "title": "Shared View Request Model",
@ -19854,6 +19942,10 @@
"password": { "password": {
"$ref": "#/components/schemas/StringOrNull", "$ref": "#/components/schemas/StringOrNull",
"description": "Password to restrict access" "description": "Password to restrict access"
},
"custom_url_path": {
"$ref": "#/components/schemas/StringOrNull",
"description": "Custom url path"
} }
}, },
"x-stoplight": { "x-stoplight": {
@ -21205,6 +21297,18 @@
} }
], ],
"description": "Associated View Model" "description": "Associated View Model"
},
"owned_by": {
"$ref": "#/components/schemas/Id",
"description": "ID of view owner user"
},
"fk_custom_url_id": {
"$ref": "#/components/schemas/StringOrNull",
"description": "Custom url id of view"
},
"custom_url_path": {
"$ref": "#/components/schemas/StringOrNull",
"description": "Custom url path"
} }
}, },
"required": [ "required": [

14
packages/nocodb/src/schema/swagger.json

@ -24958,7 +24958,7 @@
{ {
"meta": {}, "meta": {},
"password": "123456789", "password": "123456789",
"custom_path": "feedback-form" "custom_url_path": "feedback-form"
} }
], ],
"title": "Shared View Request Model", "title": "Shared View Request Model",
@ -24972,9 +24972,9 @@
"$ref": "#/components/schemas/StringOrNull", "$ref": "#/components/schemas/StringOrNull",
"description": "Password to restrict access" "description": "Password to restrict access"
}, },
"custom_path": { "custom_url_path": {
"$ref": "#/components/schemas/StringOrNull", "$ref": "#/components/schemas/StringOrNull",
"description": "Custom url" "description": "Custom url path"
} }
}, },
"x-stoplight": { "x-stoplight": {
@ -26360,6 +26360,14 @@
"owned_by": { "owned_by": {
"$ref": "#/components/schemas/Id", "$ref": "#/components/schemas/Id",
"description": "ID of view owner user" "description": "ID of view owner user"
},
"fk_custom_url_id": {
"$ref": "#/components/schemas/StringOrNull",
"description": "Custom url id of view"
},
"custom_url_path": {
"$ref": "#/components/schemas/StringOrNull",
"description": "Custom url path"
} }
}, },
"required": ["fk_model_id", "show", "title", "type"], "required": ["fk_model_id", "show", "title", "type"],

20
packages/nocodb/src/services/custom-urls.service.ts

@ -0,0 +1,20 @@
import { Injectable } from '@nestjs/common';
import type { NcContext } from '~/interface/config';
import { AppHooksService } from '~/services/app-hooks/app-hooks.service';
import CustomUrl from 'src/models/CustomUrl';
@Injectable()
export class CustomUrlsService {
constructor(private readonly appHooksService: AppHooksService) {}
async checkAvailability(
context: NcContext,
params: Pick<CustomUrl, 'id' | 'custom_path'>,
) {
return await CustomUrl.checkAvailability(context, params);
}
async getOriginalPath(context: NcContext, custom_path: string) {
return await CustomUrl.getOriginUrlByCustomPath(context, custom_path);
}
}

6
packages/nocodb/src/services/views.service.ts

@ -279,9 +279,9 @@ export class ViewsService {
}); });
if (customUrl?.id) { if (customUrl?.id) {
if (param.sharedView.custom_path) { if (param.sharedView.custom_url_path) {
await CustomUrl.update(context, view.fk_custom_url_id, { await CustomUrl.update(context, view.fk_custom_url_id, {
custom_path: param.sharedView.custom_path, custom_path: param.sharedView.custom_url_path,
}); });
} else { } else {
await CustomUrl.delete(context, { id: view.fk_custom_url_id }); await CustomUrl.delete(context, { id: view.fk_custom_url_id });
@ -295,7 +295,7 @@ export class ViewsService {
view_id: view.id, view_id: view.id,
// Todo: add original path // Todo: add original path
original_path: '', original_path: '',
custom_path: param.sharedView.custom_path, custom_path: param.sharedView.custom_url_path,
}); });
} }

Loading…
Cancel
Save