Skip to content

Commit

Permalink
Merge pull request #12 from silaselisha/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
silaselisha committed Dec 4, 2023
2 parents 3824bf1 + 8289267 commit b92230f
Show file tree
Hide file tree
Showing 11 changed files with 834 additions and 27 deletions.
740 changes: 732 additions & 8 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@
"@types/jest": "^29.5.10",
"@types/jsonwebtoken": "^9.0.5",
"@types/morgan": "^1.9.9",
"@types/multer": "^1.4.11",
"@types/sharp": "^0.32.0",
"@types/supertest": "^2.0.16",
"@types/uuid": "^9.0.7",
"@types/validator": "^13.11.7",
"@typescript-eslint/eslint-plugin": "^6.13.1",
"@typescript-eslint/parser": "^6.13.1",
Expand All @@ -48,14 +51,18 @@
},
"dependencies": {
"bcryptjs": "^2.4.3",
"cloudinary": "^1.41.0",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.0.2",
"morgan": "^1.10.0",
"multer": "^1.4.5-lts.1",
"pino": "^8.16.2",
"sharp": "^0.33.0",
"ts-node": "^10.9.1",
"uuid": "^9.0.1",
"validator": "^13.11.0"
}
}
2 changes: 1 addition & 1 deletion src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const logger = pino()
dotenv.config({ path: path.join(__dirname, '..', '.env') })

const app = express()

app.use(express.static(path.join(__dirname, 'public')))
process.env?.NODE_ENV === 'development'
? app.use(morgan('dev'))
: app.use(morgan('combined'))
Expand Down
49 changes: 42 additions & 7 deletions src/controllers/error-controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
import { type Request, type Response, type NextFunction } from 'express'
import type UtilsError from '../utils/app-error'
import UtilsError from '../utils/app-error'
import { logger } from '../app'
export interface CustomError {
code?: number
keyValue?: any
}

const handleClientError = (err: UtilsError, res: Response): void => {
res.status(err.statusCode).json({
status: err.status,
message: err.message
})
}

const handleDeveloperError = (err: UtilsError, res: Response): void => {
res.status(err.statusCode).json({
status: err.status,
message: err.message,
err,
stack: err.stack
})
}

