feat: asset url query

This commit is contained in:
Wyatt Johnson
2018-07-10 13:11:32 -06:00
parent 093e4fd736
commit c0da4e97aa
5 changed files with 144 additions and 47 deletions
@@ -1,8 +1,15 @@
import DataLoader from "dataloader";
import TenantContext from "talk-server/graph/tenant/context";
import { Asset, retrieveManyAssets } from "talk-server/models/asset";
import {
Asset,
findOrCreateAsset,
FindOrCreateAssetInput,
retrieveManyAssets,
} from "talk-server/models/asset";
export default (ctx: TenantContext) => ({
findOrCreate: (input: FindOrCreateAssetInput) =>
findOrCreateAsset(ctx.db, ctx.tenant.id, input),
asset: new DataLoader<string, Asset | null>(ids =>
retrieveManyAssets(ctx.db, ctx.tenant.id, ids)
),
@@ -4,6 +4,7 @@ import Context from "talk-server/graph/tenant/context";
import {
AssetToCommentsArgs,
CommentToRepliesArgs,
GQLCOMMENT_SORT,
} from "talk-server/graph/tenant/schema/__generated__/types";
import {
retrieveCommentAssetConnection,
@@ -15,14 +16,33 @@ export default (ctx: Context) => ({
comment: new DataLoader((ids: string[]) =>
retrieveManyComments(ctx.db, ctx.tenant.id, ids)
),
forAsset: (assetID: string, input: AssetToCommentsArgs) =>
retrieveCommentAssetConnection(ctx.db, ctx.tenant.id, assetID, input),
forParent: (assetID: string, parentID: string, input: CommentToRepliesArgs) =>
retrieveCommentRepliesConnection(
ctx.db,
ctx.tenant.id,
assetID,
parentID,
input
),
forAsset: (
assetID: string,
// Apply the graph schema defaults at the loader.
{
first = 10,
orderBy = GQLCOMMENT_SORT.CREATED_AT_DESC,
after,
}: AssetToCommentsArgs
) =>
retrieveCommentAssetConnection(ctx.db, ctx.tenant.id, assetID, {
first,
orderBy,
after,
}),
forParent: (
assetID: string,
parentID: string,
// Apply the graph schema defaults at the loader.
{
first = 10,
orderBy = GQLCOMMENT_SORT.CREATED_AT_DESC,
after,
}: CommentToRepliesArgs
) =>
retrieveCommentRepliesConnection(ctx.db, ctx.tenant.id, assetID, parentID, {
first,
orderBy,
after,
}),
});
@@ -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.asset.load(args.id),
asset: (source, args, ctx) => ctx.loaders.Assets.findOrCreate(args),
settings: (parent, args, ctx) => ctx.tenant,
};
@@ -382,8 +382,8 @@ type Comment {
replies will return the replies to this comment.
"""
replies(
first: Int! = 10
orderBy: COMMENT_SORT! = CREATED_AT_DESC
first: Int = 10
orderBy: COMMENT_SORT = CREATED_AT_DESC
after: Cursor
): CommentsConnection
}
@@ -459,8 +459,8 @@ type Asset {
comments are the comments on the Asset.
"""
comments(
first: Int! = 10
orderBy: COMMENT_SORT! = CREATED_AT_DESC
first: Int = 10
orderBy: COMMENT_SORT = CREATED_AT_DESC
after: Cursor
): CommentsConnection
@@ -533,7 +533,7 @@ type Query {
"""
asset is the Asset specified by its ID.
"""
asset(id: ID!): Asset
asset(id: ID, url: String!): Asset
"""
me is the current logged in User.
+100 -30
View File
@@ -1,10 +1,10 @@
import dotize from "dotize";
import { defaults } from "lodash";
import { Db } from "mongodb";
import uuid from "uuid";
import { Omit } from "talk-common/types";
import { TenantResource } from "talk-server/models/tenant";
import uuid from "uuid";
import Query from "./query";
function collection(db: Db) {
return db.collection<Readonly<Asset>>("assets");
@@ -27,48 +27,103 @@ export interface Asset extends TenantResource {
created_at: Date;
}
export type CreateAssetInput = Pick<Asset, "id" | "url">;
export interface UpsertAssetInput {
id?: string;
url: string;
}
export async function createAsset(
export async function upsertAsset(
db: Db,
tenantID: string,
input: CreateAssetInput
{ id, url }: UpsertAssetInput
) {
const now = new Date();
// Construct the filter.
const query = new Query<Asset>(collection(db)).where({
tenant_id: tenantID,
});
if (input.id) {
query.where({ id: input.id });
} else {
query.where({ url: input.url });
}
// TODO: verify that the url for the given Asset is whitelisted by the tenant.
// Craft the update object.
// Create the asset, optionally sourcing the id from the input, additionally
// porting in the tenant_id.
const update: { $setOnInsert: Asset } = {
$setOnInsert: defaults(input, {
id: uuid.v4(),
tenant_id: tenantID,
created_at: now,
}),
$setOnInsert: defaults(
{
url,
tenant_id: tenantID,
created_at: now,
},
{ id },
{
id: uuid.v4(),
}
),
};
// Perform the upsert operation.
const result = await collection(db).findOneAndUpdate(query.filter, update, {
// Create the object if it doesn't already exist.
upsert: true,
// False to return the updated document instead of the original
// document.
returnOriginal: false,
});
// Perform the find and update operation to try and find and or create the
// asset.
const { value: asset } = await collection(db).findOneAndUpdate(
{ url },
update,
{
// Create the object if it doesn't already exist.
upsert: true,
return result.value || null;
// False to return the updated document instead of the original
// document.
returnOriginal: false,
}
);
if (!asset) {
return null;
}
if (!asset.scraped) {
// TODO: create scrape job to collect asset metadata
}
return asset;
}
export interface FindOrCreateAssetInput {
id?: string;
url?: string;
}
export async function findOrCreateAsset(
db: Db,
tenantID: string,
{ id, url }: FindOrCreateAssetInput
) {
if (id) {
if (url) {
// The URL was specified, this is an upsert operation.
return upsertAsset(db, tenantID, {
id,
url,
});
}
// The URL was not specified, this is a lookup operation.
return retrieveAsset(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");
}
return upsertAsset(db, tenantID, { url });
}
export async function retrieveAssetByURL(
db: Db,
tenantID: string,
url: string
) {
return collection(db).findOne({ url, tenant_id: tenantID });
}
export async function retrieveAsset(db: Db, tenantID: string, id: string) {
return await collection(db).findOne({ id, tenant_id: tenantID });
return collection(db).findOne({ id, tenant_id: tenantID });
}
export async function retrieveManyAssets(
@@ -86,6 +141,21 @@ export async function retrieveManyAssets(
return ids.map(id => assets.find(asset => asset.id === id) || null);
}
export async function retrieveManyAssetsByURL(
db: Db,
tenantID: string,
urls: string[]
) {
const cursor = await collection(db).find({
url: { $in: urls },
tenant_id: tenantID,
});
const assets = await cursor.toArray();
return urls.map(url => assets.find(asset => asset.url === url) || null);
}
export type UpdateAssetInput = Omit<
Partial<Asset>,
"id" | "tenant_id" | "url" | "created_at"