alpha release
update v0.8.0
This commit is contained in:
65
src/middlewares/queryHandler/followingHandler.ts
Normal file
65
src/middlewares/queryHandler/followingHandler.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import type { Request, Response } from 'express';
|
||||
import type { Twttr } from 'twtxt-lib';
|
||||
|
||||
import {
|
||||
generateEtag,
|
||||
getQueryParameterArray,
|
||||
getValueOrFirstEntry,
|
||||
} from '../../lib/utils.js';
|
||||
import { QueryParameters } from '../../types.js';
|
||||
import NodeCache from '@cacheable/node-cache';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
* @param cache
|
||||
* @param followingParameter
|
||||
*/
|
||||
export default function followingHandler(
|
||||
req: Request,
|
||||
res: Response,
|
||||
cache: NodeCache<unknown>,
|
||||
followingParameter: QueryParameters['following']
|
||||
) {
|
||||
const followingsToMatch = getQueryParameterArray(
|
||||
req.query[followingParameter]
|
||||
);
|
||||
|
||||
const nicksToMatch = getQueryParameterArray(req.query.nick);
|
||||
const urlsToMatch = getQueryParameterArray(req.query.url);
|
||||
|
||||
const searchTermsToMatch = [
|
||||
...getQueryParameterArray(req.query.search),
|
||||
...getQueryParameterArray(req.query.s),
|
||||
];
|
||||
|
||||
const wantsJson =
|
||||
req.is('json') ||
|
||||
getValueOrFirstEntry(getQueryParameterArray(req.query.format)) === 'json';
|
||||
if (wantsJson) res.set('content-type', 'application/json');
|
||||
else res.set('content-type', 'text/plain');
|
||||
|
||||
const matchedFollowing = (cache.get('following') as Twttr[]).filter(
|
||||
({ nick, url }) =>
|
||||
(!followingsToMatch.length ||
|
||||
(followingsToMatch.length === 1 && followingsToMatch[0] === '') ||
|
||||
followingsToMatch.includes(nick) ||
|
||||
followingsToMatch.includes(`@${nick}`) ||
|
||||
followingsToMatch.includes(url)) &&
|
||||
(!nicksToMatch.length ||
|
||||
nicksToMatch.includes(nick) ||
|
||||
nicksToMatch.includes(`@${nick}`)) &&
|
||||
(!urlsToMatch.length || urlsToMatch.includes(url)) &&
|
||||
(!searchTermsToMatch.length ||
|
||||
searchTermsToMatch.some(
|
||||
(term) => nick.includes(term) || url.includes(term)
|
||||
))
|
||||
);
|
||||
|
||||
const result = wantsJson
|
||||
? JSON.stringify(matchedFollowing)
|
||||
: matchedFollowing.map(({ nick, url }) => `@${nick} ${url}`).join('\n');
|
||||
|
||||
res.set('etag', generateEtag(result)).send(result);
|
||||
}
|
||||
1
src/middlewares/queryHandler/index.ts
Normal file
1
src/middlewares/queryHandler/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from "./queryHandler.js";
|
||||
92
src/middlewares/queryHandler/metadataHandler.ts
Normal file
92
src/middlewares/queryHandler/metadataHandler.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import type { Request, Response } from 'express';
|
||||
import type { Metadata } from 'twtxt-lib';
|
||||
|
||||
import { env } from '../../lib/env.js';
|
||||
import twtxtCache from '../../lib/twtxtCache.js';
|
||||
import {
|
||||
generateEtag,
|
||||
getQueryParameterArray,
|
||||
getValueOrFirstEntry,
|
||||
} from '../../lib/utils.js';
|
||||
import NodeCache from '@cacheable/node-cache';
|
||||
import { QueryParameters } from '../../types.js';
|
||||
|
||||
export interface MetadataHandler {
|
||||
cache: NodeCache<unknown>;
|
||||
metadataParameter: QueryParameters['metadata'];
|
||||
req: Request;
|
||||
res: Response;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
* @param cache
|
||||
* @param metadataParameter
|
||||
*/
|
||||
export default function metadataHandler(
|
||||
req: Request,
|
||||
res: Response,
|
||||
cache: NodeCache<unknown>,
|
||||
metadataParameter: QueryParameters['metadata']
|
||||
) {
|
||||
const metadataToMatch = getQueryParameterArray(req.query[metadataParameter]);
|
||||
|
||||
const searchTermsToMatch = [
|
||||
...getQueryParameterArray(req.query.search),
|
||||
...getQueryParameterArray(req.query.s),
|
||||
];
|
||||
|
||||
const metadata = (cache.get('metadata') as Metadata) ?? {};
|
||||
|
||||
const wantsJson =
|
||||
req.is('json') ||
|
||||
getValueOrFirstEntry(getQueryParameterArray(req.query.format)) === 'json';
|
||||
if (wantsJson) res.set('content-type', 'application/json');
|
||||
else res.set('content-type', 'text/plain');
|
||||
|
||||
const matchedMetadata = Object.keys(metadata)
|
||||
.filter(
|
||||
(key) =>
|
||||
(!metadataToMatch.length ||
|
||||
(metadataToMatch.length === 1 && metadataToMatch[0] === '') ||
|
||||
metadataToMatch.includes(key)) &&
|
||||
(!searchTermsToMatch.length ||
|
||||
searchTermsToMatch.some((term) =>
|
||||
key.includes(term) || Array.isArray(metadata[key])
|
||||
? (metadata[key] as string[]).some((val) => val.includes(term))
|
||||
: metadata[key].includes(term)
|
||||
))
|
||||
)
|
||||
.reduce(
|
||||
(acc, key) => {
|
||||
const value = metadata[key as keyof typeof metadata];
|
||||
acc[key] = Array.isArray(value)
|
||||
? value.filter(
|
||||
(value) =>
|
||||
!searchTermsToMatch.length ||
|
||||
searchTermsToMatch.some(
|
||||
(term) => key.includes(term) || value.includes(term)
|
||||
)
|
||||
)
|
||||
: value;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, string | string[]>
|
||||
);
|
||||
|
||||
const result = wantsJson
|
||||
? JSON.stringify(matchedMetadata)
|
||||
: Object.keys(matchedMetadata)
|
||||
.map((key) => {
|
||||
const value = matchedMetadata[key as keyof typeof matchedMetadata];
|
||||
|
||||
return Array.isArray(value)
|
||||
? value.map((rowVal) => `${key}: ${rowVal}`).join('\n')
|
||||
: `${key}: ${value}`;
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
res.set('etag', generateEtag(result)).send(result);
|
||||
}
|
||||
88
src/middlewares/queryHandler/queryHandler.ts
Normal file
88
src/middlewares/queryHandler/queryHandler.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import path from 'node:path';
|
||||
import NodeCache from '@cacheable/node-cache';
|
||||
import Debug from 'debug';
|
||||
import type { NextFunction, Request, Response } from 'express';
|
||||
import { __dirname } from '../../lib/constants.js';
|
||||
import { generateEtag } from '../../lib/utils.js';
|
||||
import { TwtKprConfiguration } from '../../types.js';
|
||||
import renderApp from '../renderApp/index.js';
|
||||
import followingHandler from './followingHandler.js';
|
||||
import metadataHandler from './metadataHandler.js';
|
||||
import twtHandler from './twtHandler.js';
|
||||
import { Twt } from 'twtxt-lib';
|
||||
|
||||
const debug = Debug('twtkpr:queryHandler');
|
||||
|
||||
/**
|
||||
*
|
||||
* @param config
|
||||
* @param cache
|
||||
* @param verifyAuthRequest
|
||||
* @returns
|
||||
*/
|
||||
export default function queryHandler(
|
||||
config: TwtKprConfiguration,
|
||||
cache: NodeCache<unknown>,
|
||||
verifyAuthRequest: (r: Request) => Promise<boolean>
|
||||
) {
|
||||
const { mainRoute, queryParameters, uploadConfiguration } = config;
|
||||
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
debug({ query: JSON.stringify(req.query) });
|
||||
|
||||
if (!Object.keys(req.query).length) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.query[queryParameters.app] !== undefined) {
|
||||
const appContent = renderApp({ mainRoute, uploadConfiguration });
|
||||
res.set('etag', generateEtag(appContent)).send(appContent);
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.query[queryParameters.css] !== undefined) {
|
||||
res.sendFile('styles.css', {
|
||||
root: path.resolve(__dirname, 'client'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.query[queryParameters.js] !== undefined) {
|
||||
res.sendFile('script.js', {
|
||||
root: path.resolve(__dirname, 'client'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
req.query[queryParameters.following] !== undefined &&
|
||||
cache.get('following')
|
||||
) {
|
||||
return followingHandler(req, res, cache, queryParameters.following);
|
||||
}
|
||||
|
||||
if (
|
||||
req.query[queryParameters.metadata] !== undefined &&
|
||||
cache.get('metadata')
|
||||
) {
|
||||
return metadataHandler(req, res, cache, queryParameters.metadata);
|
||||
}
|
||||
|
||||
if (
|
||||
(req.query[queryParameters.twt] !== undefined ||
|
||||
req.query[queryParameters.twts] !== undefined) &&
|
||||
cache.get('twts')
|
||||
) {
|
||||
return twtHandler(
|
||||
req,
|
||||
res,
|
||||
cache.get('twts') as Twt[],
|
||||
queryParameters.twt,
|
||||
queryParameters.twts
|
||||
);
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
}
|
||||
101
src/middlewares/queryHandler/twtHandler.ts
Normal file
101
src/middlewares/queryHandler/twtHandler.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import type { Request, Response } from 'express';
|
||||
import type { Metadata, Twt } from 'twtxt-lib';
|
||||
|
||||
import {
|
||||
generateEtag,
|
||||
getQueryParameterArray,
|
||||
getValueOrFirstEntry,
|
||||
} from '../../lib/utils.js';
|
||||
import dayjs from 'dayjs';
|
||||
import utc from 'dayjs/plugin/utc.js';
|
||||
import NodeCache from '@cacheable/node-cache';
|
||||
import { QueryParameters } from '../../types.js';
|
||||
|
||||
dayjs.extend(utc);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
* @param twts
|
||||
* @param twtParameter
|
||||
* @param twtsParameter
|
||||
* @returns
|
||||
*/
|
||||
export default function twtHandler(
|
||||
req: Request,
|
||||
res: Response,
|
||||
twts: Twt[] = [],
|
||||
twtParameter: QueryParameters['twt'],
|
||||
twtsParameter: QueryParameters['twts']
|
||||
) {
|
||||
const twtsToMatch = getQueryParameterArray(req.query[twtsParameter]);
|
||||
const showLastTwt = getQueryParameterArray(req.query[twtParameter]);
|
||||
const hashesToMatch = getQueryParameterArray(req.query.hash);
|
||||
const searchTermsToMatch = [
|
||||
...getQueryParameterArray(req.query.search),
|
||||
...getQueryParameterArray(req.query.s),
|
||||
];
|
||||
|
||||
const createdDatesToMatch = getQueryParameterArray(req.query.created_date);
|
||||
const createdUTCStartDatesToMatch = getQueryParameterArray(
|
||||
req.query.created_date_start
|
||||
).map((val) => dayjs.utc(val));
|
||||
const createdUTCEndDatesToMatch = getQueryParameterArray(
|
||||
req.query.created_date_end
|
||||
).map((val) => dayjs.utc(val));
|
||||
|
||||
const wantsJson =
|
||||
req.is('json') ||
|
||||
getValueOrFirstEntry(getQueryParameterArray(req.query.format)) === 'json';
|
||||
if (wantsJson) res.set('content-type', 'application/json');
|
||||
else res.set('content-type', 'text/plain');
|
||||
|
||||
if (showLastTwt.length === 1 && showLastTwt[0] === '') {
|
||||
const lastTwt = twts.reduce((matched, curr) =>
|
||||
matched?.createdUTC > curr.createdUTC ? matched : curr
|
||||
);
|
||||
let result = 'No results';
|
||||
|
||||
if (lastTwt) {
|
||||
result = wantsJson
|
||||
? JSON.stringify(lastTwt)
|
||||
: `${lastTwt?.created || ''}\t${lastTwt?.content || ''}\n`;
|
||||
}
|
||||
|
||||
res.set('etag', generateEtag(result)).send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
const matchedTwts = twts.filter(({ content, created, createdUTC, hash }) => {
|
||||
return (
|
||||
(!twtsToMatch.length ||
|
||||
(twtsToMatch.length === 1 && twtsToMatch[0] === '') ||
|
||||
twtsToMatch.includes(created) ||
|
||||
(hash &&
|
||||
(twtsToMatch.includes(hash) || twtsToMatch.includes(`#${hash}`)))) &&
|
||||
(!hashesToMatch.length ||
|
||||
(hash &&
|
||||
(hashesToMatch.includes(hash) ||
|
||||
hashesToMatch.includes(`#${hash}`)))) &&
|
||||
(!createdDatesToMatch.length ||
|
||||
createdDatesToMatch.some((date) => created.includes(date))) &&
|
||||
(!createdUTCStartDatesToMatch.length ||
|
||||
createdUTCStartDatesToMatch.some(
|
||||
(date) => date.diff(createdUTC) < 0
|
||||
)) &&
|
||||
(!createdUTCEndDatesToMatch.length ||
|
||||
createdUTCEndDatesToMatch.some((date) => date.diff(createdUTC) > 0)) &&
|
||||
(!searchTermsToMatch.length ||
|
||||
searchTermsToMatch.some((term) => content.includes(term)))
|
||||
);
|
||||
});
|
||||
|
||||
const result = wantsJson
|
||||
? JSON.stringify(matchedTwts)
|
||||
: matchedTwts
|
||||
.map(({ content, created }) => `${created}\t${content}`)
|
||||
.join('\n');
|
||||
|
||||
res.set('etag', generateEtag(result)).send(result);
|
||||
}
|
||||
Reference in New Issue
Block a user