Browse Source

Merge pull request #4696 from nocodb/fix/misc

Miscellaneous bug fixes and enhancements
pull/4706/head
Pranav C 2 years ago committed by GitHub
parent
commit
77fea98c1a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      packages/nc-gui/components.d.ts
  2. 15
      packages/nc-gui/components/dashboard/TreeView.vue
  3. 67
      packages/nc-gui/components/dashboard/settings/data-sources/CreateBase.vue
  4. 6
      packages/nc-gui/components/general/ViewIcon.vue
  5. 12
      packages/nc-gui/components/smartsheet/sidebar/RenameableMenuItem.vue
  6. 31
      packages/nc-gui/components/smartsheet/toolbar/ShareView.vue
  7. 2
      packages/nc-gui/components/smartsheet/toolbar/ViewActions.vue
  8. 1
      packages/nc-gui/composables/useGlobal/types.ts
  9. 13
      packages/nc-gui/composables/useSharedView.ts
  10. 2
      packages/nc-gui/layouts/shared-view.vue
  11. 8
      packages/nc-gui/middleware/auth.global.ts
  12. 70
      packages/nc-gui/pages/index/index/create-external.vue
  13. 16
      packages/nocodb/src/lib/Noco.ts
  14. 19
      packages/nocodb/src/lib/db/sql-client/lib/SqlClientFactory.ts
  15. 19
      packages/nocodb/src/lib/db/sql-client/lib/ee/SqlClientFactoryEE.ts
  16. 11
      packages/nocodb/src/lib/meta/api/dataApis/dataAliasApis.ts
  17. 8
      packages/nocodb/src/lib/meta/api/dataApis/dataApis.ts
  18. 26
      packages/nocodb/src/lib/meta/api/userApi/initStrategies.ts
  19. 81
      packages/nocodb/src/lib/meta/api/userApi/userApis.ts
  20. 3
      packages/nocodb/src/lib/meta/api/utilApis.ts
  21. 1
      packages/nocodb/src/lib/migrations/v2/nc_013_sync_source.ts

3
packages/nc-gui/components.d.ts vendored

