mirror of
https://github.com/wassname/talk.git
synced 2026-07-25 13:30:59 +08:00
Merge branch 'next' into next-auth
This commit is contained in:
@@ -1,17 +0,0 @@
|
||||
import DataLoader from "dataloader";
|
||||
|
||||
import TenantContext from "talk-server/graph/tenant/context";
|
||||
import {
|
||||
Asset,
|
||||
FindOrCreateAssetInput,
|
||||
retrieveManyAssets,
|
||||
} from "talk-server/models/asset";
|
||||
import { findOrCreate } from "talk-server/services/assets";
|
||||
|
||||
export default (ctx: TenantContext) => ({
|
||||
findOrCreate: (input: FindOrCreateAssetInput) =>
|
||||
findOrCreate(ctx.mongo, ctx.tenant, input, ctx.queue.scraper),
|
||||
asset: new DataLoader<string, Asset | null>(ids =>
|
||||
retrieveManyAssets(ctx.mongo, ctx.tenant.id, ids)
|
||||
),
|
||||
});
|
||||
@@ -2,11 +2,11 @@ import DataLoader from "dataloader";
|
||||
|
||||
import Context from "talk-server/graph/tenant/context";
|
||||
import {
|
||||
AssetToCommentsArgs,
|
||||
CommentToParentsArgs,
|
||||
CommentToRepliesArgs,
|
||||
GQLActionPresence,
|
||||
GQLCOMMENT_SORT,
|
||||
StoryToCommentsArgs,
|
||||
} from "talk-server/graph/tenant/schema/__generated__/types";
|
||||
import {
|
||||
ACTION_ITEM_TYPE,
|
||||
@@ -14,9 +14,9 @@ import {
|
||||
} from "talk-server/models/action";
|
||||
import {
|
||||
Comment,
|
||||
retrieveCommentAssetConnection,
|
||||
retrieveCommentParentsConnection,
|
||||
retrieveCommentRepliesConnection,
|
||||
retrieveCommentStoryConnection,
|
||||
retrieveCommentUserConnection,
|
||||
retrieveManyComments,
|
||||
} from "talk-server/models/comment";
|
||||
@@ -61,29 +61,29 @@ export default (ctx: Context) => ({
|
||||
first = 10,
|
||||
orderBy = GQLCOMMENT_SORT.CREATED_AT_DESC,
|
||||
after,
|
||||
}: AssetToCommentsArgs
|
||||
}: StoryToCommentsArgs
|
||||
) =>
|
||||
retrieveCommentUserConnection(ctx.mongo, ctx.tenant.id, userID, {
|
||||
first,
|
||||
orderBy,
|
||||
after,
|
||||
}),
|
||||
forAsset: (
|
||||
assetID: string,
|
||||
forStory: (
|
||||
storyID: string,
|
||||
// Apply the graph schema defaults at the loader.
|
||||
{
|
||||
first = 10,
|
||||
orderBy = GQLCOMMENT_SORT.CREATED_AT_DESC,
|
||||
after,
|
||||
}: AssetToCommentsArgs
|
||||
}: StoryToCommentsArgs
|
||||
) =>
|
||||
retrieveCommentAssetConnection(ctx.mongo, ctx.tenant.id, assetID, {
|
||||
retrieveCommentStoryConnection(ctx.mongo, ctx.tenant.id, storyID, {
|
||||
first,
|
||||
orderBy,
|
||||
after,
|
||||
}).then(primeCommentsFromConnection(ctx)),
|
||||
forParent: (
|
||||
assetID: string,
|
||||
storyID: string,
|
||||
parentID: string,
|
||||
// Apply the graph schema defaults at the loader.
|
||||
{
|
||||
@@ -95,7 +95,7 @@ export default (ctx: Context) => ({
|
||||
retrieveCommentRepliesConnection(
|
||||
ctx.mongo,
|
||||
ctx.tenant.id,
|
||||
assetID,
|
||||
storyID,
|
||||
parentID,
|
||||
{
|
||||
first,
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import Context from "talk-server/graph/tenant/context";
|
||||
import Assets from "./assets";
|
||||
|
||||
import Auth from "./auth";
|
||||
import Comments from "./comments";
|
||||
import Stories from "./stories";
|
||||
import Users from "./users";
|
||||
|
||||
export default (ctx: Context) => ({
|
||||
Auth: Auth(ctx),
|
||||
Assets: Assets(ctx),
|
||||
Stories: Stories(ctx),
|
||||
Comments: Comments(ctx),
|
||||
Users: Users(ctx),
|
||||
});
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import DataLoader from "dataloader";
|
||||
|
||||
import TenantContext from "talk-server/graph/tenant/context";
|
||||
import {
|
||||
FindOrCreateStoryInput,
|
||||
retrieveManyStories,
|
||||
Story,
|
||||
} from "talk-server/models/story";
|
||||
import { findOrCreate } from "talk-server/services/stories";
|
||||
|
||||
export default (ctx: TenantContext) => ({
|
||||
findOrCreate: (input: FindOrCreateStoryInput) =>
|
||||
findOrCreate(ctx.mongo, ctx.tenant, input, ctx.queue.scraper),
|
||||
story: new DataLoader<string, Story | null>(ids =>
|
||||
retrieveManyStories(ctx.mongo, ctx.tenant.id, ids)
|
||||
),
|
||||
});
|
||||
@@ -27,7 +27,7 @@ export default (ctx: TenantContext) => ({
|
||||
ctx.user!,
|
||||
{
|
||||
author_id: ctx.user!.id,
|
||||
asset_id: input.assetID,
|
||||
story_id: input.storyID,
|
||||
body: input.body,
|
||||
parent_id: input.parentID,
|
||||
},
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { GQLAssetTypeResolver } from "talk-server/graph/tenant/schema/__generated__/types";
|
||||
import { decodeActionCounts } from "talk-server/models/action";
|
||||
import { Asset } from "talk-server/models/asset";
|
||||
|
||||
const Asset: GQLAssetTypeResolver<Asset> = {
|
||||
comments: (asset, input, ctx) =>
|
||||
ctx.loaders.Comments.forAsset(asset.id, input),
|
||||
// TODO: implement this.
|
||||
isClosed: () => false,
|
||||
actionCounts: asset => decodeActionCounts(asset.action_counts),
|
||||
commentCounts: asset => asset.comment_counts,
|
||||
};
|
||||
|
||||
export default Asset;
|
||||
@@ -23,7 +23,7 @@ const Comment: GQLCommentTypeResolver<Comment> = {
|
||||
ctx.loaders.Users.user.load(comment.author_id),
|
||||
replies: (comment, input, ctx) =>
|
||||
comment.reply_count > 0
|
||||
? ctx.loaders.Comments.forParent(comment.asset_id, comment.id, input)
|
||||
? ctx.loaders.Comments.forParent(comment.story_id, comment.id, input)
|
||||
: createConnection(),
|
||||
actionCounts: comment => decodeActionCounts(comment.action_counts),
|
||||
myActionPresence: (comment, input, ctx) =>
|
||||
@@ -84,8 +84,8 @@ const Comment: GQLCommentTypeResolver<Comment> = {
|
||||
comment.parent_id
|
||||
? ctx.loaders.Comments.parents(comment, input)
|
||||
: createConnection(),
|
||||
asset: (comment, input, ctx) =>
|
||||
ctx.loaders.Assets.asset.load(comment.asset_id),
|
||||
story: (comment, input, ctx) =>
|
||||
ctx.loaders.Stories.story.load(comment.story_id),
|
||||
};
|
||||
|
||||
export default Comment;
|
||||
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
GQLCOMMENT_STATUS,
|
||||
GQLCommentCountsTypeResolver,
|
||||
} from "talk-server/graph/tenant/schema/__generated__/types";
|
||||
import { CommentStatusCounts } from "talk-server/models/asset";
|
||||
import { CommentStatusCounts } from "talk-server/models/story";
|
||||
|
||||
const CommentCounts: GQLCommentCountsTypeResolver<CommentStatusCounts> = {
|
||||
totalVisible: commentCounts =>
|
||||
|
||||
@@ -3,7 +3,6 @@ import Time from "talk-server/graph/common/scalars/time";
|
||||
|
||||
import { GQLResolver } from "talk-server/graph/tenant/schema/__generated__/types";
|
||||
|
||||
import Asset from "./asset";
|
||||
import AuthIntegrations from "./auth_integrations";
|
||||
import Comment from "./comment";
|
||||
import CommentCounts from "./comment_counts";
|
||||
@@ -11,10 +10,10 @@ import Mutation from "./mutation";
|
||||
import OIDCAuthIntegration from "./oidc_auth_integration";
|
||||
import Profile from "./profile";
|
||||
import Query from "./query";
|
||||
import Story from "./story";
|
||||
import User from "./user";
|
||||
|
||||
const Resolvers: GQLResolver = {
|
||||
Asset,
|
||||
AuthIntegrations,
|
||||
Comment,
|
||||
CommentCounts,
|
||||
@@ -24,6 +23,7 @@ const Resolvers: GQLResolver = {
|
||||
Profile,
|
||||
Query,
|
||||
Time,
|
||||
Story,
|
||||
User,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { GQLQueryTypeResolver } from "talk-server/graph/tenant/schema/__generated__/types";
|
||||
|
||||
const Query: GQLQueryTypeResolver<void> = {
|
||||
asset: (source, args, ctx) => ctx.loaders.Assets.findOrCreate(args),
|
||||
story: (source, args, ctx) => ctx.loaders.Stories.findOrCreate(args),
|
||||
comment: (source, { id }, ctx) =>
|
||||
id ? ctx.loaders.Comments.comment.load(id) : null,
|
||||
settings: (source, args, ctx) => ctx.tenant,
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { GQLStoryTypeResolver } from "talk-server/graph/tenant/schema/__generated__/types";
|
||||
import { decodeActionCounts } from "talk-server/models/action";
|
||||
import { Story } from "talk-server/models/story";
|
||||
|
||||
const Story: GQLStoryTypeResolver<Story> = {
|
||||
comments: (story, input, ctx) =>
|
||||
ctx.loaders.Comments.forStory(story.id, input),
|
||||
// TODO: implement this.
|
||||
isClosed: () => false,
|
||||
actionCounts: story => decodeActionCounts(story.action_counts),
|
||||
commentCounts: story => story.comment_counts,
|
||||
};
|
||||
|
||||
export default Story;
|
||||
@@ -735,7 +735,7 @@ type Settings {
|
||||
domains: [String!] @auth(roles: [ADMIN])
|
||||
|
||||
"""
|
||||
moderation is the moderation mode for all Asset's on the site.
|
||||
moderation is the moderation mode for all Stories on the site.
|
||||
"""
|
||||
moderation: MODERATION_MODE @auth(roles: [ADMIN])
|
||||
|
||||
@@ -789,12 +789,12 @@ type Settings {
|
||||
|
||||
"""
|
||||
closedTimeout is the amount of seconds from the created_at timestamp that a
|
||||
given asset will be considered closed.
|
||||
given story will be considered closed.
|
||||
"""
|
||||
closedTimeout: Int!
|
||||
|
||||
"""
|
||||
closedMessage is the message shown to the user when the given Asset is
|
||||
closedMessage is the message shown to the user when the given Story is
|
||||
closed.
|
||||
"""
|
||||
closedMessage: String
|
||||
@@ -997,7 +997,7 @@ enum COMMENT_STATUS {
|
||||
REJECTED
|
||||
|
||||
"""
|
||||
The comment was created while the asset's premoderation option was on, and
|
||||
The comment was created while the stories premoderation option was on, and
|
||||
new comments that haven't been moderated yet are referred to as
|
||||
"premoderated" or "premod" comments.
|
||||
"""
|
||||
@@ -1011,7 +1011,7 @@ enum COMMENT_STATUS {
|
||||
}
|
||||
|
||||
"""
|
||||
Comment is a comment left by a User on an Asset or another Comment as a reply.
|
||||
Comment is a comment left by a User on an Story or another Comment as a reply.
|
||||
"""
|
||||
type Comment {
|
||||
"""
|
||||
@@ -1072,7 +1072,7 @@ type Comment {
|
||||
|
||||
"""
|
||||
rootParent is the highest level parent Comment. This Comment would have been
|
||||
left on the Asset itself.
|
||||
left on the Story itself.
|
||||
"""
|
||||
rootParent: Comment
|
||||
|
||||
@@ -1099,9 +1099,9 @@ type Comment {
|
||||
myActionPresence: ActionPresence
|
||||
|
||||
"""
|
||||
asset is the Asset that the Comment was written on.
|
||||
story is the Story that the Comment was written on.
|
||||
"""
|
||||
asset: Asset!
|
||||
story: Story!
|
||||
}
|
||||
|
||||
type PageInfo {
|
||||
@@ -1178,7 +1178,7 @@ type CommentStatusCounts {
|
||||
REJECTED: Int!
|
||||
|
||||
"""
|
||||
The comment was created while the asset's premoderation option was on, and
|
||||
The comment was created while the stories premoderation option was on, and
|
||||
new comments that haven't been moderated yet are referred to as
|
||||
"premoderated" or "premod" comments.
|
||||
"""
|
||||
@@ -1201,7 +1201,7 @@ type CommentCounts {
|
||||
}
|
||||
|
||||
################################################################################
|
||||
## Asset
|
||||
## Story
|
||||
################################################################################
|
||||
|
||||
enum COMMENT_SORT {
|
||||
@@ -1212,26 +1212,26 @@ enum COMMENT_SORT {
|
||||
}
|
||||
|
||||
"""
|
||||
Asset is an Article or Page where Comments are written on by Users.
|
||||
Story is an Article or Page where Comments are written on by Users.
|
||||
"""
|
||||
type Asset {
|
||||
type Story {
|
||||
"""
|
||||
id is the identifier of the Asset.
|
||||
id is the identifier of the Story.
|
||||
"""
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
url is the url that the Asset is located on.
|
||||
url is the url that the Story is located on.
|
||||
"""
|
||||
url: String!
|
||||
|
||||
"""
|
||||
title is the title of the scraped Asset.
|
||||
title is the title of the scraped Story.
|
||||
"""
|
||||
title: String
|
||||
|
||||
"""
|
||||
comments are the comments on the Asset.
|
||||
comments are the comments on the Story.
|
||||
"""
|
||||
comments(
|
||||
first: Int = 10
|
||||
@@ -1240,23 +1240,23 @@ type Asset {
|
||||
): CommentsConnection!
|
||||
|
||||
"""
|
||||
actionCounts stores the counts of all the actions against this Asset and it's
|
||||
actionCounts stores the counts of all the actions against this Story and it's
|
||||
Comments.
|
||||
"""
|
||||
actionCounts: ActionCounts! @auth(roles: [ADMIN, MODERATOR])
|
||||
|
||||
"""
|
||||
author is the authors listed in the meta tags for the Asset.
|
||||
author is the authors listed in the meta tags for the Story.
|
||||
"""
|
||||
author: String
|
||||
|
||||
"""
|
||||
closedAt is the Time that the Asset is closed for commenting.
|
||||
closedAt is the Time that the Story is closed for commenting.
|
||||
"""
|
||||
closedAt: Time
|
||||
|
||||
"""
|
||||
isClosed returns true when the Asset is currently closed for commenting.
|
||||
isClosed returns true when the Story is currently closed for commenting.
|
||||
"""
|
||||
isClosed: Boolean!
|
||||
|
||||
@@ -1266,19 +1266,19 @@ type Asset {
|
||||
commentCounts: CommentCounts!
|
||||
|
||||
"""
|
||||
createdAt is the date that the Asset was created at.
|
||||
createdAt is the date that the Story was created at.
|
||||
"""
|
||||
createdAt: Time!
|
||||
}
|
||||
|
||||
"""
|
||||
AssetEdge represents a unique Asset in a AssetConnection.
|
||||
StoryEdge represents a unique Story in a StoryConnection.
|
||||
"""
|
||||
type AssetEdge {
|
||||
type StoryEdge {
|
||||
"""
|
||||
node is the Asset for this edge.
|
||||
node is the Story for this edge.
|
||||
"""
|
||||
node: Asset!
|
||||
node: Story!
|
||||
|
||||
"""
|
||||
|
||||
@@ -1287,13 +1287,13 @@ type AssetEdge {
|
||||
}
|
||||
|
||||
"""
|
||||
AssetsConnection represents a subset of a Asset list.
|
||||
StoriesConnection represents a subset of a Story list.
|
||||
"""
|
||||
type AssetsConnection {
|
||||
type StoriesConnection {
|
||||
"""
|
||||
edges are a subset of AssetEdge's.
|
||||
edges are a subset of StoryEdge's.
|
||||
"""
|
||||
edges: [AssetEdge!]!
|
||||
edges: [StoryEdge!]!
|
||||
|
||||
"""
|
||||
pageInfo is
|
||||
@@ -1312,9 +1312,9 @@ type Query {
|
||||
comment(id: ID!): Comment
|
||||
|
||||
"""
|
||||
asset is the Asset specified by its ID/URL.
|
||||
story is the Story specified by its ID/URL.
|
||||
"""
|
||||
asset(id: ID, url: String): Asset
|
||||
story(id: ID, url: String): Story
|
||||
|
||||
"""
|
||||
me is the current logged in User. If no user is currently logged in, it will
|
||||
@@ -1353,9 +1353,9 @@ CreateCommentInput provides the input for the createComment Mutation.
|
||||
"""
|
||||
input CreateCommentInput {
|
||||
"""
|
||||
assetID is the ID of the Asset where we are creating a comment on.
|
||||
storyID is the ID of the Story where we are creating a comment on.
|
||||
"""
|
||||
assetID: ID!
|
||||
storyID: ID!
|
||||
|
||||
"""
|
||||
parentID is the optional ID of the Comment that we are replying to.
|
||||
@@ -1684,7 +1684,7 @@ input SettingsInput {
|
||||
domains: [String!]
|
||||
|
||||
"""
|
||||
moderation is the moderation mode for all Asset's on the site.
|
||||
moderation is the moderation mode for all Stories on the site.
|
||||
"""
|
||||
moderation: MODERATION_MODE
|
||||
|
||||
@@ -1738,12 +1738,12 @@ input SettingsInput {
|
||||
|
||||
"""
|
||||
closedTimeout is the amount of seconds from the created_at timestamp that a
|
||||
given asset will be considered closed.
|
||||
given story will be considered closed.
|
||||
"""
|
||||
closedTimeout: Int
|
||||
|
||||
"""
|
||||
closedMessage is the message shown to the user when the given Asset is
|
||||
closedMessage is the message shown to the user when the given Story is
|
||||
closed.
|
||||
"""
|
||||
closedMessage: String
|
||||
@@ -2364,5 +2364,5 @@ type Mutation {
|
||||
################################################################################
|
||||
|
||||
type Subscription {
|
||||
commentCreated(assetID: ID!): Comment
|
||||
commentCreated(storyID: ID!): Comment
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ export interface Comment extends TenantResource {
|
||||
readonly id: string;
|
||||
parent_id?: string;
|
||||
author_id: string;
|
||||
asset_id: string;
|
||||
story_id: string;
|
||||
body: string;
|
||||
body_history: BodyHistoryItem[];
|
||||
status: GQLCOMMENT_STATUS;
|
||||
@@ -288,14 +288,14 @@ function cursorGetterFactory(
|
||||
export async function retrieveCommentRepliesConnection(
|
||||
db: Db,
|
||||
tenantID: string,
|
||||
assetID: string,
|
||||
storyID: string,
|
||||
parentID: string,
|
||||
input: ConnectionInput
|
||||
) {
|
||||
// Create the query.
|
||||
const query = new Query(collection(db)).where({
|
||||
tenant_id: tenantID,
|
||||
asset_id: assetID,
|
||||
story_id: storyID,
|
||||
parent_id: parentID,
|
||||
});
|
||||
|
||||
@@ -400,23 +400,23 @@ export async function retrieveCommentParentsConnection(
|
||||
}
|
||||
|
||||
/**
|
||||
* retrieveAssetConnection returns a Connection<Comment> for a given Asset's
|
||||
* retrieveStoryConnection returns a Connection<Comment> for a given Stories
|
||||
* comments.
|
||||
*
|
||||
* @param db database connection
|
||||
* @param assetID the Asset id for the comment to retrieve
|
||||
* @param storyID the Story id for the comment to retrieve
|
||||
* @param input connection configuration
|
||||
*/
|
||||
export async function retrieveCommentAssetConnection(
|
||||
export async function retrieveCommentStoryConnection(
|
||||
db: Db,
|
||||
tenantID: string,
|
||||
assetID: string,
|
||||
storyID: string,
|
||||
input: ConnectionInput
|
||||
) {
|
||||
// Create the query.
|
||||
const query = new Query(collection(db)).where({
|
||||
tenant_id: tenantID,
|
||||
asset_id: assetID,
|
||||
story_id: storyID,
|
||||
parent_id: null,
|
||||
});
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { ModerationSettings } from "talk-server/models/settings";
|
||||
import { TenantResource } from "talk-server/models/tenant";
|
||||
|
||||
function collection(db: Db) {
|
||||
return db.collection<Readonly<Asset>>("assets");
|
||||
return db.collection<Readonly<Story>>("stories");
|
||||
}
|
||||
|
||||
// TODO: (wyattjoh) write a test to verify that this set of counts is always in sync with GQLCOMMENT_STATUS.
|
||||
@@ -21,7 +21,7 @@ export interface CommentStatusCounts {
|
||||
[GQLCOMMENT_STATUS.SYSTEM_WITHHELD]: number;
|
||||
}
|
||||
|
||||
export interface Asset extends TenantResource {
|
||||
export interface Story extends TenantResource {
|
||||
readonly id: string;
|
||||
url: string;
|
||||
scraped?: Date;
|
||||
@@ -39,40 +39,40 @@ export interface Asset extends TenantResource {
|
||||
publication_date?: Date;
|
||||
|
||||
/**
|
||||
* action_counts stores all the action counts for all Comment's on this Asset.
|
||||
* action_counts stores all the action counts for all Comment's on this Story.
|
||||
*/
|
||||
action_counts: EncodedActionCounts;
|
||||
|
||||
/**
|
||||
* comment_counts stores the different counts for each comment on the Asset
|
||||
* comment_counts stores the different counts for each comment on the Story
|
||||
* according to their statuses.
|
||||
*/
|
||||
comment_counts: CommentStatusCounts;
|
||||
|
||||
/**
|
||||
* settings provides a point where the settings can be overridden for a
|
||||
* specific Asset.
|
||||
* specific Story.
|
||||
*/
|
||||
settings?: Partial<ModerationSettings>;
|
||||
}
|
||||
|
||||
export interface UpsertAssetInput {
|
||||
export interface UpsertStoryInput {
|
||||
id?: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export async function upsertAsset(
|
||||
export async function upsertStory(
|
||||
db: Db,
|
||||
tenantID: string,
|
||||
{ id, url }: UpsertAssetInput
|
||||
{ id, url }: UpsertStoryInput
|
||||
) {
|
||||
const now = new Date();
|
||||
|
||||
// TODO: verify that the url for the given Asset is whitelisted by the tenant.
|
||||
// TODO: verify that the url for the given Story is whitelisted by the tenant.
|
||||
|
||||
// Create the asset, optionally sourcing the id from the input, additionally
|
||||
// Create the story, optionally sourcing the id from the input, additionally
|
||||
// porting in the tenant_id.
|
||||
const update: { $setOnInsert: Asset } = {
|
||||
const update: { $setOnInsert: Story } = {
|
||||
$setOnInsert: {
|
||||
id: id ? id : uuid.v4(),
|
||||
url,
|
||||
@@ -84,8 +84,8 @@ export async function upsertAsset(
|
||||
};
|
||||
|
||||
// Perform the find and update operation to try and find and or create the
|
||||
// asset.
|
||||
const { value: asset } = await collection(db).findOneAndUpdate(
|
||||
// story.
|
||||
const { value: story } = await collection(db).findOneAndUpdate(
|
||||
{
|
||||
url,
|
||||
tenant_id: tenantID,
|
||||
@@ -100,26 +100,26 @@ export async function upsertAsset(
|
||||
returnOriginal: false,
|
||||
}
|
||||
);
|
||||
if (!asset) {
|
||||
if (!story) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!asset.scraped) {
|
||||
// TODO: create scrape job to collect asset metadata
|
||||
if (!story.scraped) {
|
||||
// TODO: create scrape job to collect story metadata
|
||||
}
|
||||
|
||||
return asset;
|
||||
return story;
|
||||
}
|
||||
|
||||
/**
|
||||
* updateCommentStatusCount increments the number of status counts for the
|
||||
* given Asset ID.
|
||||
* given Story ID.
|
||||
*
|
||||
* @param mongo the database handle
|
||||
* @param tenantID the tenant that the Asset is on.
|
||||
* @param id the ID of the Asset.
|
||||
* @param tenantID the tenant that the Story is on.
|
||||
* @param id the ID of the Story.
|
||||
* @param commentStatusCounts the update document that contains a positive or
|
||||
* negative number of comments to increment on the given Asset.
|
||||
* negative number of comments to increment on the given Story.
|
||||
*/
|
||||
export async function updateCommentStatusCount(
|
||||
mongo: Db,
|
||||
@@ -127,7 +127,7 @@ export async function updateCommentStatusCount(
|
||||
id: string,
|
||||
commentStatusCounts: Partial<CommentStatusCounts>
|
||||
) {
|
||||
const { value: asset } = await collection(mongo).findOneAndUpdate(
|
||||
const { value: story } = await collection(mongo).findOneAndUpdate(
|
||||
{
|
||||
id,
|
||||
tenant_id: tenantID,
|
||||
@@ -140,7 +140,7 @@ export async function updateCommentStatusCount(
|
||||
{ returnOriginal: false }
|
||||
);
|
||||
|
||||
return asset;
|
||||
return story;
|
||||
}
|
||||
|
||||
function createEmptyCommentCounts(): CommentStatusCounts {
|
||||
@@ -153,39 +153,39 @@ function createEmptyCommentCounts(): CommentStatusCounts {
|
||||
};
|
||||
}
|
||||
|
||||
export interface FindOrCreateAssetInput {
|
||||
export interface FindOrCreateStoryInput {
|
||||
id?: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export async function findOrCreateAsset(
|
||||
export async function findOrCreateStory(
|
||||
db: Db,
|
||||
tenantID: string,
|
||||
{ id, url }: FindOrCreateAssetInput
|
||||
{ id, url }: FindOrCreateStoryInput
|
||||
) {
|
||||
if (id) {
|
||||
if (url) {
|
||||
// The URL was specified, this is an upsert operation.
|
||||
return upsertAsset(db, tenantID, {
|
||||
return upsertStory(db, tenantID, {
|
||||
id,
|
||||
url,
|
||||
});
|
||||
}
|
||||
|
||||
// The URL was not specified, this is a lookup operation.
|
||||
return retrieveAsset(db, tenantID, id);
|
||||
return retrieveStory(db, tenantID, id);
|
||||
}
|
||||
|
||||
// The ID was not specified, this is an upsert operation. Check to see that
|
||||
// the URL exists.
|
||||
if (!url) {
|
||||
throw new Error("cannot upsert an asset without the url");
|
||||
throw new Error("cannot upsert an story without the url");
|
||||
}
|
||||
|
||||
return upsertAsset(db, tenantID, { url });
|
||||
return upsertStory(db, tenantID, { url });
|
||||
}
|
||||
|
||||
export async function retrieveAssetByURL(
|
||||
export async function retrieveStoryByURL(
|
||||
db: Db,
|
||||
tenantID: string,
|
||||
url: string
|
||||
@@ -193,11 +193,11 @@ export async function retrieveAssetByURL(
|
||||
return collection(db).findOne({ url, tenant_id: tenantID });
|
||||
}
|
||||
|
||||
export async function retrieveAsset(db: Db, tenantID: string, id: string) {
|
||||
export async function retrieveStory(db: Db, tenantID: string, id: string) {
|
||||
return collection(db).findOne({ id, tenant_id: tenantID });
|
||||
}
|
||||
|
||||
export async function retrieveManyAssets(
|
||||
export async function retrieveManyStories(
|
||||
db: Db,
|
||||
tenantID: string,
|
||||
ids: string[]
|
||||
@@ -207,12 +207,12 @@ export async function retrieveManyAssets(
|
||||
tenant_id: tenantID,
|
||||
});
|
||||
|
||||
const assets = await cursor.toArray();
|
||||
const stories = await cursor.toArray();
|
||||
|
||||
return ids.map(id => assets.find(asset => asset.id === id) || null);
|
||||
return ids.map(id => stories.find(story => story.id === id) || null);
|
||||
}
|
||||
|
||||
export async function retrieveManyAssetsByURL(
|
||||
export async function retrieveManyStoriesByURL(
|
||||
db: Db,
|
||||
tenantID: string,
|
||||
urls: string[]
|
||||
@@ -222,21 +222,21 @@ export async function retrieveManyAssetsByURL(
|
||||
tenant_id: tenantID,
|
||||
});
|
||||
|
||||
const assets = await cursor.toArray();
|
||||
const stories = await cursor.toArray();
|
||||
|
||||
return urls.map(url => assets.find(asset => asset.url === url) || null);
|
||||
return urls.map(url => stories.find(story => story.url === url) || null);
|
||||
}
|
||||
|
||||
export type UpdateAssetInput = Omit<
|
||||
Partial<Asset>,
|
||||
export type UpdateStoryInput = Omit<
|
||||
Partial<Story>,
|
||||
"id" | "tenant_id" | "url" | "created_at"
|
||||
>;
|
||||
|
||||
export async function updateAsset(
|
||||
export async function updateStory(
|
||||
db: Db,
|
||||
tenantID: string,
|
||||
id: string,
|
||||
input: UpdateAssetInput
|
||||
input: UpdateStoryInput
|
||||
) {
|
||||
// Only update fields that have been updated.
|
||||
const update = {
|
||||
@@ -259,15 +259,15 @@ export async function updateAsset(
|
||||
}
|
||||
|
||||
/**
|
||||
* updateAssetActionCounts will update the given comment's action counts on
|
||||
* the Asset.
|
||||
* updateStoryActionCounts will update the given comment's action counts on
|
||||
* the Story.
|
||||
*
|
||||
* @param mongo the database handle
|
||||
* @param tenantID the id of the Tenant
|
||||
* @param id the id of the Asset being updated
|
||||
* @param actionCounts the action counts to merge into the Asset
|
||||
* @param id the id of the Story being updated
|
||||
* @param actionCounts the action counts to merge into the Story
|
||||
*/
|
||||
export async function updateAssetActionCounts(
|
||||
export async function updateStoryActionCounts(
|
||||
mongo: Db,
|
||||
tenantID: string,
|
||||
id: string,
|
||||
@@ -19,7 +19,7 @@ export interface TenantResource {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tenant describes a given Tenant on Talk that has Assets, Comments, and Users.
|
||||
* Tenant describes a given Tenant on Talk that has Stories, Comments, and Users.
|
||||
*/
|
||||
export interface Tenant extends Settings {
|
||||
readonly id: string;
|
||||
|
||||
@@ -11,12 +11,12 @@ import {
|
||||
encodeActionCounts,
|
||||
invertEncodedActionCounts,
|
||||
} from "talk-server/models/action";
|
||||
import { updateAssetActionCounts } from "talk-server/models/asset";
|
||||
import {
|
||||
retrieveComment,
|
||||
updateCommentActionCounts,
|
||||
} from "talk-server/models/comment";
|
||||
import { Comment } from "talk-server/models/comment";
|
||||
import { updateStoryActionCounts } from "talk-server/models/story";
|
||||
import { Tenant } from "talk-server/models/tenant";
|
||||
import { User } from "talk-server/models/user";
|
||||
|
||||
@@ -47,11 +47,11 @@ export async function addCommentActions(
|
||||
actionCounts
|
||||
);
|
||||
|
||||
// Update the Asset with the updated action counts.
|
||||
await updateAssetActionCounts(
|
||||
// Update the Story with the updated action counts.
|
||||
await updateStoryActionCounts(
|
||||
mongo,
|
||||
tenant.id,
|
||||
comment.asset_id,
|
||||
comment.story_id,
|
||||
actionCounts
|
||||
);
|
||||
|
||||
@@ -109,11 +109,11 @@ export async function removeCommentAction(
|
||||
actionCounts
|
||||
);
|
||||
|
||||
// Update the Asset with the updated action counts.
|
||||
await updateAssetActionCounts(
|
||||
// Update the Story with the updated action counts.
|
||||
await updateStoryActionCounts(
|
||||
mongo,
|
||||
tenant.id,
|
||||
comment.asset_id,
|
||||
comment.story_id,
|
||||
actionCounts
|
||||
);
|
||||
|
||||
|
||||
@@ -2,10 +2,6 @@ import { Db } from "mongodb";
|
||||
|
||||
import { Omit } from "talk-common/types";
|
||||
import { ACTION_ITEM_TYPE, CreateActionInput } from "talk-server/models/action";
|
||||
import {
|
||||
retrieveAsset,
|
||||
updateCommentStatusCount,
|
||||
} from "talk-server/models/asset";
|
||||
import {
|
||||
createComment,
|
||||
CreateCommentInput,
|
||||
@@ -14,6 +10,10 @@ import {
|
||||
pushChildCommentIDOntoParent,
|
||||
retrieveComment,
|
||||
} from "talk-server/models/comment";
|
||||
import {
|
||||
retrieveStory,
|
||||
updateCommentStatusCount,
|
||||
} from "talk-server/models/story";
|
||||
import { Tenant } from "talk-server/models/tenant";
|
||||
import { User } from "talk-server/models/user";
|
||||
import { addCommentActions } from "talk-server/services/comments/actions";
|
||||
@@ -32,14 +32,14 @@ export async function create(
|
||||
input: CreateComment,
|
||||
req?: Request
|
||||
) {
|
||||
// Grab the asset that we'll use to check moderation pieces with.
|
||||
const asset = await retrieveAsset(mongo, tenant.id, input.asset_id);
|
||||
if (!asset) {
|
||||
// Grab the story that we'll use to check moderation pieces with.
|
||||
const story = await retrieveStory(mongo, tenant.id, input.story_id);
|
||||
if (!story) {
|
||||
// TODO: (wyattjoh) return better error.
|
||||
throw new Error("asset referenced does not exist");
|
||||
throw new Error("story referenced does not exist");
|
||||
}
|
||||
|
||||
// TODO: (wyattjoh) Check that the asset was visible.
|
||||
// TODO: (wyattjoh) Check that the story was visible.
|
||||
|
||||
const grandparentIDs: string[] = [];
|
||||
if (input.parent_id) {
|
||||
@@ -62,7 +62,7 @@ export async function create(
|
||||
|
||||
// Run the comment through the moderation phases.
|
||||
const { actions, status, metadata } = await processForModeration({
|
||||
asset,
|
||||
story,
|
||||
tenant,
|
||||
comment: input,
|
||||
author,
|
||||
@@ -104,8 +104,8 @@ export async function create(
|
||||
);
|
||||
}
|
||||
|
||||
// Increment the status count for the particular status on the Asset.
|
||||
await updateCommentStatusCount(mongo, tenant.id, asset.id, {
|
||||
// Increment the status count for the particular status on the Story.
|
||||
await updateCommentStatusCount(mongo, tenant.id, story.id, {
|
||||
[status]: 1,
|
||||
});
|
||||
|
||||
@@ -131,16 +131,16 @@ export async function edit(
|
||||
throw new Error("comment not found");
|
||||
}
|
||||
|
||||
// Grab the asset that we'll use to check moderation pieces with.
|
||||
const asset = await retrieveAsset(mongo, tenant.id, comment.asset_id);
|
||||
if (!asset) {
|
||||
// Grab the story that we'll use to check moderation pieces with.
|
||||
const story = await retrieveStory(mongo, tenant.id, comment.story_id);
|
||||
if (!story) {
|
||||
// TODO: (wyattjoh) return better error.
|
||||
throw new Error("asset referenced does not exist");
|
||||
throw new Error("story referenced does not exist");
|
||||
}
|
||||
|
||||
// Run the comment through the moderation phases.
|
||||
const { status, metadata, actions } = await processForModeration({
|
||||
asset,
|
||||
story,
|
||||
tenant,
|
||||
comment: input,
|
||||
author,
|
||||
@@ -185,9 +185,9 @@ export async function edit(
|
||||
}
|
||||
|
||||
if (comment.status !== editedComment.status) {
|
||||
// Increment the status count for the particular status on the Asset, and
|
||||
// Increment the status count for the particular status on the Story, and
|
||||
// decrement the status on the comment's previous status.
|
||||
await updateCommentStatusCount(mongo, tenant.id, asset.id, {
|
||||
await updateCommentStatusCount(mongo, tenant.id, story.id, {
|
||||
[comment.status]: -1,
|
||||
[editedComment.status]: 1,
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Omit, Promiseable } from "talk-common/types";
|
||||
import { GQLCOMMENT_STATUS } from "talk-server/graph/tenant/schema/__generated__/types";
|
||||
import { CreateActionInput } from "talk-server/models/action";
|
||||
import { Asset } from "talk-server/models/asset";
|
||||
import { Comment } from "talk-server/models/comment";
|
||||
import { Story } from "talk-server/models/story";
|
||||
import { Tenant } from "talk-server/models/tenant";
|
||||
import { User } from "talk-server/models/user";
|
||||
import { Request } from "talk-server/types/express";
|
||||
@@ -18,7 +18,7 @@ export interface PhaseResult {
|
||||
}
|
||||
|
||||
export interface ModerationPhaseContext {
|
||||
asset: Asset;
|
||||
story: Story;
|
||||
tenant: Tenant;
|
||||
comment: Partial<Comment>;
|
||||
author: User;
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import {
|
||||
IntermediateModerationPhase,
|
||||
IntermediatePhaseResult,
|
||||
} from "talk-server/services/comments/moderation";
|
||||
|
||||
// This phase checks to see if the asset being processed is closed or not.
|
||||
export const assetClosed: IntermediateModerationPhase = ({
|
||||
asset,
|
||||
}): IntermediatePhaseResult | void => {
|
||||
// Check to see if the asset has closed commenting...
|
||||
if (asset.closedAt && asset.closedAt.valueOf() <= Date.now()) {
|
||||
// TODO: (wyattjoh) return better error.
|
||||
throw new Error("asset is currently closed for commenting");
|
||||
}
|
||||
};
|
||||
@@ -35,7 +35,7 @@ const testCharCount = (
|
||||
};
|
||||
|
||||
export const commentLength: IntermediateModerationPhase = ({
|
||||
asset,
|
||||
story,
|
||||
tenant,
|
||||
comment,
|
||||
}): IntermediatePhaseResult | void => {
|
||||
@@ -44,7 +44,7 @@ export const commentLength: IntermediateModerationPhase = ({
|
||||
// Reject if the comment is too long or too short.
|
||||
if (
|
||||
testCharCount(tenant, length) ||
|
||||
(asset.settings && testCharCount(asset.settings, length))
|
||||
(story.settings && testCharCount(story.settings, length))
|
||||
) {
|
||||
return {
|
||||
status: GQLCOMMENT_STATUS.REJECTED,
|
||||
|
||||
@@ -8,13 +8,13 @@ const testDisabledCommenting = (settings: Partial<ModerationSettings>) =>
|
||||
settings.disableCommenting;
|
||||
|
||||
export const commentingDisabled: IntermediateModerationPhase = ({
|
||||
asset,
|
||||
story,
|
||||
tenant,
|
||||
}): IntermediatePhaseResult | void => {
|
||||
// Check to see if the asset has closed commenting.
|
||||
// Check to see if the story has closed commenting.
|
||||
if (
|
||||
testDisabledCommenting(tenant) ||
|
||||
(asset.settings && testDisabledCommenting(asset.settings))
|
||||
(story.settings && testDisabledCommenting(story.settings))
|
||||
) {
|
||||
// TODO: (wyattjoh) return better error.
|
||||
throw new Error("commenting has been disabled tenant wide");
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { IntermediateModerationPhase } from "talk-server/services/comments/moderation";
|
||||
|
||||
import { assetClosed } from "./assetClosed";
|
||||
import { commentingDisabled } from "./commentingDisabled";
|
||||
import { commentLength } from "./commentLength";
|
||||
import { karma } from "./karma";
|
||||
@@ -8,6 +7,7 @@ import { links } from "./links";
|
||||
import { preModerate } from "./preModerate";
|
||||
import { spam } from "./spam";
|
||||
import { staff } from "./staff";
|
||||
import { storyClosed } from "./storyClosed";
|
||||
import { toxic } from "./toxic";
|
||||
import { wordList } from "./wordList";
|
||||
|
||||
@@ -16,7 +16,7 @@ import { wordList } from "./wordList";
|
||||
*/
|
||||
export const moderationPhases: IntermediateModerationPhase[] = [
|
||||
commentLength,
|
||||
assetClosed,
|
||||
storyClosed,
|
||||
commentingDisabled,
|
||||
wordList,
|
||||
staff,
|
||||
|
||||
@@ -25,14 +25,14 @@ const testPremodLinksEnable = (
|
||||
// This phase checks the comment if it has any links in it if the check is
|
||||
// enabled.
|
||||
export const links: IntermediateModerationPhase = ({
|
||||
asset,
|
||||
story,
|
||||
tenant,
|
||||
comment,
|
||||
}): IntermediatePhaseResult | void => {
|
||||
if (
|
||||
comment.body &&
|
||||
(testPremodLinksEnable(tenant, comment.body) ||
|
||||
(asset.settings && testPremodLinksEnable(asset.settings, comment.body)))
|
||||
(story.settings && testPremodLinksEnable(story.settings, comment.body)))
|
||||
) {
|
||||
// Add the flag related to Trust to the comment.
|
||||
return {
|
||||
|
||||
@@ -14,16 +14,16 @@ const testModerationMode = (settings: Partial<ModerationSettings>) =>
|
||||
// This phase checks to see if the settings have premod enabled, if they do,
|
||||
// the comment is premod, otherwise, it's just none.
|
||||
export const preModerate: IntermediateModerationPhase = ({
|
||||
asset,
|
||||
story,
|
||||
tenant,
|
||||
}): IntermediatePhaseResult | void => {
|
||||
// If the settings say that we're in premod mode, then the comment is in
|
||||
// premod status.
|
||||
|
||||
// TODO: (wyattjoh) pull from the asset settings.
|
||||
// TODO: (wyattjoh) pull from the story settings.
|
||||
if (
|
||||
testModerationMode(tenant) ||
|
||||
(asset.settings && testModerationMode(asset.settings))
|
||||
(story.settings && testModerationMode(story.settings))
|
||||
) {
|
||||
return {
|
||||
status: GQLCOMMENT_STATUS.PREMOD,
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
} from "talk-server/services/comments/moderation";
|
||||
|
||||
export const spam: IntermediateModerationPhase = async ({
|
||||
asset,
|
||||
story,
|
||||
tenant,
|
||||
comment,
|
||||
author,
|
||||
@@ -96,7 +96,7 @@ export const spam: IntermediateModerationPhase = async ({
|
||||
referrer, // REQUIRED
|
||||
user_agent: userAgent, // REQUIRED
|
||||
comment_content: comment.body,
|
||||
permalink: asset.url,
|
||||
permalink: story.url,
|
||||
comment_author: author.displayName || author.username || "",
|
||||
comment_type: "comment",
|
||||
is_test: false,
|
||||
|
||||
+12
-12
@@ -1,16 +1,16 @@
|
||||
import { Asset } from "talk-server/models/asset";
|
||||
import { Comment } from "talk-server/models/comment";
|
||||
import { Story } from "talk-server/models/story";
|
||||
import { Tenant } from "talk-server/models/tenant";
|
||||
import { User } from "talk-server/models/user";
|
||||
import { assetClosed } from "talk-server/services/comments/moderation/phases/assetClosed";
|
||||
import { storyClosed } from "talk-server/services/comments/moderation/phases/storyClosed";
|
||||
|
||||
describe("assetClosed", () => {
|
||||
it("throws an error when the asset is closed", () => {
|
||||
const asset = { closedAt: new Date() };
|
||||
describe("storyClosed", () => {
|
||||
it("throws an error when the story is closed", () => {
|
||||
const story = { closedAt: new Date() };
|
||||
|
||||
expect(() =>
|
||||
assetClosed({
|
||||
asset: asset as Asset,
|
||||
storyClosed({
|
||||
story: story as Story,
|
||||
tenant: (null as any) as Tenant,
|
||||
comment: (null as any) as Comment,
|
||||
author: (null as any) as User,
|
||||
@@ -18,12 +18,12 @@ describe("assetClosed", () => {
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it("does not throw an error when the asset is not closed", () => {
|
||||
it("does not throw an error when the story is not closed", () => {
|
||||
const now = new Date();
|
||||
|
||||
expect(
|
||||
assetClosed({
|
||||
asset: { closedAt: new Date(now.getTime() + 60000) } as Asset,
|
||||
storyClosed({
|
||||
story: { closedAt: new Date(now.getTime() + 60000) } as Story,
|
||||
tenant: (null as any) as Tenant,
|
||||
comment: (null as any) as Comment,
|
||||
author: (null as any) as User,
|
||||
@@ -31,8 +31,8 @@ describe("assetClosed", () => {
|
||||
).toBeUndefined();
|
||||
|
||||
expect(
|
||||
assetClosed({
|
||||
asset: {} as Asset,
|
||||
storyClosed({
|
||||
story: {} as Story,
|
||||
tenant: (null as any) as Tenant,
|
||||
comment: (null as any) as Comment,
|
||||
author: (null as any) as User,
|
||||
@@ -0,0 +1,15 @@
|
||||
import {
|
||||
IntermediateModerationPhase,
|
||||
IntermediatePhaseResult,
|
||||
} from "talk-server/services/comments/moderation";
|
||||
|
||||
// This phase checks to see if the story being processed is closed or not.
|
||||
export const storyClosed: IntermediateModerationPhase = ({
|
||||
story,
|
||||
}): IntermediatePhaseResult | void => {
|
||||
// Check to see if the story has closed commenting...
|
||||
if (story.closedAt && story.closedAt.valueOf() <= Date.now()) {
|
||||
// TODO: (wyattjoh) return better error.
|
||||
throw new Error("story is currently closed for commenting");
|
||||
}
|
||||
};
|
||||
@@ -19,7 +19,7 @@ export const wordList: IntermediateModerationPhase = ({
|
||||
return;
|
||||
}
|
||||
|
||||
// Decide the status based on whether or not the current asset/settings
|
||||
// Decide the status based on whether or not the current story/settings
|
||||
// has pre-mod enabled or not. If the comment was rejected based on the
|
||||
// wordList, then reject it, otherwise if the moderation setting is
|
||||
// premod, set it to `premod`.
|
||||
|
||||
@@ -8,7 +8,7 @@ import titleScraper from "metascraper-title";
|
||||
import { Db } from "mongodb";
|
||||
|
||||
import logger from "talk-server/logger";
|
||||
import { updateAsset } from "talk-server/models/asset";
|
||||
import { updateStory } from "talk-server/models/story";
|
||||
import Task from "talk-server/services/queue/Task";
|
||||
import { modifiedScraper } from "./rules/modified";
|
||||
import { sectionScraper } from "./rules/section";
|
||||
@@ -20,8 +20,8 @@ export interface ScrapeProcessorOptions {
|
||||
}
|
||||
|
||||
export interface ScraperData {
|
||||
assetID: string;
|
||||
assetURL: string;
|
||||
storyID: string;
|
||||
storyURL: string;
|
||||
tenantID: string;
|
||||
}
|
||||
|
||||
@@ -30,17 +30,17 @@ const createJobProcessor = (
|
||||
scraper: Scraper
|
||||
) => async (job: Job<ScraperData>) => {
|
||||
// Pull out the job data.
|
||||
const { assetID: id, assetURL: url, tenantID } = job.data;
|
||||
const { storyID: id, storyURL: url, tenantID } = job.data;
|
||||
|
||||
logger.debug(
|
||||
{
|
||||
job_id: job.id,
|
||||
job_name: JOB_NAME,
|
||||
asset_id: id,
|
||||
asset_url: url,
|
||||
story_id: id,
|
||||
story_url: url,
|
||||
tenant_id: tenantID,
|
||||
},
|
||||
"starting to scrap the asset"
|
||||
"starting to scrap the story"
|
||||
);
|
||||
|
||||
// Get the metadata from the scraped html.
|
||||
@@ -50,17 +50,17 @@ const createJobProcessor = (
|
||||
{
|
||||
job_id: job.id,
|
||||
job_name: JOB_NAME,
|
||||
asset_id: id,
|
||||
asset_url: url,
|
||||
story_id: id,
|
||||
story_url: url,
|
||||
tenant_id: tenantID,
|
||||
},
|
||||
"asset at specified url not found, can not scrape"
|
||||
"story at specified url not found, can not scrape"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the Asset with the scraped details.
|
||||
const asset = await updateAsset(options.mongo, tenantID, id, {
|
||||
// Update the Story with the scraped details.
|
||||
const story = await updateStory(options.mongo, tenantID, id, {
|
||||
title: meta.title || undefined,
|
||||
description: meta.description || undefined,
|
||||
image: meta.image ? meta.image : undefined,
|
||||
@@ -70,16 +70,16 @@ const createJobProcessor = (
|
||||
section: meta.section || undefined,
|
||||
scraped: new Date(),
|
||||
});
|
||||
if (!asset) {
|
||||
if (!story) {
|
||||
logger.error(
|
||||
{
|
||||
job_id: job.id,
|
||||
job_name: JOB_NAME,
|
||||
asset_id: id,
|
||||
asset_url: url,
|
||||
story_id: id,
|
||||
story_url: url,
|
||||
tenant_id: tenantID,
|
||||
},
|
||||
"asset at specified id not found, can not update with metadata"
|
||||
"story at specified id not found, can not update with metadata"
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -88,11 +88,11 @@ const createJobProcessor = (
|
||||
{
|
||||
job_id: job.id,
|
||||
job_name: JOB_NAME,
|
||||
asset_id: asset.id,
|
||||
asset_url: url,
|
||||
story_id: story.id,
|
||||
story_url: url,
|
||||
tenant_id: tenantID,
|
||||
},
|
||||
"scraped the asset"
|
||||
"scraped the story"
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
+12
-12
@@ -1,35 +1,35 @@
|
||||
import { Db } from "mongodb";
|
||||
|
||||
import {
|
||||
findOrCreateAsset,
|
||||
FindOrCreateAssetInput,
|
||||
} from "talk-server/models/asset";
|
||||
findOrCreateStory,
|
||||
FindOrCreateStoryInput,
|
||||
} from "talk-server/models/story";
|
||||
import { Tenant } from "talk-server/models/tenant";
|
||||
import Task from "talk-server/services/queue/Task";
|
||||
import { ScraperData } from "talk-server/services/queue/tasks/scraper";
|
||||
|
||||
export type FindOrCreateAsset = FindOrCreateAssetInput;
|
||||
export type FindOrCreateStory = FindOrCreateStoryInput;
|
||||
|
||||
export async function findOrCreate(
|
||||
db: Db,
|
||||
tenant: Tenant,
|
||||
input: FindOrCreateAsset,
|
||||
input: FindOrCreateStory,
|
||||
scraper: Task<ScraperData>
|
||||
) {
|
||||
// TODO: check to see if the tenant has enabled lazy asset creation.
|
||||
// TODO: check to see if the tenant has enabled lazy story creation.
|
||||
|
||||
const asset = await findOrCreateAsset(db, tenant.id, input);
|
||||
if (!asset) {
|
||||
const story = await findOrCreateStory(db, tenant.id, input);
|
||||
if (!story) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!asset.scraped) {
|
||||
if (!story.scraped) {
|
||||
await scraper.add({
|
||||
assetID: asset.id,
|
||||
assetURL: asset.url,
|
||||
storyID: story.id,
|
||||
storyURL: story.url,
|
||||
tenantID: tenant.id,
|
||||
});
|
||||
}
|
||||
|
||||
return asset;
|
||||
return story;
|
||||
}
|
||||
Reference in New Issue
Block a user