Skip to content

Commit 458ad57

Browse files
committed
[refactor] replace Azure Blob with AWS S3 compatibility API
[optimize] update Upstream packages [fix] 2 API detail bugs Signed-off-by: TechQuery <shiy2008@gmail.com>
1 parent 7017af6 commit 458ad57

File tree

9 files changed

+164
-176
lines changed

9 files changed

+164
-176
lines changed

.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
NEXT_PUBLIC_SITE_NAME = 开放黑客松
22
NEXT_PUBLIC_SITE_SUMMARY = 基于 Git 云开发环境的开放黑客马拉松平台
3-
NEXT_PUBLIC_API_HOST = https://openhackathon-service-server.onrender.com
3+
NEXT_PUBLIC_API_HOST = https://openhackathon-service.onrender.com
44
NEXT_PUBLIC_AUTHING_APP_ID = 60178760106d5f26cb267ac1

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Open-source [Hackathon][1] Platform with **Git-based Cloud Development Environme
1515
- testing: https://test.hackathon.kaiyuanshe.cn/
1616
- production: https://hackathon.kaiyuanshe.cn/
1717
- RESTful API
18-
- production: https://openhackathon-service-server.onrender.com/
18+
- production: https://openhackathon-service.onrender.com/
1919

2020
## Technology stack
2121

components/Activity/ActivityEditor.tsx

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
1-
import { Hackathon } from '@kaiyuanshe/openhackathon-service';
1+
import { Hackathon, Media } from '@kaiyuanshe/openhackathon-service';
22
import { Loading } from 'idea-react';
33
import { computed } from 'mobx';
44
import { textJoin } from 'mobx-i18n';
55
import { observer } from 'mobx-react';
66
import { ObservedComponent } from 'mobx-react-helper';
7-
import { BadgeInput, Field, FileUploader, RestForm } from 'mobx-restful-table';
7+
import {
8+
ArrayField,
9+
ArrayFieldProps,
10+
Field,
11+
FileUploader,
12+
FormField,
13+
RestForm,
14+
} from 'mobx-restful-table';
815

916
import activityStore from '../../models/Activity';
1017
import fileStore from '../../models/Base/File';
@@ -33,13 +40,14 @@ export class ActivityEditor extends ObservedComponent<ActivityEditorProps, typeo
3340

3441
@computed
3542
get fields(): Field<Hackathon>[] {
36-
const { t } = this.observedContext;
43+
const i18n = this.observedContext;
44+
const { t } = i18n;
3745

3846
return [
3947
{
4048
key: 'name',
4149
renderLabel: t('activity_id'),
42-
pattern: '[a-zA-Z0-9]+',
50+
pattern: '[\\w-]+',
4351
required: true,
4452
invalidMessage: t('name_placeholder'),
4553
},
@@ -49,31 +57,14 @@ export class ActivityEditor extends ObservedComponent<ActivityEditorProps, typeo
4957
required: true,
5058
invalidMessage: textJoin(t('please_enter'), t('activity_name')),
5159
},
52-
{
53-
key: 'tags',
54-
renderLabel: t('tag'),
55-
renderInput: ({ tags }, { key, ...meta }) => (
56-
<RestForm.FieldBox name={key} {...meta}>
57-
<BadgeInput name={key} placeholder={t('tag_placeholder')} defaultValue={tags} />
58-
</RestForm.FieldBox>
59-
),
60-
},
60+
{ key: 'tags', renderLabel: t('tag'), multiple: true, placeholder: t('tag_placeholder') },
6161
{
6262
key: 'banners',
6363
renderLabel: t('bannerUrls'),
64-
accept: 'image/*',
65-
required: true,
66-
multiple: true,
6764
max: 10,
68-
uploader: fileStore,
69-
renderInput: ({ banners }, { key, uploader, ...meta }) => (
65+
renderInput: ({ banners }, { key, ...meta }) => (
7066
<RestForm.FieldBox name={key} {...meta}>
71-
<FileUploader
72-
store={uploader!}
73-
name={key}
74-
{...meta}
75-
defaultValue={banners?.map(({ uri }) => uri)}
76-
/>
67+
<ArrayField name="banners" defaultValue={banners} renderItem={this.renderMedia(i18n)} />
7768
</RestForm.FieldBox>
7869
),
7970
},
@@ -148,6 +139,28 @@ export class ActivityEditor extends ObservedComponent<ActivityEditorProps, typeo
148139
];
149140
}
150141

