diff --git a/package.json b/package.json index dfe8b92e..2f03ab02 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "swagger-ui-express": "^5.0.1", "ts-jest": "^29.2.3", "ts-node-dev": "^2.0.0", + "twilio": "^5.2.2", "typeorm": "^0.3.20" } } diff --git a/src/config/index.ts b/src/config/index.ts index 61ab49cf..853e7f73 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -15,6 +15,10 @@ const config = { SMTP_HOST: process.env.SMTP_HOST, SMTP_SERVICE: process.env.SMTP_SERVICE, NODE_ENV: process.env.NODE_ENV, + TWILIO_SID: process.env.TWILIO_SID, + TWILIO_AUTH_TOKEN: process.env.TWILIO_AUTH_TOKEN, + TWILIO_PHONE_NUMBER: process.env.TWILIO_PHONE_NUMBER, + }; export default config; diff --git a/src/controllers/SmsController.ts b/src/controllers/SmsController.ts new file mode 100644 index 00000000..47bb2bc1 --- /dev/null +++ b/src/controllers/SmsController.ts @@ -0,0 +1,46 @@ +import { Request, Response } from "express"; +import SmsService from "../services/sms.services"; +import AppDataSource from "../data-source"; +import { User } from "../models"; + +export const sendSms = async (req: Request, res: Response): Promise => { + const { phone_number, message } = req.body; + const sender_id = req.user.id; + + if (!phone_number || !message || !sender_id) { + res.status(400).json({ + status: "unsuccessful", + status_code: 400, + message: + "Valid phone number, message content, and sender ID must be provided.", + }); + return; + } + + try { + const userRepository = AppDataSource.getRepository(User); + const sender = await userRepository.findOneBy({ id: sender_id }); + + if (!sender) { + res.status(404).json({ + status: "unsuccessful", + status_code: 404, + message: "Sender not found.", + }); + return; + } + + await SmsService.sendSms(sender, phone_number, message); + res.status(200).json({ + status: "success", + status_code: 200, + message: "SMS sent successfully.", + }); + } catch (error) { + res.status(500).json({ + status: "unsuccessful", + status_code: 500, + message: "Failed to send SMS. Please try again later.", + }); + } +}; diff --git a/src/controllers/TestimonialsController.ts b/src/controllers/TestimonialsController.ts index 9f1e5592..a1982b36 100644 --- a/src/controllers/TestimonialsController.ts +++ b/src/controllers/TestimonialsController.ts @@ -133,4 +133,43 @@ export default class TestimonialsController { res.status(500).send({ message: error.message }); } } + + // CODE BY TOMILLA OLUWAFEMI + public async getAllTestimonials(req: Request, res: Response) { + try { + const testimonials = await AppDataSource.getRepository(Testimonial).find(); + res.status(200).json({ + message: "Testimonials retrieved successfully", + status_code: 200, + data: testimonials, + }); + } catch (error) { + res.status(500).send({ message: error.message }); + } + } + + public async deleteTestimonial(req: Request, res: Response) { + try { + const { testimonial_id } = req.params; + + const testimonialToDelete = await AppDataSource.getRepository(Testimonial).findOne({ + where: { id: testimonial_id }, + }); + + if (!testimonialToDelete) { + return res + .status(404) + .send({ message: "Testimonial not found", status_code: 404 }); + } + + await AppDataSource.getRepository(Testimonial).remove(testimonialToDelete); + + res.status(200).json({ + message: "Testimonial deleted successfully", + status_code: 200, + }); + } catch (error) { + res.status(500).send({ message: error.message }); + } + } } diff --git a/src/index.ts b/src/index.ts index 562956ee..5a9d4c2c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,6 +11,7 @@ import { authRoute, testimonialRoute, notificationRouter, + smsRouter, } from "./routes"; import { routeNotFound, errorHandler } from "./middleware"; import { orgRouter } from "./routes/organisation"; @@ -42,6 +43,7 @@ server.get("/", (req: Request, res: Response) => { }); server.use("/api/v1", userRouter, orgRouter); server.use("/api/v1/auth", authRoute); +server.use("/api/v1/sms", smsRouter); server.use("/api/v1", testimonialRoute); server.use("/api/v1/docs", swaggerUi.serve, swaggerUi.setup(swaggerSpec)); server.use(routeNotFound); diff --git a/src/routes/index.ts b/src/routes/index.ts index a665db2c..380705bc 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -2,4 +2,5 @@ export * from "./auth"; export * from "./user"; export * from "./testimonial"; -export * from "./notificationsettings" +export * from "./sms"; +export * from "./notificationsettings"; diff --git a/src/routes/sms.ts b/src/routes/sms.ts new file mode 100644 index 00000000..de9e1594 --- /dev/null +++ b/src/routes/sms.ts @@ -0,0 +1,9 @@ +import { Router } from "express"; +import { sendSms } from "../controllers/SmsController"; +import { authMiddleware } from "../middleware"; + +const smsRouter = Router(); + +smsRouter.post("/send", authMiddleware, sendSms); + +export { smsRouter }; diff --git a/src/routes/testimonial.ts b/src/routes/testimonial.ts index 127436fe..55cdc57f 100644 --- a/src/routes/testimonial.ts +++ b/src/routes/testimonial.ts @@ -19,4 +19,16 @@ testimonialRoute.get( testimonialController.getTestimonial.bind(testimonialController) ); +// CODE BY TOMILOLA OLUWAFEMI +testimonialRoute.get( + "/testimonials", + authMiddleware, + testimonialController.getAllTestimonials.bind(testimonialController) +); +testimonialRoute.delete( + "/testimonials/:testimonial_id", + authMiddleware, + testimonialController.deleteTestimonial.bind(testimonialController) +); + export { testimonialRoute }; diff --git a/src/services/sms.services.ts b/src/services/sms.services.ts new file mode 100644 index 00000000..5e8cd52a --- /dev/null +++ b/src/services/sms.services.ts @@ -0,0 +1,35 @@ +import { Twilio } from "twilio"; +import config from "../config"; +import AppDataSource from "../data-source"; +import { Sms } from "../models/sms"; +import { User } from "../models"; + +class SmsService { + private twilioClient: Twilio; + + constructor() { + this.twilioClient = new Twilio(config.TWILIO_SID, config.TWILIO_AUTH_TOKEN); + } + + public async sendSms( + sender: User, + phoneNumber: string, + message: string + ): Promise { + await this.twilioClient.messages.create({ + body: message, + from: config.TWILIO_PHONE_NUMBER, + to: phoneNumber, + }); + + const sms = new Sms(); + sms.sender = sender; + sms.phone_number = phoneNumber; + sms.message = message; + + const smsRepository = AppDataSource.getRepository(Sms); + await smsRepository.save(sms); + } +} + +export default new SmsService(); diff --git a/yarn.lock b/yarn.lock index f191c1af..ff17d662 100644 --- a/yarn.lock +++ b/yarn.lock @@ -940,6 +940,13 @@ acorn@^8.11.0, acorn@^8.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + ansi-escapes@^4.2.1: version "4.3.2" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" @@ -1041,6 +1048,15 @@ atomic-sleep@^1.0.0: resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== +axios@^1.6.8: + version "1.7.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.2.tgz#b625db8a7051fbea61c35a3cbb3a1daa7b9c7621" + integrity sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + babel-jest@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" @@ -1487,7 +1503,7 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4: +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4: version "4.3.5" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== @@ -1828,6 +1844,11 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" +follow-redirects@^1.15.6: + version "1.15.6" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" + integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== + foreground-child@^3.1.0: version "3.2.1" resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.2.1.tgz#767004ccf3a5b30df39bed90718bab43fe0a59f7" @@ -2047,6 +2068,14 @@ http-errors@2.0.0: statuses "2.0.1" toidentifier "1.0.1" +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + human-signals@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" @@ -3258,6 +3287,11 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + pump@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" @@ -3278,7 +3312,7 @@ qs@6.11.0: dependencies: side-channel "^1.0.4" -qs@^6.11.0: +qs@^6.11.0, qs@^6.9.4: version "6.12.3" resolved "https://registry.yarnpkg.com/qs/-/qs-6.12.3.tgz#e43ce03c8521b9c7fd7f1f13e514e5ca37727754" integrity sha512-AWJm14H1vVaO/iNZ4/hO+HyaTehuy9nRqVdkTqlJt0HWvBiBIEXFmb4C0DGeYo3Xes9rrEW+TxHsaigCbN5ICQ== @@ -3396,6 +3430,11 @@ safe-stable-stringify@^2.3.1: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +scmp@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/scmp/-/scmp-2.1.0.tgz#37b8e197c425bdeb570ab91cc356b311a11f9c9a" + integrity sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q== + secure-json-parse@^2.4.0: version "2.7.0" resolved "https://registry.yarnpkg.com/secure-json-parse/-/secure-json-parse-2.7.0.tgz#5a5f9cd6ae47df23dba3151edd06855d47e09862" @@ -3847,6 +3886,19 @@ tslib@^2.5.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== +twilio@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/twilio/-/twilio-5.2.2.tgz#56285174e72fcfdbe28d4ca021337910c3d8191b" + integrity sha512-t2Nd8CvqAc0YxbJghKYQl1Vxc7e6SrWk4U28wwkarUohGcsUMLsGpYeGXKw1Va0KB9TGVZYCs8dcP4TdLJUN9Q== + dependencies: + axios "^1.6.8" + dayjs "^1.11.9" + https-proxy-agent "^5.0.0" + jsonwebtoken "^9.0.2" + qs "^6.9.4" + scmp "^2.1.0" + xmlbuilder "^13.0.2" + type-detect@4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" @@ -4007,6 +4059,11 @@ write-file-atomic@^4.0.2: imurmurhash "^0.1.4" signal-exit "^3.0.7" +xmlbuilder@^13.0.2: + version "13.0.2" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-13.0.2.tgz#02ae33614b6a047d1c32b5389c1fdacb2bce47a7" + integrity sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ== + xtend@^4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"