bugfixes, improving API request wrapper

This commit is contained in:
2025-03-12 22:30:26 -07:00
parent ca1ad74834
commit 7eec2adc7a
11 changed files with 105 additions and 64 deletions

View File

@@ -1,5 +1,4 @@
import dotenvFlow from "dotenv-flow"; import _ from "./config/dotenv.ts";
dotenvFlow.config();
import { resolve } from "path"; import { resolve } from "path";
export default { export default {

View File

@@ -1,6 +1,10 @@
import { axiosInstance } from "./axios.ts"; import { axiosInstance } from "./axios.ts";
import { type AxiosResponse, type AxiosRequestConfig } from "axios"; import {
type AxiosResponse,
type AxiosRequestConfig,
type RawAxiosRequestHeaders,
} from "axios";
import type { import type {
AddItemsToPlaylist, AddItemsToPlaylist,
EndpointHandlerBaseArgs, EndpointHandlerBaseArgs,
@@ -9,7 +13,6 @@ import type {
GetPlaylist, GetPlaylist,
GetPlaylistItems, GetPlaylistItems,
RemovePlaylistItems, RemovePlaylistItems,
Req,
Res, Res,
} from "spotify_manager/index.d.ts"; } from "spotify_manager/index.d.ts";
@@ -34,7 +37,7 @@ enum allowedMethods {
* @param inlineData true if `data` is to be placed inside config (say, axios' delete method) * @param inlineData true if `data` is to be placed inside config (say, axios' delete method)
*/ */
const singleRequest = async <RespDataType>( const singleRequest = async <RespDataType>(
req: Req, authHeaders: RawAxiosRequestHeaders,
res: Res, res: Res,
method: allowedMethods, method: allowedMethods,
path: string, path: string,
@@ -43,7 +46,7 @@ const singleRequest = async <RespDataType>(
inlineData: boolean = false inlineData: boolean = false
): Promise<AxiosResponse<RespDataType, any> | null> => { ): Promise<AxiosResponse<RespDataType, any> | null> => {
let resp: AxiosResponse<RespDataType, any>; let resp: AxiosResponse<RespDataType, any>;
config.headers = { ...config.headers, ...req.session.authHeaders }; config.headers = { ...config.headers, ...authHeaders };
try { try {
if (!data || inlineData) { if (!data || inlineData) {
if (data) config.data = data ?? null; if (data) config.data = data ?? null;
@@ -87,15 +90,12 @@ const singleRequest = async <RespDataType>(
interface GetCurrentUsersProfileArgs extends EndpointHandlerBaseArgs {} interface GetCurrentUsersProfileArgs extends EndpointHandlerBaseArgs {}
const getCurrentUsersProfile: ( const getCurrentUsersProfile: (
opts: GetCurrentUsersProfileArgs opts: GetCurrentUsersProfileArgs
) => Promise<GetCurrentUsersProfile | null> = async ({ req, res }) => { ) => Promise<GetCurrentUsersProfile | null> = async ({ authHeaders, res }) => {
const response = await singleRequest<GetCurrentUsersProfile>( const response = await singleRequest<GetCurrentUsersProfile>(
req, authHeaders,
res, res,
allowedMethods.Get, allowedMethods.Get,
"/me", "/me"
{
headers: { Authorization: `Bearer ${req.session.accessToken}` },
}
); );
return response ? response.data : null; return response ? response.data : null;
}; };
@@ -104,9 +104,12 @@ interface GetCurrentUsersPlaylistsFirstPageArgs
extends EndpointHandlerBaseArgs {} extends EndpointHandlerBaseArgs {}
const getCurrentUsersPlaylistsFirstPage: ( const getCurrentUsersPlaylistsFirstPage: (
opts: GetCurrentUsersPlaylistsFirstPageArgs opts: GetCurrentUsersPlaylistsFirstPageArgs
) => Promise<GetCurrentUsersPlaylists | null> = async ({ req, res }) => { ) => Promise<GetCurrentUsersPlaylists | null> = async ({
authHeaders,
res,
}) => {
const response = await singleRequest<GetCurrentUsersPlaylists>( const response = await singleRequest<GetCurrentUsersPlaylists>(
req, authHeaders,
res, res,
allowedMethods.Get, allowedMethods.Get,
`/me/playlists`, `/me/playlists`,
@@ -126,12 +129,12 @@ interface GetCurrentUsersPlaylistsNextPageArgs extends EndpointHandlerBaseArgs {
const getCurrentUsersPlaylistsNextPage: ( const getCurrentUsersPlaylistsNextPage: (
opts: GetCurrentUsersPlaylistsNextPageArgs opts: GetCurrentUsersPlaylistsNextPageArgs
) => Promise<GetCurrentUsersPlaylists | null> = async ({ ) => Promise<GetCurrentUsersPlaylists | null> = async ({
req, authHeaders,
res, res,
nextURL, nextURL,
}) => { }) => {
const response = await singleRequest<GetCurrentUsersPlaylists>( const response = await singleRequest<GetCurrentUsersPlaylists>(
req, authHeaders,
res, res,
allowedMethods.Get, allowedMethods.Get,
nextURL nextURL
@@ -146,13 +149,13 @@ interface GetPlaylistDetailsFirstPageArgs extends EndpointHandlerBaseArgs {
const getPlaylistDetailsFirstPage: ( const getPlaylistDetailsFirstPage: (
opts: GetPlaylistDetailsFirstPageArgs opts: GetPlaylistDetailsFirstPageArgs
) => Promise<GetPlaylist | null> = async ({ ) => Promise<GetPlaylist | null> = async ({
req, authHeaders,
res, res,
initialFields, initialFields,
playlistID, playlistID,
}) => { }) => {
const response = await singleRequest<GetPlaylist>( const response = await singleRequest<GetPlaylist>(
req, authHeaders,
res, res,
allowedMethods.Get, allowedMethods.Get,
`/playlists/${playlistID}/`, `/playlists/${playlistID}/`,
@@ -170,9 +173,13 @@ interface GetPlaylistDetailsNextPageArgs extends EndpointHandlerBaseArgs {
} }
const getPlaylistDetailsNextPage: ( const getPlaylistDetailsNextPage: (
opts: GetPlaylistDetailsNextPageArgs opts: GetPlaylistDetailsNextPageArgs
) => Promise<GetPlaylistItems | null> = async ({ req, res, nextURL }) => { ) => Promise<GetPlaylistItems | null> = async ({
authHeaders,
res,
nextURL,
}) => {
const response = await singleRequest<GetPlaylistItems>( const response = await singleRequest<GetPlaylistItems>(
req, authHeaders,
res, res,
allowedMethods.Get, allowedMethods.Get,
nextURL nextURL
@@ -187,13 +194,13 @@ interface AddItemsToPlaylistArgs extends EndpointHandlerBaseArgs {
const addItemsToPlaylist: ( const addItemsToPlaylist: (
opts: AddItemsToPlaylistArgs opts: AddItemsToPlaylistArgs
) => Promise<AddItemsToPlaylist | null> = async ({ ) => Promise<AddItemsToPlaylist | null> = async ({
req, authHeaders,
res, res,
nextBatch, nextBatch,
playlistID, playlistID,
}) => { }) => {
const response = await singleRequest<AddItemsToPlaylist>( const response = await singleRequest<AddItemsToPlaylist>(
req, authHeaders,
res, res,
allowedMethods.Post, allowedMethods.Post,
`/playlists/${playlistID}/tracks`, `/playlists/${playlistID}/tracks`,
@@ -212,7 +219,7 @@ interface RemovePlaylistItemsArgs extends EndpointHandlerBaseArgs {
const removePlaylistItems: ( const removePlaylistItems: (
opts: RemovePlaylistItemsArgs opts: RemovePlaylistItemsArgs
) => Promise<RemovePlaylistItems | null> = async ({ ) => Promise<RemovePlaylistItems | null> = async ({
req, authHeaders,
res, res,
nextBatch, nextBatch,
playlistID, playlistID,
@@ -221,7 +228,7 @@ const removePlaylistItems: (
// API doesn't document this kind of deletion via the 'positions' field // API doesn't document this kind of deletion via the 'positions' field
// but see here: https://github.com/spotipy-dev/spotipy/issues/95#issuecomment-2263634801 // but see here: https://github.com/spotipy-dev/spotipy/issues/95#issuecomment-2263634801
const response = await singleRequest<RemovePlaylistItems>( const response = await singleRequest<RemovePlaylistItems>(
req, authHeaders,
res, res,
allowedMethods.Delete, allowedMethods.Delete,
`/playlists/${playlistID}/tracks`, `/playlists/${playlistID}/tracks`,
@@ -243,11 +250,11 @@ interface CheckPlaylistEditableArgs extends EndpointHandlerBaseArgs {
} }
const checkPlaylistEditable: ( const checkPlaylistEditable: (
opts: CheckPlaylistEditableArgs opts: CheckPlaylistEditableArgs
) => Promise<boolean> = async ({ req, res, playlistID, userID }) => { ) => Promise<boolean> = async ({ authHeaders, res, playlistID, userID }) => {
let checkFields = ["collaborative", "owner(id)"]; let checkFields = ["collaborative", "owner(id)"];
const checkFromData = await getPlaylistDetailsFirstPage({ const checkFromData = await getPlaylistDetailsFirstPage({
req, authHeaders,
res, res,
initialFields: checkFields.join(), initialFields: checkFields.join(),
playlistID, playlistID,

View File

@@ -1,7 +1,6 @@
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
import curriedLogger from "../utils/logger.ts"; import logger from "../utils/logger.ts";
const logger = curriedLogger(import.meta.filename);
const __controller_func: RequestHandler = async (req, res) => { const __controller_func: RequestHandler = async (req, res) => {
try { try {

View File

@@ -48,6 +48,7 @@ const callback: RequestHandler = async (req, res) => {
try { try {
const { code, state, error } = req.query; const { code, state, error } = req.query;
const storedState = req.cookies ? req.cookies[stateKey] : null; const storedState = req.cookies ? req.cookies[stateKey] : null;
let authHeaders;
// check state // check state
if (state === null || state !== storedState) { if (state === null || state !== storedState) {
@@ -76,14 +77,18 @@ const callback: RequestHandler = async (req, res) => {
logger.debug("Tokens obtained."); logger.debug("Tokens obtained.");
req.session.accessToken = tokenResponse.data.access_token; req.session.accessToken = tokenResponse.data.access_token;
req.session.refreshToken = tokenResponse.data.refresh_token; req.session.refreshToken = tokenResponse.data.refresh_token;
authHeaders = {
Authorization: `Bearer ${req.session.accessToken}`,
};
} else { } else {
logger.error("login failed", { statusCode: tokenResponse.status }); logger.error("login failed", { statusCode: tokenResponse.status });
res res
.status(tokenResponse.status) .status(tokenResponse.status)
.send({ message: "Error: Login failed" }); .send({ message: "Error: Login failed" });
return null;
} }
const userData = await getCurrentUsersProfile({ req, res }); const userData = await getCurrentUsersProfile({ authHeaders, res });
if (!userData) return null; if (!userData) return null;
req.session.user = { req.session.user = {

View File

@@ -37,13 +37,20 @@ import logger from "../utils/logger.ts";
*/ */
const updateUser: RequestHandler = async (req, res) => { const updateUser: RequestHandler = async (req, res) => {
try { try {
let currentPlaylists: PlaylistModel_Pl[] = [];
if (!req.session.user) if (!req.session.user)
throw new ReferenceError("sessionData does not have user object"); throw new ReferenceError("session does not have user object");
const { authHeaders } = req.session;
if (!authHeaders)
throw new ReferenceError("session does not have auth headers");
const uID = req.session.user.id; const uID = req.session.user.id;
let currentPlaylists: PlaylistModel_Pl[] = [];
// get first 50 // get first 50
const respData = await getCurrentUsersPlaylistsFirstPage({ req, res }); const respData = await getCurrentUsersPlaylistsFirstPage({
authHeaders,
res,
});
if (!respData) return null; if (!respData) return null;
currentPlaylists = respData.items.map((playlist) => { currentPlaylists = respData.items.map((playlist) => {
@@ -57,7 +64,7 @@ const updateUser: RequestHandler = async (req, res) => {
// keep getting batches of 50 till exhausted // keep getting batches of 50 till exhausted
while (nextURL) { while (nextURL) {
const nextData = await getCurrentUsersPlaylistsNextPage({ const nextData = await getCurrentUsersPlaylistsNextPage({
req, authHeaders,
res, res,
nextURL, nextURL,
}); });
@@ -217,7 +224,7 @@ const fetchUser: RequestHandler = async (req, res) => {
// return null; // return null;
// } // }
if (!req.session.user) if (!req.session.user)
throw new ReferenceError("sessionData does not have user object"); throw new ReferenceError("session does not have user object");
const uID = req.session.user.id; const uID = req.session.user.id;
const currentPlaylists = await Playlists.findAll({ const currentPlaylists = await Playlists.findAll({
@@ -259,7 +266,7 @@ const createLink: RequestHandler = async (req, res) => {
try { try {
// await sleep(1000); // await sleep(1000);
if (!req.session.user) if (!req.session.user)
throw new ReferenceError("sessionData does not have user object"); throw new ReferenceError("session does not have user object");
const uID = req.session.user.id; const uID = req.session.user.id;
let fromPl, toPl; let fromPl, toPl;
@@ -351,7 +358,7 @@ const createLink: RequestHandler = async (req, res) => {
const removeLink: RequestHandler = async (req, res) => { const removeLink: RequestHandler = async (req, res) => {
try { try {
if (!req.session.user) if (!req.session.user)
throw new Error("sessionData does not have user object"); throw new ReferenceError("session does not have user object");
const uID = req.session.user.id; const uID = req.session.user.id;
let fromPl, toPl; let fromPl, toPl;
@@ -416,12 +423,16 @@ interface _GetPlaylistTracks {
} }
const _getPlaylistTracks: ( const _getPlaylistTracks: (
opts: _GetPlaylistTracksArgs opts: _GetPlaylistTracksArgs
) => Promise<_GetPlaylistTracks | null> = async ({ req, res, playlistID }) => { ) => Promise<_GetPlaylistTracks | null> = async ({
authHeaders,
res,
playlistID,
}) => {
let initialFields = ["snapshot_id,tracks(next,items(is_local,track(uri)))"]; let initialFields = ["snapshot_id,tracks(next,items(is_local,track(uri)))"];
let mainFields = ["next", "items(is_local,track(uri))"]; let mainFields = ["next", "items(is_local,track(uri))"];
const respData = await getPlaylistDetailsFirstPage({ const respData = await getPlaylistDetailsFirstPage({
req, authHeaders,
res, res,
initialFields: initialFields.join(), initialFields: initialFields.join(),
playlistID, playlistID,
@@ -460,7 +471,7 @@ const _getPlaylistTracks: (
// keep getting batches of 50 till exhausted // keep getting batches of 50 till exhausted
while (nextURL) { while (nextURL) {
const nextData = await getPlaylistDetailsNextPage({ const nextData = await getPlaylistDetailsNextPage({
req, authHeaders,
res, res,
nextURL, nextURL,
}); });
@@ -514,7 +525,7 @@ interface _PopulateSingleLinkCoreArgs extends EndpointHandlerBaseArgs {
const _populateSingleLinkCore: ( const _populateSingleLinkCore: (
opts: _PopulateSingleLinkCoreArgs opts: _PopulateSingleLinkCoreArgs
) => Promise<{ toAddNum: number; localNum: number } | null> = async ({ ) => Promise<{ toAddNum: number; localNum: number } | null> = async ({
req, authHeaders,
res, res,
link, link,
}) => { }) => {
@@ -523,12 +534,12 @@ const _populateSingleLinkCore: (
toPl = link.to; toPl = link.to;
const fromPlaylist = await _getPlaylistTracks({ const fromPlaylist = await _getPlaylistTracks({
req, authHeaders,
res, res,
playlistID: fromPl.id, playlistID: fromPl.id,
}); });
const toPlaylist = await _getPlaylistTracks({ const toPlaylist = await _getPlaylistTracks({
req, authHeaders,
res, res,
playlistID: toPl.id, playlistID: toPl.id,
}); });
@@ -547,7 +558,7 @@ const _populateSingleLinkCore: (
while (toTrackURIs.length > 0) { while (toTrackURIs.length > 0) {
const nextBatch = toTrackURIs.splice(0, 100); const nextBatch = toTrackURIs.splice(0, 100);
const addData = await addItemsToPlaylist({ const addData = await addItemsToPlaylist({
req, authHeaders,
res, res,
nextBatch, nextBatch,
playlistID: fromPl.id, playlistID: fromPl.id,
@@ -566,8 +577,11 @@ const _populateSingleLinkCore: (
const populateSingleLink: RequestHandler = async (req, res) => { const populateSingleLink: RequestHandler = async (req, res) => {
try { try {
if (!req.session.user) if (!req.session.user)
throw new Error("sessionData does not have user object"); throw new ReferenceError("session does not have user object");
const uID = req.session.user.id; const uID = req.session.user.id;
const { authHeaders } = req.session;
if (!authHeaders)
throw new ReferenceError("session does not have auth headers");
const link = { from: req.body.from, to: req.body.to }; const link = { from: req.body.from, to: req.body.to };
let fromPl, toPl; let fromPl, toPl;
@@ -599,7 +613,7 @@ const populateSingleLink: RequestHandler = async (req, res) => {
if ( if (
!(await checkPlaylistEditable({ !(await checkPlaylistEditable({
req, authHeaders,
res, res,
playlistID: fromPl.id, playlistID: fromPl.id,
userID: uID, userID: uID,
@@ -608,7 +622,7 @@ const populateSingleLink: RequestHandler = async (req, res) => {
return null; return null;
const result = await _populateSingleLinkCore({ const result = await _populateSingleLinkCore({
req, authHeaders,
res, res,
link: { from: fromPl, to: toPl }, link: { from: fromPl, to: toPl },
}); });
@@ -651,18 +665,22 @@ interface _PruneSingleLinkCoreArgs extends EndpointHandlerBaseArgs {
*/ */
const _pruneSingleLinkCore: ( const _pruneSingleLinkCore: (
opts: _PruneSingleLinkCoreArgs opts: _PruneSingleLinkCoreArgs
) => Promise<{ toDelNum: number } | null> = async ({ req, res, link }) => { ) => Promise<{ toDelNum: number } | null> = async ({
authHeaders,
res,
link,
}) => {
try { try {
const fromPl = link.from, const fromPl = link.from,
toPl = link.to; toPl = link.to;
const fromPlaylist = await _getPlaylistTracks({ const fromPlaylist = await _getPlaylistTracks({
req, authHeaders,
res, res,
playlistID: fromPl.id, playlistID: fromPl.id,
}); });
const toPlaylist = await _getPlaylistTracks({ const toPlaylist = await _getPlaylistTracks({
req, authHeaders,
res, res,
playlistID: toPl.id, playlistID: toPl.id,
}); });
@@ -684,7 +702,7 @@ const _pruneSingleLinkCore: (
while (indexes.length > 0) { while (indexes.length > 0) {
const nextBatch = indexes.splice(Math.max(indexes.length - 100, 0), 100); const nextBatch = indexes.splice(Math.max(indexes.length - 100, 0), 100);
const delResponse = await removePlaylistItems({ const delResponse = await removePlaylistItems({
req, authHeaders,
res, res,
nextBatch, nextBatch,
playlistID: toPl.id, playlistID: toPl.id,
@@ -705,8 +723,11 @@ const _pruneSingleLinkCore: (
const pruneSingleLink: RequestHandler = async (req, res) => { const pruneSingleLink: RequestHandler = async (req, res) => {
try { try {
if (!req.session.user) if (!req.session.user)
throw new Error("sessionData does not have user object"); throw new ReferenceError("session does not have user object");
const uID = req.session.user.id; const uID = req.session.user.id;
const { authHeaders } = req.session;
if (!authHeaders)
throw new ReferenceError("session does not have auth headers");
const link = { from: req.body.from, to: req.body.to }; const link = { from: req.body.from, to: req.body.to };
let fromPl, toPl; let fromPl, toPl;
@@ -738,7 +759,7 @@ const pruneSingleLink: RequestHandler = async (req, res) => {
if ( if (
!(await checkPlaylistEditable({ !(await checkPlaylistEditable({
req, authHeaders,
res, res,
playlistID: toPl.id, playlistID: toPl.id,
userID: uID, userID: uID,
@@ -747,7 +768,7 @@ const pruneSingleLink: RequestHandler = async (req, res) => {
return null; return null;
const result = await _pruneSingleLinkCore({ const result = await _pruneSingleLinkCore({
req, authHeaders,
res, res,
link: { link: {
from: fromPl, from: fromPl,

View File

@@ -16,8 +16,14 @@ import logger from "../utils/logger.ts";
*/ */
const fetchUserPlaylists: RequestHandler = async (req, res) => { const fetchUserPlaylists: RequestHandler = async (req, res) => {
try { try {
const { authHeaders } = req.session;
if (!authHeaders)
throw new ReferenceError("session does not have auth headers");
// get first 50 // get first 50
const respData = await getCurrentUsersPlaylistsFirstPage({ req, res }); const respData = await getCurrentUsersPlaylistsFirstPage({
authHeaders,
res,
});
if (!respData) return null; if (!respData) return null;
let tmpData = structuredClone(respData); let tmpData = structuredClone(respData);
@@ -32,7 +38,7 @@ const fetchUserPlaylists: RequestHandler = async (req, res) => {
// keep getting batches of 50 till exhausted // keep getting batches of 50 till exhausted
while (nextURL) { while (nextURL) {
const nextData = await getCurrentUsersPlaylistsNextPage({ const nextData = await getCurrentUsersPlaylistsNextPage({
req, authHeaders,
res, res,
nextURL, nextURL,
}); });
@@ -51,4 +57,5 @@ const fetchUserPlaylists: RequestHandler = async (req, res) => {
return null; return null;
} }
}; };
export { fetchUserPlaylists }; export { fetchUserPlaylists };

View File

@@ -91,7 +91,10 @@ app.use("/health", (_req, res) => {
}); });
app.use("/auth-health", isAuthenticated, async (req, res) => { app.use("/auth-health", isAuthenticated, async (req, res) => {
try { try {
const respData = await getCurrentUsersProfile({ req, res }); const { authHeaders } = req.session;
if (!authHeaders)
throw new ReferenceError("session does not have auth headers");
const respData = await getCurrentUsersProfile({ authHeaders, res });
if (!respData) return null; if (!respData) return null;
res.status(200).send({ message: "OK" }); res.status(200).send({ message: "OK" });
return null; return null;

View File

@@ -1,4 +1,3 @@
import type { AxiosRequestHeaders } from "axios";
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
import { sessionName } from "../constants.ts"; import { sessionName } from "../constants.ts";
@@ -9,7 +8,7 @@ export const isAuthenticated: RequestHandler = (req, res, next) => {
if (req.session.accessToken) { if (req.session.accessToken) {
req.session.authHeaders = { req.session.authHeaders = {
Authorization: `Bearer ${req.session.accessToken}`, Authorization: `Bearer ${req.session.accessToken}`,
} as AxiosRequestHeaders; };
next(); next();
} else { } else {
const delSession = req.session.destroy((error) => { const delSession = req.session.destroy((error) => {

View File

@@ -1,10 +1,10 @@
import { Router } from "express"; import { Router } from "express";
const router: Router = Router(); const playlistRouter: Router = Router();
import { fetchUserPlaylists } from "../controllers/playlists.ts"; import { fetchUserPlaylists } from "../controllers/playlists.ts";
// import { validate } from "../validators/index.ts"; // import { validate } from "../validators/index.ts";
router.get("/me", fetchUserPlaylists); playlistRouter.get("/me", fetchUserPlaylists);
export default router; export default playlistRouter;

View File

@@ -1,4 +1,4 @@
import type { AxiosRequestHeaders } from "axios"; import type { RawAxiosRequestHeaders } from "axios";
import type { User } from "spotify_manager/index.d.ts"; import type { User } from "spotify_manager/index.d.ts";
declare module "express-session" { declare module "express-session" {
@@ -6,7 +6,7 @@ declare module "express-session" {
interface SessionData { interface SessionData {
accessToken: string; accessToken: string;
refreshToken: string; refreshToken: string;
authHeaders: AxiosRequestHeaders; authHeaders: RawAxiosRequestHeaders;
user: User; user: User;
} }
} }

View File

@@ -1,3 +1,4 @@
import type { RawAxiosRequestHeaders } from "axios";
import type { Request, Response, NextFunction } from "express"; import type { Request, Response, NextFunction } from "express";
export type Req = Request; export type Req = Request;
@@ -5,6 +6,6 @@ export type Res = Response;
export type Next = NextFunction; export type Next = NextFunction;
export interface EndpointHandlerBaseArgs { export interface EndpointHandlerBaseArgs {
req: Req; authHeaders: RawAxiosRequestHeaders;
res: Res; res: Res;
} }