Skip to content
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
18,742 changes: 7,598 additions & 11,144 deletions packages/api/package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@
"eslint-plugin-json": "^4.0.1",
"globals": "^15.14.0",
"i18next-parser": "^5.3.0",
"jest": "^26.4.2",
"jest": "^29.7.0",
"prettier": "^3.5.0",
"start-server-and-test": "^1.11.7",
"typescript": "^5.6.2",
Expand Down
157 changes: 70 additions & 87 deletions packages/api/src/controllers/taskController.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,8 @@ import { customError } from '../util/customErrors.js';
const adminRoles = [1, 2, 5];

async function getTaskAssigneeAndFinalWage(farm_id, user_id, task_id) {
const {
assignee_user_id,
assignee_role_id,
wage_at_moment,
override_hourly_wage,
} = await TaskModel.getTaskAssignee(task_id, farm_id);
const { assignee_user_id, assignee_role_id, wage_at_moment, override_hourly_wage } =
await TaskModel.getTaskAssignee(task_id, farm_id);
const { role_id } = await UserFarmModel.getUserRoleId(user_id, farm_id);
if (!canCompleteTask(assignee_user_id, assignee_role_id, user_id, role_id)) {
throw new Error("Not authorized to complete other people's task");
Expand Down Expand Up @@ -395,11 +391,7 @@ const taskController = {

const checkTaskStatus = await TaskModel.getTaskStatus(task_id);

const {
assignee_user_id,
wage_at_moment,
override_hourly_wage,
} = await TaskModel.query()
const { assignee_user_id, wage_at_moment, override_hourly_wage } = await TaskModel.query()
.select('assignee_user_id', 'wage_at_moment', 'override_hourly_wage')
.where({ task_id })
.first();
Expand Down Expand Up @@ -566,9 +558,8 @@ const taskController = {
case 'irrigation_task':
return await (async () => {
if (data.irrigation_task) {
const {
irrigation_type_id,
} = await IrrigationTypesModel.checkAndAddCustomIrrigationType(data, farm_id);
const { irrigation_type_id } =
await IrrigationTypesModel.checkAndAddCustomIrrigationType(data, farm_id);
if (data.irrigation_task.default_irrigation_task_type_measurement) {
await IrrigationTypesModel.updateIrrigationType({
irrigation_type_id,
Expand Down Expand Up @@ -917,9 +908,7 @@ const taskController = {
const filteredTasks = graphTasks.map(removeNullTypes);

/* Clean before returning to frontend */
const {
task_type_id: soilAmendmentTypeId,
} = await TaskTypeModel.query()
const { task_type_id: soilAmendmentTypeId } = await TaskTypeModel.query()
.whereNotDeleted()
.where({ farm_id: null, task_translation_key: 'SOIL_AMENDMENT_TASK' })
.first();
Expand Down Expand Up @@ -971,9 +960,8 @@ const taskController = {
async getIrrigationTaskTypes(req, res) {
const { farm_id } = req.params;
try {
const irrigationTaskTypes = await IrrigationTypesModel.getAllIrrigationTaskTypesByFarmId(
farm_id,
);
const irrigationTaskTypes =
await IrrigationTypesModel.getAllIrrigationTaskTypesByFarmId(farm_id);
res.status(200).json(irrigationTaskTypes);
} catch (error) {
return res.status(400).send(error);
Expand Down Expand Up @@ -1030,73 +1018,68 @@ async function getTasksForFarm(farm_id) {
const customTaskTypesForFarm = await TaskTypeModel.query()
.select('task_type_id')
.where({ farm_id });
const [
managementTasks,
locationTasks,
plantTasks,
transplantTasks,
customTasks,
] = await Promise.all([
TaskModel.query()
.select('task.task_id')
.whereNotDeleted()
.distinct('task.task_id')
.join('management_tasks', 'management_tasks.task_id', 'task.task_id')
.join(
'planting_management_plan',
'management_tasks.planting_management_plan_id',
'planting_management_plan.planting_management_plan_id',
)
.join(
'management_plan',
'planting_management_plan.management_plan_id',
'management_plan.management_plan_id',
)
.join('crop_variety', 'crop_variety.crop_variety_id', 'management_plan.crop_variety_id')
.where('crop_variety.farm_id', farm_id),
TaskModel.query()
.select('task.task_id')
.whereNotDeleted()
.distinct('task.task_id')
.join('location_tasks', 'location_tasks.task_id', 'task.task_id')
.join('location', 'location.location_id', 'location_tasks.location_id')
.where('location.farm_id', farm_id),
PlantTaskModel.query()
.select('plant_task.task_id')
.join(
'planting_management_plan',
'planting_management_plan.planting_management_plan_id',
'plant_task.planting_management_plan_id',
)
.join(
'management_plan',
'management_plan.management_plan_id',
'planting_management_plan. management_plan_id',
)
.join('crop_variety', 'crop_variety.crop_variety_id', 'management_plan.crop_variety_id')
.where('crop_variety.farm_id', farm_id),
TransplantTaskModel.query()
.select('transplant_task.task_id')
.join(
'planting_management_plan',
'planting_management_plan.planting_management_plan_id',
'transplant_task.planting_management_plan_id',
)
.join(
'management_plan',
'management_plan.management_plan_id',
'planting_management_plan. management_plan_id',
)
.join('crop_variety', 'crop_variety.crop_variety_id', 'management_plan.crop_variety_id')
.where('crop_variety.farm_id', farm_id),
TaskModel.query()
.select('task.task_id')
.whereNotDeleted()
.whereIn(
'task_type_id',
customTaskTypesForFarm.map(({ task_type_id }) => task_type_id),
),
]);
const [managementTasks, locationTasks, plantTasks, transplantTasks, customTasks] =
await Promise.all([
TaskModel.query()
.select('task.task_id')
.whereNotDeleted()
.distinct('task.task_id')
.join('management_tasks', 'management_tasks.task_id', 'task.task_id')
.join(
'planting_management_plan',
'management_tasks.planting_management_plan_id',
'planting_management_plan.planting_management_plan_id',
)
.join(
'management_plan',
'planting_management_plan.management_plan_id',
'management_plan.management_plan_id',
)
.join('crop_variety', 'crop_variety.crop_variety_id', 'management_plan.crop_variety_id')
.where('crop_variety.farm_id', farm_id),
TaskModel.query()
.select('task.task_id')
.whereNotDeleted()
.distinct('task.task_id')
.join('location_tasks', 'location_tasks.task_id', 'task.task_id')
.join('location', 'location.location_id', 'location_tasks.location_id')
.where('location.farm_id', farm_id),
PlantTaskModel.query()
.select('plant_task.task_id')
.join(
'planting_management_plan',
'planting_management_plan.planting_management_plan_id',
'plant_task.planting_management_plan_id',
)
.join(
'management_plan',
'management_plan.management_plan_id',
'planting_management_plan. management_plan_id',
)
.join('crop_variety', 'crop_variety.crop_variety_id', 'management_plan.crop_variety_id')
.where('crop_variety.farm_id', farm_id),
TransplantTaskModel.query()
.select('transplant_task.task_id')
.join(
'planting_management_plan',
'planting_management_plan.planting_management_plan_id',
'transplant_task.planting_management_plan_id',
)
.join(
'management_plan',
'management_plan.management_plan_id',
'planting_management_plan. management_plan_id',
)
.join('crop_variety', 'crop_variety.crop_variety_id', 'management_plan.crop_variety_id')
.where('crop_variety.farm_id', farm_id),
TaskModel.query()
.select('task.task_id')
.whereNotDeleted()
.whereIn(
'task_type_id',
customTaskTypesForFarm.map(({ task_type_id }) => task_type_id),
),
]);
return [...managementTasks, ...locationTasks, ...plantTasks, ...transplantTasks, ...customTasks];
}

Expand Down
97 changes: 47 additions & 50 deletions packages/api/src/middleware/acl/hasFarmAccess.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,61 +33,58 @@ const entitiesGetters = {
};
import userFarmModel from '../../models/userFarmModel.js';

export default ({ params = null, body = null, mixed = null, tableName = null }) => async (
req,
res,
next,
) => {
let entity_key;
let id;
if (params) {
entity_key = params;
id = req.params[entity_key];
} else if (mixed) {
entity_key = mixed;
id = req;
} else if (tableName) {
entity_key = tableName;
id = req.params['id'];
} else {
entity_key = body;
if (Array.isArray(req.body)) {
//TODO: remove and fix hasFarmAccess on post harvest_tasks middleware. LF-1969
id = req.body[0][entity_key];
export default ({ params = null, body = null, mixed = null, tableName = null }) =>
async (req, res, next) => {
let entity_key;
let id;
if (params) {
entity_key = params;
id = req.params[entity_key];
} else if (mixed) {
entity_key = mixed;
id = req;
} else if (tableName) {
entity_key = tableName;
id = req.params['id'];
} else {
id = req.body[entity_key];
entity_key = body;
if (Array.isArray(req.body)) {
//TODO: remove and fix hasFarmAccess on post harvest_tasks middleware. LF-1969
id = req.body[0][entity_key];
} else {
id = req.body[entity_key];
}
}
}
if (!entity_key) {
return next();
}

try {
const { farm_id } = req.headers;

if (farm_id === undefined) {
return noFarmIdErrorResponse(res);
if (!entity_key) {
return next();
}

// A generic entity getter for tables that use plain 'id' for index name
const farmIdObjectFromEntity = tableName
? await knex(tableName).where({ id }).first()
: await entitiesGetters[entity_key](id, next);
// Is getting a seeded table and accessing community data. Go through.
if (
seededEntities.includes(entity_key) &&
req.method === 'GET' &&
farmIdObjectFromEntity.farm_id === null
) {
return next();
} else if (farmIdObjectFromEntity?.next) {
return next();
try {
const { farm_id } = req.headers;

if (farm_id === undefined) {
return noFarmIdErrorResponse(res);
}

// A generic entity getter for tables that use plain 'id' for index name
const farmIdObjectFromEntity = tableName
? await knex(tableName).where({ id }).first()
: await entitiesGetters[entity_key](id, next);
// Is getting a seeded table and accessing community data. Go through.
if (
seededEntities.includes(entity_key) &&
req.method === 'GET' &&
farmIdObjectFromEntity.farm_id === null
) {
return next();
} else if (farmIdObjectFromEntity?.next) {
return next();
}
return sameFarm(farmIdObjectFromEntity, farm_id) ? next() : notAuthorizedResponse(res);
} catch (_e) {
notAuthorizedResponse(res);
}
return sameFarm(farmIdObjectFromEntity, farm_id) ? next() : notAuthorizedResponse(res);
} catch (_e) {
notAuthorizedResponse(res);
}
};
};

async function fromTaskId(task_id) {
const taskType = await knex('task')
Expand Down
9 changes: 2 additions & 7 deletions packages/api/src/middleware/validation/checkTask.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,8 @@ export function checkAbandonTask() {
try {
const { task_id } = req.params;
const { user_id } = req.headers;
const {
abandonment_reason,
other_abandonment_reason,
happiness,
duration,
abandon_date,
} = req.body;
const { abandonment_reason, other_abandonment_reason, happiness, duration, abandon_date } =
req.body;

// Notifications will not send without, and checks below will be faulty
if (!user_id) {
Expand Down
11 changes: 7 additions & 4 deletions packages/api/src/models/taskModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -360,16 +360,19 @@ class TaskModel extends BaseModel {
*/
static async getUnassignedTasksDueThisWeekFromIds(taskIds, isDayLaterThanUTC = false) {
const dayLaterInterval = isDayLaterThanUTC ? '"1 day"' : '"0 days"';
return await TaskModel.query().select('*').whereIn('task_id', taskIds).whereRaw(
`
return await TaskModel.query()
.select('*')
.whereIn('task_id', taskIds)
.whereRaw(
`
task.assignee_user_id IS NULL
AND task.complete_date IS NULL
AND task.abandon_date IS NULL
AND task.due_date <= (now() + ('1 week')::interval + (?)::interval)::date
AND task.due_date >= (now() + (?)::interval)::date
`,
[dayLaterInterval, dayLaterInterval],
);
[dayLaterInterval, dayLaterInterval],
);
}

/**
Expand Down
Loading
Loading