Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: improve typescript coverage #572

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
13 changes: 11 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"scripts": {
"lint": "eslint . --ext .ts --fix",
"dev": "nodemon src/index.ts",
"start": "ts-node src/index.ts"
"start": "ts-node src/index.ts",
"typecheck": "tsc"
},
"eslintConfig": {
"extends": "@snapshot-labs"
Expand Down Expand Up @@ -41,7 +42,15 @@
"devDependencies": {
"@snapshot-labs/eslint-config": "^0.1.0-beta.7",
"@snapshot-labs/prettier-config": "^0.1.0-beta.7",
"@types/node": "^14.0.13",
"@types/bluebird": "^3.5.38",
"@types/cors": "^2.8.13",
"@types/express": "^4.17.17",
"@types/express-rate-limit": "^6.0.0",
"@types/graphql-depth-limit": "^1.1.3",
"@types/graphql-fields": "^1.3.5",
"@types/lodash": "^4.14.194",
"@types/mysql": "^2.15.21",
"@types/node": "^18.15.11",
"eslint": "^8.28.0",
"nodemon": "^2.0.19",
"prettier": "^2.8.0"
Expand Down
148 changes: 90 additions & 58 deletions src/graphql/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,26 @@ import { jsonParse } from '../helpers/utils';
import { spaceProposals, spaceFollowers } from '../helpers/spaces';
import db from '../helpers/mysql';

import type {
Strategy,
QueryArgs,
Countable,
SqlRow,
User,
Subscription,
Follow,
Vote,
Proposal,
Space
} from '../types';

type QueryFields = { [key: string]: string };

const network = process.env.NETWORK || 'testnet';

export class PublicError extends Error {}

const ARG_LIMITS = {
const ARG_LIMITS: { [key: string]: Countable } = {
default: {
first: 1000,
skip: 5000
Expand All @@ -20,7 +35,7 @@ const ARG_LIMITS = {
}
};

export function checkLimits(args: any = {}, type) {
export function checkLimits(args: QueryArgs, type: string) {
const { where = {} } = args;
const typeLimits = { ...ARG_LIMITS.default, ...(ARG_LIMITS[type] || {}) };

Expand All @@ -35,7 +50,7 @@ export function checkLimits(args: any = {}, type) {
return true;
}

export function formatSpace(id, settings) {
export function formatSpace(id: string, settings: string) {
const space = jsonParse(settings, {});
space.id = id;
space.private = space.private || false;
Expand All @@ -61,7 +76,7 @@ export function formatSpace(id, settings) {
space.proposalsCount = spaceProposals[id]?.count || 0;
space.voting.hideAbstain = space.voting.hideAbstain || false;
space.voteValidation = space.voteValidation || { name: 'any', params: {} };
space.strategies = space.strategies?.map(strategy => ({
space.strategies = space.strategies?.map((strategy: Strategy) => ({
...strategy,
// By default return space network if strategy network is not defined
network: strategy.network || space.network
Expand All @@ -78,13 +93,13 @@ export function formatSpace(id, settings) {
// always return parent and children in child node format
// will be overwritten if other fields than id are requested
space.parent = space.parent ? { id: space.parent } : null;
space.children = space.children?.map(child => ({ id: child })) || [];
space.children = space.children?.map((child: string) => ({ id: child })) || [];

return space;
return space as Space;
}

export function buildWhereQuery(fields, alias, where) {
let query: any = '';
export function buildWhereQuery(fields: QueryFields, alias: string, where: QueryArgs['where']) {
let query = '';
const params: any[] = [];
Object.entries(fields).forEach(([field, type]) => {
if (where[field] !== undefined) {
Expand Down Expand Up @@ -136,13 +151,13 @@ export function buildWhereQuery(fields, alias, where) {
return { query, params };
}

export async function fetchSpaces(args) {
export async function fetchSpaces(args: QueryArgs): Promise<Space[]> {
const { first = 20, skip = 0, where = {} } = args;

const fields = { id: 'string' };
const fields: QueryFields = { id: 'string' };
const whereQuery = buildWhereQuery(fields, 's', where);
const queryStr = whereQuery.query;
const params: any[] = whereQuery.params;
const params = whereQuery.params;

let orderBy = args.orderBy || 'created_at';
let orderDirection = args.orderDirection || 'desc';
Expand All @@ -159,10 +174,12 @@ export async function fetchSpaces(args) {
params.push(skip, first);

const spaces = await db.queryAsync(query, params);
return spaces.map(space => Object.assign(space, formatSpace(space.id, space.settings)));
return spaces.map(space =>
Object.assign(space, formatSpace(space.id as string, space.settings as string))
);
}

function checkRelatedSpacesNesting(requestedFields): void {
function checkRelatedSpacesNesting(requestedFields: any): void {
// for a children's parent or a parent's children, you can ONLY query id
// (for the purpose of easier cross-checking of relations in frontend)
// other than that, deeper nesting is not supported
Expand All @@ -184,7 +201,7 @@ function checkRelatedSpacesNesting(requestedFields): void {
}
}

function needsRelatedSpacesData(requestedFields): boolean {
function needsRelatedSpacesData(requestedFields: any): boolean {
// id's of parent/children are already included in the result from fetchSpaces
// an additional query is only needed if other fields are requested
return !(
Expand All @@ -193,7 +210,7 @@ function needsRelatedSpacesData(requestedFields): boolean {
);
}

function mapRelatedSpacesToSpaces(spaces, relatedSpaces) {
function mapRelatedSpacesToSpaces(spaces: Space[], relatedSpaces: Space[]) {
if (!relatedSpaces.length) return spaces;

return spaces.map(space => {
Expand All @@ -203,26 +220,29 @@ function mapRelatedSpacesToSpaces(spaces, relatedSpaces) {
.filter(s => s);
}
if (space.parent) {
space.parent = relatedSpaces.find(s => s.id === space.parent.id) || space.parent;
space.parent =
relatedSpaces.find(s => space.parent && s.id === space.parent.id) || space.parent;
}
return space;
});
}

async function fetchRelatedSpaces(spaces) {
async function fetchRelatedSpaces(spaces: Space[]) {
// collect all parent and child ids of all spaces
const relatedSpaceIDs = spaces.reduce((ids, space) => {
const relatedSpaceIDs = spaces.reduce((ids: string[], space: Space) => {
if (space.children) ids.push(...space.children.map(c => c.id));
if (space.parent) ids.push(space.parent.id);
return ids;
}, []);

return await fetchSpaces({
where: { id_in: relatedSpaceIDs }
where: { id_in: relatedSpaceIDs },
first: 20,
skip: 0
});
}

export async function handleRelatedSpaces(info: any, spaces: any[]) {
export async function handleRelatedSpaces(info: any, spaces: Space[]) {
const requestedFields = info ? graphqlFields(info) : {};
if (needsRelatedSpacesData(requestedFields)) {
checkRelatedSpacesNesting(requestedFields);
Expand All @@ -232,56 +252,68 @@ export async function handleRelatedSpaces(info: any, spaces: any[]) {
return spaces;
}

export function formatUser(user) {
const profile = jsonParse(user.profile, {});
export function formatUser(user: SqlRow) {
const profile = jsonParse(user.profile as string, {});
delete user.profile;

return {
...user,
...profile
};
} as User;
}

export function formatProposal(proposal) {
proposal.choices = jsonParse(proposal.choices, []);
proposal.strategies = jsonParse(proposal.strategies, []);
proposal.validation = jsonParse(proposal.validation, { name: 'any', params: {} }) || {
name: 'any',
params: {}
};
proposal.plugins = jsonParse(proposal.plugins, {});
proposal.scores = jsonParse(proposal.scores, []);
proposal.scores_by_strategy = jsonParse(proposal.scores_by_strategy, []);
export function formatProposal(proposal: SqlRow) {
const networkStr = network === 'testnet' ? 'demo.' : '';
let proposalState = 'pending';
const ts = parseInt((Date.now() / 1e3).toFixed());
if (ts > proposal.start) proposalState = 'active';
if (ts > proposal.end) proposalState = 'closed';
proposal.state = proposalState;
proposal.space = formatSpace(proposal.space, proposal.settings);
const networkStr = network === 'testnet' ? 'demo.' : '';
proposal.link = `https://${networkStr}snapshot.org/#/${proposal.space.id}/proposal/${proposal.id}`;
proposal.strategies = proposal.strategies.map(strategy => ({
...strategy,
// By default return proposal network if strategy network is not defined
network: strategy.network || proposal.network
}));
proposal.privacy = proposal.privacy || '';
return proposal;
if (ts > (proposal.start as number)) proposalState = 'active';
if (ts > (proposal.end as number)) proposalState = 'closed';
const space = formatSpace(proposal.space as string, proposal.settings as string);

return {
...proposal,
choices: jsonParse(proposal.choices as string, []),
validation: jsonParse(proposal.validation as string, { name: 'any', params: {} }) || {
name: 'any',
params: {}
},
plugins: jsonParse(proposal.plugins as string, {}),
scores: jsonParse(proposal.scores as string, []),
scores_by_strategy: jsonParse(proposal.scores_by_strategy as string, []),
state: proposalState,
space,
link: `https://${networkStr}snapshot.org/#/${space.id}/proposal/${proposal.id}`,
strategies: (jsonParse(proposal.strategies as string, []) as Strategy[]).map(
(strategy: Strategy) => ({
...strategy,
// By default return proposal network if strategy network is not defined
network: strategy.network || proposal.network
})
),
privacy: proposal.privacy || ''
} as Proposal;
}

export function formatVote(vote) {
vote.choice = jsonParse(vote.choice);
vote.metadata = jsonParse(vote.metadata, {});
vote.vp_by_strategy = jsonParse(vote.vp_by_strategy, []);
vote.space = formatSpace(vote.space, vote.settings);
return vote;
export function formatVote(vote: SqlRow) {
return {
...vote,
choice: jsonParse(vote.choice as string, {}),
metadata: jsonParse(vote.metadata as string, {}),
vp_by_strategy: jsonParse(vote.vp_by_strategy as string, []),
space: formatSpace(vote.space as string, vote.settings as string)
} as Vote;
}

export function formatFollow(follow) {
follow.space = formatSpace(follow.space, follow.settings);
return follow;
export function formatFollow(follow: SqlRow) {
return {
...follow,
space: formatSpace(follow.space as string, follow.settings as string)
} as Follow;
}

export function formatSubscription(subscription) {
subscription.space = formatSpace(subscription.space, subscription.settings);
return subscription;
export function formatSubscription(subscription: SqlRow) {
return {
...subscription,
space: formatSpace(subscription.space as string, subscription.settings as string)
} as Subscription;
}
1 change: 1 addition & 0 deletions src/graphql/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import path from 'path';
import fs from 'fs';
import { graphqlHTTP } from 'express-graphql';
import { makeExecutableSchema } from 'graphql-tools';
// @ts-ignore
import queryCountLimit from 'graphql-query-count-limit';
import depthLimit from 'graphql-depth-limit';
import Query from './operations';
Expand Down
5 changes: 3 additions & 2 deletions src/graphql/operations/aliases.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import db from '../../helpers/mysql';
import { buildWhereQuery, checkLimits } from '../helpers';
import log from '../../helpers/log';
import type { QueryArgs } from '../../types';

export default async function (parent, args) {
export default async function (parent: any, args: QueryArgs) {
const { first = 20, skip = 0, where = {} } = args;

checkLimits(args, 'aliases');
Expand All @@ -16,7 +17,7 @@ export default async function (parent, args) {
};
const whereQuery = buildWhereQuery(fields, 'a', where);
const queryStr = whereQuery.query;
const params: any[] = whereQuery.params;
const params = whereQuery.params;

let orderBy = args.orderBy || 'created';
let orderDirection = args.orderDirection || 'desc';
Expand Down
8 changes: 4 additions & 4 deletions src/graphql/operations/follows.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import db from '../../helpers/mysql';
import { buildWhereQuery, checkLimits, formatFollow } from '../helpers';
import log from '../../helpers/log';
import type { QueryArgs } from '../../types';

export default async function (parent, args) {
export default async function (parent: any, args: QueryArgs) {
const { first = 20, skip = 0, where = {} } = args;

checkLimits(args, 'follows');
Expand All @@ -16,7 +17,7 @@ export default async function (parent, args) {
};
const whereQuery = buildWhereQuery(fields, 'f', where);
const queryStr = whereQuery.query;
const params: any[] = whereQuery.params;
const params = whereQuery.params;

let orderBy = args.orderBy || 'created';
let orderDirection = args.orderDirection || 'desc';
Expand All @@ -33,9 +34,8 @@ export default async function (parent, args) {
`;
params.push(skip, first);

let follows: any[] = [];
try {
follows = await db.queryAsync(query, params);
const follows = await db.queryAsync(query, params);
return follows.map(follow => formatFollow(follow));
} catch (e) {
log.error(`[graphql] follows, ${JSON.stringify(e)}`);
Expand Down
5 changes: 3 additions & 2 deletions src/graphql/operations/messages.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import db from '../../helpers/mysql';
import { buildWhereQuery, checkLimits } from '../helpers';
import log from '../../helpers/log';
import type { QueryArgs } from '../../types';

export default async function (parent, args) {
export default async function (parent: any, args: QueryArgs) {
const { first = 20, skip = 0, where = {} } = args;

checkLimits(args, 'messages');
Expand All @@ -16,7 +17,7 @@ export default async function (parent, args) {
};
const whereQuery = buildWhereQuery(fields, 'm', where);
const queryStr = whereQuery.query;
const params: any[] = whereQuery.params;
const params = whereQuery.params;

let orderBy = args.orderBy || 'timestamp';
let orderDirection = args.orderDirection || 'desc';
Expand Down
9 changes: 6 additions & 3 deletions src/graphql/operations/networks.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { spaces } from '../../helpers/spaces';
import { Countable } from '../../types';

export default function () {
const networks = {};
Object.values(spaces).forEach((space: any) => {
networks[space.network] = networks[space.network] ? networks[space.network] + 1 : 1;
const networks: Countable = {};
Object.values(spaces).forEach(space => {
if (space.network) {
networks[space.network] = networks[space.network] ? networks[space.network] + 1 : 1;
}
});
return Object.entries(networks).map(network => ({
id: network[0],
Expand Down
5 changes: 3 additions & 2 deletions src/graphql/operations/plugins.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { spaces } from '../../helpers/spaces';
import { Countable } from '../../types';

export default function () {
const plugins = {};
Object.values(spaces).forEach((space: any) => {
const plugins: Countable = {};
Object.values(spaces).forEach(space => {
Object.keys(space.plugins || {}).forEach(plugin => {
plugins[plugin] = plugins[plugin] ? plugins[plugin] + 1 : 1;
});
Expand Down
Loading