Skip to content

Commit

Permalink
Merge branch 'master' into ka.testcontainers
Browse files Browse the repository at this point in the history
  • Loading branch information
kurtisassad committed Nov 22, 2024
2 parents 3ca12be + a6aec4d commit 90b9c17
Show file tree
Hide file tree
Showing 80 changed files with 1,447 additions and 874 deletions.
1 change: 1 addition & 0 deletions libs/adapters/src/trpc/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export enum Tag {
DiscordBot = 'DiscordBot',
Token = 'Token',
Contest = 'Contest',
Poll = 'Poll',
}

export type Commit<Input extends ZodSchema, Output extends ZodSchema> = (
Expand Down
23 changes: 16 additions & 7 deletions libs/model/src/community/CreateGroup.command.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { InvalidInput, type Command } from '@hicommonwealth/core';
import * as schemas from '@hicommonwealth/schemas';
import { PermissionEnum } from '@hicommonwealth/schemas';
import { Op } from 'sequelize';
import { models, sequelize } from '../database';
import { authRoles } from '../middleware';
Expand Down Expand Up @@ -70,13 +71,21 @@ export function CreateGroup(): Command<typeof schemas.CreateGroup> {

if (group.id) {
// add topic level interaction permissions for current group
const groupPermissions = (payload.topics || []).map((t) => ({
group_id: group.id!,
topic_id: t.id,
allowed_actions: sequelize.literal(
`ARRAY[${t.permissions.map((p) => `'${p}'`).join(', ')}]::"enum_GroupPermissions_allowed_actions"[]`,
) as unknown as schemas.PermissionEnum[],
}));
const groupPermissions = (payload.topics || []).map((t) => {
const permissions = t.permissions;
// Enable UPDATE_POLL by default for all group permissions
// TODO: remove once client supports selecting the UPDATE_POLL permission
permissions.push(PermissionEnum.UPDATE_POLL);
return {
group_id: group.id!,
topic_id: t.id,
allowed_actions: sequelize.literal(
`ARRAY[${permissions
.map((p) => `'${p}'`)
.join(', ')}]::"enum_GroupPermissions_allowed_actions"[]`,
) as unknown as schemas.PermissionEnum[],
};
});
await models.GroupPermission.bulkCreate(groupPermissions, {
transaction,
});
Expand Down
7 changes: 6 additions & 1 deletion libs/model/src/community/UpdateGroup.command.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { InvalidInput, type Command } from '@hicommonwealth/core';
import * as schemas from '@hicommonwealth/schemas';
import { PermissionEnum } from '@hicommonwealth/schemas';
import { Op } from 'sequelize';
import { models, sequelize } from '../database';
import { authRoles } from '../middleware';
Expand Down Expand Up @@ -90,10 +91,14 @@ export function UpdateGroup(): Command<typeof schemas.UpdateGroup> {
// update topic level interaction permissions for current group
await Promise.all(
(payload.topics || [])?.map(async (t) => {
const permissions = t.permissions;
if (!permissions.includes(PermissionEnum.UPDATE_POLL)) {
permissions.push(PermissionEnum.UPDATE_POLL);
}
if (group.id) {
await models.GroupPermission.update(
{
allowed_actions: t.permissions,
allowed_actions: permissions,
},
{
where: {
Expand Down
2 changes: 2 additions & 0 deletions libs/model/src/contest/GetAllContests.query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ select
cm.created_at,
cm.name,
cm.image_url,
cm.description,
cm.funding_token_address,
cm.prize_percentage,
cm.payout_structure,
Expand Down Expand Up @@ -84,6 +85,7 @@ group by
cm.created_at,
cm.name,
cm.image_url,
cm.description,
cm.funding_token_address,
cm.prize_percentage,
cm.payout_structure,
Expand Down
1 change: 1 addition & 0 deletions libs/model/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export * as DiscordBot from './discordBot';
export * as Email from './emails';
export * as Feed from './feed';
export * as LoadTest from './load-testing';
export * as Poll from './poll';
export * as Reaction from './reaction';
export * as Snapshot from './snapshot';
export * as Subscription from './subscription';
Expand Down
57 changes: 53 additions & 4 deletions libs/model/src/middleware/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
Context,
InvalidActor,
InvalidInput,
InvalidState,
} from '@hicommonwealth/core';
import {
Address,
Expand All @@ -12,6 +13,8 @@ import {
CommentContextInput,
Group,
GroupPermissionAction,
PollContext,
PollContextInput,
ReactionContext,
ReactionContextInput,
ThreadContext,
Expand Down Expand Up @@ -81,7 +84,7 @@ async function findThread(
thread,
author_address_id: thread.address_id,
community_id: thread.community_id,
topic_id: thread.topic_id ?? undefined,
topic_id: thread.topic_id,
is_collaborator,
};
}
Expand Down Expand Up @@ -117,6 +120,17 @@ async function findReaction(
};
}

async function findPoll(actor: Actor, poll_id: number) {
const poll = await models.Poll.findOne({
where: { id: poll_id },
});
if (!poll) {
throw new InvalidInput('Must provide a valid poll id to authorize');
}

return poll;
}

async function findAddress(
actor: Actor,
community_id: string,
Expand Down Expand Up @@ -206,9 +220,11 @@ async function hasTopicPermissions(
}
>(
`
SELECT g.*, gp.topic_id, gp.allowed_actions
FROM "Groups" as g JOIN "GroupPermissions" gp ON g.id = gp.group_id
WHERE g.community_id = :community_id AND gp.topic_id = :topic_id
SELECT g.*, gp.topic_id, gp.allowed_actions
FROM "Groups" as g
JOIN "GroupPermissions" gp ON g.id = gp.group_id
WHERE g.community_id = :community_id
AND gp.topic_id = :topic_id
`,
{
type: QueryTypes.SELECT,
Expand Down Expand Up @@ -499,3 +515,36 @@ export function authReaction() {
await mustBeAuthorized(ctx, { author: true });
};
}

export function authPoll({ action }: AggregateAuthOptions) {
return async (ctx: Context<typeof PollContextInput, typeof PollContext>) => {
const poll = await findPoll(ctx.actor, ctx.payload.poll_id);
const threadAuth = await findThread(ctx.actor, poll.thread_id, false);
const { address, is_author } = await findAddress(
ctx.actor,
threadAuth.community_id,
['admin', 'moderator', 'member'],
);

if (threadAuth.thread.archived_at)
throw new InvalidState('Thread is archived');
(ctx as { context: PollContext }).context = {
address,
is_author,
poll,
poll_id: poll.id!,
community_id: threadAuth.community_id,
thread: threadAuth.thread,
};

await mustBeAuthorized(ctx, {
author: true,
permissions: action
? {
topic_id: threadAuth.topic_id,
action,
}
: undefined,
});
};
}
13 changes: 12 additions & 1 deletion libs/model/src/middleware/guards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import {
import {
AuthContext,
CommentContext,
PollContext,
ThreadContext,
} from '@hicommonwealth/schemas';
import moment from 'moment';
import type { AddressInstance, ThreadInstance } from '../models';
import type { AddressInstance, PollInstance, ThreadInstance } from '../models';

const log = logger(import.meta);

Expand Down Expand Up @@ -116,6 +117,16 @@ export function mustBeAuthorizedComment(
};
}

export function mustBeAuthorizedPoll(actor: Actor, context?: PollContext) {
if (!context?.address) throw new InvalidActor(actor, 'Not authorized');
if (!context?.poll) throw new InvalidActor(actor, 'Not authorized poll');
return context as PollContext & {
address: AddressInstance;
poll: PollInstance;
thread: ThreadInstance;
};
}

/**
* Guards for starting and ending dates to be in a valid date range
* @param start_date start date
Expand Down
2 changes: 2 additions & 0 deletions libs/model/src/models/associations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ export const buildAssociations = (db: DB) => {
})
.withMany(db.Referral, {
foreignKey: 'referrer_id',
asOne: 'referrer',
onUpdate: 'CASCADE',
onDelete: 'CASCADE',
})
.withMany(db.Referral, {
foreignKey: 'referee_id',
asOne: 'referee',
onUpdate: 'CASCADE',
onDelete: 'CASCADE',
});
Expand Down
23 changes: 3 additions & 20 deletions libs/model/src/models/poll.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,9 @@
import { Poll } from '@hicommonwealth/schemas';
import Sequelize from 'sequelize';
import type { CommunityAttributes } from './community';
import type { ThreadAttributes } from './thread';
import { z } from 'zod';
import type { ModelInstance } from './types';
import type { VoteAttributes } from './vote';

export type PollAttributes = {
id: number;
community_id: string;
thread_id: number;
prompt: string;
options: string;
ends_at: Date;

created_at?: Date;
updated_at?: Date;
last_commented_on?: Date;

// associations
Thread?: ThreadAttributes;
Community?: CommunityAttributes;
votes?: VoteAttributes[];
};
export type PollAttributes = z.infer<typeof Poll>;

export type PollInstance = ModelInstance<PollAttributes>;

Expand Down
17 changes: 3 additions & 14 deletions libs/model/src/models/vote.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,9 @@
import { Vote } from '@hicommonwealth/schemas';
import Sequelize from 'sequelize';
import type { PollAttributes } from './poll';
import { z } from 'zod';
import type { ModelInstance } from './types';

export type VoteAttributes = {
poll_id: number;
option: string;
address: string;
author_community_id: string;
community_id: string;
id?: number;
created_at?: Date;
updated_at?: Date;

// associations
poll?: PollAttributes;
};
export type VoteAttributes = z.infer<typeof Vote>;

export type VoteInstance = ModelInstance<VoteAttributes>;

Expand Down
45 changes: 45 additions & 0 deletions libs/model/src/poll/createPollVote.command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Command, InvalidState } from '@hicommonwealth/core';
import * as schemas from '@hicommonwealth/schemas';
import moment from 'moment/moment';
import { models } from '../database';
import { authPoll } from '../middleware';
import { mustBeAuthorizedPoll } from '../middleware/guards';

export const CreateVotePollErrors = {
InvalidOption: 'Invalid response option',
PollingClosed: 'Polling already finished',
};

export function CreatePollVote(): Command<typeof schemas.CreatePollVote> {
return {
...schemas.CreatePollVote,
auth: [
authPoll({
action: 'UPDATE_POLL',
}),
],
body: async ({ actor, payload, context }) => {
const { poll, address } = mustBeAuthorizedPoll(actor, context);
if (
!poll.ends_at &&
moment(poll.ends_at).utc().isBefore(moment().utc())
) {
throw new InvalidState(CreateVotePollErrors.PollingClosed);
}

// TODO: migrate this to be JSONB array of strings in the DB
const options = JSON.parse(poll.options);
if (!options.includes(payload.option)) {
throw new InvalidState(CreateVotePollErrors.InvalidOption);
}

return models.Vote.create({
poll_id: payload.poll_id,
address: address.address,
author_community_id: address.community_id,
community_id: poll.community_id,
option: payload.option,
});
},
};
}
1 change: 1 addition & 0 deletions libs/model/src/poll/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './createPollVote.command';
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export async function getSolanaBalances(
const chainNode = await models.ChainNode.scope('withPrivateData').findOne({
where: {
balance_type: 'solana',
name: 'Solana (Mainnet Beta)',
name: 'Solana Mainnet',
},
});
if (!chainNode) {
Expand Down
29 changes: 16 additions & 13 deletions libs/model/src/token/GetToken.query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,26 @@ export function GetToken(): Query<typeof schemas.GetToken> {
${
with_stats
? `WITH latest_trades AS (SELECT DISTINCT ON (token_address) *
FROM "LaunchpadTrades"
ORDER BY token_address, timestamp DESC),
older_trades AS (SELECT DISTINCT ON (token_address) *
FROM "LaunchpadTrades"
WHERE timestamp >=
(SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP - INTERVAL '24 hours'))
ORDER BY token_address, timestamp ASC),
trades AS (SELECT lt.token_address,
lt.price as latest_price,
ot.price as old_price
FROM latest_trades lt
LEFT JOIN
older_trades ot ON lt.token_address = ot.token_address)`
FROM "LaunchpadTrades"
ORDER BY token_address, timestamp DESC),
older_trades AS (SELECT DISTINCT ON (token_address) *
FROM "LaunchpadTrades"
WHERE timestamp >=
(SELECT EXTRACT(
EPOCH FROM CURRENT_TIMESTAMP - INTERVAL '24 hours'
))
ORDER BY token_address, timestamp ASC),
trades AS (SELECT lt.token_address,
lt.price as latest_price,
ot.price as old_price
FROM latest_trades lt
LEFT JOIN
older_trades ot ON lt.token_address = ot.token_address)`
: ''
}
SELECT T.*${with_stats ? ', trades.latest_price, trades.old_price' : ''}
FROM "Tokens" as T
${with_stats ? 'LEFT JOIN trades ON trades.token_address = T.token_address' : ''}
WHERE T.namespace = :namespace;
`;

Expand Down
Loading

0 comments on commit 90b9c17

Please sign in to comment.