Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pantry info endpoint (backend & frontend) #3

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
2 changes: 2 additions & 0 deletions apps/backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UsersModule } from './users/users.module';
import { PantriesModule } from './pantries/pantries.module';
import { AuthModule } from './auth/auth.module';
import { ConfigModule, ConfigService } from '@nestjs/config';
import typeorm from './config/typeorm';
Expand All @@ -22,6 +23,7 @@ import typeorm from './config/typeorm';
}),
UsersModule,
AuthModule,
PantriesModule,
],
controllers: [AppController],
providers: [AppService],
Expand Down
16 changes: 16 additions & 0 deletions apps/backend/src/pantries/pantries.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common';
import { Pantry } from './pantry.entity';
import { PantriesService } from './pantries.service';
import { or404 } from '../utils';

@Controller('pantries')
export class PantriesController {
constructor(private pantriesService: PantriesService) {}

@Get('/:pantryId')
async getPantry(
@Param('pantryId', ParseIntPipe) pantryId: number,
): Promise<Pantry> {
return await or404(() => this.pantriesService.findOne(pantryId));
}
}
12 changes: 12 additions & 0 deletions apps/backend/src/pantries/pantries.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { PantriesController } from './pantries.controller';
import { PantriesService } from './pantries.service';
import { Pantry } from './pantry.entity';

@Module({
imports: [TypeOrmModule.forFeature([Pantry])],
controllers: [PantriesController],
providers: [PantriesService],
})
export class PantriesModule {}
18 changes: 18 additions & 0 deletions apps/backend/src/pantries/pantries.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';

import { Pantry } from './pantry.entity';

@Injectable()
export class PantriesService {
constructor(@InjectRepository(Pantry) private repo: Repository<Pantry>) {}

async findOne(id: number) {
if (!id) {
return null;
}

return this.repo.findOneBy({ id });
}
}
43 changes: 43 additions & 0 deletions apps/backend/src/pantries/pantry.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {
Entity,
Column,
PrimaryGeneratedColumn,
ManyToOne,
JoinColumn,
} from 'typeorm';
import { User } from '../users/user.entity';

@Entity('pantries')
export class Pantry {
@PrimaryGeneratedColumn()
id: number;

@Column({
type: 'varchar',
length: 255,
})
name: string;

@Column({
type: 'varchar',
length: 255,
})
address: string;

@Column()
approved: boolean;

@ManyToOne(() => User)
@JoinColumn({ name: 'ssf_representative_id' })
ssf_representative_id: number;

@ManyToOne(() => User)
@JoinColumn({ name: 'pantry_representative_id' })
pantry_representative_id: number;

@Column({
type: 'text',
array: true,
})
restrictions: string[];
}
21 changes: 21 additions & 0 deletions apps/backend/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { NotFoundException } from '@nestjs/common';

/**
* Resolve to the result of bodyFn as long as it is not null or undefined. If
* it is null or undefined, execute exceptFn to throw an exception.
*/
export const orException = async <BodyType>(
bodyFn: () => Promise<BodyType>,
exceptFn: () => never,
): Promise<NonNullable<BodyType>> => (await bodyFn()) ?? exceptFn();

/**
* Resolve to the result of bodyFn as long as it is not null or undefined. If
* it is null or undefined, throw a NotFoundException (status code 404).
*/
export const or404 = async <BodyType>(
bodyFn: () => Promise<BodyType>,
): Promise<NonNullable<BodyType>> =>
orException(bodyFn, () => {
throw new NotFoundException();
});
5 changes: 5 additions & 0 deletions apps/frontend/src/api/apiClient.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import axios, { type AxiosInstance } from 'axios';
import { Pantry } from '@api/models';

const defaultBaseUrl =
import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:3000';
Expand All @@ -14,6 +15,10 @@ export class ApiClient {
return this.get('/api') as Promise<string>;
}

public async getPantryInfo(id: string): Promise<Pantry> {
return (await this.get(`/api/pantries/${id}`)) as Promise<Pantry>;
}

private async get(path: string): Promise<unknown> {
return this.axiosInstance.get(path).then((response) => response.data);
}
Expand Down
7 changes: 7 additions & 0 deletions apps/frontend/src/api/models.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface Pantry {
id: number;
name: string;
address: string;
approved: boolean;
restrictions: string[];
}
5 changes: 4 additions & 1 deletion apps/frontend/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ const router = createBrowserRouter([
element: <LandingPage />,
},
{
path: '/pantry-overview',
path: '/pantry-overview/:id',
loader: async ({ params }) => {
return await apiClient.getPantryInfo(params.id!);
},
element: <PantryOverview />,
},
{
Expand Down
15 changes: 14 additions & 1 deletion apps/frontend/src/containers/pantryOverview.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
import { useLoaderData } from 'react-router-dom';
import { Pantry } from '@api/models';
import React from 'react';

const PantryOverview: React.FC = () => {
return <>Pantry overview</>;
const pantryInfo = useLoaderData() as Pantry;

return (
<div>
<p>Pantry overview</p>
<p>Pantry name: {pantryInfo.name}</p>
<p>Pantry address: {pantryInfo.address}</p>
<p>Pantry approval status: {String(pantryInfo.approved)}</p>
</div>
);
};

export default PantryOverview;