@ -90,6 +90,7 @@ declare module '@vue/runtime-core' {
LogosMysqlIcon: typeof import('~icons/logos/mysql-icon')['default']
LogosPostgresql: typeof import('~icons/logos/postgresql')['default']
LogosRedditIcon: typeof import('~icons/logos/reddit-icon')['default']
LogosSnowflakeIcon: typeof import('~icons/logos/snowflake-icon')['default']
LogosSwagger: typeof import('~icons/logos/swagger')['default']
MaterialSymbolsAccountTreeRounded: typeof import('~icons/material-symbols/account-tree-rounded')['default']
MaterialSymbolsArrowCircleLeftRounded: typeof import('~icons/material-symbols/arrow-circle-left-rounded')['default']
@ -142,6 +143,7 @@ declare module '@vue/runtime-core' {
MdiCloseCircleOutline: typeof import('~icons/mdi/close-circle-outline')['default']
MdiCloseThick: typeof import('~icons/mdi/close-thick')['default']
MdiCodeJson: typeof import('~icons/mdi/code-json')['default']
MdiCodeTags: typeof import('~icons/mdi/code-tags')['default']
MdiCog: typeof import('~icons/mdi/cog')['default']
MdiCommentTextOutline: typeof import('~icons/mdi/comment-text-outline')['default']
MdiContentCopy: typeof import('~icons/mdi/content-copy')['default']
@ -226,7 +228,6 @@ declare module '@vue/runtime-core' {
MdiStarOutline: typeof import('~icons/mdi/star-outline')['default']
MdiStorefrontOutline: typeof import('~icons/mdi/storefront-outline')['default']
MdiTable: typeof import('~icons/mdi/table')['default']
MdiTableArrowRight: typeof import('~icons/mdi/table-arrow-right')['default']
MdiTableColumnPlusAfter: typeof import('~icons/mdi/table-column-plus-after')['default']
MdiTableColumnPlusBefore: typeof import('~icons/mdi/table-column-plus-before')['default']
MdiTableLarge: typeof import('~icons/mdi/table-large')['default']

15
packages/nc-gui/components/dashboard/TreeView.vue

@ -18,6 +18,7 @@ import {
ref,
resolveComponent,
useDialog,
useGlobal,
useNuxtApp,
useProject,
useRoute,
@ -46,6 +47,8 @@ const route = useRoute()
const [searchActive, toggleSearchActive] = useToggle()
const { appInfo } = useGlobal()
const toggleDialog = inject(ToggleDialogInj, () => {})
const keys = $ref<Record<string, number>>({})
@ -414,7 +417,11 @@ const setIcon = async (icon: string, table: TableType) => {
MSSQL
</div>
</a-menu-item>
<a-menu-item key="connect-new-source" @click="toggleDialog(true, 'dataSources', ClientType.SNOWFLAKE)">
<a-menu-item
v-if="appInfo.ee"
key="connect-new-source"
@click="toggleDialog(true, 'dataSources', ClientType.SNOWFLAKE)"
>
<div class="color-transition nc-project-menu-item group">
<LogosSnowflakeIcon class="group-hover:text-accent" />
Snowflake
@ -530,7 +537,11 @@ const setIcon = async (icon: string, table: TableType) => {
MSSQL
</div>
</a-menu-item>
<a-menu-item key="connect-new-source" @click="toggleDialog(true, 'dataSources', ClientType.SNOWFLAKE)">
<a-menu-item
v-if="appInfo.ee"
key="connect-new-source"
@click="toggleDialog(true, 'dataSources', ClientType.SNOWFLAKE)"
>
<div class="color-transition nc-project-menu-item group">
<LogosSnowflakeIcon class="group-hover:text-accent" />
Snowflake

67
packages/nc-gui/components/dashboard/settings/data-sources/CreateBase.vue

@ -8,7 +8,7 @@ import {
DefaultConnection,
SQLiteConnection,
SSLUsage,
clientTypes,
clientTypes as _clientTypes,
computed,
extractSdkResponseErrorMsg,
fieldRequiredValidator,
@ -21,6 +21,7 @@ import {
readFile,
ref,
useApi,
useGlobal,
useI18n,
useNuxtApp,
watch,
@ -30,6 +31,8 @@ const { connectionType } = defineProps<{ connectionType: ClientType }>()
const emit = defineEmits(['baseCreated'])
const { appInfo } = useGlobal()
const { project, loadProject } = useProject()
const useForm = Form.useForm
@ -68,23 +71,29 @@ const customFormState = ref<ProjectCreateForm>({
extraParameters: [],
})
const clientTypes = computed(() => {
return _clientTypes.filter((type) => {
return appInfo.value?.ee || type.value !== ClientType.SNOWFLAKE
})
})
const validators = computed(() => {
return {
'title': [
{
required: true,
message: 'Base name is required',
},
projectTitleValidator,
],
'extraParameters': [extraParameterValidator],
'dataSource.client': [fieldRequiredValidator()],
...(formState.dataSource.client === ClientType.SQLITE
? {
let clientValidations: Record<string, any[]> = {
'dataSource.connection.host': [fieldRequiredValidator()],
'dataSource.connection.port': [fieldRequiredValidator()],
'dataSource.connection.user': [fieldRequiredValidator()],
'dataSource.connection.password': [fieldRequiredValidator()],
'dataSource.connection.database': [fieldRequiredValidator()],
}
switch (formState.dataSource.client) {
case ClientType.SQLITE:
clientValidations = {
'dataSource.connection.connection.filename': [fieldRequiredValidator()],
}
: formState.dataSource.client === ClientType.SNOWFLAKE
? {
break
case ClientType.SNOWFLAKE:
clientValidations = {
'dataSource.connection.account': [fieldRequiredValidator()],
'dataSource.connection.username': [fieldRequiredValidator()],
'dataSource.connection.password': [fieldRequiredValidator()],
@ -92,18 +101,24 @@ const validators = computed(() => {
'dataSource.connection.database': [fieldRequiredValidator()],
'dataSource.connection.schema': [fieldRequiredValidator()],
}
: {
'dataSource.connection.host': [fieldRequiredValidator()],
'dataSource.connection.port': [fieldRequiredValidator()],
'dataSource.connection.user': [fieldRequiredValidator()],
'dataSource.connection.password': [fieldRequiredValidator()],
'dataSource.connection.database': [fieldRequiredValidator()],
...([ClientType.PG, ClientType.MSSQL].includes(formState.dataSource.client)
? {
'dataSource.searchPath.0': [fieldRequiredValidator()],
break
case ClientType.PG:
case ClientType.MSSQL:
clientValidations['dataSource.searchPath.0'] = [fieldRequiredValidator()]
break
}
: {}),
}),
return {
'title': [
{
required: true,
message: 'Base name is required',
},
projectTitleValidator,
],
'extraParameters': [extraParameterValidator],
'dataSource.client': [fieldRequiredValidator()],
...clientValidations,
}
})

6
packages/nc-gui/components/general/ViewIcon.vue

@ -1,11 +1,13 @@
<script lang="ts" setup>
import { Icon as IcIcon } from '@iconify/vue'
import type { TableType } from 'nocodb-sdk'
import { viewIcons } from '#imports'
import { toRef, viewIcons } from '#imports'
const { meta: viewMeta } = defineProps<{
const props = defineProps<{
meta: TableType
}>()
const viewMeta = toRef(props, 'meta')
</script>
<template>

12
packages/nc-gui/components/smartsheet/sidebar/RenameableMenuItem.vue

@ -168,9 +168,9 @@ function onStopEdit() {
<div v-e="['a:view:open', { view: vModel.type }]" class="text-xs flex items-center w-full gap-2" data-testid="view-item">
<div class="flex w-auto min-w-5" :data-testid="`view-sidebar-drag-handle-${vModel.alias || vModel.title}`">
<a-dropdown :trigger="['click']" @click.stop>
<component :is="isUIAllowed('tableIconCustomisation') ? Tooltip : 'div'">
<component :is="isUIAllowed('viewIconCustomisation') ? Tooltip : 'div'">
<GeneralViewIcon :meta="props.view" class="nc-view-icon"></GeneralViewIcon>
<template v-if="isUIAllowed('tableIconCustomisation')" #title>Change icon</template>
<template v-if="isUIAllowed('viewIconCustomisation')" #title>Change icon</template>
</component>
<template v-if="isUIAllowed('viewIconCustomisation')" #overlay>
@ -179,7 +179,13 @@ function onStopEdit() {
</a-dropdown>
</div>
<a-input v-if="isEditing" :ref="focusInput" v-model:value="vModel.title" @blur="onCancel" @keydown="onKeyDown($event)" />
<a-input
v-if="isEditing"
:ref="focusInput"
v-model:value="vModel.title"
@blur="onCancel"
@keydown.stop="onKeyDown($event)"
/>
<div v-else>
<LazyGeneralTruncateText>{{ vModel.alias || vModel.title }}</LazyGeneralTruncateText>

31
packages/nc-gui/components/smartsheet/toolbar/ShareView.vue

@ -196,6 +196,26 @@ watch(passwordProtected, (value) => {
const { locale } = useI18n()
const isRtl = computed(() => isRtlLang(locale.value as any))
const iframeCode = computed(() => {
if (!sharedViewUrl.value) return
return `<iframe class="nc-embed"
"src="${sharedViewUrl.value}?embed"
frameborder="0"
width="100%"
height="700"
style="background: transparent; border: 1px solid #ddd"/>`
})
const copyIframeCode = async () => {
if (iframeCode.value) {
await copy(iframeCode.value)
// Copied to clipboard
message.success(t('msg.info.copiedToClipboard'))
}
}
</script>
<template>
@ -228,7 +248,7 @@ const isRtl = computed(() => isRtlLang(locale.value as any))
data-testid="nc-modal-share-view__link"
class="share-link-box !bg-primary !bg-opacity-5 ring-1 ring-accent ring-opacity-100"
>
<div class="flex-1 h-min text-xs">{{ sharedViewUrl }}</div>
<div class="flex-1 h-min text-xs text-gray-500">{{ sharedViewUrl }}</div>
<a v-e="['c:view:share:open-url']" :href="sharedViewUrl" target="_blank">
<MdiOpenInNew class="text-sm text-gray-500 mt-2" />
@ -237,6 +257,13 @@ const isRtl = computed(() => isRtlLang(locale.value as any))
<MdiContentCopy v-e="['c:view:share:copy-url']" class="text-gray-500 text-sm cursor-pointer" @click="copyLink" />
</div>
<div
class="flex gap-1 items-center pb-1 text-gray-500 cursor-pointer font-weight-medium mb-2 mt-4 pl-1"
@click="copyIframeCode"
>
<MdiCodeTags class="text-gray-500" /> Embed this view in your site
</div>
<div class="px-1 mt-2 flex flex-col gap-3">
<!-- todo: i18n -->
<div class="text-gray-500 border-b-1">Options</div>
@ -352,7 +379,7 @@ const isRtl = computed(() => isRtlLang(locale.value as any))
<style scoped>
.share-link-box {
@apply flex p-2 w-full items-center items-center gap-1 bg-gray-100 rounded;
@apply flex p-2 w-full items-center items-center gap-2 bg-gray-100 rounded;
}
:deep(.ant-collapse-header) {

2
packages/nc-gui/components/smartsheet/toolbar/ViewActions.vue

@ -95,7 +95,7 @@ useMenuCloseOnEsc(open)
<GeneralViewIcon :meta="selectedView"></GeneralViewIcon>
<span class="!text-sm font-weight-normal">
<GeneralTruncateText>{{ selectedView?.title }}</GeneralTruncateText>
<GeneralTruncateText :key="selectedView?.title">{{ selectedView?.title }}</GeneralTruncateText>
</span>
<component :is="Icon" class="text-gray-500" :class="`nc-icon-${selectedView?.lock_type}`" />

1
packages/nc-gui/composables/useGlobal/types.ts

@ -25,6 +25,7 @@ export interface AppInfo {
teleEnabled: boolean
type: string
version: string
ee?: boolean
}
export interface StoredState {

13
packages/nc-gui/composables/useSharedView.ts

@ -17,7 +17,7 @@ export function useSharedView() {
const { appInfo } = $(useGlobal())
const { loadProject } = useProject()
const { project } = useProject()
const appInfoDefaultLimit = appInfo.defaultLimit || 25
@ -77,7 +77,16 @@ export function useSharedView() {
await setMeta(viewMeta.model)
await loadProject(true, viewMeta.project_id)
// if project is not defined then set it with an object containing base
if (!project.value?.bases)
project.value = {
bases: [
{
id: viewMeta.base_id,
type: viewMeta.client,
},
],
}
const relatedMetas = { ...viewMeta.relatedMetas }
Object.keys(relatedMetas).forEach((key) => setMeta(relatedMetas[key]))

2
packages/nc-gui/layouts/shared-view.vue

@ -59,7 +59,7 @@ export default {
</template>
<div v-else class="text-xl font-semibold truncate text-white nc-shared-view-title flex gap-2 items-center">
<GeneralViewIcon class="!text-xl" :meta="sharedView" />
<GeneralViewIcon v-if="sharedView" class="!text-xl" :meta="sharedView" />
{{ sharedView?.title }}
</div>
</div>

8
packages/nc-gui/middleware/auth.global.ts

@ -1,6 +1,6 @@
import type { Api } from 'nocodb-sdk'
import type { Actions } from '~/composables/useGlobal/types'
import { defineNuxtRouteMiddleware, message, navigateTo, useApi, useGlobal, useRoles } from '#imports'
import { defineNuxtRouteMiddleware, extractSdkResponseErrorMsg, message, navigateTo, useApi, useGlobal, useRoles } from '#imports'
/**
* Global auth middleware
@ -98,10 +98,8 @@ async function tryGoogleAuth(api: Api<any>, signIn: Actions['signIn']) {
)
signIn(token)
} catch (e: any) {
if (e.response && e.response.data && e.response.data.msg) {
message.error({ content: e.response.data.msg })
}
} catch (e) {
message.error({ content: await extractSdkResponseErrorMsg(e) })
}
const newURL = window.location.href.split('?')[0]

70
packages/nc-gui/pages/index/index/create-external.vue

@ -7,7 +7,7 @@ import {
Form,
Modal,
SSLUsage,
clientTypes,
clientTypes as _clientTypes,
computed,
extractSdkResponseErrorMsg,
fieldRequiredValidator,
@ -28,6 +28,8 @@ import {
watch,
} from '#imports'
const { appInfo } = useGlobal()
const useForm = Form.useForm
const testSuccess = ref(false)
@ -64,23 +66,29 @@ const customFormState = ref<ProjectCreateForm>({
extraParameters: [],
})
const clientTypes = computed(() => {
return _clientTypes.filter((type) => {
return appInfo.value?.ee || type.value !== ClientType.SNOWFLAKE
})
})
const validators = computed(() => {
return {
'title': [
{
required: true,
message: 'Project name is required',
},
projectTitleValidator,
],
'extraParameters': [extraParameterValidator],
'dataSource.client': [fieldRequiredValidator()],
...(formState.dataSource.client === ClientType.SQLITE
? {
let clientValidations: Record<string, any[]> = {
'dataSource.connection.host': [fieldRequiredValidator()],
'dataSource.connection.port': [fieldRequiredValidator()],
'dataSource.connection.user': [fieldRequiredValidator()],
'dataSource.connection.password': [fieldRequiredValidator()],
'dataSource.connection.database': [fieldRequiredValidator()],
}
switch (formState.dataSource.client) {
case ClientType.SQLITE:
clientValidations = {
'dataSource.connection.connection.filename': [fieldRequiredValidator()],
}
: formState.dataSource.client === ClientType.SNOWFLAKE
? {
break
case ClientType.SNOWFLAKE:
clientValidations = {
'dataSource.connection.account': [fieldRequiredValidator()],
'dataSource.connection.username': [fieldRequiredValidator()],
'dataSource.connection.password': [fieldRequiredValidator()],
@ -88,18 +96,24 @@ const validators = computed(() => {
'dataSource.connection.database': [fieldRequiredValidator()],
'dataSource.connection.schema': [fieldRequiredValidator()],
}
: {
'dataSource.connection.host': [fieldRequiredValidator()],
'dataSource.connection.port': [fieldRequiredValidator()],
'dataSource.connection.user': [fieldRequiredValidator()],
'dataSource.connection.password': [fieldRequiredValidator()],
'dataSource.connection.database': [fieldRequiredValidator()],
...([ClientType.PG, ClientType.MSSQL].includes(formState.dataSource.client)
? {
'dataSource.searchPath.0': [fieldRequiredValidator()],
break
case ClientType.PG:
case ClientType.MSSQL:
clientValidations['dataSource.searchPath.0'] = [fieldRequiredValidator()]
break
}
: {}),
}),
return {
'title': [
{
required: true,
message: 'Project name is required',
},
projectTitleValidator,
],
'extraParameters': [extraParameterValidator],
'dataSource.client': [fieldRequiredValidator()],
...clientValidations,
}
})
@ -547,7 +561,9 @@ onMounted(async () => {
</div>
</div>
<a-button type="dashed" class="w-full caption mt-2" @click="addNewParam">
<div class="flex items-center justify-center"><MdiPlus /></div>
<div class="flex items-center justify-center">
<MdiPlus />
</div>
</a-button>
</a-card>
</a-form-item>

16
packages/nocodb/src/lib/Noco.ts

@ -16,7 +16,9 @@ import requestIp from 'request-ip';
import { v4 as uuidv4 } from 'uuid';
import { NcConfig } from '../interface/config';
import { NC_LICENSE_KEY } from './constants';
import Migrator from './db/sql-migrator/lib/KnexMigrator';
import Store from './models/Store';
import NcConfigFactory from './utils/NcConfigFactory';
import { Tele } from 'nc-help';
@ -53,6 +55,7 @@ const NcProjectBuilder = process.env.EE
export default class Noco {
private static _this: Noco;
private static ee: boolean;
public static get dashboardUrl(): string {
let siteUrl = `http://localhost:${process.env.PORT || 8080}`;
@ -192,6 +195,7 @@ export default class Noco {
}
await Noco._ncMeta.metaInit();
await Noco.loadEEState();
await this.initJwt();
await initAdminFromEnv();
@ -542,4 +546,16 @@ export default class Noco {
public static getConfig(): NcConfig {
return Noco.config;
}
public static isEE(): boolean {
return Noco.ee;
}
public static async loadEEState(): Promise<boolean> {
try {
return (Noco.ee = !!(await Store.get(NC_LICENSE_KEY)));
} catch {
return (Noco.ee = false);
}
}
}

19
packages/nocodb/src/lib/db/sql-client/lib/SqlClientFactory.ts

@ -1,3 +1,5 @@
import Noco from '../../../Noco';
import SqlClientFactoryEE from './ee/SqlClientFactoryEE';
import MySqlClient from './mysql/MysqlClient';
import MssqlClient from './mssql/MssqlClient';
import OracleClient from './oracle/OracleClient';
@ -6,10 +8,8 @@ import PgClient from './pg/PgClient';
import YugabyteClient from './pg/YugabyteClient';
import TidbClient from './mysql/TidbClient';
import VitessClient from './mysql/VitessClient';
import SfClient from './snowflake/SnowflakeClient';
import { SnowflakeClient } from 'nc-help';
class SqlClientFactory {
export class SqlClientFactory {
static create(connectionConfig) {
connectionConfig.meta = connectionConfig.meta || {};
connectionConfig.pool = connectionConfig.pool || { min: 0, max: 5 };
@ -33,13 +33,18 @@ class SqlClientFactory {
if (connectionConfig.meta.dbtype === 'yugabyte')
return new YugabyteClient(connectionConfig);
return new PgClient(connectionConfig);
} else if (connectionConfig.client === 'snowflake') {
connectionConfig.client = SnowflakeClient;
return new SfClient(connectionConfig);
}
throw new Error('Database not supported');
}
}
export default SqlClientFactory;
export default class {
static create(connectionConfig) {
if (Noco.isEE()) {
return SqlClientFactoryEE.create(connectionConfig);
}
return SqlClientFactory.create(connectionConfig);
}
}

19
packages/nocodb/src/lib/db/sql-client/lib/ee/SqlClientFactoryEE.ts

@ -0,0 +1,19 @@
import { SqlClientFactory } from '../SqlClientFactory';
import SfClient from '../snowflake/SnowflakeClient';
import { SnowflakeClient } from 'nc-help';
class SqlClientFactoryEE {
static create(connectionConfig) {
connectionConfig.meta = connectionConfig.meta || {};
connectionConfig.pool = connectionConfig.pool || { min: 0, max: 5 };
connectionConfig.meta.dbtype = connectionConfig.meta.dbtype || '';
if (connectionConfig.client === 'snowflake') {
connectionConfig.client = SnowflakeClient;
return new SfClient(connectionConfig);
}
return SqlClientFactory.create(connectionConfig);
}
}
export default SqlClientFactoryEE;

11
packages/nocodb/src/lib/meta/api/dataApis/dataAliasApis.ts

@ -124,8 +124,10 @@ async function getDataList(model, view: View, req) {
);
count = await baseModel.count(listArgs);
} catch (e) {
// show empty result instead of throwing error here
// e.g. search some text in a numeric field
console.log(e);
NcError.internalServerError(
'Internal Server Error, check server log for more details'
);
}
return new PagedResponseImpl(data, {
@ -281,8 +283,9 @@ async function getGroupedDataList(model, view: View, req) {
});
} catch (e) {
console.log(e);
// show empty result instead of throwing error here
// e.g. search some text in a numeric field
NcError.internalServerError(
'Internal Server Error, check server log for more details'
);
}
return data;
}

8
packages/nocodb/src/lib/meta/api/dataApis/dataApis.ts

@ -328,7 +328,9 @@ async function dataRead(req: Request, res: Response, next) {
);
} catch (e) {
console.log(e);
res.status(500).json({ msg: e.message });
NcError.internalServerError(
'Internal Server Error, check server log for more details'
);
}
}
@ -465,6 +467,10 @@ async function getDataList(model, view: View, req) {
} catch (e) {
// show empty result instead of throwing error here
// e.g. search some text in a numeric field
console.log(e);
NcError.internalServerError(
'Internal Server Error, check server log for more details'
);
}
return new PagedResponseImpl(data, {

26
packages/nocodb/src/lib/meta/api/userApi/initStrategies.ts

@ -7,7 +7,6 @@ import passport from 'passport';
import passportJWT from 'passport-jwt';
import { Strategy as AuthTokenStrategy } from 'passport-auth-token';
import { Strategy as GoogleStrategy } from 'passport-google-oauth20';
import { randomTokenString } from '../../helpers/stringHelpers';
const PassportLocalStrategy = require('passport-local').Strategy;
const ExtractJwt = passportJWT.ExtractJwt;
@ -24,6 +23,7 @@ import { CacheGetType, CacheScope } from '../../../utils/globals';
import ApiToken from '../../../models/ApiToken';
import Noco from '../../../Noco';
import Plugin from '../../../models/Plugin';
import { registerNewUserIfAllowed } from './userApis';
export function initStrategies(router): void {
passport.use(
@ -284,6 +284,8 @@ export function initStrategies(router): void {
User.getByEmail(email)
.then(async (user) => {
if (user) {
// if project id defined extract project level roles
if (req.ncProjectId) {
ProjectUser.get(req.ncProjectId, user.id)
.then(async (projectUser) => {
@ -296,30 +298,22 @@ export function initStrategies(router): void {
})
.catch((e) => done(e));
} else {
// const roles = projectUser?.roles ? JSON.parse(projectUser.roles) : {guest: true};
if (user) {
return done(null, user);
} else {
let roles = 'editor';
if (!(await User.isFirst())) {
roles = 'owner';
}
if (roles === 'editor') {
return done(new Error('User not found'));
}
// if user not found create new user if allowed
// or return error
} else {
const salt = await promisify(bcrypt.genSalt)(10);
user = await await User.insert({
const user = await registerNewUserIfAllowed({
firstname: null,
lastname: null,
email_verification_token: null,
email: profile.emails[0].value,
password: '',
salt,
roles,
email_verified: true,
token_version: randomTokenString(),
});
return done(null, user);
}
}
})
.catch((err) => {
return done(err);

81
packages/nocodb/src/lib/meta/api/userApi/userApis.ts

@ -25,6 +25,58 @@ import Noco from '../../../Noco';
import { genJwt } from './helpers';
import { randomTokenString } from '../../helpers/stringHelpers';
export async function registerNewUserIfAllowed({
firstname,
lastname,
email,
salt,
password,
email_verification_token,
}: {
firstname;
lastname;
email: string;
salt: any;
password;
email_verification_token;
}) {
let roles: string = OrgUserRoles.CREATOR;
if (await User.isFirst()) {
roles = `${OrgUserRoles.CREATOR},${OrgUserRoles.SUPER_ADMIN}`;
// todo: update in nc_store
// roles = 'owner,creator,editor'
Tele.emit('evt', {
evt_type: 'project:invite',
count: 1,
});
} else {
let settings: { invite_only_signup?: boolean } = {};
try {
settings = JSON.parse((await Store.get(NC_APP_SETTINGS))?.value);
} catch {}
if (settings?.invite_only_signup) {
NcError.badRequest('Not allowed to signup, contact super admin.');
} else {
roles = OrgUserRoles.VIEWER;
}
}
const token_version = randomTokenString();
return await User.insert({
firstname,
lastname,
email,
salt,
password,
email_verification_token,
roles,
token_version,
});
}
export async function signup(req: Request, res: Response<TableType>) {
const {
email: _email,
@ -88,40 +140,13 @@ export async function signup(req: Request, res: Response<TableType>) {
NcError.badRequest('User already exist');
}
} else {
let roles: string = OrgUserRoles.CREATOR;
if (await User.isFirst()) {
roles = `${OrgUserRoles.CREATOR},${OrgUserRoles.SUPER_ADMIN}`;
// todo: update in nc_store
// roles = 'owner,creator,editor'
Tele.emit('evt', {
evt_type: 'project:invite',
count: 1,
});
} else {
let settings: { invite_only_signup?: boolean } = {};
try {
settings = JSON.parse((await Store.get(NC_APP_SETTINGS))?.value);
} catch {}
if (settings?.invite_only_signup) {
NcError.badRequest('Not allowed to signup, contact super admin.');
} else {
roles = OrgUserRoles.VIEWER;
}
}
const token_version = randomTokenString();
await User.insert({
await registerNewUserIfAllowed({
firstname,
lastname,
email,
salt,
password,
email_verification_token,
roles,
token_version,
});
}
user = await User.getByEmail(email);

3
packages/nocodb/src/lib/meta/api/utilApis.ts

@ -3,7 +3,9 @@ import { Request, Response } from 'express';
import { compareVersions, validate } from 'compare-versions';
import { ViewTypes } from 'nocodb-sdk';
import { NC_LICENSE_KEY } from '../../constants';
import Project from '../../models/Project';
import Store from '../../models/Store';
import Noco from '../../Noco';
import NcConnectionMgrv2 from '../../utils/common/NcConnectionMgrv2';
import { MetaTable } from '../../utils/globals';
@ -55,6 +57,7 @@ export async function appInfo(req: Request, res: Response) {
ncMin: !!process.env.NC_MIN,
teleEnabled: !process.env.NC_DISABLE_TELE,
ncSiteUrl: (req as any).ncSiteUrl,
ee: !!(await Store.get(NC_LICENSE_KEY)),
};
res.json(result);

1
packages/nocodb/src/lib/migrations/v2/nc_013_sync_source.ts

@ -38,6 +38,7 @@ const up = async (knex: Knex) => {
};
const down = async (knex) => {
await knex.schema.dropTable(MetaTable.SYNC_LOGS);
await knex.schema.dropTable(MetaTable.SYNC_SOURCE);
};

Loading…
Cancel
Save