Skip to content

Commit a7509dc

Browse files
committed
init
0 parents  commit a7509dc

18 files changed

Lines changed: 7029 additions & 0 deletions

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules/
2+
actions/*/dist/
3+
*.tsbuildinfo
4+
.idea
5+
.env

.tool-versions

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
nodejs 24.13.1

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# centaurus
2+
The "central" place for all actions

actions/gls-action/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.env

actions/gls-action/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# ENV
2+
| ENV Variable | Description | Default Value |
3+
|------------------------|------------------------------------------------|---------------------|
4+
| `HERCULES_AUTH_TOKEN` | Authentication token for connecting to Aquila. | `""` (empty string) |
5+
| `HERCULES_AQUILA_URL` | URL of the Aquila server to connect to. | `"localhost:50051"` |
6+
| `HERCULES_ACTION_ID` | Identifier for the action being registered. | `"gls-action"` |
7+
| `HERCULES_SDK_VERSION` | Version of the Hercules SDK being used. | `"0.0.0"` |

actions/gls-action/package.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "@code0-tech/gls-action",
3+
"version": "0.0.0",
4+
"private": true,
5+
"type": "module",
6+
"main": "./dist/index.js",
7+
"types": "./dist/index.d.ts",
8+
"scripts": {
9+
"build": "tsc --build",
10+
"lint": "eslint ."
11+
}
12+
}

actions/gls-action/src/helpers.ts

Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
import {ZodError, ZodObject} from "zod";
2+
import {createAuxiliaryTypeStore, printNode, zodToTs} from "zod-to-ts";
3+
import ts from "typescript";
4+
import {
5+
AllowedServicesRequestData, AllowedServicesResponseData, AllowedServicesResponseDataSchema,
6+
AuthenticationRequestData, AuthenticationRequestDataSchema, AuthenticationResponseDataSchema,
7+
CancelShipmentRequestData, CancelShipmentResponseData,
8+
CancelShipmentResponseDataSchema, CreateParcelsResponse,
9+
CreateParcelsResponseSchema, CustomContent, EndOfDayRequestData, EndOfDayResponseData,
10+
EndOfDayResponseDataSchema, InternalShipmentRequestData,
11+
InternalShipmentServiceSchema, InternalShipmentUnitSchema, InternalShipper, InternalValidateShipmentRequestData,
12+
PrintingOptions,
13+
ReprintParcelRequestData, ReprintParcelResponseData,
14+
ReprintParcelResponseDataSchema, ReturnOptions, ShipmentRequestData, ShipmentRequestDataSchema, ShipmentService,
15+
ShipmentWithoutServices, Shipper,
16+
UpdateParcelWeightRequestData,
17+
UpdateParcelWeightResponseData,
18+
UpdateParcelWeightResponseDataSchema, ValidateShipmentRequestData, ValidateShipmentResponseData,
19+
ValidateShipmentResponseDataSchema
20+
} from "./types";
21+
import axios from "axios";
22+
import {HerculesFunctionContext, RuntimeErrorException} from "@code0-tech/hercules";
23+
24+
export function zodSchemaToTypescriptDefs(
25+
typeName: string,
26+
zodSchema: ZodObject<any>,
27+
extraSchemas: Record<string, any> = {}
28+
): Map<string, string> {
29+
const store = createAuxiliaryTypeStore();
30+
const result = new Map<string, string>();
31+
32+
for (const [name, schema] of Object.entries(extraSchemas)) {
33+
const {node} = zodToTs(schema, {auxiliaryTypeStore: store});
34+
35+
const alias = ts.factory.createTypeAliasDeclaration(
36+
undefined,
37+
ts.factory.createIdentifier(name),
38+
undefined,
39+
node
40+
);
41+
42+
store.definitions.set(schema, {
43+
identifier: ts.factory.createIdentifier(name),
44+
node: alias
45+
});
46+
47+
result.set(name, printNode(alias).replace(`type ${name} =`, ``));
48+
}
49+
50+
const {node} = zodToTs(zodSchema, {auxiliaryTypeStore: store});
51+
result.set(typeName, printNode(node));
52+
53+
return result;
54+
}
55+
56+
let cachedToken = {
57+
token: "",
58+
tokenType: "",
59+
expiresAt: 0,
60+
}
61+
62+
export const getAuthToken = async (context: HerculesFunctionContext) => {
63+
const data: AuthenticationRequestData = {
64+
client_id: context.matchedConfig.findConfig("client_id") as string,
65+
client_secret: context.matchedConfig.findConfig("client_secret") as string,
66+
grant_type: "client_credentials"
67+
}
68+
if (cachedToken.expiresAt > Date.now()) {
69+
console.log("Using cached access token")
70+
return cachedToken.token
71+
}
72+
73+
74+
const authValue = await axios.post("https://api-sandbox.gls-group.net/oauth2/v2/token", AuthenticationRequestDataSchema.parse(data), {
75+
headers: {
76+
"Content-Type": "application/x-www-form-urlencoded"
77+
}
78+
})
79+
const result = AuthenticationResponseDataSchema.parse(authValue.data)
80+
console.log("Authentication successful, access token:", result.token_type, result.access_token.substring(0, 10) + "...")
81+
cachedToken = {
82+
token: result.access_token,
83+
tokenType: result.token_type,
84+
expiresAt: Date.now() + (result.expires_in - 60) * 1000
85+
}
86+
return result.access_token
87+
}
88+
89+
export const validateShipment = async (data: ValidateShipmentRequestData, context: HerculesFunctionContext): Promise<ValidateShipmentResponseData> => {
90+
const url = context?.matchedConfig.findConfig("api_url") as string;
91+
const contactID = context?.matchedConfig.findConfig("contact_id") as string || ""
92+
93+
try {
94+
const result = await axios.post(`${url}/rs/shipments/validate`, transformValidateShipmentRequestDataToInternalFormat(data, context, contactID), {
95+
headers: {
96+
Authorization: `Bearer ${await getAuthToken(context)}`,
97+
"Content-Type": "application/glsVersion1+json"
98+
}
99+
})
100+
return ValidateShipmentResponseDataSchema.parse(result.data)
101+
} catch (error: any) {
102+
throw error
103+
}
104+
105+
}
106+
export const reprintParcel = async (data: ReprintParcelRequestData, context: HerculesFunctionContext): Promise<ReprintParcelResponseData> => {
107+
const url = context.matchedConfig.findConfig("api_url") as string;
108+
109+
try {
110+
const result = await axios.post(`${url}/rs/shipments/reprintparcel`, data, {
111+
headers: {
112+
Authorization: `Bearer ${await getAuthToken(context)}`,
113+
"Content-Type": "application/glsVersion1+json"
114+
}
115+
})
116+
return ReprintParcelResponseDataSchema.parse(result.data)
117+
} catch (error: any) {
118+
console.error("Error response from GLS API:", error)
119+
throw error
120+
}
121+
}
122+
123+
export const updateParcelWeight = async (data: UpdateParcelWeightRequestData, context: HerculesFunctionContext): Promise<UpdateParcelWeightResponseData> => {
124+
const url = context.matchedConfig.findConfig("api_url") as string;
125+
126+
try {
127+
const result = await axios.post(`${url}/rs/shipments/updateparcelweight`, data, {
128+
headers: {
129+
Authorization: `Bearer ${await getAuthToken(context)}`,
130+
"Content-Type": "application/glsVersion1+json"
131+
}
132+
})
133+
return UpdateParcelWeightResponseDataSchema.parse(result.data)
134+
} catch (error: any) {
135+
console.log(error)
136+
throw error
137+
}
138+
139+
}
140+
141+
export const getEndOfDayInfo = async (data: EndOfDayRequestData, context: HerculesFunctionContext): Promise<EndOfDayResponseData> => {
142+
const url = context.matchedConfig.findConfig("api_url") as string;
143+
144+
try {
145+
const result = await axios.post(`${url}/rs/shipments/endofday?date=${data.date}`, {}, {
146+
headers: {
147+
Authorization: `Bearer ${await getAuthToken(context)}`,
148+
"Content-Type": "application/glsVersion1+json"
149+
}
150+
})
151+
return EndOfDayResponseDataSchema.parse(result.data)
152+
} catch (error: any) {
153+
console.error("Error response from GLS API:", error)
154+
throw error
155+
}
156+
}
157+
158+
export const getAllowedServices = async (data: AllowedServicesRequestData, context: HerculesFunctionContext): Promise<AllowedServicesResponseData> => {
159+
const url = context.matchedConfig.findConfig("api_url") as string;
160+
161+
try {
162+
const result = await axios.post(`${url}/rs/shipments/allowedservices`, data, {
163+
headers: {
164+
Authorization: `Bearer ${await getAuthToken(context)}`,
165+
"Content-Type": "application/glsVersion1+json"
166+
}
167+
})
168+
return AllowedServicesResponseDataSchema.parse(result.data)
169+
} catch (error: any) {
170+
if (error.response?.data) {
171+
console.error("Error response from GLS API:", error.response.data)
172+
} else if (error.response?.headers) {
173+
const headers = error?.response.headers;
174+
console.error("Error sending request to GLS API:", headers.error, headers.args)
175+
throw new RuntimeErrorException("ERROR_FETCHING_ALLOWED_SERVICES", `GLS API error: ${headers.error}, args: ${headers.args}`)
176+
} else if (error instanceof ZodError) {
177+
console.error("Error sending request to GLS API:", error.message)
178+
} else {
179+
console.error("Error sending request to GLS API:", error)
180+
}
181+
throw error
182+
}
183+
184+
}
185+
186+
export const cancelShipment = async (data: CancelShipmentRequestData, context: HerculesFunctionContext): Promise<CancelShipmentResponseData> => {
187+
const url = context.matchedConfig.findConfig("api_url") as string;
188+
189+
190+
try {
191+
const result = await axios.post(`${url}/rs/shipments/cancel/${data.TrackID}`, {}, {
192+
headers: {
193+
Authorization: `Bearer ${await getAuthToken(context)}`,
194+
"Content-Type": "application/glsVersion1+json"
195+
}
196+
})
197+
return CancelShipmentResponseDataSchema.parse(result.data)
198+
} catch (error: any) {
199+
if (error.response?.headers?.args) {
200+
const headers = error.response.headers;
201+
console.error("Error sending cancel shipment request to GLS API:", headers.error, headers.args)
202+
throw new RuntimeErrorException("ERROR_CANCELING_GLS_SHIPMENT", `GLS API error: ${headers.error}, args: ${headers.args}`)
203+
}
204+
console.error("Error sending cancel shipment request to GLS API:", error)
205+
throw error
206+
}
207+
}
208+
209+
const postShipments = async (data: ShipmentRequestData, context: HerculesFunctionContext): Promise<CreateParcelsResponse> => {
210+
const contactID = context.matchedConfig.findConfig("contact_id") as string;
211+
const url = context.matchedConfig.findConfig("api_url") as string;
212+
213+
const parsedData: InternalShipmentRequestData = transformShipmentRequestDataToInternalFormat(ShipmentRequestDataSchema.parse(data), context, contactID);
214+
215+
216+
try {
217+
const result = await axios.post(`${url}/rs/shipments`, parsedData, {
218+
headers: {
219+
Authorization: `Bearer ${await getAuthToken(context)}`,
220+
"Content-Type": "application/glsVersion1+json"
221+
}
222+
})
223+
224+
return CreateParcelsResponseSchema.parse(result.data) as CreateParcelsResponse
225+
} catch (error: any) {
226+
if (error.response?.data) {
227+
console.error("Error response from GLS API:", error.response.data)
228+
} else if (error.response?.headers) {
229+
const headers = error?.response.headers;
230+
console.error("Error sending request to GLS API:", headers.error, headers.args)
231+
} else if (error instanceof ZodError) {
232+
console.error("Error sending request to GLS API:", error.message)
233+
} else {
234+
console.error("Error sending request to GLS API:", error)
235+
}
236+
return Promise.reject(error)
237+
}
238+
}
239+
240+
export function transformValidateShipmentRequestDataToInternalFormat(data: ValidateShipmentRequestData, context: HerculesFunctionContext | undefined, contactID: string): InternalValidateShipmentRequestData {
241+
return {
242+
...data,
243+
Shipment: {
244+
...data.Shipment,
245+
Middleware: "CodeZeroviaGLS",
246+
Shipper: getShipper(data.Shipment.Shipper, context, contactID),
247+
Service: InternalShipmentServiceSchema.parse(data.Shipment.Service),
248+
ShipmentUnit: InternalShipmentUnitSchema.parse(data.Shipment.ShipmentUnit)
249+
}
250+
}
251+
}
252+
253+
function transformShipmentRequestDataToInternalFormat(data: ShipmentRequestData, context: HerculesFunctionContext | undefined, contactID: string | undefined): InternalShipmentRequestData {
254+
return {
255+
...data,
256+
Shipment: {
257+
...data.Shipment,
258+
Middleware: "CodeZeroviaGLS",
259+
Shipper: getShipper(data.Shipment.Shipper, context, contactID),
260+
Service: InternalShipmentServiceSchema.parse(data.Shipment.Service),
261+
ShipmentUnit: InternalShipmentUnitSchema.parse(data.Shipment.ShipmentUnit)
262+
}
263+
}
264+
}
265+
266+
function getShipper(shipper: Shipper, context: HerculesFunctionContext | undefined, contactID: string | undefined): InternalShipper {
267+
const configShipper = context?.matchedConfig.findConfig("shipper") as Shipper || undefined
268+
269+
if (configShipper) {
270+
return {
271+
...configShipper,
272+
ContactID: contactID
273+
}
274+
} else {
275+
return {
276+
...shipper,
277+
ContactID: contactID
278+
}
279+
}
280+
}
281+
282+
export async function postShipmentHelper(context: HerculesFunctionContext, services: ShipmentService, shipment: ShipmentWithoutServices, printingOptions: PrintingOptions, customContent?: CustomContent, returnOptions?: ReturnOptions): Promise<CreateParcelsResponse> {
283+
try {
284+
return await postShipments({
285+
PrintingOptions: printingOptions,
286+
CustomContent: customContent,
287+
ReturnOptions: returnOptions,
288+
Shipment: {
289+
...shipment,
290+
Service: services
291+
},
292+
}, context)
293+
} catch (error) {
294+
if (typeof error === "string") {
295+
throw new RuntimeErrorException("ERROR_CREATING_GLS_SHIPMENT", error)
296+
}
297+
throw new RuntimeErrorException("ERROR_CREATING_GLS_SHIPMENT", "An error occurred while creating the shipment.")
298+
}
299+
}

0 commit comments

Comments
 (0)