From f393657fe1d2401e8f4cea013a41f6f31cc47f61 Mon Sep 17 00:00:00 2001
From: Vikas Jagtap
Date: Sun, 15 May 2022 21:40:41 +1000
Subject: [PATCH 1/2] OpenHarvest-13 prepare backend for production with
integrations configurable per organisation
---
backend/package.json | 225 +++++++++++++++++
backend/src/auth/IBMiDStrategy.ts | 13 +-
backend/src/auth/helpers.ts | 5 +-
backend/src/db/entities/coopManager.ts | 33 ---
backend/src/db/entities/crop.ts | 22 +-
backend/src/db/entities/farm.ts | 34 +++
backend/src/db/entities/farmer.ts | 32 +--
backend/src/db/entities/land.ts | 36 ---
backend/src/db/entities/messageLog.ts | 5 +-
backend/src/db/entities/organisation.ts | 37 ++-
backend/src/db/mongodb.ts | 1 -
.../src/integrations/EIS/EIS-api.service.ts | 107 --------
backend/src/integrations/EIS/EIS.types.ts | 33 +--
.../src/integrations/EIS/EISFarmService.ts | 116 +++++++++
backend/src/integrations/EIS/EISService.ts | 54 ++++
backend/src/integrations/eventBus.service.ts | 6 +-
.../src/integrations/messagingInterface.ts | 18 +-
.../integrations/smsSync/smsSync.service.ts | 22 +-
.../src/integrations/twilio/twilio.service.ts | 28 ++-
.../weather-company-api.service.ts | 64 -----
.../weatherCompany/WeatherCompanyService.ts | 53 ++++
.../weather-company-api.types.ts | 0
backend/src/main.ts | 8 +-
backend/src/routes/coopManager-route.ts | 90 -------
backend/src/routes/crop-route.ts | 6 +-
backend/src/routes/dashboard-route.ts | 3 +-
backend/src/routes/farmer-route.ts | 89 ++-----
backend/src/routes/lot-route.ts | 5 +-
backend/src/routes/messaging-route.ts | 4 +-
backend/src/routes/organisation-route.ts | 39 ++-
backend/src/routes/recommendations-route.ts | 3 +-
backend/src/routes/sms-route.ts | 7 +-
backend/src/routes/user-route.ts | 52 ++++
backend/src/routes/weather-route.ts | 34 ++-
.../{crop.service.ts => CropService.ts} | 13 +-
backend/src/services/FarmService.ts | 64 +++++
backend/src/services/FarmerService.ts | 32 +++
backend/src/services/OrganisationService.ts | 63 +++++
backend/src/services/UserService.ts | 54 ++++
backend/src/services/coopManager.service.ts | 55 -----
backend/src/services/land-areas.service.ts | 52 ++--
backend/src/services/organisation.service.ts | 32 ---
.../src/services/recommendations.service.ts | 77 +++---
backend/src/sockets/socket.io.ts | 8 +-
backend/src/types.d.ts | 4 +-
common-types/data-model/Farm.ts | 18 ++
common-types/data-model/Field.ts | 15 ++
common-types/data-model/FieldCrop.ts | 7 +
.../data-model/{coopManager.ts => User.ts} | 10 +-
common-types/data-model/crop.ts | 5 +-
common-types/data-model/farmer.ts | 28 +--
common-types/data-model/land.ts | 36 ---
common-types/data-model/organisation.ts | 29 +--
common-types/dto/OrganisationDto.ts | 8 +
common-types/dto/UserDto.ts | 14 ++
.../globals.ts | 29 +--
common-types/integrations/EISConfig.ts | 6 +
.../integrations/WeatherCompanyConfig.ts | 7 +
common-types/package.json | 232 ++++++++++++++++++
common-types/types.d.ts | 85 +++++++
docker-compose.yml | 12 +
react-app/src/App.tsx | 173 ++++++-------
react-app/src/components/Nav/Nav.tsx | 32 +--
react-app/src/services/auth.tsx | 152 +++++-------
react-app/src/setupProxy.js | 20 +-
65 files changed, 1627 insertions(+), 1029 deletions(-)
delete mode 100644 backend/src/db/entities/coopManager.ts
create mode 100644 backend/src/db/entities/farm.ts
delete mode 100644 backend/src/db/entities/land.ts
delete mode 100644 backend/src/integrations/EIS/EIS-api.service.ts
create mode 100644 backend/src/integrations/EIS/EISFarmService.ts
create mode 100644 backend/src/integrations/EIS/EISService.ts
delete mode 100644 backend/src/integrations/weather-company-api.service.ts
create mode 100644 backend/src/integrations/weatherCompany/WeatherCompanyService.ts
rename common-types/Fields.ts => backend/src/integrations/weatherCompany/weather-company-api.types.ts (100%)
delete mode 100644 backend/src/routes/coopManager-route.ts
create mode 100644 backend/src/routes/user-route.ts
rename backend/src/services/{crop.service.ts => CropService.ts} (70%)
create mode 100644 backend/src/services/FarmService.ts
create mode 100644 backend/src/services/FarmerService.ts
create mode 100644 backend/src/services/OrganisationService.ts
create mode 100644 backend/src/services/UserService.ts
delete mode 100644 backend/src/services/coopManager.service.ts
delete mode 100644 backend/src/services/organisation.service.ts
create mode 100644 common-types/data-model/Farm.ts
create mode 100644 common-types/data-model/Field.ts
create mode 100644 common-types/data-model/FieldCrop.ts
rename common-types/data-model/{coopManager.ts => User.ts} (59%)
delete mode 100644 common-types/data-model/land.ts
create mode 100644 common-types/dto/OrganisationDto.ts
create mode 100644 common-types/dto/UserDto.ts
rename backend/src/integrations/weather-company-api.types.ts => common-types/globals.ts (88%)
create mode 100644 common-types/integrations/EISConfig.ts
create mode 100644 common-types/integrations/WeatherCompanyConfig.ts
create mode 100644 common-types/package.json
create mode 100644 common-types/types.d.ts
diff --git a/backend/package.json b/backend/package.json
index 2f4abdbda..fe81a1367 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -16,6 +16,7 @@
"@types/geojson": "^7946.0.8",
"@types/uuid": "^8.3.4",
"axios": "^0.26.0",
+ "common-types": "file:../common-types",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"dotenv": "^16.0.0",
@@ -36,5 +37,229 @@
"nodemon": "^2.0.15",
"ts-node": "^10.5.0",
"typescript": "^4.5.5"
+ },
+ "eslintConfig": {
+ "overrides": [
+ {
+ "files": [
+ "**/*.ts?(x)"
+ ],
+ "extends": [
+ "plugin:react/recommended"
+ ],
+ "plugins": [
+ "eslint-plugin-prefer-arrow",
+ "eslint-plugin-react",
+ "@typescript-eslint"
+ ],
+ "rules": {
+ "@typescript-eslint/adjacent-overload-signatures": "warn",
+ "@typescript-eslint/array-type": [
+ "warn",
+ {
+ "default": "array"
+ }
+ ],
+ "@typescript-eslint/ban-types": [
+ "warn",
+ {
+ "types": {
+ "Object": {
+ "message": "Avoid using the `Object` type. Did you mean `object`?"
+ },
+ "Function": {
+ "message": "Avoid using the `Function` type. Prefer a specific function type, like `() => void`."
+ },
+ "Boolean": {
+ "message": "Avoid using the `Boolean` type. Did you mean `boolean`?"
+ },
+ "Number": {
+ "message": "Avoid using the `Number` type. Did you mean `number`?"
+ },
+ "String": {
+ "message": "Avoid using the `String` type. Did you mean `string`?"
+ },
+ "Symbol": {
+ "message": "Avoid using the `Symbol` type. Did you mean `symbol`?"
+ }
+ }
+ }
+ ],
+ "@typescript-eslint/consistent-type-assertions": "warn",
+ "@typescript-eslint/dot-notation": "off",
+ "@typescript-eslint/explicit-function-return-type": "warn",
+ "@typescript-eslint/explicit-member-accessibility": [
+ "off",
+ {
+ "accessibility": "explicit"
+ }
+ ],
+ "@typescript-eslint/indent": "warn",
+ "@typescript-eslint/member-delimiter-style": [
+ "warn",
+ {
+ "multiline": {
+ "delimiter": "semi",
+ "requireLast": true
+ },
+ "singleline": {
+ "delimiter": "semi",
+ "requireLast": false
+ }
+ }
+ ],
+ "@typescript-eslint/member-ordering": "warn",
+ "@typescript-eslint/naming-convention": "off",
+ "@typescript-eslint/no-empty-function": "off",
+ "@typescript-eslint/no-empty-interface": "warn",
+ "@typescript-eslint/no-explicit-any": "off",
+ "@typescript-eslint/no-inferrable-types": [
+ "warn",
+ {
+ "ignoreParameters": true
+ }
+ ],
+ "@typescript-eslint/no-misused-new": "warn",
+ "@typescript-eslint/no-namespace": "warn",
+ "@typescript-eslint/no-parameter-properties": "off",
+ "@typescript-eslint/no-shadow": [
+ "warn",
+ {
+ "hoist": "all"
+ }
+ ],
+ "@typescript-eslint/no-unused-expressions": "warn",
+ "@typescript-eslint/no-use-before-define": "off",
+ "@typescript-eslint/no-var-requires": "warn",
+ "@typescript-eslint/prefer-for-of": "warn",
+ "@typescript-eslint/prefer-function-type": "warn",
+ "@typescript-eslint/prefer-namespace-keyword": "warn",
+ "@typescript-eslint/quotes": [
+ "warn",
+ "double"
+ ],
+ "@typescript-eslint/semi": [
+ "warn",
+ "always"
+ ],
+ "@typescript-eslint/triple-slash-reference": [
+ "warn",
+ {
+ "path": "always",
+ "types": "prefer-import",
+ "lib": "always"
+ }
+ ],
+ "@typescript-eslint/type-annotation-spacing": "warn",
+ "@typescript-eslint/unified-signatures": "warn",
+ "brace-style": [
+ "warn",
+ "1tbs"
+ ],
+ "complexity": "off",
+ "constructor-super": "warn",
+ "curly": "warn",
+ "eol-last": "warn",
+ "eqeqeq": [
+ "warn",
+ "smart"
+ ],
+ "guard-for-in": "warn",
+ "id-blacklist": [
+ "warn",
+ "any",
+ "Number",
+ "number",
+ "String",
+ "string",
+ "Boolean",
+ "boolean",
+ "Undefined",
+ "undefined"
+ ],
+ "id-match": "warn",
+ "indent": "off",
+ "max-classes-per-file": [
+ "warn",
+ 1
+ ],
+ "max-len": [
+ "warn",
+ {
+ "code": 200
+ }
+ ],
+ "new-parens": "warn",
+ "no-bitwise": "warn",
+ "no-caller": "warn",
+ "no-cond-assign": "warn",
+ "no-console": [
+ "warn",
+ {
+ "allow": [
+ "log",
+ "warn",
+ "dir",
+ "timeLog",
+ "assert",
+ "clear",
+ "count",
+ "countReset",
+ "group",
+ "groupEnd",
+ "table",
+ "dirxml",
+ "error",
+ "groupCollapsed",
+ "Console",
+ "profile",
+ "profileEnd",
+ "timeStamp",
+ "context"
+ ]
+ }
+ ],
+ "no-debugger": "warn",
+ "no-empty": "off",
+ "no-eval": "warn",
+ "no-fallthrough": "warn",
+ "no-invalid-this": "off",
+ "no-new-wrappers": "warn",
+ "no-redeclare": "warn",
+ "no-restricted-imports": "warn",
+ "no-throw-literal": "warn",
+ "no-trailing-spaces": "warn",
+ "no-undef-init": "warn",
+ "no-underscore-dangle": "off",
+ "no-unsafe-finally": "warn",
+ "no-unused-labels": "warn",
+ "no-var": "warn",
+ "object-shorthand": "warn",
+ "one-var": [
+ "warn",
+ "never"
+ ],
+ "prefer-arrow/prefer-arrow-functions": "warn",
+ "prefer-const": "warn",
+ "radix": "warn",
+ "react/jsx-boolean-value": "warn",
+ "react/jsx-key": "warn",
+ "react/jsx-no-bind": "warn",
+ "react/self-closing-comp": "warn",
+ "spaced-comment": [
+ "warn",
+ "always",
+ {
+ "markers": [
+ "/"
+ ]
+ }
+ ],
+ "use-isnan": "warn",
+ "valid-typeof": "off",
+ "react/display-name": "warn"
+ }
+ }
+ ]
}
}
diff --git a/backend/src/auth/IBMiDStrategy.ts b/backend/src/auth/IBMiDStrategy.ts
index e5d192127..793b22ed5 100644
--- a/backend/src/auth/IBMiDStrategy.ts
+++ b/backend/src/auth/IBMiDStrategy.ts
@@ -1,6 +1,5 @@
import { IDaaSOIDCStrategy as OpenIDConnectStrategy } from "passport-ci-oidc";
-import { getCoopManager } from "./../services/coopManager.service";
-import { getOrganisations } from "./../services/organisation.service";
+import { userService } from "../services/UserService";
export const IBMidStrategy = new OpenIDConnectStrategy({
discoveryURL: process.env.AUTH_discovery_url,
@@ -17,19 +16,17 @@ export const IBMidStrategy = new OpenIDConnectStrategy({
profile.refreshToken = refreshToken;
// Get the farmer coop details
const id = "IBMid:" + profile.id;
- const doc = await getCoopManager(id);
+ const doc = await userService.getUser(id);
if (doc) {
profile.isOnboarded = true;
- profile.coopManager = doc.toObject();
- profile.organisations = await getOrganisations(doc.coopOrganisations);
- profile.selectedOrganisation = profile.organisations[0];
+ profile.user = doc;
}
else {
profile.isOnboarded = false;
- profile.coopManager = null;
+ profile.user = null;
}
console.log(profile);
done(null, profile);
});
}
-)
\ No newline at end of file
+)
diff --git a/backend/src/auth/helpers.ts b/backend/src/auth/helpers.ts
index 728a0aa7a..4d09e7cd4 100644
--- a/backend/src/auth/helpers.ts
+++ b/backend/src/auth/helpers.ts
@@ -1,5 +1,4 @@
-import { CoopManager } from "./../db/entities/coopManager";
-import { Organisation } from "./../db/entities/organisation";
+import { Organisation, User } from "common-types";
export interface CoopManagerUser {
id: string;
@@ -15,7 +14,7 @@ export interface CoopManagerUser {
exp: number;
accessToken: string;
refreshToken: string;
- coopManager: CoopManager | null;
+ coopManager: User | null;
organisations: Organisation[];
selectedOrganisation: Organisation;
}
diff --git a/backend/src/db/entities/coopManager.ts b/backend/src/db/entities/coopManager.ts
deleted file mode 100644
index baf91f6fd..000000000
--- a/backend/src/db/entities/coopManager.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-
-import { Schema, model, ObjectId, Types } from 'mongoose';
-import { Land } from './land';
-
-const ObjectId = Schema.Types.ObjectId;
-
-export interface CoopManager {
- /**
- * Auth provider + auth provider id. E.g. "IBMid:1SDAS61W6A"
- */
- _id?: string,
- /**
- * GeoCode / LatLng coordinate tuple
- */
- location: number[],
- mobile: string,
- coopOrganisations: string[]
-}
-
-export const CoopManagerSchema = new Schema({
- /**
- * Auth provider + auth provider id. E.g. "IBMid:1SDAS61W6A"
- */
- _id: String,
- /**
- * GeoCode / LatLng coordinate tuple
- */
- location: [Number],
- mobile: String,
- coopOrganisations: [String]
-});
-
-export const CoopManagerModel = model("coopManager", CoopManagerSchema);
diff --git a/backend/src/db/entities/crop.ts b/backend/src/db/entities/crop.ts
index 44cc8b66b..24dc11a04 100644
--- a/backend/src/db/entities/crop.ts
+++ b/backend/src/db/entities/crop.ts
@@ -1,28 +1,16 @@
+import { model, Schema, Types } from 'mongoose';
-import { Schema, model, ObjectId, Types } from 'mongoose';
-
-const ObjectId = Schema.Types.ObjectId;
-
-export interface Crop {
- _id?: Types.ObjectId,
- type: string,
- name: string,
- // Start Day of year to end Day of year when to plant the ground nuts
- planting_season: number[],
- time_to_harvest: number,
- is_ongoing: boolean,
- yield_per_sqm: number
-}
+import { Crop } from "common-types"
// Mongoose will automatically add _id property.
-export const CropSchema = new Schema({
+export const CropSchema = new Schema({
+ _id: Types.ObjectId,
type: String,
name: String,
// Start Day of year to end Day of year when to plant the ground nuts
planting_season: [Number],
time_to_harvest: Number,
- is_ongoing: Boolean,
yield_per_sqm: Number
});
-export const CropModel = model("crop", CropSchema);
\ No newline at end of file
+export const CropModel = model("crop", CropSchema);
diff --git a/backend/src/db/entities/farm.ts b/backend/src/db/entities/farm.ts
new file mode 100644
index 000000000..f64fa7608
--- /dev/null
+++ b/backend/src/db/entities/farm.ts
@@ -0,0 +1,34 @@
+import { model, Schema, Types } from 'mongoose';
+import { FarmerSchema } from './farmer';
+
+import { Field, FieldCrop, NewFarm } from "common-types"
+import { CropSchema } from "./crop";
+
+export const FieldCropSchema = new Schema({
+ crop: CropSchema,
+ planted_date: Date,
+ harvested_date: Date
+}, {id: false});
+
+export const FieldSchema = new Schema({
+ _id: Types.ObjectId,
+ name: String,
+ crops: [FieldCropSchema],
+ geoShape: {
+ type: "Polygon",
+ coordinates: [[Number]]
+ },
+});
+
+export const FarmSchema = new Schema({
+ _id: Types.ObjectId,
+ farmer: FarmerSchema,
+ name: String,
+ fields: [FieldSchema],
+ geoShape: {
+ type: "Polygon",
+ coordinates: [[Number]]
+ },
+});
+
+export const FarmModel = model("farm", FarmSchema);
diff --git a/backend/src/db/entities/farmer.ts b/backend/src/db/entities/farmer.ts
index e34e97810..c313817a8 100644
--- a/backend/src/db/entities/farmer.ts
+++ b/backend/src/db/entities/farmer.ts
@@ -1,30 +1,14 @@
+import { model, Schema, Types } from 'mongoose';
+import { Farmer } from "common-types"
+import { FarmSchema } from "./farm";
-import { FieldResponse } from './../../integrations/EIS/EIS.types';
-import { Schema, model, ObjectId, Types } from 'mongoose';
-import { Land } from './land';
-
-const ObjectId = Schema.Types.ObjectId;
-
-export interface Farmer {
- _id?: Types.ObjectId,
- name: string,
- mobile: string,
- address: string,
- coopOrganisations: string[],
- fieldCount: number;
- field?: FieldResponse;
-}
-
-export const FarmerSchema = new Schema({
- _id: {
- type: ObjectId,
- auto: true
- },
+export const FarmerSchema = new Schema({
+ _id: Types.ObjectId,
name: String,
mobile: String,
address: String,
- coopOrganisations: [String],
- fieldCount: Number
+ organisation: String,
+ farms: [FarmSchema]
});
-export const FarmerModel = model("farmer", FarmerSchema);
\ No newline at end of file
+export const FarmerModel = model("farmer", FarmerSchema);
diff --git a/backend/src/db/entities/land.ts b/backend/src/db/entities/land.ts
deleted file mode 100644
index b2a1e2474..000000000
--- a/backend/src/db/entities/land.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-// Using Node.js `require()`
-import { Schema, Model, model, Types } from 'mongoose';
-import { CropSchema, Crop } from "./crop";
-import { FarmerSchema, Farmer } from './farmer';
-
-const ObjectId = Schema.Types.ObjectId;
-
-export interface FarmerCrop {
- _id?: Types.ObjectId,
- farmer: Farmer,
- crop: Crop
-}
-
-export const FarmerCropSchema = new Schema({
- _id: ObjectId,
- farmer: FarmerSchema,
- crop: CropSchema
-});
-
-export interface Land {
- _id?: Types.ObjectId,
- type: string,
- fid: number,
- name: string,
- crops: FarmerCrop[]
-}
-
-export const LandSchema = new Schema({
- _id: ObjectId,
- type: String,
- fid: Number,
- name: String,
- crops: [FarmerCropSchema]
-});
-
-export const LandModel = model("land", LandSchema);
\ No newline at end of file
diff --git a/backend/src/db/entities/messageLog.ts b/backend/src/db/entities/messageLog.ts
index a5a633fee..6e6e0ae38 100644
--- a/backend/src/db/entities/messageLog.ts
+++ b/backend/src/db/entities/messageLog.ts
@@ -1,5 +1,4 @@
-
-import { Schema, model, ObjectId, Types } from 'mongoose';
+import { model, Schema, Types } from 'mongoose';
const ObjectId = Schema.Types.ObjectId;
@@ -56,4 +55,4 @@ export const MessageLogSchema = new Schema({
messageRef: String
});
-export const MessageLogModel = model("messageLog", MessageLogSchema);
\ No newline at end of file
+export const MessageLogModel = model("messageLog", MessageLogSchema);
diff --git a/backend/src/db/entities/organisation.ts b/backend/src/db/entities/organisation.ts
index 0ec005eb2..129ae1d78 100644
--- a/backend/src/db/entities/organisation.ts
+++ b/backend/src/db/entities/organisation.ts
@@ -1,20 +1,31 @@
+import { model, Schema } from 'mongoose';
-import { Schema, model, ObjectId, Types } from 'mongoose';
-import { Land } from './land';
+import { Organisation, User } from "common-types";
-const ObjectId = Schema.Types.ObjectId;
+export const UserSchema = new Schema({
+ /**
+ * Auth provider + auth provider id. E.g. "IBMid:1SDAS61W6A"
+ */
+ _id: String,
+ /**
+ * GeoCode / LatLng coordinate tuple
+ */
+ location: [{
+ type: "Point",
+ coordinates: [Number]
+ }],
+ mobile: String,
+});
-export interface Organisation {
- _id?: Types.ObjectId,
- name: string
-}
-export const OrganisationSchema = new Schema({
- _id: {
- type: ObjectId,
- auto: true
+export const OrganisationSchema = new Schema({
+ name: {
+ type: String,
+ unique: true,
+ required: true,
+ index: true
},
- name: String,
-});
+ users: [UserSchema]
+}, { _id : false });
export const OrganisationModel = model("organisation", OrganisationSchema);
diff --git a/backend/src/db/mongodb.ts b/backend/src/db/mongodb.ts
index b4e313d70..019ec1d89 100644
--- a/backend/src/db/mongodb.ts
+++ b/backend/src/db/mongodb.ts
@@ -1,6 +1,5 @@
// Using Node.js `require()`
import { connect } from 'mongoose';
-import { writeFile } from "fs/promises";
export async function mongoInit() {
// console.log(process.env.mongodb_url);
diff --git a/backend/src/integrations/EIS/EIS-api.service.ts b/backend/src/integrations/EIS/EIS-api.service.ts
deleted file mode 100644
index b8f4bf20a..000000000
--- a/backend/src/integrations/EIS/EIS-api.service.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-import axios from "axios";
-import { EISField, EISFieldCreateResponse, EISSubFieldSearchReturn, FieldResponse, Fields } from "./EIS.types";
-
-
-export class EISAPIService {
-
- access_token = "";
- /**
- * This is a millisecond based timestamp of when the token is due to expire.
- * If we're 10 minutes away from it we renew the token
- */
- expiration = 0;
-
- baseAPI = "https://foundation.agtech.ibm.com/v2/";
-
- authAxios = axios.create({});
-
- constructor(private apiKey: string) {
-
- }
-
-
- async ensureToken(): Promise {
- // 10 mins earlier we try to renew the token
- if (this.expiration == 0 || Date.now() > (this.expiration - 600000)) {
- // Token is expired
- const res = await axios.post("https://auth-b2b-twc.ibm.com/Auth/GetBearerForClient", {
- "apiKey": this.apiKey,
- "clientId": "ibm-agro-api"
- });
-
- this.access_token = res.data.access_token;
- this.expiration = Date.now() + (res.data.expires_in * 1000);
-
- this.authAxios.defaults.headers.common['Authorization'] = `Bearer ${this.access_token}`;
-
- return true;
- }
- return false;
- }
-
- async createField(field: EISField): Promise {
- await this.ensureToken();
-
- const res = await this.authAxios.post(this.baseAPI + "field", field);
- return res.data;
- }
-
- async getFields() {
- await this.ensureToken();
- const res = await this.authAxios.get(this.baseAPI + "field");
- return res.data
- }
-
- async getField(uuid: string) {
- await this.ensureToken();
- const fieldRes = await this.authAxios.get(this.baseAPI + `field/${uuid}`);
-
- const field = fieldRes.data;
-
- // We need to convert the open harvest object from a string to JSON because EIS stores it as a string
- for (let i = 0; i < field.subFields.features.length; i++) {
- const subField = field.subFields.features[i];
- subField.properties.open_harvest = JSON.parse(subField.properties.open_harvest as any);
- }
-
- return fieldRes.data;
- }
-
- async getFarmerField(farmer_id: string): Promise {
- await this.ensureToken();
-
- const queryBody = {
- "uuidsOnly": false,
- "inputType": "SPECIFIED_FIELD",
- "includeDeleted": true,
- "includeAssetGeometry": true,
- "properties": {
- open_harvest: {
- farmer_id
- }
- }
- };
-
- const res = await this.authAxios.post(this.baseAPI + "asset/search", queryBody);
- const subfields = res.data;
-
- if (subfields.totalRecords == 0) {
- return null;
- }
-
- // Get the parent reference which points to the field uuid
- const fieldUuid = subfields.features[0].parentReference;
-
- try {
- return await this.getField(fieldUuid);
- }
- catch (e) {
- console.error(e);
- return null;
- }
- }
-
-
-
-
-}
\ No newline at end of file
diff --git a/backend/src/integrations/EIS/EIS.types.ts b/backend/src/integrations/EIS/EIS.types.ts
index 6f988246c..2b4099d1b 100644
--- a/backend/src/integrations/EIS/EIS.types.ts
+++ b/backend/src/integrations/EIS/EIS.types.ts
@@ -1,6 +1,5 @@
-import { Crop } from "../../db/entities/crop";
-import { Feature, FeatureCollection, Geometry, Polygon } from "geojson";
-import { LatLng } from "integrations/weather-company-api.types";
+import { Feature, FeatureCollection, Polygon } from "geojson";
+import { GeoCodeNumber, NewFieldCrop } from "common-types";
/**
* There are many redundant fields you'll notice because EIS's data structures
@@ -37,7 +36,7 @@ export interface FieldResponseSubfieldFeatureExtras {
projection: 4326;
}
-export type FieldResponseSubfieldFeature = Feature & FieldResponseSubfieldFeatureExtras;
+export type FieldResponseSubfieldFeature = Feature & FieldResponseSubfieldFeatureExtras;
export interface FieldResponseSubfield {
type: "FeatureCollection",
@@ -51,16 +50,10 @@ export interface FieldResponse {
subFields: FieldResponseSubfield;
}
-// Field Create Structures
-export interface SubFieldCrop {
- planted: Date;
- harvested: Date | null;
- farmer: string;
- /**
- * Crop Information
- */
- crop: Crop;
-}
+export type OpenHarvestSubFieldProps = {
+ farmer_id: string;
+ crops: NewFieldCrop[]
+};
export interface EISSubFieldProperties {
farm_name: string
@@ -68,10 +61,7 @@ export interface EISSubFieldProperties {
/**
* When getting a field from EIS this is initially a string and we need to parse the object
*/
- open_harvest: {
- farmer_id: string;
- crops: SubFieldCrop[]
- }
+ open_harvest: OpenHarvestSubFieldProps
}
export interface EISSubField {
@@ -109,7 +99,7 @@ export interface EISSubFieldSearchReturnFeatureProperties {
east: number;
west: number;
};
- centroid: LatLng;
+ centroid: GeoCodeNumber;
ianaTimeZone: string;
deleted: boolean;
inputType: string;
@@ -118,10 +108,7 @@ export interface EISSubFieldSearchReturnFeatureProperties {
farm_name: string;
field_id: string;
field_name: string;
- open_harvest: {
- farmer_id: string;
- crops: SubFieldCrop[]
- }
+ open_harvest: OpenHarvestSubFieldProps
}
export interface EISSubFieldSearchReturnFeatureExtras {
diff --git a/backend/src/integrations/EIS/EISFarmService.ts b/backend/src/integrations/EIS/EISFarmService.ts
new file mode 100644
index 000000000..1b38ffaa5
--- /dev/null
+++ b/backend/src/integrations/EIS/EISFarmService.ts
@@ -0,0 +1,116 @@
+import { EISConfig, Farm, Farmer, Field, NewFarm, NewField } from "common-types";
+import { EISField, EISFieldCreateResponse, EISSubField, EISSubFieldProperties, EISSubFieldSearchReturn, FieldResponse, OpenHarvestSubFieldProps } from "./EIS.types";
+import { EISService } from "./EISService";
+import { Feature, FeatureCollection, Polygon } from "geojson";
+
+export class EISFarmService extends EISService {
+
+ async getFarmerFarms(eisConfig: EISConfig, farmer: Farmer): Promise {
+ const eisSession = await this.getToken(eisConfig);
+
+ const queryBody = {
+ "uuidsOnly": false,
+ "inputType": "SPECIFIED_FIELD",
+ "includeDeleted": true,
+ "includeAssetGeometry": true,
+ "properties": {
+ open_harvest: {
+ farmer_id: farmer?._id
+ }
+ }
+ };
+
+ const res = await eisSession.authAxios.post(eisSession.eisConfig.apiUrl + "asset/search", queryBody);
+ const subfields = res.data;
+
+ if (subfields.totalRecords == 0) {
+ return [];
+ }
+
+ // Get the parent reference which points to the field uuid
+ const parentRefs: {[name: string] : string} = {};
+
+ subfields.features.forEach((subField) => {
+ parentRefs[subField.parentReference] = subField.parentReference;
+ });
+
+ const farms: Farm[] = [];
+
+ try {
+ for (const parentRef of Object.keys(parentRefs)) {
+ farms.push(await this.getFarm(eisConfig, parentRef, farmer));
+ }
+ } catch (e) {
+ console.error(e);
+ }
+ return farms;
+ }
+
+ async saveFarm(eisConfig: EISConfig, farm: NewFarm): Promise {
+ const eisSession = await this.getToken(eisConfig);
+
+ const eisField: EISField = {
+ name: farm.name,
+ subFields: farm.fields.map((field) => EISFarmService.convertToEisSubField(farm.farmer, field))
+ };
+
+ const res = await eisSession.authAxios.post(eisSession.eisConfig.apiUrl + "field", eisField);
+
+ return this.getFarm(eisConfig, res.data.field, farm.farmer);
+ }
+
+ private async getFarm(eisConfig: EISConfig, uuid: string, farmer: Farmer): Promise {
+ const eisSession = await this.getToken(eisConfig);
+
+ const fieldRes = await eisSession.authAxios.get(eisSession.eisConfig.apiUrl + `field/${uuid}`);
+
+ const fieldResponse: FieldResponse = fieldRes.data;
+
+ const fields: Field[] = [];
+ for (const subField of fieldResponse.subFields.features) {
+ // We need to convert the open harvest object from a string to JSON because EIS stores it as a string
+ const openHarvestProps: OpenHarvestSubFieldProps = JSON.parse(subField.properties.open_harvest as any);
+
+ let field: Field = {
+ _id: subField.uuid,
+ geoShape: subField.geometry,
+ name: subField.properties.field_name,
+ crops: openHarvestProps.crops
+ };
+
+ fields.push(field);
+ }
+
+ return {_id: uuid, fields, farmer, name: fieldResponse.properties.name};
+ }
+
+ private static convertToEisSubField(farmer: Farmer, field: NewField): EISSubField {
+ const feature: Feature = {
+ geometry: field.geoShape,
+ properties: {
+ farm_name: field.name,
+ open_harvest_farmer_id: farmer._id,
+ open_harvest: {
+ farmer_id: farmer._id,
+ crops: field.crops
+ }
+ },
+ type: "Feature"
+ }
+
+ const featureCollection: FeatureCollection = {
+ features: [feature],
+ type: "FeatureCollection"
+ }
+
+ return {
+ geo: {
+ geojson: featureCollection,
+ type: "geojson"
+ },
+ name: field.name
+ }
+ }
+}
+
+export const eisFarmService: EISFarmService = new EISFarmService();
diff --git a/backend/src/integrations/EIS/EISService.ts b/backend/src/integrations/EIS/EISService.ts
new file mode 100644
index 000000000..dcc1eaef3
--- /dev/null
+++ b/backend/src/integrations/EIS/EISService.ts
@@ -0,0 +1,54 @@
+import axios, { AxiosInstance } from "axios";
+import { EISConfig } from "../../../../common-types/integrations/EISConfig";
+import { isUndefined } from "common-types";
+
+type EISSession = {
+ token: string,
+ expiration: number,
+ authAxios: AxiosInstance,
+ eisConfig: EISConfig
+};
+
+export abstract class EISService {
+ private readonly accessTokens: {
+ [name: string]: EISSession
+ } = {};
+
+ /**
+ * This is a millisecond based timestamp of when the token is due to expire.
+ * If we're 10 minutes away from it, we renew the token
+ */
+
+ // baseAPI = "https://foundation.agtech.ibm.com/v2/";
+ //tokenUrl = "https://auth-b2b-twc.ibm.com/Auth/GetBearerForClient";
+
+ async getToken(eisConfig: EISConfig): Promise {
+
+ const accessTokenKey = `${eisConfig.clientId}:${eisConfig.apiKey}`;
+
+ const accessToken: EISSession = this.accessTokens[accessTokenKey];
+
+ // 10 minutes earlier we try to renew the token
+ if (isUndefined(accessToken) || accessToken.expiration == 0 || Date.now() > (accessToken.expiration - 600000)) {
+ // Token is expired
+ const res = await axios.post(eisConfig.tokenUrl, {
+ "apiKey": eisConfig.apiKey,
+ "clientId": eisConfig.clientId // "ibm-agro-api"
+ });
+
+ const authAxios = accessToken?.authAxios || axios.create({});
+ const newToken = res.data.access_token;
+ authAxios.defaults.headers.common['Authorization'] = `Bearer ${newToken}`
+
+ this.accessTokens[accessTokenKey] = {
+ token: newToken,
+ expiration: Date.now() + (res.data.expires_in * 1000),
+ authAxios,
+ eisConfig
+ };
+ }
+ return this.accessTokens[accessTokenKey];
+ }
+
+}
+
diff --git a/backend/src/integrations/eventBus.service.ts b/backend/src/integrations/eventBus.service.ts
index 95856e3e0..3a555cdc0 100644
--- a/backend/src/integrations/eventBus.service.ts
+++ b/backend/src/integrations/eventBus.service.ts
@@ -5,11 +5,11 @@
* will make the move easy as it will just become an interface
*/
+import { Organisation } from "common-types";
import { EventEmitter } from "events";
-import { Organisation } from "./../db/entities/organisation";
-import { MessageLog } from "./../db/entities/messageLog";
-import { SocketIOManagerInstance } from "./../sockets/socket.io";
+import { MessageLog } from "../db/entities/messageLog";
+import { SocketIOManagerInstance } from "../sockets/socket.io";
export declare interface EventBus {
on(event: 'onMessage', listener: (message: MessageLog) => void): this;
diff --git a/backend/src/integrations/messagingInterface.ts b/backend/src/integrations/messagingInterface.ts
index ddb44e807..21967e344 100644
--- a/backend/src/integrations/messagingInterface.ts
+++ b/backend/src/integrations/messagingInterface.ts
@@ -1,9 +1,9 @@
import { EventEmitter } from "events";
-import { Farmer, FarmerModel } from "./../db/entities/farmer";
-import { MessageLog } from "./../db/entities/messageLog";
-import { CoopManager } from "./../db/entities/coopManager";
-import { EventBusInstance } from "./../integrations/eventBus.service";
-import { OrganisationModel } from "./../db/entities/organisation";
+import { FarmerModel } from "../db/entities/farmer";
+import { MessageLog } from "../db/entities/messageLog";
+import { EventBusInstance } from "./eventBus.service";
+import { OrganisationModel } from "../db/entities/organisation";
+import { Farmer, User } from "common-types";
export declare interface MessagingInterface {
on(event: 'onMessage', listener: (message: MessageLog) => void): this;
@@ -30,7 +30,7 @@ export abstract class MessagingInterface extends EventEmitt
* @param coopManager CoopManager we're sending a message to.
* @param message The string message we want to send.
*/
- abstract sendMessageToCoopManager(coopManager: CoopManager, message: string): Promise;
+ abstract sendMessageToCoopManager(coopManager: User, message: string): Promise;
/**
* Send a message to an arbitrary destination. This is a way of giving flexibility
@@ -67,15 +67,13 @@ export abstract class MessagingInterface extends EventEmitt
throw new Error("Farmer from MessageLog not Found!");
}
- for (const org_id of farmer.coopOrganisations) {
- OrganisationModel.findById(org_id).then(org => {
+ OrganisationModel.findById(farmer.organisation).then(org => {
if (org === null) {
throw new Error("Org in Farmer not found!");
}
EventBusInstance.publishMessage(org, message);
})
- }
}
-}
\ No newline at end of file
+}
diff --git a/backend/src/integrations/smsSync/smsSync.service.ts b/backend/src/integrations/smsSync/smsSync.service.ts
index f2e516ecb..9a0728d83 100644
--- a/backend/src/integrations/smsSync/smsSync.service.ts
+++ b/backend/src/integrations/smsSync/smsSync.service.ts
@@ -1,9 +1,9 @@
-import { CoopManager } from "../../db/entities/coopManager";
-import { Farmer, FarmerModel } from "../../db/entities/farmer";
+import { FarmerModel } from "../../db/entities/farmer";
import { MessagingInterface } from "../messagingInterface";
import { v4 as uuidv4 } from "uuid";
-import { MessageLog, MessageLogModel, Source, Status } from "./../../db/entities/messageLog";
+import { MessageLog, MessageLogModel, Source, Status } from "../../db/entities/messageLog";
+import { Farmer, User } from "common-types";
export interface SMSSyncMessage {
to: string;
@@ -43,12 +43,16 @@ export class SMSSyncAPI extends MessagingInterface
private pendingMessages: SMSSyncMessage[] = [];
async sendMessageToFarmer(farmer: Farmer, message: string): Promise {
- const number = farmer.mobile;
-
if (message === undefined || message === null || message === "") {
throw new Error("Message is empty!");
}
+ if (farmer.mobile.length === 0) {
+ throw new Error("Farmer has no mobile numbers: " + farmer);
+ }
+
+ const number = farmer.mobile[0];
+
const messageRef = await this.sendMessage(number, message);
const messageLogEntry: MessageLog = {
@@ -61,12 +65,10 @@ export class SMSSyncAPI extends MessagingInterface
messageRef: messageRef
}
- const messageLog = await MessageLogModel.create(messageLogEntry);
-
- return messageLog;
+ return await MessageLogModel.create(messageLogEntry);
}
- async sendMessageToCoopManager(coopManager: CoopManager, message: string): Promise {
+ async sendMessageToCoopManager(coopManager: User, message: string): Promise {
throw new Error("Method not implemented.");
// const number = coopManager.mobile;
@@ -124,7 +126,7 @@ export class SMSSyncAPI extends MessagingInterface
const messageLog = await MessageLogModel.create(messageLogEntry);
this.emit("onMessage", messageLog);
- this.notify(messageLog)
+ await this.notify(messageLog)
return messageLog;
}
diff --git a/backend/src/integrations/twilio/twilio.service.ts b/backend/src/integrations/twilio/twilio.service.ts
index d835a0556..b16275fc4 100644
--- a/backend/src/integrations/twilio/twilio.service.ts
+++ b/backend/src/integrations/twilio/twilio.service.ts
@@ -1,8 +1,8 @@
-import twilio, { Twilio} from "twilio";
-import { CoopManager } from "./../../db/entities/coopManager";
-import { Farmer, FarmerModel } from "./../../db/entities/farmer";
-import { MessageLog, MessageLogModel, Source, Status } from "./../../db/entities/messageLog";
-import { MessagingInterface } from "./../../integrations/messagingInterface";
+import { Farmer, User } from "common-types";
+import twilio, { Twilio } from "twilio";
+import { FarmerModel } from "../../db/entities/farmer";
+import { MessageLog, MessageLogModel, Source, Status } from "../../db/entities/messageLog";
+import { MessagingInterface } from "../messagingInterface";
/**
* The Message we get from Twilio on our webhook
@@ -25,25 +25,29 @@ export interface TwilioMessage {
* This class handles interfacing with twilio.
* It provides one
*/
-class TwilioAPI extends MessagingInterface {
+export class TwilioAPI extends MessagingInterface {
client: Twilio;
messagingServiceSid: string;
+ twilioInstance: TwilioAPI;
constructor() {
super();
const accountSid = process.env.Twilio_accountSid;
const authToken = process.env.Twilio_token;
this.messagingServiceSid = process.env.Twilio_messaging_service as string;
this.client = twilio(accountSid, authToken);
+ this.twilioInstance = new TwilioAPI();
}
async sendMessageToFarmer(farmer: Farmer, message: string): Promise {
- const number = farmer.mobile;
-
if (message === undefined || message === null || message === "") {
throw new Error("Message is empty!");
}
+ if (farmer.mobile.length === 0) {
+ throw new Error("Farmer has no mobile numbers: " + farmer);
+ }
+ const number = farmer.mobile[0];
const messageRef = await this.sendMessage(number, message);
const messageLogEntry: MessageLog = {
@@ -56,12 +60,10 @@ class TwilioAPI extends MessagingInterface {
messageRef
}
- const messageLog = await MessageLogModel.create(messageLogEntry);
-
- return messageLog;
+ return await MessageLogModel.create(messageLogEntry);
}
- async sendMessageToCoopManager(coopManager: CoopManager, message: string): Promise {
+ async sendMessageToCoopManager(coopManager: User, message: string): Promise {
throw new Error("Method not implemented.");
}
@@ -112,4 +114,4 @@ class TwilioAPI extends MessagingInterface {
}
-export const TwilioInstance = new TwilioAPI();
+
diff --git a/backend/src/integrations/weather-company-api.service.ts b/backend/src/integrations/weather-company-api.service.ts
deleted file mode 100644
index c1c878b88..000000000
--- a/backend/src/integrations/weather-company-api.service.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import axios from "axios";
-import { GeoCode, Languages, Units, CommonOptions, Formats } from "./weather-company-api.types";
-
-const testForecastData = {"calendarDayTemperatureMax":[21,22,24,24,24,24,24,24,25,25,25,25,24,25,25],"calendarDayTemperatureMin":[18,17,17,17,16,17,17,17,17,17,17,17,17,17,17],"dayOfWeek":["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"],"expirationTimeUtc":[1645343951,1645343951,1645343951,1645343951,1645343951,1645343951,1645343951,1645343951,1645343951,1645343951,1645343951,1645343951,1645343951,1645343951,1645343951],"moonPhase":["Waning Gibbous","Waning Gibbous","Waning Gibbous","Waning Gibbous","Last Quarter","Waning Crescent","Waning Crescent","Waning Crescent","Waning Crescent","Waning Crescent","New","Waxing Crescent","Waxing Crescent","Waxing Crescent","Waxing Crescent"],"moonPhaseCode":["WNG","WNG","WNG","WNG","LQ","WNC","WNC","WNC","WNC","WNC","N","WXC","WXC","WXC","WXC"],"moonPhaseDay":[18,19,20,21,22,24,25,26,27,28,29,1,2,3,3],"moonriseTimeLocal":["2022-02-20T21:05:50+0200","2022-02-21T21:47:27+0200","2022-02-22T22:31:47+0200","2022-02-23T23:21:30+0200","","2022-02-25T00:15:43+0200","2022-02-26T01:15:50+0200","2022-02-27T02:18:43+0200","2022-02-28T03:23:17+0200","2022-03-01T04:25:19+0200","2022-03-02T05:25:08+0200","2022-03-03T06:20:36+0200","2022-03-04T07:13:59+0200","2022-03-05T08:04:43+0200","2022-03-06T08:54:39+0200"],"moonriseTimeUtc":[1645383950,1645472847,1645561907,1645651290,null,1645740943,1645830950,1645921123,1646011397,1646101519,1646191508,1646281236,1646370839,1646460283,1646549679],"moonsetTimeLocal":["2022-02-20T08:50:23+0200","2022-02-21T09:43:37+0200","2022-02-22T10:38:23+0200","2022-02-23T11:37:00+0200","2022-02-24T12:37:49+0200","2022-02-25T13:41:32+0200","2022-02-26T14:44:20+0200","2022-02-27T15:45:07+0200","2022-02-28T16:40:27+0200","2022-03-01T17:31:14+0200","2022-03-02T18:16:18+0200","2022-03-03T18:58:16+0200","2022-03-04T19:37:52+0200","2022-03-05T20:15:34+0200","2022-03-06T20:53:52+0200"],"moonsetTimeUtc":[1645339823,1645429417,1645519103,1645609020,1645699069,1645789292,1645879460,1645969507,1646059227,1646148674,1646237778,1646326696,1646415472,1646504134,1646592832],"narrative":["Thunderstorms developing in the afternoon. Highs 20 to 22ºC and lows 16 to 18ºC.","Thunderstorms. Highs 21 to 23ºC and lows 16 to 18ºC.","Scattered thunderstorms. Highs 23 to 25ºC and lows 16 to 18ºC.","Thunderstorms developing in the afternoon. Highs 23 to 25ºC and lows 15 to 17ºC.","Thunderstorms. Highs 23 to 25ºC and lows 16 to 18ºC.","Thunderstorms. Highs 23 to 25ºC and lows 16 to 18ºC.","Scattered thunderstorms. Highs 23 to 25ºC and lows 16 to 18ºC.","Scattered thunderstorms. Highs 23 to 25ºC and lows 16 to 18ºC.","Scattered thunderstorms. Highs 24 to 26ºC and lows 16 to 18ºC.","Scattered thunderstorms. Highs 24 to 26ºC and lows 16 to 18ºC.","Scattered thunderstorms. Highs 24 to 26ºC and lows 16 to 18ºC.","Scattered thunderstorms. Highs 24 to 26ºC and lows 16 to 18ºC.","Thunderstorms. Highs 23 to 25ºC and lows 16 to 18ºC.","Thunderstorms. Highs 24 to 26ºC and lows 16 to 18ºC.","Scattered thunderstorms. Highs 24 to 26ºC and lows 15 to 17ºC."],"qpf":[1.64,8,3.47,1.64,7.29,5.77,3.96,3.2,1.8,4.84,4,4.2,4.77,5.43,4.05],"qpfSnow":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"sunriseTimeLocal":["2022-02-20T05:48:00+0200","2022-02-21T05:48:16+0200","2022-02-22T05:48:31+0200","2022-02-23T05:48:45+0200","2022-02-24T05:48:59+0200","2022-02-25T05:49:13+0200","2022-02-26T05:49:26+0200","2022-02-27T05:49:38+0200","2022-02-28T05:49:50+0200","2022-03-01T05:50:02+0200","2022-03-02T05:50:13+0200","2022-03-03T05:50:24+0200","2022-03-04T05:50:34+0200","2022-03-05T05:50:44+0200","2022-03-06T05:50:53+0200"],"sunriseTimeUtc":[1645328880,1645415296,1645501711,1645588125,1645674539,1645760953,1645847366,1645933778,1646020190,1646106602,1646193013,1646279424,1646365834,1646452244,1646538653],"sunsetTimeLocal":["2022-02-20T18:16:07+0200","2022-02-21T18:15:38+0200","2022-02-22T18:15:08+0200","2022-02-23T18:14:38+0200","2022-02-24T18:14:06+0200","2022-02-25T18:13:35+0200","2022-02-26T18:13:02+0200","2022-02-27T18:12:29+0200","2022-02-28T18:11:56+0200","2022-03-01T18:11:21+0200","2022-03-02T18:10:47+0200","2022-03-03T18:10:11+0200","2022-03-04T18:09:36+0200","2022-03-05T18:09:00+0200","2022-03-06T18:08:23+0200"],"sunsetTimeUtc":[1645373767,1645460138,1645546508,1645632878,1645719246,1645805615,1645891982,1645978349,1646064716,1646151081,1646237447,1646323811,1646410176,1646496540,1646582903],"temperatureMax":[21,22,24,24,24,24,24,24,25,25,25,25,24,25,25],"temperatureMin":[17,17,17,16,17,17,17,17,17,17,17,17,17,17,16],"validTimeLocal":["2022-02-20T07:00:00+0200","2022-02-21T07:00:00+0200","2022-02-22T07:00:00+0200","2022-02-23T07:00:00+0200","2022-02-24T07:00:00+0200","2022-02-25T07:00:00+0200","2022-02-26T07:00:00+0200","2022-02-27T07:00:00+0200","2022-02-28T07:00:00+0200","2022-03-01T07:00:00+0200","2022-03-02T07:00:00+0200","2022-03-03T07:00:00+0200","2022-03-04T07:00:00+0200","2022-03-05T07:00:00+0200","2022-03-06T07:00:00+0200"],"validTimeUtc":[1645333200,1645419600,1645506000,1645592400,1645678800,1645765200,1645851600,1645938000,1646024400,1646110800,1646197200,1646283600,1646370000,1646456400,1646542800],"daypart":[{"cloudCover":[94,92,83,84,73,61,57,59,67,66,68,69,59,68,64,64,49,62,52,72,57,68,56,53,66,52,68,70,60,62],"dayOrNight":["D","N","D","N","D","N","D","N","D","N","D","N","D","N","D","N","D","N","D","N","D","N","D","N","D","N","D","N","D","N"],"daypartName":["Today","Tonight","Tomorrow","Tomorrow night","Tuesday","Tuesday night","Wednesday","Wednesday night","Thursday","Thursday night","Friday","Friday night","Saturday","Saturday night","Sunday","Sunday night","Monday","Monday night","Tuesday","Tuesday night","Wednesday","Wednesday night","Thursday","Thursday night","Friday","Friday night","Saturday","Saturday night","Sunday","Sunday night"],"iconCode":[38,11,4,47,38,47,38,47,4,47,4,47,38,47,38,29,38,29,38,47,38,47,38,47,4,4,4,4,38,11],"iconCodeExtend":[7203,1140,400,3809,3800,6200,7203,3809,400,3809,400,3809,3800,3809,3800,2900,3800,2900,3800,3809,3800,3809,3800,3809,400,400,400,400,3800,1100],"narrative":["Thunderstorms developing in the afternoon. High 21ºC. Winds WSW at 10 to 15 km/h. Chance of rain 40%.","Thundershowers. Low 17ºC. Winds WSW and variable. Chance of rain 40%.","Thunderstorms. High 22ºC. Winds SW at 10 to 15 km/h. Chance of rain 80%.","Scattered thunderstorms. Low 17ºC. Winds SW and variable. Chance of rain 50%.","Scattered thunderstorms. High 24ºC. Winds S at 10 to 15 km/h. Chance of rain 50%.","Thunderstorms early. Low 17ºC. Winds S and variable. Chance of rain 40%.","Thunderstorms developing in the afternoon. High 24ºC. Winds S at 10 to 15 km/h. Chance of rain 50%.","Scattered thunderstorms. Low 16ºC. Winds SSE and variable. Chance of rain 40%.","Thunderstorms. High 24ºC. Winds S and variable. Chance of rain 70%.","Scattered thunderstorms. Low 17ºC. Winds NW and variable. Chance of rain 60%.","Thunderstorms. High 24ºC. Winds WNW and variable. Chance of rain 70%.","Scattered thunderstorms. Low 17ºC. Winds SW and variable. Chance of rain 60%.","Scattered thunderstorms. High 24ºC. Winds SSW at 10 to 15 km/h. Chance of rain 50%.","Scattered thunderstorms. Low 17ºC. Winds SE and variable. Chance of rain 50%.","Scattered thunderstorms. High 24ºC. Winds SSE at 10 to 15 km/h. Chance of rain 50%.","Partly cloudy. Low 17ºC. Winds SE and variable.","Scattered thunderstorms. High 25ºC. Winds SSE at 10 to 15 km/h. Chance of rain 40%.","Partly cloudy. Low 17ºC. Winds ESE and variable.","Scattered thunderstorms. High 25ºC. Winds SE at 10 to 15 km/h. Chance of rain 60%.","Scattered thunderstorms. Low 17ºC. Winds SE and variable. Chance of rain 50%.","Scattered thunderstorms. High 25ºC. Winds SSE at 10 to 15 km/h. Chance of rain 50%.","Scattered thunderstorms. Low 17ºC. Winds SSE and variable. Chance of rain 50%.","Scattered thunderstorms. High 25ºC. Winds SSE at 10 to 15 km/h. Chance of rain 50%.","Scattered thunderstorms. Low 17ºC. Winds SSE and variable. Chance of rain 50%.","Thunderstorms. High 24ºC. Winds SSE at 10 to 15 km/h. Chance of rain 60%.","Thunderstorms. Low 17ºC. Winds SSE and variable. Chance of rain 60%.","Thunderstorms. High 25ºC. Winds SSE at 10 to 15 km/h. Chance of rain 60%.","Thunderstorms. Low 17ºC. Winds SSE and variable. Chance of rain 60%.","Scattered thunderstorms. High 25ºC. Winds SSE at 10 to 15 km/h. Chance of rain 50%.","Showers. Low 16ºC. Winds SSE and variable. Chance of rain 50%."],"precipChance":[40,42,78,47,51,43,45,42,68,55,74,58,53,53,54,24,44,24,57,51,53,51,50,45,60,60,60,60,51,50],"precipType":["rain","rain","rain","rain","rain","rain","rain","rain","rain","rain","rain","rain","rain","rain","rain","rain","rain","rain","rain","rain","rain","rain","rain","rain","rain","rain","rain","rain","rain","rain"],"qpf":[0.3,1.34,6.46,1.54,2.6,0.86,0.84,0.8,4.73,2.56,4.5,1.26,2.76,1.2,3,0,1.56,0,3.3,1.54,2.6,1.4,2.9,1.3,3.24,1.53,4.03,1.4,2.9,1.15],"qpfSnow":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"qualifierCode":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"qualifierPhrase":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"relativeHumidity":[90,96,87,94,79,95,78,94,81,94,80,95,80,96,79,96,76,94,75,93,74,93,75,94,77,94,77,94,77,96],"snowRange":["","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"temperature":[21,17,22,17,24,17,24,16,24,17,24,17,24,17,24,17,25,17,25,17,25,17,25,17,24,17,25,17,25,16],"temperatureHeatIndex":[22,20,23,21,25,21,24,20,24,21,24,21,24,21,25,21,27,21,27,21,26,21,25,21,25,21,25,21,25,20],"temperatureWindChill":[20,18,18,18,19,17,18,17,18,17,18,17,18,17,18,17,18,17,18,17,19,17,18,17,18,17,18,17,18,17],"thunderCategory":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"thunderIndex":[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,2,0,2,2,2,2,2,2,2,2,2,2,2,0],"uvDescription":["High","Low","Very High","Low","Extreme","Low","Extreme","Low","Extreme","Low","Extreme","Low","Extreme","Low","Extreme","Low","Extreme","Low","Extreme","Low","Extreme","Low","Extreme","Low","Extreme","Low","Extreme","Low","Extreme","Low"],"uvIndex":[7,0,10,0,11,0,11,0,11,0,11,0,11,0,11,0,11,0,11,0,11,0,11,0,11,0,11,0,11,0],"windDirection":[240,250,232,228,189,183,171,157,171,326,295,229,195,145,163,139,156,113,146,124,150,149,156,162,158,155,150,154,152,160],"windDirectionCardinal":["WSW","WSW","SW","SW","S","S","S","SSE","S","NW","WNW","SW","SSW","SE","SSE","SE","SSE","ESE","SE","SE","SSE","SSE","SSE","SSE","SSE","SSE","SSE","SSE","SSE","SSE"],"windPhrase":["Winds WSW at 10 to 15 km/h.","Winds WSW and variable.","Winds SW at 10 to 15 km/h.","Winds SW and variable.","Winds S at 10 to 15 km/h.","Winds S and variable.","Winds S at 10 to 15 km/h.","Winds SSE and variable.","Winds S and variable.","Winds NW and variable.","Winds WNW and variable.","Winds SW and variable.","Winds SSW at 10 to 15 km/h.","Winds SE and variable.","Winds SSE at 10 to 15 km/h.","Winds SE and variable.","Winds SSE at 10 to 15 km/h.","Winds ESE and variable.","Winds SE at 10 to 15 km/h.","Winds SE and variable.","Winds SSE at 10 to 15 km/h.","Winds SSE and variable.","Winds SSE at 10 to 15 km/h.","Winds SSE and variable.","Winds SSE at 10 to 15 km/h.","Winds SSE and variable.","Winds SSE at 10 to 15 km/h.","Winds SSE and variable.","Winds SSE at 10 to 15 km/h.","Winds SSE and variable."],"windSpeed":[12,7,14,6,15,8,14,9,9,5,10,5,12,7,11,6,10,7,10,6,10,7,12,7,12,7,11,7,12,7],"wxPhraseLong":["PM T-Storms","T-Showers","T-Storms","Scattered T-Storms","Scattered T-Storms","T-Storms Early","PM T-Storms","Scattered T-Storms","T-Storms","Scattered T-Storms","T-Storms","Scattered T-Storms","Scattered T-Storms","Scattered T-Storms","Scattered T-Storms","Partly Cloudy","Scattered T-Storms","Partly Cloudy","Scattered T-Storms","Scattered T-Storms","Scattered T-Storms","Scattered T-Storms","Scattered T-Storms","Scattered T-Storms","T-Storms","T-Storms","T-Storms","T-Storms","Scattered T-Storms","Showers"],"wxPhraseShort":["","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""]}]}
-
-const apiRequestLimit = 50; // It's really 100 per minute
-let apiRequestCounter = 0;
-
-export class WeatherCompanyAPI {
-
- defaultOptions: CommonOptions;
-
- baseAPI = "https://api.weather.com/"
-
- constructor(private apiKey = process.env.weather_company_apiKey as string, private unit = Units.metric, private language = Languages.English, private format = Formats.JSON) {
- if (apiKey == undefined) {
- console.error("Weather Company API isn't defined!");
- console.error("Please set it on the 'weather_company_apiKey' env variable or pass it to the constructor");
- throw new Error("Please pass an API key to the Weather API");
- }
-
- this.defaultOptions = {
- format: Formats.JSON,
- language,
- units: unit
- }
-
- // setup API throttler, every minute it resets the apiRequestLimit
- setInterval(() => {
- apiRequestCounter = 0;
- }, 1000 * 60);
- }
-
- GeoCodeToString(geocode: GeoCode) {
- return `${geocode.latitude},${geocode.longitude}`
- }
-
- apiHitCounter() {
- if (apiRequestCounter >= apiRequestLimit) {
- throw new Error("Too many Requests");
- }
- else {
- apiRequestCounter++;
- }
- }
-
- async daily15DayForecast(geocode: GeoCode, commonOptions = this.defaultOptions) {
- const paramOptions = {
- geocode: this.GeoCodeToString(geocode),
- apiKey: this.apiKey
- }
- const queryOptions = Object.assign({}, commonOptions, paramOptions);
-
- this.apiHitCounter()
-
- const response = await axios.get(this.baseAPI + "v3/wx/forecast/daily/15day", {
- params: queryOptions
- });
-
- return response.data;
-
- // return testForecastData;
- }
-}
diff --git a/backend/src/integrations/weatherCompany/WeatherCompanyService.ts b/backend/src/integrations/weatherCompany/WeatherCompanyService.ts
new file mode 100644
index 000000000..787a8b336
--- /dev/null
+++ b/backend/src/integrations/weatherCompany/WeatherCompanyService.ts
@@ -0,0 +1,53 @@
+import axios from "axios";
+import { CommonOptions, DataFormat, GeoCodeNumber, geoCodeToString, Language, Unit, WeatherCompanyConfig } from "common-types";
+
+const apiRequestLimit = 50; // It's really 100 per minute
+let apiRequestCounter = 0;
+
+class WeatherCompanyService {
+
+ defaultOptions: CommonOptions;
+
+ // baseAPI = "https://api.weather.com/"
+
+ constructor() {
+ this.defaultOptions = {
+ format: DataFormat.JSON,
+ language: Language.English,
+ units: Unit.metric
+ }
+
+ // setup API throttler, every minute it resets the apiRequestLimit
+ setInterval(() => {
+ apiRequestCounter = 0;
+ }, 1000 * 60);
+ }
+
+ apiHitCounter() {
+ if (apiRequestCounter >= apiRequestLimit) {
+ throw new Error("Too many Requests");
+ }
+ else {
+ apiRequestCounter++;
+ }
+ }
+
+ async daily15DayForecast(config: WeatherCompanyConfig, geocode: GeoCodeNumber) {
+ const commonOptions = config.options || this.defaultOptions;
+ const paramOptions = {
+ geocode: geoCodeToString(geocode),
+ apiKey: config.apiKey
+ }
+ const queryOptions = Object.assign({}, commonOptions, paramOptions);
+
+ this.apiHitCounter()
+
+ const response = await axios.get(config.apiUrl + "v3/wx/forecast/daily/15day", {
+ params: queryOptions
+ });
+
+ return response.data;
+ }
+}
+
+export const weatherCompanyService: WeatherCompanyService = new WeatherCompanyService();
diff --git a/common-types/Fields.ts b/backend/src/integrations/weatherCompany/weather-company-api.types.ts
similarity index 100%
rename from common-types/Fields.ts
rename to backend/src/integrations/weatherCompany/weather-company-api.types.ts
diff --git a/backend/src/main.ts b/backend/src/main.ts
index 9dea7d38c..440fc7a67 100644
--- a/backend/src/main.ts
+++ b/backend/src/main.ts
@@ -17,14 +17,14 @@ import cropRoutes from "./routes/crop-route";
import dashboardRoutes from "./routes/dashboard-route";
import recommendationsRoutes from "./routes/recommendations-route";
import weatherRoutes from "./routes/weather-route";
-import coopManagerRoutes from "./routes/coopManager-route";
+import userRoutes from "./routes/user-route";
import organisationRoutes from "./routes/organisation-route";
import messageLogRoutes from "./routes/messaging-route";
import smsRoutes from "./routes/sms-route";
-import { formatUser, ensureAuthenticated } from "./auth/helpers";
+import { ensureAuthenticated, formatUser } from "./auth/helpers";
import { IBMidStrategy } from "./auth/IBMiDStrategy";
-import { SocketIOManager, SocketIOManagerInstance } from "./sockets/socket.io";
+import { SocketIOManagerInstance } from "./sockets/socket.io";
import { Server } from "http";
mongoInit();
@@ -118,7 +118,7 @@ app.use("/api/dashboard", dashboardRoutes);
app.use("/api/recommendations", recommendationsRoutes);
app.use("/api/weather", weatherRoutes);
-app.use("/api/coopManager", coopManagerRoutes);
+app.use("/api/coopManager", userRoutes);
app.use("/api/organisation", organisationRoutes);
app.use("/api/messaging", messageLogRoutes);
app.use("/api/sms", smsRoutes);
diff --git a/backend/src/routes/coopManager-route.ts b/backend/src/routes/coopManager-route.ts
deleted file mode 100644
index 4253813bc..000000000
--- a/backend/src/routes/coopManager-route.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-// import dependencies and initialize the express router
-import { Router } from "express";
-import { getOrganisations } from "./../services/organisation.service";
-import { addCoopManagerToOrganisation, doesUserExist, getCoopManager, onBoardUser } from "./../services/coopManager.service";
-
-const router = Router();
-
-// define routes
-router.get(":id", async (req, res) => {
- const id = req.params.id;
- const manager = await getCoopManager(id);
- if (manager !== null) {
- return res.json(manager.toObject());
- }
- else {
- return res.status(404).end();
- }
-});
-
-router.get("/hasBeenOnBoarded", async (req, res) => {
- const prefix = "IBMid:";
- if (req.user === undefined) {
- return res.status(400).send("User hasn't logged in.");
- }
- const id = `${prefix}${req.user.id}`;
- const result = await doesUserExist(id);
- res.json({exists: result});
-});
-
-router.post("/onboard", async (req, res) => {
- if (req.body === undefined) {
- return res.status(400).send("Body is missing");
- }
- if (req.body.oAuthSource === undefined) {
- return res.status(400).send("oAuthSource is missing");
- }
- if (req.body.oAuthId === undefined) {
- return res.status(400).send("oAuthId is missing");
- }
- if (req.body.user === undefined) {
- return res.status(400).send("user (Coop Manager) is missing");
- }
-
- const userDoc = await onBoardUser(req.body.oAuthSource, req.body.oAuthId, req.body.user);
-
- // Set the Organisation variables on the user
- req.user.isOnboarded = true;
- req.user.coopManager = userDoc.toObject();
- req.user.organisations = await getOrganisations(userDoc.coopOrganisations);
- req.user.selectedOrganisation = req.user.organisations[0];
-
- res.json(userDoc.toObject());
-});
-
-router.put("/setCurrentOrganisation", async (req, res) => {
- if (req.body === undefined) {
- return res.status(400).send("Body is missing");
- }
- if (req.body.orgId === undefined) {
- return res.status(400).send("orgId is missing");
- }
- const orgId = req.body.orgId;
- const org = req.user.organisations.find(it => it._id == orgId);
- if (org == undefined) {
- return res.status(400).json("User is not part of organisation");
- }
-
- req.user.selectedOrganisation = org;
-
- res.json(org);
-})
-
-router.put("/:id/addOrganisation", async (req, res) => {
- if (req.body === undefined) {
- return res.status(400).send("Body is missing");
- }
- if (req.body.orgId === undefined) {
- return res.status(400).send("coopManagerId is missing");
- }
- const orgId = req.body.orgId;
- const coopManagerId = req.params.id;
- const coopUser = await addCoopManagerToOrganisation(coopManagerId, orgId);
-
- req.user.coopManager = coopUser.toObject();
- req.user.organisations = await getOrganisations(coopUser.coopOrganisations, true);
-
- res.json(coopUser.toObject());
-});
-
-export default router;
diff --git a/backend/src/routes/crop-route.ts b/backend/src/routes/crop-route.ts
index 0b6ad622a..5fd471e92 100644
--- a/backend/src/routes/crop-route.ts
+++ b/backend/src/routes/crop-route.ts
@@ -1,7 +1,7 @@
import { Request, Response, Router } from "express";
-import CropService from "../services/crop.service";
-var router = Router();
-const cropService = new CropService();
+import { cropService } from "../services/CropService";
+
+const router = Router();
router.get("/", getAllCrops);
diff --git a/backend/src/routes/dashboard-route.ts b/backend/src/routes/dashboard-route.ts
index c5c00f016..d202ed157 100644
--- a/backend/src/routes/dashboard-route.ts
+++ b/backend/src/routes/dashboard-route.ts
@@ -1,7 +1,8 @@
import { Router } from "express";
+import LandAreasService from "../services/land-areas.service";
+
var router = Router();
-import LandAreasService from "../services/land-areas.service";
const lotAreas = new LandAreasService();
//data table
diff --git a/backend/src/routes/farmer-route.ts b/backend/src/routes/farmer-route.ts
index 35ee77091..925cb51ab 100644
--- a/backend/src/routes/farmer-route.ts
+++ b/backend/src/routes/farmer-route.ts
@@ -1,27 +1,18 @@
-import { Router, Request, Response } from "express";
-import { EISField } from "../integrations/EIS/EIS.types";
-import { EISAPIService } from "../integrations/EIS/EIS-api.service";
-import { Farmer, FarmerModel } from "../db/entities/farmer";
-import LandAreasService from "../services/land-areas.service";
+import { Request, Response, Router } from "express";
+import { farmerService } from "../services/FarmerService";
+
+import { Farmer, isUndefined, NewFarmer } from "common-types";
+import { FarmerModel } from "../db/entities/farmer";
+import { farmService } from "../services/FarmService";
// const LotAreaService = require("./../services/lot-areas.service");
// const lotAreas = new LandAreasService();
-const EISKey = process.env.EIS_apiKey;
-
-if (EISKey == undefined) {
- console.error("You must define 'EIS_apiKey' in the environment!");
- process.exit(-1);
-}
-
-const eisAPIService = new EISAPIService(EISKey);
-
const router = Router();
router.get("/", async (req: Request, res: Response) => {
try {
- const docs = await FarmerModel.find().lean().exec();
- res.json(docs);
+ res.json(farmerService.getFarmers());
} catch (e) {
console.error(e);
res.status(500).json(e);
@@ -35,9 +26,7 @@ async function createOrUpdateFarmer(req: Request, res: Response) {
return;
}
try {
- const farmerDoc = new FarmerModel(farmer);
- const updatedDoc = farmerDoc.save();
- res.json(updatedDoc);
+ res.json(farmerService.saveFarmer(farmer));
} catch (e) {
console.error(e);
res.status(500).json(e);
@@ -45,17 +34,8 @@ async function createOrUpdateFarmer(req: Request, res: Response) {
}
-async function getFarmer(id: string) {
- // Aggregate with land areas eventually
- const farmer = await FarmerModel.findById(id).lean().exec();
- if (farmer == null) {
- return null;
- }
-
- // Get Fields
- const field = await eisAPIService.getFarmerField(id);
-
- return farmer;
+async function getFarmer(id: string): Promise {
+ return farmerService.getFarmer(id);
}
router.post("/", createOrUpdateFarmer);
@@ -70,7 +50,7 @@ router.get("/:id", async (req: Request, res: Response) => {
}
try {
- const farmer = getFarmer(id);
+ const farmer = await getFarmer(id);
if (farmer == null) {
res.status(404).end();
}
@@ -92,7 +72,7 @@ router.delete("/:id", async(req: Request, res: Response) => {
return;
}
try {
- const result = await FarmerModel.deleteOne({_id: id});
+ const result = await FarmerModel.findByIdAndDelete(id);
res.json(result);
} catch (e) {
console.error(e);
@@ -100,48 +80,23 @@ router.delete("/:id", async(req: Request, res: Response) => {
}
});
-export interface FarmerAddDTO {
- farmer: Farmer;
- field: EISField
-}
-router.post("/add", async(req: Request, res: Response) => {
- const {farmer, field}: FarmerAddDTO = req.body;
- if (farmer == undefined) {
- res.status(400).send("Farmer not defined");
+
+router.post("/add", async(req: Request<{}, {}, NewFarmer>, res: Response) => {
+ const newFarmer: NewFarmer = req.body;
+ if (isUndefined(newFarmer)) {
+ res.status(400).send("Farmer is not defined");
return;
}
- if (field == undefined) {
- res.status(400).send("Field not defined");
+ if (isUndefined(newFarmer.farms)) {
+ res.status(400).send("Farm is not defined");
return;
}
- // First we'll create the farmer
- const farmerDoc = new FarmerModel(farmer);
- const newFarmer = await farmerDoc.save();
-
- if (newFarmer._id == undefined) {
- throw new Error("Farmer ID is not defined after saving!")
- }
-
- // Then we'll create the Field
-
- // We have to set the farmer ID on the field first
- for (let i = 0; i < field.subFields.length; i++) {
- const properties = field.subFields[i].geo.geojson.features[0].properties;
- properties.open_harvest_farmer_id = newFarmer._id!!.toString();
- properties.open_harvest.farmer_id = newFarmer._id!!.toString();
- }
-
- const createdFieldsUuids = await eisAPIService.createField(field);
- const fieldUuid = createdFieldsUuids.field;
-
- const createdField = await eisAPIService.getField(fieldUuid);
-
- const farmerObj = newFarmer.toObject();
+ const farmer = await farmerService.saveFarmer(newFarmer);
- farmerObj.field = createdField;
+ const farms = await farmService.saveFarms(farmer, newFarmer.farms);
- res.json(farmerObj);
+ res.json(farmer);
});
// // Link Lot
diff --git a/backend/src/routes/lot-route.ts b/backend/src/routes/lot-route.ts
index 7320379f3..aef5e3c07 100644
--- a/backend/src/routes/lot-route.ts
+++ b/backend/src/routes/lot-route.ts
@@ -1,7 +1,8 @@
-import { Router, Request, Response } from "express";
+import { Request, Response, Router } from "express";
+import LandAreasService from "../services/land-areas.service";
+
var router = Router();
-import LandAreasService from "../services/land-areas.service";
const lotAreas = new LandAreasService();
router.get("/", getAllLots);
diff --git a/backend/src/routes/messaging-route.ts b/backend/src/routes/messaging-route.ts
index dd99e5177..bcef68094 100644
--- a/backend/src/routes/messaging-route.ts
+++ b/backend/src/routes/messaging-route.ts
@@ -1,6 +1,6 @@
// import dependencies and initialize the express router
import { Router } from "express";
-import { TwilioInstance } from "./../integrations/twilio/twilio.service";
+import { TwilioAPI } from "../integrations/twilio/twilio.service";
import { MessageLogModel } from "../db/entities/messageLog";
import { FarmerModel } from "../db/entities/farmer";
@@ -22,7 +22,7 @@ router.post("/sendSMSToFarmer", async (req, res) => {
}
try {
- const messageLog = await TwilioInstance.sendMessageToFarmer(farmer, message);
+ const messageLog = await new TwilioAPI().sendMessageToFarmer(farmer, message);
res.json(messageLog)
}
catch (e: any) {
diff --git a/backend/src/routes/organisation-route.ts b/backend/src/routes/organisation-route.ts
index 63f654e5e..222a0a36b 100644
--- a/backend/src/routes/organisation-route.ts
+++ b/backend/src/routes/organisation-route.ts
@@ -1,11 +1,12 @@
// import dependencies and initialize the express router
-import { Router } from "express";
-import { createOrganisationFromName, getAllOrganisations, getOrganisation, getOrganisations } from "./../services/organisation.service";
+import { isDefined, isUndefined, OrganisationDto } from "common-types";
+import { Request, Router } from "express";
+import { organisationService } from "../services/OrganisationService";
const router = Router();
router.get("/", async (req, res) => {
- const orgs = await getAllOrganisations(true);
+ const orgs = await organisationService.getAllOrganisations(true);
// console.log(orgs);
return res.json(orgs);
});
@@ -13,41 +14,33 @@ router.get("/", async (req, res) => {
// define routes
router.get("/:id", async (req, res) => {
const id = req.params.id;
- const org = await getOrganisation(id);
+ const org = await organisationService.getOrganisation(id);
if (org == null) {
return res.sendStatus(404);
}
else {
- return res.json(org.toObject());
+ return res.json(org);
}
});
-router.post("/", async (req, res) => {
- if (req.body === undefined) {
+router.post("/", async (req: Request<{}, {}, OrganisationDto>, res) => {
+ if (isUndefined(req.body)) {
return res.status(400).send("Body is missing");
}
- if (req.body.name === undefined) {
+ if (isUndefined(req.body.name)) {
return res.status(400).send("name is missing");
}
const name = req.body.name;
- console.log("Creating Org:", name);
- const doc = await createOrganisationFromName(name);
- res.json(doc.toObject());
-});
-router.get("/my", async (req, res) => {
- if (req.user == undefined) {
- return res.status(401);
- }
+ const organisation = await organisationService.getOrganisation(name)
- const isOnboarded = req.user.isOnboarded;
- if (!isOnboarded) {
- return res.status(400).json({error: "user is not onboarded"});
+ if (isDefined(organisation)) {
+ return res.status(409).send("Organisation already exists: " + name);
}
-
- // Get the organisations of the user
- const orgs = await getOrganisations(req.user, true);
- return orgs;
+ console.log("Creating Org:", name);
+ const doc = await organisationService.createOrganisation(req.body);
+ res.json(doc);
});
+
export default router;
diff --git a/backend/src/routes/recommendations-route.ts b/backend/src/routes/recommendations-route.ts
index 11554a5cf..9ba82126c 100644
--- a/backend/src/routes/recommendations-route.ts
+++ b/backend/src/routes/recommendations-route.ts
@@ -1,7 +1,8 @@
import { Router } from "express";
+import RecommendationsService from "../services/recommendations.service";
+
var router = Router();
-import RecommendationsService from "../services/recommendations.service";
const recommendationsService = new RecommendationsService();
router.post("/", async(req, res) => {
try {
diff --git a/backend/src/routes/sms-route.ts b/backend/src/routes/sms-route.ts
index 89ba26a9f..0997038bd 100644
--- a/backend/src/routes/sms-route.ts
+++ b/backend/src/routes/sms-route.ts
@@ -4,9 +4,8 @@
import { Router } from "express";
-import { MessageLogModel } from "../db/entities/messageLog";
-import { SMSSyncAPIInstance, SMSSyncMessageReceivedFormat } from "./../integrations/smsSync/smsSync.service";
-import { TwilioInstance, TwilioMessage } from "../integrations/twilio/twilio.service";
+import { SMSSyncAPIInstance, SMSSyncMessageReceivedFormat } from "../integrations/smsSync/smsSync.service";
+import { TwilioAPI, TwilioMessage } from "../integrations/twilio/twilio.service";
const router = Router();
@@ -53,7 +52,7 @@ router.post("/twilio-sms-incoming", async (req, res) => {
// console.log("Twilio Message:", req.body);
const message: TwilioMessage = req.body;
- TwilioInstance.onReceivedMessage(message);
+ new TwilioAPI().onReceivedMessage(message);
res.status(200).end();
});
diff --git a/backend/src/routes/user-route.ts b/backend/src/routes/user-route.ts
new file mode 100644
index 000000000..805463ed9
--- /dev/null
+++ b/backend/src/routes/user-route.ts
@@ -0,0 +1,52 @@
+import { Request, Router } from "express";
+import { organisationService } from "../services/OrganisationService";
+import { userService } from "../services/UserService";
+import { isUndefined, OrganisationDto, UserDto } from "common-types";
+
+const router = Router();
+
+// define routes
+router.get(":id", async (req, res) => {
+ const id = req.params.id;
+ const users = await userService.getUser(id);
+ if (isUndefined(users) || users.length === 0) {
+ return res.status(404).end();
+ }
+
+ return res.json(users);
+});
+
+router.post("/hasBeenOnBoarded", async (req: Request<{}, {}, UserDto>, res) => {
+ if (req.body === undefined) {
+ return res.status(400).send("User hasn't logged in.");
+ }
+ const result = await userService.doesUserExist(req.body);
+ res.json({exists: result});
+});
+
+router.post("/onboard", async (req: Request<{}, {}, UserDto>, res) => {
+ let userDto = req.body;
+ if (userDto === undefined) {
+ return res.status(400).send("Body is missing");
+ }
+ if (userDto.organisation === undefined) {
+ return res.status(400).send("organisation is missing");
+ }
+
+
+ const userDoc = await userService.onBoardUser(userDto);
+ res.json(userDoc);
+});
+
+
+router.put("/addOrganisation", async (req: Request<{}, {}, OrganisationDto>, res) => {
+ if (req.body === undefined) {
+ return res.status(400).send("Body is missing");
+ }
+
+ const org = await organisationService.createOrganisation(req.body);
+
+ res.json(org);
+});
+
+export default router;
diff --git a/backend/src/routes/weather-route.ts b/backend/src/routes/weather-route.ts
index 29dacd0b4..138d98b00 100644
--- a/backend/src/routes/weather-route.ts
+++ b/backend/src/routes/weather-route.ts
@@ -1,24 +1,34 @@
// import dependencies and initialize the express router
-import { Router } from "express";
-import { GeoCode } from "integrations/weather-company-api.types";
-import { WeatherCompanyAPI } from "./../integrations/weather-company-api.service";
+import { Request, Router } from "express";
+import { isUndefined, toGroCodeFromPoint, UserDto } from "common-types";
+import { weatherCompanyService } from "../integrations/weatherCompany/WeatherCompanyService";
+import { organisationService } from "../services/OrganisationService";
const router = Router();
-const api = new WeatherCompanyAPI();
-
-const testMchinjiMalawiCoords: GeoCode = {
- latitude: -13.7971726,
- longitude: 32.8874963
-}
+// const testMchinjiMalawiCoords: GeoCode = {
+// latitude: -13.7971726,
+// longitude: 32.8874963
+// }
/**
* Gets the forecast for a farmer. Right now this is hardcoded while we wait for farmer data from the session
*/
-router.get("/farmerForecast", async (req, res) => {
+router.post("/farmerForecast", async (req: Request<{}, {}, UserDto>, res) => {
console.log("farmerForecast");
- const geocode = testMchinjiMalawiCoords;
- const forecast = await api.daily15DayForecast(geocode);
+
+ const userDto = req.body;
+ const organisation = await organisationService.getOrganisation(userDto.organisation);
+
+ if (isUndefined(organisation)) {
+ throw new Error("Organisation does not exist: " + userDto.organisation)
+ }
+
+ if (isUndefined(organisation.weatherCompanyConfig)) {
+ return res.status(400).send( "User's organisation is not configured to use weather company");
+ }
+
+ const forecast = await weatherCompanyService.daily15DayForecast(organisation.weatherCompanyConfig, toGroCodeFromPoint(userDto.location));
// console.log(forecast);
res.json(forecast);
});
diff --git a/backend/src/services/crop.service.ts b/backend/src/services/CropService.ts
similarity index 70%
rename from backend/src/services/crop.service.ts
rename to backend/src/services/CropService.ts
index d2e49a25f..b7e623a64 100644
--- a/backend/src/services/crop.service.ts
+++ b/backend/src/services/CropService.ts
@@ -2,15 +2,10 @@
// const {cropDetailsView} = require("../db/cloudant");
// const {cropDetailsDdoc} = require("../db/cloudant");
-import { CropModel, Crop } from "../db/entities/crop";
+import { Crop } from "common-types";
+import { CropModel } from "../db/entities/crop";
-// const APPLICATION_DB = "application-db";
-// const db = APPLICATION_DB;
-
-// const LOT_DB = "lot-areas";
-let cropDetails;
-
-export default class CropService {
+export class CropService {
constructor() {
}
@@ -36,4 +31,4 @@ export default class CropService {
}
}
-// module.exports = CropService;
+export const cropService = new CropService();
diff --git a/backend/src/services/FarmService.ts b/backend/src/services/FarmService.ts
new file mode 100644
index 000000000..07e3902c9
--- /dev/null
+++ b/backend/src/services/FarmService.ts
@@ -0,0 +1,64 @@
+import { EISConfig, Farm, Farmer, isDefined, isUndefined, NewFarm, Organisation } from "common-types";
+import { FarmModel } from "../db/entities/farm";
+import { eisFarmService } from "../integrations/EIS/EISFarmService";
+import { organisationService } from "./OrganisationService";
+
+export interface IFarmService {
+ getFarmerFarms(farmer: Farmer): Promise;
+ saveFarm(newFarm: NewFarm): Promise;
+ saveFarms(farmer: Farmer, newFarms: NewFarm[]): Promise;
+}
+
+class FarmService implements IFarmService {
+
+ async getFarmerFarms(farmer: Farmer): Promise {
+ const eisConfig = await FarmService.getEISConfig(farmer.organisation);
+
+ if (isDefined(eisConfig)) {
+ return await eisFarmService.getFarmerFarms(eisConfig, farmer);
+ }
+
+ return await FarmModel.find({"farmer._id": farmer._id}).exec() as Farm[];
+ }
+
+ async saveFarm(newFarm: NewFarm): Promise {
+
+ const eisConfig = await FarmService.getEISConfig(newFarm.farmer.organisation);
+
+ if (isDefined(eisConfig)) {
+ return await eisFarmService.saveFarm(eisConfig, newFarm);
+ }
+
+ const farmDoc = new FarmModel(newFarm);
+ return await farmDoc.save() as Farm;
+ }
+
+ async saveFarms(farmer: Farmer, newFarms: NewFarm[]): Promise {
+ const eisConfig = await FarmService.getEISConfig(farmer.organisation);
+ const farms: Farm[] = [];
+ for (const newFarm of newFarms) {
+ newFarm.farmer = farmer;
+ let farm: Farm;
+ if (isDefined(eisConfig)) {
+ farm = await eisFarmService.saveFarm(eisConfig, newFarm);
+ } else {
+ farm = await this.saveFarm(newFarm) as Farm;
+ }
+ farms.push(farm);
+ }
+
+ return farms;
+ }
+
+ private static async getEISConfig(org: string): Promise {
+ let organisation: Organisation | null = await organisationService.getOrganisation(org);
+ if (isUndefined(organisation)) {
+ throw new Error("Organisation does not exist: " + org)
+ }
+ return organisation.eisConfig;
+ }
+
+}
+
+export const farmService = new FarmService();
+
diff --git a/backend/src/services/FarmerService.ts b/backend/src/services/FarmerService.ts
new file mode 100644
index 000000000..73d55c921
--- /dev/null
+++ b/backend/src/services/FarmerService.ts
@@ -0,0 +1,32 @@
+import { FarmerModel } from "../db/entities/farmer";
+import { Farmer, NewFarmer } from "common-types";
+import { farmService } from "./FarmService";
+
+class FarmerService {
+ async getFarmers(): Promise {
+ return await FarmerModel.find().lean().exec();
+ }
+
+ async saveFarmer(newFarmer: NewFarmer): Promise {
+ const farmerDoc = new FarmerModel(newFarmer);
+ return await farmerDoc.save();
+ }
+
+ async getFarmer(id: string): Promise {
+ const farmer = await FarmerModel.findById(id).lean().exec();
+ if (farmer == null) {
+ return null;
+ }
+
+ // Get Fields
+ const farms = await farmService.getFarmerFarms(farmer);
+
+ return {
+ ...farmer,
+ farms
+ };
+ }
+}
+
+
+export const farmerService = new FarmerService()
diff --git a/backend/src/services/OrganisationService.ts b/backend/src/services/OrganisationService.ts
new file mode 100644
index 000000000..dbe0a5a6c
--- /dev/null
+++ b/backend/src/services/OrganisationService.ts
@@ -0,0 +1,63 @@
+import { isDefined, isUndefined, Organisation, OrganisationDto, User, UserDto } from "common-types";
+import { OrganisationModel } from "../db/entities/organisation";
+import { createToUserDto } from "./UserService";
+
+
+class OrganisationService {
+
+ async getOrganisationsByUserId(userId: string): Promise {
+ return await OrganisationModel.find({
+ "users._id": userId
+ }
+ ).exec();
+ };
+
+ async getAllOrganisations(lean = true): Promise {
+ if (lean)
+ return OrganisationModel.find({}).lean();
+ else
+ return OrganisationModel.find({});
+ }
+
+ async getOrganisation(id: string): Promise {
+ return OrganisationModel.findById(id);
+ }
+
+ async createOrganisation(org: OrganisationDto): Promise {
+ const organisation = new OrganisationModel();
+
+ organisation.authMethod = org.authMethod;
+ organisation.users = org.users.map(userDto => {
+ const user: User = {
+ location: userDto.location,
+ mobile: userDto.mobile
+ }
+ return user;
+ });
+ return organisation.save();
+ }
+
+ async addUserToOrganisation(userDto: UserDto): Promise {
+ const org = await OrganisationModel.findById(userDto.organisation)
+ if (isUndefined(org)) {
+ throw new Error("Organisation does not exist: " + userDto.organisation);
+ }
+
+ let orgUser = org.users.find(orgUser => orgUser._id === user._id);
+ if (isDefined(orgUser)) {
+ return createToUserDto(orgUser, org);
+ }
+
+ const user: User = {
+ location: userDto.location,
+ _id: `${org.authMethod}:${userDto.id}`,
+ mobile: userDto.mobile
+ }
+
+ org.users.push(user);
+ await org.save();
+ return createToUserDto(user, org);
+ }
+}
+
+export const organisationService = new OrganisationService();
diff --git a/backend/src/services/UserService.ts b/backend/src/services/UserService.ts
new file mode 100644
index 000000000..4832a20ba
--- /dev/null
+++ b/backend/src/services/UserService.ts
@@ -0,0 +1,54 @@
+import { isUndefined, NewUserDto, Organisation, User, UserDto } from "common-types";
+import { organisationService } from "./OrganisationService";
+
+
+class UserService {
+
+ async getUser(userId: string): Promise {
+ const orgs: Organisation[] = await organisationService.getOrganisationsByUserId(userId);
+
+ if (isUndefined(orgs) || orgs.length === 0) {
+ return [];
+ }
+
+ const userDtos: UserDto[] = [];
+
+ orgs.forEach(org => {
+ org.users.filter(user => user._id === userId).map(user => {
+ userDtos.push(createToUserDto(user, org));
+ })
+ });
+
+ return userDtos;
+ }
+
+ /**
+ * Explicit use of the word user here because it's the OAuth User we're talking about.
+ * provider + id
+ * e.g. "IBMid:1SD54A1"
+ */
+ async doesUserExist(userDto: UserDto): Promise {
+ return (await this.getUser(userDto.id)) != null
+ }
+
+ async onBoardUser(userDto: NewUserDto): Promise {
+ // Check if the organisation exists
+ return await organisationService.addUserToOrganisation(userDto);
+
+ }
+}
+
+export const userService = new UserService();
+
+export function createToUserDto(user: User, org: Organisation): UserDto {
+ if (isUndefined(user._id)) {
+ throw new Error("User does not have id.")
+ }
+ return {
+ email: "",
+ location: user.location,
+ mobile: user.mobile,
+ organisation: org.name,
+ id: user._id
+ };
+}
diff --git a/backend/src/services/coopManager.service.ts b/backend/src/services/coopManager.service.ts
deleted file mode 100644
index befbd436b..000000000
--- a/backend/src/services/coopManager.service.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import { CoopManagerModel, CoopManager } from "./../db/entities/coopManager";
-import { OrganisationModel, Organisation } from "./../db/entities/organisation";
-
-export function getCoopManager(id: string) {
- return CoopManagerModel.findById(id);
-}
-
-/**
- * Explicit use of the word user here because it's the OAuth User we're talking about.
- * provider + id
- * e.g. "IBMid:1SD54A1"
- */
-export async function doesUserExist(id: string) {
- const manager = await CoopManagerModel.findById(id);
- return manager !== null;
-}
-
-export async function onBoardUser(oAuthSource: string, oAuthId: string, user: CoopManager) {
- // Check if the organisation exists
- const orgs = await OrganisationModel.find({
- _id: {
- $in: [user.coopOrganisations]
- }
- });
- if (orgs.length !== user.coopOrganisations.length) {
- throw new Error("Organisation's given weren't found!");
- }
- const newID = `${oAuthSource}:${oAuthId}`
- user._id = newID;
- const userDoc = await CoopManagerModel.create(user);
- return userDoc;
-}
-
-export async function addCoopManagerToOrganisation(coopManagerId: string, orgId: string) {
- // Check if the Coop Manager exists
- const coopManager = await CoopManagerModel.findById(coopManagerId);
- if (coopManager == null) {
- throw new Error("Coop Manager doesn't exist!");
- }
- const org = await OrganisationModel.findById(orgId);
- if (org == null) {
- throw new Error("Organisation doesn't exist!");
- }
-
- if (coopManager.coopOrganisations.includes(coopManagerId)) {
- return coopManager;
- }
- else {
- coopManager.coopOrganisations.push(orgId);
- const newCoopManager = await coopManager.save();
- return newCoopManager;
- }
-}
-
-// module.exports = CropService;
diff --git a/backend/src/services/land-areas.service.ts b/backend/src/services/land-areas.service.ts
index 17533015c..50ea97e0d 100644
--- a/backend/src/services/land-areas.service.ts
+++ b/backend/src/services/land-areas.service.ts
@@ -1,6 +1,4 @@
-import { Land, LandModel } from "../db/entities/land";
-import { Types } from 'mongoose';
-import { FarmerModel } from "./../db/entities/farmer";
+// import { Land, LandModel } from "../db/entities/land";
// const nswBbox = "140.965576,-37.614231,154.687500,-28.071980"; // lng lat
// const nswBboxLatLng = "-37.614231,140.965576,-28.071980,154.687500"; // lat lng
@@ -17,24 +15,24 @@ export interface BoundingBox {
export default class LandAreasService {
constructor() {}
- async updateLot(lot: Land) {
- const landModel = new LandModel(lot);
-
- const savedDoc = await landModel.save();
- return savedDoc;
- }
-
- getLot(id: string) {
- return LandModel.findById(id);
- }
-
- getLots(ids: string[]) {
- return LandModel.find({ '_id': { $in: ids } });
- }
-
- getAllLots() {
- return LandModel.find();
- }
+ // async updateLot(lot: Land) {
+ // const landModel = new LandModel(lot);
+ //
+ // const savedDoc = await landModel.save();
+ // return savedDoc;
+ // }
+
+ // getLot(id: string) {
+ // return LandModel.findById(id);
+ // }
+ //
+ // getLots(ids: string[]) {
+ // return LandModel.find({ '_id': { $in: ids } });
+ // }
+ //
+ // getAllLots() {
+ // return LandModel.find();
+ // }
getAreasInBbox(box: BoundingBox) {
// const bbox = `${box.lowerLeft.lng},${box.lowerLeft.lat},${box.upperRight.lng},${box.upperRight.lat}`;
@@ -162,14 +160,10 @@ export default class LandAreasService {
return 0;
}
- getTotalFarmers() {
- //return this.getViewValue(farmerCountDoc, farmerCountView, APPLICATION_DB);
- return FarmerModel.count().exec();
- }
getCropsPlanted() {
// return this.getViewValue(cropsPlantedDoc, cropsPlantedView, LOT_DB);
-
+
// Aggregate of crops planted
return 0;
}
@@ -181,9 +175,9 @@ export default class LandAreasService {
return 0;
}
- getTotalLots() {
- return LandModel.count().exec();
- }
+ // getTotalLots() {
+ // return LandModel.count().exec();
+ // }
}
// module.exports = LotAreas;
diff --git a/backend/src/services/organisation.service.ts b/backend/src/services/organisation.service.ts
deleted file mode 100644
index ab9dd6d22..000000000
--- a/backend/src/services/organisation.service.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { OrganisationModel, Organisation } from "./../db/entities/organisation";
-import { CoopManagerModel, CoopManager } from "./../db/entities/coopManager";
-
-export function getAllOrganisations(lean = true) {
- if (lean)
- return OrganisationModel.find({}).lean();
- else
- return OrganisationModel.find({});
-}
-
-export function getOrganisation(id: string) {
- return OrganisationModel.findById(id);
-}
-
-export function getOrganisations(id: string[], lean = true) {
- const query = OrganisationModel.find({_id: {$in: id}});
- return lean ? query.lean() : query;
-}
-
-export async function createOrganisationFromName(name: string) {
- const orgModel = new OrganisationModel();
- orgModel.name = name;
- // console.log(orgModel);
- const orgDoc = orgModel.save();
- return orgDoc;
-}
-
-export function createOrganisation(org: Organisation) {
- return OrganisationModel.create(org);
-}
-
-// module.exports = CropService;
diff --git a/backend/src/services/recommendations.service.ts b/backend/src/services/recommendations.service.ts
index 379aa4069..03f9c550d 100644
--- a/backend/src/services/recommendations.service.ts
+++ b/backend/src/services/recommendations.service.ts
@@ -3,8 +3,7 @@
// const client = CloudantV1.newInstance({});
// const { plantedCrops, cropProductionForecast} = require("../db/cloudant");
-import LandAreasService from "./land-areas.service";
-import CropService from "./crop.service";
+import { cropService } from "./CropService";
// const nswBbox = "140.965576,-37.614231,154.687500,-28.071980"; // lng lat
// const nswBboxLatLng = "-37.614231,140.965576,-28.071980,154.687500"; // lat lng
@@ -22,17 +21,9 @@ export interface RecommendationsRequest {
}
export default class RecommendationsService {
- lotAreaService: LandAreasService;
- cropService: CropService;
-
- constructor() {
- this.lotAreaService = new LandAreasService();
- this.cropService = new CropService();
- }
-
async getRecommendations(request: RecommendationsRequest) {
this.createOrUpdateShortlistForLot(request);
- const cropDetails = await this.cropService.getAllCrops();
+ const cropDetails = await cropService.getAllCrops();
const plantDate = new Date(request.plantDate);
const plantMonth = plantDate.getMonth() + 1;
@@ -68,41 +59,41 @@ export default class RecommendationsService {
crops[crop.toLowerCase()].shortlist = 100;
});
- const overallCropDistribution: any = await this.lotAreaService.getOverallCropDistribution();
- const minArea = Math.min(...overallCropDistribution.map(dist => dist.area));
-
- overallCropDistribution.forEach((dist) => {
- if (crops[dist.crop.toLowerCase()]) {
- crops[dist.crop.toLowerCase()].area = dist.area;
- }
- });
-
- const cropProductionForecast: any = await this.lotAreaService.getCropProductionForecast();
- cropProductionForecast.forEach((dist) => {
- const harvestDate = new Date(dist.date);
- const crop = crops[dist.crop.toLowerCase()];
- if (harvestDate <= crop.harvestEnd && harvestDate >= crop.harvestStart) {
- crop.yield += dist.yield;
- }
- });
- const minYield = Math.min(...cropDetails.map(crop => crops[crop.name.toLowerCase()].yield));
+ // const overallCropDistribution: any = await this.lotAreaService.getOverallCropDistribution();
+ // const minArea = Math.min(...overallCropDistribution.map(dist => dist.area));
+ //
+ // overallCropDistribution.forEach((dist) => {
+ // if (crops[dist.crop.toLowerCase()]) {
+ // crops[dist.crop.toLowerCase()].area = dist.area;
+ // }
+ // });
+ //
+ // const cropProductionForecast: any = await this.lotAreaService.getCropProductionForecast();
+ // cropProductionForecast.forEach((dist) => {
+ // const harvestDate = new Date(dist.date);
+ // const crop = crops[dist.crop.toLowerCase()];
+ // if (harvestDate <= crop.harvestEnd && harvestDate >= crop.harvestStart) {
+ // crop.yield += dist.yield;
+ // }
+ // });
+ // const minYield = Math.min(...cropDetails.map(crop => crops[crop.name.toLowerCase()].yield));
const cropScores: any = [];
- cropDetails.forEach((cropDetail) => {
- const crop = crops[cropDetail.name.toLowerCase()];
- const cropScore: any = {};
- cropScore.crop = cropDetail.name;
- cropScore.shortlistScore = crop.shortlist / 100 * weights.onShortlist;
- cropScore.inSeasonScore = crop.inSeason / 100 * weights.inSeason;
- cropScore.plantedAreaScore = crop.area === 0 ? 0 : (minArea / crop.area * weights.lowPlantedArea);
- cropScore.yieldForecastScore = crop.yield === 0 ? 0 : (minYield / crop.yield * weights.lowYieldForecast);
- cropScore.score = 10 * (cropScore.shortlistScore +
- cropScore.inSeasonScore +
- cropScore.plantedAreaScore +
- cropScore.yieldForecastScore);
- cropScores.push(cropScore);
- });
+ // cropDetails.forEach((cropDetail) => {
+ // const crop = crops[cropDetail.name.toLowerCase()];
+ // const cropScore: any = {};
+ // cropScore.crop = cropDetail.name;
+ // cropScore.shortlistScore = crop.shortlist / 100 * weights.onShortlist;
+ // cropScore.inSeasonScore = crop.inSeason / 100 * weights.inSeason;
+ // cropScore.plantedAreaScore = crop.area === 0 ? 0 : (minArea / crop.area * weights.lowPlantedArea);
+ // cropScore.yieldForecastScore = crop.yield === 0 ? 0 : (minYield / crop.yield * weights.lowYieldForecast);
+ // cropScore.score = 10 * (cropScore.shortlistScore +
+ // cropScore.inSeasonScore +
+ // cropScore.plantedAreaScore +
+ // cropScore.yieldForecastScore);
+ // cropScores.push(cropScore);
+ // });
return cropScores.sort((a, b) => b.score - a.score);
}
diff --git a/backend/src/sockets/socket.io.ts b/backend/src/sockets/socket.io.ts
index c221c15d2..04b66e504 100644
--- a/backend/src/sockets/socket.io.ts
+++ b/backend/src/sockets/socket.io.ts
@@ -1,7 +1,7 @@
-import { MessageLog } from "./../db/entities/messageLog";
+import { MessageLog } from "../db/entities/messageLog";
import { Server as NodejsServer } from "http";
import { Namespace, Server } from "socket.io";
-import { Organisation } from "./../db/entities/organisation";
+import { Organisation } from "common-types";
// interface ServerToClientEvents {
@@ -67,7 +67,7 @@ export class SocketIOManager {
getNamespaceOfOrg(org: Organisation) {
this.ensureInitialised()
- return this.ioServer.of(`/org-${org._id}`);
+ return this.ioServer.of(`/org-${org.name}`);
}
publishMessage(org: Organisation, message: MessageLog) {
@@ -77,7 +77,7 @@ export class SocketIOManager {
publish(org: Organisation, event: string, ...args: any) {
this.ensureInitialised()
- console.log("[Socket IO Server] Publishing Event. Namespace:", `/org-${org._id}`, "Event:", event, "Args", ...args);
+ console.log("[Socket IO Server] Publishing Event. Namespace:", `/org-${org.name}`, "Event:", event, "Args", ...args);
const nsp = this.getNamespaceOfOrg(org);
nsp.emit(event, ...args);
diff --git a/backend/src/types.d.ts b/backend/src/types.d.ts
index 5bbcea49c..12f024eea 100644
--- a/backend/src/types.d.ts
+++ b/backend/src/types.d.ts
@@ -1,5 +1,3 @@
-import * as express from "express"
-
declare global {
namespace Express {
interface Request {
@@ -7,4 +5,4 @@ declare global {
session: any
}
}
-}
\ No newline at end of file
+}
diff --git a/common-types/data-model/Farm.ts b/common-types/data-model/Farm.ts
new file mode 100644
index 000000000..a186a90d8
--- /dev/null
+++ b/common-types/data-model/Farm.ts
@@ -0,0 +1,18 @@
+import Farmer from "./Farmer";
+import { Polygon } from "geojson";
+import Field, { NewField } from "./Field";
+
+export interface NewFarm {
+ _id?: string,
+ name: string;
+ farmer: Farmer;
+ fields: NewField[];
+ geoShape?: Polygon;
+}
+
+export default interface Farm extends NewFarm {
+ _id: string,
+ fields: Field[]
+}
+
+
diff --git a/common-types/data-model/Field.ts b/common-types/data-model/Field.ts
new file mode 100644
index 000000000..2dc4b6cec
--- /dev/null
+++ b/common-types/data-model/Field.ts
@@ -0,0 +1,15 @@
+import { Polygon } from "geojson";
+import FieldCrop, { NewFieldCrop } from "./FieldCrop";
+
+
+export interface NewField {
+ _id?: string,
+ name: string;
+ geoShape: Polygon;
+ crops: NewFieldCrop[]
+}
+
+export default interface Field extends NewField {
+ _id: string,
+ crops: FieldCrop[]
+}
diff --git a/common-types/data-model/FieldCrop.ts b/common-types/data-model/FieldCrop.ts
new file mode 100644
index 000000000..ebd17ad39
--- /dev/null
+++ b/common-types/data-model/FieldCrop.ts
@@ -0,0 +1,7 @@
+import Crop from "./Crop";
+
+export default interface FieldCrop {
+ crop: Crop,
+ planted_date: Date,
+ harvested_date?: Date
+}
diff --git a/common-types/data-model/coopManager.ts b/common-types/data-model/User.ts
similarity index 59%
rename from common-types/data-model/coopManager.ts
rename to common-types/data-model/User.ts
index 5824cfca4..30d2d3a06 100644
--- a/common-types/data-model/coopManager.ts
+++ b/common-types/data-model/User.ts
@@ -1,5 +1,6 @@
+import { Point } from "geojson";
-export interface CoopManager {
+export default interface User {
/**
* Auth provider + auth provider id. E.g. "IBMid:1SDAS61W6A"
*/
@@ -7,7 +8,8 @@ export interface CoopManager {
/**
* GeoCode / LatLng coordinate tuple
*/
- location: number[],
- mobile: string,
- coopOrganisations: string[]
+ location: Point,
+ mobile: string
}
+
+
diff --git a/common-types/data-model/crop.ts b/common-types/data-model/crop.ts
index f83f9d559..9bb3894b8 100644
--- a/common-types/data-model/crop.ts
+++ b/common-types/data-model/crop.ts
@@ -1,8 +1,9 @@
-export interface Crop {
+export default interface Crop {
_id?: string,
type: string,
name: string,
- planting_season: Date[],
+ planting_season: number[],
time_to_harvest: number,
+ yield_per_sqm: number,
is_ongoing: boolean
}
diff --git a/common-types/data-model/farmer.ts b/common-types/data-model/farmer.ts
index d411c96f2..f415406d9 100644
--- a/common-types/data-model/farmer.ts
+++ b/common-types/data-model/farmer.ts
@@ -1,23 +1,15 @@
+import Farm, { NewFarm } from "./Farm";
-
-export interface Farmer {
- _id?: Types.ObjectId,
+export interface NewFarmer {
+ _id?: string,
name: string,
mobile: string[],
- coopOrganisations: string[]
- land_ids: string[]
- lands?: Land[]
+ address: string,
+ organisation: string,
+ farms: NewFarm[]
}
-export const FarmerSchema = new Schema({
- _id: {
- type: ObjectId,
- auto: true
- },
- name: String,
- mobile: [String],
- coopOrganisations: [String],
- land_ids: [ObjectId]
-});
-
-export const FarmerModel = model("farmer", FarmerSchema);
\ No newline at end of file
+export default interface Farmer extends NewFarmer{
+ _id: string,
+ farms: Farm[]
+}
diff --git a/common-types/data-model/land.ts b/common-types/data-model/land.ts
deleted file mode 100644
index b2a1e2474..000000000
--- a/common-types/data-model/land.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-// Using Node.js `require()`
-import { Schema, Model, model, Types } from 'mongoose';
-import { CropSchema, Crop } from "./crop";
-import { FarmerSchema, Farmer } from './farmer';
-
-const ObjectId = Schema.Types.ObjectId;
-
-export interface FarmerCrop {
- _id?: Types.ObjectId,
- farmer: Farmer,
- crop: Crop
-}
-
-export const FarmerCropSchema = new Schema({
- _id: ObjectId,
- farmer: FarmerSchema,
- crop: CropSchema
-});
-
-export interface Land {
- _id?: Types.ObjectId,
- type: string,
- fid: number,
- name: string,
- crops: FarmerCrop[]
-}
-
-export const LandSchema = new Schema({
- _id: ObjectId,
- type: String,
- fid: Number,
- name: String,
- crops: [FarmerCropSchema]
-});
-
-export const LandModel = model("land", LandSchema);
\ No newline at end of file
diff --git a/common-types/data-model/organisation.ts b/common-types/data-model/organisation.ts
index 0ec005eb2..f07a48021 100644
--- a/common-types/data-model/organisation.ts
+++ b/common-types/data-model/organisation.ts
@@ -1,20 +1,11 @@
-
-import { Schema, model, ObjectId, Types } from 'mongoose';
-import { Land } from './land';
-
-const ObjectId = Schema.Types.ObjectId;
-
-export interface Organisation {
- _id?: Types.ObjectId,
- name: string
+import User from "./User";
+import { AuthMethod } from "../globals";
+import { EISConfig, WeatherCompanyConfig } from "../types";
+
+export default interface Organisation {
+ name: string,
+ authMethod: AuthMethod,
+ users: User[],
+ eisConfig?: EISConfig,
+ weatherCompanyConfig?: WeatherCompanyConfig
}
-
-export const OrganisationSchema = new Schema({
- _id: {
- type: ObjectId,
- auto: true
- },
- name: String,
-});
-
-export const OrganisationModel = model("organisation", OrganisationSchema);
diff --git a/common-types/dto/OrganisationDto.ts b/common-types/dto/OrganisationDto.ts
new file mode 100644
index 000000000..d01b902aa
--- /dev/null
+++ b/common-types/dto/OrganisationDto.ts
@@ -0,0 +1,8 @@
+import { UserDto } from "./UserDto";
+import { AuthMethod } from "../globals";
+
+export default interface OrganisationDto {
+ name: string;
+ authMethod: AuthMethod;
+ users: Omit[]
+}
diff --git a/common-types/dto/UserDto.ts b/common-types/dto/UserDto.ts
new file mode 100644
index 000000000..9b09d3307
--- /dev/null
+++ b/common-types/dto/UserDto.ts
@@ -0,0 +1,14 @@
+import { Point } from "geojson";
+
+export type UserDto = {
+ location: Point;
+ id: string;
+ email: string;
+ organisation: string,
+ mobile: string
+}
+
+export type NewUserDto = UserDto & {
+ id?: string
+}
+
diff --git a/backend/src/integrations/weather-company-api.types.ts b/common-types/globals.ts
similarity index 88%
rename from backend/src/integrations/weather-company-api.types.ts
rename to common-types/globals.ts
index 61aea2c65..a35341e78 100644
--- a/backend/src/integrations/weather-company-api.types.ts
+++ b/common-types/globals.ts
@@ -1,4 +1,4 @@
-export enum Languages {
+export enum Language {
Amharic_Ethiopia = "am-ET",
Arabic_United_Arab_Emirates = "ar-AE",
Azerbaijani_Azerbaijan = "az-AZ",
@@ -92,7 +92,7 @@ export enum Languages {
/**
* Measurement units to return from the API
*/
-export enum Units {
+export enum Unit {
imperial = "e",
metric = "m",
/**
@@ -101,28 +101,23 @@ export enum Units {
SI = "s"
}
-/**
- * LatLng representation for the Weather Company
- */
-export interface GeoCode {
- latitude: number;
- longitude: number;
-}
-
/**
* Result format of the API Call
* NOTE: Not every api call supports CSV.
*/
-export enum Formats {
+export enum DataFormat {
JSON = "json",
CSV = "csv"
}
-export interface CommonOptions {
- format: Formats;
- language: Languages;
- units: Units
-}
+export const isDefined = (value: T | null | undefined): value is T => {
+ return value !== undefined && value != null;
+};
-export type LatLng = GeoCode;
+export const isUndefined = (value: T | null | undefined): value is undefined | null => {
+ return value === undefined || value == null;
+};
+export enum AuthMethod {
+ IBM_ID = "IBM_ID"
+}
diff --git a/common-types/integrations/EISConfig.ts b/common-types/integrations/EISConfig.ts
new file mode 100644
index 000000000..f236e132f
--- /dev/null
+++ b/common-types/integrations/EISConfig.ts
@@ -0,0 +1,6 @@
+export type EISConfig = {
+ apiUrl: string;
+ tokenUrl: string;
+ apiKey: string;
+ clientId: string;
+}
diff --git a/common-types/integrations/WeatherCompanyConfig.ts b/common-types/integrations/WeatherCompanyConfig.ts
new file mode 100644
index 000000000..f276c4d29
--- /dev/null
+++ b/common-types/integrations/WeatherCompanyConfig.ts
@@ -0,0 +1,7 @@
+import { CommonOptions } from "../types";
+
+export type WeatherCompanyConfig = {
+ apiUrl: string;
+ apiKey: string;
+ options?: CommonOptions
+}
diff --git a/common-types/package.json b/common-types/package.json
new file mode 100644
index 000000000..32e92b4c2
--- /dev/null
+++ b/common-types/package.json
@@ -0,0 +1,232 @@
+{
+ "name": "common-types",
+ "version": "1.0.0",
+ "types": "types.d.ts",
+ "dependencies": {
+ "@types/geojson": "^7946.0.8"
+ },
+ "eslintConfig": {
+ "overrides": [
+ {
+ "files": [
+ "**/*.ts?(x)"
+ ],
+ "extends": [
+ "plugin:react/recommended"
+ ],
+ "plugins": [
+ "eslint-plugin-prefer-arrow",
+ "eslint-plugin-react",
+ "@typescript-eslint"
+ ],
+ "rules": {
+ "@typescript-eslint/adjacent-overload-signatures": "warn",
+ "@typescript-eslint/array-type": [
+ "warn",
+ {
+ "default": "array"
+ }
+ ],
+ "@typescript-eslint/ban-types": [
+ "warn",
+ {
+ "types": {
+ "Object": {
+ "message": "Avoid using the `Object` type. Did you mean `object`?"
+ },
+ "Function": {
+ "message": "Avoid using the `Function` type. Prefer a specific function type, like `() => void`."
+ },
+ "Boolean": {
+ "message": "Avoid using the `Boolean` type. Did you mean `boolean`?"
+ },
+ "Number": {
+ "message": "Avoid using the `Number` type. Did you mean `number`?"
+ },
+ "String": {
+ "message": "Avoid using the `String` type. Did you mean `string`?"
+ },
+ "Symbol": {
+ "message": "Avoid using the `Symbol` type. Did you mean `symbol`?"
+ }
+ }
+ }
+ ],
+ "@typescript-eslint/consistent-type-assertions": "warn",
+ "@typescript-eslint/dot-notation": "off",
+ "@typescript-eslint/explicit-function-return-type": "warn",
+ "@typescript-eslint/explicit-member-accessibility": [
+ "off",
+ {
+ "accessibility": "explicit"
+ }
+ ],
+ "@typescript-eslint/indent": "warn",
+ "@typescript-eslint/member-delimiter-style": [
+ "warn",
+ {
+ "multiline": {
+ "delimiter": "semi",
+ "requireLast": true
+ },
+ "singleline": {
+ "delimiter": "semi",
+ "requireLast": false
+ }
+ }
+ ],
+ "@typescript-eslint/member-ordering": "warn",
+ "@typescript-eslint/naming-convention": "off",
+ "@typescript-eslint/no-empty-function": "off",
+ "@typescript-eslint/no-empty-interface": "warn",
+ "@typescript-eslint/no-explicit-any": "off",
+ "@typescript-eslint/no-inferrable-types": [
+ "warn",
+ {
+ "ignoreParameters": true
+ }
+ ],
+ "@typescript-eslint/no-misused-new": "warn",
+ "@typescript-eslint/no-namespace": "warn",
+ "@typescript-eslint/no-parameter-properties": "off",
+ "@typescript-eslint/no-shadow": [
+ "warn",
+ {
+ "hoist": "all"
+ }
+ ],
+ "@typescript-eslint/no-unused-expressions": "warn",
+ "@typescript-eslint/no-use-before-define": "off",
+ "@typescript-eslint/no-var-requires": "warn",
+ "@typescript-eslint/prefer-for-of": "warn",
+ "@typescript-eslint/prefer-function-type": "warn",
+ "@typescript-eslint/prefer-namespace-keyword": "warn",
+ "@typescript-eslint/quotes": [
+ "warn",
+ "double"
+ ],
+ "@typescript-eslint/semi": [
+ "warn",
+ "always"
+ ],
+ "@typescript-eslint/triple-slash-reference": [
+ "warn",
+ {
+ "path": "always",
+ "types": "prefer-import",
+ "lib": "always"
+ }
+ ],
+ "@typescript-eslint/type-annotation-spacing": "warn",
+ "@typescript-eslint/unified-signatures": "warn",
+ "brace-style": [
+ "warn",
+ "1tbs"
+ ],
+ "complexity": "off",
+ "constructor-super": "warn",
+ "curly": "warn",
+ "eol-last": "warn",
+ "eqeqeq": [
+ "warn",
+ "smart"
+ ],
+ "guard-for-in": "warn",
+ "id-blacklist": [
+ "warn",
+ "any",
+ "Number",
+ "number",
+ "String",
+ "string",
+ "Boolean",
+ "boolean",
+ "Undefined",
+ "undefined"
+ ],
+ "id-match": "warn",
+ "indent": "off",
+ "max-classes-per-file": [
+ "warn",
+ 1
+ ],
+ "max-len": [
+ "warn",
+ {
+ "code": 200
+ }
+ ],
+ "new-parens": "warn",
+ "no-bitwise": "warn",
+ "no-caller": "warn",
+ "no-cond-assign": "warn",
+ "no-console": [
+ "warn",
+ {
+ "allow": [
+ "log",
+ "warn",
+ "dir",
+ "timeLog",
+ "assert",
+ "clear",
+ "count",
+ "countReset",
+ "group",
+ "groupEnd",
+ "table",
+ "dirxml",
+ "error",
+ "groupCollapsed",
+ "Console",
+ "profile",
+ "profileEnd",
+ "timeStamp",
+ "context"
+ ]
+ }
+ ],
+ "no-debugger": "warn",
+ "no-empty": "off",
+ "no-eval": "warn",
+ "no-fallthrough": "warn",
+ "no-invalid-this": "off",
+ "no-new-wrappers": "warn",
+ "no-redeclare": "warn",
+ "no-restricted-imports": "warn",
+ "no-throw-literal": "warn",
+ "no-trailing-spaces": "warn",
+ "no-undef-init": "warn",
+ "no-underscore-dangle": "off",
+ "no-unsafe-finally": "warn",
+ "no-unused-labels": "warn",
+ "no-var": "warn",
+ "object-shorthand": "warn",
+ "one-var": [
+ "warn",
+ "never"
+ ],
+ "prefer-arrow/prefer-arrow-functions": "warn",
+ "prefer-const": "warn",
+ "radix": "warn",
+ "react/jsx-boolean-value": "warn",
+ "react/jsx-key": "warn",
+ "react/jsx-no-bind": "warn",
+ "react/self-closing-comp": "warn",
+ "spaced-comment": [
+ "warn",
+ "always",
+ {
+ "markers": [
+ "/"
+ ]
+ }
+ ],
+ "use-isnan": "warn",
+ "valid-typeof": "off",
+ "react/display-name": "warn"
+ }
+ }
+ ]
+ }
+}
diff --git a/common-types/types.d.ts b/common-types/types.d.ts
new file mode 100644
index 000000000..a4357a73a
--- /dev/null
+++ b/common-types/types.d.ts
@@ -0,0 +1,85 @@
+/**
+ * LatLng representation for the Weather Company
+ */
+import { DataFormat, isDefined, isUndefined, Language, Unit } from "./globals";
+import Crop from "./data-model/Crop";
+import Farm, { NewFarm } from "./data-model/Farm";
+import User, { OrganisationUser } from "./data-model/User";
+import Farmer, { NewFarmer } from "./data-model/Farmer";
+import Organisation from "./data-model/Organisation";
+import FieldCrop from "./data-model/FieldCrop";
+import Field, { NewField } from "./data-model/Field";
+import { NewUserDto, UserDto } from "./dto/UserDto";
+import OrganisationDto from "./dto/OrganisationDto";
+import { EISConfig } from "./integrations/EISConfig";
+import { WeatherCompanyConfig } from "./integrations/WeatherCompanyConfig";
+import { Point } from "geojson";
+
+export { DataFormat, Language, Unit, isDefined, isUndefined };
+
+export { Crop, Farm, NewFarm, User, OrganisationUser, Farmer, NewFarmer, Organisation, Field, NewField, FieldCrop };
+
+export {EISConfig, WeatherCompanyConfig}
+
+export {UserDto, NewUserDto, OrganisationDto};
+
+export type LatLngNumber = {
+ lat: number,
+ lng: number
+}
+
+export type LatLngString = {
+ lat: string,
+ lng: string
+}
+
+export type GeoCodeNumber = {
+ latitude: number,
+ longitude: number
+}
+
+export type GeoCodeString = {
+ latitude: string,
+ longitude: string
+}
+
+export type LatLng = LatLngNumber | LatLngString;
+export type GeoCode = GeoCodeNumber | GeoCodeString;
+
+export function toLatLng(geoCode: GeoCode): LatLng {
+ return {
+ lat: geoCode.latitude,
+ lng: geoCode.longitude
+ }
+}
+
+export function toGeoCode(latLng: LatLng): GeoCode {
+ return {
+ latitude: latLng.lat,
+ longitude: latLng.lng
+ }
+}
+
+export function toGroCodeFromPoint(point: Point): GeoCodeNumber {
+ return {
+ latitude: point.coordinates[0],
+ longitude: point.coordinates[1]
+ }
+}
+
+export function geoCodeToString(geocode: GeoCodeNumber) {
+ return `${geocode.latitude},${geocode.longitude}`
+}
+
+export type BoundingBox = {
+ lowerLeft: LatLng,
+ upperRight: LatLng
+}
+
+export interface CommonOptions {
+ format: DataFormat;
+ language: Language;
+ units: Unit
+}
+
+
diff --git a/docker-compose.yml b/docker-compose.yml
index 37be3e9d8..56a6000c3 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -8,3 +8,15 @@ services:
- "./backend/.env.dev.production:/home/node/app/.env:ro"
ports:
- "3000:3000"
+
+ mongodb:
+ container_name: 'mongodb-open_harvest'
+ image: mongo
+ restart: unless-stopped
+ environment:
+ - PUID=1000
+ - PGID=1000
+ ports:
+ - 27017:27017
+ volumes:
+ - ~/mongodb:/data/db
diff --git a/react-app/src/App.tsx b/react-app/src/App.tsx
index c8e4198c7..4c1c8660e 100644
--- a/react-app/src/App.tsx
+++ b/react-app/src/App.tsx
@@ -3,39 +3,35 @@ import "carbon-addons-iot-react/scss/styles.scss";
import PrivateRoute from "./helpers/privateRoute";
import "./App.scss";
-import { Content, Header, HeaderContainer, HeaderMenuItem, HeaderName, HeaderNavigation, SkipToContent } from "carbon-components-react";
+import { Content, HeaderContainer } from "carbon-components-react";
import { Redirect, Route, Switch, withRouter } from "react-router";
import { RouteComponentProps } from "react-router/ts4.0";
-import Nav from "./components/Nav/Nav"
+import Nav from "./components/Nav/Nav";
import CoOpHome from "./components/CoOpHome/CoOpHome";
import Farmers from "./components/Farmers/Farmers";
import Crops from "./components/Crops/Crops";
-import { AuthContext, AuthProvider } from "./services/auth";
-import UserOnboarding from "./components/Onboarding/UserOnboarding";
+import { AuthProvider } from "./services/auth";
+import UserOnBoarding from "./components/Onboarding/UserOnboarding";
import { AddFarmer } from "./components/Farmers/AddFarmer";
-import {enableAllPlugins} from "immer"
+import { enableAllPlugins } from "immer";
import { Messaging } from "./components/Messaging/Messaging";
+
enableAllPlugins();
type AppProps = RouteComponentProps ;
type AppState = {
// showOnBoardingWizard: boolean;
- showLogoutModal: boolean;
+ showLogoutModal?: boolean;
+ newUser?: boolean;
};
class App extends Component {
constructor(props: any) {
super(props);
- console.log(props.location);
- this.state = {
- // showOnBoardingWizard: false,
- showLogoutModal: false
- };
+ this.state = {};
this.setShowLogoutModal = this.setShowLogoutModal.bind(this);
-
-
}
async componentDidMount() {
@@ -45,15 +41,12 @@ class App extends Component {
const res = await fetch("/api/coopManager/hasBeenOnBoarded");
const result = await res.json();
newUser = result.exists;
- }
- catch (e) {}
-
+ } catch (e) {}
+
+ this.setState({newUser});
+
if (newUser) {
- // this.state = {
- // // showOnBoardingWizard: true,
- // showLogoutModal: false
- // };
- this.props.history.push('/onboarding')
+ this.props.history.push("/onboarding");
}
}
@@ -63,72 +56,88 @@ class App extends Component {
<>
(
+ render={() => (
)}
/>
-
-
-
-
- }
- />
- }
- />
-
-
-
-
- }
- />
-
-
-
-
- }
- />
-
-
-
-
- }
- />
-
-
-
-
- }
- />
-
-
+ {
+ this.state.newUser ?
+
+
+
+
+
+ }
+ />
+ :
+
+
+
+
+
+ }
+ />
+ }
+ />
+
+
+
+
+ }
+ />
+
+
+
+
+ }
+ />
+
+
+
+
+ }
+ />
+
+
+
+
+ }
+ />
+
+
+ }
>
diff --git a/react-app/src/components/Nav/Nav.tsx b/react-app/src/components/Nav/Nav.tsx
index 4aa2291e5..d57561477 100644
--- a/react-app/src/components/Nav/Nav.tsx
+++ b/react-app/src/components/Nav/Nav.tsx
@@ -15,7 +15,7 @@ export default function Nav() {
return (
// @ts-ignore
-
+
Open Harvest
@@ -24,12 +24,16 @@ export default function Nav() {
-
- element={NavLink} to="/home">Home
- element={NavLink} to="/farmers">Farmers
- element={NavLink} to="/crops">Crops
- element={NavLink} to="/messaging">Messaging
-
+ {auth.user && (
+
+ element={NavLink} to="/home">Home
+ element={NavLink} to="/farmers">Farmers
+ element={NavLink} to="/crops">Crops
+ element={NavLink} to="/messaging">Messaging
+
+ )
+
+ }
{!auth.user && (
@@ -43,9 +47,9 @@ export default function Nav() {
{/* // Displays when the user is onboarded */}
{auth.user.coopManager && (
- {auth.user!!.organisations!!.map(it =>
+ {auth.user!!.organisations!!.map(it =>
{it.name}
- )}
+ )}
)}
@@ -57,14 +61,14 @@ export default function Nav() {
Hi {auth.user!!.displayName}
-
+
>
)}
-
+
-
+
- )
-}
\ No newline at end of file
+ );
+}
diff --git a/react-app/src/services/auth.tsx b/react-app/src/services/auth.tsx
index 84e58701c..a82354cbc 100644
--- a/react-app/src/services/auth.tsx
+++ b/react-app/src/services/auth.tsx
@@ -1,11 +1,11 @@
-import React, { createContext, useContext, PropsWithChildren, useState, useEffect } from 'react';
+import React, { createContext, PropsWithChildren, useContext, useEffect, useState } from "react";
import { EventEmitter } from "eventemitter3";
-import { CoopManager } from './coopManager';
-import { Organisation } from './organisation';
+import { CoopManager } from "./coopManager";
+import { Organisation } from "./organisation";
export interface CoopManagerUser {
id: string;
- email_verified: boolean
+ email_verified: boolean;
displayName: string;
given_name: string;
name: string;
@@ -25,94 +25,78 @@ export interface CoopManagerUser {
export const AuthContext = createContext(undefined!!);
export function AuthProvider({ children }: PropsWithChildren<{}>) {
- const auth = useProvideAuth()
- return {children}
+ const auth = useProvideAuth();
+ return {children};
}
export const useAuth = () => {
- return useContext(AuthContext)
-}
+ return useContext(AuthContext);
+};
-export interface AuthProviderType {
- user: CoopManagerUser | null;
- loading: boolean;
- login: () => void;
- checkIfSignedIn: () => Promise;
- signout: () => void;
+export interface AuthProviderType {
+ user: CoopManagerUser | null;
+ loading: boolean;
+ login: () => void;
+ checkIfSignedIn: () => Promise;
+ signout: () => void;
}
// EventEmitter for Auth
export const AuthEventEmitter = new EventEmitter();
function useProvideAuth(): AuthProviderType {
- const [user, setUser] = useState(null)
- const [loading, setLoading] = useState(true)
-
- const handleUser = (rawUser: any) => {
- if (rawUser) {
- const user = rawUser;
-
- setLoading(false)
- setUser(user)
- AuthEventEmitter.emit("signedIn", user);
- return user
- } else {
- setLoading(false)
- setUser(null)
- AuthEventEmitter.emit("signedOut");
- return null
- }
- }
-
- const login = () => {
- if (process.env.NODE_ENV == "production") {
- window.location.href = "/login";
- }
- else {
- window.location.href = "https://localhost:3000/login";
- }
- }
-
- const checkIfSignedIn = async () => {
- setLoading(true)
-
- let userInfo = null;
- try {
- const res = await fetch("/me");
- userInfo = await res.json();
- console.log(userInfo);
- }
- catch(e) {
- console.log("User is not signed in.")
- }
-
- return handleUser(userInfo);
- }
-
- const signout = () => {
- // return firebase
- // .auth()
- // .signOut()
- // .then(() => handleUser(false))
- if (process.env.NODE_ENV == "production") {
- window.location.href = "/logout";
- }
- else {
- window.location.href = "https://localhost:3000/logout";
- }
- }
-
- // Check if we're signed in already
- useEffect(() => {
- const userSignedInInfo = checkIfSignedIn();
- }, [])
-
-
- return {
- user,
- loading,
- login,
- checkIfSignedIn,
- signout,
- }
+ const [user, setUser] = useState(null);
+ const [loading, setLoading] = useState(true);
+
+ const handleUser = (rawUser: any) => {
+ if (rawUser) {
+ const user = rawUser;
+
+ setLoading(false);
+ setUser(user);
+ AuthEventEmitter.emit("signedIn", user);
+ return user;
+ } else {
+ setLoading(false);
+ setUser(null);
+ AuthEventEmitter.emit("signedOut");
+ return null;
+ }
+ };
+
+ const login = () => {
+ window.location.href = "/login";
+ };
+
+ const checkIfSignedIn = async () => {
+ setLoading(true);
+
+ let userInfo = null;
+ try {
+ const res = await fetch("/me");
+ userInfo = await res.json();
+ } catch(e) {
+ console.log("User is not signed in.");
+ }
+
+ return handleUser(userInfo);
+ };
+
+ const signout = () => {
+ window.location.href = "/logout";
+ };
+
+ // Check if we're signed in already
+ useEffect(() => {
+ const userSignedInInfo = checkIfSignedIn();
+ }, []);
+
+
+ return {
+ user,
+ loading,
+ login,
+ checkIfSignedIn,
+ signout,
+ };
}
diff --git a/react-app/src/setupProxy.js b/react-app/src/setupProxy.js
index 02e8fc004..3ed722c4a 100644
--- a/react-app/src/setupProxy.js
+++ b/react-app/src/setupProxy.js
@@ -17,4 +17,22 @@ module.exports = function(app) {
secure: false
})
);
-};
\ No newline at end of file
+
+ app.use(
+ '/login',
+ createProxyMiddleware({
+ target: 'https://localhost:3000',
+ changeOrigin: true,
+ secure: false
+ })
+ );
+
+ app.use(
+ '/logout',
+ createProxyMiddleware({
+ target: 'https://localhost:3000',
+ changeOrigin: true,
+ secure: false
+ })
+ );
+};
From d4c70da197f30e99745f1286b8eef0402086fd53 Mon Sep 17 00:00:00 2001
From: Vikas Jagtap
Date: Tue, 17 May 2022 21:32:26 +1000
Subject: [PATCH 2/2] OpenHarvest-13 creating common-types typescript library
and running backend successfully using it
---
backend/package.json | 20 ++++-
backend/src/auth/IBMiDStrategy.ts | 1 +
backend/src/auth/helpers.ts | 3 +-
backend/src/db/entities/crop.ts | 2 +-
backend/src/db/entities/farm.ts | 15 +---
backend/src/db/entities/farmer.ts | 2 +-
backend/src/db/entities/organisation.ts | 11 +--
backend/src/db/mongodb.ts | 27 ++++++-
backend/src/declarations.d.ts | 1 +
backend/src/integrations/EIS/EIS.types.ts | 4 +-
.../src/integrations/EIS/EISFarmService.ts | 18 ++---
backend/src/integrations/EIS/EISService.ts | 3 +-
backend/src/integrations/eventBus.service.ts | 2 +-
.../src/integrations/messagingInterface.ts | 2 +-
.../integrations/smsSync/smsSync.service.ts | 2 +-
.../src/integrations/twilio/twilio.service.ts | 2 +-
.../weatherCompany/WeatherCompanyService.ts | 2 +-
.../weather-company-api.types.ts | 0
backend/src/main.ts | 23 +++---
backend/src/routes/farmer-route.ts | 4 +-
backend/src/routes/lot-route.ts | 76 -----------------
backend/src/routes/organisation-route.ts | 11 ++-
backend/src/routes/user-route.ts | 13 +--
backend/src/routes/weather-route.ts | 11 +--
backend/src/services/CropService.ts | 2 +-
backend/src/services/FarmService.ts | 21 ++---
backend/src/services/FarmerService.ts | 4 +-
backend/src/services/OrganisationService.ts | 81 ++++++++++++++-----
backend/src/services/UserService.ts | 14 ++--
.../src/services/recommendations.service.ts | 18 ++---
backend/src/sockets/socket.io.ts | 2 +-
backend/tsconfig.json | 13 +--
common-types/dto/OrganisationDto.ts | 8 --
common-types/package.json | 5 +-
.../crop.ts => src/data-model/Crop.ts} | 0
common-types/{ => src}/data-model/Farm.ts | 4 +-
.../farmer.ts => src/data-model/Farmer.ts} | 0
common-types/{ => src}/data-model/Field.ts | 7 +-
.../{ => src}/data-model/FieldCrop.ts | 0
.../data-model/Organisation.ts} | 2 +-
common-types/{ => src}/data-model/User.ts | 0
common-types/src/dto/OrganisationDto.ts | 12 +++
common-types/{ => src}/dto/UserDto.ts | 3 +-
common-types/{ => src}/globals.ts | 6 ++
common-types/{types.d.ts => src/index.ts} | 28 ++-----
.../{ => src}/integrations/EISConfig.ts | 0
.../integrations/WeatherCompanyConfig.ts | 2 +-
common-types/tsconfig.json | 28 +++++++
package.json | 8 --
react-app/package.json | 2 +-
tsconfig.base.json | 27 +++++++
tsconfig.settings.json | 13 +++
52 files changed, 303 insertions(+), 262 deletions(-)
create mode 100644 backend/src/declarations.d.ts
delete mode 100644 backend/src/integrations/weatherCompany/weather-company-api.types.ts
delete mode 100644 backend/src/routes/lot-route.ts
delete mode 100644 common-types/dto/OrganisationDto.ts
rename common-types/{data-model/crop.ts => src/data-model/Crop.ts} (100%)
rename common-types/{ => src}/data-model/Farm.ts (77%)
rename common-types/{data-model/farmer.ts => src/data-model/Farmer.ts} (100%)
rename common-types/{ => src}/data-model/Field.ts (65%)
rename common-types/{ => src}/data-model/FieldCrop.ts (100%)
rename common-types/{data-model/organisation.ts => src/data-model/Organisation.ts} (80%)
rename common-types/{ => src}/data-model/User.ts (100%)
create mode 100644 common-types/src/dto/OrganisationDto.ts
rename common-types/{ => src}/dto/UserDto.ts (69%)
rename common-types/{ => src}/globals.ts (97%)
rename common-types/{types.d.ts => src/index.ts} (66%)
rename common-types/{ => src}/integrations/EISConfig.ts (100%)
rename common-types/{ => src}/integrations/WeatherCompanyConfig.ts (72%)
create mode 100644 common-types/tsconfig.json
delete mode 100644 package.json
create mode 100644 tsconfig.base.json
create mode 100644 tsconfig.settings.json
diff --git a/backend/package.json b/backend/package.json
index fe81a1367..98dd1160b 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -1,12 +1,22 @@
{
- "name": "OpenHarvest-backend",
+ "name": "@openharvest/backend",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
- "start": "nodemon",
- "build": "tsc",
+ "build-ts": "tsc --build",
+ "build": "npm run build-ts && npm run lint",
+ "debug": "npm run build && npm run watch-debug",
+ "lint": "tsc --noEmit && eslint \"**/*.{js,ts}\" --quiet --fix",
+ "serve-debug": "nodemon --inspect dist/server.js",
+ "serve": "node dist/server.js",
+ "start": "npm run serve",
+ "watch-debug": "concurrently -k -p \"[{name}]\" -n \"TypeScript,Node\" -c \"yellow.bold,cyan.bold,green.bold\" \"npm run watch-ts\" \"npm run serve-debug\"",
+ "watch-node": "nodemon dist/server.js",
+ "watch-test": "npm run test -- --watchAll",
+ "watch-ts": "tsc --build -w ",
+ "watch": "npm run build-ts && concurrently -k -p \"[{name}]\" -n \"TypeScript,Node\" -c \"yellow.bold,cyan.bold,green.bold\" \"npm run watch-ts\" \"npm run watch-node\"",
"mkcert": "mkcert localhost 127.0.0.1"
},
"keywords": [],
@@ -16,7 +26,6 @@
"@types/geojson": "^7946.0.8",
"@types/uuid": "^8.3.4",
"axios": "^0.26.0",
- "common-types": "file:../common-types",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"dotenv": "^16.0.0",
@@ -31,9 +40,12 @@
"uuid": "^8.3.2"
},
"devDependencies": {
+ "@types/cookie-parser": "^1.4.3",
"@types/cors": "^2.8.12",
"@types/express": "^4.17.13",
"@types/express-session": "^1.17.4",
+ "@types/passport": "^1.0.7",
+ "concurrently": "^7.2.0",
"nodemon": "^2.0.15",
"ts-node": "^10.5.0",
"typescript": "^4.5.5"
diff --git a/backend/src/auth/IBMiDStrategy.ts b/backend/src/auth/IBMiDStrategy.ts
index 793b22ed5..a0e586331 100644
--- a/backend/src/auth/IBMiDStrategy.ts
+++ b/backend/src/auth/IBMiDStrategy.ts
@@ -10,6 +10,7 @@ export const IBMidStrategy = new OpenIDConnectStrategy({
callbackURL: process.env.AUTH_callback_url,
skipUserProfile: true},
// Add your own data here.
+ // @ts-ignore
function (iss, sub, profile, accessToken, refreshToken, params, done) {
process.nextTick(async function () {
profile.accessToken = accessToken;
diff --git a/backend/src/auth/helpers.ts b/backend/src/auth/helpers.ts
index 4d09e7cd4..640f60895 100644
--- a/backend/src/auth/helpers.ts
+++ b/backend/src/auth/helpers.ts
@@ -1,4 +1,4 @@
-import { Organisation, User } from "common-types";
+import { Organisation, User } from "../../../common-types/src";
export interface CoopManagerUser {
id: string;
@@ -40,6 +40,7 @@ export function formatUser(user: any): CoopManagerUser {
}
}
+// @ts-ignore
export function ensureAuthenticated(req, res, next) {
// console.log(req);
if (!req.isAuthenticated()) {
diff --git a/backend/src/db/entities/crop.ts b/backend/src/db/entities/crop.ts
index 24dc11a04..51d0050fa 100644
--- a/backend/src/db/entities/crop.ts
+++ b/backend/src/db/entities/crop.ts
@@ -1,6 +1,6 @@
import { model, Schema, Types } from 'mongoose';
-import { Crop } from "common-types"
+import { Crop } from "../../../../common-types/src"
// Mongoose will automatically add _id property.
export const CropSchema = new Schema({
diff --git a/backend/src/db/entities/farm.ts b/backend/src/db/entities/farm.ts
index f64fa7608..4c71a697f 100644
--- a/backend/src/db/entities/farm.ts
+++ b/backend/src/db/entities/farm.ts
@@ -1,8 +1,8 @@
import { model, Schema, Types } from 'mongoose';
-import { FarmerSchema } from './farmer';
-import { Field, FieldCrop, NewFarm } from "common-types"
import { CropSchema } from "./crop";
+import { PolygonSchema } from "../mongodb";
+import { Field, FieldCrop, NewFarm } from '../../../../common-types/src';
export const FieldCropSchema = new Schema({
crop: CropSchema,
@@ -14,21 +14,14 @@ export const FieldSchema = new Schema({
_id: Types.ObjectId,
name: String,
crops: [FieldCropSchema],
- geoShape: {
- type: "Polygon",
- coordinates: [[Number]]
- },
+ geometry: PolygonSchema,
});
export const FarmSchema = new Schema({
_id: Types.ObjectId,
- farmer: FarmerSchema,
name: String,
fields: [FieldSchema],
- geoShape: {
- type: "Polygon",
- coordinates: [[Number]]
- },
+ geometry: PolygonSchema,
});
export const FarmModel = model("farm", FarmSchema);
diff --git a/backend/src/db/entities/farmer.ts b/backend/src/db/entities/farmer.ts
index c313817a8..9994c75c1 100644
--- a/backend/src/db/entities/farmer.ts
+++ b/backend/src/db/entities/farmer.ts
@@ -1,5 +1,5 @@
import { model, Schema, Types } from 'mongoose';
-import { Farmer } from "common-types"
+import { Farmer } from '../../../../common-types/src';
import { FarmSchema } from "./farm";
export const FarmerSchema = new Schema({
diff --git a/backend/src/db/entities/organisation.ts b/backend/src/db/entities/organisation.ts
index 129ae1d78..e7818fdb5 100644
--- a/backend/src/db/entities/organisation.ts
+++ b/backend/src/db/entities/organisation.ts
@@ -1,19 +1,14 @@
import { model, Schema } from 'mongoose';
+import { Organisation, User } from '../../../../common-types/src';
-import { Organisation, User } from "common-types";
+import { PointSchema } from "../mongodb";
export const UserSchema = new Schema({
/**
* Auth provider + auth provider id. E.g. "IBMid:1SDAS61W6A"
*/
_id: String,
- /**
- * GeoCode / LatLng coordinate tuple
- */
- location: [{
- type: "Point",
- coordinates: [Number]
- }],
+ location: PointSchema,
mobile: String,
});
diff --git a/backend/src/db/mongodb.ts b/backend/src/db/mongodb.ts
index 019ec1d89..1eac9649a 100644
--- a/backend/src/db/mongodb.ts
+++ b/backend/src/db/mongodb.ts
@@ -1,5 +1,5 @@
// Using Node.js `require()`
-import { connect } from 'mongoose';
+import { connect, Schema } from 'mongoose';
export async function mongoInit() {
// console.log(process.env.mongodb_url);
@@ -19,8 +19,31 @@ export async function mongoInit() {
else {
await connect(process.env.mongodb_url!!);
}
- console.log("Connected to DB");
}
+export const PointSchema = new Schema({
+ type: {
+ type: String,
+ enum: ['Point'],
+ required: true
+ },
+ coordinates: {
+ type: [Number],
+ required: true
+ }
+});
+
+export const PolygonSchema = new Schema({
+ type: {
+ type: String,
+ enum: ['Polygon'],
+ required: true
+ },
+ coordinates: {
+ type: [[[Number]]], // Array of arrays of arrays of numbers
+ required: true
+ }
+});
+
diff --git a/backend/src/declarations.d.ts b/backend/src/declarations.d.ts
new file mode 100644
index 000000000..6e492060a
--- /dev/null
+++ b/backend/src/declarations.d.ts
@@ -0,0 +1 @@
+declare module "passport-ci-oidc";
diff --git a/backend/src/integrations/EIS/EIS.types.ts b/backend/src/integrations/EIS/EIS.types.ts
index 2b4099d1b..ae3060fb7 100644
--- a/backend/src/integrations/EIS/EIS.types.ts
+++ b/backend/src/integrations/EIS/EIS.types.ts
@@ -1,5 +1,5 @@
import { Feature, FeatureCollection, Polygon } from "geojson";
-import { GeoCodeNumber, NewFieldCrop } from "common-types";
+import { FieldCrop, GeoCodeNumber } from "../../../../common-types/src";
/**
* There are many redundant fields you'll notice because EIS's data structures
@@ -52,7 +52,7 @@ export interface FieldResponse {
export type OpenHarvestSubFieldProps = {
farmer_id: string;
- crops: NewFieldCrop[]
+ crops: FieldCrop[]
};
export interface EISSubFieldProperties {
diff --git a/backend/src/integrations/EIS/EISFarmService.ts b/backend/src/integrations/EIS/EISFarmService.ts
index 1b38ffaa5..d17f1a5d4 100644
--- a/backend/src/integrations/EIS/EISFarmService.ts
+++ b/backend/src/integrations/EIS/EISFarmService.ts
@@ -1,4 +1,4 @@
-import { EISConfig, Farm, Farmer, Field, NewFarm, NewField } from "common-types";
+import { EISConfig, Farm, Farmer, Field, NewFarm, NewField } from "../../../../common-types/src";
import { EISField, EISFieldCreateResponse, EISSubField, EISSubFieldProperties, EISSubFieldSearchReturn, FieldResponse, OpenHarvestSubFieldProps } from "./EIS.types";
import { EISService } from "./EISService";
import { Feature, FeatureCollection, Polygon } from "geojson";
@@ -38,7 +38,7 @@ export class EISFarmService extends EISService {
try {
for (const parentRef of Object.keys(parentRefs)) {
- farms.push(await this.getFarm(eisConfig, parentRef, farmer));
+ farms.push(await this.getFarm(eisConfig, parentRef));
}
} catch (e) {
console.error(e);
@@ -46,20 +46,20 @@ export class EISFarmService extends EISService {
return farms;
}
- async saveFarm(eisConfig: EISConfig, farm: NewFarm): Promise {
+ async saveFarm(eisConfig: EISConfig, farmer: Farmer, farm: NewFarm): Promise {
const eisSession = await this.getToken(eisConfig);
const eisField: EISField = {
name: farm.name,
- subFields: farm.fields.map((field) => EISFarmService.convertToEisSubField(farm.farmer, field))
+ subFields: farm.fields.map((field) => EISFarmService.convertToEisSubField(farmer, field))
};
const res = await eisSession.authAxios.post(eisSession.eisConfig.apiUrl + "field", eisField);
- return this.getFarm(eisConfig, res.data.field, farm.farmer);
+ return this.getFarm(eisConfig, res.data.field);
}
- private async getFarm(eisConfig: EISConfig, uuid: string, farmer: Farmer): Promise {
+ private async getFarm(eisConfig: EISConfig, uuid: string): Promise {
const eisSession = await this.getToken(eisConfig);
const fieldRes = await eisSession.authAxios.get(eisSession.eisConfig.apiUrl + `field/${uuid}`);
@@ -73,7 +73,7 @@ export class EISFarmService extends EISService {
let field: Field = {
_id: subField.uuid,
- geoShape: subField.geometry,
+ geometry: subField.geometry,
name: subField.properties.field_name,
crops: openHarvestProps.crops
};
@@ -81,12 +81,12 @@ export class EISFarmService extends EISService {
fields.push(field);
}
- return {_id: uuid, fields, farmer, name: fieldResponse.properties.name};
+ return {_id: uuid, fields, name: fieldResponse.properties.name};
}
private static convertToEisSubField(farmer: Farmer, field: NewField): EISSubField {
const feature: Feature = {
- geometry: field.geoShape,
+ geometry: field.geometry,
properties: {
farm_name: field.name,
open_harvest_farmer_id: farmer._id,
diff --git a/backend/src/integrations/EIS/EISService.ts b/backend/src/integrations/EIS/EISService.ts
index dcc1eaef3..1915f751b 100644
--- a/backend/src/integrations/EIS/EISService.ts
+++ b/backend/src/integrations/EIS/EISService.ts
@@ -1,6 +1,5 @@
import axios, { AxiosInstance } from "axios";
-import { EISConfig } from "../../../../common-types/integrations/EISConfig";
-import { isUndefined } from "common-types";
+import { EISConfig, isUndefined } from "../../../../common-types/src";
type EISSession = {
token: string,
diff --git a/backend/src/integrations/eventBus.service.ts b/backend/src/integrations/eventBus.service.ts
index 3a555cdc0..8ef5b8ebe 100644
--- a/backend/src/integrations/eventBus.service.ts
+++ b/backend/src/integrations/eventBus.service.ts
@@ -5,7 +5,7 @@
* will make the move easy as it will just become an interface
*/
-import { Organisation } from "common-types";
+import { Organisation } from "../../../common-types/src";
import { EventEmitter } from "events";
import { MessageLog } from "../db/entities/messageLog";
diff --git a/backend/src/integrations/messagingInterface.ts b/backend/src/integrations/messagingInterface.ts
index 21967e344..a6278ecb3 100644
--- a/backend/src/integrations/messagingInterface.ts
+++ b/backend/src/integrations/messagingInterface.ts
@@ -3,7 +3,7 @@ import { FarmerModel } from "../db/entities/farmer";
import { MessageLog } from "../db/entities/messageLog";
import { EventBusInstance } from "./eventBus.service";
import { OrganisationModel } from "../db/entities/organisation";
-import { Farmer, User } from "common-types";
+import { Farmer, User } from "../../../common-types/src";
export declare interface MessagingInterface {
on(event: 'onMessage', listener: (message: MessageLog) => void): this;
diff --git a/backend/src/integrations/smsSync/smsSync.service.ts b/backend/src/integrations/smsSync/smsSync.service.ts
index 9a0728d83..5fbdcd32d 100644
--- a/backend/src/integrations/smsSync/smsSync.service.ts
+++ b/backend/src/integrations/smsSync/smsSync.service.ts
@@ -3,7 +3,7 @@ import { MessagingInterface } from "../messagingInterface";
import { v4 as uuidv4 } from "uuid";
import { MessageLog, MessageLogModel, Source, Status } from "../../db/entities/messageLog";
-import { Farmer, User } from "common-types";
+import { Farmer, User } from "../../../../common-types/src";
export interface SMSSyncMessage {
to: string;
diff --git a/backend/src/integrations/twilio/twilio.service.ts b/backend/src/integrations/twilio/twilio.service.ts
index b16275fc4..0bd083041 100644
--- a/backend/src/integrations/twilio/twilio.service.ts
+++ b/backend/src/integrations/twilio/twilio.service.ts
@@ -1,4 +1,4 @@
-import { Farmer, User } from "common-types";
+import { Farmer, User } from "../../../../common-types/src";
import twilio, { Twilio } from "twilio";
import { FarmerModel } from "../../db/entities/farmer";
import { MessageLog, MessageLogModel, Source, Status } from "../../db/entities/messageLog";
diff --git a/backend/src/integrations/weatherCompany/WeatherCompanyService.ts b/backend/src/integrations/weatherCompany/WeatherCompanyService.ts
index 787a8b336..1b993c73c 100644
--- a/backend/src/integrations/weatherCompany/WeatherCompanyService.ts
+++ b/backend/src/integrations/weatherCompany/WeatherCompanyService.ts
@@ -1,5 +1,5 @@
import axios from "axios";
-import { CommonOptions, DataFormat, GeoCodeNumber, geoCodeToString, Language, Unit, WeatherCompanyConfig } from "common-types";
+import { CommonOptions, DataFormat, GeoCodeNumber, geoCodeToString, Language, Unit, WeatherCompanyConfig } from "../../../../common-types/src";
const apiRequestLimit = 50; // It's really 100 per minute
let apiRequestCounter = 0;
diff --git a/backend/src/integrations/weatherCompany/weather-company-api.types.ts b/backend/src/integrations/weatherCompany/weather-company-api.types.ts
deleted file mode 100644
index e69de29bb..000000000
diff --git a/backend/src/main.ts b/backend/src/main.ts
index 440fc7a67..30c3109d5 100644
--- a/backend/src/main.ts
+++ b/backend/src/main.ts
@@ -12,7 +12,7 @@ import 'dotenv/config';
import { mongoInit } from "./db/mongodb";
import farmerRoutes from "./routes/farmer-route";
-import lotRoutes from "./routes/lot-route";
+// import lotRoutes from "./routes/lot-route";
import cropRoutes from "./routes/crop-route";
import dashboardRoutes from "./routes/dashboard-route";
import recommendationsRoutes from "./routes/recommendations-route";
@@ -27,7 +27,7 @@ import { IBMidStrategy } from "./auth/IBMiDStrategy";
import { SocketIOManagerInstance } from "./sockets/socket.io";
import { Server } from "http";
-mongoInit();
+mongoInit().then(() => console.log("Connected to DB"));
const app = express();
@@ -49,11 +49,14 @@ app.use(session({
app.use(passport.initialize());
app.use(passport.session());
+// @ts-ignore
passport.serializeUser(function(user, done) {
done(null, user);
});
+// @ts-ignore
passport.deserializeUser(function(obj, done) {
+ // @ts-ignore
done(null, obj);
});
@@ -62,9 +65,9 @@ passport.use(IBMidStrategy);
app.get('/login', passport.authenticate('openidconnect', { state: Math.random().toString(36).substr(2, 10) }));
app.get('/auth/sso/callback', function (req, res, next) {
- // @ts-ignore
- let redirect_url = "/app";
+ let redirect_url;
if (process.env.NODE_ENV == "production") {
+ // @ts-ignore
redirect_url = req.session.originalUrl;
redirect_url = "https://openharvest.net/";
}
@@ -85,11 +88,13 @@ app.get('/failure', function(req, res) {
app.get('/hello', ensureAuthenticated, function (req, res) {
- var claims = req.user['_json'];
- var html ="Hello " + claims.given_name + " " + claims.family_name + ":
";
+ // @ts-ignore
+ const claims = req.user['_json'];
+ let html ="Hello " + claims.given_name + " " + claims.family_name + ":
";
html += "User details (ID token in _json object):
";
+ // @ts-ignore
html += "" + JSON.stringify(req.user, null, 4) + "
";
html += "
logout";
@@ -104,6 +109,7 @@ app.get('/hello', ensureAuthenticated, function (req, res) {
app.get('/me', ensureAuthenticated, (req, res) => {
+ // @ts-ignore
return res.json(formatUser(req.user));
});
@@ -112,7 +118,7 @@ app.get('/me', ensureAuthenticated, (req, res) => {
// app.use('/api/names', nameRoutes);
app.use("/api/farmer", farmerRoutes);
-app.use("/api/lot", lotRoutes);
+// app.use("/api/lot", lotRoutes);
app.use("/api/crop", cropRoutes);
app.use("/api/dashboard", dashboardRoutes);
app.use("/api/recommendations", recommendationsRoutes);
@@ -149,8 +155,7 @@ if (process.env.NODE_ENV == "production") {
console.log("Server starting on http://localhost:" + port);
});
-}
-else {
+} else {
const sslKey = process.env['SSL_Key'];
const sslCert = process.env['SSL_Cert'];
if (sslKey == undefined || sslCert == undefined) {
diff --git a/backend/src/routes/farmer-route.ts b/backend/src/routes/farmer-route.ts
index 925cb51ab..e4acea426 100644
--- a/backend/src/routes/farmer-route.ts
+++ b/backend/src/routes/farmer-route.ts
@@ -1,9 +1,9 @@
import { Request, Response, Router } from "express";
import { farmerService } from "../services/FarmerService";
-import { Farmer, isUndefined, NewFarmer } from "common-types";
import { FarmerModel } from "../db/entities/farmer";
import { farmService } from "../services/FarmService";
+import { Farmer, isUndefined, NewFarmer } from "../../../common-types/src";
// const LotAreaService = require("./../services/lot-areas.service");
// const lotAreas = new LandAreasService();
@@ -94,7 +94,7 @@ router.post("/add", async(req: Request<{}, {}, NewFarmer>, res: Response) => {
}
const farmer = await farmerService.saveFarmer(newFarmer);
- const farms = await farmService.saveFarms(farmer, newFarmer.farms);
+ await farmService.saveFarms(farmer, newFarmer.farms);
res.json(farmer);
});
diff --git a/backend/src/routes/lot-route.ts b/backend/src/routes/lot-route.ts
deleted file mode 100644
index aef5e3c07..000000000
--- a/backend/src/routes/lot-route.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-import { Request, Response, Router } from "express";
-import LandAreasService from "../services/land-areas.service";
-
-var router = Router();
-
-const lotAreas = new LandAreasService();
-
-router.get("/", getAllLots);
-router.get("/:id", getLot);
-router.get("/inBbox/:bboxString", getAreaInBox);
-router.put("/", updateLot);
-
-async function getAllLots(req: Request, res: Response) {
- try {
- const response = await lotAreas.getAllLots();
- res.json(response);
- } catch (e) {
- console.error(e);
- res.status(500).json(e);
- }
-}
-
-async function getLot(req: Request, res: Response) {
- const id = req.params["id"];
- if (!id) {
- res.sendStatus(400).end();
- return;
- }
- try {
- const lot = await lotAreas.getLot(id);
- res.json(lot);
- } catch (e) {
- console.error(e);
- res.status(500).json(e);
- }
-}
-
-async function updateLot(req: Request, res: Response) {
- const lot = req.body;
- if (!lot) {
- res.sendStatus(400).end();
- return;
- }
- try {
- const newLot = await lotAreas.updateLot(lot);
- res.json(newLot);
- } catch (e) {
- console.error(e);
- res.status(500).json(e);
- }
-}
-
-async function getAreaInBox(req: Request, res: Response) {
- const bboxStr = req.params["bboxString"];
- const elems = bboxStr.split(",");
- const bbox = {
- lowerLeft: {
- lat: elems[0],
- lng: elems[1],
- },
- upperRight: {
- lat: elems[2],
- lng: elems[3],
- },
- };
-
- try {
- const response = await lotAreas.getAreasInBbox(bbox);
- res.json(response);
- } catch (e) {
- console.error(e);
- res.status(500).json(e);
- }
-}
-
-export default router;
diff --git a/backend/src/routes/organisation-route.ts b/backend/src/routes/organisation-route.ts
index 222a0a36b..2c91f1563 100644
--- a/backend/src/routes/organisation-route.ts
+++ b/backend/src/routes/organisation-route.ts
@@ -1,5 +1,5 @@
// import dependencies and initialize the express router
-import { isDefined, isUndefined, OrganisationDto } from "common-types";
+import { isDefined, isUndefined, UserOrganisationDto } from "../../../common-types/src";
import { Request, Router } from "express";
import { organisationService } from "../services/OrganisationService";
@@ -7,7 +7,6 @@ const router = Router();
router.get("/", async (req, res) => {
const orgs = await organisationService.getAllOrganisations(true);
- // console.log(orgs);
return res.json(orgs);
});
@@ -23,7 +22,7 @@ router.get("/:id", async (req, res) => {
}
});
-router.post("/", async (req: Request<{}, {}, OrganisationDto>, res) => {
+router.post("/", async (req: Request<{}, {}, UserOrganisationDto>, res) => {
if (isUndefined(req.body)) {
return res.status(400).send("Body is missing");
}
@@ -32,11 +31,17 @@ router.post("/", async (req: Request<{}, {}, OrganisationDto>, res) => {
}
const name = req.body.name;
+ if (!/^[\dA-Za-z\s\-]+$/.test(name)) {
+ return res.status(400).send("Invalid name with not allowed characters");
+ }
+
const organisation = await organisationService.getOrganisation(name)
if (isDefined(organisation)) {
return res.status(409).send("Organisation already exists: " + name);
}
+
+
console.log("Creating Org:", name);
const doc = await organisationService.createOrganisation(req.body);
res.json(doc);
diff --git a/backend/src/routes/user-route.ts b/backend/src/routes/user-route.ts
index 805463ed9..f59987c07 100644
--- a/backend/src/routes/user-route.ts
+++ b/backend/src/routes/user-route.ts
@@ -1,7 +1,6 @@
import { Request, Router } from "express";
-import { organisationService } from "../services/OrganisationService";
import { userService } from "../services/UserService";
-import { isUndefined, OrganisationDto, UserDto } from "common-types";
+import { isUndefined, UserDto } from "../../../common-types/src";
const router = Router();
@@ -39,14 +38,4 @@ router.post("/onboard", async (req: Request<{}, {}, UserDto>, res) => {
});
-router.put("/addOrganisation", async (req: Request<{}, {}, OrganisationDto>, res) => {
- if (req.body === undefined) {
- return res.status(400).send("Body is missing");
- }
-
- const org = await organisationService.createOrganisation(req.body);
-
- res.json(org);
-});
-
export default router;
diff --git a/backend/src/routes/weather-route.ts b/backend/src/routes/weather-route.ts
index 138d98b00..ca281f797 100644
--- a/backend/src/routes/weather-route.ts
+++ b/backend/src/routes/weather-route.ts
@@ -1,6 +1,6 @@
// import dependencies and initialize the express router
import { Request, Router } from "express";
-import { isUndefined, toGroCodeFromPoint, UserDto } from "common-types";
+import { isUndefined, toGroCodeFromPoint, UserDto } from "../../../common-types/src";
import { weatherCompanyService } from "../integrations/weatherCompany/WeatherCompanyService";
import { organisationService } from "../services/OrganisationService";
@@ -18,17 +18,14 @@ router.post("/farmerForecast", async (req: Request<{}, {}, UserDto>, res) => {
console.log("farmerForecast");
const userDto = req.body;
- const organisation = await organisationService.getOrganisation(userDto.organisation);
- if (isUndefined(organisation)) {
- throw new Error("Organisation does not exist: " + userDto.organisation)
- }
- if (isUndefined(organisation.weatherCompanyConfig)) {
+ const weatherCompanyConfig = await organisationService.getWeatherCompanyConfig(userDto.organisation.name);
+ if (isUndefined(weatherCompanyConfig)) {
return res.status(400).send( "User's organisation is not configured to use weather company");
}
- const forecast = await weatherCompanyService.daily15DayForecast(organisation.weatherCompanyConfig, toGroCodeFromPoint(userDto.location));
+ const forecast = await weatherCompanyService.daily15DayForecast(weatherCompanyConfig, toGroCodeFromPoint(userDto.location));
// console.log(forecast);
res.json(forecast);
});
diff --git a/backend/src/services/CropService.ts b/backend/src/services/CropService.ts
index b7e623a64..8cb92759a 100644
--- a/backend/src/services/CropService.ts
+++ b/backend/src/services/CropService.ts
@@ -2,7 +2,7 @@
// const {cropDetailsView} = require("../db/cloudant");
// const {cropDetailsDdoc} = require("../db/cloudant");
-import { Crop } from "common-types";
+import { Crop } from "../../../common-types/src";
import { CropModel } from "../db/entities/crop";
export class CropService {
diff --git a/backend/src/services/FarmService.ts b/backend/src/services/FarmService.ts
index 07e3902c9..24fcbd1f5 100644
--- a/backend/src/services/FarmService.ts
+++ b/backend/src/services/FarmService.ts
@@ -1,11 +1,11 @@
-import { EISConfig, Farm, Farmer, isDefined, isUndefined, NewFarm, Organisation } from "common-types";
+import { EISConfig, Farm, Farmer, isDefined, NewFarm } from "../../../common-types/src";
import { FarmModel } from "../db/entities/farm";
import { eisFarmService } from "../integrations/EIS/EISFarmService";
import { organisationService } from "./OrganisationService";
export interface IFarmService {
getFarmerFarms(farmer: Farmer): Promise;
- saveFarm(newFarm: NewFarm): Promise;
+ saveFarm(farmer: Farmer, newFarm: NewFarm): Promise;
saveFarms(farmer: Farmer, newFarms: NewFarm[]): Promise;
}
@@ -21,12 +21,12 @@ class FarmService implements IFarmService {
return await FarmModel.find({"farmer._id": farmer._id}).exec() as Farm[];
}
- async saveFarm(newFarm: NewFarm): Promise {
+ async saveFarm(farmer: Farmer, newFarm: NewFarm): Promise {
- const eisConfig = await FarmService.getEISConfig(newFarm.farmer.organisation);
+ const eisConfig = await FarmService.getEISConfig(farmer.organisation);
if (isDefined(eisConfig)) {
- return await eisFarmService.saveFarm(eisConfig, newFarm);
+ return await eisFarmService.saveFarm(eisConfig, farmer, newFarm);
}
const farmDoc = new FarmModel(newFarm);
@@ -37,12 +37,11 @@ class FarmService implements IFarmService {
const eisConfig = await FarmService.getEISConfig(farmer.organisation);
const farms: Farm[] = [];
for (const newFarm of newFarms) {
- newFarm.farmer = farmer;
let farm: Farm;
if (isDefined(eisConfig)) {
- farm = await eisFarmService.saveFarm(eisConfig, newFarm);
+ farm = await eisFarmService.saveFarm(eisConfig, farmer, newFarm);
} else {
- farm = await this.saveFarm(newFarm) as Farm;
+ farm = await this.saveFarm(farmer, newFarm) as Farm;
}
farms.push(farm);
}
@@ -51,11 +50,7 @@ class FarmService implements IFarmService {
}
private static async getEISConfig(org: string): Promise {
- let organisation: Organisation | null = await organisationService.getOrganisation(org);
- if (isUndefined(organisation)) {
- throw new Error("Organisation does not exist: " + org)
- }
- return organisation.eisConfig;
+ return organisationService.getEISConfig(org);
}
}
diff --git a/backend/src/services/FarmerService.ts b/backend/src/services/FarmerService.ts
index 73d55c921..04db6d757 100644
--- a/backend/src/services/FarmerService.ts
+++ b/backend/src/services/FarmerService.ts
@@ -1,10 +1,10 @@
import { FarmerModel } from "../db/entities/farmer";
-import { Farmer, NewFarmer } from "common-types";
+import { Farmer, NewFarmer } from "../../../common-types/src";
import { farmService } from "./FarmService";
class FarmerService {
async getFarmers(): Promise {
- return await FarmerModel.find().lean().exec();
+ return await FarmerModel.find().lean().exec();
}
async saveFarmer(newFarmer: NewFarmer): Promise {
diff --git a/backend/src/services/OrganisationService.ts b/backend/src/services/OrganisationService.ts
index dbe0a5a6c..029fc4c9e 100644
--- a/backend/src/services/OrganisationService.ts
+++ b/backend/src/services/OrganisationService.ts
@@ -1,29 +1,30 @@
-import { isDefined, isUndefined, Organisation, OrganisationDto, User, UserDto } from "common-types";
+import { EISConfig, isDefined, isUndefined, Organisation, OrganisationDto, User, UserDto, UserOrganisationDto, WeatherCompanyConfig } from "../../../common-types/src";
import { OrganisationModel } from "../db/entities/organisation";
-import { createToUserDto } from "./UserService";
+import { toUserDto } from "./UserService";
class OrganisationService {
- async getOrganisationsByUserId(userId: string): Promise {
- return await OrganisationModel.find({
- "users._id": userId
- }
- ).exec();
+ async getOrganisationsByUserId(userId: string): Promise {
+
+ const organisations = await OrganisationService.findAll({
+ "users._id": userId
+ });
+
+ return organisations.map(org => toUserOrganisationDto(org));
};
- async getAllOrganisations(lean = true): Promise {
- if (lean)
- return OrganisationModel.find({}).lean();
- else
- return OrganisationModel.find({});
+ async getAllOrganisations(filter = {}): Promise {
+ const organisations = await OrganisationService.findAll(filter);
+ return organisations.map(org => toOrganisationDto(org));
}
- async getOrganisation(id: string): Promise {
- return OrganisationModel.findById(id);
+ async getOrganisation(id: string): Promise {
+ const org = await OrganisationModel.findById(id);
+ return isUndefined(org) ? null : toOrganisationDto(org);
}
- async createOrganisation(org: OrganisationDto): Promise {
+ async createOrganisation(org: UserOrganisationDto): Promise {
const organisation = new OrganisationModel();
organisation.authMethod = org.authMethod;
@@ -34,7 +35,7 @@ class OrganisationService {
}
return user;
});
- return organisation.save();
+ return toOrganisationDto(await organisation.save());
}
async addUserToOrganisation(userDto: UserDto): Promise {
@@ -45,7 +46,7 @@ class OrganisationService {
let orgUser = org.users.find(orgUser => orgUser._id === user._id);
if (isDefined(orgUser)) {
- return createToUserDto(orgUser, org);
+ return toUserDto(orgUser, org);
}
const user: User = {
@@ -56,8 +57,52 @@ class OrganisationService {
org.users.push(user);
await org.save();
- return createToUserDto(user, org);
+ return toUserDto(user, org);
+ }
+
+ async getEISConfig(orgName: string): Promise {
+ const org = await OrganisationModel.findById(orgName);
+
+ if (isUndefined(org)) {
+ throw new Error("Organisation does not exist: " + orgName);
+ }
+
+ return org.eisConfig;
+ }
+
+ async getWeatherCompanyConfig(orgName: string): Promise {
+ const org = await OrganisationModel.findById(orgName);
+
+ if (isUndefined(org)) {
+ throw new Error("Organisation does not exist: " + orgName);
+ }
+
+ return org.weatherCompanyConfig;
+ }
+
+
+ private static async findAll(filter = {}): Promise {
+ return OrganisationModel.find(filter).lean();
}
}
export const organisationService = new OrganisationService();
+
+export function toOrganisationDto(org: Organisation): OrganisationDto {
+ return {
+ name: org.name,
+ authMethod: org.authMethod,
+ integrations: {
+ EIS: isDefined(org.eisConfig),
+ WEATHER: isDefined(org.weatherCompanyConfig)
+ }
+ };
+}
+
+export function toUserOrganisationDto(org: Organisation): UserOrganisationDto {
+ const dto = toOrganisationDto(org);
+ return {
+ ...dto,
+ users: org.users.map(user => toUserDto(user, org))
+ };
+}
diff --git a/backend/src/services/UserService.ts b/backend/src/services/UserService.ts
index 4832a20ba..6f1eb200f 100644
--- a/backend/src/services/UserService.ts
+++ b/backend/src/services/UserService.ts
@@ -1,11 +1,11 @@
-import { isUndefined, NewUserDto, Organisation, User, UserDto } from "common-types";
-import { organisationService } from "./OrganisationService";
+import { isUndefined, NewUserDto, Organisation, User, UserDto, UserOrganisationDto } from "../../../common-types/src";
+import { organisationService, toOrganisationDto } from "./OrganisationService";
class UserService {
async getUser(userId: string): Promise {
- const orgs: Organisation[] = await organisationService.getOrganisationsByUserId(userId);
+ const orgs: UserOrganisationDto[] = await organisationService.getOrganisationsByUserId(userId);
if (isUndefined(orgs) || orgs.length === 0) {
return [];
@@ -14,8 +14,8 @@ class UserService {
const userDtos: UserDto[] = [];
orgs.forEach(org => {
- org.users.filter(user => user._id === userId).map(user => {
- userDtos.push(createToUserDto(user, org));
+ org.users.filter(user => user.id === userId).map(user => {
+ userDtos.push(toUserDto(user, org));
})
});
@@ -40,7 +40,7 @@ class UserService {
export const userService = new UserService();
-export function createToUserDto(user: User, org: Organisation): UserDto {
+export function toUserDto(user: User, org: Organisation): UserDto {
if (isUndefined(user._id)) {
throw new Error("User does not have id.")
}
@@ -48,7 +48,7 @@ export function createToUserDto(user: User, org: Organisation): UserDto {
email: "",
location: user.location,
mobile: user.mobile,
- organisation: org.name,
+ organisation: toOrganisationDto(org),
id: user._id
};
}
diff --git a/backend/src/services/recommendations.service.ts b/backend/src/services/recommendations.service.ts
index 03f9c550d..02d2a69d8 100644
--- a/backend/src/services/recommendations.service.ts
+++ b/backend/src/services/recommendations.service.ts
@@ -7,13 +7,13 @@ import { cropService } from "./CropService";
// const nswBbox = "140.965576,-37.614231,154.687500,-28.071980"; // lng lat
// const nswBboxLatLng = "-37.614231,140.965576,-28.071980,154.687500"; // lat lng
-const weights = {
- lowPlantedArea: 0.15,
- lowYieldForecast: 0.20,
- onShortlist: 0.25,
- inSeason: 0.40,
- // notOnOthersShortlist: 0.30,
-};
+// const weights = {
+// lowPlantedArea: 0.15,
+// lowYieldForecast: 0.20,
+// onShortlist: 0.25,
+// inSeason: 0.40,
+// // notOnOthersShortlist: 0.30,
+// };
export interface RecommendationsRequest {
plantDate: string
@@ -38,7 +38,7 @@ export default class RecommendationsService {
const seasonStartMonth = startDate.getMonth() + 1;
// let seasonEndMonth = crop.planting_season[1];
let seasonEndMonth = endDate.getMonth() + 1;
- let inSeason = false;
+ let inSeason;
if (seasonStartMonth > seasonEndMonth) {
inSeason = (plantMonth >= seasonStartMonth && plantMonth <= 12) || (plantMonth >= 1 && plantMonth <= seasonEndMonth);
} else {
@@ -94,7 +94,7 @@ export default class RecommendationsService {
// cropScore.yieldForecastScore);
// cropScores.push(cropScore);
// });
-
+ // @ts-ignore
return cropScores.sort((a, b) => b.score - a.score);
}
diff --git a/backend/src/sockets/socket.io.ts b/backend/src/sockets/socket.io.ts
index 04b66e504..b07ab3d87 100644
--- a/backend/src/sockets/socket.io.ts
+++ b/backend/src/sockets/socket.io.ts
@@ -1,7 +1,7 @@
import { MessageLog } from "../db/entities/messageLog";
import { Server as NodejsServer } from "http";
import { Namespace, Server } from "socket.io";
-import { Organisation } from "common-types";
+import { Organisation } from "../../../common-types/src";
// interface ServerToClientEvents {
diff --git a/backend/tsconfig.json b/backend/tsconfig.json
index 9b16f8306..5721658b3 100644
--- a/backend/tsconfig.json
+++ b/backend/tsconfig.json
@@ -1,12 +1,13 @@
{
+ "extends": "../tsconfig.settings.json",
"include": ["src/**/*"],
"exclude": ["node_modules", "**/*.spec.ts"],
- "ts-node": {
- "files": true
- },
"files": [
"src/types.d.ts"
],
+ "references": [
+ { "path": "../common-types" }
+ ],
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
@@ -14,12 +15,12 @@
"incremental": true, /* Enable incremental compilation */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
"tsBuildInfoFile": "./tsbuildinfo", /* Specify the folder for .tsbuildinfo incremental compilation files. */
- // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
+ "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
- "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
+ "target": "es6", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
@@ -83,7 +84,7 @@
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
- "noImplicitAny": false, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
+ "noImplicitAny": false, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
// "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
diff --git a/common-types/dto/OrganisationDto.ts b/common-types/dto/OrganisationDto.ts
deleted file mode 100644
index d01b902aa..000000000
--- a/common-types/dto/OrganisationDto.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { UserDto } from "./UserDto";
-import { AuthMethod } from "../globals";
-
-export default interface OrganisationDto {
- name: string;
- authMethod: AuthMethod;
- users: Omit[]
-}
diff --git a/common-types/package.json b/common-types/package.json
index 32e92b4c2..44b0cef63 100644
--- a/common-types/package.json
+++ b/common-types/package.json
@@ -1,7 +1,8 @@
{
- "name": "common-types",
+ "name": "@openharvest/common-types",
+ "main": "dist/index.js",
+ "types": "dist/index.d.ts",
"version": "1.0.0",
- "types": "types.d.ts",
"dependencies": {
"@types/geojson": "^7946.0.8"
},
diff --git a/common-types/data-model/crop.ts b/common-types/src/data-model/Crop.ts
similarity index 100%
rename from common-types/data-model/crop.ts
rename to common-types/src/data-model/Crop.ts
diff --git a/common-types/data-model/Farm.ts b/common-types/src/data-model/Farm.ts
similarity index 77%
rename from common-types/data-model/Farm.ts
rename to common-types/src/data-model/Farm.ts
index a186a90d8..97114fef7 100644
--- a/common-types/data-model/Farm.ts
+++ b/common-types/src/data-model/Farm.ts
@@ -1,13 +1,11 @@
-import Farmer from "./Farmer";
import { Polygon } from "geojson";
import Field, { NewField } from "./Field";
export interface NewFarm {
_id?: string,
name: string;
- farmer: Farmer;
fields: NewField[];
- geoShape?: Polygon;
+ geometry?: Polygon;
}
export default interface Farm extends NewFarm {
diff --git a/common-types/data-model/farmer.ts b/common-types/src/data-model/Farmer.ts
similarity index 100%
rename from common-types/data-model/farmer.ts
rename to common-types/src/data-model/Farmer.ts
diff --git a/common-types/data-model/Field.ts b/common-types/src/data-model/Field.ts
similarity index 65%
rename from common-types/data-model/Field.ts
rename to common-types/src/data-model/Field.ts
index 2dc4b6cec..6b18ea8ad 100644
--- a/common-types/data-model/Field.ts
+++ b/common-types/src/data-model/Field.ts
@@ -1,15 +1,14 @@
import { Polygon } from "geojson";
-import FieldCrop, { NewFieldCrop } from "./FieldCrop";
+import FieldCrop from "./FieldCrop";
export interface NewField {
_id?: string,
name: string;
- geoShape: Polygon;
- crops: NewFieldCrop[]
+ geometry: Polygon;
+ crops: FieldCrop[]
}
export default interface Field extends NewField {
_id: string,
- crops: FieldCrop[]
}
diff --git a/common-types/data-model/FieldCrop.ts b/common-types/src/data-model/FieldCrop.ts
similarity index 100%
rename from common-types/data-model/FieldCrop.ts
rename to common-types/src/data-model/FieldCrop.ts
diff --git a/common-types/data-model/organisation.ts b/common-types/src/data-model/Organisation.ts
similarity index 80%
rename from common-types/data-model/organisation.ts
rename to common-types/src/data-model/Organisation.ts
index f07a48021..5ca5994c2 100644
--- a/common-types/data-model/organisation.ts
+++ b/common-types/src/data-model/Organisation.ts
@@ -1,6 +1,6 @@
import User from "./User";
import { AuthMethod } from "../globals";
-import { EISConfig, WeatherCompanyConfig } from "../types";
+import { EISConfig, WeatherCompanyConfig } from "../index";
export default interface Organisation {
name: string,
diff --git a/common-types/data-model/User.ts b/common-types/src/data-model/User.ts
similarity index 100%
rename from common-types/data-model/User.ts
rename to common-types/src/data-model/User.ts
diff --git a/common-types/src/dto/OrganisationDto.ts b/common-types/src/dto/OrganisationDto.ts
new file mode 100644
index 000000000..0d60b56b8
--- /dev/null
+++ b/common-types/src/dto/OrganisationDto.ts
@@ -0,0 +1,12 @@
+import { UserDto } from "./UserDto";
+import { AuthMethod, Integration } from "../globals";
+
+export default interface OrganisationDto {
+ name: string;
+ authMethod: AuthMethod;
+ integrations: Partial>
+}
+
+export interface UserOrganisationDto extends OrganisationDto{
+ users: Omit[];
+}
diff --git a/common-types/dto/UserDto.ts b/common-types/src/dto/UserDto.ts
similarity index 69%
rename from common-types/dto/UserDto.ts
rename to common-types/src/dto/UserDto.ts
index 9b09d3307..37853b3b2 100644
--- a/common-types/dto/UserDto.ts
+++ b/common-types/src/dto/UserDto.ts
@@ -1,10 +1,11 @@
import { Point } from "geojson";
+import OrganisationDto from "./OrganisationDto";
export type UserDto = {
location: Point;
id: string;
email: string;
- organisation: string,
+ organisation: OrganisationDto,
mobile: string
}
diff --git a/common-types/globals.ts b/common-types/src/globals.ts
similarity index 97%
rename from common-types/globals.ts
rename to common-types/src/globals.ts
index a35341e78..67406a5ce 100644
--- a/common-types/globals.ts
+++ b/common-types/src/globals.ts
@@ -121,3 +121,9 @@ export const isUndefined = (value: T | null | undefined): value is undefined
export enum AuthMethod {
IBM_ID = "IBM_ID"
}
+
+export enum Integration {
+ EIS = "EIS",
+ WEATHER = "WEATHER",
+ MESSAGING = "MESSAGING",
+}
diff --git a/common-types/types.d.ts b/common-types/src/index.ts
similarity index 66%
rename from common-types/types.d.ts
rename to common-types/src/index.ts
index a4357a73a..8817b7f57 100644
--- a/common-types/types.d.ts
+++ b/common-types/src/index.ts
@@ -1,27 +1,26 @@
/**
* LatLng representation for the Weather Company
*/
-import { DataFormat, isDefined, isUndefined, Language, Unit } from "./globals";
+import { DataFormat, Integration, isDefined, isUndefined, Language, Unit } from "./globals";
import Crop from "./data-model/Crop";
import Farm, { NewFarm } from "./data-model/Farm";
-import User, { OrganisationUser } from "./data-model/User";
+import User from "./data-model/User";
import Farmer, { NewFarmer } from "./data-model/Farmer";
import Organisation from "./data-model/Organisation";
import FieldCrop from "./data-model/FieldCrop";
import Field, { NewField } from "./data-model/Field";
import { NewUserDto, UserDto } from "./dto/UserDto";
-import OrganisationDto from "./dto/OrganisationDto";
+import OrganisationDto, { UserOrganisationDto } from "./dto/OrganisationDto";
import { EISConfig } from "./integrations/EISConfig";
import { WeatherCompanyConfig } from "./integrations/WeatherCompanyConfig";
import { Point } from "geojson";
export { DataFormat, Language, Unit, isDefined, isUndefined };
-
-export { Crop, Farm, NewFarm, User, OrganisationUser, Farmer, NewFarmer, Organisation, Field, NewField, FieldCrop };
+export { Crop, Farm, NewFarm, User, Farmer, NewFarmer, Organisation, Field, NewField, FieldCrop, Integration};
export {EISConfig, WeatherCompanyConfig}
-export {UserDto, NewUserDto, OrganisationDto};
+export {UserDto, NewUserDto, OrganisationDto, UserOrganisationDto};
export type LatLngNumber = {
lat: number,
@@ -38,22 +37,14 @@ export type GeoCodeNumber = {
longitude: number
}
-export type GeoCodeString = {
- latitude: string,
- longitude: string
-}
-
-export type LatLng = LatLngNumber | LatLngString;
-export type GeoCode = GeoCodeNumber | GeoCodeString;
-
-export function toLatLng(geoCode: GeoCode): LatLng {
+export function toLatLng(geoCode: GeoCodeNumber): LatLngNumber {
return {
lat: geoCode.latitude,
lng: geoCode.longitude
}
}
-export function toGeoCode(latLng: LatLng): GeoCode {
+export function toGeoCode(latLng: LatLngNumber): GeoCodeNumber {
return {
latitude: latLng.lat,
longitude: latLng.lng
@@ -71,11 +62,6 @@ export function geoCodeToString(geocode: GeoCodeNumber) {
return `${geocode.latitude},${geocode.longitude}`
}
-export type BoundingBox = {
- lowerLeft: LatLng,
- upperRight: LatLng
-}
-
export interface CommonOptions {
format: DataFormat;
language: Language;
diff --git a/common-types/integrations/EISConfig.ts b/common-types/src/integrations/EISConfig.ts
similarity index 100%
rename from common-types/integrations/EISConfig.ts
rename to common-types/src/integrations/EISConfig.ts
diff --git a/common-types/integrations/WeatherCompanyConfig.ts b/common-types/src/integrations/WeatherCompanyConfig.ts
similarity index 72%
rename from common-types/integrations/WeatherCompanyConfig.ts
rename to common-types/src/integrations/WeatherCompanyConfig.ts
index f276c4d29..2ac219104 100644
--- a/common-types/integrations/WeatherCompanyConfig.ts
+++ b/common-types/src/integrations/WeatherCompanyConfig.ts
@@ -1,4 +1,4 @@
-import { CommonOptions } from "../types";
+import { CommonOptions } from "../index";
export type WeatherCompanyConfig = {
apiUrl: string;
diff --git a/common-types/tsconfig.json b/common-types/tsconfig.json
new file mode 100644
index 000000000..b255b60a7
--- /dev/null
+++ b/common-types/tsconfig.json
@@ -0,0 +1,28 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "target": "es6",
+ "allowJs": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true,
+ "noFallthroughCasesInSwitch": true,
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "module": "commonjs",
+ "isolatedModules": false,
+ "jsx": "preserve",
+ "declaration": true,
+ "declarationMap": true,
+ "outDir": "./dist",
+ "rootDir": "./src",
+ "noImplicitAny": true,
+ "typeRoots": [
+ "src/index.ts"
+ ]
+ }
+}
diff --git a/package.json b/package.json
deleted file mode 100644
index 12a94bf0f..000000000
--- a/package.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "dependencies": {
- "@carbon/charts": "^0.54.12",
- "@carbon/charts-react": "^0.54.12",
- "carbon-components": "^10.54.0",
- "d3": "^7.3.0"
- }
-}
diff --git a/react-app/package.json b/react-app/package.json
index 0cabab6ab..da84cf742 100644
--- a/react-app/package.json
+++ b/react-app/package.json
@@ -1,5 +1,5 @@
{
- "name": "react-app",
+ "name": "@openharvest/frontend",
"version": "0.1.0",
"private": true,
"dependencies": {
diff --git a/tsconfig.base.json b/tsconfig.base.json
new file mode 100644
index 000000000..c746c7d8c
--- /dev/null
+++ b/tsconfig.base.json
@@ -0,0 +1,27 @@
+{
+ "compilerOptions": {
+ "allowSyntheticDefaultImports": true,
+ "baseUrl": ".",
+ "declaration": false,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "importHelpers": true,
+ "isolatedModules": false,
+ "lib": ["es6", "dom"],
+ "module": "ESNext",
+ "moduleResolution": "node",
+ "noEmitHelpers": true,
+ "pretty": true,
+ "resolveJsonModule": true,
+ "rootDir": ".",
+ "skipLibCheck": true,
+ "sourceMap": true,
+ "strict": true,
+ "target": "es6",
+ "typeRoots": ["node_modules/@types"],
+ "paths": {
+ "@openharvest/common-types": ["./common-types/index.ts"]
+ }
+ },
+ "exclude": ["node_modules", "tmp"]
+}
diff --git a/tsconfig.settings.json b/tsconfig.settings.json
new file mode 100644
index 000000000..51bd005ee
--- /dev/null
+++ b/tsconfig.settings.json
@@ -0,0 +1,13 @@
+{
+ "extends": "./tsconfig.base.json",
+ "compilerOptions": {
+ "declaration": true,
+ "noEmit": false,
+ "composite": true,
+ "incremental": true
+ },
+ "exclude": [
+ "node_modules",
+ "tmp"
+ ]
+}