diff --git a/public/css/style.css b/public/css/style.css index 709c565..44a1757 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -135,7 +135,7 @@ form { text-decoration: none; color: inherit; display: inline-block; - width: 100px; + /* width: 100px; */ overflow: hidden; text-overflow: ellipsis; } diff --git a/public/js/index.js b/public/js/index.js index 05c1199..20d4043 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -3,60 +3,81 @@ const table_body = document.querySelector('#tbody'); const clipsListContainer = document.querySelector('#clips-list-container'); const err_msg = document.querySelector('#msg'); const clipText = " \n Amazingly shortened with Trim. Visit http://trim.ng to trim your Links!!!"; +const syncDevicesForm = document.querySelector('#sync-devices-form'); +const showSyncID = document.querySelector('.show-sync-id') + +const generateClipRow = (shortenedUrl) => { + let expiry + if (shortenedUrl.expiry_date) { + expiry = new Date(shortenedUrl.expiry_date).toDateString() + } + return ` + + + ${shortenedUrl.click_count} + + + + + ${shortenedUrl.clipped_url} + + + + + + + + + + + + + + + + + ${shortenedUrl.long_url} + + + ${expiry || '—' } + + ` +} /**Gets the new trim returned from the server and adds it to the display. * Prints an error if the server returns an error message. * @param {Response} response. The response object. */ -const printNewTrim = async(response)=> { +const renderShorts = async(response)=> { if (!response.ok) return showError(response, true); - trimUrlForm.reset(); - let tr_clip = document.createElement('tr') - tr_clip.id = 'table-body' - // Logic to add new trim to the list here. try { - const newClip = await response.json() - let { _id, click_count, long_url, urlCode, clipped_url, expiry_date} = await newClip.payload; - - if (expiry_date) - expiry_date = new Date(expiry_date).toDateString(); - - const clip_row = ` - - - ${click_count} - - - - - ${clipped_url} - - - - - - - - - - - - - - - ${long_url} - - - ${expiry_date || '—'} - - ` - - tr_clip.innerHTML = clip_row - clipsListContainer.style.display = "initial"; - - table_body.prepend(tr_clip) + const clips = await response.json() + if (clips.userClips) { + const { userClips } = clips + for (let shortUrl of userClips) { + const tr_clip = document.createElement('tr') + tr_clip.id = 'table-body' + tr_clip.innerHTML = generateClipRow(shortUrl) + table_body.append(tr_clip) + } + syncDevicesForm.reset() + showSyncID.innerHTML = `Here is your syncID: ${clips.created_by}` + } else if (clips.payload) { + // Logic to add new trim to the list here. + const { payload } = clips + const tr_clip = document.createElement('tr') + tr_clip.id = 'table-body' + tr_clip.innerHTML = generateClipRow(payload) + clipsListContainer.style.display = "initial"; + table_body.prepend(tr_clip) + showSyncID.innerHTML = `Here is your syncID: ${payload.created_by}` + } + if(syncDevicesForm){ + syncDevicesForm.style.display = 'none' + } + showSyncID.style.display = 'block' } //Handle browser error here. catch(error) { @@ -104,7 +125,23 @@ if(trimUrlForm){ }, body: JSON.stringify({...urlData}) }) - .then(printNewTrim) //Be sure to handle error response from the server. + .then(renderShorts) //Be sure to handle error response from the server. .catch(showError); //If the browser fails to communicate with the server, handle such errors here. } +} + +if (syncDevicesForm) { + syncDevicesForm.onsubmit = (e) => { + e.preventDefault() + const userID = document.querySelector("input[name='userID'") + fetch('/sync-device', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({userID: userID.value}) + }) + .then(renderShorts) + .catch(showError) + } } \ No newline at end of file diff --git a/src/controllers/urlController.js b/src/controllers/urlController.js index c19d786..7aa88a1 100644 --- a/src/controllers/urlController.js +++ b/src/controllers/urlController.js @@ -83,3 +83,24 @@ export const getUrlAndUpdateCount = async (req, res, next) => { return res.status(404).render('error'); } }; + + +/** + * This function gets original url by the trim code supplied as a parameter + * e.g trim.ly/TRIM_CODE + * @param {object} req + * @param {object} res + * @returns {object} next middleware + */ +export const deleteUrl = async (req, res, next) => { + try { + const { urlId } = req.body; + if (!urlId) { + return next(); + } + await UrlShorten.findByIdAndDelete(urlId); + return res.status(200).json({ success: true }) + } catch (error) { + return res.status(404).render('error'); + } +}; \ No newline at end of file diff --git a/src/index.js b/src/index.js index 90026c5..a2291d7 100644 --- a/src/index.js +++ b/src/index.js @@ -8,7 +8,6 @@ const { initRoutes } = require('./routes/routes'); const db = require('./database/db'); const app = express(); - app.use((req, res, next) => { //res.setHeader('Access-Control-Allow-Origin', '*'); //Don't think we need CORS here. res.setHeader( diff --git a/src/middlewares/getSyncData.js b/src/middlewares/getSyncData.js new file mode 100644 index 0000000..c67a929 --- /dev/null +++ b/src/middlewares/getSyncData.js @@ -0,0 +1,27 @@ +import UrlShorten from "../models/UrlShorten"; +/** + * This function renders the landing page and gets list of user trimmed urls + * @param {object} req + * @param {object} res + * @returns {object} response object with trimmed urls + */ +export const getSyncedData = (req, res) => { + const { userID } = req.body; + res.cookie("userID", userID, { + maxAge: 1000 * 60 * 60 * 60 * 30, + }); + UrlShorten.find({ + created_by: userID //Find all clips created by this user. + }) + .sort({ + createdAt: "desc" // sort the created clips in a decending order + }) + .then(clips => { + //Pass the user's clips to the view engine to render the customized view for this user. + return res.status(200).json({ + userClips: clips, + created_by: userID, + success: true + }); + }); +}; \ No newline at end of file diff --git a/src/middlewares/middlewares.js b/src/middlewares/middlewares.js index 3ecdf4a..7064fc1 100644 --- a/src/middlewares/middlewares.js +++ b/src/middlewares/middlewares.js @@ -3,5 +3,6 @@ import { validateCookie } from './validateCookie'; import { renderLandingPage } from './renderLandingPage'; import{ aboutPage } from './aboutPage'; import { validateOwnDomain, urlAlreadyTrimmedByUser, stripUrl, customUrlExists } from './validateUrl'; +import { getSyncedData } from './getSyncData'; -export { renderLandingPage, aboutPage, validateOwnDomain, validateCookie, urlAlreadyTrimmedByUser, stripUrl, customUrlExists }; +export { renderLandingPage, aboutPage, validateOwnDomain, validateCookie, urlAlreadyTrimmedByUser, stripUrl, customUrlExists, getSyncedData }; diff --git a/src/routes/routes.js b/src/routes/routes.js index 322f92d..93cd76b 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -7,7 +7,8 @@ import { validateCookie, urlAlreadyTrimmedByUser, stripUrl, - customUrlExists + customUrlExists, + getSyncedData } from "../middlewares/middlewares"; import { getUrlAndUpdateCount, @@ -19,6 +20,8 @@ import { getUrlClickMetrics } from '../controllers/metricsController'; export const initRoutes = app => { app.get("/", validateCookie, renderLandingPage); + app.post("/sync-device", validateCookie, getSyncedData); + app.delete('/', deleteUrl); app.get("/about", (req, res) => res.status(200).render("about")); app.get("/docs", (req,res)=>res.status(200).redirect("https://documenter.getpostman.com/view/4447136/SzYaWe6j?version=latest")); app.post("/", [check('long_url').isString().not().isEmpty().withMessage('Long url cannot be empty'), diff --git a/src/views/index.ejs b/src/views/index.ejs index 7aba182..f082efd 100644 --- a/src/views/index.ejs +++ b/src/views/index.ejs @@ -118,9 +118,11 @@ + + - <%=clip.long_url%> + <%=clip.long_url%> <%=clip.expiry_date ? new Date(clip.expiry_date).toDateString() : '—' %> @@ -130,6 +132,22 @@ + + + + <% if (userClips.length === 0) {%> +
+
+ +
+ +
+
+
+ <%} else {%> +

Here is your syncID: <%= created_by %>

+ <%}%> + diff --git a/src/views/partials/footer.ejs b/src/views/partials/footer.ejs index cc34c4b..0aec840 100644 --- a/src/views/partials/footer.ejs +++ b/src/views/partials/footer.ejs @@ -90,6 +90,20 @@ copyToClipboard(x); }) } + + function deleteUrl(clip) { + const el = clip.closest('tr'); + if (window.confirm(`Do you really want to delete "${el.childNodes[7].innerText}"`)) { + el.remove() + } + fetch('/', { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({urlId: clip.dataset.id}) + }) + }