Skip to content

Commit

Permalink
Merge pull request #8
Browse files Browse the repository at this point in the history
Tandoor import
  • Loading branch information
georgegebbett authored May 12, 2022
2 parents 00ae46e + 64213a6 commit 8107dd9
Show file tree
Hide file tree
Showing 11 changed files with 161 additions and 11 deletions.
3 changes: 2 additions & 1 deletion api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@
"ts-loader": "^9.2.3",
"ts-node": "^10.0.0",
"tsconfig-paths": "^3.10.1",
"typescript": "^4.3.5"
"typescript": "^4.3.5",
"@types/multer": "^1.4.7"
},
"jest": {
"moduleFileExtensions": [
Expand Down
2 changes: 2 additions & 0 deletions api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { JwtAuthGuard } from './auth/jwt-auth.guard';
import { RolesGuard } from './auth/roles.guard';
import { RecipesModule } from './recipes/recipes.module';
import { GrocyModule } from './grocy/grocy.module';
import { ImportModule } from './import/import.module';

@Module({
imports: [
Expand All @@ -17,6 +18,7 @@ import { GrocyModule } from './grocy/grocy.module';
UsersModule,
RecipesModule,
GrocyModule,
ImportModule,
],
providers: [
AppService,
Expand Down
19 changes: 19 additions & 0 deletions api/src/import/import.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {
Controller,
Post,
UploadedFile,
UseInterceptors,
} from '@nestjs/common';
import { ImportService } from './import.service';
import { FileInterceptor } from '@nestjs/platform-express';

@Controller('import')
export class ImportController {
constructor(private readonly importService: ImportService) {}

@Post('upload')
@UseInterceptors(FileInterceptor('file'))
uploadFile(@UploadedFile() file: Express.Multer.File) {
return this.importService.addRecipesToDatabaseFromFile(file);
}
}
12 changes: 12 additions & 0 deletions api/src/import/import.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { ImportService } from './import.service';
import { ImportController } from './import.controller';
import { RecipesService } from '../recipes/recipes.service';
import { RecipesModule } from '../recipes/recipes.module';

@Module({
imports: [RecipesModule],
controllers: [ImportController],
providers: [ImportService, RecipesService],
})
export class ImportModule {}
40 changes: 40 additions & 0 deletions api/src/import/import.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Injectable } from '@nestjs/common';
import { RecipesService } from '../recipes/recipes.service';
import { Recipe } from 'schema-dts';

@Injectable()
export class ImportService {
constructor(private recipesService: RecipesService) {}

addRecipesToDatabaseFromFile(importFile: Express.Multer.File) {
const parseSteps: (stepsArr: object[]) => string[] = (
stepsArr: object[],
) => {
// @ts-ignore
return stepsArr.map((step) => step.text);
};

try {
const textContent = importFile.buffer.toString();
const jsonObject: Recipe[] = JSON.parse(textContent);

for (const recipe of jsonObject) {
this.recipesService.createHydratedRecipe({
url: 'no url for imported recipe',
name: recipe.name.toString(),
ingredients: recipe.recipeIngredient.valueOf() as string[],
steps: parseSteps(recipe.recipeInstructions.valueOf() as object[]),
});
}
return Promise.resolve({
statusCode: 201,
message: 'Recipes imported successfully',
});
} catch (e) {
return Promise.reject({
statusCode: 500,
message: e.message,
});
}
}
}
4 changes: 4 additions & 0 deletions api/src/recipes/recipes.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export class RecipesService {
return new this.recipeModel(recipe).save();
}

createHydratedRecipe(createRecipeDto: CreateRecipeDto): Promise<Recipe> {
return new this.recipeModel(createRecipeDto).save();
}

findAll() {
return this.recipeModel.find().exec();
}
Expand Down
7 changes: 7 additions & 0 deletions api/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -912,6 +912,13 @@
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a"
integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==

"@types/multer@^1.4.7":
version "1.4.7"
resolved "https://registry.yarnpkg.com/@types/multer/-/multer-1.4.7.tgz#89cf03547c28c7bbcc726f029e2a76a7232cc79e"
integrity sha512-/SNsDidUFCvqqcWDwxv2feww/yqhNeTRL5CVoL3jU4Goc4kKEL10T7Eye65ZqPNi4HRx8sAEX59pV1aEH7drNA==
dependencies:
"@types/express" "*"

"@types/node@*":
version "17.0.29"
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.29.tgz#7f2e1159231d4a077bb660edab0fde373e375a3d"
Expand Down
4 changes: 3 additions & 1 deletion app/src/components/RecipeCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ export function RecipeCard({ recipe, getRecipes }: RecipeProps) {
return (
<Card key={recipe._id} sx={{ maxWidth: 345, minHeight: 350 }}>
<CardHeader title={recipe.name} />
<CardMedia height="190" component="img" image={recipe.imageUrl} />
{recipe.imageUrl ? (
<CardMedia height="190" component="img" image={recipe.imageUrl} />
) : null}
<CardContent>
<Collapse in={expanded}>
<RecipeIngredients ingredients={recipe.ingredients} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ interface PropTypes {
open: boolean;
error: boolean;
errorMessage: string;
successMessage: string;
handleClose: () => void;
}

export function RecipeResultSnackBar(props: PropTypes) {
const { open, error, errorMessage, handleClose } = props;
export function ResultSnackBar(props: PropTypes) {
const { open, error, errorMessage, successMessage, handleClose } = props;

return (
<Snackbar
Expand All @@ -18,8 +19,8 @@ export function RecipeResultSnackBar(props: PropTypes) {
anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
>
<Alert severity={error ? "error" : "success"} onClose={handleClose}>
<AlertTitle>{error ? "Error adding recipe" : "Success"}</AlertTitle>
{error ? errorMessage : "Recipe added successfully"}
<AlertTitle>{error ? "Error" : "Success"}</AlertTitle>
{error ? errorMessage : successMessage}
</Alert>
</Snackbar>
);
Expand Down
5 changes: 3 additions & 2 deletions app/src/pages/RecipeDisplayPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { rbTheme } from "../styles/styles";
import MenuAppBar from "../components/MenuAppBar";
import { tokenAtom } from "../App";
import { post } from "../helpers/backendRequests";
import { RecipeResultSnackBar } from "../components/RecipeResultSnackBar";
import { ResultSnackBar } from "../components/ResultSnackBar";

export function RecipeDisplayPage() {
const [token] = useAtom(tokenAtom);
Expand Down Expand Up @@ -82,10 +82,11 @@ export function RecipeDisplayPage() {
setModalOpen={setModalOpen}
addRecipe={addRecipe}
/>
<RecipeResultSnackBar
<ResultSnackBar
open={snackbarOpen}
error={snackbarIsError}
errorMessage={snackbarErrorMessage}
successMessage="Recipe added successfully"
handleClose={() => setSnackbarOpen(false)}
/>
<Container maxWidth="xl" sx={{ mt: 4, mb: 4 }}>
Expand Down
67 changes: 64 additions & 3 deletions app/src/pages/SettingsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useEffect, useState } from "react";
import { ChangeEvent, useEffect, useState } from "react";
import {
Box,
Button,
Container,
Input,
Paper,
Stack,
TextField,
Expand All @@ -16,6 +17,7 @@ import { rbTheme } from "../styles/styles";
import MenuAppBar from "../components/MenuAppBar";
import { useNavigate } from "react-router-dom";
import { tokenAtom } from "../App";
import { ResultSnackBar } from "../components/ResultSnackBar";

export function SettingsPage() {
const [token, setToken] = useAtom(tokenAtom);
Expand All @@ -25,6 +27,16 @@ export function SettingsPage() {
const [grocySettingsCorrect, setGrocySettingsCorrect] =
useState<boolean>(false);

const [uploadFile, setUploadFile] = useState<File>();

const [snackBarOpen, setSnackBarOpen] = useState<boolean>(false);
const [snackBarError, setSnackBarError] = useState<boolean>(false);
const [snackbarErrorMessage, setSnackbarErrorMessage] = useState<string>("");

const handleSnackBarClose = () => {
setSnackBarOpen(false);
};

const navigate = useNavigate();

async function updateSettings() {
Expand Down Expand Up @@ -85,6 +97,36 @@ export function SettingsPage() {
getCurrentSettings();
}, []);

const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
if (!e.target.files) return;
setUploadFile(e.target.files[0]);
};

const onFileUpload = async () => {
if (!uploadFile) {
return;
}

const formData = new FormData();

formData.append("file", uploadFile, uploadFile.name);

try {
await axios.post("/api/import/upload", formData, {
headers: {
Authorization: `Bearer ${token.access_token}`,
},
});
setUploadFile(undefined);
setSnackBarError(false);
setSnackBarOpen(true);
} catch (e: any) {
setSnackBarOpen(true);
setSnackBarError(true);
setSnackbarErrorMessage(e.response.data.message);
}
};

return (
<ThemeProvider theme={rbTheme}>
<Box sx={{ display: "flex" }}>
Expand Down Expand Up @@ -132,10 +174,29 @@ export function SettingsPage() {
</Paper>
<Paper>
<Box sx={{ p: 2 }}>
<Typography variant="h6">System info</Typography>
<Typography variant="h6">
Import recipes from Tandoor
</Typography>
<Typography variant="body1">
Backend available at {import.meta.env.VITE_BACKEND_BASE_URL}
To import recipes from Tandoor, export your chosen recipes
using the 'Saffron' export type, and upload the resulting
JSON file here.
</Typography>
<Input
type="file"
onChange={handleFileChange}
inputProps={{ accept: "application/json" }}
/>
<Button onClick={onFileUpload} disabled={!uploadFile}>
Upload
</Button>
<ResultSnackBar
open={snackBarOpen}
error={snackBarError}
errorMessage={snackbarErrorMessage}
successMessage="Recipes imported successfully"
handleClose={handleSnackBarClose}
/>
</Box>
</Paper>
</Stack>
Expand Down

0 comments on commit 8107dd9

Please sign in to comment.