Upload First Image with Multer
This commit is contained in:
parent
6f7c22f618
commit
7a1027932d
BIN
backend/assets/upload/1635898929689.png
Normal file
BIN
backend/assets/upload/1635898929689.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 195 KiB |
1428
backend/package-lock.json
generated
1428
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -25,9 +25,12 @@
|
||||
"faker": "^5.4.0",
|
||||
"got": "^11.7.0",
|
||||
"helmet": "^4.4.1",
|
||||
"image-hash": "^4.0.1",
|
||||
"imghash": "^0.0.9",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"mongoose": "^5.10.7",
|
||||
"mongoose-unique-validator": "^2.0.3",
|
||||
"multer": "^1.4.3",
|
||||
"node-fetch": "^2.6.1",
|
||||
"nodemailer": "^6.5.0",
|
||||
"nodemailer-pug-engine": "^2.0.0",
|
||||
@ -59,6 +62,7 @@
|
||||
"@types/jsonwebtoken": "^8.5.0",
|
||||
"@types/mocha": "^8.2.3",
|
||||
"@types/mongoose": "^5.7.36",
|
||||
"@types/multer": "^1.4.7",
|
||||
"@types/node": "^14.11.2",
|
||||
"@types/node-fetch": "^2.5.7",
|
||||
"@types/nodemailer": "^6.4.1",
|
||||
|
@ -2,9 +2,6 @@ import { Document, model, Schema } from 'mongoose';
|
||||
import patterns from '../../types/patterns';
|
||||
import { createdAtToDate, useId } from '../../utils/utils';
|
||||
import { generateSnowflake } from '../snowflake-entity';
|
||||
import AES from 'crypto-js/aes';
|
||||
import { readFileSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
|
||||
export interface MessageDocument extends Document, Entity.Message {
|
||||
_id: string | never;
|
||||
@ -17,6 +14,9 @@ export const Message = model<MessageDocument>('message', new Schema({
|
||||
type: String,
|
||||
default: generateSnowflake,
|
||||
},
|
||||
attachments: {
|
||||
type: [Object],
|
||||
},
|
||||
authorId: {
|
||||
type: String,
|
||||
required: [true, 'Author ID is required'],
|
||||
|
@ -5,17 +5,45 @@ import passport from 'passport';
|
||||
import cors from 'cors';
|
||||
import { User } from '../../data/models/user';
|
||||
import rateLimiter from '../modules/rate-limiter';
|
||||
import multer from 'multer';
|
||||
import { generateSnowflake } from '../../data/snowflake-entity';
|
||||
import { imageHash } from 'image-hash';
|
||||
import path, { extname, resolve } from 'path';
|
||||
|
||||
export default (app: Application) => {
|
||||
function setupMulter(app: Application) {
|
||||
const storage = multer.diskStorage({
|
||||
destination: (req, file, cb) => cb(null, resolve('./assets/upload')),
|
||||
filename: (req, file, cb) => {
|
||||
// imageHash({ ext: file.mimetype, data: file.buffer }, 8, true, (error, data) => {
|
||||
// if (error) return log.error(error);
|
||||
// log.debug(data);
|
||||
// });
|
||||
console.log(file);
|
||||
cb(null, Date.now() + extname(file.originalname));
|
||||
},
|
||||
});
|
||||
const upload = multer({ storage });
|
||||
|
||||
// TODO: validate is logged in, etc.
|
||||
app.post('/v2/upload', upload.single('file'), (req, res) => {
|
||||
res.status(201).json({ message: 'Files uploaded' });
|
||||
});
|
||||
}
|
||||
function setupPassport(app: Application) {
|
||||
passport.use(new LocalStrategy(
|
||||
{ usernameField: 'email' },
|
||||
(User as any).authenticate(),
|
||||
));
|
||||
passport.serializeUser((User as any).serializeUser());
|
||||
passport.deserializeUser((User as any).deserializeUser());
|
||||
}
|
||||
|
||||
export default (app: Application) => {
|
||||
app.use(cors());
|
||||
app.use(bodyParser.json());
|
||||
app.use(passport.initialize());
|
||||
app.use(rateLimiter);
|
||||
|
||||
setupPassport(app);
|
||||
setupMulter(app);
|
||||
}
|
@ -1,10 +1,12 @@
|
||||
import { faUpload } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import classNames from 'classnames';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useDispatch, useSelector, useStore } from 'react-redux';
|
||||
import { Link } from 'react-router-dom';
|
||||
import TextareaAutosize from 'react-textarea-autosize';
|
||||
import usePerms from '../../hooks/use-perms';
|
||||
import { createMessage, updateMessage } from '../../store/messages';
|
||||
import { createMessage, updateMessage, uploadFileAsMessage } from '../../store/messages';
|
||||
import { getTypersInChannel, startTyping } from '../../store/typing';
|
||||
import { actions as ui } from '../../store/ui';
|
||||
import { getUser } from '../../store/users';
|
||||
@ -64,8 +66,7 @@ const MessageBox: React.FunctionComponent<MessageBoxProps> = (props) => {
|
||||
|
||||
const handleEscape = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (event.key !== 'Escape') return;
|
||||
if (props.editingMessageId)
|
||||
esc();
|
||||
if (props.editingMessageId) esc();
|
||||
}
|
||||
|
||||
const user = (userId: string) => getUser(userId)(store.getState());
|
||||
@ -85,23 +86,49 @@ const MessageBox: React.FunctionComponent<MessageBoxProps> = (props) => {
|
||||
if (!canSend) return `Insufficient perms.`;
|
||||
if (!props.editingMessageId) return `Message #${channel.name}`;
|
||||
}
|
||||
|
||||
const MessageBoxLeftSide = () => {
|
||||
const onChange: any = (e: Event) => {
|
||||
const input = e.target as HTMLInputElement;
|
||||
console.log(input.files);
|
||||
dispatch(uploadFileAsMessage(input.files![0]));
|
||||
}
|
||||
|
||||
return (!props.editingMessageId) ? (
|
||||
<div className="px-4">
|
||||
<div className="relative">
|
||||
{/* TODO: add multiple file support */}
|
||||
<input
|
||||
className="absolute opacity-0 w-full"
|
||||
type="file"
|
||||
name="file"
|
||||
accept="image/*"
|
||||
onChange={onChange} />
|
||||
<FontAwesomeIcon icon={faUpload} />
|
||||
</div>
|
||||
</div>
|
||||
) : null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`${props.editingMessageId ? 'mt-2' : 'px-4'}`}>
|
||||
<TextareaAutosize
|
||||
id="messageBox"
|
||||
onChange={e => setContent(e.target.value)}
|
||||
onKeyDown={onKeyDown}
|
||||
value={content}
|
||||
rows={1}
|
||||
placeholder={getPlaceholder()}
|
||||
className={classNames(
|
||||
'resize-none normal appearance-none rounded-lg leading-tight',
|
||||
'focus:outline-none w-full right-5 left-5 max-h-96 py-3 px-4',
|
||||
{ 'cursor-not-allowed': !canSend },
|
||||
)}
|
||||
disabled={!canSend}
|
||||
autoFocus />
|
||||
<div className="rounded-lg bg-bg-secondary flex items-center">
|
||||
<MessageBoxLeftSide />
|
||||
<TextareaAutosize
|
||||
id="messageBox"
|
||||
onChange={e => setContent(e.target.value)}
|
||||
onKeyDown={onKeyDown}
|
||||
value={content}
|
||||
rows={1}
|
||||
className={classNames(
|
||||
'resize-none normal rounded-lg appearance-none leading-tight',
|
||||
'focus:outline-none w-full right-5 left-5 max-h-96 py-3 px-4',
|
||||
{ 'cursor-not-allowed': !canSend },
|
||||
)}
|
||||
placeholder={getPlaceholder()}
|
||||
disabled={!canSend}
|
||||
autoFocus />
|
||||
</div>
|
||||
{(props.editingMessageId)
|
||||
? <span className="text-xs py-2">
|
||||
escape to <Link to="#" onClick={esc}>cancel</Link> •
|
||||
|
@ -55,6 +55,19 @@ export const createMessage = (channelId: string, payload: Partial<Entity.Message
|
||||
}));
|
||||
}
|
||||
|
||||
// each file is uploaded individually as a separate API call
|
||||
export const uploadFileAsMessage = (file: File) => (dispatch) => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file, file.name);
|
||||
|
||||
dispatch(api.restCallBegan({
|
||||
method: 'post',
|
||||
url: '/upload',
|
||||
data: formData,
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
}));
|
||||
}
|
||||
|
||||
export const updateMessage = (id: string, payload: Partial<Entity.Message>) => (dispatch) => {
|
||||
dispatch(api.wsCallBegan({
|
||||
event: 'MESSAGE_UPDATE',
|
||||
|
7
types/entity.d.ts
vendored
7
types/entity.d.ts
vendored
@ -43,6 +43,7 @@ declare namespace Entity {
|
||||
}
|
||||
export interface Message {
|
||||
id: string;
|
||||
attachments: MessageTypes.Attachment[];
|
||||
authorId: string;
|
||||
channelId: string;
|
||||
content: string;
|
||||
@ -110,6 +111,12 @@ declare namespace InviteTypes {
|
||||
}
|
||||
|
||||
declare namespace MessageTypes {
|
||||
export interface Attachment {
|
||||
id: string;
|
||||
name: string;
|
||||
size: number;
|
||||
url: string;
|
||||
}
|
||||
export interface Embed {
|
||||
description: string;
|
||||
imageURL: string;
|
||||
|
Loading…
x
Reference in New Issue
Block a user