Skip to content

Commit

Permalink
Merge pull request #9584 from hicommonwealth/rotorsoft/9103-get-topic…
Browse files Browse the repository at this point in the history
…s-query

Refactor getTopics
  • Loading branch information
Rotorsoft authored Oct 21, 2024
2 parents 1927f79 + 79a5417 commit d5240f1
Show file tree
Hide file tree
Showing 49 changed files with 262 additions and 374 deletions.
107 changes: 107 additions & 0 deletions libs/model/src/community/GetTopics.query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { Query } from '@hicommonwealth/core';
import * as schemas from '@hicommonwealth/schemas';
import { QueryTypes } from 'sequelize';
import { z } from 'zod';
import { models } from '../database';

export function GetTopics(): Query<typeof schemas.GetTopics> {
return {
...schemas.GetTopics,
auth: [],
secure: false,
body: async ({ payload }) => {
const { community_id, with_contest_managers } = payload;

const contest_managers = with_contest_managers
? `
SELECT
td.*,
coalesce((
SELECT
jsonb_agg(jsonb_set(to_jsonb (cm), -- Convert the contest manager (cm) row to JSONB
'{content}', -- Set the 'content' key in the resulting JSONB
coalesce((
SELECT
jsonb_agg(ca) -- Aggregates the filtered actions into content
FROM "ContestActions" ca
WHERE
ca.contest_address = cm.contest_address
AND ca.action = 'added'
AND ca.created_at > co.start_time
AND ca.created_at < co.end_time), '[]'::jsonb) -- Use an empty array as fallback if no actions are found
))
FROM "ContestTopics" ct
LEFT JOIN "ContestManagers" cm ON cm.contest_address = ct.contest_address
JOIN (
-- Subquery to get the max contest_id, start_time, and end_time for each contest address
SELECT
contest_address,
max(contest_id) AS max_contest_id,
max(start_time) AS start_time,
max(end_time) AS end_time
FROM
"Contests"
GROUP BY
contest_address) co ON cm.contest_address = co.contest_address
WHERE
ct.topic_id = td.id
AND cm.community_id = :community_id
AND COALESCE(cm.cancelled, FALSE) = FALSE -- Exclude cancelled managers
AND (cm.interval = 0
AND now() < co.end_time -- Check if the interval is 0 and the contest is ongoing
OR cm.interval > 0 -- Or if there is a valid interval
)), '[]'::jsonb) AS active_contest_managers
FROM
topic_data td
`
: `SELECT *, '[]'::json as active_contest_managers FROM topic_data`;

const sql = `
WITH topic_data AS (
SELECT
id,
name,
community_id,
description,
telegram,
featured_in_sidebar,
featured_in_new_post,
default_offchain_template,
"order",
channel_id,
group_ids,
weighted_voting,
token_symbol,
vote_weight_multiplier,
created_at::text AS created_at,
updated_at::text AS updated_at,
deleted_at::text AS deleted_at,
(
SELECT
count(*)::int
FROM
"Threads"
WHERE
community_id = :community_id
AND topic_id = t.id
AND deleted_at IS NULL) AS total_threads
FROM
"Topics" t
WHERE
t.community_id = :community_id
AND t.deleted_at IS NULL
)
${contest_managers}
`;

return await models.sequelize.query<z.infer<typeof schemas.TopicView>>(
sql,
{
replacements: { community_id },
type: QueryTypes.SELECT,
raw: true,
},
);
},
};
}
1 change: 1 addition & 0 deletions libs/model/src/community/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export * from './GetCommunityStake.query';
export * from './GetMembers.query';
export * from './GetStakeHistoricalPrice.query';
export * from './GetStakeTransaction.query';
export * from './GetTopics.query';
export * from './JoinCommunity.command';
export * from './RefreshCommunityMemberships.command';
export * from './RefreshCustomDomain.query';
Expand Down
2 changes: 1 addition & 1 deletion libs/model/src/thread/CreateThread.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export function CreateThread(): Command<
...schemas.CreateThread,
auth: [
isAuthorized({
action: schemas.PermissionEnum.CREATE_THREAD
action: schemas.PermissionEnum.CREATE_THREAD,
}),
verifyThreadSignature,
],
Expand Down
9 changes: 9 additions & 0 deletions libs/model/test/community/community-lifecycle.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
DeleteTopic,
GetCommunities,
GetMembers,
GetTopics,
JoinCommunity,
JoinCommunityErrors,
MAX_GROUPS_PER_COMMUNITY,
Expand Down Expand Up @@ -580,6 +581,14 @@ describe('Community lifecycle', () => {
}),
).rejects.toThrow(InvalidActor);
});

