-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtransaction.ts
More file actions
243 lines (209 loc) · 9.12 KB
/
transaction.ts
File metadata and controls
243 lines (209 loc) · 9.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
import { DataSource, DeleteResult } from "typeorm";
import { LocationAddress } from "../../entities/LocationAddress";
import {
CreateLocationAddressBulkDTO,
CreateLocationAddressDTO,
GetLocationAddressDTO,
UpdateLocationAddressBulkDTO,
UpdateLocationAddressBulkResponse,
UpdateLocationAddressDTO,
UpdateLocationAddressResponse,
} from "../../types/Location";
import { plainToClass } from "class-transformer";
import { FemaDisaster } from "../../entities/FemaDisaster";
import { User } from "../../entities/User";
import { logMessageToFile } from "../../utilities/logger";
import Boom from "@hapi/boom";
export interface ILocationAddressTransaction {
/**
* Adds a new location address to the database
* @param payload The location information to be inserted into the database
* @returns Promise resolving to inserted LocationAddress or null if failed
*/
createLocationAddress(payload: CreateLocationAddressDTO, companyId: string): Promise<LocationAddress | null>;
createLocationAddressBulk(payload: CreateLocationAddressBulkDTO, companyId: string): Promise<LocationAddress[]>;
/**
* Finds an existing location address in the database
* @param payload The id of the location that must be fetched
* @returns Promise resolving the LocationAddress associated with the given ID or null if the value does not exist
*/
getLocationAddressById(payload: GetLocationAddressDTO): Promise<LocationAddress | null>;
/**
* Removes the location address with the given id
* @param payload the id of the location that must be removed
* @return the location address that was removed
*/
removeLocationAddressById(payload: GetLocationAddressDTO): Promise<DeleteResult>;
/**
* Gets all user-disaster pairs based on locations affedted by new disasters, for notification creation
* @param disasters Array of FEMA disasters to check
* @returns Promise resolving to array of user-disaster pairs
*/
getUsersAffectedByDisasters(
disasters: FemaDisaster[]
): Promise<{ user: User; disaster: FemaDisaster; location: LocationAddress }[]>;
/**
* Updates a location address by its id
* @param payload The updated location address
*/
updateLocationAddressById(
payload: UpdateLocationAddressDTO,
companyId: string
): Promise<UpdateLocationAddressResponse | null>;
/**
* Updates multiple location addresses in bulk by their ids
* @param payload The updated location addresses
*/
updateLocationAddressBulk(
payload: UpdateLocationAddressBulkDTO,
companyId: string
): Promise<UpdateLocationAddressBulkResponse>;
}
/**
* All of the supported transactions for a LocationAddress
*/
export class LocationAddressTransactions implements ILocationAddressTransaction {
private db: DataSource;
constructor(db: DataSource) {
this.db = db;
}
async createLocationAddressBulk(
payload: CreateLocationAddressBulkDTO,
companyId: string
): Promise<LocationAddress[]> {
const addresses: LocationAddress[] = payload.map((element) =>
plainToClass(LocationAddress, {
...element,
companyId: companyId,
})
);
const newAddress: LocationAddress[] = await this.db.getRepository(LocationAddress).save(addresses);
return newAddress;
}
/**
* Adds a new location address to the database
* @param payload The location information to be inserted into the database
* @returns Promise resolving to inserted LocationAddress or null if failed
*/
async createLocationAddress(payload: CreateLocationAddressDTO, companyId: string): Promise<LocationAddress | null> {
const address: LocationAddress = plainToClass(LocationAddress, {
...payload,
companyId: companyId,
});
const newAddress: LocationAddress = await this.db.getRepository(LocationAddress).save(address);
return newAddress ?? null;
}
/**
* Finds an existing location address in the database
* @param payload The
* @returns Promise resolving the LocationAddress associated with the given ID or null if the value does not exist
*/
async getLocationAddressById(payload: GetLocationAddressDTO): Promise<LocationAddress | null> {
const { id: givenId } = payload;
const maybeFoundLocation = await this.db.manager.findOneBy(LocationAddress, { id: givenId });
return maybeFoundLocation;
}
async removeLocationAddressById(payload: GetLocationAddressDTO): Promise<DeleteResult> {
const id = payload.id;
const result = await this.db.manager.delete(LocationAddress, { id: id });
return result;
}
async getUsersAffectedByDisasters(
disasters: FemaDisaster[]
): Promise<{ user: User; disaster: FemaDisaster; location: LocationAddress }[]> {
const fipsPairs = disasters.map((d) => ({
fipsStateCode: d.fipsStateCode,
fipsCountyCode: d.fipsCountyCode,
disaster: d,
}));
if (fipsPairs.length === 0) {
return [];
}
const query = this.db
.getRepository(LocationAddress)
.createQueryBuilder("location")
.innerJoinAndSelect("location.company", "company")
.innerJoinAndSelect("company.user", "user");
// Build OR conditions for each FIPS location pair
const conditions = fipsPairs
.map(
(pair, index) =>
`(location.fipsStateCode = :state${index} AND location.fipsCountyCode = :county${index})`
)
.join(" OR ");
query.where(`(${conditions})`);
// Set parameters for type enforcing
const parameters: any = {};
fipsPairs.forEach((pair, index) => {
parameters[`state${index}`] = pair.fipsStateCode;
parameters[`county${index}`] = pair.fipsCountyCode;
});
query.setParameters(parameters);
const locations = await query.getMany();
logMessageToFile(`There are ${locations.length} locations that are affected by new FEMA Disasters.`);
// Map to user-disaster pairs
const userDisasterPairs: { user: User; disaster: FemaDisaster; location: LocationAddress }[] = [];
for (const location of locations) {
// Find which disasters affect this location, there could be multiple
const affectingDisasters = disasters.filter(
(disaster) =>
disaster.fipsStateCode === location.fipsStateCode &&
disaster.fipsCountyCode === location.fipsCountyCode
);
// Create user-disaster pairs for each affecting disaster, to easily convert pairs to notifications
for (const disaster of affectingDisasters) {
if (location.company?.user) {
logMessageToFile(`User ${location.company.user} is affected by disaster ${disaster}.`);
userDisasterPairs.push({
user: location.company.user,
disaster: disaster,
location: location,
});
}
}
}
return userDisasterPairs;
}
async updateLocationAddressById(
payload: UpdateLocationAddressDTO,
companyId: string
): Promise<UpdateLocationAddressResponse | null> {
const updateData = Object.fromEntries(
Object.entries(payload).filter(([_, value]) => value !== null)
) as Partial<LocationAddress>;
const updateResponse = await this.db.manager.update(
LocationAddress,
{ id: payload.id, companyId: companyId },
updateData
);
return updateResponse.affected === 1 ? this.db.manager.findOneBy(LocationAddress, { id: payload.id }) : null;
}
async updateLocationAddressBulk(
payload: UpdateLocationAddressBulkDTO,
companyId: string
): Promise<UpdateLocationAddressBulkResponse> {
const updatedLocations: LocationAddress[] = [];
await this.db.transaction(async (manager) => {
for (const location of payload) {
const updateData = Object.fromEntries(
Object.entries(location).filter(([_, value]) => value !== null)
) as Partial<LocationAddress>;
const updateResponse = await manager.update(
LocationAddress,
{ id: location.id, companyId: companyId },
updateData
);
if (updateResponse.affected === 1) {
const updatedLocation = await manager.findOneBy(LocationAddress, { id: location.id });
if (updatedLocation) {
updatedLocations.push(updatedLocation);
}
} else {
// will rollback the transaction if any update fails
throw Boom.internal(`Failed to update policy with ID: ${location.id}`);
}
}
});
return updatedLocations;
}
}