Skip to content

Commit

Permalink
Format typescript codebase with prettier
Browse files Browse the repository at this point in the history
  • Loading branch information
yong-jie committed May 18, 2020
1 parent deed7cb commit a339eea
Show file tree
Hide file tree
Showing 14 changed files with 137 additions and 102 deletions.
7 changes: 6 additions & 1 deletion .eslintrc.ts.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
{
"parser": "@typescript-eslint/parser",
"extends": ["airbnb", "plugin:import/typescript"],
"extends": [
"airbnb",
"plugin:import/typescript",
"prettier/@typescript-eslint",
"plugin:prettier/recommended"
],
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module"
Expand Down
21 changes: 8 additions & 13 deletions src/server/api/redirect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,9 @@ export default async function redirect(
const { logRedirectAnalytics } = container.get<AnalyticsLogger>(
DependencyIds.analyticsLogging,
)
const {
userHasVisitedShortlink,
writeShortlinkToCookie,
} = container.get<CookieReducer>(DependencyIds.cookieReducer)
const { userHasVisitedShortlink, writeShortlinkToCookie } = container.get<
CookieReducer
>(DependencyIds.cookieReducer)

let { shortUrl } = req.params

Expand Down Expand Up @@ -115,20 +114,16 @@ export default async function redirect(
req.session!.visits,
shortUrl,
)
req.session!.visits = writeShortlinkToCookie(
req.session!.visits,
shortUrl,
)
req.session!.visits = writeShortlinkToCookie(req.session!.visits, shortUrl)

if (renderTransitionPage) {
// Extract root domain from long url.
const rootDomain: string = parseDomain(longUrl)

res.status(200)
.render(TRANSITION_PATH, {
longUrl,
rootDomain,
})
res.status(200).render(TRANSITION_PATH, {
longUrl,
rootDomain,
})
return
}
res.status(302).redirect(longUrl)
Expand Down
4 changes: 3 additions & 1 deletion src/server/api/repositories/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export interface UserRepository {
["findOrCreateWithEmail"] }] */
export class UserRepositorySequelize implements UserRepository {
async findOrCreateWithEmail(email: string): Promise<object> {
return User.findOrCreate({ where: { email } }).then(([user, _]) => user.get())
return User.findOrCreate({ where: { email } }).then(([user, _]) =>
user.get(),
)
}
}
87 changes: 58 additions & 29 deletions src/server/api/statistics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,41 +11,70 @@ const router = Express.Router()
*/
router.get('/', async (_: Express.Request, res: Express.Response) => {
// Check if cache contains value
statClient.mget(['userCount', 'linkCount', 'clickCount'], async (cacheError, results: Array<string | null>) => {
if (cacheError) {
// log and fallback to database
logger.error(`Access to statistics cache failed unexpectedly:\t${cacheError}`)
}

// Since the expiry in Redis of the values are the same,
// all 3 should be present (or absent) from Redis together
// If the data is not in Redis, results will be [null, null, null]
if (!cacheError && !results.includes(null)) {
// Turn each value into an integer
const [userCount, linkCount, clickCount] = results.map((x) => Number(x))
statClient.mget(
['userCount', 'linkCount', 'clickCount'],
async (cacheError, results: Array<string | null>) => {
if (cacheError) {
// log and fallback to database
logger.error(
`Access to statistics cache failed unexpectedly:\t${cacheError}`,
)
}

res.json({ userCount, linkCount, clickCount })
return
}
// Since the expiry in Redis of the values are the same,
// all 3 should be present (or absent) from Redis together
// If the data is not in Redis, results will be [null, null, null]
if (!cacheError && !results.includes(null)) {
// Turn each value into an integer
const [userCount, linkCount, clickCount] = results.map((x) => Number(x))

// 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')])
res.json({ userCount, linkCount, clickCount })
return
}

// Cater to the edge case where clickCount is NaN because there are no links
const clickCount = Number.isNaN(clickCountUntrusted) ? 0 : clickCountUntrusted
// 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'),
])

res.json({ userCount, linkCount, clickCount })
// Cater to the edge case where clickCount is NaN because there are no links
const clickCount = Number.isNaN(clickCountUntrusted)
? 0
: clickCountUntrusted

res.json({ userCount, linkCount, clickCount })

// Store values into Redis
const callback = (err: Error | null) => {
if (err) {
logger.error(`Cache write failed:\t${err}`)
// Store values into Redis
const callback = (err: Error | null) => {
if (err) {
logger.error(`Cache write failed:\t${err}`)
}
}
}
statClient.set('userCount', `${userCount}`, 'EX', statisticsExpiry, callback)
statClient.set('linkCount', `${linkCount}`, 'EX', statisticsExpiry, callback)
statClient.set('clickCount', `${clickCount}`, 'EX', statisticsExpiry, callback)
})
statClient.set(
'userCount',
`${userCount}`,
'EX',
statisticsExpiry,
callback,
)
statClient.set(
'linkCount',
`${linkCount}`,
'EX',
statisticsExpiry,
callback,
)
statClient.set(
'clickCount',
`${clickCount}`,
'EX',
statisticsExpiry,
callback,
)
},
)
})

