Skip to content

Commit

Permalink
feat(strapi-admin-extensions): enable to import VAC as CSV
Browse files Browse the repository at this point in the history
  • Loading branch information
AliKdhim87 committed Jan 14, 2025
1 parent ae0360a commit 033875f
Show file tree
Hide file tree
Showing 28 changed files with 864 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .changeset/rare-cows-hug.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@frameless/strapi-admin-extensions": major
---

Maak het mogelijk om VAC als een CSV-bestand te importeren via de API.
2 changes: 2 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ module.exports = {
project: [
'./apps/overige-objecten-api/tsconfig.json',
'./apps/overige-objecten-api/tsconfig.test.json',
'./apps/strapi-admin-extensions/tsconfig.json',
'./apps/strapi-admin-extensions/tsconfig.test.json',
'./apps/kennisbank-dashboard/src/admin/tsconfig.json',
'./apps/kennisbank-dashboard/tsconfig.json',
'./apps/kennisbank-frontend/tsconfig.json',
Expand Down
6 changes: 4 additions & 2 deletions Dockerfile.dev
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ COPY ./apps/vth-dashboard/package.json apps/vth-dashboard/package.json
COPY ./apps/vth-frontend/package.json apps/vth-frontend/package.json
COPY ./apps/kennisbank-dashboard/package.json apps/kennisbank-dashboard/package.json
COPY ./apps/overige-objecten-api/package.json apps/overige-objecten-api/package.json
COPY ./apps/strapi-admin-extensions/package.json apps/strapi-admin-extensions/package.json
COPY ./apps/kennisbank-frontend/package.json apps/kennisbank-frontend/package.json
COPY ./packages/catalogi-data/package.json packages/catalogi-data/package.json
COPY ./packages/preview-button/package.json packages/preview-button/package.json
Expand All @@ -37,7 +38,7 @@ COPY ./packages/strapi-plugin-language/package.json packages/strapi-plugin-langu
FROM build AS dependencies
# Install prod dependencies
COPY ./patches /opt/app/patches
RUN yarn install
RUN yarn install --frozen-lockfile

# Build target builder #
########################
Expand All @@ -55,7 +56,8 @@ RUN npm run build --workspace @frameless/upl && \
npm run build --workspace @frameless/strapi-plugin-uuid-field && \
npm run build --workspace @frameless/strapi-plugin-env-label && \
npm run build --workspace @frameless/strapi-plugin-language && \
npm run build --workspace @frameless/overige-objecten-api
npm run build --workspace @frameless/overige-objecten-api && \
npm run build --workspace @frameless/strapi-admin-extensions

# Build target production #
###########################
Expand Down
53 changes: 53 additions & 0 deletions apps/strapi-admin-extensions/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Strapi Admin Extensions

This project contains custom extensions for the Strapi admin panel. It depends on another app called Strapi dashboard.

## Prerequisites

- Ensure `pdc-dashboard` is installed and set up properly before using `strapi-admin-extensions`.

## Installation

1. **Clone the repository:**

```bash
git clone [email protected]:frameless/strapi.git
```

2. **Install dependencies:**
Make sure you are in the project root:

```bash
yarn install
```

## Usage

1. Ensure the `pdc-dashboard` app is running:

```bash
yarn workspace @frameless/pdc-dashboard dev
```

2. Copy the environment configuration file to the `strapi-admin-extensions` folder:

```bash
cp .env.example .env
```

3. Run the development server for `strapi-admin-extensions`:

```bash
yarn workspace @frameless/strapi-admin-extensions dev
```

## Contributing

We welcome contributions! Feel free to:

- Open an issue to report bugs or suggest new features.
- Submit a pull request with improvements or fixes.

## License

This project is licensed under the EUPL-1.2 License.
21 changes: 21 additions & 0 deletions apps/strapi-admin-extensions/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { Config } from 'jest';

const config: Config = {
preset: 'ts-jest',
// to obtain access to the matchers.
moduleFileExtensions: ['ts', 'tsx', 'js', 'json', 'node'],
setupFilesAfterEnv: ['<rootDir>/src/tests/jest.setup.ts'],
modulePaths: ['<rootDir>'],
testEnvironment: 'node',
roots: ['<rootDir>/src'],
transform: {
'^.+\\.(ts)$': [
'ts-jest',
{
tsconfig: 'tsconfig.test.json',
},
],
},
};

export default config;
55 changes: 55 additions & 0 deletions apps/strapi-admin-extensions/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"name": "@frameless/strapi-admin-extensions",
"version": "0.0.0",
"private": true,
"author": "@frameless",
"description": "Strapi Admin Extensions",
"license": "EUPL-1.2",
"keywords": [],
"scripts": {
"prebuild": "yarn clean",
"build": "npm-run-all --parallel build:*",
"build:server": "tsc -p ./tsconfig.json",
"watch": "tsc -p ./tsconfig.json -w",
"start": "NODE_ENV=production node ./dist/src/server.js",
"dev": "NODE_ENV=development nodemon src/server.ts",
"clean": "rimraf dist src/types tmp",
"test": "OVERIGE_OBJECTEN_API_PORT=3000 jest --coverage --forceExit --verbose",
"test:watch": "OVERIGE_OBJECTEN_API_PORT=3000 jest --watch"
},
"dependencies": {
"cors": "2.8.5",
"csv-parser": "3.0.0",
"dompurify": "3.2.1",
"dotenv": "16.4.5",
"express": "4.21.0",
"lodash.memoize": "4.1.2",
"lodash.merge": "4.6.2",
"lodash.snakecase": "4.1.1",
"p-limit": "3.0.0",
"morgan": "1.10.0"
},
"devDependencies": {
"@types/cors": "2.8.17",
"@types/dompurify": "3.2.0",
"@types/jest": "29.5.12",
"@types/lodash.memoize": "4.1.9",
"@types/lodash.merge": "4.6.9",
"@types/lodash.snakecase": "4.1.9",
"@types/supertest": "6.0.2",
"jest": "29.7.0",
"jest-fetch-mock": "3.0.3",
"nodemon": "3.1.7",
"rimraf": "6.0.1",
"supertest": "7.0.0",
"ts-jest": "29.2.3",
"ts-node": "10.9.2",
"typescript": "5.0.4",
"@types/morgan": "1.9.9"
},
"repository": {
"type": "git+ssh",
"url": "[email protected]:frameless/strapi.git",
"directory": "apps/strapi-admin-extensio0s"
}
}
64 changes: 64 additions & 0 deletions apps/strapi-admin-extensions/src/controllers/import/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import type { NextFunction, Request, Response } from 'express';
import fs from 'node:fs';
import pLimit from 'p-limit';
import { CREATE_VAC } from '../../queries';
import { CreateVacResponse } from '../../strapi-product-types';
import { fetchData, processCsvFile } from '../../utils';

const limit = pLimit(5); // Limit the number of concurrent file uploads
export const importController = async (req: Request, res: Response, next: NextFunction) => {
const type = req.body.type;
if (req.file) {
const filePath = req.file.path;
const requiredColumns = ['vraag', 'antwoord'];

try {
// Process the CSV file and sanitize results
const authorizationHeader = req.headers?.authorization || '';
const [authType, authToken] = authorizationHeader.split(/\s+/);
const tokenAuth = authType === 'Token' ? authToken : authorizationHeader;
const graphqlURL = new URL('/graphql', process.env.STRAPI_PRIVATE_URL);
const sanitizedResults = await processCsvFile(filePath, requiredColumns);
const locale = req.query?.locale || 'nl';

if (type === 'vac') {
// Loop through the sanitized results and create entries one by one
const results = await Promise.all(
sanitizedResults.map((entry) =>
limit(async () => {
try {
const { data: responseData } = await fetchData<CreateVacResponse>({
url: graphqlURL.href,
query: CREATE_VAC,
variables: { locale, data: entry },
headers: {
Authorization: `Bearer ${tokenAuth}`,
},
});
return responseData;
} catch (error: any) {
next(error);
// eslint-disable-next-line no-console
console.error('Error processing entry:', error);
return { error: error.message, entry };
}
}),
),
);
res.json({ message: 'CSV converted to JSON', data: results });
// Delete temporary file after processing
await fs.promises.unlink(filePath);
} else {
res.status(400).send('Invalid import type.');
}
} catch (error) {
await fs.promises.unlink(filePath); // Delete the temporary file in case of error
// Forward any errors to the error handler middleware
next(error);
return null;
}
} else {
res.status(400).send('No file uploaded.');
}
return null;
};
1 change: 1 addition & 0 deletions apps/strapi-admin-extensions/src/controllers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { importController } from './import';
35 changes: 35 additions & 0 deletions apps/strapi-admin-extensions/src/queries/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const gql = (query: any) => query;

export const CREATE_VAC = gql(`
mutation createVac($data: VacInput!) {
createVac(data: $data){
data {
id
attributes {
createdAt
publishedAt
vac {
id
vraag
antwoord(pagination: { start: 0, limit: -1 }) {
content
kennisartikelCategorie
}
status
doelgroep
uuid
toelichting
afdelingen {
afdelingId
afdelingNaam
}
trefwoorden {
id
trefwoord
}
}
}
}
}
}
`);
8 changes: 8 additions & 0 deletions apps/strapi-admin-extensions/src/routers/import/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import express from 'express';
import multer from 'multer';
import { importController } from '../../controllers';
const upload = multer({ dest: 'tmp/uploads/' });
const router = express.Router({ mergeParams: true });

router.post('/import', upload.single('file'), importController);
export default router;
2 changes: 2 additions & 0 deletions apps/strapi-admin-extensions/src/routers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import importRoute from './import';
export { importRoute };
82 changes: 82 additions & 0 deletions apps/strapi-admin-extensions/src/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import type { CorsOptions } from 'cors';
import cors from 'cors';
import { config } from 'dotenv';
import express from 'express';
import { NextFunction, Request, Response } from 'express';
import morgan from 'morgan';
import { importRoute } from './routers';
import { envAvailability, ErrorHandler } from './utils';
config();

// Validate environment variables
envAvailability({
env: process.env,
keys: ['STRAPI_PRIVATE_URL', 'STRAPI_ADMIN_EXTENSIONS_PORT'],
});

const whitelist = process.env.STRAPI_ADMIN_EXTENSIONS_CORS?.split(', ') || [];
const corsOption: CorsOptions = {
origin: (origin, callback) => {
if (!origin || whitelist.indexOf(origin) !== -1) {
callback(null, true);
} else {
callback(
new ErrorHandler('Not allowed by CORS', {
statusCode: 403,
}),
);
}
},
optionsSuccessStatus: 200,
};
const app = express();
// Multer file upload middleware.
// The order is important, so this should be before the express.json() middleware to parse the file.
app.use('/api/v2', importRoute);
// parse application/json
app.use(express.json());
// parse application/x-www-form-urlencoded
app.use(express.urlencoded({ extended: true }));
// log HTTP requests
app.use(morgan('dev'));

const port = process.env.STRAPI_ADMIN_EXTENSIONS_PORT;
// Centralized error handler middleware
const globalErrorHandler = (err: ErrorHandler, _req: Request, res: Response, _next: NextFunction) => {
if (err instanceof ErrorHandler || (err as ErrorHandler)?.isOperational) {
// Send the proper error response with status code and message
return res.status(err?.options?.statusCode || 500).json({
message: err.message,
});
}

// If it's an unknown error (not an operational error), log it and send a generic response
// eslint-disable-next-line no-console
console.error('Unexpected error:', err);
return res.status(500).json({
message: 'An unexpected error occurred.',
});
};

/**
* CORS
* Enable CORS with a whitelist of allowed origins
*/
app.use(cors(corsOption));
// handle non existing routes
app.use((_req, res) => {
res.status(404).send('Route not found');
});
// Use global error handler middleware
app.use(globalErrorHandler);
/**
* Start the server
*/
if (process.env.NODE_ENV !== 'test') {
app.listen(port, () => {
// eslint-disable-next-line no-console
console.log(`Overige Objecten app listening on port ${port}!`);
});
}

export default app;
Loading

0 comments on commit 033875f

Please sign in to comment.