mirror of
https://github.com/wassname/talk.git
synced 2026-07-27 11:28:12 +08:00
[CORL-681] Configurable Staff Badges (#2620)
* feat: added configurable staff badge * fix: removed ambigous styles
This commit is contained in:
@@ -11,6 +11,7 @@ import GuidelinesConfigContainer from "./GuidelinesConfigContainer";
|
||||
import LocaleConfigContainer from "./LocaleConfigContainer";
|
||||
import ReactionConfigContainer from "./ReactionConfigContainer";
|
||||
import SitewideCommentingConfigContainer from "./SitewideCommentingConfigContainer";
|
||||
import StaffConfigContainer from "./StaffConfigContainer";
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
@@ -19,6 +20,7 @@ interface Props {
|
||||
PropTypesOf<typeof CommentEditingConfigContainer>["settings"] &
|
||||
PropTypesOf<typeof ClosedStreamMessageConfigContainer>["settings"] &
|
||||
PropTypesOf<typeof ReactionConfigContainer>["settings"] &
|
||||
PropTypesOf<typeof StaffConfigContainer>["settings"] &
|
||||
PropTypesOf<typeof ClosingCommentStreamsConfigContainer>["settings"] &
|
||||
PropTypesOf<typeof SitewideCommentingConfigContainer>["settings"] &
|
||||
PropTypesOf<typeof LocaleConfigContainer>["settings"];
|
||||
@@ -71,6 +73,11 @@ const General: FunctionComponent<Props> = ({
|
||||
settings={settings}
|
||||
onInitValues={onInitValues}
|
||||
/>
|
||||
<StaffConfigContainer
|
||||
disabled={disabled}
|
||||
settings={settings}
|
||||
onInitValues={onInitValues}
|
||||
/>
|
||||
</HorizontalGutter>
|
||||
);
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ const enhanced = withFragmentContainer<Props>({
|
||||
...ClosingCommentStreamsConfigContainer_settings
|
||||
...SitewideCommentingConfigContainer_settings
|
||||
...ReactionConfigContainer_settings
|
||||
...StaffConfigContainer_settings
|
||||
}
|
||||
`,
|
||||
})(GeneralConfigContainer);
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.textInput {
|
||||
min-width: 300px;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { FunctionComponent } from "react";
|
||||
import { Field } from "react-final-form";
|
||||
|
||||
import { StaffConfigContainer_settings as SettingsData } from "coral-admin/__generated__/StaffConfigContainer_settings.graphql";
|
||||
import { required } from "coral-framework/lib/validation";
|
||||
import {
|
||||
FieldSet,
|
||||
Flex,
|
||||
FormField,
|
||||
HorizontalGutter,
|
||||
InputLabel,
|
||||
Tag,
|
||||
TextField,
|
||||
Typography,
|
||||
} from "coral-ui/components";
|
||||
|
||||
import Header from "../../Header";
|
||||
import SectionContent from "../../SectionContent";
|
||||
import ValidationMessage from "../../ValidationMessage";
|
||||
|
||||
import styles from "./StaffConfig.css";
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
settings: SettingsData;
|
||||
}
|
||||
|
||||
const StaffConfig: FunctionComponent<Props> = ({ disabled, settings }) => (
|
||||
<HorizontalGutter size="oneAndAHalf" container={<FieldSet />}>
|
||||
<Localized id="configure-general-staff-title">
|
||||
<Header container="legend">Staff member badge</Header>
|
||||
</Localized>
|
||||
<SectionContent>
|
||||
<Localized id="configure-general-staff-explanation">
|
||||
<Typography variant="bodyShort">
|
||||
Show a custom badge for staff members of your organization. This badge
|
||||
appears on the comment stream and in the admin interface.
|
||||
</Typography>
|
||||
</Localized>
|
||||
<HorizontalGutter size="double">
|
||||
<FormField>
|
||||
<Field name="staff.label" validate={required}>
|
||||
{({ input, meta }) => (
|
||||
<Flex itemGutter="double">
|
||||
<HorizontalGutter>
|
||||
<Localized id="configure-general-staff-label">
|
||||
<InputLabel>Badge text</InputLabel>
|
||||
</Localized>
|
||||
<Localized id="configure-general-staff-input">
|
||||
<TextField
|
||||
className={styles.textInput}
|
||||
id={input.name}
|
||||
type="text"
|
||||
fullWidth
|
||||
placeholder="E.g. Staff"
|
||||
disabled={disabled}
|
||||
{...input}
|
||||
/>
|
||||
</Localized>
|
||||
<ValidationMessage fullWidth meta={meta} />
|
||||
</HorizontalGutter>
|
||||
<HorizontalGutter>
|
||||
<Localized id="configure-general-staff-preview">
|
||||
<Typography variant="heading3">Preview</Typography>
|
||||
</Localized>
|
||||
{input.value && <Tag>{input.value}</Tag>}
|
||||
</HorizontalGutter>
|
||||
</Flex>
|
||||
)}
|
||||
</Field>
|
||||
</FormField>
|
||||
</HorizontalGutter>
|
||||
</SectionContent>
|
||||
</HorizontalGutter>
|
||||
);
|
||||
|
||||
export default StaffConfig;
|
||||
@@ -0,0 +1,37 @@
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { StaffConfigContainer_settings as SettingsData } from "coral-admin/__generated__/StaffConfigContainer_settings.graphql";
|
||||
import { withFragmentContainer } from "coral-framework/lib/relay";
|
||||
|
||||
import StaffConfig from "./StaffConfig";
|
||||
|
||||
interface Props {
|
||||
settings: SettingsData;
|
||||
onInitValues: (values: SettingsData) => void;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
class StaffConfigContainer extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
props.onInitValues(props.settings);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { disabled, settings } = this.props;
|
||||
return <StaffConfig settings={settings} disabled={disabled} />;
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
settings: graphql`
|
||||
fragment StaffConfigContainer_settings on Settings {
|
||||
staff {
|
||||
label
|
||||
}
|
||||
}
|
||||
`,
|
||||
})(StaffConfigContainer);
|
||||
|
||||
export default enhanced;
|
||||
@@ -1063,6 +1063,76 @@ each other's comments.
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset
|
||||
className="FieldSet-root Box-root HorizontalGutter-root HorizontalGutter-oneAndAHalf"
|
||||
>
|
||||
<legend
|
||||
className="Box-root Typography-root Typography-heading3 Typography-colorTextPrimary Header-root"
|
||||
>
|
||||
Staff member badge
|
||||
</legend>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root SectionContent-sectionContent HorizontalGutter-double"
|
||||
>
|
||||
<p
|
||||
className="Box-root Typography-root Typography-bodyShort Typography-colorTextPrimary"
|
||||
>
|
||||
Show a custom badge for staff members of your organization. This badge
|
||||
appears on the comment stream and in the admin interface.
|
||||
</p>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex Flex-doubleItemGutter gutter"
|
||||
>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<label
|
||||
className="Box-root Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
|
||||
>
|
||||
Badge text
|
||||
</label>
|
||||
<div
|
||||
className="TextField-root TextField-fullWidth StaffConfig-textInput"
|
||||
>
|
||||
<input
|
||||
className="TextField-input TextField-colorRegular"
|
||||
disabled={false}
|
||||
id="staff.label"
|
||||
name="staff.label"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
placeholder="E.g. Staff"
|
||||
type="text"
|
||||
value="Staff"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<h1
|
||||
className="Box-root Typography-root Typography-heading3 Typography-colorTextPrimary"
|
||||
>
|
||||
Preview
|
||||
</h1>
|
||||
<span
|
||||
className="Tag-root Tag-colorGrey Tag-uppercase"
|
||||
>
|
||||
Staff
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -50,6 +50,9 @@ export const settings = createFixture<GQLSettings>({
|
||||
timeout: 604800,
|
||||
message: "Comments are closed on this story.",
|
||||
},
|
||||
staff: {
|
||||
label: "Staff",
|
||||
},
|
||||
reaction: {
|
||||
label: "Reaction",
|
||||
labelActive: "reacted",
|
||||
|
||||
@@ -253,6 +253,7 @@ export class CommentContainer extends Component<Props, State> {
|
||||
<UserTagsContainer
|
||||
className={CLASSES.comment.topBar.userTag}
|
||||
comment={comment}
|
||||
settings={settings}
|
||||
/>
|
||||
<UserBadgesContainer
|
||||
className={CLASSES.comment.topBar.userBadge}
|
||||
@@ -445,6 +446,7 @@ const enhanced = withSetCommentIDMutation(
|
||||
...ReactionButtonContainer_settings
|
||||
...ReplyCommentFormContainer_settings
|
||||
...EditCommentFormContainer_settings
|
||||
...UserTagsContainer_settings
|
||||
}
|
||||
`,
|
||||
})(CommentContainer)
|
||||
|
||||
@@ -1,27 +1,25 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { FunctionComponent } from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import withFragmentContainer from "coral-framework/lib/relay/withFragmentContainer";
|
||||
import { UserTagsContainer_comment as CommentData } from "coral-stream/__generated__/UserTagsContainer_comment.graphql";
|
||||
import { UserTagsContainer_comment } from "coral-stream/__generated__/UserTagsContainer_comment.graphql";
|
||||
import { UserTagsContainer_settings } from "coral-stream/__generated__/UserTagsContainer_settings.graphql";
|
||||
import { Tag } from "coral-ui/components";
|
||||
|
||||
interface Props {
|
||||
comment: CommentData;
|
||||
comment: UserTagsContainer_comment;
|
||||
settings: UserTagsContainer_settings;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const UserTagsContainer: FunctionComponent<Props> = props => {
|
||||
const { comment } = props;
|
||||
const UserTagsContainer: FunctionComponent<Props> = ({
|
||||
settings,
|
||||
comment,
|
||||
className,
|
||||
}) => {
|
||||
const staffTag = comment.tags.find(t => t.code === "STAFF");
|
||||
return (
|
||||
<>
|
||||
{staffTag && (
|
||||
<Localized id="comments-staffTag">
|
||||
<Tag className={props.className}>Staff</Tag>
|
||||
</Localized>
|
||||
)}
|
||||
</>
|
||||
<>{staffTag && <Tag className={className}>{settings.staff.label}</Tag>}</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -33,6 +31,13 @@ const enhanced = withFragmentContainer<Props>({
|
||||
}
|
||||
}
|
||||
`,
|
||||
settings: graphql`
|
||||
fragment UserTagsContainer_settings on Settings {
|
||||
staff {
|
||||
label
|
||||
}
|
||||
}
|
||||
`,
|
||||
})(UserTagsContainer);
|
||||
|
||||
export default enhanced;
|
||||
|
||||
+49
@@ -143,6 +143,13 @@ exports[`hide reply button 1`] = `
|
||||
"tags": Array [],
|
||||
}
|
||||
}
|
||||
settings={
|
||||
Object {
|
||||
"disableCommenting": Object {
|
||||
"enabled": false,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Relay(AuthorBadgesContainer)
|
||||
className="coral coral-userBadge coral-comment-userBadge"
|
||||
@@ -326,6 +333,13 @@ exports[`renders body only 1`] = `
|
||||
"tags": Array [],
|
||||
}
|
||||
}
|
||||
settings={
|
||||
Object {
|
||||
"disableCommenting": Object {
|
||||
"enabled": false,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Relay(AuthorBadgesContainer)
|
||||
className="coral coral-userBadge coral-comment-userBadge"
|
||||
@@ -509,6 +523,13 @@ exports[`renders disabled reply when commenting has been disabled 1`] = `
|
||||
"tags": Array [],
|
||||
}
|
||||
}
|
||||
settings={
|
||||
Object {
|
||||
"disableCommenting": Object {
|
||||
"enabled": true,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Relay(AuthorBadgesContainer)
|
||||
className="coral coral-userBadge coral-comment-userBadge"
|
||||
@@ -692,6 +713,13 @@ exports[`renders disabled reply when story is closed 1`] = `
|
||||
"tags": Array [],
|
||||
}
|
||||
}
|
||||
settings={
|
||||
Object {
|
||||
"disableCommenting": Object {
|
||||
"enabled": false,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Relay(AuthorBadgesContainer)
|
||||
className="coral coral-userBadge coral-comment-userBadge"
|
||||
@@ -887,6 +915,13 @@ exports[`renders in reply to 1`] = `
|
||||
"tags": Array [],
|
||||
}
|
||||
}
|
||||
settings={
|
||||
Object {
|
||||
"disableCommenting": Object {
|
||||
"enabled": false,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Relay(AuthorBadgesContainer)
|
||||
className="coral coral-userBadge coral-comment-userBadge"
|
||||
@@ -1074,6 +1109,13 @@ exports[`renders username and body 1`] = `
|
||||
"tags": Array [],
|
||||
}
|
||||
}
|
||||
settings={
|
||||
Object {
|
||||
"disableCommenting": Object {
|
||||
"enabled": false,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Relay(AuthorBadgesContainer)
|
||||
className="coral coral-userBadge coral-comment-userBadge"
|
||||
@@ -1272,6 +1314,13 @@ exports[`shows conversation link 1`] = `
|
||||
"tags": Array [],
|
||||
}
|
||||
}
|
||||
settings={
|
||||
Object {
|
||||
"disableCommenting": Object {
|
||||
"enabled": false,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Relay(AuthorBadgesContainer)
|
||||
className="coral coral-userBadge coral-comment-userBadge"
|
||||
|
||||
@@ -100,6 +100,7 @@ class ConversationThreadContainer extends React.Component<
|
||||
<UserTagsContainer
|
||||
className={CLASSES.conversationThread.rootParent.userTag}
|
||||
comment={rootParent}
|
||||
settings={settings}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
@@ -192,6 +193,7 @@ const enhanced = withContext(ctx => ({
|
||||
fragment ConversationThreadContainer_settings on Settings {
|
||||
...CommentContainer_settings
|
||||
...LocalReplyListContainer_settings
|
||||
...UserTagsContainer_settings
|
||||
}
|
||||
`,
|
||||
comment: graphql`
|
||||
|
||||
+2
@@ -71,6 +71,7 @@ const FeaturedCommentContainer: FunctionComponent<Props> = props => {
|
||||
<UserTagsContainer
|
||||
className={CLASSES.featuredComment.authorBar.userTag}
|
||||
comment={comment}
|
||||
settings={settings}
|
||||
/>
|
||||
</Box>
|
||||
<Box ml={2} clone>
|
||||
@@ -174,6 +175,7 @@ const enhanced = withSetCommentIDMutation(
|
||||
settings: graphql`
|
||||
fragment FeaturedCommentContainer_settings on Settings {
|
||||
...ReactionButtonContainer_settings
|
||||
...UserTagsContainer_settings
|
||||
}
|
||||
`,
|
||||
})(FeaturedCommentContainer)
|
||||
|
||||
@@ -88,6 +88,9 @@ export const settings = createFixture<GQLSettings>({
|
||||
},
|
||||
},
|
||||
},
|
||||
staff: {
|
||||
label: "Staff",
|
||||
},
|
||||
reaction: {
|
||||
icon: "thumb_up",
|
||||
label: "Respect",
|
||||
|
||||
@@ -1085,16 +1085,29 @@ type CommenterAccountFeatures {
|
||||
changeUsername when true, non-sso user may change username every 14 days
|
||||
"""
|
||||
changeUsername: Boolean!
|
||||
|
||||
"""
|
||||
downloadComments when true, user may download their comment history
|
||||
"""
|
||||
downloadComments: Boolean!
|
||||
|
||||
"""
|
||||
deleteAccount when true, non-sso user may permanently delete their account
|
||||
"""
|
||||
deleteAccount: Boolean!
|
||||
}
|
||||
|
||||
"""
|
||||
StaffConfiguration specifies the configuration for the staff badges assigned to
|
||||
users with any role above COMMENTER.
|
||||
"""
|
||||
type StaffConfiguration {
|
||||
"""
|
||||
label is the string used when displaying the STAFF badge to users.
|
||||
"""
|
||||
label: String!
|
||||
}
|
||||
|
||||
"""
|
||||
Settings stores the global settings for a given Tenant.
|
||||
"""
|
||||
@@ -1210,6 +1223,12 @@ type Settings {
|
||||
"""
|
||||
reaction: ReactionConfiguration!
|
||||
|
||||
"""
|
||||
staff specifies the configuration for the staff badges assigned to users with
|
||||
any role above COMMENTER.
|
||||
"""
|
||||
staff: StaffConfiguration!
|
||||
|
||||
"""
|
||||
accountFeatures stores the configuration for commenter account features.
|
||||
"""
|
||||
@@ -3280,8 +3299,8 @@ input StoryConfigurationInput {
|
||||
}
|
||||
|
||||
"""
|
||||
ReactionConfigurationInput stores the configuration for reactions used across this
|
||||
Tenant.
|
||||
ReactionConfigurationInput stores the configuration for reactions used across
|
||||
this Tenant.
|
||||
"""
|
||||
input ReactionConfigurationInput {
|
||||
"""
|
||||
@@ -3318,6 +3337,17 @@ input ReactionConfigurationInput {
|
||||
color: String
|
||||
}
|
||||
|
||||
"""
|
||||
StaffConfigurationInput specifies the configuration for the staff badges
|
||||
assigned to users with any role above COMMENTER.
|
||||
"""
|
||||
input StaffConfigurationInput {
|
||||
"""
|
||||
label is the string used when displaying the STAFF badge to users.
|
||||
"""
|
||||
label: String
|
||||
}
|
||||
|
||||
"""
|
||||
CommenterAccountFeatures stores the configuration for commenter account features.
|
||||
"""
|
||||
@@ -3435,6 +3465,12 @@ input SettingsInput {
|
||||
"""
|
||||
reaction: ReactionConfigurationInput
|
||||
|
||||
"""
|
||||
staff specifies the configuration for the staff badges assigned to users with
|
||||
any role above COMMENTER.
|
||||
"""
|
||||
staff: StaffConfigurationInput
|
||||
|
||||
"""
|
||||
accountFeatures specifies the configuration for accounts.
|
||||
"""
|
||||
|
||||
@@ -158,7 +158,10 @@ class Server {
|
||||
);
|
||||
|
||||
// Create the migration manager.
|
||||
this.migrationManager = new MigrationManager(this.tenantCache);
|
||||
this.migrationManager = new MigrationManager({
|
||||
tenantCache: this.tenantCache,
|
||||
i18n: this.i18n,
|
||||
});
|
||||
|
||||
// Load and upsert the persisted queries.
|
||||
this.persistedQueryCache = new PersistedQueryCache({ mongo: this.mongo });
|
||||
@@ -198,6 +201,7 @@ class Server {
|
||||
// Run migrations if there is already a Tenant installed.
|
||||
if (await isInstalled(this.tenantCache)) {
|
||||
await this.migrationManager.executePendingMigrations(this.mongo);
|
||||
await this.tenantCache.primeAll();
|
||||
} else {
|
||||
logger.info("no tenants are installed, skipping running migrations");
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
staff-label = Personale
|
||||
@@ -11,3 +11,5 @@ comment-count =
|
||||
[one] Comment
|
||||
*[other] Comments
|
||||
}</span>
|
||||
|
||||
staff-label = Staff
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
staff-label = Miembro del equipo
|
||||
@@ -0,0 +1 @@
|
||||
staff-label = Staf
|
||||
@@ -4,3 +4,4 @@ disableCommentingDefaultMessage = Comentários foram fechados nessa história.
|
||||
reaction-labelRespect = Respeitar
|
||||
reaction-labelActiveRespected = Respeitado
|
||||
reaction-sortLabelMostRespected = Respeitados
|
||||
staff-label = Staff
|
||||
|
||||
@@ -94,6 +94,7 @@ export type Settings = GlobalModerationSettings &
|
||||
| "wordList"
|
||||
| "integrations"
|
||||
| "reaction"
|
||||
| "staff"
|
||||
| "editCommentWindowLength"
|
||||
| "customCSSURL"
|
||||
| "communityGuidelines"
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import crypto from "crypto";
|
||||
import { FluentBundle } from "fluent/compat";
|
||||
|
||||
import { GQLReactionConfiguration } from "coral-server/graph/tenant/schema/__generated__/types";
|
||||
import {
|
||||
GQLReactionConfiguration,
|
||||
GQLStaffConfiguration,
|
||||
} from "coral-server/graph/tenant/schema/__generated__/types";
|
||||
import { translate } from "coral-server/services/i18n";
|
||||
|
||||
export const getDefaultReactionConfiguration = (
|
||||
@@ -19,6 +22,12 @@ export const getDefaultReactionConfiguration = (
|
||||
icon: "thumb_up",
|
||||
});
|
||||
|
||||
export const getDefaultStaffConfiguration = (
|
||||
bundle: FluentBundle
|
||||
): GQLStaffConfiguration => ({
|
||||
label: translate(bundle, "Staff", "staff-label"),
|
||||
});
|
||||
|
||||
export function generateSSOKey() {
|
||||
// Generate a new key. We generate a key of minimum length 32 up to 37 bytes,
|
||||
// as 16 was the minimum length recommended.
|
||||
|
||||
@@ -13,7 +13,11 @@ import { Settings } from "coral-server/models/settings";
|
||||
import { I18n } from "coral-server/services/i18n";
|
||||
import { tenants as collection } from "coral-server/services/mongodb/collections";
|
||||
|
||||
import { generateSSOKey, getDefaultReactionConfiguration } from "./helpers";
|
||||
import {
|
||||
generateSSOKey,
|
||||
getDefaultReactionConfiguration,
|
||||
getDefaultStaffConfiguration,
|
||||
} from "./helpers";
|
||||
|
||||
/**
|
||||
* TenantResource references a given resource that should be owned by a specific
|
||||
@@ -64,6 +68,9 @@ export async function createTenant(
|
||||
input: CreateTenantInput,
|
||||
now = new Date()
|
||||
) {
|
||||
// Get the tenant's bundle.
|
||||
const bundle = i18n.getBundle(input.locale);
|
||||
|
||||
const defaults: Sub<Tenant, CreateTenantInput> = {
|
||||
// Create a new ID.
|
||||
id: uuid.v4(),
|
||||
@@ -168,7 +175,8 @@ export async function createTenant(
|
||||
doNotStore: true,
|
||||
},
|
||||
},
|
||||
reaction: getDefaultReactionConfiguration(i18n.getBundle(input.locale)),
|
||||
reaction: getDefaultReactionConfiguration(bundle),
|
||||
staff: getDefaultStaffConfiguration(bundle),
|
||||
stories: {
|
||||
scraping: {
|
||||
enabled: true,
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
retrieveAllMigrationRecords,
|
||||
startMigration,
|
||||
} from "coral-server/models/migration";
|
||||
import { I18n } from "coral-server/services/i18n";
|
||||
import TenantCache from "coral-server/services/tenant/cache";
|
||||
|
||||
import {
|
||||
@@ -23,16 +24,21 @@ import Migration from "./migration";
|
||||
// Extract the id from the filename with this regex.
|
||||
const fileNamePattern = /^(\d+)_([\S_]+)\.[tj]s$/;
|
||||
|
||||
export interface ManagerOptions {
|
||||
tenantCache: TenantCache;
|
||||
i18n: I18n;
|
||||
}
|
||||
|
||||
export default class Manager {
|
||||
private clientID: string;
|
||||
private migrations: Migration[];
|
||||
private tenants: TenantCache;
|
||||
private tenantCache: TenantCache;
|
||||
private ran: boolean = false;
|
||||
|
||||
constructor(tenants: TenantCache) {
|
||||
constructor({ tenantCache, i18n }: ManagerOptions) {
|
||||
this.clientID = uuid.v4();
|
||||
this.migrations = [];
|
||||
this.tenants = tenants;
|
||||
this.tenantCache = tenantCache;
|
||||
|
||||
const fileNames = fs.readdirSync(path.join(__dirname, "migrations"));
|
||||
for (const fileName of fileNames) {
|
||||
@@ -60,9 +66,10 @@ export default class Manager {
|
||||
throw new Error("fileName format is invalid");
|
||||
}
|
||||
const id = parseInt(matches[1], 10);
|
||||
const name = matches[2];
|
||||
|
||||
// Create the migration instance.
|
||||
const migration = new m.default(id, matches[2]);
|
||||
const migration = new m.default({ id, name, i18n });
|
||||
|
||||
// Insert the migration into the migrations array.
|
||||
if (!(migration instanceof Migration)) {
|
||||
@@ -179,7 +186,7 @@ export default class Manager {
|
||||
|
||||
if (migration.up) {
|
||||
// The migration provides an up method, we should run this per Tenant.
|
||||
for await (const tenant of this.tenants) {
|
||||
for await (const tenant of this.tenantCache) {
|
||||
log = log.child({ tenantID: tenant.id }, true);
|
||||
|
||||
const migrationStartTime = now();
|
||||
|
||||
@@ -2,6 +2,7 @@ import Logger from "bunyan";
|
||||
import { Db } from "mongodb";
|
||||
|
||||
import logger from "coral-server/logger";
|
||||
import { I18n } from "coral-server/services/i18n";
|
||||
|
||||
interface Migration {
|
||||
indexes?(mongo: Db): Promise<void>;
|
||||
@@ -10,18 +11,26 @@ interface Migration {
|
||||
down?(mongo: Db, tenantID: string): Promise<void>;
|
||||
}
|
||||
|
||||
export interface MigrationOptions {
|
||||
id: number;
|
||||
name: string;
|
||||
i18n: I18n;
|
||||
}
|
||||
|
||||
abstract class Migration {
|
||||
public readonly name: string;
|
||||
public readonly id: number;
|
||||
public readonly logger: Logger;
|
||||
public readonly i18n: I18n;
|
||||
|
||||
constructor(version: number, name: string) {
|
||||
this.id = version;
|
||||
constructor({ id, name, i18n }: MigrationOptions) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.i18n = i18n;
|
||||
this.logger = logger.child(
|
||||
{
|
||||
migrationName: this.name,
|
||||
migrationVersion: this.id,
|
||||
migrationID: this.id,
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Db } from "mongodb";
|
||||
|
||||
import { getDefaultStaffConfiguration } from "coral-server/models/tenant";
|
||||
import Migration from "coral-server/services/migrate/migration";
|
||||
import collections from "coral-server/services/mongodb/collections";
|
||||
|
||||
import { MigrationError } from "../error";
|
||||
|
||||
export default class extends Migration {
|
||||
private async getTenant(mongo: Db, id: string) {
|
||||
const tenant = await collections.tenants(mongo).findOne({ id });
|
||||
if (!tenant) {
|
||||
throw new MigrationError(id, "tenant not found", "tenants", [id]);
|
||||
}
|
||||
|
||||
return tenant;
|
||||
}
|
||||
|
||||
public async test(mongo: Db, id: string) {
|
||||
const tenant = await this.getTenant(mongo, id);
|
||||
if (!tenant.staff) {
|
||||
throw new MigrationError(id, "staff not set", "tenants", [id]);
|
||||
}
|
||||
}
|
||||
|
||||
public async up(mongo: Db, id: string) {
|
||||
const tenant = await this.getTenant(mongo, id);
|
||||
const bundle = this.i18n.getBundle(tenant.locale);
|
||||
await collections.tenants(mongo).updateOne(
|
||||
{ id, staff: null },
|
||||
{
|
||||
$set: {
|
||||
staff: getDefaultStaffConfiguration(bundle),
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
+11
-12
@@ -114,8 +114,8 @@ comments-userPopover-ignore = Ignorere
|
||||
|
||||
comments-userIgnorePopover-ignoreUser = Ignorerer {$username}?
|
||||
comments-userIgnorePopover-description =
|
||||
Når du ignorerer en kommentator, vil alle kommentarer,
|
||||
de skrev på webstedet, være skjult for dig. Du kan fortryde
|
||||
Når du ignorerer en kommentator, vil alle kommentarer,
|
||||
de skrev på webstedet, være skjult for dig. Du kan fortryde
|
||||
dette senere fra Min profil.
|
||||
comments-userIgnorePopover-ignore = Ignorerer
|
||||
comments-userIgnorePopover-cancel = Annuller
|
||||
@@ -136,7 +136,6 @@ comments-rejectedTombstone =
|
||||
Du har afvist denne kommentar. <TextLink>Gå til moderation for at gennemgå denne beslutning.</TextLink>
|
||||
|
||||
comments-featuredTag = Fremhævet
|
||||
comments-staffTag = Personale
|
||||
|
||||
### Featured Comments
|
||||
comments-featured-gotoConversation = Gå til samtale
|
||||
@@ -196,7 +195,7 @@ comments-reportPopover-submit = Indsend
|
||||
comments-reportPopover-thankYou = Mange tak
|
||||
comments-reportPopover-receivedMessage =
|
||||
Vi har modtaget din besked.
|
||||
Rapporteringer fra medlemmer som dig,
|
||||
Rapporteringer fra medlemmer som dig,
|
||||
hjælper til at holde en god tone.
|
||||
|
||||
comments-reportPopover-dismiss = Luk
|
||||
@@ -228,8 +227,8 @@ configure-liveUpdates-description =
|
||||
|
||||
configure-messageBox-title = Aktivér meddelelsesboks til denne stream
|
||||
configure-messageBox-description =
|
||||
Tilføj en besked øverst i kommentarfeltet for dine læsere. Brug dette til at stille et emne,
|
||||
stille et spørgsmål eller offentliggøre meddelelser vedrørende denne historie.
|
||||
Tilføj en besked øverst i kommentarfeltet for dine læsere. Brug dette til at stille et emne,
|
||||
stille et spørgsmål eller offentliggøre meddelelser vedrørende denne historie.
|
||||
configure-messageBox-preview = Forhåndsvisning
|
||||
configure-messageBox-selectAnIcon = Vælg et ikon
|
||||
configure-messageBox-noIcon = Intet ikon
|
||||
@@ -237,8 +236,8 @@ configure-messageBox-writeAMessage = Skriv en besked
|
||||
|
||||
configure-closeStream-title = Luk kommentarer
|
||||
configure-closeStream-description =
|
||||
Kommentarerne er i øjeblikket åbne.
|
||||
Ved at lukke kommentarerne kan der ikke indsendes nye kommentarer,
|
||||
Kommentarerne er i øjeblikket åbne.
|
||||
Ved at lukke kommentarerne kan der ikke indsendes nye kommentarer,
|
||||
og alle tidligere indsendte kommentarer vises stadig.
|
||||
configure-closeStream-closeStream = Luk kommentarer
|
||||
|
||||
@@ -251,8 +250,8 @@ configure-openStream-openStream = Åbn kommentarer
|
||||
comments-tombstone-ignore = Denne kommentar er skjult, fordi du ignorerede {$username}
|
||||
|
||||
suspendInfo-heading = Din konto er midlertidigt suspenderet for at kommentere.
|
||||
suspendInfo-info =
|
||||
I overensstemmelse med fællesskabsretningslinjerne for { $organization}
|
||||
er din konto midlertidigt suspenderet. Mens du er suspenderet,
|
||||
kan du ikke kommentere, respektere eller rapportere kommentarer.
|
||||
suspendInfo-info =
|
||||
I overensstemmelse med fællesskabsretningslinjerne for { $organization}
|
||||
er din konto midlertidigt suspenderet. Mens du er suspenderet,
|
||||
kan du ikke kommentere, respektere eller rapportere kommentarer.
|
||||
Gå igen med til samtalen på { $until }.
|
||||
|
||||
@@ -541,10 +541,10 @@ moderate-user-drawer-recent-history-tooltip-button =
|
||||
.aria-label = Toggle recent comment history tooltip
|
||||
moderate-user-drawer-recent-history-tooltip-submitted = Submitted
|
||||
|
||||
moderate-user-drawer-notes-field =
|
||||
moderate-user-drawer-notes-field =
|
||||
.placeholder = Leave a note...
|
||||
moderate-user-drawer-notes-button = Add note
|
||||
moderatorNote-left-by = Left by:
|
||||
moderatorNote-left-by = Left by:
|
||||
moderatorNote-delete = Delete
|
||||
|
||||
## Create Username
|
||||
@@ -780,6 +780,15 @@ configure-general-reactions-sort-input =
|
||||
configure-general-reactions-preview = Preview
|
||||
configure-general-reaction-sortMenu-sortBy = Sort by
|
||||
|
||||
configure-general-staff-title = Staff member badge
|
||||
configure-general-staff-explanation =
|
||||
Show a custom badge for staff members of your organization. This badge
|
||||
appears on the comment stream and in the admin interface.
|
||||
configure-general-staff-label = Badge text
|
||||
configure-general-staff-input =
|
||||
.placeholder = E.g. Staff
|
||||
configure-general-staff-preview = Preview
|
||||
|
||||
configure-account-features-title = Commenter account management features
|
||||
configure-account-features-explanation =
|
||||
You can enable and disable certain features for your commenters to use
|
||||
|
||||
@@ -157,7 +157,6 @@ comments-rejectedTombstone =
|
||||
You have rejected this comment. <TextLink>Go to Moderate to review this decision.</TextLink>
|
||||
|
||||
comments-featuredTag = Featured
|
||||
comments-staffTag = Staff
|
||||
|
||||
### Account Deletion Stream
|
||||
|
||||
|
||||
@@ -135,7 +135,6 @@ comments-rejectedTombstone =
|
||||
Has rechazado este comentario.<TextLink>Vaya a moderación para revisar esta decisión.</TextLink>
|
||||
|
||||
comments-featuredTag = Resaltado
|
||||
comments-staffTag = Miembro del equipo
|
||||
|
||||
### Featured Comments
|
||||
comments-featured-gotoConversation = Ir a conversación
|
||||
|
||||
@@ -135,7 +135,6 @@ comments-rejectedTombstone =
|
||||
U heeft deze reactie afgewezen. <TextLink>Ga naar modereren om dit besluit te herzien.</TextLink>
|
||||
|
||||
comments-featuredTag = Uitgelicht
|
||||
comments-staffTag = Staf
|
||||
|
||||
### Featured Comments
|
||||
comments-featured-gotoConversation = Ga naar het gesprek
|
||||
|
||||
@@ -149,7 +149,6 @@ comments-rejectedTombstone =
|
||||
Você rejeitou este comentário. <TextLink> Vá para Moderados para rever esta decisão. </TextLink>
|
||||
|
||||
comments-featuredTag = Destaques
|
||||
comments-staffTag = Staff
|
||||
|
||||
### Account Deletion Stream
|
||||
|
||||
|
||||
Reference in New Issue
Block a user