export = router
28 changes: 15 additions & 13 deletions src/server/api/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,7 @@ function validatePresignedUrlRequest(
* Endpoint for a user to create a short URL.
*/
router.post('/url', validateUrls, async (req, res) => {
const {
isFile, userId, longUrl, shortUrl,
} = req.body
const { isFile, userId, longUrl, shortUrl } = req.body

try {
const user = await User.findByPk(userId)
Expand All @@ -157,15 +155,17 @@ router.post('/url', validateUrls, async (req, res) => {
}

// Success
const result = await transaction((t) => Url.create(
{
userId: user.id,
longUrl,
shortUrl,
isFile: !!isFile,
},
{ transaction: t },
))
const result = await transaction((t) =>
Url.create(
{
userId: user.id,
longUrl,
shortUrl,
isFile: !!isFile,
},
{ transaction: t },
),
)

res.ok(result)
} catch (error) {
Expand Down Expand Up @@ -227,7 +227,9 @@ router.patch('/url/ownership', async (req, res) => {
}

// Success
const result = await transaction((t) => url.update({ userId: newUserId }, { transaction: t }))
const result = await transaction((t) =>
url.update({ userId: newUserId }, { transaction: t }),
)
res.ok(result)
} catch (error) {
logger.error(`Error transferring ownership of short URL:\t${error}`)
Expand Down
6 changes: 4 additions & 2 deletions src/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ const OTP_EXPIRY: number = Number(process.env.OTP_EXPIRY) || 5 * 60
// in seconds, for URL cache expiry
const REDIRECT_EXPIRY: number = Number(process.env.REDIRECT_EXPIRY) || 5 * 60
// in seconds, for statistics cache expiry
const STATISTICS_EXPIRY: number = Number(process.env.STATISTICS_EXPIRY) || 5 * 60
const STATISTICS_EXPIRY: number =
Number(process.env.STATISTICS_EXPIRY) || 5 * 60

// Compulsory environment variables required for booting up
const requiredVars: string[] = [
Expand Down Expand Up @@ -147,7 +148,8 @@ export const getOTP: OtpFunction = otpFunction
export const transporterOptions: nodemailer.TransporterOptions | null = transporterOpts
export const trustProxy: boolean = proxy
export const cookieSettings: CookieSettings = cookieConfig
export const cookieSessionMaxSizeBytes = Number(process.env.COOKIE_SESSION_MAX_SIZE_BYTES) || 2000
export const cookieSessionMaxSizeBytes =
Number(process.env.COOKIE_SESSION_MAX_SIZE_BYTES) || 2000
export const ogUrl = process.env.OG_URL as string
export const ogHostname = parse(ogUrl).hostname
export const gaTrackingId: string | undefined = process.env.GA_TRACKING_ID
Expand Down
20 changes: 11 additions & 9 deletions src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@ import api from './api'
import redirect from './api/redirect'

// Logger configuration
import {
cookieSettings, logger, sessionSettings, trustProxy,
} from './config'
import { cookieSettings, logger, sessionSettings, trustProxy } from './config'

// Services
const SessionStore = connectRedis(session)
Expand Down Expand Up @@ -44,14 +42,18 @@ import { Mailer } from './util/email'
morgan.token('client-ip', (req: express.Request) => getIp(req) as string)
morgan.token(
'redirectUrl',
(_: express.Request, res: express.Response): string => (res.statusCode === 302 ? (res.getHeader('location') as string) : ''),
(_: express.Request, res: express.Response): string =>
res.statusCode === 302 ? (res.getHeader('location') as string) : '',
)
morgan.token('userId', (req: express.Request) =>
req.session && req.session.user && req.session.user.id
? (req.session.user.id as string)
: '',
)
morgan.token('userId', (req: express.Request) => (req.session && req.session.user && req.session.user.id
? (req.session.user.id as string)
: ''))

const MORGAN_LOG_FORMAT = ':client-ip - [:date[clf]] ":method :url HTTP/:http-version" :status '
+ '":redirectUrl" ":userId" :res[content-length] ":referrer" ":user-agent" :response-time ms'
const MORGAN_LOG_FORMAT =
':client-ip - [:date[clf]] ":method :url HTTP/:http-version" :status ' +
'":redirectUrl" ":userId" :res[content-length] ":referrer" ":user-agent" :response-time ms'

const app = express()
app.use(helmet())
Expand Down
9 changes: 5 additions & 4 deletions src/server/models/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,12 @@ export const Url = <UrlTypeStatic>sequelize.define(
)
return Promise.resolve()
},
beforeBulkUpdate: () => Promise.reject(
new Error(
'Bulk updates are not allowed: please edit URLs individually instead.',
beforeBulkUpdate: () =>
Promise.reject(
new Error(
'Bulk updates are not allowed: please edit URLs individually instead.',
),
),
),
},
indexes: [
{
Expand Down
17 changes: 9 additions & 8 deletions src/server/util/aws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,7 @@ export const s3 = new S3()
*/
const reformatPresignedUrl = (url: string, fileName: string) => {
const urlObj = parse(url)
const {
host,
pathname,
protocol,
search,
} = urlObj
const { host, pathname, protocol, search } = urlObj
const newUrl = new URL('https://lorem-ipsum.com')
newUrl.protocol = protocol as string
newUrl.host = `${pathname?.split('/')[1]}.${host}`
Expand All @@ -35,7 +30,10 @@ const reformatPresignedUrl = (url: string, fileName: string) => {
return newUrl.href
}

export const generatePresignedUrl = async (fileName: string, fileType: string) => {
export const generatePresignedUrl = async (
fileName: string,
fileType: string,
) => {
const params = {
Bucket: s3Bucket,
Key: fileName,
Expand All @@ -46,7 +44,10 @@ export const generatePresignedUrl = async (fileName: string, fileType: string) =
return reformatPresignedUrl(presignedUrl, fileName)
}

export const setS3ObjectACL = (key: string, acl: FileVisibility): Promise<any> => {
export const setS3ObjectACL = (
key: string,
acl: FileVisibility,
): Promise<any> => {
const params = {
Bucket: s3Bucket,
Key: key,
Expand Down
2 changes: 1 addition & 1 deletion src/server/util/ga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export function sendPageViewHit(

// user language
if (req.headers['accept-language']) {
[form.ul] = (req.headers['accept-language'] as string).split(',')
;[form.ul] = (req.headers['accept-language'] as string).split(',')
}

request.post(
Expand Down
10 changes: 2 additions & 8 deletions src/server/util/transitionPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,12 @@ export interface CookieReducer {
["userHasVisitedShortlink", "writeShortlinkToCookie"] }] */
@injectable()
export class CookieArrayReducer implements CookieReducer {
userHasVisitedShortlink(
cookie: string[] | null,
shortUrl: string,
): boolean {
userHasVisitedShortlink(cookie: string[] | null, shortUrl: string): boolean {
if (!cookie) return false
return cookie.includes(shortUrl)
}

writeShortlinkToCookie(
cookie: string[] | null,
shortUrl: string,
): string[] {
writeShortlinkToCookie(cookie: string[] | null, shortUrl: string): string[] {
if (!cookie) return [shortUrl]
if (cookie.includes(shortUrl)) {
return _.without(cookie, shortUrl).concat(shortUrl)
Expand Down
6 changes: 4 additions & 2 deletions src/types/server/models/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// variable during compile time. For use in setters in model
// definition.
export interface Settable {
setDataValue(key: string, value: any): void
setDataValue(key: string, value: any): void
}

export interface IdType { readonly id: number }
export interface IdType {
readonly id: number
}
1 change: 0 additions & 1 deletion src/types/server/util/nodemailer.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
declare module 'nodemailer' {

interface TransporterCredentials {
user: string
pass: string
Expand Down

0 comments on commit a339eea

Please sign in to comment.