Skip to content

Commit 538096b

Browse files
authored
Merge pull request #35 from devxtra-community/shanu
Shanu
2 parents 24f3b4e + 985d2fe commit 538096b

85 files changed

Lines changed: 4432 additions & 1047 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

backend/package.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,37 @@
88
"lint": "eslint"
99
},
1010
"dependencies": {
11+
"@aws-sdk/client-s3": "^3.962.0",
12+
"bcrypt": "^6.0.0",
13+
"cors": "^2.8.5",
1114
"dotenv": "^17.2.3",
1215
"express": "^5.2.1",
16+
"form-data": "^4.0.5",
17+
"jsonwebtoken": "^9.0.3",
18+
"mailgun.js": "^12.4.1",
19+
"multer": "^2.0.2",
1320
"pg": "^8.16.3",
1421
"pino": "^10.1.0",
1522
"pino-http": "^11.0.0",
1623
"reflect-metadata": "^0.2.2",
24+
"twilio": "^5.11.1",
1725
"typeorm": "^0.3.28"
1826
},
1927
"devDependencies": {
2028
"@commitlint/cli": "^20.2.0",
2129
"@commitlint/config-conventional": "^20.2.0",
2230
"@eslint/js": "^9.39.2",
31+
"@types/bcrypt": "^6.0.0",
32+
"@types/cors": "^2.8.19",
2333
"@types/express": "^5.0.6",
34+
"@types/jsonwebtoken": "^9.0.10",
35+
"@types/multer": "^2.0.0",
2436
"@types/node": "^25.0.3",
2537
"@types/pg": "^8.16.0",
2638
"@typescript-eslint/eslint-plugin": "^8.51.0",
2739
"@typescript-eslint/parser": "^8.51.0",
2840
"eslint": "^9.39.2",
41+
"ts-node": "^10.9.2",
2942
"ts-node-dev": "^2.0.0",
3043
"typescript": "~5.9.3",
3144
"typescript-eslint": "^8.51.0"

backend/src/Services/authToken.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { appDataSouce } from '../data-source';
2+
import { RefreshTokenEntity } from '../entities/refreshToken';
3+
import { User } from '../entities/User';
4+
import { generateRefreshToken, hashRefreshToken } from './refreshToken';
5+
const REFRESH_TOKEN_DAYS = 30;
6+
7+
export const createRefreshTokenSession = async (user: User) => {
8+
const refreshToken = generateRefreshToken();
9+
console.log('Raw refresh token generated');
10+
11+
const tokenHash = hashRefreshToken(refreshToken);
12+
console.log('Token hashed');
13+
14+
const expiresAt = new Date();
15+
16+
expiresAt.setDate(expiresAt.getDate() + REFRESH_TOKEN_DAYS);
17+
18+
const repo = appDataSouce.getRepository(RefreshTokenEntity);
19+
console.log('Got refresh token repository');
20+
21+
await repo.save({
22+
tokenHash,
23+
user,
24+
expiresAt,
25+
});
26+
console.log('Refresh token saved');
27+
return refreshToken;
28+
};
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+
};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import jwt from 'jsonwebtoken';
2+
3+
export const signAccessToken = (payload: { userId: string }) => {
4+
return jwt.sign(payload, process.env.ACCESS_TOKEN_SECRET!, {
5+
expiresIn: '15m',
6+
});
7+
};
8+
export const verifyAccessToken = (token: string) => {
9+
return jwt.verify(token, process.env.ACCESS_TOKEN_SECRET!);
10+
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import crypto from 'crypto';
2+
export const generateRefreshToken = (): string => {
3+
return crypto.randomBytes(64).toString();
4+
};
5+
export const hashRefreshToken = (token: string) => {
6+
return crypto.createHash('sha256').update(token).digest('hex');
7+
};
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import Twilio from 'twilio';
2+
import { logger } from '../utils/logger';
3+
const accountSid = process.env.TWILIO_ACCOUNT_SID!;
4+
const authToken = process.env.TWILIO_AUTH_TOKEN!;
5+
const fromNumber = process.env.TWILIO_FROM_NUMBER!;
6+
const client = Twilio(accountSid, authToken);
7+
export const sendOtpSms = async (phoneNumber: string, otp: string) => {
8+
try {
9+
await client.messages.create({
10+
body: `otp is ${otp}`,
11+
from: fromNumber,
12+
to: phoneNumber,
13+
});
14+
} catch (err) {
15+
logger.error({ err }, 'failed to send otp');
16+
throw new Error('failed to send');
17+
}
18+
};

backend/src/app.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
import express from 'express';
2+
import cors from 'cors';
23
import { pinoHttp } from 'pino-http';
34
import { logger } from './utils/logger';
45
import { notFound } from './middleware/notFound';
56
import { errorHandler } from './middleware/errorHandler';
6-
import Healthrouter from './routes/health';
7+
import Healthrouter from './modules/health/health';
8+
import authRouter from './modules/auth/auth.routes';
9+
import userRouter from './modules/user/user.routes';
10+
711
const app = express();
12+
app.use(cors());
813
app.use(express.json());
914
app.use(
1015
pinoHttp({ logger, autoLogging: { ignore: (req) => req.url === 'health' } }),
1116
);
1217
app.use('/health', Healthrouter);
18+
app.use('/auth', authRouter);
19+
app.use('/user', userRouter);
1320
app.use(notFound);
1421
app.use(errorHandler);
1522
export default app;

backend/src/data-source.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import 'dotenv/config';
22
import { DataSource } from 'typeorm';
33
import { User } from './entities/User';
4+
import { Otp } from './entities/opt';
5+
import { RefreshTokenEntity } from './entities/refreshToken';
46
if (!process.env.DATABASE_URL) {
57
throw new Error('DATABASE_URL is not defined');
68
}
@@ -10,6 +12,6 @@ export const appDataSouce = new DataSource({
1012
ssl: {
1113
rejectUnauthorized: false,
1214
},
13-
entities: [User],
15+
entities: [User, Otp, RefreshTokenEntity],
1416
synchronize: true,
1517
});

backend/src/entities/User.ts

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,42 @@
1-
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
2-
@Entity()
1+
import {
2+
Entity,
3+
PrimaryGeneratedColumn,
4+
Column,
5+
CreateDateColumn,
6+
} from 'typeorm';
7+
8+
@Entity('users')
39
export class User {
4-
@PrimaryGeneratedColumn()
5-
id!: number;
10+
@PrimaryGeneratedColumn('uuid')
11+
id!: string;
12+
13+
@Column({ unique: true, nullable: true })
14+
email!: string;
15+
16+
@Column({ unique: true, nullable: true })
17+
phoneNumber!: string;
18+
619
@Column()
7-
phone!: string;
20+
name!: string;
21+
22+
@Column({ nullable: true })
23+
age?: number;
24+
25+
@Column({ nullable: true })
26+
gender?: string;
27+
28+
@Column('text', { array: true, nullable: true })
29+
interests?: string[];
30+
31+
@Column({ nullable: true })
32+
profileImageUrl?: string;
33+
34+
@Column({ nullable: true })
35+
passwordHash?: string;
36+
37+
@Column({ default: false })
38+
isPhoneVerified!: boolean;
39+
40+
@CreateDateColumn()
41+
createdAt!: Date;
842
}

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+
phoneNumber!: 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+
}

0 commit comments

Comments
 (0)