test('should get topics', async () => {
const topics = await query(GetTopics(), {
actor: superAdminActor,
payload: { community_id: community.id, with_contest_managers: false },
});
expect(topics?.length).toBe(4);
});
});

describe('updates', () => {
Expand Down
31 changes: 31 additions & 0 deletions libs/schemas/src/queries/community.schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ import {
Community,
CommunityMember,
CommunityStake,
ContestManager,
ExtendedCommunity,
Topic,
} from '../entities';
import * as projections from '../projections';
import { PG_INT } from '../utils';
import { PaginatedResultSchema, PaginationParamsSchema } from './pagination';

Expand Down Expand Up @@ -159,3 +162,31 @@ export const GetStakeHistoricalPrice = {
})
.array(),
};

export const ConstestManagerView = ContestManager.extend({
created_at: z.string(),
topics: z.undefined(),
contests: z.undefined(),
content: z.array(
projections.ContestAction.extend({
created_at: z.string(),
}),
),
});

export const TopicView = Topic.extend({
created_at: z.string(),
updated_at: z.string().nullish(),
deleted_at: z.string().nullish(),
contest_topics: z.undefined(),
total_threads: z.number(),
active_contest_managers: z.array(ConstestManagerView),
});

export const GetTopics = {
input: z.object({
community_id: z.string(),
with_contest_managers: z.boolean().optional(),
}),
output: z.array(TopicView),
};
2 changes: 1 addition & 1 deletion libs/schemas/src/queries/thread.schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { z } from 'zod';
import { Thread } from '../entities';
import {
DiscordMetaSchema,
PG_INT,
linksSchema,
paginationSchema,
PG_INT,
} from '../utils';

