5.4.0 Release Bug Fixes (#2789)

* fix: addresses CORL-848

Fixed copy for new commenters feature.

* fix: address CORL-847

Revert the line hight changes on select fields for now.

* fix: addressed CORL-851

Changed copy on CSS field.

* fix: addressed CORL-840

Changed deletion window to 24 hours.
Refactored durations to use TIME enum.
This commit is contained in:
Wyatt Johnson
2020-01-14 16:39:21 +00:00
committed by GitHub
parent a88644d98e
commit 9b8ab6de5f
36 changed files with 1061 additions and 1063 deletions
@@ -2,7 +2,8 @@ import { Localized } from "@fluent/react/compat";
import cn from "classnames";
import React, { FunctionComponent } from "react";
import { reduceSeconds, UNIT } from "coral-common/helpers/i18n";
import { reduceSeconds } from "coral-common/helpers/i18n";
import TIME from "coral-common/time";
import {
Flex,
HorizontalGutter,
@@ -25,7 +26,7 @@ const RecentHistory: FunctionComponent<Props> = ({
rejectionRate,
submitted,
}) => {
const { scaled, unit } = reduceSeconds(timeFrame, [UNIT.DAYS]);
const { scaled, unit } = reduceSeconds(timeFrame, [TIME.DAY]);
return (
<HorizontalGutter spacing={2}>
@@ -30,13 +30,10 @@ const CustomCSSConfig: FunctionComponent<Props> = ({ disabled }) => (
}
>
<FormField>
<Localized
id="configure-advanced-customCSS-explanation"
strong={<strong />}
>
<Localized id="configure-advanced-customCSS-override" strong={<strong />}>
<FormFieldDescription>
URL of a CSS stylesheet that will override default Embed Stream
styles. Can be internal or external.
styles.
</FormFieldDescription>
</Localized>
<Field name="customCSSURL" parse={parseEmptyAsNull} format={formatEmpty}>
@@ -80,9 +80,9 @@ const ClosingCommentStreamsConfig: FunctionComponent<Props> = ({
<DurationField
{...input}
units={[
DURATION_UNIT.HOURS,
DURATION_UNIT.DAYS,
DURATION_UNIT.WEEKS,
DURATION_UNIT.HOUR,
DURATION_UNIT.DAY,
DURATION_UNIT.WEEK,
]}
disabled={disabled}
color={colorFromMeta(meta)}
@@ -67,9 +67,9 @@ const CommentEditingConfig: FunctionComponent<Props> = ({ disabled }) => (
<DurationField
{...input}
units={[
DURATION_UNIT.SECONDS,
DURATION_UNIT.MINUTES,
DURATION_UNIT.HOURS,
DURATION_UNIT.SECOND,
DURATION_UNIT.MINUTE,
DURATION_UNIT.HOUR,
]}
color={colorFromMeta(meta)}
disabled={disabled}
@@ -60,7 +60,7 @@ const NewCommentersConfig: FunctionComponent<Props> = ({ disabled }) => {
</FormField>
<FormField>
<Localized id="configure-moderation-newCommenters-approvedCommentsThreshold">
<Label>Number of first comments sent for approval</Label>
<Label>Number of comments that must be approved</Label>
</Localized>
<Field
name="newCommenters.approvedCommentsThreshold"
@@ -77,7 +77,7 @@ const RecentCommentHistoryConfig: FunctionComponent<Props> = ({ disabled }) => {
{({ input, meta }) => (
<>
<DurationField
units={[DURATION_UNIT.DAYS]}
units={[DURATION_UNIT.DAY]}
disabled={disabled}
color={hasError(meta) ? "error" : "regular"}
{...input}
@@ -234,7 +234,7 @@ each of your sites stories.
<p
className="FormFieldDescription-root"
>
URL of a CSS stylesheet that will override default Embed Stream styles. Can be internal or external.
URL of a CSS stylesheet that will override default Embed Stream styles.
</p>
<div
className="Box-root HorizontalGutter-root HorizontalGutter-spacing-2"
@@ -367,7 +367,7 @@ for moderator approval before publication.
<label
className="Label-root"
>
Number of first comments sent for approval
Number of comments that must be approved
</label>
<div
className="TextField-root"
+5 -5
View File
@@ -1,7 +1,8 @@
import {
DEFAULT_SESSION_LENGTH,
DEFAULT_SESSION_DURATION,
TOXICITY_THRESHOLD_DEFAULT,
} from "coral-common/constants";
import TIME from "coral-common/time";
import { pureMerge } from "coral-common/utils";
import {
GQLComment,
@@ -77,8 +78,7 @@ export const settings = createFixture<GQLSettings>({
},
recentCommentHistory: {
enabled: false,
// 7 days in seconds.
timeFrame: 604800,
timeFrame: 7 * TIME.DAY,
// Rejection rate defaulting to 30%, once exceeded, comments will be
// pre-moderated.
triggerRejectionRate: 0.3,
@@ -94,7 +94,7 @@ export const settings = createFixture<GQLSettings>({
},
},
auth: {
sessionDuration: DEFAULT_SESSION_LENGTH,
sessionDuration: DEFAULT_SESSION_DURATION,
integrations: {
local: {
enabled: true,
@@ -175,7 +175,7 @@ export const settingsWithEmptyAuth = createFixture<GQLSettings>(
{
id: "settings",
auth: {
sessionDuration: DEFAULT_SESSION_LENGTH,
sessionDuration: DEFAULT_SESSION_DURATION,
integrations: {
local: {
enabled: true,
@@ -24,7 +24,7 @@ it("renders correctly with specified units", () => {
value: "",
disabled: false,
onChange: noop,
units: [DURATION_UNIT.SECONDS, DURATION_UNIT.HOURS],
units: [DURATION_UNIT.SECOND, DURATION_UNIT.HOUR],
};
const renderer = createRenderer();
renderer.render(<DurationField {...props} />);
@@ -37,7 +37,7 @@ it("use best matching unit", () => {
value: "3600",
disabled: false,
onChange: noop,
units: [DURATION_UNIT.SECONDS, DURATION_UNIT.MINUTES, DURATION_UNIT.HOURS],
units: [DURATION_UNIT.SECOND, DURATION_UNIT.MINUTE, DURATION_UNIT.HOUR],
};
const renderer = createRenderer();
renderer.render(<DurationField {...props} />);
@@ -50,7 +50,7 @@ it("use initial unit if 0", () => {
value: "0",
disabled: false,
onChange: noop,
units: [DURATION_UNIT.SECONDS, DURATION_UNIT.MINUTES, DURATION_UNIT.HOURS],
units: [DURATION_UNIT.SECOND, DURATION_UNIT.MINUTE, DURATION_UNIT.HOUR],
};
const renderer = createRenderer();
renderer.render(<DurationField {...props} />);
@@ -63,7 +63,7 @@ it("accepts invalid input", () => {
value: "this is so invalid",
disabled: false,
onChange: noop,
units: [DURATION_UNIT.SECONDS, DURATION_UNIT.MINUTES, DURATION_UNIT.HOURS],
units: [DURATION_UNIT.SECOND, DURATION_UNIT.MINUTE, DURATION_UNIT.HOUR],
};
const renderer = createRenderer();
renderer.render(<DurationField {...props} />);
@@ -8,7 +8,7 @@ import React, {
useState,
} from "react";
import { UNIT } from "coral-common/helpers/i18n";
import TIME from "coral-common/time";
import {
Flex,
Option,
@@ -23,14 +23,14 @@ import styles from "./DurationField.css";
* DURATION_UNIT are units that can be used in the
* DurationField components.
*/
export const DURATION_UNIT = UNIT;
export const DURATION_UNIT = TIME;
const DURATION_UNIT_MAP = {
[DURATION_UNIT.SECONDS]: "second",
[DURATION_UNIT.MINUTES]: "minute",
[DURATION_UNIT.HOURS]: "hour",
[DURATION_UNIT.DAYS]: "day",
[DURATION_UNIT.WEEKS]: "week",
[DURATION_UNIT.SECOND]: "second",
[DURATION_UNIT.MINUTE]: "minute",
[DURATION_UNIT.HOUR]: "hour",
[DURATION_UNIT.DAY]: "day",
[DURATION_UNIT.WEEK]: "week",
};
interface Props {
@@ -39,18 +39,18 @@ interface Props {
disabled: boolean;
onChange: (v: string) => void;
/** Specifiy units to include */
units?: ReadonlyArray<UNIT>;
units?: ReadonlyArray<TIME>;
}
function convertToSeconds(value: string, unit?: UNIT) {
function convertToSeconds(value: string, unit?: TIME) {
const parsed = parseInt(value, 10);
return (isNaN(parsed) || !unit ? value : parsed * unit).toString();
}
function convertFromSeconds(
value: string,
units: ReadonlyArray<UNIT>,
unit?: UNIT
units: ReadonlyArray<TIME>,
unit?: TIME
) {
const parsed = parseInt(value, 10);
@@ -82,7 +82,7 @@ function convertFromSeconds(
*/
const DurationField: FunctionComponent<Props> = ({
value,
units = [UNIT.HOURS, UNIT.DAYS, UNIT.WEEKS],
units = [TIME.HOUR, TIME.DAY, TIME.WEEK],
onChange,
disabled,
name,
@@ -7,6 +7,8 @@ import {
urlMiddleware,
} from "react-relay-network-modern/es";
import TIME from "coral-common/time";
import clientIDMiddleware from "./clientIDMiddleware";
import { ManagedSubscriptionClient } from "./createManagedSubscriptionClient";
import customErrorMiddleware from "./customErrorMiddleware";
@@ -45,7 +47,7 @@ export default function createNetwork(
customErrorMiddleware,
cacheMiddleware({
size: 100, // max 100 requests
ttl: 900000, // 15 minutes
ttl: 15 * TIME.MINUTE,
clearOnMutation: true,
}),
urlMiddleware({
@@ -2,7 +2,7 @@ import { Localized } from "@fluent/react/compat";
import React, { FunctionComponent, useCallback } from "react";
import { graphql } from "react-relay";
import { SCHEDULED_DELETION_TIMESPAN_DAYS } from "coral-common/constants";
import { SCHEDULED_DELETION_WINDOW_DURATION } from "coral-common/constants";
import { useCoralContext } from "coral-framework/lib/bootstrap";
import { useMutation, withFragmentContainer } from "coral-framework/lib/relay";
import CLASSES from "coral-stream/classes";
@@ -24,8 +24,8 @@ interface Props {
viewer: StreamDeletionRequestCalloutContainer_viewer;
}
const formatter = (locales: string[], date: Date) => {
return Intl.DateTimeFormat(locales, {
const formatter = (locales: string[], date: Date) =>
Intl.DateTimeFormat(locales, {
year: "numeric",
month: "numeric",
day: "numeric",
@@ -33,11 +33,9 @@ const formatter = (locales: string[], date: Date) => {
minute: "numeric",
second: "numeric",
}).format(date);
};
const subtractDays = (date: Date, days: number) => {
const millisecondsInADay = 86400000;
return new Date(date.getTime() - days * millisecondsInADay);
const subtractSeconds = (date: Date, seconds: number) => {
return new Date(date.getTime() - seconds * 1000);
};
const StreamDeletionRequestCalloutContainer: FunctionComponent<Props> = ({
@@ -57,9 +55,9 @@ const StreamDeletionRequestCalloutContainer: FunctionComponent<Props> = ({
const requestDate = viewer.scheduledDeletionDate
? formatter(
locales,
subtractDays(
subtractSeconds(
new Date(viewer.scheduledDeletionDate),
SCHEDULED_DELETION_TIMESPAN_DAYS
SCHEDULED_DELETION_WINDOW_DURATION
)
)
: null;
@@ -9,8 +9,9 @@ import React, {
} from "react";
import { Field, Form } from "react-final-form";
import { ALLOWED_USERNAME_CHANGE_FREQUENCY } from "coral-common/constants";
import { reduceSeconds, UNIT } from "coral-common/helpers/i18n";
import { ALLOWED_USERNAME_CHANGE_TIMEFRAME_DURATION } from "coral-common/constants";
import { reduceSeconds } from "coral-common/helpers/i18n";
import TIME from "coral-common/time";
import getAuthenticationIntegrations from "coral-framework/helpers/getAuthenticationIntegrations";
import { useCoralContext } from "coral-framework/lib/bootstrap";
import { InvalidRequestError } from "coral-framework/lib/errors";
@@ -50,9 +51,10 @@ import UpdateUsernameMutation from "./UpdateUsernameMutation";
import styles from "./ChangeUsernameContainer.css";
const FREQUENCYSCALED = reduceSeconds(ALLOWED_USERNAME_CHANGE_FREQUENCY, [
UNIT.DAYS,
]);
const FREQUENCYSCALED = reduceSeconds(
ALLOWED_USERNAME_CHANGE_TIMEFRAME_DURATION,
[TIME.DAY]
);
interface Props {
viewer: ViewerData;
@@ -107,7 +109,8 @@ const ChangeUsernameContainer: FunctionComponent<Props> = ({
if (username && username.history.length > 1) {
const lastUsernameEditAllowed = new Date();
lastUsernameEditAllowed.setSeconds(
lastUsernameEditAllowed.getSeconds() - ALLOWED_USERNAME_CHANGE_FREQUENCY
lastUsernameEditAllowed.getSeconds() -
ALLOWED_USERNAME_CHANGE_TIMEFRAME_DURATION
);
const lastUsernameEdit =
username.history[username.history.length - 1].createdAt;
@@ -122,7 +125,9 @@ const ChangeUsernameContainer: FunctionComponent<Props> = ({
const date = new Date(
username.history[username.history.length - 1].createdAt
);
date.setSeconds(date.getSeconds() + ALLOWED_USERNAME_CHANGE_FREQUENCY);
date.setSeconds(
date.getSeconds() + ALLOWED_USERNAME_CHANGE_TIMEFRAME_DURATION
);
return date;
}
return null;
@@ -1,8 +1,7 @@
import { DateTime } from "luxon";
import { graphql } from "react-relay";
import { Environment } from "relay-runtime";
import { SCHEDULED_DELETION_TIMESPAN_DAYS } from "coral-common/constants";
import { SCHEDULED_DELETION_WINDOW_DURATION } from "coral-common/constants";
import { getViewer } from "coral-framework/helpers";
import {
commitMutationPromiseNormalized,
@@ -49,9 +48,9 @@ const RequestAccountDeletionMutation = createMutation(
},
optimisticUpdater: store => {
const viewer = getViewer(environment)!;
const deletionDate = DateTime.fromJSDate(new Date())
.plus({ days: SCHEDULED_DELETION_TIMESPAN_DAYS })
.toISO();
const deletionDate = new Date(
Date.now() + SCHEDULED_DELETION_WINDOW_DURATION * 1000
).toISOString();
const viewerProxy = store.get(viewer.id);
if (viewerProxy !== null) {
viewerProxy.setValue(deletionDate, "scheduledDeletionDate");
@@ -3,8 +3,9 @@ import cn from "classnames";
import React, { FunctionComponent, useCallback } from "react";
import { graphql } from "react-relay";
import { DOWNLOAD_LIMIT_TIMEFRAME } from "coral-common/constants";
import { reduceSeconds, UNIT } from "coral-common/helpers/i18n";
import { DOWNLOAD_LIMIT_TIMEFRAME_DURATION } from "coral-common/constants";
import { reduceSeconds } from "coral-common/helpers/i18n";
import TIME from "coral-common/time";
import { useCoralContext } from "coral-framework/lib/bootstrap";
import { useMutation, withFragmentContainer } from "coral-framework/lib/relay";
import CLASSES from "coral-stream/classes";
@@ -34,8 +35,8 @@ const DownloadCommentsContainer: FunctionComponent<Props> = ({ viewer }) => {
? Math.ceil((Date.now() - lastDownloadedAt.getTime()) / 1000)
: 0;
const canDownload =
!lastDownloadedAt || sinceLastDownload >= DOWNLOAD_LIMIT_TIMEFRAME;
const tilCanDownload = DOWNLOAD_LIMIT_TIMEFRAME - sinceLastDownload;
!lastDownloadedAt || sinceLastDownload >= DOWNLOAD_LIMIT_TIMEFRAME_DURATION;
const tilCanDownload = DOWNLOAD_LIMIT_TIMEFRAME_DURATION - sinceLastDownload;
const formatter = new Intl.DateTimeFormat(locales, {
day: "2-digit",
month: "2-digit",
@@ -46,9 +47,9 @@ const DownloadCommentsContainer: FunctionComponent<Props> = ({ viewer }) => {
timeZoneName: "short",
});
const { scaled, unit } = reduceSeconds(tilCanDownload, [
UNIT.DAYS,
UNIT.HOURS,
UNIT.MINUTES,
TIME.DAY,
TIME.HOUR,
TIME.MINUTE,
]);
return (
@@ -24,7 +24,7 @@ it("renders correctly with specified units", () => {
value: "",
disabled: false,
onChange: noop,
units: [DURATION_UNIT.SECONDS, DURATION_UNIT.HOURS],
units: [DURATION_UNIT.SECOND, DURATION_UNIT.HOUR],
};
const renderer = createRenderer();
renderer.render(<DurationField {...props} />);
@@ -37,7 +37,7 @@ it("use best matching unit", () => {
value: "3600",
disabled: false,
onChange: noop,
units: [DURATION_UNIT.SECONDS, DURATION_UNIT.MINUTES, DURATION_UNIT.HOURS],
units: [DURATION_UNIT.SECOND, DURATION_UNIT.MINUTE, DURATION_UNIT.HOUR],
};
const renderer = createRenderer();
renderer.render(<DurationField {...props} />);
@@ -50,7 +50,7 @@ it("use initial unit if 0", () => {
value: "0",
disabled: false,
onChange: noop,
units: [DURATION_UNIT.SECONDS, DURATION_UNIT.MINUTES, DURATION_UNIT.HOURS],
units: [DURATION_UNIT.SECOND, DURATION_UNIT.MINUTE, DURATION_UNIT.HOUR],
};
const renderer = createRenderer();
renderer.render(<DurationField {...props} />);
@@ -63,7 +63,7 @@ it("accepts invalid input", () => {
value: "this is so invalid",
disabled: false,
onChange: noop,
units: [DURATION_UNIT.SECONDS, DURATION_UNIT.MINUTES, DURATION_UNIT.HOURS],
units: [DURATION_UNIT.SECOND, DURATION_UNIT.MINUTE, DURATION_UNIT.HOUR],
};
const renderer = createRenderer();
renderer.render(<DurationField {...props} />);
@@ -8,7 +8,7 @@ import React, {
useState,
} from "react";
import { UNIT } from "coral-common/helpers/i18n";
import TIME from "coral-common/time";
import { Flex } from "coral-ui/components";
import { Option, SelectField } from "../SelectField";
@@ -20,32 +20,32 @@ import styles from "./DurationField.css";
* DURATION_UNIT are units that can be used in the
* DurationField components.
*/
export const DURATION_UNIT = UNIT;
export const DURATION_UNIT = TIME;
const DURATION_UNIT_MAP = {
[DURATION_UNIT.SECONDS]: "second",
[DURATION_UNIT.MINUTES]: "minute",
[DURATION_UNIT.HOURS]: "hour",
[DURATION_UNIT.DAYS]: "day",
[DURATION_UNIT.WEEKS]: "week",
[DURATION_UNIT.SECOND]: "second",
[DURATION_UNIT.MINUTE]: "minute",
[DURATION_UNIT.HOUR]: "hour",
[DURATION_UNIT.DAY]: "day",
[DURATION_UNIT.WEEK]: "week",
};
interface Props extends Pick<TextFieldProps, "color" | "name" | "disabled"> {
value: string;
onChange: (v: string) => void;
/** Specifiy units to include */
units?: ReadonlyArray<UNIT>;
units?: ReadonlyArray<TIME>;
}
function convertToSeconds(value: string, unit?: UNIT) {
function convertToSeconds(value: string, unit?: TIME) {
const parsed = parseInt(value, 10);
return (isNaN(parsed) || !unit ? value : parsed * unit).toString();
}
function convertFromSeconds(
value: string,
units: ReadonlyArray<UNIT>,
unit?: UNIT
units: ReadonlyArray<TIME>,
unit?: TIME
) {
const parsed = parseInt(value, 10);
@@ -77,7 +77,7 @@ function convertFromSeconds(
*/
const DurationField: FunctionComponent<Props> = ({
value,
units = [UNIT.HOURS, UNIT.DAYS, UNIT.WEEKS],
units = [TIME.HOUR, TIME.DAY, TIME.WEEK],
onChange,
disabled,
name,
@@ -25,7 +25,7 @@
font-family: var(--v2-font-family-primary);
font-weight: var(--v2-font-weight-primary-regular);
font-size: var(--v2-font-size-3);
line-height: var(--v2-line-height-tall);
line-height: var(--v2-line-height-reset);
appearance: none;
outline: none;
color: var(--v2-palette-input-value);
-1
View File
@@ -277,7 +277,6 @@ const variables2 = {
bodyShort: 1.3,
reset: 1,
title: 1.15,
tall: 1.3,
},
fontSize: {
1: "0.75rem",
+18 -14
View File
@@ -1,3 +1,5 @@
import TIME from "./time";
/**
* CLIENT_ID_HEADER references the name of the header used to extract/send the
* client ID to enable automatic de-duplication.
@@ -36,30 +38,32 @@ export const TOXICITY_ENDPOINT_DEFAULT =
"https://commentanalyzer.googleapis.com/v1alpha1";
/**
* DOWNLOAD_LIMIT_TIMEFRAME is the number of seconds that a given download may
* be made within.
* DOWNLOAD_LIMIT_TIMEFRAME_DURATION is the number of seconds that a given
* download may be made within.
*/
export const DOWNLOAD_LIMIT_TIMEFRAME = 14 * 86400;
export const DOWNLOAD_LIMIT_TIMEFRAME_DURATION = 14 * TIME.DAY;
/**
* ALLOWED_USERNAME_CHANGE_FREQUENCY is the length of time in seconds a user must wait after changing their username to change it again.
* ALLOWED_USERNAME_CHANGE_TIMEFRAME_DURATION is the length of time in seconds
* a user must wait after changing their username to change it again.
*/
export const ALLOWED_USERNAME_CHANGE_FREQUENCY = 14 * 86400;
export const ALLOWED_USERNAME_CHANGE_TIMEFRAME_DURATION = 14 * TIME.DAY;
/**
* SCHEDULED_DELETION_TIMESPAN_DAYS is the length of time in days a user
* will have to wait for their account to be deleted after requesting a
* deletion.
* SCHEDULED_DELETION_TIMEFRAME is the length of time in seconds a user will
* have to wait for their account to be deleted after requesting a deletion.
*/
export const SCHEDULED_DELETION_TIMESPAN_DAYS = 14;
export const SCHEDULED_DELETION_WINDOW_DURATION = 1 * TIME.DAY;
/**
* DEFAULT_SESSION_LENTTH is the length of time in seconds a session is valid for unless configured in tenant.
* DEFAULT_SESSION_DURATION is the length of time in seconds a session is valid
* for unless configured in tenant.
*/
export const DEFAULT_SESSION_LENGTH = 7776000;
export const DEFAULT_SESSION_DURATION = 90 * TIME.DAY;
/**
* COMMENT_REPEAT_POST_TIMESPAN is the length of time in seconds that a previous comment ID is stored for a
* user to prevent them from posting the same comment repeatedly.
* COMMENT_REPEAT_POST_DURATION is the length of time in seconds that a
* previous comment ID is stored for a user to prevent them from posting the
* same comment repeatedly.
*/
export const COMMENT_REPEAT_POST_TIMESPAN = 21600;
export const COMMENT_REPEAT_POST_DURATION = 6 * TIME.MINUTE;
+13 -23
View File
@@ -1,29 +1,19 @@
/**
* UNIT are units that can be used in the
* DurationField components.
*/
export enum UNIT {
SECONDS = 1,
MINUTES = 60,
HOURS = 3600,
DAYS = 86400,
WEEKS = 604800,
}
import TIME from "coral-common/time";
export const UNIT_MAP = {
[UNIT.SECONDS]: "second",
[UNIT.MINUTES]: "minute",
[UNIT.HOURS]: "hour",
[UNIT.DAYS]: "day",
[UNIT.WEEKS]: "week",
[TIME.SECOND]: "second",
[TIME.MINUTE]: "minute",
[TIME.HOUR]: "hour",
[TIME.DAY]: "day",
[TIME.WEEK]: "week",
};
export const DEFAULT_UNITS = [
UNIT.WEEKS,
UNIT.DAYS,
UNIT.HOURS,
UNIT.MINUTES,
UNIT.SECONDS,
TIME.WEEK,
TIME.DAY,
TIME.HOUR,
TIME.MINUTE,
TIME.SECOND,
];
type ValueOf<T> = T[keyof T];
@@ -37,11 +27,11 @@ export interface ScaledUnit {
export default function reduceSeconds(
value: number,
units: UNIT[] = DEFAULT_UNITS
units: TIME[] = DEFAULT_UNITS
): ScaledUnit {
// Find the largest match for the smallest number.
const unit: keyof typeof UNIT_MAP =
units.find(compare => value >= compare) || UNIT.SECONDS;
units.find(compare => value >= compare) || TIME.SECOND;
// Scale the value to the unit.
const scaled = Math.round((value / unit) * 100) / 100;
+12
View File
@@ -0,0 +1,12 @@
/**
* TIME represends various constants for second representations of times.
*/
enum TIME {
SECOND = 1,
MINUTE = 60 * TIME.SECOND,
HOUR = 60 * TIME.MINUTE,
DAY = 24 * TIME.HOUR,
WEEK = 7 * TIME.DAY,
}
export default TIME;
+9 -6
View File
@@ -5,13 +5,15 @@ import { MongoError } from "mongodb";
import uuid from "uuid";
import { VError } from "verror";
import { ALLOWED_USERNAME_CHANGE_FREQUENCY } from "coral-common/constants";
import { ALLOWED_USERNAME_CHANGE_TIMEFRAME_DURATION } from "coral-common/constants";
import { ERROR_CODES, ERROR_TYPES } from "coral-common/errors";
import { reduceSeconds, UNIT } from "coral-common/helpers/i18n";
import { reduceSeconds } from "coral-common/helpers/i18n";
import TIME from "coral-common/time";
import { Writable } from "coral-common/types";
import { translate } from "coral-server/services/i18n";
import { Writable } from "coral-common/types";
import { GQLUSER_AUTH_CONDITIONS } from "coral-server/graph/schema/__generated__/types";
import { ERROR_TRANSLATIONS } from "./translations";
/**
@@ -315,9 +317,10 @@ export class UsernameAlreadySetError extends CoralError {
export class UsernameUpdatedWithinWindowError extends CoralError {
constructor(lastUpdate: Date) {
const { scaled, unit } = reduceSeconds(ALLOWED_USERNAME_CHANGE_FREQUENCY, [
UNIT.DAYS,
]);
const { scaled, unit } = reduceSeconds(
ALLOWED_USERNAME_CHANGE_TIMEFRAME_DURATION,
[TIME.DAY]
);
super({
code: ERROR_CODES.USERNAME_UPDATED_WITHIN_WINDOW,
context: {
+3 -7
View File
@@ -45,13 +45,9 @@ export function getStoryClosedAt(
if (tenant.closeCommenting.auto) {
// Auto-close stream has been enabled, convert the createdAt time into the
// closedAt time by adding the closedTimeout.
return (
DateTime.fromJSDate(story.createdAt)
// closedTimeout is in seconds, so multiply by 1000 to get
// milliseconds.
.plus(tenant.closeCommenting.timeout * 1000)
.toJSDate()
);
return DateTime.fromJSDate(story.createdAt)
.plus({ seconds: tenant.closeCommenting.timeout })
.toJSDate();
}
return;
+6 -10
View File
@@ -2,8 +2,9 @@ import { isEmpty } from "lodash";
import { Db } from "mongodb";
import uuid from "uuid";
import { DEFAULT_SESSION_LENGTH } from "coral-common/constants";
import { DEFAULT_SESSION_DURATION } from "coral-common/constants";
import { LanguageCode } from "coral-common/helpers/i18n/locales";
import TIME from "coral-common/time";
import { DeepPartial, Omit, Sub } from "coral-common/types";
import { dotize } from "coral-common/utils/dotize";
import { Settings } from "coral-server/models/settings";
@@ -99,16 +100,12 @@ export async function createTenant(
premodLinksEnable: false,
closeCommenting: {
auto: false,
// 2 weeks timeout.
timeout: 60 * 60 * 24 * 7 * 2,
timeout: 2 * TIME.WEEK,
},
disableCommenting: {
enabled: false,
},
// 30 seconds edit window length.
editCommentWindowLength: 30,
editCommentWindowLength: 30 * TIME.SECOND,
charCount: {
enabled: false,
},
@@ -117,7 +114,7 @@ export async function createTenant(
banned: [],
},
auth: {
sessionDuration: DEFAULT_SESSION_LENGTH,
sessionDuration: DEFAULT_SESSION_DURATION,
integrations: {
local: {
enabled: true,
@@ -169,8 +166,7 @@ export async function createTenant(
},
recentCommentHistory: {
enabled: false,
// 7 days in seconds.
timeFrame: 604800,
timeFrame: 7 * TIME.DAY,
// Rejection rate defaulting to 30%, once exceeded, comments will be
// pre-moderated.
triggerRejectionRate: 0.3,
+3 -3
View File
@@ -1,6 +1,7 @@
import Queue, { Job, Queue as QueueType } from "bull";
import Logger from "bunyan";
import TIME from "coral-common/time";
import logger from "coral-server/logger";
export interface TaskOptions<T, U = any> {
@@ -31,11 +32,10 @@ export default class Task<T, U = any> {
// with completed entries if we don't need to.
removeOnComplete: true,
// By default, configure jobs to use an exponential backoff
// strategy starting at a 10 second delay.
// By default, configure jobs to use an exponential backoff strategy.
backoff: {
type: "exponential",
delay: 10000,
delay: 10 * TIME.SECOND,
},
// Be default, try all jobs at least 5 times.
@@ -23,13 +23,9 @@ export function getCommentEditableUntilDate(
tenant: Pick<Tenant, "editCommentWindowLength">,
createdAt: Date
): Date {
return (
DateTime.fromJSDate(createdAt)
// editCommentWindowLength is in seconds, so multiply by 1000 to get
// milliseconds.
.plus(tenant.editCommentWindowLength * 1000)
.toJSDate()
);
return DateTime.fromJSDate(createdAt)
.plus({ seconds: tenant.editCommentWindowLength })
.toJSDate();
}
export async function addTag(
+2 -2
View File
@@ -7,7 +7,7 @@ import { DateTime } from "luxon";
import { Bearer, BearerOptions } from "permit";
import uuid from "uuid/v4";
import { DEFAULT_SESSION_LENGTH } from "coral-common/constants";
import { DEFAULT_SESSION_DURATION } from "coral-common/constants";
import { Omit } from "coral-common/types";
import {
AuthenticationError,
@@ -262,7 +262,7 @@ export const signTokenString = async (
secret,
{
jwtid: uuid(),
expiresIn: DEFAULT_SESSION_LENGTH,
expiresIn: DEFAULT_SESSION_DURATION,
...options,
issuer: tenant.id,
subject: user.id,
+8 -8
View File
@@ -2,11 +2,11 @@ import { DateTime } from "luxon";
import { Db } from "mongodb";
import {
ALLOWED_USERNAME_CHANGE_FREQUENCY,
COMMENT_REPEAT_POST_TIMESPAN,
DOWNLOAD_LIMIT_TIMEFRAME,
ALLOWED_USERNAME_CHANGE_TIMEFRAME_DURATION,
COMMENT_REPEAT_POST_DURATION,
DOWNLOAD_LIMIT_TIMEFRAME_DURATION,
} from "coral-common/constants";
import { SCHEDULED_DELETION_TIMESPAN_DAYS } from "coral-common/constants";
import { SCHEDULED_DELETION_WINDOW_DURATION } from "coral-common/constants";
import { Config } from "coral-server/config";
import {
DuplicateEmailError,
@@ -369,7 +369,7 @@ export async function requestAccountDeletion(
}
const deletionDate = DateTime.fromJSDate(now).plus({
days: SCHEDULED_DELETION_TIMESPAN_DAYS,
seconds: SCHEDULED_DELETION_WINDOW_DURATION,
});
const updatedUser = await scheduleDeletionDate(
@@ -528,7 +528,7 @@ export async function updateUsername(
// Get the earliest date that the username could have been edited before to/
// allow it now.
const lastUsernameEditAllowed = DateTime.fromJSDate(now)
.plus({ seconds: -ALLOWED_USERNAME_CHANGE_FREQUENCY })
.plus({ seconds: -ALLOWED_USERNAME_CHANGE_TIMEFRAME_DURATION })
.toJSDate();
const { history } = user.status.username;
@@ -1118,7 +1118,7 @@ export async function requestCommentsDownload(
if (
user.lastDownloadedAt &&
DateTime.fromJSDate(user.lastDownloadedAt)
.plus({ seconds: DOWNLOAD_LIMIT_TIMEFRAME })
.plus({ seconds: DOWNLOAD_LIMIT_TIMEFRAME_DURATION })
.toSeconds() >= DateTime.fromJSDate(now).toSeconds()
) {
throw new Error("requested download too early");
@@ -1212,7 +1212,7 @@ export async function updateUserLastCommentID(
) {
const key = userLastCommentIDKey(tenant, user);
await redis.set(key, commentID, "EX", COMMENT_REPEAT_POST_TIMESPAN);
await redis.set(key, commentID, "EX", COMMENT_REPEAT_POST_DURATION);
}
/**
+3 -7
View File
@@ -42,13 +42,9 @@ function getLastCommentEditableUntilDate(
tenant: Pick<Tenant, "editCommentWindowLength">,
now = new Date()
): Date {
return (
DateTime.fromJSDate(now)
// editCommentWindowLength is in seconds, so multiply by 1000 to get
// milliseconds.
.minus(tenant.editCommentWindowLength * 1000)
.toJSDate()
);
return DateTime.fromJSDate(now)
.minus({ seconds: tenant.editCommentWindowLength })
.toJSDate();
}
export type EditComment = Omit<