Files
kittyBE/src/services/linkService.ts
sherl f86630c51e
All checks were successful
Update changelog / changelog (push) Successful in 26s
feat: add link generation support (short/sentence links) + wordlist
2026-01-02 17:50:52 +01:00

138 lines
3.8 KiB
TypeScript

import { Link } from '../entities/Link';
import { User } from '../entities/User';
import { AppDataSource } from '../data-source';
import { getEnvString } from '../tools/jwt';
export type IdResponse = {
id: number;
exists: boolean;
};
export class LinkService {
dataSource = AppDataSource;
linkRepo = this.dataSource.getRepository(Link);
// Retrieve config to check whether subdomains are allowed
useSubdomains: boolean = getEnvString('useSubdomains', true) === '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 };
// Sanity check: don't allow for adding links
// with subdomains if server has it disabled.
if (link.subdomain && !this.useSubdomains) link.subdomain = null;
// Check if entry can be inserted.
if (await this.canInsert(link)) {
await this.linkRepo.insert(link);
// Then get new link's ID
const insertedLink: Link[] = await this.linkRepo.findBy({
shortUri: link.shortUri,
fullUrl: link.fullUrl
});
// Return appropriate id (or error)
if (insertedLink.length !== 1) {
result.id = -2;
result.exists = false;
} else {
result.id = insertedLink[0].id;
result.exists = true;
}
}
return result;
}
/**
* Adds a new link entity, and links it with passed user.
*
* USE ONLY FOR AUTHENTICATED USERS. For unauthenticated users
* use addAnonymous() instead. Returns IdResponse, containing
* exists field indicating if an existing field has been found,
* and if so, what is it's id. If exists is false, ID of the
* newly created entity gets returned.
*
* Errors: an error ocurred if ID is negative.
*
* This can be:
*
* - -1 - link can't be inserted (because an entry with shortUri or shortUri+subdomain combo exist),
*
* - -2 - no conflicting entry exists but transaction failed anyway.
*
* @param {Link} link The link
* @param {User} user The user
* @return {Promise<IdResponse>} Dictionary containing link ID and whether it already existed, or was just inserted
*/
async addIfNew(link: Link, user: User): Promise<IdResponse> {
let result: IdResponse = { id: -1, exists: false };
// 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) => {
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
});
// Return appropriate id (or error)
if (insertedLink.length !== 1) {
result.id = -2;
result.exists = false;
} else {
result.id = insertedLink[0].id;
result.exists = true;
}
}
return result;
}
async canInsert(link: Link): Promise<boolean> {
let shortUriExists: boolean = true
// If both subdomains are enabled, and user
// provided a subdomain, find if a record exists
// that either has the exact shortUri
// or shortUri and subdomain combo.
// If any of these turns out to be true, the request
// must be invalidated and we can't proceed
// with creation of a new link.
if (this.useSubdomains && link.subdomain)
{
shortUriExists = await this.linkRepo.existsBy({
shortUri: link.shortUri,
subdomain: link.subdomain
});
}
// If custom subdomains are disabled, fallback to
// checking only by the URIs - thus discarding
// any possible subdomains.
else
{
shortUriExists = await this.linkRepo.existsBy({ shortUri: link.shortUri });
}
return !shortUriExists;
}
}