142+
renderMedia =
143+
({ t }: typeof i18n): ArrayFieldProps<Media>['renderItem'] =>
144+
({ uri, name, description }) => (
145+
<div className="d-flex align-items-center gap-2">
146+
<FileUploader
147+
store={fileStore}
148+
name="uri"
149+
accept="image/*"
150+
multiple
151+
defaultValue={uri ? [uri] : []}
152+
/>
153+
<FormField label={t('name')} name="name" defaultValue={name} />
154+
<FormField
155+
label={t('description')}
156+
as="textarea"
157+
rows={3}
158+
name="description"
159+
defaultValue={description}
160+
/>
161+
</div>
162+
);
163+
151164
render() {
152165
const i18n = this.observedContext,
153166
{ downloading, uploading } = activityStore;

components/Message/MessageList.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ export class AnnouncementList extends ObservedComponent<AnnouncementListProps, t
2525
render() {
2626
const i18n = this.observedContext;
2727

28-
return (
29-
<RestTable {...this.props} translator={i18n} columns={this.columns} editable deletable />
30-
);
28+
return <RestTable {...this.props} translator={i18n} columns={this.columns} />;
3129
}
3230
}

models/Base/File.ts

Lines changed: 21 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,34 @@
1-
import { HTTPError, Request, request } from 'koajax';
2-
import { DataObject, toggle } from 'mobx-restful';
1+
import { SignedLink } from '@kaiyuanshe/openhackathon-service';
2+
import { toggle } from 'mobx-restful';
33
import { FileModel } from 'mobx-restful-table';
4+
import { blobOf, uniqueID } from 'web-utility';
45

56
import sessionStore from '../User/Session';
6-
import { ErrorBaseData, UploadUrl } from './index';
77

