feat: add link creation and lookup
finally has the bare minimum functionality to say that it works!
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "kittyBE",
|
||||
"version": "0.0.0",
|
||||
"version": "0.0.1",
|
||||
"description": "Your go-to place for short and memorable URLs.",
|
||||
"type": "commonjs",
|
||||
"devDependencies": {
|
||||
|
||||
77
src/app.ts
77
src/app.ts
@@ -1,27 +1,33 @@
|
||||
import * as dotenv from 'dotenv';
|
||||
dotenv.config({ quiet: true });
|
||||
|
||||
import express from 'express';
|
||||
import { version } from '../package.json';
|
||||
import { AppDataSource } from './data-source'
|
||||
import { Link } from './entities/Link';
|
||||
import inferUser from './middleware/inferUser';
|
||||
import miscRouter from './routes/miscRoutes';
|
||||
import userRouter from './routes/userRoutes';
|
||||
import linkRouter from './routes/linkRoutes';
|
||||
import { getCorsConfig } from './tools/cors';
|
||||
import { LinkService } from './services/linkService';
|
||||
import * as env from './tools/env';
|
||||
import * as z from 'zod';
|
||||
|
||||
AppDataSource.initialize().then(async () => {
|
||||
|
||||
await AppDataSource.runMigrations();
|
||||
|
||||
const app: express.Express = express();
|
||||
const linkService = new LinkService();
|
||||
const rs = env.getRewriteStrings();
|
||||
|
||||
const removedExpired = await linkService.removeAllExpired();
|
||||
if (removedExpired !== 0) console.log(`[${Date.now() / 1_000}] (DB) Removed ${removedExpired} expired links.`);
|
||||
|
||||
app.use(express.json());
|
||||
app.use(getCorsConfig());
|
||||
app.use(inferUser);
|
||||
app.use(miscRouter, userRouter, linkRouter);
|
||||
|
||||
if (process.env['DEBUG'] === 'true') {
|
||||
if (env.getBool('debug', true)) {
|
||||
const swaggerJsdocOpts = {
|
||||
failOnErrors: true,
|
||||
definition: {
|
||||
@@ -67,16 +73,65 @@ AppDataSource.initialize().then(async () => {
|
||||
|
||||
// Handle 404s
|
||||
// https://stackoverflow.com/a/9802006
|
||||
app.use(function(req: express.Request, res: express.Response) {
|
||||
res.status(404);
|
||||
app.use(async function(req: express.Request, res: express.Response) {
|
||||
|
||||
if (req.accepts('json')) {
|
||||
res.json({ status: 'error', error: 'Not found' });
|
||||
return;
|
||||
// Check if host header seems right
|
||||
try {
|
||||
z.string()
|
||||
.includes(rs.fqdn)
|
||||
.parse(req.headers.host);
|
||||
} catch {
|
||||
return res.status(400)
|
||||
.json({
|
||||
status: 'error',
|
||||
error: 'Invalid host. Is your browser sending the host header?',
|
||||
code: 'no_host'
|
||||
});
|
||||
}
|
||||
|
||||
// Retrieve url, subdomain from request.
|
||||
let uri: string = req.url.slice(1); // discards / from /abc, /abc -> abc
|
||||
let subdomain: string | null = req.headers.host!.replace(rs.fqdn, '') || null;
|
||||
|
||||
// Try to lookup the url in DB
|
||||
const reversedLink: Link | null = await linkService.lookupUriWithExpiryValidation(uri, subdomain);
|
||||
|
||||
// Found something?
|
||||
if (reversedLink !== null) {
|
||||
// Count this as a visit
|
||||
reversedLink.visits += 1;
|
||||
linkService.save(reversedLink);
|
||||
|
||||
// Redirect the user.
|
||||
return res.redirect(302, reversedLink.fullUrl);
|
||||
}
|
||||
|
||||
res.type('txt').send('Not found');
|
||||
// Nothing found? Return the standard 404.
|
||||
res.status(404);
|
||||
if (req.accepts('json')) {
|
||||
return res.json({
|
||||
status: 'error',
|
||||
error: 'Not found',
|
||||
code: 'uri_not_found'
|
||||
});
|
||||
}
|
||||
|
||||
return res.type('txt')
|
||||
.send('Not found');
|
||||
});
|
||||
app.listen(6567, () => console.log('(HTTP Server) Listening on port 6567.'));
|
||||
|
||||
const errorHandler: express.ErrorRequestHandler = (err, req, res, next) => {
|
||||
console.error(`[${Date.now() / 1_000}] (ErrorHandler) Server error! Error stack:`);
|
||||
console.error(err?.stack);
|
||||
return res.status(500)
|
||||
.json({
|
||||
status: 'error',
|
||||
error: 'Server error! Something broke',
|
||||
code: 'generic_server_error'
|
||||
});
|
||||
};
|
||||
app.use(errorHandler);
|
||||
|
||||
app.listen(6567, () => console.log(`[${Date.now() / 1_000}] (HTTP Server) Listening on port 6567.`));
|
||||
|
||||
}).catch(error => console.log(error))
|
||||
|
||||
@@ -3,9 +3,10 @@ import { Link } from '../entities/Link';
|
||||
import { User } from '../entities/User';
|
||||
import * as ms from '../schemas/miscSchema';
|
||||
import * as ls from '../schemas/linkSchema';
|
||||
import { LinkService } from '../services/linkService';
|
||||
import { LinkService, IdResponse } from '../services/linkService';
|
||||
import { UserService } from '../services/userService';
|
||||
import * as env from '../tools/env';
|
||||
import * as jwt from '../tools/jwt';
|
||||
import { generateSentenceString, generateShortString } from '../tools/wordlist';
|
||||
|
||||
/**
|
||||
@@ -77,3 +78,128 @@ export async function generateSentenceLinkHandler(
|
||||
return res.status(200)
|
||||
.send(userResponse);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* `POST /api/v1/link/new`
|
||||
*
|
||||
* Handles requests for submitting a new shortened URL
|
||||
*
|
||||
* @param {Request} req The Express request
|
||||
* @param {Response} res The Express resource
|
||||
*/
|
||||
export async function createLinkHandler(
|
||||
req: Request<{}, {}, ls.CreateLinkRequestDTO['body']>,
|
||||
res: Response
|
||||
) {
|
||||
|
||||
// Using locals to retrieve decoded user JWT.
|
||||
const decodedUser: jwt.JwtDecoded | undefined = res.locals.user?.decoded;
|
||||
const linkService = new LinkService();
|
||||
const subdomainsAllowed: boolean = env.getBool('useSubdomains', true)!;
|
||||
const rewriteStrings: env.RewriteStrings = env.getRewriteStrings();
|
||||
|
||||
// Sanity check: does the uri start with a forbidden schema?
|
||||
const disallowedResult = ms.disallowedUriSchema.safeParse(req.body.uri);
|
||||
|
||||
// Uri is part of forbidden schema
|
||||
if (disallowedResult.success) {
|
||||
const error: ms.ErrorDTO = {
|
||||
status: 'error',
|
||||
error: 'This uri starts with a forbidden keyword',
|
||||
code: 'forbidden_schema'
|
||||
}
|
||||
return res.status(406)
|
||||
.send(error);
|
||||
}
|
||||
|
||||
let user: User | null = null;
|
||||
if (decodedUser !== undefined) {
|
||||
// If user is logged in, retrieve the account.
|
||||
const userService = new UserService();
|
||||
user = await userService.findById(decodedUser.sub);
|
||||
}
|
||||
|
||||
let generatedSubdomain: string | null = null;
|
||||
// Subdomain passed, but isn't supported? Return an error.
|
||||
if (req.body.subdomain !== undefined && !subdomainsAllowed) {
|
||||
const error: ms.ErrorDTO = {
|
||||
status: 'error',
|
||||
error: 'Server configuration disallows usage of subdomain',
|
||||
code: 'server_subdomain_disabled'
|
||||
};
|
||||
return res.status(400)
|
||||
.send(error);
|
||||
}
|
||||
// Subdomain passed, and server config allows it? Then use it.
|
||||
else if (req.body.subdomain !== undefined && subdomainsAllowed)
|
||||
generatedSubdomain = req.body.subdomain;
|
||||
|
||||
// Similarly, check if expiry date has been passed.
|
||||
let expiryDate: number | null = null;
|
||||
if (req.body.expiryDate !== undefined)
|
||||
expiryDate = req.body.expiryDate;
|
||||
|
||||
// Construct the link
|
||||
const createDate = Date.now();
|
||||
const link: Link = {
|
||||
id: 0, // Can we? Seems like so.
|
||||
subdomain: generatedSubdomain,
|
||||
shortUri: req.body.uri,
|
||||
fullUrl: req.body.remoteUrl,
|
||||
createDate,
|
||||
expiryDate,
|
||||
visits: 0,
|
||||
privacy: req.body.privacy ?? true,
|
||||
author: user
|
||||
};
|
||||
|
||||
// Try to add the row
|
||||
let returnedId: IdResponse | null = null;
|
||||
if (user === null) {
|
||||
// Add anonymously
|
||||
returnedId = await linkService.addIfNewAnonymous(link);
|
||||
} else {
|
||||
// Use a transaction to add the link,
|
||||
// and link it to the user.
|
||||
returnedId = await linkService.addIfNew(link, user);
|
||||
}
|
||||
|
||||
// Failed (short uri + if enabled, subdomain combo is taken)?
|
||||
if (returnedId.exists && returnedId.id == -1) {
|
||||
console.log(returnedId);
|
||||
const error: ms.ErrorDTO = {
|
||||
status: 'error',
|
||||
error: `"${req.body.uri}" is already taken. Maybe try "${generateSentenceString()}"?`,
|
||||
code: 'uri_not_unique'
|
||||
};
|
||||
return res.status(403)
|
||||
.send(error);
|
||||
}
|
||||
|
||||
// Some other, unknown error occurred.
|
||||
if (returnedId.id < 0) {
|
||||
console.log(returnedId);
|
||||
const error: ms.ErrorDTO = {
|
||||
status: 'error',
|
||||
error: 'Server error',
|
||||
code: 'generic_error'
|
||||
};
|
||||
return res.status(500)
|
||||
.send(error);
|
||||
}
|
||||
|
||||
// If we've arrived this far, seems like it's safe
|
||||
// to assume everything went OK.
|
||||
const rs = rewriteStrings;
|
||||
const sd = req.body.subdomain;
|
||||
const shortenedUrl = `${rs.proto}://${sd ? sd + '.' : ''}${rs.fqdn}${rs.path}${req.body.uri}`;
|
||||
const userResponse: ls.CreateLinkResponseDTO = {
|
||||
status: 'ok',
|
||||
uri: shortenedUrl,
|
||||
id: returnedId.id
|
||||
};
|
||||
|
||||
return res.status(200)
|
||||
.send(userResponse);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { Router } from 'express';
|
||||
import validateSchema from '../tools/validateSchema';
|
||||
import * as lc from '../controllers/linkController';
|
||||
import * as ls from '../schemas/linkSchema';
|
||||
import requireUser from '../middleware/requireUser';
|
||||
|
||||
const linkRouter = Router();
|
||||
|
||||
@@ -96,5 +97,42 @@ linkRouter.get('/api/v1/link/short', validateSchema(ls.shortLinkRequestSchema),
|
||||
*/
|
||||
linkRouter.get('/api/v1/link/fromWordlist', validateSchema(ls.sentenceLinkRequestSchema), lc.generateSentenceLinkHandler);
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
*
|
||||
* /api/v1/link/new:
|
||||
* post:
|
||||
* description:
|
||||
* Register a new shortened URL. <br/>
|
||||
* See linkSchema.ts for constraints.
|
||||
* tags: [Link]
|
||||
* summary: Shorten a link
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/CreateLinkRequestDTO'
|
||||
* produces:
|
||||
* - application/json
|
||||
* responses:
|
||||
* 200:
|
||||
* description: New link created successfully
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/CreateLinkResponseDTO'
|
||||
* 400:
|
||||
* description: Bad request
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorDTO'
|
||||
*/
|
||||
linkRouter.post('/api/v1/link/new',
|
||||
validateSchema(ls.createLinkRequestSchema),
|
||||
lc.createLinkHandler
|
||||
);
|
||||
|
||||
|
||||
export default linkRouter;
|
||||
@@ -62,3 +62,84 @@ export type LinkResponseDTO = {
|
||||
subdomain?: string | null; // null when server does not support generating subdomains
|
||||
};
|
||||
|
||||
|
||||
// POST /api/v1/link/short
|
||||
/**
|
||||
* @openapi
|
||||
* components:
|
||||
* schemas:
|
||||
* CreateLinkRequestDTO:
|
||||
* type: object
|
||||
* required:
|
||||
* - uri
|
||||
* - remoteUrl
|
||||
* properties:
|
||||
* uri:
|
||||
* type: string
|
||||
* default: short uri
|
||||
* remoteUrl:
|
||||
* type: string
|
||||
* default: source url
|
||||
* subdomain:
|
||||
* type: string
|
||||
* default: optional subdomain
|
||||
* privacy:
|
||||
* type: boolean
|
||||
* default: true # privacy by default
|
||||
* expiryDate:
|
||||
* type: number
|
||||
* default: 1767413102824 # UNIX timestamp in ms, Date.now()
|
||||
*/
|
||||
const createLinkRequestSchemaBody = z.object({
|
||||
uri: z.string({
|
||||
error: (e) =>
|
||||
e.input === undefined ? 'Uri is required' : '/, ?, &, = and & are not allowed'
|
||||
}).min( 3, 'Shortened Uri must be at least 3 characters long')
|
||||
.max(128, 'Shortened Uri cannot be longer than 128 characters')
|
||||
.regex(/^[^\/?&=#]*$/),
|
||||
remoteUrl: z.url({
|
||||
error: (e) =>
|
||||
e.input === undefined ? 'RemoteUrl is required' : 'RemoteUrl is not a valid URL'
|
||||
}),
|
||||
privacy: z.boolean({ error: 'Privacy must be a boolean (true by default)' })
|
||||
.optional(),
|
||||
subdomain: z.string('Subdomain must be a string of length between 1 and 32')
|
||||
.min( 1, 'Subdomain should be at least 1 character long')
|
||||
.max(32, 'Subdomain should not be longer than 32 characters')
|
||||
.regex(/^[a-zA-Z0-9]*$/)
|
||||
.optional(),
|
||||
expiryDate: z.number('Expiry date must be a number (UNIX timestamp with milliseconds)')
|
||||
.min(Date.now(), 'Expiry date is a UNIX timestamp with milliseconds')
|
||||
.optional()
|
||||
});
|
||||
|
||||
export const createLinkRequestSchema = z.object({
|
||||
body: createLinkRequestSchemaBody
|
||||
});
|
||||
export type CreateLinkRequestDTO = z.TypeOf<typeof createLinkRequestSchema>;
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* components:
|
||||
* schemas:
|
||||
* CreateLinkResponseDTO:
|
||||
* type: object
|
||||
* required:
|
||||
* - status
|
||||
* - uri
|
||||
* - subdomain
|
||||
* properties:
|
||||
* status:
|
||||
* type: string
|
||||
* default: ok on success, otherwise ErrorDTO with error
|
||||
* uri:
|
||||
* type: string
|
||||
* default: full public, shortened url
|
||||
* id:
|
||||
* type: number
|
||||
*/
|
||||
export type CreateLinkResponseDTO = {
|
||||
status: 'ok';
|
||||
uri: string;
|
||||
id: number;
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as z from 'zod';
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
@@ -24,3 +25,8 @@ export type ErrorDTO = {
|
||||
error: string;
|
||||
code?: string | undefined;
|
||||
};
|
||||
|
||||
// Used to check against reserved names.
|
||||
export const disallowedUriSchema = z
|
||||
.string()
|
||||
.regex(/^(about|assets|kttydocs|panel)/);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Link } from '../entities/Link';
|
||||
import { User } from '../entities/User';
|
||||
import { AppDataSource } from '../data-source';
|
||||
import { getEnvString } from '../tools/jwt';
|
||||
import * as env from '../tools/env';
|
||||
import { IsNull, LessThan } from 'typeorm';
|
||||
|
||||
export type IdResponse = {
|
||||
id: number;
|
||||
@@ -13,15 +14,15 @@ export class LinkService {
|
||||
linkRepo = this.dataSource.getRepository(Link);
|
||||
|
||||
// Retrieve config to check whether subdomains are allowed
|
||||
useSubdomains: boolean = getEnvString('useSubdomains', true) === 'true';
|
||||
useSubdomains: boolean = env.getBool('useSubdomains', true)!;
|
||||
|
||||
/**
|
||||
* Simply insert a new link entity anonymously.
|
||||
*
|
||||
* @param {Link} link The link to insert
|
||||
*/
|
||||
async addAnonymous(link: Link): Promise<IdResponse> {
|
||||
let result: IdResponse = { id: -1, exists: false };
|
||||
async addIfNewAnonymous(link: Link): Promise<IdResponse> {
|
||||
let result: IdResponse = { id: -1, exists: true };
|
||||
|
||||
// Sanity check: don't allow for adding links
|
||||
// with subdomains if server has it disabled.
|
||||
@@ -33,16 +34,17 @@ export class LinkService {
|
||||
|
||||
// Then get new link's ID
|
||||
const insertedLink: Link[] = await this.linkRepo.findBy({
|
||||
shortUri: link.shortUri,
|
||||
fullUrl: link.fullUrl
|
||||
// Add subdomain if used, https://stackoverflow.com/a/69151874
|
||||
subdomain: link.subdomain ?? IsNull(),
|
||||
shortUri: link.shortUri
|
||||
});
|
||||
|
||||
// Return appropriate id (or error)
|
||||
if (insertedLink.length !== 1) {
|
||||
result.id = -2;
|
||||
result.exists = false;
|
||||
} else {
|
||||
if (insertedLink.length === 1) {
|
||||
result.id = insertedLink[0].id;
|
||||
result.exists = false;
|
||||
} else {
|
||||
result.id = -2;
|
||||
result.exists = true;
|
||||
}
|
||||
}
|
||||
@@ -73,31 +75,29 @@ export class LinkService {
|
||||
*/
|
||||
async addIfNew(link: Link, user: User): Promise<IdResponse> {
|
||||
|
||||
let result: IdResponse = { id: -1, exists: false };
|
||||
let result: IdResponse = { id: -1, exists: true };
|
||||
|
||||
// If no conflicts are found,
|
||||
// proceed with creating a new link entry.
|
||||
if (await this.canInsert(link)) {
|
||||
// Commit a transaction
|
||||
this.dataSource.transaction(async (t) => {
|
||||
await this.dataSource.transaction(async (t) => {
|
||||
link.author = user;
|
||||
user.links.push(link);
|
||||
t.insert(Link, link);
|
||||
t.save(user);
|
||||
});
|
||||
|
||||
// Then get new link's ID
|
||||
const insertedLink: Link[] = await this.linkRepo.findBy({
|
||||
shortUri: link.shortUri,
|
||||
fullUrl: link.fullUrl
|
||||
subdomain: link.subdomain ?? IsNull(),
|
||||
shortUri: link.shortUri
|
||||
});
|
||||
|
||||
// Return appropriate id (or error)
|
||||
if (insertedLink.length !== 1) {
|
||||
result.id = -2;
|
||||
result.exists = false;
|
||||
} else {
|
||||
if (insertedLink.length === 1) {
|
||||
result.id = insertedLink[0].id;
|
||||
result.exists = false;
|
||||
} else {
|
||||
result.id = -2;
|
||||
result.exists = true;
|
||||
}
|
||||
|
||||
@@ -128,10 +128,97 @@ export class LinkService {
|
||||
// any possible subdomains.
|
||||
else
|
||||
{
|
||||
shortUriExists = await this.linkRepo.existsBy({ shortUri: link.shortUri });
|
||||
shortUriExists = await this.linkRepo.existsBy({
|
||||
shortUri: link.shortUri,
|
||||
subdomain: IsNull()
|
||||
});
|
||||
}
|
||||
|
||||
return !shortUriExists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all expired links.
|
||||
*
|
||||
* @return {Promise<number>} Amount of removed rows.
|
||||
*/
|
||||
async removeAllExpired(): Promise<number> {
|
||||
// https://github.com/typeorm/typeorm/issues/960#issuecomment-489554674
|
||||
|
||||
let rowsRemoved = 0;
|
||||
const currentDate = Date.now();
|
||||
|
||||
rowsRemoved = await this.linkRepo.countBy({ expiryDate: LessThan(currentDate) });
|
||||
const affectedRows = await this.linkRepo.delete({ expiryDate: LessThan(currentDate) });
|
||||
|
||||
return rowsRemoved;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a Link by it's identifier.
|
||||
*
|
||||
* @param {number} id The identifier
|
||||
* @return {(Promise<Link|null>)} Link (or null if not found)
|
||||
*/
|
||||
async findById(id: number): Promise<Link | null> {
|
||||
return await this.linkRepo.findOneBy({id});
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup the uri + subdomain combo.
|
||||
*
|
||||
* @param {string} uri The uri
|
||||
* @param {(null|string)} [subdomain=null] The subdomain
|
||||
* @return {(Promise<Link|null>)} Link entity containing provided params
|
||||
*/
|
||||
async lookupUri(uri: string, subdomain: string | null = null): Promise<Link | null> {
|
||||
let result: Link | null = null;
|
||||
|
||||
result = await this.linkRepo.findOneBy({
|
||||
shortUri: uri,
|
||||
subdomain: subdomain ?? IsNull()
|
||||
}) ?? null;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup the uri + subdomain combo.
|
||||
* AUTOMATICALLY REMOVES the entry if it expired.
|
||||
*
|
||||
* @param {string} uri The uri
|
||||
* @param {(null|string)} [subdomain=null] The subdomain
|
||||
* @return {(Promise<Link|null>)} Link entity containing provided params
|
||||
*/
|
||||
async lookupUriWithExpiryValidation(uri: string, subdomain: string | null = null): Promise<Link | null> {
|
||||
let result: Link | null = null;
|
||||
|
||||
result = await this.linkRepo.findOneBy({
|
||||
shortUri: uri,
|
||||
subdomain: subdomain ?? IsNull()
|
||||
}) ?? null;
|
||||
|
||||
const now = Date.now();
|
||||
if (result !== null && result.expiryDate !== null)
|
||||
if (now > (result.expiryDate ?? now + 1)) {
|
||||
// Remove found entry
|
||||
this.linkRepo.remove(result);
|
||||
// Set to null to not propagate expired link
|
||||
result = null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a link.
|
||||
* Saves all changes made to the link entity.
|
||||
*
|
||||
* @param {Link} link The link
|
||||
* @return {Promise} None
|
||||
*/
|
||||
async save(link: Link) {
|
||||
await this.linkRepo.save(link);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -35,3 +35,53 @@ export function getString(
|
||||
return process.env[keyName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the environmental boolean from .env.
|
||||
* Supports rewriting names to UPPER_SNAKE_CASE if isGlobal is set.
|
||||
*
|
||||
* @param {string} key The key
|
||||
* @param {boolean} [isGlobal=true] Indicates if global
|
||||
* @return {(boolean|undefined)} The environment boolean.
|
||||
*/
|
||||
export function getBool(
|
||||
key: string,
|
||||
isGlobal: boolean = true
|
||||
): boolean | undefined {
|
||||
|
||||
const valueRead: string | undefined = getString(key, isGlobal);
|
||||
if (valueRead === undefined) return undefined;
|
||||
if (valueRead.toLowerCase() === 'true')
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes public url, returning protocol, fqdn and path.
|
||||
* proto://fqdn/path/ = fullPublicUrl
|
||||
* @return {RewriteStrings} The rewrite strings.
|
||||
*/
|
||||
export function getRewriteStrings(): RewriteStrings {
|
||||
const fullPublicUrl: string = getString('publicUrl', true)!;
|
||||
|
||||
const url = new URL(fullPublicUrl);
|
||||
const proto = url.protocol.slice(0, -1); // https: -> https
|
||||
const fqdn = url.host;
|
||||
const path = url.pathname.replace(/\/+$/, '') + '/'; // /abc -> /abc/
|
||||
|
||||
const result: RewriteStrings = {
|
||||
fullPublicUrl,
|
||||
proto,
|
||||
fqdn,
|
||||
path
|
||||
};
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export type RewriteStrings = {
|
||||
fullPublicUrl: string;
|
||||
proto: string;
|
||||
fqdn: string;
|
||||
path: string;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user