Skip to content

Commit

Permalink
feat(#42): Driver Manages Ride Request Notification and Trip Completi…
Browse files Browse the repository at this point in the history
…on (#46)

* feat(#42): Driver Manages Ride Request Notification and Trip Completion

- Adding app icon on ios
- generating routes using mapbox
- checking for permissions and blocking the app if they are not given

* feat(#42): Driver Manages Ride Request Notification and Trip Completion

- Mark pending ride requests as completed when accepting one

* feat(#42): Driver Manages Ride Request Notification and Trip Completion

- only fetch route once every minute when there is a ride request
- remove asking for locaton permission on location manager (optional)
- refactoring

* feat(#42): Driver Manages Ride Request Notification and Trip Completion

- fixing getting routes multiple times

* feat(#42): Driver Manages Ride Request Notification and Trip Completion

- adding reconecting to mqtt
- moving subscribing from publish location to connect
- showing connection status to the user

* feat(#42): Driver Manages Ride Request Notification and Trip Completion

- updating route if driver moves and there is an ongoing ride

* feat(#42): Driver Manages Ride Request Notification and Trip Completion

- removed harcoded references to ips

* feat(#42): Driver Manages Ride Request Notification and Trip Completion

- fixed a bug (more than 1 nearby driver only found 1
- improving mqtt client

* feat(#42): Driver Manages Ride Request Notification and Trip Completion

- added endpoint for requests
- loading requests on app start
- loading requests on auth

* feat(#42): Driver Manages Ride Request Notification and Trip Completion

- fixing nearby drivers utility

* feat(#42): Driver Manages Ride Request Notification and Trip Completion

- updating ride request instead of replacing
- using axios due to cache issues

* feat(#42): Driver Manages Ride Request Notification and Trip Completion

- updating ride cards ui
  • Loading branch information
chriscoderdr authored Nov 5, 2024
1 parent ba337d2 commit a788ccd
Show file tree
Hide file tree
Showing 57 changed files with 1,748 additions and 522 deletions.
2 changes: 1 addition & 1 deletion apps/api/.env
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
PORT=3000
DATABASE_URL=postgres://morro_user:morro_pass@127.0.0.1:5432/morrotaxi
DATABASE_URL=postgres://morro_user:morro_pass@db:5432/morrotaxi
BCRYPT_SALT_ROUNDS=12
3 changes: 2 additions & 1 deletion apps/api/src/config/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ dotenv.config();
const sequelize = new Sequelize(process.env.DATABASE_URL as string, {
dialect: "postgres",
logging: false,
operatorsAliases: false,
});



export default sequelize;
33 changes: 30 additions & 3 deletions apps/api/src/controllers/driver-controller.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { transformRideData } from '@/utils/ride-data-transformer';
import bcrypt from 'bcrypt';
import { Context } from 'koa';
import { Op } from 'sequelize';
import Driver from '../models/driver';
import RideRequest from '../models/ride-request';
import Rider from '../models/rider';
import { Driver, RideRequest, Rider } from '../models';

import logger from '../utils/logger';
import {
generateAccessToken,
Expand Down Expand Up @@ -307,3 +307,30 @@ export const completeRideRequest = async (ctx: Context) => {
ctx.body = { error: 'Server error. Please try again later.' };
}
};

export const getRideRequests = async (ctx: Context) => {
const driverId = ctx.state.user.id; // Assumes driver is authenticated, and ID is stored in the token
logger.info(`Fetching ride requests for driver ${driverId}`);

console.error('HOLAAAA');
try {
const rideRequests = await RideRequest.findAll({
where: { driverId },
order: [['updatedAt', 'DESC']] // Orders by updatedAt descending
});
const user = (await Driver.findByPk(driverId)) as any;
const transformedRideRequests = await Promise.all(
rideRequests.map((rideRequest) => transformRideData(user, rideRequest))
);

ctx.status = 200;
ctx.body = { data: transformedRideRequests }; // Returns an empty array if no ride requests are found
logger.info(
`Fetched ${rideRequests.length} ride requests for driver ${driverId}`
);
} catch (error) {
logger.error(error);
ctx.status = 500;
ctx.body = { error: 'Server error. Please try again later.' };
}
};
99 changes: 52 additions & 47 deletions apps/api/src/controllers/rider-controller.ts
Original file line number Diff line number Diff line change
@@ -1,90 +1,94 @@
import { queueService } from '@/services';
import { Context } from 'koa';
import { Op } from 'sequelize';
import RideRequest from '../models/ride-request';
import Rider from '../models/rider';
import { RideRequest, Rider } from '../models';
import logger from '../utils/logger';
import {
generateAccessToken,
generateRefreshToken
} from '../utils/token-utils';

interface RegisterRiderRequestBody {
name: string;
email: string;
phone: string;
password: string;
}

interface RideRequestLocation {
latitude: number;
longitude: number;
address: string;
}

interface CreateRideRequestBody {
pickupLocation: RideRequestLocation;
dropOffLocation: RideRequestLocation;
}

// Utility function to send error response
const sendErrorResponse = (ctx: Context, status: number, message: string) => {
ctx.status = status;
ctx.body = { error: message };
};

// Register Rider
export const registerRider = async (ctx: Context) => {
const { name, email, phone, password } = ctx.request.body as {
name: string;
email: string;
phone: string;
password: string;
};
const { name, email, phone, password } = ctx.request.body as RegisterRiderRequestBody;

// Validation
if (!name || !email || !phone || !password) {
ctx.status = 400;
ctx.body = { error: 'All fields are required.' };
return;
return sendErrorResponse(ctx, 400, 'All fields are required.');
}
if (password.length < 8) {
ctx.status = 400;
ctx.body = { error: 'Password must be at least 8 characters.' };
return;
return sendErrorResponse(ctx, 400, 'Password must be at least 8 characters.');
}

try {
// Check if rider exists
const existingRider = await Rider.findOne({
where: { [Op.or]: [{ email }, { phone }] }
});
if (existingRider) {
ctx.status = 409;
ctx.body = { error: 'Email or phone number already registered.' };
return;
return sendErrorResponse(ctx, 409, 'Email or phone number already registered.');
}

// Create new rider
const newRider = await Rider.create({ name, email, phone, password });

const accessToken = generateAccessToken(newRider.id, 'rider');
const accessToken = generateAccessToken(newRider.dataValues.id, 'rider');
const refreshToken = generateRefreshToken();

await newRider.update({ refreshToken });

// Success response
ctx.status = 201;
ctx.body = {
message: 'Rider registered successfully.',
riderId: newRider.id,
riderId: newRider.dataValues.id,
accessToken,
refreshToken
};
} catch (error) {
logger.error(error);
ctx.status = 500;
ctx.body = { error: 'Server error. Please try again later.' };
logger.error('Error registering rider:', error);
sendErrorResponse(ctx, 500, 'Server error. Please try again later.');
}
};

// Create Ride Request
export const createRideRequest = async (ctx: Context) => {
const { pickupLocation, dropOffLocation } = ctx.request.body as {
pickupLocation: {
latitude: number;
longitude: number;
address: string;
};
dropOffLocation: {
latitude: number;
longitude: number;
address: string;
};
};

const { pickupLocation, dropOffLocation } = ctx.request.body as CreateRideRequestBody;
const riderId = ctx.state.user.id;

// Validation
if (!pickupLocation || !dropOffLocation) {
ctx.status = 400;
ctx.body = {
error:
'Pickup location and drop-off location are required for a ride request.'
};
return;
return sendErrorResponse(
ctx,
400,
'Pickup location and drop-off location are required for a ride request.'
);
}

try {
// Create new ride request
const newRideRequest = await RideRequest.create({
riderId,
pickupLocation: {
Expand All @@ -101,17 +105,18 @@ export const createRideRequest = async (ctx: Context) => {
});

logger.info(`Ride request created: ${newRideRequest.pickupAddress}`);


// Queue the ride request
await queueService.addRideRequestToQueue(newRideRequest);

// Success response
ctx.status = 201;
ctx.body = {
message: 'Ride request created successfully and added to the queue.',
rideRequestId: newRideRequest.id
};
} catch (error) {
logger.error(error);
ctx.status = 500;
ctx.body = { error: 'Server error. Please try again later.' };
logger.error('Error creating ride request:', error);
sendErrorResponse(ctx, 500, 'Server error. Please try again later.');
}
};
2 changes: 1 addition & 1 deletion apps/api/src/controllers/token-controlller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Context } from 'koa';
import Driver from '../models/driver';
import { Driver } from '../models';
import { generateAccessToken } from '../utils/token-utils';

export const refreshAccessToken = async (ctx: Context) => {
Expand Down
3 changes: 1 addition & 2 deletions apps/api/src/middleware/auth.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import logger from '@/utils/logger';
import jwt from 'jsonwebtoken';
import { Context, Next } from 'koa';
import Driver from '../models/driver';
import Rider from '../models/rider';
import { Driver, Rider } from '../models';

const ACCESS_TOKEN_SECRET = process.env.ACCESS_TOKEN_SECRET || 'access-secret';

Expand Down
33 changes: 18 additions & 15 deletions apps/api/src/models/driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ interface DriverAttributes {
coordinates: [number, number]; // [longitude, latitude]
};
lastLocationUpdatedAt?: Date;
isAvailable?: boolean;
isAvailable: boolean;
}

interface DriverCreationAttributes extends Optional<DriverAttributes, 'id'> {}

class Driver extends Model<DriverAttributes, DriverCreationAttributes> {
class Driver extends Model<DriverAttributes, DriverCreationAttributes> implements DriverAttributes {
public id!: string;
public name!: string;
public email!: string;
Expand All @@ -41,68 +41,71 @@ class Driver extends Model<DriverAttributes, DriverCreationAttributes> {
public lastLocationUpdatedAt?: Date;
public isAvailable!: boolean;

// Method to compare encrypted password
public async comparePassword(password: string): Promise<boolean> {
return bcrypt.compare(password, this.password);
}
}

// Initialize the Driver model
Driver.init(
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
primaryKey: true
},
name: {
type: DataTypes.STRING,
allowNull: false,
allowNull: false
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: { isEmail: true },
validate: { isEmail: true }
},
phone: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
unique: true
},
password: {
type: DataTypes.STRING,
allowNull: false,
allowNull: false
},
refreshToken: {
type: DataTypes.STRING,
allowNull: true,
allowNull: true
},
loginAt: {
type: DataTypes.DATE,
allowNull: true,
allowNull: true
},
location: {
type: DataTypes.GEOGRAPHY('POINT', 4326),
allowNull: true,
allowNull: true
},
lastLocationUpdatedAt: {
type: DataTypes.DATE,
allowNull: true,
allowNull: true
},
isAvailable: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: true,
},
defaultValue: true
}
},
{
sequelize,
tableName: 'drivers',
hooks: {
// Hook to hash password before creating the driver instance
beforeCreate: async (driver) => {
driver.password = await bcrypt.hash(driver.password, saltRounds);
},
}
},
timestamps: true,
timestamps: true
}
);

Expand Down
15 changes: 15 additions & 0 deletions apps/api/src/models/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import sequelize from '@/config/database';
import Driver from './driver';
import RideRequest from './ride-request';
import Rider from './rider';

Driver.hasOne(RideRequest, {
foreignKey: 'driverId',
as: 'currentRideRequest'
});
RideRequest.belongsTo(Driver, { foreignKey: 'driverId', as: 'driver' });

Rider.hasMany(RideRequest, { foreignKey: 'riderId', as: 'rideRequests' });
RideRequest.belongsTo(Rider, { foreignKey: 'riderId', as: 'rider' });

export { Driver, Rider, RideRequest, sequelize };
Loading

0 comments on commit a788ccd

Please sign in to comment.