Skip to content

Commit 3f4b37d

Browse files
committed
feat: send otp api created
1 parent 2c53b47 commit 3f4b37d

13 files changed

Lines changed: 270 additions & 9 deletions

File tree

backend/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
"dependencies": {
1111
"dotenv": "^17.2.3",
1212
"express": "^5.2.1",
13+
"form-data": "^4.0.5",
14+
"mailgun.js": "^12.4.1",
1315
"pg": "^8.16.3",
1416
"pino": "^10.1.0",
1517
"pino-http": "^11.0.0",
@@ -26,6 +28,7 @@
2628
"@typescript-eslint/eslint-plugin": "^8.51.0",
2729
"@typescript-eslint/parser": "^8.51.0",
2830
"eslint": "^9.39.2",
31+
"ts-node": "^10.9.2",
2932
"ts-node-dev": "^2.0.0",
3033
"typescript": "~5.9.3",
3134
"typescript-eslint": "^8.51.0"
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import Mailgun from 'mailgun.js';
2+
import FormData from 'form-data';
3+
import { logger } from '../utils/logger';
4+
5+
const mailgun = new Mailgun(FormData);
6+
7+
const mg = mailgun.client({
8+
username: 'api',
9+
key: process.env.MAILGUN_API_KEY!,
10+
});
11+
12+
export const sendOtpEmail = async (to: string, otp: string) => {
13+
try {
14+
await mg.messages.create(process.env.MAILGUN_DOMAIN!, {
15+
from: process.env.MAIL_FROM_EMAIL!,
16+
to,
17+
subject: 'Your SocialCode verification code',
18+
text: `Your SocialCode verification code is ${otp}. This code expires in 5 minutes.`,
19+
});
20+
} catch (err) {
21+
logger.error({ err }, 'Error sending OTP email');
22+
throw new Error('Failed to send OTP email');
23+
}
24+
};

backend/src/app.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@ import { pinoHttp } from 'pino-http';
33
import { logger } from './utils/logger';
44
import { notFound } from './middleware/notFound';
55
import { errorHandler } from './middleware/errorHandler';
6-
import Healthrouter from './routes/health';
6+
import Healthrouter from './modules/health/health';
7+
import authRouter from './modules/auth/auth.routes'
8+
9+
710
const app = express();
811
app.use(express.json());
912
app.use(
1013
pinoHttp({ logger, autoLogging: { ignore: (req) => req.url === 'health' } }),
1114
);
1215
app.use('/health', Healthrouter);
16+
app.use('/auth', authRouter);
1317
app.use(notFound);
1418
app.use(errorHandler);
1519
export default app;

backend/src/data-source.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'dotenv/config';
22
import { DataSource } from 'typeorm';
33
import { User } from './entities/User';
4+
import { Otp } from './entities/opt';
45
if (!process.env.DATABASE_URL) {
56
throw new Error('DATABASE_URL is not defined');
67
}
@@ -10,6 +11,6 @@ export const appDataSouce = new DataSource({
1011
ssl: {
1112
rejectUnauthorized: false,
1213
},
13-
entities: [User],
14+
entities: [User,Otp],
1415
synchronize: true,
1516
});

backend/src/entities/opt.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import {
2+
Entity,
3+
PrimaryGeneratedColumn,
4+
Column,
5+
CreateDateColumn,
6+
} from 'typeorm';
7+
8+
@Entity('otps')
9+
export class Otp {
10+
@PrimaryGeneratedColumn('uuid')
11+
id!: string;
12+
@Column()
13+
email!: string;
14+
@Column()
15+
otp!: string;
16+
@Column({ type: 'timestamp' })
17+
expiresAt!: Date;
18+
@Column({default:false})
19+
verified!: boolean;
20+
@CreateDateColumn()
21+
createdAt!: Date;
22+
}
Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,37 @@
11
import { Request, Response, NextFunction } from 'express';
2+
import { logger } from '../utils/logger';
3+
4+
type AppError = {
5+
statusCode?: number;
6+
message?: string;
7+
};
28

39
export const errorHandler = (
410
err: unknown,
511
req: Request,
612
res: Response,
713
_next: NextFunction,
814
) => {
9-
console.error(err);
15+
logger.info('reached inside global error');
16+
const error = err as AppError;
1017

11-
let message = 'Internal Server Error';
18+
const statusCode = error.statusCode ?? 500;
19+
const message =
20+
statusCode === 500
21+
? 'Internal Server Error'
22+
: (error.message ?? 'Something went wrong');
1223

13-
if (err instanceof Error) {
14-
message = err.message;
15-
}
24+
logger.error(
25+
{
26+
err,
27+
path: req.path,
28+
method: req.method,
29+
},
30+
'Unhandled error',
31+
);
1632

17-
res.status(500).json({ message });
33+
res.status(statusCode).json({
34+
success: false,
35+
message,
36+
});
1837
};
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { Request, Response, NextFunction } from 'express';
2+
import { logger } from '../../utils/logger';
3+
import { emailSchema } from './auth.schema';
4+
import { sendOtpEmail } from '../../Services/email.service';
5+
import { generateotp } from '../../utils/otp';
6+
import { appDataSouce } from '../../data-source';
7+
import { Otp } from '../../entities/opt';
8+
export const sendOtp = async (
9+
req: Request,
10+
res: Response,
11+
next: NextFunction,
12+
) => {
13+
try {
14+
logger.info('reached');
15+
const result = emailSchema.safeParse(req.body);
16+
if (!result.success) {
17+
return res
18+
.status(400)
19+
.json({ message: 'validation vailed', error: result.error?.format() });}
20+
const otpRep = appDataSouce.getRepository(Otp)
21+
const otpcode = generateotp()
22+
logger.debug({otpcode},"otp is")
23+
const email = result.data?.email;
24+
await sendOtpEmail(email.toString(),otpcode.toString())
25+
await otpRep.delete({email})
26+
await otpRep.save({
27+
email,
28+
otp:otpcode.toString(),
29+
expiresAt: new Date(Date.now()+5*60*1000)
30+
31+
})
32+
res.status(200).json({ message: "otp sent success fully ",success:true });
33+
} catch (err) {
34+
logger.error('error in register');
35+
next(err);
36+
}
37+
};
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import express from 'express';
2+
import { sendOtp } from './auth.controller';
3+
const authRouter = express.Router();
4+
authRouter.post('/send-otp', sendOtp);
5+
6+
export default authRouter;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { z } from 'zod';
2+
export const emailSchema = z.object({
3+
email: z.string().email('Invalid email'),
4+
});
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import express from 'express';
2-
import { appDataSouce } from '../data-source';
2+
import { appDataSouce } from '../../data-source';
33
const Healthrouter = express.Router();
44
Healthrouter.get('/', async (req, res) => {
55
try {

0 commit comments

Comments
 (0)