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

Add Contract endpoint and queries #307

Merged
merged 6 commits into from
Nov 13, 2023
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
2 changes: 2 additions & 0 deletions server/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import responseTime from 'response-time';
import organizationsRouterV2 from 'routers/organizationsRouterV2';
import boundsRouter from './routers/boundsRouter';
import capturesRouter from './routers/capturesRouter';
import contractsRouter from './routers/contractsRouter';
import countriesRouter from './routers/countriesRouter';
import gisRouter from './routers/gisRouter';
import growerAccountsRouter from './routers/growerAccountsRouter';
Expand Down Expand Up @@ -80,6 +81,7 @@ app.use('/v2/growers', growerAccountsRouter);
app.use('/v2/trees', treesRouterV2);
app.use('/bounds', boundsRouter);
app.use('/gis', gisRouter);
app.use('/contract', contractsRouter);
// Global error handler
app.use(errorHandler);

Expand Down
193 changes: 193 additions & 0 deletions server/infra/database/ContractRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import Contract from 'interfaces/Contract';
import ContractFilter from 'interfaces/ContractFilter';
import FilterOptions from 'interfaces/FilterOptions';
import HttpError from 'utils/HttpError';
import BaseRepository from './BaseRepository';
import Session from './Session';

export default class ContractRepository extends BaseRepository<Contract> {
constructor(session: Session) {
super('contract', session);
this.tableName = 'contracts.contract';
}

filterWhereBuilder(object, builder) {
const result = builder;
const {
whereNulls = [],
whereNotNulls = [],
whereIns = [],
...parameters
} = object;

if (parameters.tokenized === 'true') {
whereNotNulls.push('wallet.token.id');
} else if (parameters.tokenized === 'false') {
whereNulls.push('wallet.token.id');
}
delete parameters.tokenized;

result.whereNot(`${this.tableName}.status`, 'deleted');

whereNotNulls.forEach((whereNot) => {
// to map table names to fields for query
switch (true) {
case whereNot === 'tag_id':
result.whereNotNull('treetracker.capture_tag.tag_id');
break;
default:
result.whereNotNull(whereNot);
}
});

whereNulls.forEach((whereNull) => {
// to map table names to fields for query
switch (true) {
case whereNull === 'tag_id':
result.whereNull('treetracker.capture_tag.tag_id');
break;
default:
result.whereNull(whereNull);
}
});

whereIns.forEach((whereIn) => {
result.whereIn(whereIn.field, whereIn.values);
});

const filterObject = { ...parameters };

if (filterObject.startDate) {
result.where(
`${this.tableName}.captured_at`,
'>=',
filterObject.startDate,
);
delete filterObject.startDate;
}
if (filterObject.endDate) {
result.where(`${this.tableName}.captured_at`, '<=', filterObject.endDate);
delete filterObject.endDate;
}

if (filterObject.id) {
result.where(`${this.tableName}.id`, '=', filterObject.id);
delete filterObject.id;
}

if (filterObject.organization_id) {
result.where(`${this.tableName}.growing_organization_id`, 'in', [
...filterObject.organization_id,
]);
delete filterObject.organization_id;
}

result.where(filterObject);
}

async getByFilter(filterCriteria: ContractFilter, options: FilterOptions) {
const knex = this.session.getDB();
const { sort, ...filter } = filterCriteria;

let promise = knex
.select(
knex.raw(
`
${this.tableName}.id,
${this.tableName}.status,
${this.tableName}.notes,
${this.tableName}.created_at,
${this.tableName}.updated_at,
${this.tableName}.signed_at,
${this.tableName}.closed_at,
${this.tableName}.listed,
row_to_json(agreement.*) AS agreement,
row_to_json(grower_account.*) AS worker,
row_to_json(stakeholder.*) AS stakeholder
FROM ${this.tableName}
LEFT JOIN contracts.agreement AS agreement
ON agreement.id = ${this.tableName}.agreement_id
LEFT JOIN stakeholder.stakeholder AS stakeholder
ON stakeholder.id = agreement.growing_organization_id
LEFT JOIN treetracker.grower_account AS grower_account
ON grower_account.id = ${this.tableName}.worker_id
`,
),
)
.where((builder) => this.filterWhereBuilder(filter, builder));

promise = promise.orderBy(
`${this.tableName}.${sort?.order_by}` || `${this.tableName}.id`,
sort?.order || 'desc',
);

const { limit, offset } = options;
if (limit) {
promise = promise.limit(limit);
}
if (offset) {
promise = promise.offset(offset);
}

const captures = await promise;

return captures;
}

async getCount(filterCriteria: ContractFilter) {
const knex = this.session.getDB();
const { ...filter } = filterCriteria;

const result = await knex
.select(
knex.raw(
`COUNT(*) AS count
FROM ${this.tableName}
LEFT JOIN contracts.agreement AS agreement
ON agreement.id = ${this.tableName}.agreement_id
LEFT JOIN stakeholder.stakeholder AS stakeholder
ON stakeholder.id = agreement.growing_organization_id
LEFT JOIN treetracker.grower_account AS grower_account
ON grower_account.id = ${this.tableName}.worker_id
`,
),
)
.where((builder) => this.filterWhereBuilder(filter, builder));

return result[0].count;
}

async getById(id: string) {
const object = await this.session
.getDB()
.select(
this.session.getDB().raw(`
${this.tableName}.id,
${this.tableName}.status,
${this.tableName}.notes,
${this.tableName}.created_at,
${this.tableName}.updated_at,
${this.tableName}.signed_at,
${this.tableName}.closed_at,
${this.tableName}.listed,
row_to_json(agreement.*) AS agreement,
row_to_json(grower_account.*) AS worker,
row_to_json(stakeholder.*) AS stakeholder
FROM ${this.tableName}
LEFT JOIN contracts.agreement AS agreement
ON agreement.id = ${this.tableName}.agreement_id
LEFT JOIN stakeholder.stakeholder AS stakeholder
ON stakeholder.id = agreement.growing_organization_id
LEFT JOIN treetracker.grower_account AS grower_account
ON grower_account.id = ${this.tableName}.worker_id
`),
)
.where(`${this.tableName}.id`, id)
.first();

if (!object) {
throw new HttpError(404, `Can not find ${this.tableName} by id:${id}`);
}
return object;
}
}
6 changes: 5 additions & 1 deletion server/infra/database/GrowerAccountRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,11 @@ export default class GrowerAccountRepository extends BaseRepository<GrowerAccoun
}