const globalErrorHandler = (
err: Error,
Expand All @@ -10,13 +31,27 @@ const globalErrorHandler = (
const error = err as UtilsError
error.statusCode = error.statusCode ?? 500
error.status = error.status ?? 'internal server error'
logger.warn(error.code)

res.status(error.statusCode).json({
status: error.status,
message: error.message,
error,
stack: error.stack
})
if (process.env.NODE_ENV?.startsWith('dev') ?? false) {
handleDeveloperError(error, res)
} else if (process.env.NODE_ENV?.startsWith('prod') ?? false) {
let err = { ...error }

if (err.code === 11000) {
const [key, value] = Object.entries(err.keyValue)[0]
err = new UtilsError(`${key} ${value as string} is taken`, 400)
}
if (err.name === 'TokenExpiredError') {
err = new UtilsError(`${err?.message}`, 401)
}
if (err.name === 'JsonWebTokenError') {
err = new UtilsError(`${err?.message}`, 401)
}

logger.warn(err)
handleClientError(err, res)
}
}

export default globalErrorHandler
10 changes: 8 additions & 2 deletions src/controllers/post-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import type mongoose from 'mongoose'
import { catchAsync } from '../utils/app-error'
import { type Request, type Response, type NextFunction } from 'express'
import postModel from '../models/post-model'
import { imageProcessing } from '../utils'
import { type UploadApiResponse } from 'cloudinary'

export interface PostReqParams {
headline: string
article_body: string
article_section: string
citation?: string[]
citations?: string[]
summary?: string
}

Expand All @@ -24,9 +26,13 @@ export const createPost = catchAsync(
next: NextFunction
): Promise<void> => {
const { _id } = req.user
let imageData: UploadApiResponse | undefined
if (req.file !== undefined) imageData = await imageProcessing(req) as UploadApiResponse

const data: PostParams = {
user_id: _id,
...req.body
...req.body,
image: imageData?.public_id
}

const post = await postModel.create(data)
Expand Down
4 changes: 3 additions & 1 deletion src/middlewares/auth-middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ import { verifyAccessToken } from '../utils/token'
const authMiddleware = catchAsync(
async (req: Request, res: Response, next: NextFunction): Promise<void> => {
const authorization: string = req.headers.authorization as string
if (authorization === undefined) throw new UtilsError('authorization header invalid', 401)
if (authorization === undefined) {
throw new UtilsError('authorization header invalid', 401)
}
const fields: string[] = authorization?.split(' ')

if (fields.length !== 2) {
Expand Down
10 changes: 5 additions & 5 deletions src/models/post-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ export interface IPost extends mongoose.Document {
headline: string
article_body: string
article_section: string
image?: string[]
summary: string
citation: string[]
image?: string
summary?: string
citations: string[]
word_count?: number
comments?: mongoose.Schema.Types.ObjectId[]
comment_count?: number
Expand All @@ -33,9 +33,9 @@ const postSchema = new mongoose.Schema<IPost>(
required: [true, 'article section is compulsory'],
trim: true
},
image: [String],
image: String,
summary: String,
citation: [String],
citations: [String],
word_count: Number,
comments: [mongoose.Schema.Types.ObjectId],
comment_count: Number,
Expand Down
3 changes: 2 additions & 1 deletion src/routes/post-route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
import express, { type Router } from 'express'
import authMiddleware from '../middlewares/auth-middleware'
import { createPost } from '../controllers/post-controller'
import { uploadFiles } from '../utils'

const router: Router = express.Router()
router.route('/').post(authMiddleware, createPost)
router.route('/').post(authMiddleware, uploadFiles.single('thumbnail'), createPost)

export default router
8 changes: 8 additions & 0 deletions src/server.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { v2 as cloudinary } from 'cloudinary'
import app, { logger } from './app'
import db from './utils/db'

Expand All @@ -7,5 +8,12 @@ const URI: string = process.env.DB_URI?.replace('<password>', DB_PASSWORD) ?? ''

app.listen(port, () => {
void db(URI)
cloudinary.config({
secure: true,
cloud_name: process.env.CLOUDINARY_NAME,
api_key: process.env.CLOUDINARY_API_KEY,
api_secret: process.env.CLOUDINARY_API_SECRET
})
logger.info(process.env.CLOUDINARY_NAME)
logger.info(`Listening http://localhost:${port}`)
})
6 changes: 5 additions & 1 deletion src/utils/app-error.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { type NextFunction, type Request, type Response } from 'express'
import { type CustomError } from '../controllers/error-controller'

type AsyncMiddlewareFunc = (
req: Request,
res: Response,
next: NextFunction
) => Promise<void>

class UtilsError extends Error {
class UtilsError extends Error implements CustomError {
public statusCode: number
public isOperational: boolean
public status: string
public code?: number | undefined
public keyValue?: any

constructor (message: string, statusCode: number) {
super(message)
Expand Down
22 changes: 21 additions & 1 deletion src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
// import path from 'path'
import bcrypt from 'bcryptjs'
import multer from 'multer'
import { type UploadApiResponse, v2 as cloudinary } from 'cloudinary'
import { v4 as uuidv4 } from 'uuid'
import { logger } from '../app'
import UtilsError from './app-error'

const encryptPassword = async (password: string): Promise<string> => {
return await bcrypt.hash(password, 12)
Expand All @@ -11,4 +17,18 @@ const decryptPassword = async (
return await bcrypt.compare(password, hashedPassword)
}

export { decryptPassword, encryptPassword }
const storage = multer.memoryStorage()
const uploadFiles = multer({ storage })

const imageProcessing = async (req: any): Promise<UploadApiResponse | undefined> => {
try {
const res = await cloudinary.uploader.upload(`data:image/jpeg;base64,${req.file.buffer.toString('base64')}`, { use_filename: true, unique_filename: false, public_id: `assets/images/posts/thumbnails/${uuidv4()}` })

return res
} catch (error) {
logger.error(error)
throw new UtilsError('internal server error', 500)
}
}

export { decryptPassword, encryptPassword, imageProcessing, uploadFiles }

0 comments on commit b92230f

Please sign in to comment.