138 lines
3.8 KiB
TypeScript
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;
|
|
}
|
|
|
|
}
|