Skip to content

Commit 763d669

Browse files
committed
completed thumbnail upload and apply
1 parent 8a9f698 commit 763d669

File tree

13 files changed

+186
-30
lines changed

13 files changed

+186
-30
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// @flow
2+
import Sequelize from 'sequelize';
3+
import db from 'database/db';
4+
import { primaryUUID } from 'lib/common';
5+
import { User } from 'database/models';
6+
7+
const UserThumbnail = db.define('user_thumbnail', {
8+
id: primaryUUID,
9+
fk_user_id: Sequelize.UUID,
10+
path: Sequelize.STRING,
11+
filesize: Sequelize.INTEGER,
12+
});
13+
14+
UserThumbnail.associate = () => {
15+
UserThumbnail.belongsTo(User, {
16+
foreignKey: 'fk_user_id',
17+
onDelete: 'CASCADE',
18+
onUpdate: 'restrict',
19+
});
20+
};
21+
22+
export default UserThumbnail;

velog-backend/src/database/models/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ export { default as PostImage } from './PostImage';
1717
export { default as Feed } from './Feed';
1818
export { default as PostScore } from './PostScore';
1919
export { default as PostRead } from './PostRead';
20+
export { default as UserThumbnail } from './UserThumbnail';

velog-backend/src/database/sync.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
Feed,
1919
PostScore,
2020
PostRead,
21+
UserThumbnail,
2122
} from './models';
2223
import * as views from './views';
2324

@@ -39,6 +40,7 @@ export function associate() {
3940
Feed.associate();
4041
PostScore.associate();
4142
PostRead.associate();
43+
UserThumbnail.associate();
4244
}
4345
export default function sync() {
4446
associate();

velog-backend/src/router/auth/auth.ctrl.js

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -270,30 +270,45 @@ export const createLocalAccount = async (ctx: Context): Promise<*> => {
270270
}
271271
};
272272

