Skip to content

Commit

Permalink
Switch carts to REST
Browse files Browse the repository at this point in the history
  • Loading branch information
koredefashokun committed Jan 3, 2025
1 parent de64cbd commit cb68df8
Show file tree
Hide file tree
Showing 17 changed files with 124 additions and 95 deletions.
2 changes: 2 additions & 0 deletions api/src/controllers/carts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Request, Response } from 'express';
import prismaClient from '../config/prisma';

export default class CartController {
// GET /carts/:id
public async getCartById(req: Request, res: Response) {
if (!req.auth) {
return res.status(401).json({ error: 'User not authenticated' });
Expand All @@ -23,6 +24,7 @@ export default class CartController {
return res.json({ cart });
}

// POST /carts/products
public async addProductToCart(req: Request, res: Response) {
const { productId, quantity } = req.body;

Expand Down
34 changes: 33 additions & 1 deletion api/src/controllers/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,22 @@ export default class UserController {
return res.json({ user });
}

// PUT /users/current
public async updateCurrentUser(req: Request, res: Response) {
const { name, email } = req.body;

if (!req.auth) {
return res.status(401).json({ error: 'User not authenticated' });
}

const updatedUser = await prismaClient.user.update({
where: { id: req.auth.id },
data: { name, email }
});

return res.json({ user: updatedUser });
}

// GET /users/current/followed-stores
public async getFollowedStores(req: Request, res: Response) {
if (!req.auth) {
Expand Down Expand Up @@ -42,12 +58,14 @@ export default class UserController {

// GET /users/current/carts
public async getCarts(req: Request, res: Response) {
console.log('get carts');
if (!req.auth) {
return res.status(401).json({ error: 'User not authenticated' });
}

const carts = await prismaClient.cart.findMany({
where: { userId: req.auth.id }
where: { userId: req.auth.id },
include: { store: { include: { image: true } }, products: true }
});

return res.json({ carts });
Expand All @@ -66,6 +84,20 @@ export default class UserController {
return res.json({ cards });
}

// GET /users/managed-stores
public async getManagedStores(req: Request, res: Response) {
if (!req.auth) {
return res.status(401).json({ error: 'User not authenticated' });
}

const stores = await prismaClient.storeManager.findMany({
where: { managerId: req.auth.id },
include: { store: true }
});

return res.json({ stores });
}

// GET /users/current/delivery-addresses
public async getDeliveryAddresses(req: Request, res: Response) {
if (!req.auth) {
Expand Down
4 changes: 4 additions & 0 deletions api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import redisClient from './config/redis';
import { initSentry } from './config/sentry';
import health from './routes/health';
import payments from './routes/payments';
import stores from './routes/stores';
import users from './routes/users';
import webhooks from './routes/webhooks';
import schema from './schema';
import Services from './services';
Expand Down Expand Up @@ -62,6 +64,8 @@ const main = async () => {
app.use('/webhooks', webhooks);
app.use('/payments', payments);
app.use('/health', health);
app.use('/users', users);
app.use('/stores', stores);

const PORT = Number(process.env.PORT || 3000);
httpServer.listen({ port: PORT });
Expand Down
6 changes: 6 additions & 0 deletions api/src/routes/carts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,11 @@ const router: Router = Router();
const cartController = new CartController();

router.get('/:id', authenticate, cartController.getCartById);
router.post('/products', authenticate, cartController.addProductToCart);
router.delete(
'/:id/products/:productId',
authenticate,
cartController.removeProductFromCart
);

export default router;
13 changes: 6 additions & 7 deletions api/src/routes/search.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Router, Request, Response } from 'express';

import { db } from '../db';
import prismaClient from '../config/prisma';

const router: Router = Router();

Expand All @@ -9,16 +9,15 @@ router.get('/', async (req: Request, res: Response) => {
const { query } = req.query;

if (!query || typeof query !== 'string') {
res.json({ data: [] });
return;
return res.json({ data: [] });
}

const [products, stores] = await Promise.all([
db.query.Product.findMany({
where: (products, { ilike }) => ilike(products.name, `%${query}%`)
prismaClient.product.findMany({
where: { name: { contains: query, mode: 'insensitive' } }
}),
db.query.Store.findMany({
where: (stores, { ilike }) => ilike(stores.name, `%${query}%`)
prismaClient.store.findMany({
where: { name: { contains: query, mode: 'insensitive' } }
})
]);

Expand Down
26 changes: 8 additions & 18 deletions api/src/routes/users.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import { Router, Request, Response } from 'express';
import { Router } from 'express';

import prismaClient from '../config/prisma';
import UserController from '../controllers/users';
import { authenticate } from '../middleware/auth';

const router: Router = Router();
const userController = new UserController();

router.get('/current', authenticate, userController.getCurrentUser);
router.put('/current', authenticate, userController.updateCurrentUser);

router.get(
'/current/followed-stores',
authenticate,
userController.getFollowedStores
);
router.get(
'/current/managed-stores',
authenticate,
userController.getManagedStores
);
router.get('/current/orders', authenticate, userController.getOrders);
router.get('/current/carts', authenticate, userController.getCarts);
router.get('/current/cards', authenticate, userController.getCards);
Expand All @@ -22,20 +28,4 @@ router.get(
userController.getDeliveryAddresses
);

// PUT /users/current
router.put('/current', authenticate, async (req: Request, res: Response) => {
if (!req.auth) {
return res.status(401).json({ error: 'User not authenticated' });
}

const { name, email } = req.body;

const updatedUser = await prismaClient.user.update({
where: { id: req.auth!.id },
data: { name, email }
});

return res.json({ user: updatedUser });
});

export default router;
4 changes: 1 addition & 3 deletions api/src/utils/paystack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,7 @@ export const storeCard = async (data: StoreCardData) => {
signature: data.authorization.signature,
cardType: data.authorization.card_type,
countryCode: data.authorization.country_code,
user: {
connect: { email: data.customer.email }
}
user: { connect: { email: data.customer.email } }
}
});
};
Expand Down
23 changes: 13 additions & 10 deletions apps/app/App.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,31 @@
import 'core-js/full/symbol/async-iterator';
import { ThemeProvider } from '@habiti/components';
import * as Sentry from '@sentry/react-native';
import { QueryClientProvider, QueryClient } from '@tanstack/react-query';
import React from 'react';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { SafeAreaProvider } from 'react-native-safe-area-context';

import Routes from './src/navigation/Routes';
import useStore from './src/state';

Sentry.init({
dsn: process.env.EXPO_PUBLIC_SENTRY_DSN
});
Sentry.init({ dsn: process.env.EXPO_PUBLIC_SENTRY_DSN });

const queryClient = new QueryClient();

const App: React.FC = () => {
const theme = useStore(({ theme }) => theme);

return (
<SafeAreaProvider>
<ThemeProvider theme={theme}>
<GestureHandlerRootView style={{ flex: 1 }}>
<Routes />
</GestureHandlerRootView>
</ThemeProvider>
</SafeAreaProvider>
<QueryClientProvider client={queryClient}>
<SafeAreaProvider>
<ThemeProvider theme={theme}>
<GestureHandlerRootView style={{ flex: 1 }}>
<Routes />
</GestureHandlerRootView>
</ThemeProvider>
</SafeAreaProvider>
</QueryClientProvider>
);
};

Expand Down
9 changes: 8 additions & 1 deletion apps/app/src/components/product/AddToCart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,14 @@ const AddToCart: React.FC<AddToCartProps> = ({

return (
<View
style={[styles.container, { bottom, borderTopColor: theme.border.color }]}
style={[
styles.container,
{
backgroundColor: theme.screen.background,
bottom,
borderTopColor: theme.border.color
}
]}
>
<QuantityControl
inCart={inCart}
Expand Down
20 changes: 8 additions & 12 deletions apps/app/src/components/product/RelatedProducts.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { CustomImage, Typography } from '@habiti/components';
import { CustomImage, Spacer, Typography } from '@habiti/components';
import { NavigationProp, useNavigation } from '@react-navigation/native';
import React from 'react';
import { View, StyleSheet, ScrollView, TouchableOpacity } from 'react-native';
import { View, StyleSheet, ScrollView, Pressable } from 'react-native';

import { ProductQuery } from '../../types/api';
import { AppStackParamList } from '../../types/navigation';
Expand All @@ -14,20 +14,17 @@ const RelatedProducts: React.FC<RelatedProductsProps> = ({ products }) => {
const { navigate } = useNavigation<NavigationProp<AppStackParamList>>();
return (
<View style={styles.container}>
<Typography
variant='label'
weight='medium'
style={{ marginBottom: 4, marginLeft: 16 }}
>
Related products
<Typography variant='label' weight='medium' style={{ marginLeft: 16 }}>
Related
</Typography>
<Spacer y={4} />
<ScrollView
contentContainerStyle={styles.scroll}
horizontal
showsHorizontalScrollIndicator={false}
>
{products.map(product => (
<TouchableOpacity
<Pressable
key={product.id}
onPress={() => navigate('Product', { productId: product.id })}
>
Expand All @@ -38,7 +35,7 @@ const RelatedProducts: React.FC<RelatedProductsProps> = ({ products }) => {
width={120}
/>
</View>
</TouchableOpacity>
</Pressable>
))}
</ScrollView>
</View>
Expand All @@ -47,8 +44,7 @@ const RelatedProducts: React.FC<RelatedProductsProps> = ({ products }) => {

const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 16
flex: 1
},
scroll: {
paddingLeft: 8,
Expand Down
2 changes: 1 addition & 1 deletion apps/app/src/components/store/StoreListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { StyleSheet, Pressable } from 'react-native';
import { StoreProductsQuery } from '../../types/api';

interface StoreListItemProps {
item: StoreProductsQuery['store']['products'][number];
item: StoreProductsQuery['store']['products']['edges'][number]['node'];
onPress(): void;
side: 'left' | 'right';
}
Expand Down
2 changes: 1 addition & 1 deletion apps/app/src/components/store/StoreProducts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ const StoreProducts: React.FC<StoreProductsProps> = ({ store }) => {
estimatedItemSize={240}
renderItem={({ item, index }) => (
<StoreListItem
item={item}
item={item.node}
onPress={handleProductPress(item.node.id)}
side={index % 2 === 0 ? 'left' : 'right'}
/>
Expand Down
2 changes: 1 addition & 1 deletion apps/app/src/data/cards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ export class CardService {
constructor(private api: APIService) {}

public getCards() {
return this.api.get('/cards');
return this.api.get('/users/current/cards');
}
}
13 changes: 0 additions & 13 deletions apps/app/src/data/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,19 +80,6 @@ export const useStoreProductsQuery = (
});
};

export const useStoreOrdersQuery = (
storeId: string,
filter?: any,
orderBy?: any
) => {
return useQuery({
queryKey: ['stores', storeId, 'orders', filter, orderBy],
queryFn: () =>
dataService.stores.getStoreOrders(storeId, { filter, orderBy }),
enabled: !!storeId
});
};

// Card Queries
export const useCardsQuery = () => {
return useQuery({
Expand Down
19 changes: 14 additions & 5 deletions apps/app/src/data/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,26 @@ export default class UserService {
addressId: string,
body: UpdateDeliveryAddressBody
) {
return this.api.patch(
`/users/current/delivery-addresses/${addressId}`,
body
);
return this.api.patch(`/delivery-addresses/${addressId}`, body);
}

public deleteDeliveryAddress(addressId: string) {
return this.api.delete(`/users/current/delivery-addresses/${addressId}`);
return this.api.delete(`/delivery-addresses/${addressId}`);
}

public getFollowedStores() {
return this.api.get('/users/current/followed-stores');
}

public getManagedStores() {
return this.api.get('/users/current/managed-stores');
}

public getOrders() {
return this.api.get('/users/current/orders');
}

public getCarts() {
return this.api.get('/users/current/carts');
}
}
13 changes: 13 additions & 0 deletions apps/app/src/hooks/useRefreshing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react';

const useRefreshing = <T>(refetch: () => Promise<T>) => {
const [refreshing, setRefreshing] = React.useState(false);
const handleRefresh = React.useCallback(() => {
setRefreshing(true);
refetch().then(() => setRefreshing(false));
}, []);

return { refreshing, handleRefresh };
};

export default useRefreshing;
Loading

0 comments on commit cb68df8

Please sign in to comment.