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 { 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} Dictionary containing link ID and whether it already existed, or was just inserted */ async addIfNew(link: Link, user: User): Promise { 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 { 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; } }