Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce UrlRepository abstraction layer #165

Merged
merged 1 commit into from
Jun 9, 2020
Merged
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
56 changes: 0 additions & 56 deletions src/server/api/cache/url.ts

This file was deleted.

10 changes: 8 additions & 2 deletions src/server/api/qrcode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,21 @@ import { ogUrl } from '../config'
import createGoQrCode from '../util/qrcode'
import ImageFormat from '../../shared/util/image-format'
import { isValidShortUrl } from '../../shared/util/validation'
import { Url } from '../models/url'
import { container } from '../util/inversify'
import { UrlRepositoryInterface } from '../repositories/interfaces/UrlRepositoryInterface'
import { DependencyIds } from '../constants'

const urlRepository = container.get<UrlRepositoryInterface>(
DependencyIds.urlRepository,
)

function isValidFormat(format: string): boolean {
const validFormats = Object.values(ImageFormat) as string[]
return validFormats.includes(format)
}

async function shortUrlExists(shortUrl: string): Promise<boolean> {
return !!(await Url.findOne({ where: { shortUrl } }))
return !!(await urlRepository.findByShortUrl(shortUrl))
}

const qrCodeRequestSchema = Joi.object({
Expand Down
41 changes: 6 additions & 35 deletions src/server/api/redirect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,14 @@ import { logger } from '../config'
import { NotFoundError } from '../util/error'
import parseDomain from '../util/domain'
import { container } from '../util/inversify'
import { UrlCache } from './cache/url'
import { UrlRepository } from './repositories/url'
import { DependencyIds } from '../constants'
import { AnalyticsLogger } from './analytics/analyticsLogger'
import { CookieReducer } from '../util/transition-page'
import { UrlRepositoryInterface } from '../repositories/interfaces/UrlRepositoryInterface'

const ERROR_404_PATH = '404.error.ejs'
const TRANSITION_PATH = 'transition-page.ejs'

/**
* Looks up the longUrl given a shortUrl from the cache, falling back
* to the database. The cache is re-populated if the database lookup is
* performed successfully.
* @param {string} shortUrl
* @returns {Promise<string>}
* @throws {NotFoundError}
*/
async function getLongUrlFromStore(shortUrl: string): Promise<string> {
const { getLongUrlFromCache, cacheShortUrl } = container.get<UrlCache>(
DependencyIds.urlCache,
)
const { getLongUrlFromDatabase } = container.get<UrlRepository>(
DependencyIds.urlRepository,
)

try {
// Cache lookup
return await getLongUrlFromCache(shortUrl)
} catch {
// Cache failed, look in database
const longUrl = await getLongUrlFromDatabase(shortUrl)
cacheShortUrl(shortUrl, longUrl).catch((error) =>
logger.error(`Unable to cache short URL: ${error}`),
)
return longUrl
}
}

/**
* Checks whether the input short url is valid.
* @param {string} shortUrl
Expand Down Expand Up @@ -84,16 +54,17 @@ export default async function redirect(
req: Express.Request,
res: Express.Response,
) {
const { incrementClick } = container.get<UrlRepository>(
DependencyIds.urlRepository,
)
const { logRedirectAnalytics } = container.get<AnalyticsLogger>(
DependencyIds.analyticsLogging,
)
const { userHasVisitedShortlink, writeShortlinkToCookie } = container.get<
CookieReducer
>(DependencyIds.cookieReducer)

const { getLongUrl, incrementClick } = container.get<UrlRepositoryInterface>(
DependencyIds.urlRepository,
)

let { shortUrl }: { shortUrl: string } = req.params

// Short link must consist of valid characters
Expand All @@ -105,7 +76,7 @@ export default async function redirect(

try {
// Find longUrl to redirect to
const longUrl = await getLongUrlFromStore(shortUrl)
const longUrl = await getLongUrl(shortUrl)

// Update clicks in database
incrementClick(shortUrl).catch((error) =>
Expand Down
65 changes: 0 additions & 65 deletions src/server/api/repositories/url.ts

This file was deleted.

11 changes: 8 additions & 3 deletions src/server/api/statistics.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import Express from 'express'
import { statClient } from '../redis'
import { User } from '../models/user'
import { Url } from '../models/url'
import { logger, statisticsExpiry } from '../config'
import { DependencyIds } from '../constants'
import { container } from '../util/inversify'
import { UrlRepositoryInterface } from '../repositories/interfaces/UrlRepositoryInterface'

const router = Express.Router()
const urlRepository = container.get<UrlRepositoryInterface>(
DependencyIds.urlRepository,
)

/**
* Endpoint to retrieve total user, link, and click counts.
Expand Down Expand Up @@ -35,8 +40,8 @@ router.get('/', async (_: Express.Request, res: Express.Response) => {
// If the values are not found in the cache, we read from the DB
const [userCount, linkCount, clickCountUntrusted] = await Promise.all([
User.count(),
Url.count(),
Url.sum('clicks'),
urlRepository.getNumUrls(),
urlRepository.getTotalLinkClicks(),
])

// Cater to the edge case where clickCount is NaN because there are no links
Expand Down
Loading