From fa6e7e9ca960cef9e691561aed5acc8a033bae05 Mon Sep 17 00:00:00 2001 From: dicedtomato <35403473+dicedtomatoreal@users.noreply.github.com> Date: Mon, 12 Oct 2020 21:30:21 +0000 Subject: [PATCH] url shortener api --- src/controllers/RootController.ts | 22 ++++++++ src/controllers/URLSController.ts | 87 +++++++++++++++++++++++++++++++ src/entities/URL.ts | 27 ++++++++++ src/index.ts | 3 +- 4 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 src/controllers/URLSController.ts create mode 100644 src/entities/URL.ts diff --git a/src/controllers/RootController.ts b/src/controllers/RootController.ts index 2ff3f6a5..83255c4d 100644 --- a/src/controllers/RootController.ts +++ b/src/controllers/RootController.ts @@ -11,6 +11,7 @@ import { createWriteStream, existsSync, mkdirSync } from 'fs'; import { join } from 'path'; import { Repository } from 'typeorm'; import { Image } from '../entities/Image'; +import { URL } from '../entities/URL'; import { User } from '../entities/User'; import { AuthError } from '../lib/api/APIErrors'; import { Configuration } from '../lib/Config'; @@ -26,12 +27,33 @@ export class RootController { private users: Repository = this.instance.orm.getRepository(User); private images: Repository = this.instance.orm.getRepository(Image); + private urls: Repository = this.instance.orm.getRepository(URL); + @GET('/config/uploader') async uploaderConfig() { return Configuration.readConfig().uploader; } + @GET('/s/:id') + async getShorten(req: FastifyRequest<{ Params: { id: string } }>, reply: FastifyReply) { + const urlId = await this.urls.findOne({ + where: { + id: req.params.id + } + }); + + const urlVanity = await this.urls.findOne({ + where: { + vanity: req.params.id + } + }); + + if (urlVanity) return reply.redirect(urlVanity.url); + if (!urlId) reply.send('Not found'); + return reply.redirect(urlId.url); + } + @GET('/users') async allUsers(req: FastifyRequest, reply: FastifyReply) { if (!req.cookies.zipline) throw new Error('Not logged in.'); diff --git a/src/controllers/URLSController.ts b/src/controllers/URLSController.ts new file mode 100644 index 00000000..588bb15c --- /dev/null +++ b/src/controllers/URLSController.ts @@ -0,0 +1,87 @@ +import { FastifyReply, FastifyRequest, FastifyInstance } from 'fastify'; +import { + Controller, + FastifyInstanceToken, + Inject, + GET, + DELETE, + POST +} from 'fastify-decorators'; +import { Repository } from 'typeorm'; +import { URL } from '../entities/URL'; +import { User } from '../entities/User'; +import { LoginError } from '../lib/api/APIErrors'; +import { Configuration } from '../lib/Config'; +import { createRandomId, readBaseCookie } from '../lib/Encryption'; + +const config = Configuration.readConfig(); +if (!config) process.exit(0); + +@Controller('/api/urls') +export class URLSController { + @Inject(FastifyInstanceToken) + private instance!: FastifyInstance; + + private urls: Repository = this.instance.orm.getRepository(URL); + private users: Repository = this.instance.orm.getRepository(User); + + @GET('/') + async allURLS(req: FastifyRequest, reply: FastifyReply) { + if (!req.cookies.zipline) throw new LoginError('Not logged in.'); + + const all = await this.urls.find({ + where: { + user: readBaseCookie(req.cookies.zipline), + }, + }); + + return reply.send(all); + } + + @DELETE('/:id') + async deleteURL(req: FastifyRequest<{ Params: { id: string } }>, reply: FastifyReply) { + if (!req.cookies.zipline) throw new LoginError('Not logged in.'); + + const url = await this.urls.findOne({ + where: { + user: readBaseCookie(req.cookies.zipline), + id: req.params.id + } + }); + + if (!url) throw new Error('No url'); + + this.urls.delete({ + id: req.params.id + }); + + return reply.send(url); + } + + @POST('/') + async createURL(req: FastifyRequest<{ Body: { vanity: string, url: string } }>, reply: FastifyReply) { + if (!req.cookies.zipline) throw new LoginError('Not logged in.'); + + if (req.body.vanity) { + const existingVanity = await this.urls.findOne({ + where: { + vanity: req.body.vanity + } + }); + if (existingVanity) throw new Error('There is an existing vanity!'); + } + + const user = await this.users.findOne({ + where: { + id: readBaseCookie(req.cookies.zipline) + } + }); + + if (!user) throw new LoginError('No user'); + + const url = await this.urls.save( + new URL(createRandomId(4), user.id, req.body.url, req.body.vanity || null) + ) + return reply.send(url); + } +} diff --git a/src/entities/URL.ts b/src/entities/URL.ts new file mode 100644 index 00000000..adbb0f09 --- /dev/null +++ b/src/entities/URL.ts @@ -0,0 +1,27 @@ +import { Entity, Column, PrimaryColumn } from 'typeorm'; + +@Entity({ name: 'zipline_urls' }) +export class URL { + @PrimaryColumn('text') + public id: string; + + @Column('text', { default: null }) + public url: string; + + @Column('text', { default: null, nullable: true }) + public vanity: string; + + @Column('bigint') + public user: number; + + @Column('bigint', { default: 0 }) + public clicks: 0; + + public constructor(id: string, user: number, url: string, vanity: string = null) { + this.id = id; + this.user = user; + this.url = url; + this.vanity = vanity; + this.clicks = 0; + } +} diff --git a/src/index.ts b/src/index.ts index 4b0a6f54..58061c34 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,6 +15,7 @@ import { UserController } from './controllers/UserController'; import { RootController } from './controllers/RootController'; import { join } from 'path'; import { ImagesController } from './controllers/ImagesController'; +import { URLSController } from './controllers/URLSController'; Console.setFormatter(new ConsoleFormatter()); @@ -55,7 +56,7 @@ server.register(fastifyTypeorm, { }); server.register(bootstrap, { - controllers: [UserController, RootController, ImagesController], + controllers: [UserController, RootController, ImagesController, URLSController], }); server.register(fastifyCookies, {