Skip to content

Commit

Permalink
feat: add createLink route
Browse files Browse the repository at this point in the history
  • Loading branch information
HarshPatel5940 committed Aug 2, 2024
1 parent d4ca000 commit ea6b91e
Show file tree
Hide file tree
Showing 11 changed files with 282 additions and 123 deletions.
18 changes: 18 additions & 0 deletions bruno/Create New Link.bru
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
meta {
name: Create New Link
type: http
seq: 2
}

post {
url: http://localhost:5050/api/v1/links
body: json
auth: none
}

body:json {
{
"longUrl": "http://github.com/HelloWorld",
"customCode": "hiii"
}
}
9 changes: 9 additions & 0 deletions bruno/bruno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"version": "1",
"name": "kzilla.xyz",
"type": "collection",
"ignore": [
"node_modules",
".git"
]
}
41 changes: 40 additions & 1 deletion src/controllers/links-controller.ts
Original file line number Diff line number Diff line change
@@ -1 +1,40 @@
// controller functions
import type { Context } from "hono";
import { createLinkSchema } from "../models/links";
import { createLink, fetchLink } from "../services/link-service";
import { generateRandomCode } from "../utils/links";
import { BackendError } from "../utils/errors";
import { getConnInfo } from "@hono/node-server/conninfo";

/**
@summary Validates the request body and creates a new link
*/
export async function handleCreateLink(c: Context) {
const ipAddress = getConnInfo(c).remote.address || "::1";
const reqBody = await c.req.json();
const { longUrl, customCode } = await createLinkSchema.parseAsync(reqBody);

let shortCode = customCode ?? generateRandomCode(6);
let analyticsCode = generateRandomCode(6);
let linkId = generateRandomCode(12);

const shortCodeConflict = await fetchLink(shortCode, analyticsCode, linkId);

if (shortCodeConflict) {
if (customCode && shortCodeConflict.customCode === customCode) {
throw new BackendError("CONFLICT", {
message: "CUSTOM_CODE_CONFLICT",
details: "The custom url you provided already exists",
});
}

// TODO: possiblity of bad practice here... check later
console.log("DEBUG: Conflicts occured, retrying");
shortCode = generateRandomCode(6);
analyticsCode = generateRandomCode(6);
linkId = generateRandomCode(12);
}

await createLink(longUrl, shortCode, analyticsCode, linkId, ipAddress);

return c.json({ success: true, message: "Created Successfully" });
}
29 changes: 29 additions & 0 deletions src/models/links.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { z } from "zod";

export const linkSchema = z.object({
linkId: z.string().min(10),
clicks: z.number().default(0),
analyticsCode: z.string().trim().min(5),
longUrl: z.string().url().trim().min(5),
customCode: z.string().trim().min(4).max(25).optional(),
creatorIpAddress: z.string().ip().optional().default("::1"),
logs: z
.array(
z.object({
ipAddress: z.string().ip(),
timestamp: z.number(),
}),
)
.optional(),
// * This naming is followed to keep it backward compatible don't try to improve it Lol'
enabled: z.boolean().default(true),
timestamp: z.date().default(new Date()),
});

export const createLinkSchema = linkSchema.pick({
longUrl: true,
customCode: true,
});

export type LinkSchemaType = z.infer<typeof linkSchema>;
export type CreateLinkSchemaType = z.infer<typeof createLinkSchema>;
10 changes: 7 additions & 3 deletions src/routes/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { Hono } from 'hono';
import linkRouter from './links-router';
import { Hono } from "hono";
import linkRouter from "./link-router";

const appRouter = new Hono();

appRouter.route('/links', linkRouter);
appRouter.get("/", (c) => {
return c.text("Hello World!");
});

appRouter.route("/links", linkRouter);

export default appRouter;
14 changes: 14 additions & 0 deletions src/routes/link-router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Hono } from "hono";
import { handleCreateLink } from "../controllers/links-controller";

const linkRouter = new Hono();

// linkRouter.get("/me", handleFetchMyLinks);
// linkRouter.get("/:shortCode", handleFetchLink);

linkRouter.post("/", handleCreateLink);

// linkRouter.put("/", handleUpdateLink);
// linkRouter.put("/flush", handleFlushLinks);

export default linkRouter;
7 changes: 0 additions & 7 deletions src/routes/links-router.ts

This file was deleted.

53 changes: 53 additions & 0 deletions src/services/link-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { LinkSchemaType } from "../models/links";
import db from "../utils/db";
import { BackendError } from "../utils/errors";

export async function fetchLink(
shortCode: string,
analyticsCode?: string,
linkId?: string,
) {
try {
const linksCollection = (await db()).collection<LinkSchemaType>("links");

return await linksCollection.findOne({
shortCode,
analyticsCode,
linkId,
});
} catch (err) {
throw new BackendError("INTERNAL_ERROR", {
message: "Error while fetch a link",
details: err,
});
}
}

export async function createLink(
longUrl: string,
shortCode: string,
analyticsCode: string,
linkId: string,
ipAddress: string,
) {
try {
const linksCollection = (await db()).collection<LinkSchemaType>("links");

return await linksCollection.insertOne({
linkId,
longUrl,
customCode: shortCode,
analyticsCode,
clicks: 0,
creatorIpAddress: ipAddress,
enabled: true,
timestamp: new Date(),
logs: [],
});
} catch (err) {
throw new BackendError("INTERNAL_ERROR", {
message: "Error while fetch a link",
details: err,
});
}
}
Loading

0 comments on commit ea6b91e

Please sign in to comment.