First pass, can create and update
This commit is contained in:
@ -25,6 +25,7 @@ class Details extends React.Component<Props> {
|
|||||||
form: ?HTMLFormElement;
|
form: ?HTMLFormElement;
|
||||||
|
|
||||||
@observable name: string;
|
@observable name: string;
|
||||||
|
@observable subdomain: string;
|
||||||
@observable avatarUrl: ?string;
|
@observable avatarUrl: ?string;
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -51,12 +52,16 @@ class Details extends React.Component<Props> {
|
|||||||
this.name = ev.target.value;
|
this.name = ev.target.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleSubdomainChange = (ev: SyntheticInputEvent<*>) => {
|
||||||
|
this.subdomain = ev.target.value.toLowerCase();
|
||||||
|
};
|
||||||
|
|
||||||
handleAvatarUpload = (avatarUrl: string) => {
|
handleAvatarUpload = (avatarUrl: string) => {
|
||||||
this.avatarUrl = avatarUrl;
|
this.avatarUrl = avatarUrl;
|
||||||
};
|
};
|
||||||
|
|
||||||
handleAvatarError = (error: ?string) => {
|
handleAvatarError = (error: ?string) => {
|
||||||
this.props.ui.showToast(error || 'Unable to upload new avatar');
|
this.props.ui.showToast(error || 'Unable to upload new logo');
|
||||||
};
|
};
|
||||||
|
|
||||||
get isValid() {
|
get isValid() {
|
||||||
@ -104,11 +109,28 @@ class Details extends React.Component<Props> {
|
|||||||
<form onSubmit={this.handleSubmit} ref={ref => (this.form = ref)}>
|
<form onSubmit={this.handleSubmit} ref={ref => (this.form = ref)}>
|
||||||
<Input
|
<Input
|
||||||
label="Name"
|
label="Name"
|
||||||
|
name="name"
|
||||||
value={this.name}
|
value={this.name}
|
||||||
onChange={this.handleNameChange}
|
onChange={this.handleNameChange}
|
||||||
required
|
required
|
||||||
short
|
short
|
||||||
/>
|
/>
|
||||||
|
<Input
|
||||||
|
label="Subdomain"
|
||||||
|
name="subdomain"
|
||||||
|
value={this.subdomain}
|
||||||
|
onChange={this.handleSubdomainChange}
|
||||||
|
placeholder="Optional"
|
||||||
|
short
|
||||||
|
/>
|
||||||
|
|
||||||
|
{this.subdomain && (
|
||||||
|
<HelpText small>
|
||||||
|
You will be able to access your wiki at{' '}
|
||||||
|
<strong>{this.subdomain}.getoutline.com</strong>
|
||||||
|
</HelpText>
|
||||||
|
)}
|
||||||
|
|
||||||
<Button type="submit" disabled={isSaving || !this.isValid}>
|
<Button type="submit" disabled={isSaving || !this.isValid}>
|
||||||
{isSaving ? 'Saving…' : 'Save'}
|
{isSaving ? 'Saving…' : 'Save'}
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -12,7 +12,7 @@ const { authorize } = policy;
|
|||||||
const router = new Router();
|
const router = new Router();
|
||||||
|
|
||||||
router.post('team.update', auth(), async ctx => {
|
router.post('team.update', auth(), async ctx => {
|
||||||
const { name, avatarUrl, sharing } = ctx.body;
|
const { name, avatarUrl, subdomain, sharing } = ctx.body;
|
||||||
const endpoint = publicS3Endpoint();
|
const endpoint = publicS3Endpoint();
|
||||||
|
|
||||||
const user = ctx.state.user;
|
const user = ctx.state.user;
|
||||||
@ -20,6 +20,7 @@ router.post('team.update', auth(), async ctx => {
|
|||||||
authorize(user, 'update', team);
|
authorize(user, 'update', team);
|
||||||
|
|
||||||
if (name) team.name = name;
|
if (name) team.name = name;
|
||||||
|
if (subdomain) team.subdomain = subdomain;
|
||||||
if (sharing !== undefined) team.sharing = sharing;
|
if (sharing !== undefined) team.sharing = sharing;
|
||||||
if (avatarUrl && avatarUrl.startsWith(`${endpoint}/uploads/${user.id}`)) {
|
if (avatarUrl && avatarUrl.startsWith(`${endpoint}/uploads/${user.id}`)) {
|
||||||
team.avatarUrl = avatarUrl;
|
team.avatarUrl = avatarUrl;
|
||||||
|
25
server/domains.js
Normal file
25
server/domains.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// @flow
|
||||||
|
export const RESERVED_SUBDOMAINS = [
|
||||||
|
'admin',
|
||||||
|
'api',
|
||||||
|
'beta',
|
||||||
|
'blog',
|
||||||
|
'cdn',
|
||||||
|
'community',
|
||||||
|
'developer',
|
||||||
|
'forum',
|
||||||
|
'help',
|
||||||
|
'imap',
|
||||||
|
'localhost',
|
||||||
|
'mail',
|
||||||
|
'ns1',
|
||||||
|
'ns2',
|
||||||
|
'ns3',
|
||||||
|
'ns4',
|
||||||
|
'smtp',
|
||||||
|
'support',
|
||||||
|
'status',
|
||||||
|
'static',
|
||||||
|
'test',
|
||||||
|
'www',
|
||||||
|
];
|
14
server/migrations/20181031015046-add-subdomain-to-team.js
Normal file
14
server/migrations/20181031015046-add-subdomain-to-team.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
module.exports = {
|
||||||
|
up: async (queryInterface, Sequelize) => {
|
||||||
|
await queryInterface.addColumn('teams', 'subdomain', {
|
||||||
|
type: Sequelize.STRING,
|
||||||
|
allowNull: true,
|
||||||
|
unique: true
|
||||||
|
});
|
||||||
|
await queryInterface.addIndex('teams', ['subdomain']);
|
||||||
|
},
|
||||||
|
down: async (queryInterface, Sequelize) => {
|
||||||
|
await queryInterface.removeColumn('teams', 'subdomain');
|
||||||
|
await queryInterface.removeIndex('teams', ['subdomain']);
|
||||||
|
}
|
||||||
|
}
|
@ -2,33 +2,34 @@
|
|||||||
import uuid from 'uuid';
|
import uuid from 'uuid';
|
||||||
import { DataTypes, sequelize, Op } from '../sequelize';
|
import { DataTypes, sequelize, Op } from '../sequelize';
|
||||||
import { publicS3Endpoint, uploadToS3FromUrl } from '../utils/s3';
|
import { publicS3Endpoint, uploadToS3FromUrl } from '../utils/s3';
|
||||||
|
import { RESERVED_SUBDOMAINS } from '../domains';
|
||||||
import Collection from './Collection';
|
import Collection from './Collection';
|
||||||
import User from './User';
|
import User from './User';
|
||||||
|
|
||||||
const Team = sequelize.define(
|
const Team = sequelize.define('team', {
|
||||||
'team',
|
|
||||||
{
|
|
||||||
id: {
|
id: {
|
||||||
type: DataTypes.UUID,
|
type: DataTypes.UUID,
|
||||||
defaultValue: DataTypes.UUIDV4,
|
defaultValue: DataTypes.UUIDV4,
|
||||||
primaryKey: true,
|
primaryKey: true,
|
||||||
},
|
},
|
||||||
name: DataTypes.STRING,
|
name: DataTypes.STRING,
|
||||||
|
subdomain: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true,
|
||||||
|
validate: {
|
||||||
|
isLowercase: true,
|
||||||
|
isAlphanumeric: true,
|
||||||
|
len: [4, 32],
|
||||||
|
notIn: [RESERVED_SUBDOMAINS],
|
||||||
|
},
|
||||||
|
unique: true,
|
||||||
|
},
|
||||||
slackId: { type: DataTypes.STRING, allowNull: true },
|
slackId: { type: DataTypes.STRING, allowNull: true },
|
||||||
googleId: { type: DataTypes.STRING, allowNull: true },
|
googleId: { type: DataTypes.STRING, allowNull: true },
|
||||||
avatarUrl: { type: DataTypes.STRING, allowNull: true },
|
avatarUrl: { type: DataTypes.STRING, allowNull: true },
|
||||||
sharing: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: true },
|
sharing: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: true },
|
||||||
slackData: DataTypes.JSONB,
|
slackData: DataTypes.JSONB,
|
||||||
},
|
});
|
||||||
{
|
|
||||||
indexes: [
|
|
||||||
{
|
|
||||||
unique: true,
|
|
||||||
fields: ['slackId'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
Team.associate = models => {
|
Team.associate = models => {
|
||||||
Team.hasMany(models.Collection, { as: 'collections' });
|
Team.hasMany(models.Collection, { as: 'collections' });
|
||||||
|
Reference in New Issue
Block a user