export const OrderByQueriesKeys = z.enum([
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ProposalType } from '@hicommonwealth/shared';
import type moment from 'moment';
import type Topic from './Topic';
import type { Topic } from './Topic';
import type { IUniqueId } from './interfaces';

class AbridgedThread implements IUniqueId {
Expand Down
5 changes: 2 additions & 3 deletions packages/commonwealth/client/scripts/models/Thread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import moment, { Moment } from 'moment';
import { z } from 'zod';
import Comment from './Comment';
import type { ReactionType } from './Reaction';
import Topic from './Topic';
import type { Topic } from './Topic';
import type { IUniqueId } from './interfaces';
import type { ThreadKind, ThreadStage } from './types';

Expand Down Expand Up @@ -375,8 +375,7 @@ export class Thread implements IUniqueId {
this.identifier = `${id}`;
this.createdAt = moment(created_at);
this.updatedAt = moment(updated_at);
// @ts-expect-error StrictNullChecks
this.topic = topic?.id ? new Topic({ ...(topic || {}) } as any) : null;
this.topic = { ...topic };
this.kind = kind;
this.stage = stage;
this.authorCommunity = Address?.community_id;
Expand Down
77 changes: 1 addition & 76 deletions packages/commonwealth/client/scripts/models/Topic.ts
Original file line number Diff line number Diff line change
@@ -1,79 +1,4 @@
import * as schemas from '@hicommonwealth/schemas';
import { z } from 'zod';

const ActiveContestManagers = z.object({
content: z.array(schemas.ContestAction),
contest_manager: schemas.ContestManager,
});

type TopicAttributesBase = z.infer<typeof schemas.Topic>;

type TopicAttributesExtended = TopicAttributesBase & {
active_contest_managers: Array<z.infer<typeof ActiveContestManagers>>;
total_threads: number;
};

export type TopicAttributes = TopicAttributesExtended;

class Topic {
public readonly name: TopicAttributes['name'];
public readonly id: TopicAttributes['id'];
public readonly description: TopicAttributes['description'];
public readonly telegram: TopicAttributes['telegram'];
public readonly communityId: TopicAttributes['community_id'];
public readonly channelId: TopicAttributes['channel_id'];
public readonly featuredInSidebar: TopicAttributes['featured_in_sidebar'];
public readonly featuredInNewPost: TopicAttributes['featured_in_new_post'];
public order: TopicAttributes['order'];
public readonly defaultOffchainTemplate: TopicAttributes['default_offchain_template'];
public totalThreads: TopicAttributes['total_threads'];
public readonly activeContestManagers: TopicAttributes['active_contest_managers'];
public readonly groupIds: TopicAttributes['group_ids'];
public readonly defaultOffchainTemplateBackup: TopicAttributes['default_offchain_template_backup'];
public readonly weightedVoting: TopicAttributes['weighted_voting'];
public readonly tokenAddress: TopicAttributes['token_address'];
public readonly tokenSymbol: TopicAttributes['token_symbol'];
public readonly voteWeightMultiplier: TopicAttributes['vote_weight_multiplier'];

constructor({
name,
id,
description,
telegram,
community_id,
featured_in_sidebar,
featured_in_new_post,
order,
default_offchain_template,
total_threads,
channel_id,
active_contest_managers,
group_ids,
default_offchain_template_backup,
weighted_voting,
token_address,
token_symbol,
vote_weight_multiplier,
}: TopicAttributes) {
this.name = name;
this.id = id;
this.description = description;
this.telegram = telegram;
this.communityId = community_id;
this.featuredInSidebar = featured_in_sidebar;
this.featuredInNewPost = featured_in_new_post;
this.order = order;
this.defaultOffchainTemplate = default_offchain_template;
this.totalThreads = total_threads || 0;
this.channelId = channel_id;
this.activeContestManagers = active_contest_managers || [];
this.groupIds = group_ids;
this.defaultOffchainTemplateBackup = default_offchain_template_backup;
this.weightedVoting = weighted_voting;
this.tokenAddress = token_address;
this.tokenSymbol = token_symbol;
this.voteWeightMultiplier = vote_weight_multiplier;
}
}

export default Topic;
export type Topic = z.infer<typeof schemas.TopicView>;
1 change: 0 additions & 1 deletion packages/commonwealth/client/scripts/state/api/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export const queryClient = new QueryClient({
export const ApiEndpoints = {
// endpoint builder functions like getEndpoint(id) => /endpoint/:id should have camel cased keys
// stand alone endpoints should be have upper snake case keys so we can easily tell them apart in code
BULK_TOPICS: '/topics',
FETCH_ADMIN: '/roles',
FETCH_COMMUNITY_STAKES: '/communityStakes',
FETCH_COMMENTS: '/viewComments',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AxiosResponse } from 'axios';
import Thread from 'models/Thread';
import Topic from 'models/Topic';
import type { Topic } from 'models/Topic';
import { ThreadKind, ThreadStage } from 'models/types';

type ActivityResponse = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { toCanvasSignedDataApiArgs } from '@hicommonwealth/shared';
import { signThread } from 'controllers/server/sessions';
import Topic from 'models/Topic';
import type { Topic } from 'models/Topic';
import { ThreadStage } from 'models/types';
import useUserOnboardingSliderMutationStore from 'state/ui/userTrainingCards';
import { trpc } from 'utils/trpcClient';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Thread from 'models/Thread';
import Topic from 'models/Topic';
import type { Topic } from 'models/Topic';
import { ApiEndpoints, queryClient } from 'state/api/config';

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { trpc } from 'client/scripts/utils/trpcClient';
import { ApiEndpoints, queryClient } from 'state/api/config';
import { trpc } from 'utils/trpcClient';

const useCreateTopicMutation = () => {
const utils = trpc.useUtils();
return trpc.community.createTopic.useMutation({
onSuccess: async (response) => {
await queryClient.invalidateQueries({
queryKey: [ApiEndpoints.BULK_TOPICS, response.topic.community_id],
await utils.community.getTopics.invalidate({
community_id: response.topic.community_id,
});
},
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { trpc } from 'client/scripts/utils/trpcClient';
import { ApiEndpoints, queryClient } from 'state/api/config';
import { trpc } from 'utils/trpcClient';

const useDeleteTopicMutation = () => {
const utils = trpc.useUtils();
return trpc.community.deleteTopic.useMutation({
onSuccess: async (response) => {
await queryClient.invalidateQueries({
queryKey: [ApiEndpoints.BULK_TOPICS, response.community_id],
await utils.community.getTopics.invalidate({
community_id: response.community_id,
});
// TODO: add a new method in thread cache to deal with this
// await app.threads.listingStore.removeTopic(variables.topicName);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { trpc } from 'client/scripts/utils/trpcClient';
import { ApiEndpoints, queryClient } from 'state/api/config';
import { trpc } from 'utils/trpcClient';

const useEditTopicMutation = () => {
const utils = trpc.useUtils();
return trpc.community.updateTopic.useMutation({
onSuccess: async (data) => {
await queryClient.invalidateQueries({
queryKey: [ApiEndpoints.BULK_TOPICS, data.topic.community_id],
onSuccess: async (response) => {
await utils.community.getTopics.invalidate({
community_id: response.topic.community_id,
});
},
});
Expand Down
Loading

0 comments on commit d5240f1

Please sign in to comment.