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
2 changes: 1 addition & 1 deletion public/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ form {
text-decoration: none;
color: inherit;
display: inline-block;
width: 100px;
/* width: 100px; */
overflow: hidden;
text-overflow: ellipsis;
}
Expand Down
129 changes: 83 additions & 46 deletions public/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 `
<td>
<a id="clipCount" href="#chartModal" data-clip="${shortenedUrl._id}" onclick="getChartInfo(event, 'device')" data-toggle="modal">
${shortenedUrl.click_count}
</a>
</td>
<td>
<a class="trimmed" target="_blank" href="/${shortenedUrl.urlCode}">
${shortenedUrl.clipped_url}
</a>
</td>
<td class="action-btn">
<a href="javascript:void(0);" class="fas fa-copy fa-lg copy" data="${shortenedUrl.clipped_url}" data-tippy-placement="top" data-tippy-content="COPIED!">
</a>
<a href="https://api.whatsapp.com/send?&text=${shortenedUrl.clipped_url}+' '+ ${clipText}*">
<i class="fab fa-whatsapp fa-lg"></i>
</a>
<a target="_blank" href="https://www.facebook.com/sharer/sharer.php?u=${shortenedUrl.clipped_url}+' '+ ${clipText}%>&amp;src=sdkpreparse" class="fb-xfbml-parse-ignore"><i class="fab fa-facebook fa-lg"></i></a>
<a class="" href="https://twitter.com/intent/tweet?text=${shortenedUrl.clipped_url}+' '+ ${clipText}%>" data-size="large">
<i class="fab fa-twitter fa-lg"></i>
</a>
<a href="javascript:void(0);" data-id="${shortenedUrl._id}" class="fas fa-trash-alt fa-lg" onclick="deleteUrl(this)">
</a>
</td>
<td>
<a class="long-url" href="${shortenedUrl.long_url}">${shortenedUrl.long_url}</a>
</td>
<td id="col-expiry">
${expiry || '—' }
</td>
`
}

/**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 = `
<td>
<a id="clipCount" href="#chartModal" data-clip="${_id}" onclick="getChartInfo(event, 'device')" data-toggle="modal">
${click_count}
</a>
</td>
<td>
<a class="trimmed" target="_blank" href="/${urlCode}">
${clipped_url}
</a>
</td>
<td class="action-btn">
<a href="javascript:void(0);" class="fas fa-copy fa-lg copy" data="${clipped_url}" data-tippy-placement="top" data-tippy-content="COPIED!">
</a>
<a href="https://api.whatsapp.com/send?&text=${clipped_url}+' '+ ${clipText}*">
<i class="fab fa-whatsapp fa-lg"></i>
</a>
<a target="_blank" href="https://www.facebook.com/sharer/sharer.php?u=${clipped_url}+' '+ ${clipText}%>&amp;src=sdkpreparse" class="fb-xfbml-parse-ignore"><i class="fab fa-facebook fa-lg"></i></a>
<a class="" href="https://twitter.com/intent/tweet?text=${clipped_url}+' '+ ${clipText}%>" data-size="large">
<i class="fab fa-twitter fa-lg"></i>
</a>
</td>
<td>
<a class="long-url" href="${long_url}">${long_url}</a>
</td>
<td id="col-expiry">
${expiry_date || '—'}
</td>
`

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: <b>${clips.created_by}</b>`
} 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: <b>${payload.created_by}</b>`
}
if(syncDevicesForm){
syncDevicesForm.style.display = 'none'
}
showSyncID.style.display = 'block'
}
//Handle browser error here.
catch(error) {
Expand Down Expand Up @@ -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)
}
}
21 changes: 21 additions & 0 deletions src/controllers/urlController.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
};
1 change: 0 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
27 changes: 27 additions & 0 deletions src/middlewares/getSyncData.js
Original file line number Diff line number Diff line change
@@ -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
});
});
};
3 changes: 2 additions & 1 deletion src/middlewares/middlewares.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
5 changes: 4 additions & 1 deletion src/routes/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
validateCookie,
urlAlreadyTrimmedByUser,
stripUrl,
customUrlExists
customUrlExists,
getSyncedData
} from "../middlewares/middlewares";
import {
getUrlAndUpdateCount,
Expand All @@ -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'),
Expand Down
20 changes: 19 additions & 1 deletion src/views/index.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,11 @@
<a class="" href="https://twitter.com/intent/tweet?text=<%=clip.clipped_url+' '+ clipText%>" data-size="large" target="_blank">
<i class="fab fa-twitter fa-lg"></i>
</a>
<a href="javascript:void(0);" data-id="<%= clip.id %>" class="fas fa-trash-alt fa-lg" onclick="deleteUrl(this)">
</a>
</td>
<td>
<a class="long-url" href="<%=clip.long_url%>"><%=clip.long_url%></a>
<a class="long-url" href="<%=clip.long_url%>"><b><%=clip.long_url%></b></a>
</td>
<td id="col-expiry">
<%=clip.expiry_date ? new Date(clip.expiry_date).toDateString() : '—' %>
Expand All @@ -130,6 +132,22 @@
</tbody>
</table>
</div>

<!-- IMPORT TO DEVICE -->
<p class="show-sync-id" style="display: none;"></b></p>
<% if (userClips.length === 0) {%>
<form id="sync-devices-form">
<div class="input-group mx-auto">
<input type="text" name="userID" class="form-control form-control-lg" placeholder="Enter SyncID (Optional)" />
<div class="input-group-append">
<button class="btn btn-success custom-btn">Import</button>
</div>
</div>
</form>
<%} else {%>
<p>Here is your syncID: <b><%= created_by %></b></p>
<%}%>
<!-- IMPORT TO DEVICE -->
</div>
</div>
</div>
Expand Down
14 changes: 14 additions & 0 deletions src/views/partials/footer.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -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})
})
}
</script>

<script type="text/babel">
Expand Down