Added generated SDK + route
This commit is contained in:
		
							parent
							
								
									084e726af7
								
							
						
					
					
						commit
						f454b22569
					
				| @ -1,6 +1,5 @@ | ||||
| import { RowLabelArgs } from "payload/dist/admin/components/forms/RowLabel/types"; | ||||
| import { CollectionGroups, Collections, KeysTypes } from "../../constants"; | ||||
| import { createGetByEndpoint } from "../../endpoints/createGetByEndpoint"; | ||||
| import { createGetSlugsEndpoint } from "../../endpoints/createGetSlugsEndpoint"; | ||||
| import { imageField } from "../../fields/imageField/imageField"; | ||||
| import { keysField } from "../../fields/keysField/keysField"; | ||||
| @ -8,6 +7,7 @@ import { slugField } from "../../fields/slugField/slugField"; | ||||
| import { translatedFields } from "../../fields/translatedFields/translatedFields"; | ||||
| import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig"; | ||||
| import { AppearanceRowLabel } from "./components/AppearanceRowLabel"; | ||||
| import { getBySlugEndpoint } from "./endpoints/getBySlugEndpoint"; | ||||
| import { importFromStrapi } from "./endpoints/importFromStrapi"; | ||||
| 
 | ||||
| const fields = { | ||||
| @ -46,7 +46,7 @@ export const Weapons = buildVersionedCollectionConfig({ | ||||
|   endpoints: [ | ||||
|     importFromStrapi, | ||||
|     createGetSlugsEndpoint(Collections.Weapons), | ||||
|     createGetByEndpoint(Collections.Weapons, "slug"), | ||||
|     getBySlugEndpoint, | ||||
|   ], | ||||
|   fields: [ | ||||
|     { | ||||
|  | ||||
							
								
								
									
										124
									
								
								src/collections/Weapons/endpoints/getBySlugEndpoint.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								src/collections/Weapons/endpoints/getBySlugEndpoint.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,124 @@ | ||||
| import { Collections } from "../../../constants"; | ||||
| import { createGetByEndpoint } from "../../../endpoints/createGetByEndpoint"; | ||||
| import { EndpointBasicWeapon, EndpointWeapon } from "../../../sdk"; | ||||
| import { Key, Language, Recorder, Weapon, WeaponsThumbnail } from "../../../types/collections"; | ||||
| import { isDefined, isUndefined } from "../../../utils/asserts"; | ||||
| 
 | ||||
| export const getBySlugEndpoint = createGetByEndpoint( | ||||
|   Collections.Weapons, | ||||
|   "slug", | ||||
|   (weapon: Weapon): EndpointWeapon => { | ||||
|     let group: EndpointWeapon["group"] = undefined; | ||||
| 
 | ||||
|     // We only send the group if the group has at least 2 weapons (1 weapon beside the current one)
 | ||||
|     // The weapons are ordered alphabetically using their slugs
 | ||||
|     if ( | ||||
|       typeof weapon.group === "object" && | ||||
|       isDefined(weapon.group.weapons) && | ||||
|       weapon.group.weapons.length > 1 | ||||
|     ) { | ||||
|       const { slug, translations = [], weapons } = weapon.group; | ||||
| 
 | ||||
|       const groupWeapons: EndpointBasicWeapon[] = []; | ||||
|       weapons.forEach((groupWeapon) => { | ||||
|         if (typeof groupWeapon === "object" && groupWeapon.id !== weapon.id) { | ||||
|           groupWeapons.push(convertWeaponToEndpointBasicWeapon(groupWeapon)); | ||||
|         } | ||||
|       }); | ||||
| 
 | ||||
|       groupWeapons.sort((a, b) => a.slug.localeCompare(b.slug)); | ||||
| 
 | ||||
|       group = { | ||||
|         slug, | ||||
|         translations: translations.map(({ language, name }) => ({ | ||||
|           language: getLanguageId(language), | ||||
|           name, | ||||
|         })), | ||||
|         weapons: groupWeapons, | ||||
|       }; | ||||
|     } | ||||
| 
 | ||||
|     return { | ||||
|       ...convertWeaponToEndpointBasicWeapon(weapon), | ||||
|       appearances: weapon.appearances.map(({ categories, translations }) => ({ | ||||
|         categories: categories.map(getKeyId), | ||||
|         translations: translations.map( | ||||
|           ({ | ||||
|             language, | ||||
|             sourceLanguage, | ||||
|             transcribers = [], | ||||
|             translators = [], | ||||
|             proofreaders = [], | ||||
|             ...otherTranslatedProps | ||||
|           }) => ({ | ||||
|             language: getLanguageId(language), | ||||
|             sourceLanguage: getLanguageId(sourceLanguage), | ||||
|             transcribers: transcribers.map(getRecorderId), | ||||
|             translators: translators.map(getRecorderId), | ||||
|             proofreaders: proofreaders.map(getRecorderId), | ||||
|             ...otherTranslatedProps, | ||||
|           }) | ||||
|         ), | ||||
|       })), | ||||
|       group, | ||||
|     }; | ||||
|   } | ||||
| ); | ||||
| 
 | ||||
| const getRecorderId = (recorder: string | Recorder) => | ||||
|   typeof recorder === "object" ? recorder.id : recorder; | ||||
| const getKeyId = (key: string | Key) => (typeof key === "object" ? key.id : key); | ||||
| const getLanguageId = (language: string | Language) => | ||||
|   typeof language === "object" ? language.id : language; | ||||
| 
 | ||||
| const getThumbnail = (thumbnail?: string | WeaponsThumbnail): WeaponsThumbnail | undefined => { | ||||
|   if (isUndefined(thumbnail)) return undefined; | ||||
|   if (typeof thumbnail === "string") return undefined; | ||||
|   delete thumbnail.weapon; | ||||
|   return thumbnail; | ||||
| }; | ||||
| 
 | ||||
| const convertWeaponToEndpointBasicWeapon = ({ | ||||
|   slug, | ||||
|   thumbnail, | ||||
|   type, | ||||
|   appearances, | ||||
| }: Weapon): EndpointBasicWeapon => { | ||||
|   const categories = new Set<string>(); | ||||
|   appearances.forEach((appearance) => | ||||
|     appearance.categories.forEach((category) => categories.add(getKeyId(category))) | ||||
|   ); | ||||
| 
 | ||||
|   const languages = new Set<string>(); | ||||
|   appearances.forEach(({ translations }) => | ||||
|     translations.forEach(({ language }) => languages.add(getLanguageId(language))) | ||||
|   ); | ||||
| 
 | ||||
|   const translations: EndpointWeapon["translations"] = [...languages.values()].map( | ||||
|     (targetLanguage) => { | ||||
|       const names = new Set<string>(); | ||||
|       appearances.forEach(({ translations }) => { | ||||
|         const translation = translations.find( | ||||
|           ({ language }) => getLanguageId(language) === targetLanguage | ||||
|         ); | ||||
|         if (translation) { | ||||
|           names.add(translation.name); | ||||
|         } | ||||
|       }); | ||||
|       const [name, ...aliases] = names; | ||||
| 
 | ||||
|       if (isUndefined(name)) | ||||
|         throw new Error("A weapon should always have a name for each of its translatiion"); | ||||
| 
 | ||||
|       return { language: targetLanguage, name: name, aliases }; | ||||
|     } | ||||
|   ); | ||||
| 
 | ||||
|   return { | ||||
|     slug, | ||||
|     thumbnail: getThumbnail(thumbnail), | ||||
|     type: getKeyId(type), | ||||
|     categories: [...categories.values()], | ||||
|     translations, | ||||
|   }; | ||||
| }; | ||||
							
								
								
									
										129
									
								
								src/sdk.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								src/sdk.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,129 @@ | ||||
| import { Collections } from "./constants"; | ||||
| import { WeaponsThumbnail } from "./types/collections"; | ||||
| 
 | ||||
| class NodeCache { | ||||
|   constructor(_params: any) {} | ||||
|   getTtl(_key: string): number | undefined { | ||||
|     return undefined; | ||||
|   } | ||||
|   get<T>(_key: string): T | undefined { | ||||
|     return undefined; | ||||
|   } | ||||
|   set<T>(_key: string, _value: T, _ttl: number | string) {} | ||||
| } | ||||
| 
 | ||||
| // END MOCKING SECTION
 | ||||
| 
 | ||||
| const REFRESH_FREQUENCY_IN_SEC = 60; | ||||
| const CACHE = new NodeCache({ | ||||
|   checkperiod: REFRESH_FREQUENCY_IN_SEC, | ||||
|   deleteOnExpire: true, | ||||
|   forceString: true, | ||||
|   maxKeys: 1, | ||||
| }); | ||||
| const TOKEN_KEY = "token"; | ||||
| 
 | ||||
| type PayloadLoginResponse = { | ||||
|   token: string; | ||||
|   exp: number; | ||||
| }; | ||||
| 
 | ||||
| const refreshToken = async () => { | ||||
|   const loginUrl = payloadApiUrl(Collections.Recorders, "login"); | ||||
|   const loginResult = await fetch(loginUrl, { | ||||
|     method: "POST", | ||||
|     headers: { "Content-Type": "application/json" }, | ||||
|     body: JSON.stringify({ | ||||
|       email: process.env.PAYLOAD_USER, | ||||
|       password: process.env.PAYLOAD_PASSWORD, | ||||
|     }), | ||||
|   }); | ||||
|   logResponse(loginResult); | ||||
| 
 | ||||
|   if (loginResult.status !== 200) { | ||||
|     throw new Error("Unable to login"); | ||||
|   } | ||||
| 
 | ||||
|   const loginJson = (await loginResult.json()) as PayloadLoginResponse; | ||||
|   const { token, exp } = loginJson; | ||||
|   const now = Math.floor(Date.now() / 1000); | ||||
|   const ttl = Math.floor(exp - now - REFRESH_FREQUENCY_IN_SEC * 2); | ||||
|   const ttlInMinutes = Math.floor(ttl / 60); | ||||
|   console.log("Token was refreshed. TTL is", ttlInMinutes, "minutes."); | ||||
|   CACHE.set(TOKEN_KEY, token, ttl); | ||||
|   return token; | ||||
| }; | ||||
| 
 | ||||
| const getToken = async (): Promise<string> => { | ||||
|   const cachedToken = CACHE.get<string>(TOKEN_KEY); | ||||
|   if (cachedToken !== undefined) { | ||||
|     const cachedTokenTtl = CACHE.getTtl(TOKEN_KEY) as number; | ||||
|     const diffInMinutes = Math.floor((cachedTokenTtl - Date.now()) / 1000 / 60); | ||||
|     console.log("Retrieved token from cache. TTL is", diffInMinutes, "minutes."); | ||||
|     return cachedToken; | ||||
|   } | ||||
|   console.log("Refreshing token"); | ||||
|   return await refreshToken(); | ||||
| }; | ||||
| 
 | ||||
| const injectAuth = async (init?: RequestInit): Promise<RequestInit> => ({ | ||||
|   ...init, | ||||
|   headers: { ...init?.headers, Authorization: `JWT ${await getToken()}` }, | ||||
| }); | ||||
| 
 | ||||
| const logResponse = (res: Response) => console.log(res.status, res.statusText, res.url); | ||||
| 
 | ||||
| const payloadApiUrl = (collection: Collections, endpoint?: string): string => | ||||
|   `${process.env.PAYLOAD_API_URL}/${collection}${endpoint === undefined ? "" : `/${endpoint}`}`; | ||||
| 
 | ||||
| const request = async (url: string, init?: RequestInit): Promise<Response> => { | ||||
|   const result = await fetch(url, await injectAuth(init)); | ||||
|   logResponse(result); | ||||
| 
 | ||||
|   if (result.status !== 200) { | ||||
|     throw new Error("Unhandled fetch error"); | ||||
|   } | ||||
| 
 | ||||
|   return result; | ||||
| }; | ||||
| 
 | ||||
| // SDK and Types
 | ||||
| 
 | ||||
| export type EndpointWeapon = EndpointBasicWeapon & { | ||||
|   appearances: { | ||||
|     categories: string[]; | ||||
|     translations: { | ||||
|       language: string; | ||||
|       sourceLanguage: string; | ||||
|       name: string; | ||||
|       description?: string; | ||||
|       level1?: string; | ||||
|       level2?: string; | ||||
|       level3?: string; | ||||
|       level4?: string; | ||||
|       transcribers: string[]; | ||||
|       translators: string[]; | ||||
|       proofreaders: string[]; | ||||
|     }[]; | ||||
|   }[]; | ||||
|   group?: { | ||||
|     slug: string; | ||||
|     translations: { language: string; name: string }[]; | ||||
|     weapons: EndpointBasicWeapon[]; | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
| export type EndpointBasicWeapon = { | ||||
|   slug: string; | ||||
|   type: string; | ||||
|   categories: string[]; | ||||
|   translations: { language: string; name: string; aliases: string[] }[]; | ||||
|   thumbnail?: WeaponsThumbnail; | ||||
| }; | ||||
| 
 | ||||
| export const payload = { | ||||
|   getSlugsWeapons: async (): Promise<string[]> => | ||||
|     await (await request(payloadApiUrl(Collections.Weapons, "slugs"))).json(), | ||||
|   getWeapon: async (slug: string): Promise<EndpointWeapon> => | ||||
|     await (await request(payloadApiUrl(Collections.Weapons, `slug/${slug}`))).json(), | ||||
| }; | ||||
| @ -1,5 +1,6 @@ | ||||
| import "dotenv/config"; | ||||
| import express from "express"; | ||||
| import { readFileSync } from "fs"; | ||||
| import path from "path"; | ||||
| import payload from "payload"; | ||||
| import { Collections, RecordersRoles } from "./constants"; | ||||
| @ -46,7 +47,7 @@ const start = async () => { | ||||
|             email: process.env.SEEDING_ADMIN_EMAIL, | ||||
|             password: process.env.SEEDING_ADMIN_PASSWORD, | ||||
|             username: process.env.SEEDING_ADMIN_USERNAME, | ||||
|             role: [RecordersRoles.Admin], | ||||
|             role: [RecordersRoles.Admin, RecordersRoles.Api], | ||||
|             anonymize: false, | ||||
|           }; | ||||
|           await payload.create({ | ||||
| @ -61,6 +62,22 @@ const start = async () => { | ||||
|   // Add your own express routes here
 | ||||
|   app.use("/public", express.static(path.join(__dirname, "../public"))); | ||||
| 
 | ||||
|   app.get("/api/sdk", (_, res) => { | ||||
|     const collections = readFileSync(path.join(__dirname, "types/collections.ts"), "utf-8"); | ||||
| 
 | ||||
|     const constantsHeader = "/////////////// CONSTANTS ///////////////\n"; | ||||
|     const constants = readFileSync(path.join(__dirname, "constants.ts"), "utf-8"); | ||||
| 
 | ||||
|     const sdkHeader = "////////////////// SDK //////////////////\n"; | ||||
|     const sdkLines = readFileSync(path.join(__dirname, "sdk.ts"), "utf-8").split("\n"); | ||||
|     const endMockingLine = sdkLines.findIndex((line) => line === "// END MOCKING SECTION") ?? 0; | ||||
|     const sdk = | ||||
|       `import NodeCache from "node-cache";\n\n` + sdkLines.slice(endMockingLine + 1).join("\n"); | ||||
| 
 | ||||
|     res.type("text/plain"); | ||||
|     res.send([collections, constantsHeader, constants, sdkHeader, sdk].join("\n\n")); | ||||
|   }); | ||||
| 
 | ||||
|   app.get("/robots.txt", (_, res) => { | ||||
|     res.type("text/plain"); | ||||
|     res.send("User-agent: *\nDisallow: /"); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 DrMint
						DrMint