if (filterObject.organization_id) {
result.where(`${this.tableName}.organization_id`, '=',`${filterObject.organization_id}`);
result.where(
`${this.tableName}.organization_id`,
'=',
`${filterObject.organization_id}`,
);
delete filterObject.organization_id;
}

Expand Down
6 changes: 4 additions & 2 deletions server/infra/database/TreeRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,14 @@ export default class TreeRepository extends BaseRepository<Tree> {
.first();

if (!object) {
throw new HttpError(404, `Can not find ${this.tableName} by uuid:${uuid}`);
throw new HttpError(
404,
`Can not find ${this.tableName} by uuid:${uuid}`,
);
}
return object;
}


async getByOrganization(
organization_id: number,
options: FilterOptions,
Expand Down
14 changes: 14 additions & 0 deletions server/interfaces/Contract.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import DbModel from './DbModel';

export default interface Contract extends DbModel {
id: number;
agreement_id: number;
worker_id: number;
status: string;
notes: string;
created_at: string;
updated_at: string;
signed_at: string;
closed_at: string;
listed: true;
}
16 changes: 16 additions & 0 deletions server/interfaces/ContractFilter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import DbModel from './DbModel';

interface ContractFilter extends DbModel {
id?: string | undefined;
agreement_id?: string | undefined;
worker_id?: string | undefined;
status?: string | undefined;
notes?: string | undefined;
created_at?: string | undefined;
updated_at?: string | undefined;
signed_at?: string | undefined;
closed_at?: string | undefined;
listed?: true | false;
}

export default ContractFilter;
29 changes: 29 additions & 0 deletions server/models/Contract.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import ContractRepository from 'infra/database/ContractRepository';
import { delegateRepository } from 'infra/database/delegateRepository';
import Contract from 'interfaces/Contract';
import ContractFilter from 'interfaces/ContractFilter';
import FilterOptions from 'interfaces/FilterOptions';

function getByFilter(
contractRepository: ContractRepository,
): (filter: ContractFilter, options: FilterOptions) => Promise<Contract[]> {
return async function (filter: ContractFilter, options: FilterOptions) {
const contracts = await contractRepository.getByFilter(filter, options);
return contracts;
};
}

function getCount(
contractRepository: ContractRepository,
): (filter: ContractFilter) => Promise<Contract[]> {
return async function (filter: ContractFilter) {
const count = await contractRepository.getCount(filter);
return count;
};
}

export default {
getByFilter,
getCount,
getById: delegateRepository<ContractRepository, Contract>('getById'),
};
2 changes: 1 addition & 1 deletion server/models/Organization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Organization from 'interfaces/Organization';
import { delegateRepository } from '../infra/database/delegateRepository';
import OrganizationRepository from '../infra/database/OrganizationRepository';

type Filter = Partial<{ planter_id: number; organization_id: number}>;
type Filter = Partial<{ planter_id: number; organization_id: number }>;

function getByFilter(
organizationRepository: OrganizationRepository,
Expand Down
16 changes: 11 additions & 5 deletions server/models/OrganizationV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import FilterOptions from 'interfaces/FilterOptions';
import Organization from 'interfaces/Organization';
import { delegateRepository } from '../infra/database/delegateRepository';

type Filter = Partial<{ planter_id: number; organization_id: number, grower_id:string }>;
type Filter = Partial<{
planter_id: number;
organization_id: number;
grower_id: string;
}>;

function getByFilter(
organizationRepository: OrganizationRepositoryV2,
Expand All @@ -18,7 +22,7 @@ function getByFilter(
);
return trees;
}
if (filter.grower_id){
if (filter.grower_id) {
log.warn('using grower filter...');
const trees = await organizationRepository.getByGrower(
filter.grower_id,
Expand All @@ -41,14 +45,16 @@ function getOrganizationLinks(organization) {
}

export default {
getById: delegateRepository<OrganizationRepositoryV2, Organization>('getById'),
getById: delegateRepository<OrganizationRepositoryV2, Organization>(
'getById',
),
getByMapName: delegateRepository<OrganizationRepositoryV2, Organization>(
'getByMapName',
),
getByFilter,
getOrganizationLinks,
getFeaturedOrganizations: delegateRepository<
OrganizationRepositoryV2,
OrganizationRepositoryV2,
Organization
>('getFeaturedOrganizations'),
};
};
6 changes: 4 additions & 2 deletions server/models/TreeV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ function getFeaturedTreeDepricated(treeRepository: TreeRepositoryV2) {
export default {
getById: delegateRepository<TreeRepositoryV2, Tree>('getById'),
getByFilter,
getFeaturedTree: delegateRepository<TreeRepositoryV2, Tree>('getFeaturedTree'),
getFeaturedTree: delegateRepository<TreeRepositoryV2, Tree>(
'getFeaturedTree',
),
countByFilter,
};
};
Loading
Loading