Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions __tests__/feeds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1120,19 +1120,24 @@ describe('query feedByIds', () => {
);
});

it('should not authorize when no user is not team member', async () => {
it('should not authorize when no user is not team member and more then 10 post ids', async () => {
loggedUser = '1';
await testQueryErrorCode(
client,
{
query: QUERY,
variables: { first: 10, postIds: ['p1', 'p2'] },
variables: {
first: 10,
postIds: Array(11)
.fill(undefined)
.map((_, index) => `p${index + 1}`),
},
},
'FORBIDDEN',
);
});

it('should return feed by ids for team member', async () => {
it('should return feed by ids', async () => {
loggedUser = '1';
state = await initializeGraphQLTesting(
(req) => new MockContext(con, loggedUser, [], req, true),
Expand Down
114 changes: 114 additions & 0 deletions __tests__/workers/brief/userGenerateBrief.ts
Original file line number Diff line number Diff line change
Expand Up @@ -620,4 +620,118 @@ describe('userGenerateBrief worker', () => {
- **AI agents are still pretty dumb**: Salesforce's benchmark found AI agents only 58% successful on single tasks.`);
expect(briefPost!.content).not.toContain('Empty section');
});

it('should add read more links to items', async () => {
const postId = await generateShortId();

const post = con.getRepository(BriefPost).create({
id: postId,
shortId: postId,
authorId: 'ugbw-1',
private: true,
visible: false,
});

await con.getRepository(BriefPost).save(post);

let requestBody = null;

nock('http://api')
.post('/api/user/briefing', (body) => {
requestBody = body;

return true;
})
.reply(200, {
sections: [
{
title: 'Must know',
items: [
{
title: 'OpenAI gets a DoD contract, Microsoft gets salty',
body: 'OpenAI landed a $200 million contract with the US Department of Defense for AI tools, marking its first direct federal government partnership. This move, reported by The Verge and TechCrunch, signals a shift from OpenAI’s previous stance on military use. It also puts them in direct competition with Microsoft, their main investor, who previously handled government AI contracts through Azure. The tension is real, with OpenAI reportedly considering an antitrust complaint against Microsoft to loosen their grip.',
postIds: ['post-1', 'post-2', 'post-3'],
},
],
},
{
title: 'Good to know',
items: [
{
title: 'AI agents are still pretty dumb, and dangerous',
body: "Salesforce's CRMArena-Pro benchmark found AI agents only 58% successful on single tasks and 35% on multi-step CRM tasks, often mishandling sensitive data due to poor confidentiality awareness.",
},
{
title: 'Threads gets Fediverse feed',
body: "Meta's Threads now offers a dedicated opt-in feed for ActivityPub content and improved profile search for Fediverse users, marking its most prominent integration with the open social web to date.",
postIds: [
'post-4',
'post-5',
'post-6',
'post-7',
'post-8',
'post-9',
'post-10',
'post-11',
'post-12',
'post-13',
'post-14',
],
},
],
},
],
});

await expectSuccessfulTypedBackground(worker, {
payload: new UserBriefingRequest({
userId: 'ugbw-1',
frequency: BriefingType.Daily,
modelName: BriefingModel.Default,
}),
postId,
});

expect(requestBody).toEqual({
user_id: 'ugbw-1',
frequency: BriefingType.Daily,
model_name: BriefingModel.Default,
allowed_tags: ['webdev', 'development'],
seniority_level: 'NOT_ENGINEER',
});

const briefPost = await con.getRepository(BriefPost).findOne({
where: {
id: postId,
},
});

expect(briefPost).toBeDefined();
expect(briefPost!.private).toBe(false);
expect(briefPost!.visible).toBe(true);
expect(briefPost!.content).toBe(`## Must know

- **OpenAI gets a DoD contract, Microsoft gets salty**: OpenAI landed a $200 million contract with the US Department of Defense for AI tools, marking its first direct federal government partnership. This move, reported by The Verge and TechCrunch, signals a shift from OpenAI’s previous stance on military use. It also puts them in direct competition with Microsoft, their main investor, who previously handled government AI contracts through Azure. The tension is real, with OpenAI reportedly considering an antitrust complaint against Microsoft to loosen their grip. [Read more](http://localhost:5002/feed-by-ids?id=post-1&id=post-2&id=post-3)

## Good to know

- **AI agents are still pretty dumb, and dangerous**: Salesforce's CRMArena-Pro benchmark found AI agents only 58% successful on single tasks and 35% on multi-step CRM tasks, often mishandling sensitive data due to poor confidentiality awareness.
- **Threads gets Fediverse feed**: Meta's Threads now offers a dedicated opt-in feed for ActivityPub content and improved profile search for Fediverse users, marking its most prominent integration with the open social web to date. [Read more](http://localhost:5002/feed-by-ids?id=post-4&id=post-5&id=post-6&id=post-7&id=post-8&id=post-9&id=post-10&id=post-11&id=post-12&id=post-13)`);

expect(triggerTypedEvent).toHaveBeenCalledWith(
expect.anything(),
'api.v1.brief-ready',
{
payload: new UserBriefingRequest({
userId: 'ugbw-1',
frequency: BriefingType.Daily,
modelName: BriefingModel.Default,
allowedTags: ['webdev', 'development'],
seniorityLevel: 'NOT_ENGINEER',
}),
postId,
},
);
expect(triggerTypedEvent).toHaveBeenCalledTimes(1);
});
});
2 changes: 2 additions & 0 deletions src/common/brief.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,5 @@ export const getBriefGenerationCost = async (

return pricingConfig[type];
};

export const briefingPostIdsMaxItems = 10;
14 changes: 9 additions & 5 deletions src/schema/feeds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ import { ContentPreferenceSource } from '../entity/contentPreference/ContentPref
import { randomUUID } from 'crypto';
import { SourceMemberRoles } from '../roles';
import { ContentPreferenceKeyword } from '../entity/contentPreference/ContentPreferenceKeyword';
import { briefingPostIdsMaxItems } from '../common/brief';

interface GQLTagsCategory {
id: string;
Expand Down Expand Up @@ -1581,11 +1582,14 @@ export const resolvers: IResolvers<unknown, BaseContext> = traceResolvers<
(ctx, args, { limit, offset }, builder) =>
builder.limit(limit).offset(offset),
{
fetchQueryParams: async (ctx): Promise<void> => {
if (!ctx.isTeamMember) {
throw new ForbiddenError(
'Access denied! You need to be authorized to perform this action!',
);
fetchQueryParams: async (ctx, { postIds }): Promise<void> => {
// limit for non-team members, eg. for brief
if (postIds.length > briefingPostIdsMaxItems) {
if (!ctx.isTeamMember) {
throw new ForbiddenError(
'Access denied! You need to be authorized to perform this action!',
);
}
}
},
},
Expand Down
14 changes: 13 additions & 1 deletion src/workers/brief/userGenerateBrief.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { updateFlagsStatement } from '../../common';
import { insertOrIgnoreAction } from '../../schema/actions';
import {
briefFeedClient,
briefingPostIdsMaxItems,
getUserConfigForBriefingRequest,
} from '../../common/brief';
import { queryReadReplica } from '../../common/queryReadReplica';
Expand All @@ -24,7 +25,18 @@ const generateMarkdown = (data: Briefing): string => {
markdown += `## ${section.title}\n\n`;

for (const item of section.items) {
markdown += `- **${item.title}**: ${item.body}\n`;
const readMoreUrl = new URL(
`${process.env.COMMENTS_PREFIX}/feed-by-ids`,
);
const hasPostIds = item.postIds && item.postIds.length > 0;

if (hasPostIds) {
item.postIds.slice(0, briefingPostIdsMaxItems).forEach((postId) => {
readMoreUrl.searchParams.append('id', postId);
});
}

markdown += `- **${item.title}**: ${item.body}${hasPostIds ? ` [Read more](${readMoreUrl.toString()})` : ''}\n`;
}

markdown += '\n';
Expand Down
Loading