mirror of
https://github.com/wassname/talk.git
synced 2026-07-01 10:02:43 +08:00
feat: initial implementation (#2409)
This commit is contained in:
@@ -10,6 +10,7 @@ import ConfigureRoute from "./routes/Configure";
|
||||
import {
|
||||
AdvancedConfigRoute,
|
||||
AuthConfigRoute,
|
||||
EmailConfigRoute,
|
||||
GeneralConfigRoute,
|
||||
ModerationConfigRoute,
|
||||
OrganizationConfigRoute,
|
||||
@@ -71,6 +72,7 @@ export default makeRouteConfig(
|
||||
<Route path="wordList" {...WordListConfigRoute.routeConfig} />
|
||||
<Route path="auth" {...AuthConfigRoute.routeConfig} />
|
||||
<Route path="advanced" {...AdvancedConfigRoute.routeConfig} />
|
||||
<Route path="email" {...EmailConfigRoute.routeConfig} />
|
||||
</Route>
|
||||
</Route>
|
||||
</Route>
|
||||
|
||||
@@ -49,6 +49,9 @@ const Configure: FunctionComponent<Props> = ({
|
||||
<Localized id="configure-sideBarNavigation-authentication">
|
||||
<Link to="/admin/configure/auth">Authentication</Link>
|
||||
</Localized>
|
||||
<Localized id="configure-sideBarNavigation-email">
|
||||
<Link to="/admin/configure/email">Email</Link>
|
||||
</Localized>
|
||||
<Localized id="configure-sideBarNavigation-advanced">
|
||||
<Link to="/admin/configure/advanced">Advanced</Link>
|
||||
</Localized>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.title {
|
||||
display: flex;
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
import { FormApi } from "final-form";
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import { RouteProps } from "found";
|
||||
import React from "react";
|
||||
import { Field } from "react-final-form";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { EmailConfigContainer_email } from "coral-admin/__generated__/EmailConfigContainer_email.graphql";
|
||||
import { DeepNullable, DeepPartial } from "coral-common/types";
|
||||
import { pureMerge } from "coral-common/utils";
|
||||
import { parseBool } from "coral-framework/lib/form";
|
||||
import { withFragmentContainer } from "coral-framework/lib/relay";
|
||||
import { GQLEmailConfiguration } from "coral-framework/schema";
|
||||
import {
|
||||
CheckBox,
|
||||
Flex,
|
||||
FormField,
|
||||
HorizontalGutter,
|
||||
} from "coral-ui/components";
|
||||
|
||||
import Header from "../../Header";
|
||||
import FromContainer from "./FromContainer";
|
||||
import SMTPContainer from "./SMTPContainer";
|
||||
|
||||
import styles from "./EmailConfigContainer.css";
|
||||
|
||||
interface Props {
|
||||
form: FormApi;
|
||||
submitting: boolean;
|
||||
email: EmailConfigContainer_email;
|
||||
}
|
||||
|
||||
export type FormProps = DeepNullable<GQLEmailConfiguration>;
|
||||
export type OnInitValuesFct = (values: DeepPartial<FormProps>) => void;
|
||||
|
||||
class EmailConfigContainer extends React.Component<Props> {
|
||||
public static routeConfig: RouteProps;
|
||||
private initialValues: DeepPartial<FormProps> = {};
|
||||
|
||||
public componentDidMount() {
|
||||
this.props.form.initialize({ email: this.initialValues });
|
||||
}
|
||||
|
||||
private handleOnInitValues: OnInitValuesFct = values => {
|
||||
if (values.smtp && values.smtp.authentication === null) {
|
||||
values = { ...values, smtp: { ...values.smtp, authentication: true } };
|
||||
}
|
||||
if (values.smtp && values.smtp.secure === null) {
|
||||
values = { ...values, smtp: { ...values.smtp, secure: true } };
|
||||
}
|
||||
|
||||
this.initialValues = pureMerge<DeepPartial<FormProps>>(
|
||||
this.initialValues,
|
||||
values
|
||||
);
|
||||
};
|
||||
|
||||
public render() {
|
||||
const { email, submitting } = this.props;
|
||||
|
||||
return (
|
||||
<HorizontalGutter size="double">
|
||||
<Field name="email.enabled" type="checkbox" parse={parseBool}>
|
||||
{({ input }) => (
|
||||
<Header
|
||||
className={styles.title}
|
||||
container={<Flex justifyContent="space-between" />}
|
||||
>
|
||||
<div>
|
||||
<Localized id="configure-email">
|
||||
<span>Email settings</span>
|
||||
</Localized>
|
||||
</div>
|
||||
<div>
|
||||
<FormField>
|
||||
<Localized id="configure-email-configBoxEnabled">
|
||||
<CheckBox
|
||||
id={input.name}
|
||||
name={input.name}
|
||||
onChange={input.onChange}
|
||||
checked={input.value}
|
||||
disabled={submitting}
|
||||
>
|
||||
Enabled
|
||||
</CheckBox>
|
||||
</Localized>
|
||||
</FormField>
|
||||
</div>
|
||||
</Header>
|
||||
)}
|
||||
</Field>
|
||||
<Field name="email.enabled" subscription={{ value: true }}>
|
||||
{({ input: { value } }) => (
|
||||
<>
|
||||
<FromContainer
|
||||
email={email}
|
||||
disabled={submitting || !value}
|
||||
onInitValues={this.handleOnInitValues}
|
||||
/>
|
||||
<SMTPContainer
|
||||
email={email}
|
||||
disabled={submitting || !value}
|
||||
onInitValues={this.handleOnInitValues}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
</HorizontalGutter>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
email: graphql`
|
||||
fragment EmailConfigContainer_email on EmailConfiguration {
|
||||
enabled
|
||||
...FromContainer_email
|
||||
...SMTPContainer_email
|
||||
}
|
||||
`,
|
||||
})(EmailConfigContainer);
|
||||
|
||||
export default enhanced;
|
||||
@@ -0,0 +1,49 @@
|
||||
import { FormApi } from "final-form";
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { EmailConfigRouteQueryResponse } from "coral-admin/__generated__/EmailConfigRouteQuery.graphql";
|
||||
import { withRouteConfig } from "coral-framework/lib/router";
|
||||
import { Delay, Spinner } from "coral-ui/components";
|
||||
|
||||
import EmailConfigContainer from "./EmailConfigContainer";
|
||||
|
||||
interface Props {
|
||||
data: EmailConfigRouteQueryResponse | null;
|
||||
form: FormApi;
|
||||
submitting: boolean;
|
||||
}
|
||||
|
||||
class EmailConfigRoute extends React.Component<Props> {
|
||||
public render() {
|
||||
if (!this.props.data) {
|
||||
return (
|
||||
<Delay>
|
||||
<Spinner />
|
||||
</Delay>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<EmailConfigContainer
|
||||
email={this.props.data.settings.email}
|
||||
form={this.props.form}
|
||||
submitting={this.props.submitting}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withRouteConfig<Props>({
|
||||
query: graphql`
|
||||
query EmailConfigRouteQuery {
|
||||
settings {
|
||||
email {
|
||||
...EmailConfigContainer_email
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
cacheConfig: { force: true },
|
||||
})(EmailConfigRoute);
|
||||
|
||||
export default enhanced;
|
||||
@@ -0,0 +1,79 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { FunctionComponent } from "react";
|
||||
import { Field } from "react-final-form";
|
||||
|
||||
import { validateEmail } from "coral-framework/lib/validation";
|
||||
import {
|
||||
FieldSet,
|
||||
FormField,
|
||||
HorizontalGutter,
|
||||
InputDescription,
|
||||
InputLabel,
|
||||
TextField,
|
||||
ValidationMessage,
|
||||
} from "coral-ui/components";
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const From: FunctionComponent<Props> = ({ disabled }) => (
|
||||
<HorizontalGutter size="oneAndAHalf" container={<FieldSet />}>
|
||||
<FormField>
|
||||
<Localized id="configure-email-fromNameLabel">
|
||||
<InputLabel>From name</InputLabel>
|
||||
</Localized>
|
||||
<Localized id="configure-email-fromNameDescription">
|
||||
<InputDescription>
|
||||
Name as it will appear on all outgoing emails
|
||||
</InputDescription>
|
||||
</Localized>
|
||||
<Field name="email.fromName">
|
||||
{({ input, meta }) => (
|
||||
<>
|
||||
<TextField fullWidth disabled={disabled} {...input} />
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
</FormField>
|
||||
<FormField>
|
||||
<Localized id="configure-email-fromEmailLabel">
|
||||
<InputLabel>From email address</InputLabel>
|
||||
</Localized>
|
||||
<Localized id="configure-email-fromEmailDescription">
|
||||
<InputDescription>
|
||||
Email address that will be used to send messages
|
||||
</InputDescription>
|
||||
</Localized>
|
||||
<Field name="email.fromEmail" validate={validateEmail}>
|
||||
{({ input, meta }) => (
|
||||
<>
|
||||
<TextField
|
||||
type="email"
|
||||
fullWidth
|
||||
color={
|
||||
meta.touched && (meta.error || meta.submitError)
|
||||
? "error"
|
||||
: "regular"
|
||||
}
|
||||
disabled={disabled}
|
||||
{...input}
|
||||
/>
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
</FormField>
|
||||
</HorizontalGutter>
|
||||
);
|
||||
|
||||
export default From;
|
||||
@@ -0,0 +1,38 @@
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { FromContainer_email } from "coral-admin/__generated__/FromContainer_email.graphql";
|
||||
import { withFragmentContainer } from "coral-framework/lib/relay";
|
||||
|
||||
import { OnInitValuesFct } from "./EmailConfigContainer";
|
||||
import From from "./From";
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
onInitValues: OnInitValuesFct;
|
||||
email: FromContainer_email;
|
||||
}
|
||||
|
||||
class FromContainer extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
props.onInitValues(props.email);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { disabled } = this.props;
|
||||
return <From disabled={disabled} />;
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
email: graphql`
|
||||
fragment FromContainer_email on EmailConfiguration {
|
||||
enabled
|
||||
fromName
|
||||
fromEmail
|
||||
}
|
||||
`,
|
||||
})(FromContainer);
|
||||
|
||||
export default enhanced;
|
||||
@@ -0,0 +1,149 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { FunctionComponent } from "react";
|
||||
import { Field } from "react-final-form";
|
||||
|
||||
import {
|
||||
FieldSet,
|
||||
FormField,
|
||||
HorizontalGutter,
|
||||
InputDescription,
|
||||
InputLabel,
|
||||
PasswordField,
|
||||
TextField,
|
||||
ValidationMessage,
|
||||
} from "coral-ui/components";
|
||||
|
||||
import OnOffField from "../../OnOffField";
|
||||
import Subheader from "../../Subheader";
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const SMTP: FunctionComponent<Props> = ({ disabled }) => (
|
||||
<HorizontalGutter size="oneAndAHalf" container={<FieldSet />}>
|
||||
<FormField>
|
||||
<Localized id="configure-email-smtpHostLabel">
|
||||
<InputLabel>SMTP host</InputLabel>
|
||||
</Localized>
|
||||
<Localized id="configure-email-smtpHostDescription">
|
||||
<InputDescription>(ex. smtp.sendgrid.com)</InputDescription>
|
||||
</Localized>
|
||||
<Field name="email.smtp.host">
|
||||
{({ input, meta }) => (
|
||||
<>
|
||||
<TextField
|
||||
id={input.name}
|
||||
fullWidth
|
||||
disabled={disabled}
|
||||
{...input}
|
||||
/>
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
</FormField>
|
||||
<FormField>
|
||||
<Localized id="configure-email-smtpPortLabel">
|
||||
<InputLabel>SMTP port</InputLabel>
|
||||
</Localized>
|
||||
<Localized id="configure-email-smtpPortDescription">
|
||||
<InputDescription>(ex. 25)</InputDescription>
|
||||
</Localized>
|
||||
<Field name="email.smtp.port">
|
||||
{({ input, meta }) => (
|
||||
<>
|
||||
<TextField
|
||||
id={input.name}
|
||||
type="number"
|
||||
fullWidth
|
||||
disabled={disabled}
|
||||
{...input}
|
||||
/>
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
</FormField>
|
||||
<FormField>
|
||||
<Localized id="configure-email-smtpTLSLabel">
|
||||
<InputLabel>TLS</InputLabel>
|
||||
</Localized>
|
||||
<OnOffField name="email.smtp.secure" disabled={disabled} />
|
||||
</FormField>
|
||||
<FormField>
|
||||
<Localized id="configure-email-smtpAuthenticationLabel">
|
||||
<InputLabel>SMTP Authentication</InputLabel>
|
||||
</Localized>
|
||||
<OnOffField name="email.smtp.authentication" disabled={disabled} />
|
||||
</FormField>
|
||||
<Field name="email.smtp.authentication" subscription={{ value: true }}>
|
||||
{({ input: { value: enabled } }) => (
|
||||
<>
|
||||
<Localized id="configure-email-smtpCredentialsHeader">
|
||||
<Subheader>Email credentials</Subheader>
|
||||
</Localized>
|
||||
<FormField>
|
||||
<Localized id="configure-email-smtpUsernameLabel">
|
||||
<InputLabel>Username</InputLabel>
|
||||
</Localized>
|
||||
<Field name="email.smtp.username">
|
||||
{({ input, meta }) => (
|
||||
<>
|
||||
<TextField
|
||||
id={input.name}
|
||||
fullWidth
|
||||
disabled={disabled || !enabled}
|
||||
{...input}
|
||||
/>
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
</FormField>
|
||||
<FormField>
|
||||
<Localized id="configure-email-smtpPasswordLabel">
|
||||
<InputLabel>Password</InputLabel>
|
||||
</Localized>
|
||||
<Field name="email.smtp.password">
|
||||
{({ input, meta }) => (
|
||||
<>
|
||||
<PasswordField
|
||||
id={input.name}
|
||||
color={
|
||||
meta.touched && (meta.error || meta.submitError)
|
||||
? "error"
|
||||
: "regular"
|
||||
}
|
||||
{...input}
|
||||
disabled={disabled || !enabled}
|
||||
fullWidth
|
||||
/>
|
||||
{meta.touched && (meta.error || meta.submitError) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
</FormField>
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
</HorizontalGutter>
|
||||
);
|
||||
|
||||
export default SMTP;
|
||||
@@ -0,0 +1,44 @@
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { SMTPContainer_email } from "coral-admin/__generated__/SMTPContainer_email.graphql";
|
||||
import { withFragmentContainer } from "coral-framework/lib/relay";
|
||||
|
||||
import { OnInitValuesFct } from "./EmailConfigContainer";
|
||||
import SMTP from "./SMTP";
|
||||
|
||||
interface Props {
|
||||
email: SMTPContainer_email;
|
||||
disabled: boolean;
|
||||
onInitValues: OnInitValuesFct;
|
||||
}
|
||||
|
||||
class SMTPContainer extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
props.onInitValues(props.email);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { disabled } = this.props;
|
||||
return <SMTP disabled={disabled} />;
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
email: graphql`
|
||||
fragment SMTPContainer_email on EmailConfiguration {
|
||||
enabled
|
||||
smtp {
|
||||
host
|
||||
port
|
||||
secure
|
||||
authentication
|
||||
username
|
||||
password
|
||||
}
|
||||
}
|
||||
`,
|
||||
})(SMTPContainer);
|
||||
|
||||
export default enhanced;
|
||||
@@ -0,0 +1 @@
|
||||
export { default, default as EmailConfigRoute } from "./EmailConfigRoute";
|
||||
@@ -1,5 +1,6 @@
|
||||
export { AdvancedConfigRoute } from "./Advanced";
|
||||
export { AuthConfigRoute } from "./Auth";
|
||||
export { EmailConfigRoute } from "./Email";
|
||||
export { GeneralConfigRoute } from "./General";
|
||||
export { ModerationConfigRoute } from "./Moderation";
|
||||
export { OrganizationConfigRoute } from "./Organization";
|
||||
|
||||
@@ -70,6 +70,15 @@ exports[`renders configure advanced 1`] = `
|
||||
Authentication
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
className="Link-link"
|
||||
href="/admin/configure/email"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Email
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
className="Link-link Link-linkActive"
|
||||
|
||||
@@ -1936,6 +1936,15 @@ exports[`renders configure auth 1`] = `
|
||||
Authentication
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
className="Link-link"
|
||||
href="/admin/configure/email"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Email
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
className="Link-link"
|
||||
|
||||
@@ -70,6 +70,15 @@ exports[`renders configure general 1`] = `
|
||||
Authentication
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
className="Link-link"
|
||||
href="/admin/configure/email"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Email
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
className="Link-link"
|
||||
|
||||
@@ -70,6 +70,15 @@ exports[`renders configure moderation 1`] = `
|
||||
Authentication
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
className="Link-link"
|
||||
href="/admin/configure/email"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Email
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
className="Link-link"
|
||||
|
||||
@@ -70,6 +70,15 @@ exports[`renders configure organization 1`] = `
|
||||
Authentication
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
className="Link-link"
|
||||
href="/admin/configure/email"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Email
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
className="Link-link"
|
||||
|
||||
@@ -70,6 +70,15 @@ exports[`renders configure wordList 1`] = `
|
||||
Authentication
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
className="Link-link"
|
||||
href="/admin/configure/email"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Email
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
className="Link-link"
|
||||
|
||||
@@ -20,6 +20,18 @@ export type Writeable<T> = { -readonly [P in keyof T]: T[P] };
|
||||
*/
|
||||
export type Promiseable<T> = Promise<T> | T;
|
||||
|
||||
export type Nullable<T> = { [P in keyof T]: T[P] | null };
|
||||
|
||||
export type DeepNullable<T> = T extends object
|
||||
? {
|
||||
[P in keyof T]: T[P] extends Array<infer U>
|
||||
? Array<DeepNullable<U>>
|
||||
: T[P] extends ReadonlyArray<infer V>
|
||||
? ReadonlyArray<DeepNullable<V>>
|
||||
: DeepNullable<T[P]>
|
||||
}
|
||||
: T | null;
|
||||
|
||||
/**
|
||||
* Like Partial, but recurses down the object marking each field as Partial.
|
||||
*/
|
||||
|
||||
@@ -869,24 +869,27 @@ type DisableCommenting {
|
||||
}
|
||||
|
||||
################################################################################
|
||||
## Email
|
||||
## EmailConfiguration
|
||||
################################################################################
|
||||
|
||||
type Email {
|
||||
type SMTP {
|
||||
secure: Boolean
|
||||
host: String
|
||||
port: Int
|
||||
authentication: Boolean
|
||||
username: String
|
||||
password: String
|
||||
}
|
||||
|
||||
type EmailConfiguration {
|
||||
"""
|
||||
enabled when True, will enable the emailing functionality in Coral.
|
||||
enabled when true, will enable the emailing functionality in Coral.
|
||||
"""
|
||||
enabled: Boolean!
|
||||
|
||||
"""
|
||||
smtpURI is the SMTP connection url to send emails on.
|
||||
"""
|
||||
smtpURI: String @auth(roles: [ADMIN])
|
||||
|
||||
"""
|
||||
fromAddress is the email address that will be used to send emails from.
|
||||
"""
|
||||
fromAddress: String
|
||||
fromName: String
|
||||
fromEmail: String
|
||||
smtp: SMTP! @auth(roles: [ADMIN])
|
||||
}
|
||||
|
||||
################################################################################
|
||||
@@ -1148,7 +1151,7 @@ type Settings {
|
||||
"""
|
||||
email is the set of credentials and settings associated with the organization.
|
||||
"""
|
||||
email: Email! @auth(roles: [ADMIN, MODERATOR])
|
||||
email: EmailConfiguration! @auth(roles: [ADMIN, MODERATOR])
|
||||
|
||||
"""
|
||||
wordList will return a given list of words.
|
||||
@@ -2599,21 +2602,23 @@ input SettingsOIDCAuthIntegrationInput {
|
||||
issuer: String
|
||||
}
|
||||
|
||||
input SettingsEmailInput {
|
||||
input SettingsSMTPInput {
|
||||
secure: Boolean
|
||||
host: String
|
||||
port: Int
|
||||
authentication: Boolean
|
||||
username: String
|
||||
password: String
|
||||
}
|
||||
|
||||
input SettingsEmailConfigurationInput {
|
||||
"""
|
||||
enabled when True, will enable the emailing functionality in Coral.
|
||||
"""
|
||||
enabled: Boolean
|
||||
|
||||
"""
|
||||
smtpURI is the SMTP connection url to send emails on.
|
||||
"""
|
||||
smtpURI: String
|
||||
|
||||
"""
|
||||
fromAddress is the email address that will be used to send emails from.
|
||||
"""
|
||||
fromAddress: String
|
||||
smtp: SettingsSMTPInput
|
||||
fromName: String
|
||||
fromEmail: String
|
||||
}
|
||||
|
||||
input SettingsWordListInput {
|
||||
@@ -3048,7 +3053,7 @@ input SettingsInput {
|
||||
"""
|
||||
email is the set of credentials and settings associated with the organization.
|
||||
"""
|
||||
email: SettingsEmailInput
|
||||
email: SettingsEmailConfigurationInput
|
||||
|
||||
"""
|
||||
auth contains all the settings related to authentication and authorization.
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import { LocalProfile, User } from "coral-server/models/user";
|
||||
|
||||
/**
|
||||
* getLocalProfile will get the LocalProfile from the User if it exists.
|
||||
*
|
||||
* @param user the User to pull the LocalProfile out of
|
||||
*/
|
||||
export function getLocalProfile(
|
||||
user: Pick<User, "profiles">
|
||||
): LocalProfile | undefined {
|
||||
return user.profiles.find(({ type }) => type === "local") as
|
||||
| LocalProfile
|
||||
| undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* hasLocalProfile will return true if the User has a LocalProfile, optionally
|
||||
* checking the email on it as well.
|
||||
*
|
||||
* @param user the User to pull the LocalProfile out of
|
||||
* @param withEmail when specified, will ensure that the LocalProfile has the
|
||||
* specific email provided
|
||||
*/
|
||||
export function hasLocalProfile(
|
||||
user: Pick<User, "profiles">,
|
||||
withEmail?: string
|
||||
): boolean {
|
||||
const profile = getLocalProfile(user);
|
||||
if (!profile) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (withEmail && profile.id !== withEmail) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Omit } from "coral-common/types";
|
||||
import {
|
||||
GQLAuth,
|
||||
GQLEmailConfiguration,
|
||||
GQLFacebookAuthIntegration,
|
||||
GQLGoogleAuthIntegration,
|
||||
GQLLiveConfiguration,
|
||||
@@ -13,6 +14,8 @@ import {
|
||||
|
||||
export type LiveConfiguration = Omit<GQLLiveConfiguration, "configurable">;
|
||||
|
||||
export type EmailConfiguration = GQLEmailConfiguration;
|
||||
|
||||
export interface GlobalModerationSettings {
|
||||
live: LiveConfiguration;
|
||||
moderation: GQLMODERATION_MODE;
|
||||
@@ -77,7 +80,6 @@ export type Settings = GlobalModerationSettings &
|
||||
Pick<
|
||||
GQLSettings,
|
||||
| "charCount"
|
||||
| "email"
|
||||
| "karma"
|
||||
| "wordList"
|
||||
| "integrations"
|
||||
@@ -93,6 +95,12 @@ export type Settings = GlobalModerationSettings &
|
||||
*/
|
||||
auth: Auth;
|
||||
|
||||
/**
|
||||
* email is the set of credentials and settings associated with the
|
||||
* organization.
|
||||
*/
|
||||
email: EmailConfiguration;
|
||||
|
||||
/**
|
||||
* closeCommenting contains settings related to the automatic closing of commenting on
|
||||
* Stories.
|
||||
|
||||
@@ -158,6 +158,7 @@ export async function createTenant(
|
||||
},
|
||||
email: {
|
||||
enabled: false,
|
||||
smtp: {},
|
||||
},
|
||||
karma: {
|
||||
enabled: true,
|
||||
|
||||
@@ -6,9 +6,10 @@ import htmlToText from "html-to-text";
|
||||
import Joi from "joi";
|
||||
import { JSDOM } from "jsdom";
|
||||
import { juiceResources } from "juice";
|
||||
import { camelCase } from "lodash";
|
||||
import { camelCase, isNil } from "lodash";
|
||||
import { Db } from "mongodb";
|
||||
import { createTransport } from "nodemailer";
|
||||
import { Options } from "nodemailer/lib/smtp-connection";
|
||||
import now from "performance-now";
|
||||
|
||||
import { LanguageCode } from "coral-common/helpers/i18n/locales";
|
||||
@@ -202,24 +203,28 @@ export const createJobProcessor = (options: MailProcessorOptions) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const { enabled, smtpURI, fromAddress } = tenant.email;
|
||||
const { enabled, smtp, fromEmail, fromName } = tenant.email;
|
||||
if (!enabled) {
|
||||
log.error("not sending email, it was disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!smtpURI) {
|
||||
log.error("email was enabled but the smtpURI configuration was missing");
|
||||
// Check that we have enough to generate the smtp credentials.
|
||||
if (isNil(smtp.secure) || isNil(smtp.host) || isNil(smtp.port)) {
|
||||
log.error("email enabled, but configuration is incomplete");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fromAddress) {
|
||||
if (!fromEmail) {
|
||||
log.error(
|
||||
"email was enabled but the fromAddress configuration was missing"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Construct the fromAddress.
|
||||
const fromAddress = fromName ? `${fromName} <${fromEmail}>` : fromEmail;
|
||||
|
||||
const startTemplateGenerationTime = now();
|
||||
|
||||
// Get the message to send.
|
||||
@@ -242,8 +247,24 @@ export const createJobProcessor = (options: MailProcessorOptions) => {
|
||||
let transport = cache.get(tenantID);
|
||||
if (!transport) {
|
||||
try {
|
||||
// Create the new transport options.
|
||||
const opts: Options = {
|
||||
host: smtp.host,
|
||||
port: smtp.port,
|
||||
secure: smtp.secure,
|
||||
};
|
||||
if (smtp.authentication && smtp.username && smtp.password) {
|
||||
// If authentication details are provided, add them to the transport
|
||||
// configuration.
|
||||
opts.auth = {
|
||||
type: "login",
|
||||
user: smtp.username,
|
||||
pass: smtp.password,
|
||||
};
|
||||
}
|
||||
|
||||
// Create the transport based on the smtp uri.
|
||||
transport = createTransport(smtpURI);
|
||||
transport = createTransport(opts);
|
||||
} catch (err) {
|
||||
throw new InternalError(err, "could not create email transport");
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ configure-sideBarNavigation-authentication = Authentication
|
||||
configure-sideBarNavigation-moderation = Moderation
|
||||
configure-sideBarNavigation-organization = Organization
|
||||
configure-sideBarNavigation-advanced = Advanced
|
||||
configure-sideBarNavigation-email = Email
|
||||
configure-sideBarNavigation-bannedAndSuspectWords = Banned and Suspect Words
|
||||
|
||||
configure-sideBar-saveChanges = Save Changes
|
||||
@@ -151,6 +152,26 @@ configure-organization-emailExplanation =
|
||||
the organization should they have any questions about the
|
||||
status of their accounts or moderation questions.
|
||||
|
||||
### Email
|
||||
|
||||
configure-email = Email settings
|
||||
configure-email-configBoxEnabled = Enabled
|
||||
configure-email-fromNameLabel = From name
|
||||
configure-email-fromNameDescription =
|
||||
Name as it will appear on all outgoing emails
|
||||
configure-email-fromEmailLabel = From email address
|
||||
configure-email-fromEmailDescription =
|
||||
Email address that will be used to send messages
|
||||
configure-email-smtpHostLabel = SMTP host
|
||||
configure-email-smtpHostDescription = (ex. smtp.sendgrid.com)
|
||||
configure-email-smtpPortLabel = SMTP port
|
||||
configure-email-smtpPortDescription = (ex. 25)
|
||||
configure-email-smtpTLSLabel = TLS
|
||||
configure-email-smtpAuthenticationLabel = SMTP Authentication
|
||||
configure-email-smtpCredentialsHeader = Email credentials
|
||||
configure-email-smtpUsernameLabel = Username
|
||||
configure-email-smtpPasswordLabel = Password
|
||||
|
||||
### Authentication
|
||||
|
||||
configure-auth-authIntegrations = Authentication Integrations
|
||||
|
||||
Reference in New Issue
Block a user