Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.vscode
config.js
node_modules
.env
error.log
info.log
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

The 1pt.co API is public so anyone can create a shortened URL

## Instructions to use it localy
1. To use this on local host download XAMPP (`https://www.apachefriends.org/download.html`) or MySQL database.
2. Set up .env file or modify `config.js` file to enter database credentials.
3. Open terminal in the project root and type `npm start`


## EndPoints
Endpoint: `csclub.uwaterloo.ca/~phthakka/1pt-express`

> Note: the old endpoint (`csclub.uwaterloo.ca/~phthakka/1pt`) is still live but will soon be **deprecated**.
Expand All @@ -25,4 +32,8 @@ Endpoint: `csclub.uwaterloo.ca/~phthakka/1pt-express`
}
```

With this example 1pt.co/param will redirect to https://www.param.me
### `/r/<short code>`

#### Method: `GET`

###### With this endpoint 1pt.co/r/param will redirect to https://www.param.me
6 changes: 6 additions & 0 deletions config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default {
HOST: process.env.PTHOST || "127.0.0.1",
USER: process.env.PTUSER ||'root',
PASSWORD: process.env.PTPASSWORD || '',
DB: process.env.PTDB || '1PTCO'
}
34 changes: 22 additions & 12 deletions handleRequests.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import generateRandomString from "./helpers/generateRandomString.js";
import isHarmful from "./helpers/safebrowsing.js"
import urlExists from "./helpers/urlExists.js";
import verifyToken from "./helpers/verifyToken.js";
import { addhttp, validURL } from "./helpers/urlUtils.js";

export const getURL = async (req, res) => {
const data = (await query(`SELECT long_url FROM 1pt WHERE short_url = '${req.query.url}' LIMIT 1`))[0];
Expand All @@ -14,7 +15,7 @@ export const getURL = async (req, res) => {
})

await query(`UPDATE 1pt SET hits=hits+1 WHERE short_url='${req.query.url}'`)
} else {
} else {
res.status(404).send({
message: "URL doesn't exist!"
})
Expand Down Expand Up @@ -53,13 +54,15 @@ export const addURL = async (req, res, logger) => {
const ipAddress = req.ip;

if (long === undefined || long === "") {
res.status(400).send({
message: "Bad request",
return res.status(400).send({
message: "parameter `long` is missing",
})

return;
}

if (!validURL(long)) return res.status(400).send({
message: 'Malformed URL'
})

let short;

if (!requestedShort || await urlExists(requestedShort)) {
Expand All @@ -78,11 +81,9 @@ export const addURL = async (req, res, logger) => {
await query(`INSERT INTO 1pt (short_url, long_url, ip, email) VALUES ('${short}', '${long}', '${ipAddress}', '${email}')`);

} catch {
res.status(401).send({
message: "Unauthorized",
return res.status(401).send({
message: "Unauthorized",
})

return
}

} else {
Expand All @@ -91,7 +92,7 @@ export const addURL = async (req, res, logger) => {

logger.info(`Inserting ${short} -> ${long}`);

res.status(201).send({
return res.status(201).send({
message: "Added!",
short: short,
long: long
Expand All @@ -109,8 +110,17 @@ export const getProfileInfo = async (req, res) => {

const user = await verifyToken(auth.split(" ")[1]);
const email = user.email;

const data = await query(`SELECT short_url, long_url, timestamp, hits, ip, email FROM 1pt WHERE email = '${email}' ORDER BY timestamp DESC`);

res.status(200).send(data)
}
}


export const redirect = async (req, res) => {
const data = (await query(`SELECT long_url FROM 1pt WHERE short_url = '${req.params.shortCode}' LIMIT 1`))[0];
if (data) {
return res.status(301).redirect(addhttp(data.long_url))
} else {
return res.status(404).redirect('/')
}
}
2 changes: 1 addition & 1 deletion helpers/generateRandomString.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const generateRandomString = async n => {

const exists = await urlExists(randomString);

if (exists) {
if (exists) {
randomString = await generateRandomString(n)
}

Expand Down
19 changes: 19 additions & 0 deletions helpers/urlUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// source: https://stackoverflow.com/a/5717133

export const validURL = (str) => {
var pattern = new RegExp('^(https?:\\/\\/)?'+ // protocol
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name
'((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path
'(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string
'(\\#[-a-z\\d_]*)?$','i'); // fragment locator
return pattern.test(str);
}

// https://stackoverflow.com/a/24657561
export const addhttp = (url) => {
if (!/^(?:f|ht)tps?\:\/\//.test(url)) {
url = "http://" + url;
}
return url;
}
8 changes: 8 additions & 0 deletions helpers/verifyTable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import query from "./db.js";
export const verifyTable = async(req,res,next) => {
const tableExistsQuery = `SELECT TABLE_NAME FROM information_schema.tables WHERE table_schema = '1ptco' AND table_name = '1pt';`
const insertTableQuery = `CREATE TABLE \`1pt\` (\`short_url\` varchar(10) NOT NULL,\`long_url\` varchar(256) NOT NULL,\`timestamp\` date NOT NULL DEFAULT current_timestamp(), \`hits\` int(11) NOT NULL, \`ip\` varchar(32) NOT NULL, \`email\` varchar(64) NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`
const exists = await query(tableExistsQuery)
if (!exists[0]) await query(insertTableQuery)
next()
}
13 changes: 11 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import dotenv from "dotenv";
dotenv.config()
import express from "express";
import helmet from "helmet";
import cors from "cors";
import winston from "winston"
import { addURL, getInfo, getProfileInfo, getURL } from "./handleRequests.js";
import { addURL, getInfo, getProfileInfo, getURL, redirect } from "./handleRequests.js";
import safeguard from "./helpers/safeguard.js";
import { verifyTable } from "./helpers/verifyTable.js";

const PORT = 8000;

Expand All @@ -25,14 +28,20 @@ app.get("/", (req, res) => {
res.status(200).send("Welcome to the 1pt.co API! Read the docs at github.com/1pt-co/1pt");
});

app.get("/getURL", (req, res) => safeguard(getURL, logger, req, res));
app.use('*', verifyTable)

app.get("/getURL" ,(req, res) => safeguard(getURL, logger, req, res));

app.get("/getInfo", (req, res) => safeguard(getInfo, logger, req, res));

app.post("/addURL", (req, res) => safeguard(addURL, logger, req, res));

app.get("/getProfileInfo", getProfileInfo);

// to replicate redirect functionality of frontend
app.get('/r/:shortCode', (req,res) => safeguard(redirect, logger, req, res));


app.listen(
PORT,
() => console.log(`1pt API running on http://localhost:${PORT}`)
Expand Down
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "1pt.co API",
"exports": "./index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"start": "node index.js"
},
"repository": {
"type": "git",
Expand All @@ -19,6 +19,7 @@
"homepage": "https://github.com/1pt-co/api#readme",
"dependencies": {
"cors": "^2.8.5",
"dotenv": "^16.0.3",
"express": "^4.18.2",
"geoip-lite": "^1.4.6",
"google-auth-library": "^8.7.0",
Expand Down