273+
// TODO: query optimization needed
273274
export const check = async (ctx: Context): Promise<*> => {
274275
if (!ctx.user) {
275276
ctx.status = 401;
276277
return;
277278
}
278279

279-
const now = new Date();
280-
if (ctx.tokenExpire - now < 1000 * 60 * 60 * 24 * 4) {
281-
try {
282-
const user = await User.findById(ctx.user.id);
283-
const token = await user.generateToken();
284-
// $FlowFixMe: intersection bug
285-
ctx.cookies.set('access_token', token, {
286-
httpOnly: true,
287-
maxAge: 1000 * 60 * 60 * 24 * 7,
288-
});
289-
} catch (e) {
290-
ctx.throw(500, e);
280+
try {
281+
const now = new Date();
282+
const user = await User.findById(ctx.user.id);
283+
if (!user) {
284+
ctx.status = 401;
285+
return;
291286
}
287+
const profile = await user.getProfile();
288+
const data = {
289+
id: user.id,
290+
displayName: profile.display_name,
291+
thumbnail: profile.thumbnail,
292+
username: user.username,
293+
};
294+
if (ctx.tokenExpire - now < 1000 * 60 * 60 * 24 * 4) {
295+
try {
296+
const token = await user.generateToken();
297+
// $FlowFixMe: intersection bug
298+
ctx.cookies.set('access_token', token, {
299+
httpOnly: true,
300+
maxAge: 1000 * 60 * 60 * 24 * 7,
301+
});
302+
} catch (e) {
303+
ctx.throw(500, e);
304+
}
305+
}
306+
ctx.body = {
307+
user: data,
308+
};
309+
} catch (e) {
310+
console.log(e);
292311
}
293-
294-
ctx.body = {
295-
user: ctx.user,
296-
};
297312
};
298313

299314
export const logout = (ctx: Context) => {

velog-backend/src/router/files/files.ctrl.js

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ import PostImage from 'database/models/PostImage';
88
import { isUUID } from 'lib/common';
99
import downloadImage from 'lib/downloadImage';
1010
import mimeTypes from 'mime-types';
11+
import UserThumbnail from 'database/models/UserThumbnail';
1112

1213
const s3 = new AWS.S3({ region: 'ap-northeast-2', signatureVersion: 'v4' });
1314

14-
export const createSignedUrl: Middleware = async (ctx: Context) => {
15+
export const createPostImageSignedUrl: Middleware = async (ctx: Context) => {
1516
const { post_id, filename } = (ctx.request.body: any);
1617
if (!post_id) {
1718
ctx.status = 400;
@@ -57,7 +58,9 @@ export const createSignedUrl: Middleware = async (ctx: Context) => {
5758
fk_post_id: post_id,
5859
fk_user_id: ctx.user.id,
5960
});
60-
const imagePath = `post-images/${ctx.user.username}/${postImage.id}/${filename}`;
61+
const imagePath = `post-images/${ctx.user.username}/${
62+
postImage.id
63+
}/${filename}`;
6164
postImage.path = imagePath;
6265
await postImage.save();
6366
const url = await s3.getSignedUrl('putObject', {
@@ -96,6 +99,33 @@ export const retrieveSize: Middleware = async (ctx: Context) => {
9699
// return result
97100
};
98101

102+
export const createThumbnailSignedUrl: Middleware = async (ctx) => {
103+
const { id: userId, username } = ctx.user;
104+
const { filename } = (ctx.request.body: any);
105+
106+
try {
107+
const contentType = mimeTypes.contentType(filename);
108+
const userThumbnail = UserThumbnail.build({
109+
fk_user_id: userId,
110+
});
111+
const imagePath = `thumbnails/${username}/${userThumbnail.id}-${filename}`;
112+
userThumbnail.path = imagePath;
113+
await userThumbnail.save();
114+
const url = await s3.getSignedUrl('putObject', {
115+
Bucket: 's3.images.velog.io',
116+
Key: imagePath,
117+
ContentType: contentType,
118+
});
119+
ctx.body = {
120+
url,
121+
image_path: imagePath,
122+
id: userThumbnail.id,
123+
};
124+
} catch (e) {
125+
ctx.throw(500, e);
126+
}
127+
};
128+
99129
export const upload: Middleware = async (ctx: Context) => {
100130
const { files, fields } = (ctx.request.body: any);
101131
// check whether every parameter exists
@@ -169,7 +199,9 @@ export const upload: Middleware = async (ctx: Context) => {
169199
fk_user_id: ctx.user.id,
170200
filesize: stats.size,
171201
});
172-
const imagePath = `post-images/${ctx.user.username}/${postImage.id}/${image.name}`;
202+
const imagePath = `post-images/${ctx.user.username}/${postImage.id}/${
203+
image.name
204+
}`;
173205
console.log(imagePath);
174206
postImage.path = imagePath;
175207
const read = fs.createReadStream(image.path);

velog-backend/src/router/files/index.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,16 @@ import * as filesCtrl from './files.ctrl';
66

77
const files: Router = new Router();
88

9-
files.post('/create-url', filesCtrl.createSignedUrl);
9+
files.post(
10+
'/create-url/post-image',
11+
needsAuth,
12+
filesCtrl.createPostImageSignedUrl,
13+
);
14+
files.post(
15+
'/create-url/thumbnail',
16+
needsAuth,
17+
filesCtrl.createThumbnailSignedUrl,
18+
);
1019
files.post('/upload', needsAuth, filesCtrl.upload);
1120
files.post('/retrieve-size/:id', checkUUID, filesCtrl.retrieveSize);
1221

velog-backend/src/router/me/me.ctrl.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export const updateProfile = async (ctx: Context): Promise<*> => {
1212
.min(1)
1313
.max(40),
1414
short_bio: Joi.string().max(140),
15+
thumbnail: Joi.string(),
1516
});
1617

1718
if (!validateSchema(ctx, schema)) {
@@ -21,6 +22,7 @@ export const updateProfile = async (ctx: Context): Promise<*> => {
2122
type BodySchema = {
2223
display_name?: string,
2324
short_bio?: string,
25+
thumbnail?: string,
2426
};
2527

2628
const body: BodySchema = (ctx.request.body: any);
@@ -36,7 +38,7 @@ export const updateProfile = async (ctx: Context): Promise<*> => {
3638
ctx.throw(500, 'Invalid Profile');
3739
}
3840

39-
['display_name', 'short_bio'].forEach((key) => {
41+
['display_name', 'short_bio', 'thumbnail'].forEach((key) => {
4042
if (body[key]) {
4143
profile[key] = body[key];
4244
}

velog-frontend/src/components/settings/SettingProfile/SettingProfile.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import './SettingProfile.scss';
1010
type Props = {
1111
profile: Profile,
1212
onUpdateProfile: ({ displayName: string, shortBio: string }) => any,
13+
onUploadThumbnail: () => any,
1314
};
1415

1516
type State = {
@@ -70,13 +71,13 @@ class SettingProfile extends Component<Props, State> {
7071
}
7172
};
7273
render() {
73-
const { profile } = this.props;
74+
const { profile, onUploadThumbnail } = this.props;
7475
const { editing, displayName, shortBio } = this.state;
7576
return (
7677
<div className="SettingProfile">
7778
<div className="thumbnail-area">
7879
<img src={profile.thumbnail || defaultThumbnail} alt="thumbnail" />
79-
<Button large fullWidth>
80+
<Button large fullWidth onClick={onUploadThumbnail}>
8081
썸네일 변경
8182
</Button>
8283
</div>

velog-frontend/src/containers/settings/SettingProfileContainer.js

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@ import type { State } from 'store';
44
import { connect } from 'react-redux';
55
import SettingProfile from 'components/settings/SettingProfile/SettingProfile';
66
import { type UserData } from 'store/modules/user';
7-
import { SettingsActions } from 'store/actionCreators';
7+
import { SettingsActions, UserActions } from 'store/actionCreators';
88
import { type Profile } from 'store/modules/profile';
9+
import { type UploadInfo } from 'store/modules/settings';
10+
import axios from 'axios';
11+
import storage, { keys } from 'lib/storage';
912

1013
type Props = {
1114
user: ?UserData,
1215
profile: ?Profile,
16+
uploadInfo: ?UploadInfo,
1317
};
1418

1519
class SettingProfileContainer extends Component<Props> {
@@ -29,21 +33,65 @@ class SettingProfileContainer extends Component<Props> {
2933
}
3034
}
3135

36+
uploadFile = async (file: any) => {
37+
try {
38+
await SettingsActions.createThumbnailSignedUrl(file.name);
39+
if (!this.props.uploadInfo) return;
40+
const { url, image_path: imagePath } = this.props.uploadInfo;
41+
await axios.put(url, file, {
42+
headers: {
43+
'Content-Type': file.type,
44+
},
45+
withCredentials: false,
46+
onUploadProgress: (e) => {
47+
if (window.nanobar) {
48+
window.nanobar.go(e.loaded / e.total * 100);
49+
}
50+
},
51+
});
52+
const imageUrl = `https://images.velog.io/${imagePath}`;
53+
await SettingsActions.updateProfile({
54+
thumbnail: imageUrl,
55+
});
56+
await UserActions.checkUser();
57+
storage.set(keys.user, this.props.user);
58+
} catch (e) {
59+
console.log(e);
60+
}
61+
};
62+
onUploadThumbnail = () => {
63+
const upload = document.createElement('input');
64+
upload.type = 'file';
65+
upload.onchange = (e) => {
66+
if (!upload.files) return;
67+
const file = upload.files[0];
68+
this.uploadFile(file);
69+
};
70+
upload.click();
71+
};
72+
3273
onUpdateProfile = ({ displayName, shortBio }: { displayName: string, shortBio: string }) => {
3374
return SettingsActions.updateProfile({ displayName, shortBio });
3475
};
3576

3677
render() {
3778
const { profile } = this.props;
3879
if (!profile) return null;
39-
return <SettingProfile profile={profile} onUpdateProfile={this.onUpdateProfile} />;
80+
return (
81+
<SettingProfile
82+
profile={profile}
83+
onUpdateProfile={this.onUpdateProfile}
84+
onUploadThumbnail={this.onUploadThumbnail}
85+
/>
86+
);
4087
}
4188
}
4289

4390
export default connect(
4491
({ user, settings }: State) => ({
4592
user: user.user,
4693
profile: settings.profile,
94+
uploadInfo: settings.uploadInfo,
4795
}),
4896
() => ({}),
4997
)(SettingProfileContainer);

velog-frontend/src/containers/write/CodeEditorContainer.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ class CodeEditorContainer extends Component<Props> {
9898
try {
9999
WriteActions.setUploadStatus(true);
100100
if (!this.props.uploadUrl) return;
101-
const response = await axios.put(this.props.uploadUrl, file, {
101+
await axios.put(this.props.uploadUrl, file, {
102102
headers: {
103103
'Content-Type': file.type,
104104
},
@@ -109,7 +109,6 @@ class CodeEditorContainer extends Component<Props> {
109109
}
110110
},
111111
});
112-
console.log(this.props.imagePath);
113112
if (!this.props.imagePath) return;
114113
const imageUrl = `${'\n'}![${file.name}](https://images.velog.io/${
115114
this.props.imagePath

0 commit comments

Comments
 (0)