반응형
admin-bro 대시보드에서 이미지를 업로드하고 싶다
세팅해 보자
① [프로젝트명]/package.json
일단 내가 설치해서 사용하는 패키지들은 이렇다
...
"dependencies": {
"admin-bro": "^2.2.4",
"admin-bro-expressjs": "^2.0.2",
"admin-bro-mongoose": "^0.5.0",
"argon2": "^0.27.2",
"bcrypt": "^5.0.1",
"body-parser": "^1.19.0",
"cookie-parser": "^1.4.5",
"cors": "^2.8.5",
"express": "^4.17.1",
"express-formidable": "^1.2.0",
"express-session": "^1.17.1",
"jsonwebtoken": "^8.5.1",
"moment": "^2.29.1",
"mongoose": "^5.9.7",
"save": "^2.4.0"
},
...
pacakage.json 파일이 있는 경로에서 명렁어를 입력해서 패키지들을 설치하자
npm install
② [프로젝트명]/index.js
const express = require("express");
const bodyParser = require("body-parser");
const cors = require("cors");
const mongoose = require("mongoose");
const config = require("./config/key");
const cookieParser = require("cookie-parser");
const AdminBro = require('admin-bro');
const options = require('./admin.options');
const buildAdminRouter = require('./admin.router');
const app = express();
mongoose
.connect('[mongoDB URI 입력]', { useNewUrlParser: true, useUnifiedTopology: true })
.then(() => console.log("DB connected"))
.catch(err => console.error(err));
const admin = new AdminBro(options);
const router = buildAdminRouter(admin);
app.use(admin.options.rootPath, router);
app.use('/uploads', express.static('uploads'));
// parse requests of content-type: application/json
app.use(bodyParser.json());
app.use(cors());
// parse requests of content-type: application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieParser());
// set port, listen for requests
app.listen(5000, () => {
console.log("Server is running on port 5000.");
});
③ [프로젝트명]/admin.options.js
const { default: AdminBro } = require('admin-bro');
const AdminBroMongoose = require('admin-bro-mongoose');
AdminBro.registerAdapter(AdminBroMongoose);
const AdminCompany = require('./models/company.admin');
/** @type {import('admin-bro').AdminBroOptions} */
const options = {
resources: [AdminCompany],
};
module.exports = options;
④ [프로젝트명]/admin.router.js
const AdminBro = require('admin-bro');
const { buildAuthenticatedRouter } = require('admin-bro-expressjs');
const express = require('express');
const mongoose = require('mongoose');
const session = require('express-session');
const config = require("./config/key");
const { Company } = require('./models/company.entity');
/**
* @param {AdminBro} admin
* @return {express.Router} router
*/
const ADMIN = {
email: 'admin@a.aa', // 관리자 계정
password: '12341234', // 관리자 비밀번호
saveUninitialized: true
};
const buildAdminRouter = (admin) => {
const router = buildAuthenticatedRouter(admin, {
cookieName: 'admin-bro',
cookiePassword: 'testtest',
authenticate: async (email, password) => {
if (ADMIN.password === password && ADMIN.email === email) {
return ADMIN
}
return null
},
}, null, {
resave: false,
saveUninitialized: true,
});
return router;
};
module.exports = buildAdminRouter;
⑤ [프로젝트명]/models/company.entity.js
const mongoose = require('mongoose');
const CompanySchema = new mongoose.Schema({
companyName: {
type: String,
required: true,
},
email: {
type: String,
required: true,
},
address: {
type: String,
},
profilePhotoLocation: {
type: String,
},
encryptedPassword: {
type: String,
required: true,
},
});
const Company = mongoose.model('Company', CompanySchema);
module.exports = { CompanySchema, Company };
⑥ [프로젝트명]/models/company.admin.js
const AdminBro = require('admin-bro');
const { Company } = require('./company.entity');
const {
after: passwordAfterHook,
before: passwordBeforeHook,
} = require('../actions/password.hook');
const {
after: uploadAfterHook,
before: uploadBeforeHook,
} = require('../actions/upload-image.hook');
/** @type {AdminBro.ResourceOptions} */
const options = {
properties: {
encryptedPassword: {
isVisible: false,
},
profilePhotoLocation: {
isVisible: false,
},
password: {
type: 'password',
},
uploadImage: {
components: {
edit: AdminBro.bundle('../components/upload-image.edit.tsx'),
list: AdminBro.bundle('../components/upload-image.list.tsx'),
},
},
},
actions: {
new: {
after: async (response, request, context) => {
const modifiedResponse = await passwordAfterHook(response, request, context);
return uploadAfterHook(modifiedResponse, request, context);
},
before: async (request, context) => {
const modifiedRequest = await passwordBeforeHook(request, context);
return uploadBeforeHook(modifiedRequest, context);
},
},
edit: {
after: async (response, request, context) => {
const modifiedResponse = await passwordAfterHook(response, request, context);
return uploadAfterHook(modifiedResponse, request, context);
},
before: async (request, context) => {
const modifiedRequest = await passwordBeforeHook(request, context);
return uploadBeforeHook(modifiedRequest, context);
},
},
show: {
isVisible: false,
},
},
};
module.exports = {
options,
resource: Company,
};
⑦ [프로젝트명]/actions/password.hook.js
const argon2 = require('argon2');
const AdminBro = require('admin-bro');
/** @type {AdminBro.After<AdminBro.ActionResponse>} */
const after = async (response) => {
if (response.record && response.record.errors && response.record.errors.encryptedPassword) {
response.record.errors.password = response.record.errors.encryptedPassword;
}
return response;
};
/** @type {AdminBro.Before} */
const before = async (request) => {
if (request.method === 'post') {
const { password, ...otherParams } = request.payload;
if (password) {
const encryptedPassword = await argon2.hash(password);
return {
...request,
payload: {
...otherParams,
encryptedPassword,
},
};
}
}
return request;
};
module.exports = { after, before };
⑧ [프로젝트명]/actions/upload-image.hook.js
const path = require('path');
const fs = require('fs');
const AdminBro = require('admin-bro');
/** @type {AdminBro.After<AdminBro.ActionResponse>} */
const after = async (response, request, context) => {
const { record, uploadImage } = context;
if (record.isValid() && uploadImage) {
const filePath = path.join('uploads', record.id().toString(), uploadImage.name);
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
await fs.promises.rename(uploadImage.path, filePath);
await record.update({ profilePhotoLocation: `/${filePath}` });
}
return response;
};
/** @type {AdminBro.Before} */
const before = async (request, context) => {
if (request.method === 'post') {
const { uploadImage, ...otherParams } = request.payload;
// eslint-disable-next-line no-param-reassign
context.uploadImage = uploadImage;
return {
...request,
payload: otherParams,
};
}
return request;
};
module.exports = { after, before };
⑨ [프로젝트명]/components/upload-image.edit.tsx
import React from 'react'
import { Label, Box, DropZone, BasePropertyProps, DropZoneProps, DropZoneItem } from 'admin-bro'
const Edit: React.FC<BasePropertyProps> = (props) => {
const { property, onChange, record } = props
const handleDropZoneChange: DropZoneProps['onChange'] = (files) => {
onChange(property.name, files[0])
}
const uploadedPhoto = record.params.profilePhotoLocation
const photoToUpload = record.params[property.name]
return (
<Box marginBottom="xxl">
<Label>{property.label}</Label>
<DropZone onChange={handleDropZoneChange}/>
{uploadedPhoto && !photoToUpload && (
<DropZoneItem src={uploadedPhoto} />
)}
</Box>
)
}
export default Edit
⑩ [프로젝트명]/components/upload-image.list.tsx
import React from 'react'
import { Box, BasePropertyProps } from 'admin-bro'
const Edit: React.FC<BasePropertyProps> = (props) => {
const { record } = props
const srcImg = record.params['profilePhotoLocation']
return (
<Box>
{srcImg ? (
<img src={srcImg} width="100px"/>
) : 'no image'}
</Box>
)
}
export default Edit
node index.js
서버를 실행하고 localhost:5000/admin에 접속하면 대시보드가 잘 뜨는 것을 확인할 수 있다
이미지 drag and drop 영역도 잘 뜬다
아래 유튜브 영상을 참고했다
www.youtube.com/watch?v=7WtKcFqJHho
반응형