diff --git a/packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue b/packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue
index ef100d2db9..823edbc787 100644
--- a/packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue
+++ b/packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue
@@ -70,9 +70,9 @@ async function changeLockType(type: LockType) {
if (!view.value) return
- if (type === 'personal') {
- // Coming soon
- return message.info(t('msg.toast.futureRelease'))
+ // if default view block the change since it's not allowed
+ if (type === 'personal' && view.value.is_default) {
+ return message.info(t('msg.toast.notAllowedToChangeDefaultView'))
}
try {
view.value.lock_type = type
@@ -306,6 +306,10 @@ const onDelete = async () => {
+
+
+
+
diff --git a/packages/nc-gui/lang/en.json b/packages/nc-gui/lang/en.json
index 74274b11a5..cb6d850470 100644
--- a/packages/nc-gui/lang/en.json
+++ b/packages/nc-gui/lang/en.json
@@ -1806,7 +1806,8 @@
"formEmailSMTP": "Please activate SMTP plugin in App store for enabling email notification",
"collabView": "Successfully Switched to collaborative view",
"lockedView": "Successfully Switched to locked view",
- "futureRelease": "Coming soon!"
+ "futureRelease": "Coming soon!",
+ "notAllowedToChangeDefaultView": "You are not allowed to change the default view"
},
"success": {
"licenseKeyUpdated": "License Key Updated",
diff --git a/packages/nc-gui/lib/enums.ts b/packages/nc-gui/lib/enums.ts
index df97fe0c36..366884de03 100644
--- a/packages/nc-gui/lib/enums.ts
+++ b/packages/nc-gui/lib/enums.ts
@@ -1,3 +1,5 @@
+import { ViewLockType } from 'nocodb-sdk'
+
export { ClientType, IntegrationCategoryType, SyncDataType } from 'nocodb-sdk'
export enum Language {
@@ -50,11 +52,7 @@ export enum NavigateDir {
PREV,
}
-export enum LockType {
- Personal = 'personal',
- Locked = 'locked',
- Collaborative = 'collaborative',
-}
+export { ViewLockType as LockType }
export enum TabType {
TABLE = 'table',
diff --git a/packages/nocodb-sdk/src/lib/enums.ts b/packages/nocodb-sdk/src/lib/enums.ts
index 375cfc6f5f..c02830a779 100644
--- a/packages/nocodb-sdk/src/lib/enums.ts
+++ b/packages/nocodb-sdk/src/lib/enums.ts
@@ -434,3 +434,9 @@ export enum IntegrationCategoryType {
STORAGE = 'storage',
OTHERS = 'others',
}
+
+export enum ViewLockType {
+ Personal = 'personal',
+ Locked = 'locked',
+ Collaborative = 'collaborative',
+}
diff --git a/packages/nocodb/src/controllers/meta-diffs.controller.ts b/packages/nocodb/src/controllers/meta-diffs.controller.ts
index b2abb16d34..98ac24aae6 100644
--- a/packages/nocodb/src/controllers/meta-diffs.controller.ts
+++ b/packages/nocodb/src/controllers/meta-diffs.controller.ts
@@ -1,10 +1,10 @@
-import { Controller, Get, Param, UseGuards } from '@nestjs/common';
+import { Controller, Get, Param, Req, UseGuards } from '@nestjs/common';
import { GlobalGuard } from '~/guards/global/global.guard';
import { MetaDiffsService } from '~/services/meta-diffs.service';
import { Acl } from '~/middlewares/extract-ids/extract-ids.middleware';
import { MetaApiLimiterGuard } from '~/guards/meta-api-limiter.guard';
import { TenantContext } from '~/decorators/tenant-context.decorator';
-import { NcContext } from '~/interface/config';
+import { NcContext, NcRequest } from '~/interface/config';
@Controller()
@UseGuards(MetaApiLimiterGuard, GlobalGuard)
@@ -32,10 +32,12 @@ export class MetaDiffsController {
@TenantContext() context: NcContext,
@Param('baseId') baseId: string,
@Param('sourceId') sourceId: string,
+ @Req() req: NcRequest,
) {
return await this.metaDiffsService.baseMetaDiff(context, {
sourceId,
baseId,
+ user: req.user,
});
}
}
diff --git a/packages/nocodb/src/helpers/populateMeta.ts b/packages/nocodb/src/helpers/populateMeta.ts
index 6e1c22f2e2..d3d900454d 100644
--- a/packages/nocodb/src/helpers/populateMeta.ts
+++ b/packages/nocodb/src/helpers/populateMeta.ts
@@ -3,6 +3,7 @@ import { isVirtualCol, RelationTypes } from 'nocodb-sdk';
import { pluralize, singularize } from 'inflection';
import { isLinksOrLTAR } from 'nocodb-sdk';
import { getUniqueColumnAliasName, getUniqueColumnName } from './getUniqueName';
+import type { UserType } from 'nocodb-sdk';
import type { RollupColumn } from '~/models';
import type LinkToAnotherRecordColumn from '~/models/LinkToAnotherRecordColumn';
import type Source from '~/models/Source';
@@ -205,9 +206,17 @@ export async function extractAndGenerateManyToManyRelations(
export async function populateMeta(
context: NcContext,
- source: Source,
- base: Base,
- logger?: (message: string) => void,
+ {
+ source,
+ base,
+ logger,
+ user,
+ }: {
+ source: Source;
+ base: Base;
+ logger?: (message: string) => void;
+ user: UserType;
+ },
): Promise {
const info = {
type: 'rest',
@@ -347,6 +356,7 @@ export async function populateMeta(
title: table.title,
type: table.type || 'table',
order: table.order,
+ user_id: user.id,
},
);
@@ -489,6 +499,7 @@ export async function populateMeta(
// todo: sanitize
type: ModelTypes.VIEW,
order: table.order,
+ user_id: user.id,
},
);
diff --git a/packages/nocodb/src/meta/migrations/v2/nc_066_personal_view.ts b/packages/nocodb/src/meta/migrations/v2/nc_066_personal_view.ts
index 7dc89aa4dd..8780fc63b0 100644
--- a/packages/nocodb/src/meta/migrations/v2/nc_066_personal_view.ts
+++ b/packages/nocodb/src/meta/migrations/v2/nc_066_personal_view.ts
@@ -3,15 +3,15 @@ import { MetaTable } from '~/utils/globals';
const up = async (knex: Knex) => {
await knex.schema.alterTable(MetaTable.VIEWS, (table) => {
- table.boolean('is_personal').defaultTo(false);
table.string('created_by', 20).index();
+ table.string('owned_by', 20).index();
});
};
const down = async (knex: Knex) => {
await knex.schema.alterTable(MetaTable.SOURCES, (table) => {
- table.dropColumn('is_personal');
table.dropColumn('created_by');
+ table.dropColumn('owned_by');
});
};
diff --git a/packages/nocodb/src/models/Model.ts b/packages/nocodb/src/models/Model.ts
index 8cc813452b..00a4590789 100644
--- a/packages/nocodb/src/models/Model.ts
+++ b/packages/nocodb/src/models/Model.ts
@@ -140,6 +140,7 @@ export default class Model implements TableType {
mm?: BoolType;
type?: ModelTypes;
source_id?: string;
+ user_id: string;
},
ncMeta = Noco.ncMeta,
) {
@@ -199,6 +200,8 @@ export default class Model implements TableType {
type: ViewTypes.GRID,
base_id: baseId,
source_id: sourceId,
+ created_by: model.user_id,
+ owned_by: model.user_id,
},
{
getColumns: async () => insertedColumns,
@@ -1154,7 +1157,7 @@ export default class Model implements TableType {
context: NcContext,
{
modelId,
- userId
+ userId,
}: {
modelId: string;
userId?: string;
diff --git a/packages/nocodb/src/models/View.ts b/packages/nocodb/src/models/View.ts
index 998f39eb9c..9471223a6f 100644
--- a/packages/nocodb/src/models/View.ts
+++ b/packages/nocodb/src/models/View.ts
@@ -74,7 +74,7 @@ export default class View implements ViewType {
type: ViewTypes;
lock_type?: ViewType['lock_type'];
created_by?: string;
- is_personal?: boolean;
+ owned_by?: string;
fk_model_id: string;
model?: Model;
@@ -1263,7 +1263,10 @@ export default class View implements ViewType {
password?: string;
uuid?: string;
meta?: any;
+ owned_by?: string;
+ created_by?: string;
},
+ includeCreatedByAndUpdateBy = false,
ncMeta = Noco.ncMeta,
) {
const updateObj = extractProps(body, [
@@ -1275,6 +1278,7 @@ export default class View implements ViewType {
'password',
'meta',
'uuid',
+ ...(includeCreatedByAndUpdateBy ? ['owned_by', 'created_by'] : [])
]);
const oldView = await this.get(context, viewId, ncMeta);
@@ -1986,6 +1990,8 @@ export default class View implements ViewType {
copy_from_id?: string;
fk_grp_col_id?: string;
calendar_range?: Partial[];
+ created_by: string;
+ owned_by: string;
},
model: {
getColumns: (context: NcContext, ncMeta?) => Promise;
@@ -2002,6 +2008,8 @@ export default class View implements ViewType {
'base_id',
'source_id',
'meta',
+ 'created_by',
+ 'owned_by',
]);
if (!insertObj.order) {
diff --git a/packages/nocodb/src/services/bases.service.ts b/packages/nocodb/src/services/bases.service.ts
index cb7f6e2dfa..4f3c40ffdc 100644
--- a/packages/nocodb/src/services/bases.service.ts
+++ b/packages/nocodb/src/services/bases.service.ts
@@ -282,7 +282,7 @@ export class BasesService {
// populate metadata if existing table
for (const source of await base.getSources()) {
if (process.env.NC_CLOUD !== 'true' && !base.is_meta) {
- const info = await populateMeta(context, source, base);
+ const info = await populateMeta(context, {source, base, user: param.user});
this.appHooksService.emit(AppEvents.APIS_CREATED, {
info,
diff --git a/packages/nocodb/src/services/calendars.service.ts b/packages/nocodb/src/services/calendars.service.ts
index 0e8a5de173..fb4c5e9661 100644
--- a/packages/nocodb/src/services/calendars.service.ts
+++ b/packages/nocodb/src/services/calendars.service.ts
@@ -45,6 +45,8 @@ export class CalendarsService {
type: ViewTypes.CALENDAR,
base_id: model.base_id,
source_id: model.source_id,
+ created_by: param.user.id,
+ owned_by: param.user.id,
},
model,
);
diff --git a/packages/nocodb/src/services/columns.service.ts b/packages/nocodb/src/services/columns.service.ts
index b4de119103..c26e474801 100644
--- a/packages/nocodb/src/services/columns.service.ts
+++ b/packages/nocodb/src/services/columns.service.ts
@@ -3067,6 +3067,7 @@ export class ColumnsService {
base: Base;
reuse?: ReusableParams;
colExtra?: any;
+ user: UserType;
},
) {
validateParams(['parentId', 'childId', 'type'], param.column);
@@ -3369,6 +3370,7 @@ export class ColumnsService {
// todo: sanitize
mm: true,
columns: associateTableCols,
+ user_id: param.user.id,
},
);
diff --git a/packages/nocodb/src/services/forms.service.ts b/packages/nocodb/src/services/forms.service.ts
index 6e409fee6c..29ccca24c9 100644
--- a/packages/nocodb/src/services/forms.service.ts
+++ b/packages/nocodb/src/services/forms.service.ts
@@ -53,6 +53,8 @@ export class FormsService {
type: ViewTypes.FORM,
base_id: model.base_id,
source_id: model.source_id,
+ created_by: param.user.id,
+ owned_by: param.user.id,
},
model,
);
diff --git a/packages/nocodb/src/services/galleries.service.ts b/packages/nocodb/src/services/galleries.service.ts
index 2a093c6d20..24d85ed471 100644
--- a/packages/nocodb/src/services/galleries.service.ts
+++ b/packages/nocodb/src/services/galleries.service.ts
@@ -47,6 +47,8 @@ export class GalleriesService {
type: ViewTypes.GALLERY,
base_id: model.base_id,
source_id: model.source_id,
+ created_by: param.user.id,
+ owned_by: param.user.id,
},
model,
);
diff --git a/packages/nocodb/src/services/grids.service.ts b/packages/nocodb/src/services/grids.service.ts
index 8f141ea29e..3df1b3d8f6 100644
--- a/packages/nocodb/src/services/grids.service.ts
+++ b/packages/nocodb/src/services/grids.service.ts
@@ -37,6 +37,8 @@ export class GridsService {
type: ViewTypes.GRID,
base_id: model.base_id,
source_id: model.source_id,
+ created_by: param.req?.user.id,
+ owned_by: param.req?.user.id,
},
model,
);
diff --git a/packages/nocodb/src/services/kanbans.service.ts b/packages/nocodb/src/services/kanbans.service.ts
index e8ffb835f3..039d68dfca 100644
--- a/packages/nocodb/src/services/kanbans.service.ts
+++ b/packages/nocodb/src/services/kanbans.service.ts
@@ -46,6 +46,8 @@ export class KanbansService {
type: ViewTypes.KANBAN,
base_id: model.base_id,
source_id: model.source_id,
+ owned_by: param.user.id,
+ created_by: param.user.id,
},
model,
);
diff --git a/packages/nocodb/src/services/maps.service.ts b/packages/nocodb/src/services/maps.service.ts
index 3e12393393..a22afe7c64 100644
--- a/packages/nocodb/src/services/maps.service.ts
+++ b/packages/nocodb/src/services/maps.service.ts
@@ -42,6 +42,8 @@ export class MapsService {
type: ViewTypes.MAP,
base_id: model.base_id,
source_id: model.source_id,
+ created_by: param.user.id,
+ owned_by: param.user.id,
},
model,
);
diff --git a/packages/nocodb/src/services/meta-diffs.service.ts b/packages/nocodb/src/services/meta-diffs.service.ts
index 68145e8b06..fd40f37f7a 100644
--- a/packages/nocodb/src/services/meta-diffs.service.ts
+++ b/packages/nocodb/src/services/meta-diffs.service.ts
@@ -8,6 +8,7 @@ import {
UITypes,
} from 'nocodb-sdk';
import { pluralize, singularize } from 'inflection';
+import type { UserType } from 'nocodb-sdk';
import type { LinksColumn, LinkToAnotherRecordColumn } from '~/models';
import type { NcContext } from '~/interface/config';
import { AppHooksService } from '~/services/app-hooks/app-hooks.service';
@@ -647,7 +648,7 @@ export class MetaDiffsService {
async baseMetaDiff(
context: NcContext,
- param: { baseId: string; sourceId: string },
+ param: { baseId: string; sourceId: string; user: UserType },
) {
const base = await Base.getWithInfo(context, param.baseId);
const source = await Source.get(context, param.sourceId);
@@ -662,9 +663,17 @@ export class MetaDiffsService {
async syncBaseMeta(
context: NcContext,
- base: Base,
- source: Source,
- throwOnFail = false,
+ {
+ base,
+ source,
+ throwOnFail = false,
+ user,
+ }: {
+ base: Base;
+ source: Source;
+ throwOnFail?: boolean;
+ user: UserType;
+ },
) {
if (source.is_meta) {
if (throwOnFail) NcError.badRequest('Cannot sync meta source');
@@ -711,6 +720,7 @@ export class MetaDiffsService {
source,
),
type: ModelTypes.TABLE,
+ user_id: user.id,
});
for (const column of columns) {
@@ -738,6 +748,7 @@ export class MetaDiffsService {
table_name: table_name,
title: getTableNameAlias(table_name, base.prefix, source),
type: ModelTypes.VIEW,
+ user_id: user.id,
});
for (const column of columns) {
@@ -909,7 +920,7 @@ export class MetaDiffsService {
async metaDiffSync(context: NcContext, param: { baseId: string; req: any }) {
const base = await Base.getWithInfo(context, param.baseId);
for (const source of base.sources) {
- await this.syncBaseMeta(context, base, source);
+ await this.syncBaseMeta(context, { base, source, user: param.req.user });
}
this.appHooksService.emit(AppEvents.META_DIFF_SYNC, {
@@ -931,7 +942,12 @@ export class MetaDiffsService {
const base = await Base.getWithInfo(context, param.baseId);
const source = await Source.get(context, param.sourceId);
- await this.syncBaseMeta(context, base, source, true);
+ await this.syncBaseMeta(context, {
+ base,
+ source,
+ throwOnFail: true,
+ user: param.req.user,
+ });
this.appHooksService.emit(AppEvents.META_DIFF_SYNC, {
base,
diff --git a/packages/nocodb/src/services/sources.service.ts b/packages/nocodb/src/services/sources.service.ts
index f2551509e7..0802dd5cf5 100644
--- a/packages/nocodb/src/services/sources.service.ts
+++ b/packages/nocodb/src/services/sources.service.ts
@@ -173,7 +173,7 @@ export class SourcesService {
param.logger?.('Populating meta');
- const info = await populateMeta(context, source, base, param.logger);
+ const info = await populateMeta(context, {source, base, logger:param.logger, user: param.req.user});
await populateRollupColumnAndHideLTAR(context, source, base);
diff --git a/packages/nocodb/src/services/tables.service.ts b/packages/nocodb/src/services/tables.service.ts
index 3a8c6af779..93800fcb88 100644
--- a/packages/nocodb/src/services/tables.service.ts
+++ b/packages/nocodb/src/services/tables.service.ts
@@ -10,7 +10,9 @@ import {
ProjectRoles,
RelationTypes,
UITypes,
+ ViewLockType,
} from 'nocodb-sdk';
+import { LockType } from 'nc-gui/lib/enums';
import { MetaDiffsService } from './meta-diffs.service';
import { ColumnsService } from './columns.service';
import type {
@@ -353,9 +355,13 @@ export class TablesService {
);
//await View.list(param.tableId)
- table.views = viewList.filter((table: any) => {
- return Object.keys(param.user?.roles).some(
- (role) => param.user?.roles[role] && !table.disabled[role],
+ table.views = viewList.filter((view: any) => {
+ return (
+ Object.keys(param.user?.roles).some(
+ (role) => param.user?.roles[role] && !view.disabled[role],
+ ) &&
+ (view.lock_type !== ViewLockType.Locked ||
+ view.fk_owned_by === param.user.id)
);
});
diff --git a/packages/nocodb/src/services/views.service.ts b/packages/nocodb/src/services/views.service.ts
index 5d55bded25..7c1f0cc19a 100644
--- a/packages/nocodb/src/services/views.service.ts
+++ b/packages/nocodb/src/services/views.service.ts
@@ -10,6 +10,7 @@ import { AppHooksService } from '~/services/app-hooks/app-hooks.service';
import { validatePayload } from '~/helpers';
import { NcError } from '~/helpers/catchError';
import { Model, ModelRoleVisibility, View } from '~/models';
+import {WorkspaceUser} from "~/ee/models";
// todo: move
async function xcVisibilityMetaGet(
@@ -136,18 +137,72 @@ export class ViewsService {
'swagger.json#/components/schemas/ViewUpdateReq',
param.view,
);
+ const oldView = await View.get(context, param.viewId);
- const view = await View.get(context, param.viewId);
-
- if (!view) {
+ if (!oldView) {
NcError.viewNotFound(param.viewId);
}
- const result = await View.update(context, param.viewId, param.view);
+ let ownedBy = oldView.owned_by;
+ let createdBy = oldView.created_by;
+ let includeCreatedByAndUpdateBy = false;
+
+ // check if the lock_type changing to `personal` and only allow if user is the owner
+ // if the owned_by is not the same as the user, then throw error
+ // if owned_by is empty, then only allow owner of project to change
+ if (
+ param.view.lock_type === 'personal' &&
+ param.view.lock_type !== oldView.lock_type
+ ) {
+ // if owned_by is not empty then check if the user is the owner of the project
+ if (ownedBy && ownedBy !== param.user.id) {
+ NcError.unauthorized('Only owner can change to personal view');
+ }
+
+ // if empty then check if current user is the owner of the project then allow and update the owned_by
+ if (!ownedBy && (param.user as any).base_roles?.[ProjectRoles.OWNER]) {
+ includeCreatedByAndUpdateBy = true;
+ ownedBy = param.user.id;
+ if (!createdBy) {
+ createdBy = param.user.id;
+ }
+ } else if (!ownedBy) {
+ // todo: move to catchError
+ NcError.unauthorized('Only owner can change to personal view');
+ }
+ }
+
+ if(ownedBy && param.view.owned_by && param.user.id === ownedBy) {
+ ownedBy = param.view.owned_by
+
+ // verify if the new owned_by is a valid user who have access to the base/workspace
+ // if not then throw error
+ const baseUser = await BaseUser.get(context,param.view.owned_by, context.base_id);
+
+ if(!baseUser){
+ NcError.badRequest('Invalid user');
+ }
+
+ // // todo: ee only
+ // if(!baseUser) {
+ // const workspace = await WorkspaceUser
+ // }
+ }
+
+ const result = await View.update(
+ context,
+ param.viewId,
+ {
+ ...param.view,
+ owned_by: ownedBy,
+ created_by: createdBy,
+ },
+ includeCreatedByAndUpdateBy,
+ );
this.appHooksService.emit(AppEvents.VIEW_UPDATE, {
view: {
- ...view,
+ ...oldView,
...param.view,
},
user: param.user,