1import svgCaptcha from "svg-captcha";
2import { Router } from "express";
3
4import db from "./db.js";
5import config from "./config.js";
6import { ifLoggedInGoHome, userSessionRequired } from "./middlewares.js";
7
8import { pageHtml } from "./pages.js";
9import { makeId, makeToken } from "./utils.js";
10
11const router = Router();
12
13// all pages
14router.get("/", ifLoggedInGoHome, (req, res) => {
15 return res.send(
16 pageHtml.home({
17 query: req.query,
18 })
19 );
20});
21
22router.get("/new", userSessionRequired, (req, res) => {
23 return res.send(
24 pageHtml.newUrl({
25 query: req.query,
26 user: res.locals.user,
27 })
28 );
29});
30
31router.get("/me", userSessionRequired, (req, res) => {
32 return res.send(
33 pageHtml.me({
34 query: req.query,
35 user: res.locals.user,
36 })
37 );
38});
39
40router.get("/list", userSessionRequired, (req, res) => {
41 let limit = 20;
42 let offset = 0;
43
44 const page = req.query.p || 1; // 1
45 offset = page * limit - limit;
46
47 const count = db.get(`SELECT count(id) as count FROM urls WHERE user=@user`, {
48 user: res.locals.user.id,
49 });
50
51 const results = db.query(
52 `SELECT
53 id, destination, short
54 FROM
55 urls WHERE user=@user
56 ORDER BY created_at DESC
57 LIMIT ${limit}
58 OFFSET ${offset}`,
59 {
60 user: res.locals.user.id,
61 }
62 );
63 const totalPages = Math.ceil(count.count / limit);
64 let nextPageLink = "";
65 let prevPageLink = "";
66 if (page < totalPages) {
67 nextPageLink = `/list?p=${Number(page) + 1}`;
68 }
69 if (page > 1) {
70 prevPageLink = `/list?p=${Number(page) - 1}`;
71 }
72
73 return res.send(
74 pageHtml.listUrls({
75 query: req.query,
76 user: res.locals.user,
77 results,
78 totalPages,
79 offset,
80 nextPageLink,
81 prevPageLink,
82 page,
83 })
84 );
85});
86
87router.get("/logout", (req, res) => {
88 req.session.destroy(() => {
89 return res.redirect("/");
90 });
91});
92
93router.get("/captcha.svg", (req, res) => {
94 const captcha = svgCaptcha.create();
95 req.session.captchaText = captcha.text;
96 res.set("Content-Type", "image/svg+xml");
97 return res.send(captcha.data);
98});
99
100// all submissions
101router.post("/login", (req, res, next) => {
102 const { body } = req;
103 const backUrl = req.header("Referer") || "/";
104
105 if (!body.username || !body.captcha) {
106 return res.redirect(backUrl + "?message=Invalid+payload");
107 }
108
109 if (!req.session.captchaText || req.session.captchaText !== body.captcha) {
110 return res.redirect(backUrl + "?message=Invalid+captcha");
111 }
112
113 let user = {
114 id: makeId(),
115 username: body.username,
116 api_token: makeToken(),
117 status: "active",
118 };
119 const userResults = db.query(
120 `SELECT id, username, api_token, created_at, status FROM users where username=@username LIMIT 1`,
121 {
122 username: body.username,
123 }
124 );
125
126 if (userResults.length == 0) {
127 // create user
128 db.run(
129 `INSERT INTO users (id, username, api_token, status) VALUES (@id, @username, @api_token, @status);`,
130 { ...user }
131 );
132 }
133
134 if (userResults[0]) {
135 user = {
136 ...userResults[0],
137 };
138 }
139
140 if (user.status !== "active") {
141 return res.redirect(backUrl + "?message=User+cannot+login");
142 }
143
144 req.session.user = user;
145 return res.redirect("/list");
146});
147
148// where all urls goto shorten :)
149router.post("/", userSessionRequired, (req, res, next) => {
150 const { body } = req;
151 const backUrl = req.header("Referer") || "/new";
152
153 if (!body || !body.full_url) {
154 if (res.locals.not_browser) {
155 return res.status(400).send("Invalid payload");
156 }
157 return res.redirect(backUrl + "?message=Invalid+payload");
158 }
159
160 if (body.short) {
161 const shortResults = db.query(`SELECT id FROM urls WHERE short=@short`, {
162 short: body.short,
163 });
164
165 if (shortResults.length) {
166 if (res.locals.not_browser) {
167 return res.status(400).send("Short code already in use");
168 }
169 return res.redirect(backUrl + "?message=Short+code+already+in+use");
170 }
171 }
172
173 const url = {
174 id: makeId(),
175 destination: body.full_url,
176 short: body.short || makeId(),
177 user: res.locals.user.id,
178 };
179
180 // create url
181 db.run(
182 `INSERT INTO urls (id, destination, short, user) VALUES (@id, @destination, @short, @user);`,
183 { ...url }
184 );
185
186 if (res.locals.not_browser) {
187 return res.send(`${config.hostname}/${url.short}`);
188 }
189
190 return res.redirect("/list");
191});
192
193// last route to get full url for a short
194router.get("/:short", (req, res, next) => {
195 const { short } = req.params;
196
197 const data = db.get(`SELECT destination FROM urls WHERE short=@short`, {
198 short,
199 });
200
201 if (data) {
202 return res.status(301).redirect(data.destination);
203 }
204
205 next();
206});
207
208export default router;