mirror of
https://github.com/wassname/talk.git
synced 2026-07-04 07:36:00 +08:00
feat: asset url query
This commit is contained in:
@@ -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
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user