Skip to content

Commit

Permalink
refactor: controller service pattern for user api (#195)
Browse files Browse the repository at this point in the history
* refactor: userurlservice

* fix: bracket on

* refactor: usercontroller

* fix: change ownership response
  • Loading branch information
JasonChong96 authored Jun 20, 2020
1 parent 0af7c09 commit 380a142
Show file tree
Hide file tree
Showing 12 changed files with 396 additions and 231 deletions.
239 changes: 11 additions & 228 deletions src/server/api/user/index.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,21 @@
import Express from 'express'
import fileUpload from 'express-fileupload'
import { createValidator } from 'express-joi-validation'
import jsonMessage from '../../util/json'
import { logger } from '../../config'
import { DependencyIds } from '../../constants'
import { container } from '../../util/inversify'
import {
OldUrlEditRequest,
OwnershipTransferRequest,
UrlCreationRequest,
UrlEditRequest,
} from '../../../types/server/api/user.d'
import { addFileExtension, getFileExtension } from '../../util/fileFormat'
import { MessageType } from '../../../shared/util/messages'
import { MAX_FILE_UPLOAD_SIZE } from '../../../shared/constants'
import { UrlRepositoryInterface } from '../../repositories/interfaces/UrlRepositoryInterface'
import { StorableFile } from '../../repositories/types'
import { UserRepositoryInterface } from '../../repositories/interfaces/UserRepositoryInterface'
import { NotFoundError } from '../../util/error'
import {
ownershipTransferSchema,
urlEditSchema,
urlRetrievalSchema,
urlSchema,
} from './validators'
import { UserControllerInterface } from '../../controllers/interfaces/UserControllerInterface'

const router = Express.Router()

const urlRepository = container.get<UrlRepositoryInterface>(
DependencyIds.urlRepository,
)
const userRepository = container.get<UserRepositoryInterface>(
DependencyIds.userRepository,
const userController = container.get<UserControllerInterface>(
DependencyIds.userController,
)

const fileUploadMiddleware = fileUpload({
Expand Down Expand Up @@ -73,107 +57,13 @@ router.post(
fileUploadMiddleware,
preprocessPotentialIncomingFile,
validator.body(urlSchema),
async (req, res) => {
const { userId, longUrl, shortUrl }: UrlCreationRequest = req.body
const file = req.files?.file

if (Array.isArray(file)) {
res.badRequest(jsonMessage('Only single file uploads are supported.'))
return
}

try {
const user = await userRepository.findById(userId)

if (!user) {
res.notFound(jsonMessage('User not found'))
return
}

const existsShortUrl = await urlRepository.findByShortUrl(shortUrl)
if (existsShortUrl) {
res.badRequest(
jsonMessage(
`Short link "${shortUrl}" already exists`,
MessageType.ShortUrlError,
),
)
return
}

const storableFile: StorableFile | undefined = file
? {
data: file.data,
key: addFileExtension(shortUrl, getFileExtension(file.name)),
mimetype: file.mimetype,
}
: undefined

// Success
const result = await urlRepository.create(
{
userId: user.id,
longUrl,
shortUrl,
},
storableFile,
)

res.ok(result)
} catch (error) {
logger.error(`Error creating short URL:\t${error}`)
res.badRequest(jsonMessage('Server error.'))
}
},
userController.createUrl,
)

router.patch(
'/url/ownership',
validator.body(ownershipTransferSchema),
async (req, res) => {
const {
userId,
shortUrl,
newUserEmail,
}: OwnershipTransferRequest = req.body
try {
// Test current user really owns the shortlink
const url = await userRepository.findOneUrlForUser(userId, shortUrl)

if (!url) {
res.notFound(
jsonMessage(`Short link "${shortUrl}" not found for user.`),
)
return
}

// Check that the new user exists
const newUser = await userRepository.findByEmail(
newUserEmail.toLowerCase(),
)

if (!newUser) {
res.notFound(jsonMessage('User not found.'))
return
}
const newUserId = newUser.id

// Do nothing if it is the same user
if (userId === newUserId) {
res.badRequest(jsonMessage('You already own this link.'))
return
}

// Success
const result = await urlRepository.update(url, {
userId: newUserId,
})
res.ok(result)
} catch (error) {
logger.error(`Error transferring ownership of short URL:\t${error}`)
res.badRequest(jsonMessage('An error has occured'))
}
},
userController.changeOwnership,
)

/**
Expand All @@ -183,128 +73,21 @@ router.patch(
* the long URL. This is to ensure a one-to-one mapping between
* the short URL and S3 object key.
*/
router.patch(
'/url/edit',
fileUploadMiddleware,
preprocessPotentialIncomingFile,
validator.body(urlSchema),
async (req, res) => {
const { userId, longUrl, shortUrl }: OldUrlEditRequest = req.body
const file = req.files?.file
if (Array.isArray(file)) {
res.badRequest(jsonMessage('Only single file uploads are supported.'))
return
}

try {
const url = await userRepository.findOneUrlForUser(userId, shortUrl)

if (!url) {
res.notFound(
jsonMessage(`Short link "${shortUrl}" not found for user.`),
)
return
}

const storableFile: StorableFile | undefined = file
? {
data: file.data,
key: addFileExtension(shortUrl, getFileExtension(file.name)),
mimetype: file.mimetype,
}
: undefined

await urlRepository.update(url, { longUrl }, storableFile)

res.ok(jsonMessage(`Short link "${shortUrl}" has been updated`))
} catch (e) {
logger.error(`Error editing long URL:\t${e}`)
res.badRequest(jsonMessage('Invalid URL.'))
}
},
)

/**
* Endpoint for user to make edits to their link.
*/
router.patch(
'/url',
fileUploadMiddleware,
preprocessPotentialIncomingFile,
validator.body(urlEditSchema),
async (req, res) => {
const { userId, longUrl, shortUrl, state }: UrlEditRequest = req.body
const file = req.files?.file
if (Array.isArray(file)) {
res.badRequest(jsonMessage('Only single file uploads are supported.'))
return
}

try {
const url = await userRepository.findOneUrlForUser(userId, shortUrl)

if (!url) {
res.notFound(
jsonMessage(`Short link "${shortUrl}" not found for user.`),
)
return
}

const storableFile: StorableFile | undefined = file
? {
data: file.data,
key: addFileExtension(shortUrl, getFileExtension(file.name)),
mimetype: file.mimetype,
}
: undefined

await urlRepository.update(url, { longUrl, state }, storableFile)
res.ok()
} catch (error) {
logger.error(`Error editing URL:\t${error}`)
res.badRequest(jsonMessage(`Unable to edit short link "${shortUrl}"`))
}
},
userController.updateUrl,
)

/**
* Endpoint for a user to retrieve their own URLs based on the query conditions.
*/
router.get('/url', validator.body(urlRetrievalSchema), async (req, res) => {
const { userId } = req.body
let { limit = 10000, searchText = '' } = req.query
limit = Math.min(10000, limit)
searchText = searchText.toLowerCase()
const {
offset = 0,
orderBy = 'updatedAt',
sortDirection = 'desc',
isFile,
state,
} = req.query
const queryConditions = {
limit,
offset,
orderBy,
sortDirection,
searchText,
userId,
state,
isFile,
}
// Find user and paginated urls
try {
const { urls, count } = await userRepository.findUrlsForUser(
queryConditions,
)
res.ok({ urls, count })
} catch (error) {
if (error instanceof NotFoundError) {
res.notFound(error.message)
} else {
res.serverError(jsonMessage('Error retrieving URLs for user'))
}
}
})
router.get(
'/url',
validator.body(urlRetrievalSchema),
userController.getUrlsWithConditions,
)

export = router
2 changes: 2 additions & 0 deletions src/server/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export const DependencyIds = {
authService: Symbol.for('authService'),
loginController: Symbol.for('loginController'),
logoutController: Symbol.for('logoutController'),
urlManagementService: Symbol.for('urlManagementService'),
userController: Symbol.for('userController'),
}

export default DependencyIds
Loading

0 comments on commit 380a142

Please sign in to comment.