8-
export class AzureFileModel extends FileModel {
9-
static async uploadBlob<T = void>(
10-
fullPath: string,
11-
method: Request['method'] = 'PUT',
12-
body?: any,
13-
headers: DataObject = {},
14-
) {
15-
headers['x-ms-blob-type'] = 'BlockBlob';
16-
17-
const { response } = request<T>({
18-
path: fullPath,
19-
method,
20-
body,
21-
headers,
22-
});
23-
const { headers: header, body: data } = await response;
24-
25-
if (!data || !('traceId' in (data as DataObject))) return data!;
26-
27-
const { status, title, detail } = data as unknown as ErrorBaseData;
28-
29-
throw new HTTPError(
30-
detail || title,
31-
{ method, path: fullPath, headers, body },
32-
{ status, statusText: title, headers: header, body: data },
33-
);
34-
}
8+
export class S3FileModel extends FileModel {
9+
client = sessionStore.client;
3510

3611
@toggle('uploading')
37-
async upload(file: File) {
38-
const { type, name } = file;
39-
40-
const { body } = await sessionStore.client.post<UploadUrl>(
41-
`user/generateFileUrl`,
42-
{ filename: name },
12+
async upload(file: string | Blob) {
13+
if (typeof file === 'string') {
14+
const name = file.split('/').pop()!;
15+
16+
file = new File([await blobOf(file)], name);
17+
}
18+
const { body } = await this.client.post<SignedLink>(
19+
`file/signed-link/${file instanceof File ? file.name : uniqueID()}`,
4320
);
44-
const parts = body!.uploadUrl.split('/');
45-
46-
const path = parts.slice(0, -1).join('/'),
47-
[fileName, data] = parts.at(-1)!.split('?');
48-
49-
const URI_Put = `${path}/${encodeURIComponent(fileName)}?${data}`;
21+
await this.client.put(body!.putLink, file, { 'Content-Type': file.type });
5022

51-
await AzureFileModel.uploadBlob(URI_Put, 'PUT', file, {
52-
'Content-Type': type,
53-
});
54-
55-
const { origin, pathname } = new URL(body!.url);
23+
return super.upload(body!.getLink);
24+
}
5625

57-
const URI_Get = `${
58-
origin + pathname.split('/').slice(0, -1).join('/')
59-
}/${encodeURIComponent(fileName)}`;
26+
@toggle('uploading')
27+
async delete(link: string) {
28+
await this.client.delete(`file/${link.replace(`${this.client.baseURI}/file/`, '')}`);
6029

61-
return super.upload(URI_Get);
30+
await super.delete(link);
6231
}
6332
}
6433

65-
export default new AzureFileModel();
34+
export default new S3FileModel();

models/Base/index.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import { Base, ListChunk } from '@kaiyuanshe/openhackathon-service';
2-
import { Filter as BaseFilter, ListModel, RESTClient } from 'mobx-restful';
2+
import { Filter as BaseFilter, IDType, ListModel, RESTClient, toggle } from 'mobx-restful';
33
import { buildURLData } from 'web-utility';
44

55
import { ownClient } from '../User/Session';
66

7-
export interface UploadUrl
8-
extends Record<'filename' | 'uploadUrl' | 'url', string> {
7+
export interface UploadUrl extends Record<'filename' | 'uploadUrl' | 'url', string> {
98
expiration: number;
109
}
1110

@@ -59,6 +58,15 @@ export abstract class TableModel<
5958
> extends ListModel<D, F> {
6059
client = ownClient;
6160

61+
@toggle('uploading')
62+
async updateOne(data: BaseFilter<D>, id?: IDType) {
63+
const { body } = await (id
64+
? this.client.put<D>(`${this.baseURI}/${id}`, data)
65+
: this.client.post<D>(this.baseURI, data));
66+
67+
return (this.currentOne = body!);
68+
}
69+
6270
async loadPage(pageIndex: number, pageSize: number, filter: F) {
6371
const { body } = await this.client.get<ListChunk<D>>(
6472
`${this.baseURI}?${buildURLData({ ...filter, pageIndex, pageSize })}`,

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"mobx-react": "^9.2.0",
3131
"mobx-react-helper": "^0.4.1",
3232
"mobx-restful": "^2.1.0",
33-
"mobx-restful-table": "^2.5.1",
33+
"mobx-restful-table": "^2.5.2",
3434
"next": "^15.3.3",
3535
"next-ssr-middleware": "^1.0.0",
3636
"open-react-map": "^0.9.0",
@@ -49,8 +49,8 @@
4949
"@cspell/eslint-plugin": "^9.0.2",
5050
"@eslint/compat": "^1.2.9",
5151
"@eslint/eslintrc": "^3.3.1",
52-
"@eslint/js": "^9.27.0",
53-
"@kaiyuanshe/openhackathon-service": "^0.21.1",
52+
"@eslint/js": "^9.28.0",
53+
"@kaiyuanshe/openhackathon-service": "^0.22.0",
5454
"@next/eslint-plugin-next": "^15.3.3",
5555
"@octokit/openapi-types": "^25.1.0",
5656
"@softonus/prettier-plugin-duplicate-remover": "^1.1.2",
@@ -62,7 +62,7 @@
6262
"@types/next-pwa": "^5.6.9",
6363
"@types/node": "^22.15.29",
6464
"@types/react": "^19.1.6",
65-
"eslint": "^9.27.0",
65+
"eslint": "^9.28.0",
6666
"eslint-config-next": "^15.3.3",
6767
"eslint-config-prettier": "^10.1.5",
6868
"eslint-plugin-react": "^7.37.5",

pages/activity/[name]/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export const getServerSideProps = compose<{ name?: string }, ActivityPageProps>(
4949
activityStore.organizationOf(name).getList(),
5050
]);
5151

52-
return { props: { activity, organizationList } };
52+
return { props: JSON.parse(JSON.stringify({ activity, organizationList })) };
5353
},
5454
);
5555

0 commit comments

Comments
 (0)