Skip to content

Commit

Permalink
update links to support custom URL hash
Browse files Browse the repository at this point in the history
  • Loading branch information
JaneJeon committed Sep 29, 2019
1 parent 4e2a1f3 commit 6ba719a
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 30 deletions.
67 changes: 37 additions & 30 deletions models/link.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,47 @@
const mongoose = require('../lib/mongoose')
const Sequence = require('./sequence')

const { URL } = require('url')
const normalizeUrl = require('normalize-url')
const HashIds = require('hashids/cjs')
const hash = new HashIds(process.env.DOMAIN, process.env.HASH_MIN_LENGTH - 0)

const mongoose = require('../lib/mongoose')
const AutoIncrement = require('mongoose-sequence')(mongoose)

const schema = new mongoose.Schema(
{
_id: {
type: Number,
immutable: true,
get: id => hash.encode(id)
const schema = new mongoose.Schema({
url: {
type: String,
required: true,
unique: true,
trim: true,
validate: {
validator: url => new URL(url).host !== process.env.DOMAIN,
msg: `Cannot shorten ${process.env.DOMAIN} URLs`
},
url: {
type: String,
required: true,
trim: true,
minlength: process.env.HASH_MIN_LENGTH,
maxlength: process.env.HASH_MAX_LENGTH,
validate: [
{
validator: url => new URL(url).host !== process.env.DOMAIN,
msg: `Cannot shorten ${process.env.DOMAIN} URLs`
}
],
set: url => normalizeUrl(url, { forceHttps: true }),
unique: true
}
set: url => normalizeUrl(url, { forceHttps: true })
},
{ _id: false }
)
hash: {
type: String,
unique: true,
trim: true,
minlength: process.env.HASH_MIN_LENGTH,
maxlength: process.env.HASH_MAX_LENGTH,
validate: [
{
validator: hash => /^\w+$/.test(hash),
msg: 'Hashes must be alphanumeric'
},
{
validator: val => !hash.decode(val).length,
msg: 'Cannot use this hash'
}
]
}
})

schema.plugin(AutoIncrement)
schema.statics.findByHashId = function(hashId) {
return this.findById(hash.decode(hashId)[0])
}
schema.pre('save', async function() {
if (!this.hash) this.hash = hash.encode(await Sequence.next())
})
schema.virtual('redirectTo').get(function() {
return `${process.env.DOMAIN}/${this.hash}`
})

module.exports = mongoose.model('Link', schema)
37 changes: 37 additions & 0 deletions models/link.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
require('dotenv-defaults').config()

const Link = require('./link')
const HashIds = require('hashids/cjs')
const hashIds = new HashIds(process.env.DOMAIN, process.env.HASH_MIN_LENGTH - 0)

describe('Link model', () => {
const url = 'www.nodejs.org'
const normalizedURL = 'https://nodejs.org'
let hash

beforeAll(async () => {
return Link.deleteMany({})
})

test('shorten URL', async () => {
const doc = await Link.create({ url })
hash = doc.hash

expect(typeof doc.hash).toBe('string')
expect(doc.hash.length).toBeGreaterThanOrEqual(6)
})

test('fetch URL', async () => {
const doc = await Link.findOne({ hash })

expect(doc.url).toBe(normalizedURL)
expect(doc.redirectTo.startsWith(process.env.DOMAIN)).toBe(true)
})

test('set custom hash', async () => {
await Link.create({ url: 'example.com', hash: 'FooBar' })

// eslint-disable-next-line no-unused-expressions
expect(new Link({ url, hash: hashIds.encode(500) }).validate()).rejects
})
})
17 changes: 17 additions & 0 deletions models/sequence.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const mongoose = require('../lib/mongoose')

const schema = new mongoose.Schema({
_id: { type: String, required: true },
i: { type: Number, default: 0 }
})

schema.static('next', async function() {
const doc = await this.findOneAndUpdate(
{ _id: 'ctr' },
{ $inc: { i: 1 } },
{ new: true, upsert: true, setDefaultsOnInsert: true, lean: true }
)
return doc.i
})

module.exports = mongoose.model('Sequence', schema, 'sequence')

0 comments on commit 6ba719a

Please sign in to comment.