mirror of
https://github.com/plankanban/planka.git
synced 2025-07-18 12:49:43 +02:00
feat: Use environment variables for default admin configuration
This commit is contained in:
parent
2dfa79801f
commit
e59535b9b4
20 changed files with 224 additions and 121 deletions
|
@ -1,4 +1,5 @@
|
|||
import { dequal } from 'dequal';
|
||||
import omit from 'lodash/omit';
|
||||
import pickBy from 'lodash/pickBy';
|
||||
import React, { useCallback, useMemo, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
@ -9,7 +10,7 @@ import { useForm } from '../../hooks';
|
|||
|
||||
import styles from './UserInformationEdit.module.scss';
|
||||
|
||||
const UserInformationEdit = React.memo(({ defaultData, onUpdate }) => {
|
||||
const UserInformationEdit = React.memo(({ defaultData, isNameEditable, onUpdate }) => {
|
||||
const [t] = useTranslation();
|
||||
|
||||
const [data, handleFieldChange] = useForm(() => ({
|
||||
|
@ -32,13 +33,17 @@ const UserInformationEdit = React.memo(({ defaultData, onUpdate }) => {
|
|||
const nameField = useRef(null);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
if (!cleanData.name) {
|
||||
nameField.current.select();
|
||||
return;
|
||||
}
|
||||
if (isNameEditable) {
|
||||
if (!cleanData.name) {
|
||||
nameField.current.select();
|
||||
return;
|
||||
}
|
||||
|
||||
onUpdate(cleanData);
|
||||
}, [onUpdate, cleanData]);
|
||||
onUpdate(cleanData);
|
||||
} else {
|
||||
onUpdate(omit(cleanData, 'name'));
|
||||
}
|
||||
}, [isNameEditable, onUpdate, cleanData]);
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit}>
|
||||
|
@ -48,6 +53,7 @@ const UserInformationEdit = React.memo(({ defaultData, onUpdate }) => {
|
|||
ref={nameField}
|
||||
name="name"
|
||||
value={data.name}
|
||||
disabled={!isNameEditable}
|
||||
className={styles.field}
|
||||
onChange={handleFieldChange}
|
||||
/>
|
||||
|
@ -74,6 +80,7 @@ const UserInformationEdit = React.memo(({ defaultData, onUpdate }) => {
|
|||
|
||||
UserInformationEdit.propTypes = {
|
||||
defaultData: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||
isNameEditable: PropTypes.bool.isRequired,
|
||||
onUpdate: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
|
|
|
@ -5,33 +5,40 @@ import { Popup } from '../lib/custom-ui';
|
|||
|
||||
import UserInformationEdit from './UserInformationEdit';
|
||||
|
||||
const UserInformationEditStep = React.memo(({ defaultData, onUpdate, onBack, onClose }) => {
|
||||
const [t] = useTranslation();
|
||||
const UserInformationEditStep = React.memo(
|
||||
({ defaultData, isNameEditable, onUpdate, onBack, onClose }) => {
|
||||
const [t] = useTranslation();
|
||||
|
||||
const handleUpdate = useCallback(
|
||||
(data) => {
|
||||
onUpdate(data);
|
||||
onClose();
|
||||
},
|
||||
[onUpdate, onClose],
|
||||
);
|
||||
const handleUpdate = useCallback(
|
||||
(data) => {
|
||||
onUpdate(data);
|
||||
onClose();
|
||||
},
|
||||
[onUpdate, onClose],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Popup.Header onBack={onBack}>
|
||||
{t('common.editInformation', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Popup.Header>
|
||||
<Popup.Content>
|
||||
<UserInformationEdit defaultData={defaultData} onUpdate={handleUpdate} />
|
||||
</Popup.Content>
|
||||
</>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<Popup.Header onBack={onBack}>
|
||||
{t('common.editInformation', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Popup.Header>
|
||||
<Popup.Content>
|
||||
<UserInformationEdit
|
||||
defaultData={defaultData}
|
||||
isNameEditable={isNameEditable}
|
||||
onUpdate={handleUpdate}
|
||||
/>
|
||||
</Popup.Content>
|
||||
</>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
UserInformationEditStep.propTypes = {
|
||||
defaultData: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||
isNameEditable: PropTypes.bool.isRequired,
|
||||
onUpdate: PropTypes.func.isRequired,
|
||||
onBack: PropTypes.func,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
|
|
|
@ -23,6 +23,7 @@ const AccountPane = React.memo(
|
|||
phone,
|
||||
organization,
|
||||
language,
|
||||
isLocked,
|
||||
isAvatarUpdating,
|
||||
usernameUpdateForm,
|
||||
emailUpdateForm,
|
||||
|
@ -74,6 +75,7 @@ const AccountPane = React.memo(
|
|||
phone,
|
||||
organization,
|
||||
}}
|
||||
isNameEditable={!isLocked}
|
||||
onUpdate={onUpdate}
|
||||
/>
|
||||
<Divider horizontal section>
|
||||
|
@ -102,63 +104,67 @@ const AccountPane = React.memo(
|
|||
value={language || 'auto'}
|
||||
onChange={handleLanguageChange}
|
||||
/>
|
||||
<Divider horizontal section>
|
||||
<Header as="h4">
|
||||
{t('common.authentication', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Header>
|
||||
</Divider>
|
||||
<div className={styles.action}>
|
||||
<UserUsernameEditPopup
|
||||
usePasswordConfirmation
|
||||
defaultData={usernameUpdateForm.data}
|
||||
username={username}
|
||||
isSubmitting={usernameUpdateForm.isSubmitting}
|
||||
error={usernameUpdateForm.error}
|
||||
onUpdate={onUsernameUpdate}
|
||||
onMessageDismiss={onUsernameUpdateMessageDismiss}
|
||||
>
|
||||
<Button className={styles.actionButton}>
|
||||
{t('action.editUsername', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Button>
|
||||
</UserUsernameEditPopup>
|
||||
</div>
|
||||
<div className={styles.action}>
|
||||
<UserEmailEditPopup
|
||||
usePasswordConfirmation
|
||||
defaultData={emailUpdateForm.data}
|
||||
email={email}
|
||||
isSubmitting={emailUpdateForm.isSubmitting}
|
||||
error={emailUpdateForm.error}
|
||||
onUpdate={onEmailUpdate}
|
||||
onMessageDismiss={onEmailUpdateMessageDismiss}
|
||||
>
|
||||
<Button className={styles.actionButton}>
|
||||
{t('action.editEmail', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Button>
|
||||
</UserEmailEditPopup>
|
||||
</div>
|
||||
<div className={styles.action}>
|
||||
<UserPasswordEditPopup
|
||||
usePasswordConfirmation
|
||||
defaultData={passwordUpdateForm.data}
|
||||
isSubmitting={passwordUpdateForm.isSubmitting}
|
||||
error={passwordUpdateForm.error}
|
||||
onUpdate={onPasswordUpdate}
|
||||
onMessageDismiss={onPasswordUpdateMessageDismiss}
|
||||
>
|
||||
<Button className={styles.actionButton}>
|
||||
{t('action.editPassword', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Button>
|
||||
</UserPasswordEditPopup>
|
||||
</div>
|
||||
{!isLocked && (
|
||||
<>
|
||||
<Divider horizontal section>
|
||||
<Header as="h4">
|
||||
{t('common.authentication', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Header>
|
||||
</Divider>
|
||||
<div className={styles.action}>
|
||||
<UserUsernameEditPopup
|
||||
usePasswordConfirmation
|
||||
defaultData={usernameUpdateForm.data}
|
||||
username={username}
|
||||
isSubmitting={usernameUpdateForm.isSubmitting}
|
||||
error={usernameUpdateForm.error}
|
||||
onUpdate={onUsernameUpdate}
|
||||
onMessageDismiss={onUsernameUpdateMessageDismiss}
|
||||
>
|
||||
<Button className={styles.actionButton}>
|
||||
{t('action.editUsername', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Button>
|
||||
</UserUsernameEditPopup>
|
||||
</div>
|
||||
<div className={styles.action}>
|
||||
<UserEmailEditPopup
|
||||
usePasswordConfirmation
|
||||
defaultData={emailUpdateForm.data}
|
||||
email={email}
|
||||
isSubmitting={emailUpdateForm.isSubmitting}
|
||||
error={emailUpdateForm.error}
|
||||
onUpdate={onEmailUpdate}
|
||||
onMessageDismiss={onEmailUpdateMessageDismiss}
|
||||
>
|
||||
<Button className={styles.actionButton}>
|
||||
{t('action.editEmail', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Button>
|
||||
</UserEmailEditPopup>
|
||||
</div>
|
||||
<div className={styles.action}>
|
||||
<UserPasswordEditPopup
|
||||
usePasswordConfirmation
|
||||
defaultData={passwordUpdateForm.data}
|
||||
isSubmitting={passwordUpdateForm.isSubmitting}
|
||||
error={passwordUpdateForm.error}
|
||||
onUpdate={onPasswordUpdate}
|
||||
onMessageDismiss={onPasswordUpdateMessageDismiss}
|
||||
>
|
||||
<Button className={styles.actionButton}>
|
||||
{t('action.editPassword', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Button>
|
||||
</UserPasswordEditPopup>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Tab.Pane>
|
||||
);
|
||||
},
|
||||
|
@ -172,6 +178,7 @@ AccountPane.propTypes = {
|
|||
phone: PropTypes.string,
|
||||
organization: PropTypes.string,
|
||||
language: PropTypes.string,
|
||||
isLocked: PropTypes.bool.isRequired,
|
||||
isAvatarUpdating: PropTypes.bool.isRequired,
|
||||
/* eslint-disable react/forbid-prop-types */
|
||||
usernameUpdateForm: PropTypes.object.isRequired,
|
||||
|
|
|
@ -16,6 +16,7 @@ const UserSettingsModal = React.memo(
|
|||
phone,
|
||||
organization,
|
||||
language,
|
||||
isLocked,
|
||||
subscribeToOwnCards,
|
||||
isAvatarUpdating,
|
||||
usernameUpdateForm,
|
||||
|
@ -48,6 +49,7 @@ const UserSettingsModal = React.memo(
|
|||
phone={phone}
|
||||
organization={organization}
|
||||
language={language}
|
||||
isLocked={isLocked}
|
||||
isAvatarUpdating={isAvatarUpdating}
|
||||
usernameUpdateForm={usernameUpdateForm}
|
||||
emailUpdateForm={emailUpdateForm}
|
||||
|
@ -104,6 +106,7 @@ UserSettingsModal.propTypes = {
|
|||
phone: PropTypes.string,
|
||||
organization: PropTypes.string,
|
||||
language: PropTypes.string,
|
||||
isLocked: PropTypes.bool.isRequired,
|
||||
subscribeToOwnCards: PropTypes.bool.isRequired,
|
||||
isAvatarUpdating: PropTypes.bool.isRequired,
|
||||
/* eslint-disable react/forbid-prop-types */
|
||||
|
|
|
@ -64,6 +64,7 @@ const ActionsStep = React.memo(
|
|||
return (
|
||||
<UserInformationEditStep
|
||||
defaultData={pick(user, ['name', 'phone', 'organization'])}
|
||||
isNameEditable={!user.isLocked}
|
||||
onUpdate={onUpdate}
|
||||
onBack={handleBack}
|
||||
onClose={onClose}
|
||||
|
@ -135,26 +136,30 @@ const ActionsStep = React.memo(
|
|||
context: 'title',
|
||||
})}
|
||||
</Menu.Item>
|
||||
<Menu.Item className={styles.menuItem} onClick={handleEditUsernameClick}>
|
||||
{t('action.editUsername', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Menu.Item>
|
||||
<Menu.Item className={styles.menuItem} onClick={handleEditEmailClick}>
|
||||
{t('action.editEmail', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Menu.Item>
|
||||
<Menu.Item className={styles.menuItem} onClick={handleEditPasswordClick}>
|
||||
{t('action.editPassword', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Menu.Item>
|
||||
<Menu.Item className={styles.menuItem} onClick={handleDeleteClick}>
|
||||
{t('action.deleteUser', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Menu.Item>
|
||||
{!user.isLocked && (
|
||||
<>
|
||||
<Menu.Item className={styles.menuItem} onClick={handleEditUsernameClick}>
|
||||
{t('action.editUsername', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Menu.Item>
|
||||
<Menu.Item className={styles.menuItem} onClick={handleEditEmailClick}>
|
||||
{t('action.editEmail', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Menu.Item>
|
||||
<Menu.Item className={styles.menuItem} onClick={handleEditPasswordClick}>
|
||||
{t('action.editPassword', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Menu.Item>
|
||||
<Menu.Item className={styles.menuItem} onClick={handleDeleteClick}>
|
||||
{t('action.deleteUser', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Menu.Item>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
</Popup.Content>
|
||||
</>
|
||||
|
|
|
@ -17,6 +17,7 @@ const Item = React.memo(
|
|||
organization,
|
||||
phone,
|
||||
isAdmin,
|
||||
isLocked,
|
||||
emailUpdateForm,
|
||||
passwordUpdateForm,
|
||||
usernameUpdateForm,
|
||||
|
@ -46,7 +47,7 @@ const Item = React.memo(
|
|||
<Table.Cell>{username || '-'}</Table.Cell>
|
||||
<Table.Cell>{email}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Radio toggle checked={isAdmin} onChange={handleIsAdminChange} />
|
||||
<Radio toggle checked={isAdmin} disabled={isLocked} onChange={handleIsAdminChange} />
|
||||
</Table.Cell>
|
||||
<Table.Cell textAlign="right">
|
||||
<ActionsPopup
|
||||
|
@ -57,6 +58,7 @@ const Item = React.memo(
|
|||
organization,
|
||||
phone,
|
||||
isAdmin,
|
||||
isLocked,
|
||||
emailUpdateForm,
|
||||
passwordUpdateForm,
|
||||
usernameUpdateForm,
|
||||
|
@ -88,6 +90,7 @@ Item.propTypes = {
|
|||
organization: PropTypes.string,
|
||||
phone: PropTypes.string,
|
||||
isAdmin: PropTypes.bool.isRequired,
|
||||
isLocked: PropTypes.bool.isRequired,
|
||||
/* eslint-disable react/forbid-prop-types */
|
||||
emailUpdateForm: PropTypes.object.isRequired,
|
||||
passwordUpdateForm: PropTypes.object.isRequired,
|
||||
|
|
|
@ -110,6 +110,7 @@ const UsersModal = React.memo(
|
|||
organization={item.organization}
|
||||
phone={item.phone}
|
||||
isAdmin={item.isAdmin}
|
||||
isLocked={item.isLocked}
|
||||
emailUpdateForm={item.emailUpdateForm}
|
||||
passwordUpdateForm={item.passwordUpdateForm}
|
||||
usernameUpdateForm={item.usernameUpdateForm}
|
||||
|
|
|
@ -14,6 +14,7 @@ const mapStateToProps = (state) => {
|
|||
phone,
|
||||
organization,
|
||||
language,
|
||||
isLocked,
|
||||
subscribeToOwnCards,
|
||||
isAvatarUpdating,
|
||||
emailUpdateForm,
|
||||
|
@ -29,6 +30,7 @@ const mapStateToProps = (state) => {
|
|||
phone,
|
||||
organization,
|
||||
language,
|
||||
isLocked,
|
||||
subscribeToOwnCards,
|
||||
isAvatarUpdating,
|
||||
emailUpdateForm,
|
||||
|
|
|
@ -50,6 +50,9 @@ export default class extends BaseModel {
|
|||
isAdmin: attr({
|
||||
getDefault: () => false,
|
||||
}),
|
||||
isLocked: attr({
|
||||
getDefault: () => false,
|
||||
}),
|
||||
isAvatarUpdating: attr({
|
||||
getDefault: () => false,
|
||||
}),
|
||||
|
|
|
@ -24,6 +24,12 @@ services:
|
|||
- DATABASE_URL=postgresql://postgres@postgres/planka
|
||||
- SECRET_KEY=notsecretkey
|
||||
|
||||
# Can be removed after installation
|
||||
- DEFAULT_ADMIN_EMAIL=demo@demo.demo # Do not remove if you want to prevent this user from being edited/deleted
|
||||
- DEFAULT_ADMIN_PASSWORD=demo
|
||||
- DEFAULT_ADMIN_NAME=Demo Demo
|
||||
- DEFAULT_ADMIN_USERNAME=demo
|
||||
|
||||
# related: https://github.com/knex/knex/issues/2354
|
||||
# As knex does not pass query parameters from the connection string we
|
||||
# have to use environment variables in order to pass the desired values, e.g.
|
||||
|
|
|
@ -4,6 +4,13 @@ BASE_URL=http://localhost:1337
|
|||
DATABASE_URL=postgresql://postgres@localhost/planka
|
||||
SECRET_KEY=notsecretkey
|
||||
|
||||
## Can be removed after installation
|
||||
|
||||
DEFAULT_ADMIN_EMAIL=demo@demo.demo # Do not remove if you want to prevent this user from being edited/deleted
|
||||
DEFAULT_ADMIN_PASSWORD=demo
|
||||
DEFAULT_ADMIN_NAME=Demo Demo
|
||||
DEFAULT_ADMIN_USERNAME=demo
|
||||
|
||||
## Optional
|
||||
|
||||
# TRUST_PROXY=0
|
||||
|
|
|
@ -26,6 +26,10 @@ module.exports = {
|
|||
throw Errors.USER_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (user.email === sails.config.custom.defaultAdminEmail) {
|
||||
throw Errors.USER_NOT_FOUND; // Forbidden
|
||||
}
|
||||
|
||||
user = await sails.helpers.users.deleteOne.with({
|
||||
record: user,
|
||||
request: this.req,
|
||||
|
|
|
@ -59,6 +59,10 @@ module.exports = {
|
|||
throw Errors.USER_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (user.email === sails.config.custom.defaultAdminEmail) {
|
||||
throw Errors.USER_NOT_FOUND; // Forbidden
|
||||
}
|
||||
|
||||
if (
|
||||
inputs.id === currentUser.id &&
|
||||
!bcrypt.compareSync(inputs.currentPassword, user.password)
|
||||
|
|
|
@ -58,6 +58,10 @@ module.exports = {
|
|||
throw Errors.USER_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (user.email === sails.config.custom.defaultAdminEmail) {
|
||||
throw Errors.USER_NOT_FOUND; // Forbidden
|
||||
}
|
||||
|
||||
if (
|
||||
inputs.id === currentUser.id &&
|
||||
!bcrypt.compareSync(inputs.currentPassword, user.password)
|
||||
|
|
|
@ -61,6 +61,10 @@ module.exports = {
|
|||
throw Errors.USER_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (user.email === sails.config.custom.defaultAdminEmail) {
|
||||
throw Errors.USER_NOT_FOUND; // Forbidden
|
||||
}
|
||||
|
||||
if (
|
||||
inputs.id === currentUser.id &&
|
||||
!bcrypt.compareSync(inputs.currentPassword, user.password)
|
||||
|
|
|
@ -67,6 +67,13 @@ module.exports = {
|
|||
throw Errors.USER_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (user.email === sails.config.custom.defaultAdminEmail) {
|
||||
/* eslint-disable no-param-reassign */
|
||||
delete inputs.isAdmin;
|
||||
delete inputs.name;
|
||||
/* eslint-enable no-param-reassign */
|
||||
}
|
||||
|
||||
const values = {
|
||||
..._.pick(inputs, [
|
||||
'isAdmin',
|
||||
|
|
|
@ -114,6 +114,7 @@ module.exports = {
|
|||
avatarUrl:
|
||||
this.avatar &&
|
||||
`${sails.config.custom.userAvatarsUrl}/${this.avatar.dirname}/square-100.${this.avatar.extension}`,
|
||||
isLocked: this.email === sails.config.custom.defaultAdminEmail,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
|
@ -40,4 +40,6 @@ module.exports.custom = {
|
|||
oidcJwksUri: process.env.OIDC_JWKS_URI,
|
||||
oidcScopes: process.env.OIDC_SCOPES || 'openid profile email',
|
||||
oidcSkipUserInfo: process.env.OIDC_SKIP_USER_INFO === 'true',
|
||||
|
||||
defaultAdminEmail: process.env.DEFAULT_ADMIN_EMAIL,
|
||||
};
|
||||
|
|
|
@ -6,12 +6,8 @@ const knex = initKnex(knexfile);
|
|||
|
||||
(async () => {
|
||||
try {
|
||||
const isExists = await knex.schema.hasTable(knexfile.migrations.tableName);
|
||||
|
||||
await knex.migrate.latest();
|
||||
if (!isExists) {
|
||||
await knex.seed.run();
|
||||
}
|
||||
await knex.seed.run();
|
||||
} catch (error) {
|
||||
process.exitCode = 1;
|
||||
|
||||
|
|
|
@ -1,12 +1,42 @@
|
|||
const bcrypt = require('bcrypt');
|
||||
|
||||
exports.seed = (knex) =>
|
||||
knex('user_account').insert({
|
||||
email: 'demo@demo.demo',
|
||||
password: bcrypt.hashSync('demo', 10),
|
||||
const buildData = () => {
|
||||
const data = {
|
||||
isAdmin: true,
|
||||
name: 'Demo Demo',
|
||||
username: 'demo',
|
||||
subscribeToOwnCards: false,
|
||||
createdAt: new Date().toISOString(),
|
||||
});
|
||||
};
|
||||
|
||||
if (process.env.DEFAULT_ADMIN_PASSWORD) {
|
||||
data.password = bcrypt.hashSync(process.env.DEFAULT_ADMIN_PASSWORD, 10);
|
||||
}
|
||||
if (process.env.DEFAULT_ADMIN_NAME) {
|
||||
data.name = process.env.DEFAULT_ADMIN_NAME;
|
||||
}
|
||||
if (process.env.DEFAULT_ADMIN_USERNAME) {
|
||||
data.username = process.env.DEFAULT_ADMIN_USERNAME;
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
exports.seed = async (knex) => {
|
||||
if (!process.env.DEFAULT_ADMIN_EMAIL) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = buildData();
|
||||
|
||||
try {
|
||||
await knex('user_account').insert({
|
||||
...data,
|
||||
email: process.env.DEFAULT_ADMIN_EMAIL,
|
||||
subscribeToOwnCards: false,
|
||||
createdAt: new Date().toISOString(),
|
||||
});
|
||||
} catch (error) {
|
||||
if (Object.keys(data).length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
await knex('user_account').update(data).where('email', process.env.DEFAULT_ADMIN_EMAIL);
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue