Skip to content

Commit 3e4c4a6

Browse files
authored
[image-upload]: configurable max image upload size (#22)
* max file size is configurable * edit version.ts * rename property
1 parent 208b8a3 commit 3e4c4a6

File tree

9 files changed

+67
-9
lines changed

9 files changed

+67
-9
lines changed

api/config.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ db:
1111
challenges:
1212
liveParty:
1313
defaultTimePerSubmissionSeconds: 45
14+
fileUpload:
15+
maxFileSizeMb: 5
1416
imgProxy:
1517
enabled: true
1618
url: "http://localhost:8081"

api/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ func init() {
3434
viper.SetDefault("db.host", "localhost:5432")
3535
viper.SetDefault("db.poolSize", 50)
3636
viper.SetDefault("challenges.party.live.defaultTimePerSubmissionSeconds", 45)
37+
viper.SetDefault("challenges.fileUpload.maxFileSizeMb", 5)
3738
viper.SetDefault("imgProxy.enabled", false)
3839
viper.SetDefault("imgProxy.url", "http://localhost:8081")
3940
viper.SetDefault("imgProxy.sharedLocalCacheDir", "/tmp/group-challenge-cache")

api/pkg/group-challenge/api/api.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ var (
2525
livePartyHub *liveparty.LivePartyHub
2626
imgCache *ttlcache.Cache[string, models.Image]
2727
wsHub *ws.Hub
28-
maxImageFileSize int64 = 4 << 20
28+
maxImageFileSize int64
2929
imagesInMemoryCacheSize uint64 = 35
3030
imgProxyConfig config.ImgProxyConfig
3131
)
@@ -76,6 +76,10 @@ func configureAPIRouter(router *gin.Engine, con *pg.DB) {
7676
user.GET("", usersHandler)
7777
user.GET("/:id", userByIdHandler)
7878
}
79+
config := v1.Group("/config")
80+
{
81+
config.GET("", configHandler)
82+
}
7983

8084
v1.GET("ws", createWsHandler())
8185
}
@@ -106,6 +110,7 @@ func RunServer(serverConfig config.ServerConfig, challengesConfig config.Challen
106110
con = _con
107111
livePartyHub = liveparty.CreateLivePartyHub(challengesConfig.LiveParty, con)
108112
imgProxyConfig = _imgProxyConfig
113+
maxImageFileSize = int64(challengesConfig.FileUpload.MaxFileSizeMb) << 20
109114

110115
// in-memory image cache
111116
loader := ttlcache.LoaderFunc[string, models.Image](

api/pkg/group-challenge/api/config.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package api
2+
3+
import (
4+
"github.com/gin-gonic/gin"
5+
)
6+
7+
type ConfigResponse struct {
8+
MaxUploadSize int64 `json:"maxUploadSize"`
9+
}
10+
11+
func configHandler(c *gin.Context) {
12+
configResponse := ConfigResponse{
13+
MaxUploadSize: maxImageFileSize,
14+
}
15+
c.JSON(200, configResponse)
16+
}

api/pkg/group-challenge/config/config.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,12 @@ type DBConfig struct {
2626
}
2727

2828
type ChallengesConfig struct {
29-
LiveParty LivePartyConfig
29+
LiveParty LivePartyConfig
30+
FileUpload FileUploadConfig
31+
}
32+
33+
type FileUploadConfig struct {
34+
MaxFileSizeMb int
3035
}
3136

3237
type LivePartyConfig struct {

frontend/src/api/api-models.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
//response interfaces
22

3+
export interface ConfigResponse {
4+
maxUploadSize: number;
5+
}
6+
37
export interface PaginationResponse<T> {
48
pageSize: number;
59
page: number;

frontend/src/api/api.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
GCWebSocketEvent,
1010
PartyReaction,
1111
PaginationPartiesResponse,
12+
ConfigResponse,
1213
} from './api-models';
1314
import { PartyFormData } from '../party/PartyForm';
1415
import { useSession } from '../user/session';
@@ -165,6 +166,21 @@ export const usePartyStatus = (id: string) =>
165166
export const useUsers = () => useApiHook<UserResponse[]>({ queryKey: ['users'] });
166167
export const useUser = (id: string) => useApiHook<UserResponse>({ queryKey: ['users', id] });
167168

169+
export const useConfig = () => {
170+
const [session] = useSession();
171+
172+
return useQuery(['config'], () => config({ sessionToken: session?.token! }));
173+
};
174+
175+
export async function config({ sessionToken }: { sessionToken: string }): Promise<ConfigResponse> {
176+
return fetch(`${API_URLS.API}/config`, {
177+
method: 'GET',
178+
headers: {
179+
'X-AuthToken': sessionToken,
180+
},
181+
}).then((res) => res.json());
182+
}
183+
168184
export async function signIn(emailOrUsername: string, password: string): Promise<UserSession | undefined> {
169185
const response = await fetch(`${API_URLS.AUTH}/signin`, {
170186
body: JSON.stringify({ emailOrUsername, password: window.btoa(password) }),

frontend/src/party/submissions/PostPartySubmission.tsx

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,38 +4,43 @@ import { FaUpload } from 'react-icons/fa';
44
import { useMutation } from '@tanstack/react-query';
55
import { useParams } from 'react-router';
66
import { toast } from 'react-toastify';
7-
import { addSubmission } from '../../api/api';
7+
import { addSubmission, useConfig } from '../../api/api';
88
import { PartyResponse, PartySubmissionFormData } from '../../api/api-models';
99
import { useSession } from '../../user/session';
1010

11-
const MAX_FILE_SIZE = 4 << 20;
12-
1311
function PostPartySubmission({ party, afterUpload }: { party: PartyResponse; afterUpload?: () => any }) {
1412
const [session] = useSession();
1513
const { id } = useParams<{ id: string }>();
1614
const [imgPrevSrc, setImgPrevSrc] = useState<string | undefined>();
1715
const form = useForm<PartySubmissionFormData>();
1816
const { mutateAsync } = useMutation(addSubmission);
17+
const { data: appConfig } = useConfig();
1918

2019
const file = form.watch('files')?.[0];
21-
const fileTooLarge = file?.size > MAX_FILE_SIZE;
2220
const hasPreview = () => !!imgPrevSrc;
2321

2422
useEffect(() => {
25-
if (!file || fileTooLarge) {
23+
if (!file || !appConfig) {
24+
return;
25+
}
26+
27+
if (file?.size > appConfig?.maxUploadSize) {
2628
return;
2729
}
30+
2831
const reader = new FileReader();
2932
reader.onload = function (e: ProgressEvent<FileReader>) {
3033
setImgPrevSrc(e.target!.result as string);
3134
};
3235
reader.readAsDataURL(file);
33-
}, [file, fileTooLarge]);
36+
}, [file, appConfig]);
3437

3538
if (!id) {
3639
return <div>No party id provided</div>;
3740
}
3841

42+
const fileTooLarge = appConfig?.maxUploadSize && file?.size > appConfig?.maxUploadSize;
43+
3944
const onSubmit = async (data: PartySubmissionFormData) => {
4045
const req = await mutateAsync({ partyId: id, submission: data, sessionToken: session!.token });
4146
if (req.status === 413) {
@@ -68,7 +73,7 @@ function PostPartySubmission({ party, afterUpload }: { party: PartyResponse; aft
6873
>
6974
<FaUpload size={26} />
7075
<span className="mt-2 text-base leading-normal uppercase">Select a file</span>
71-
<span className="font-light">max {MAX_FILE_SIZE >> 20}MB</span>
76+
<span className="font-light">max {(appConfig?.maxUploadSize ?? 0) >> 20}MB</span>
7277
<input
7378
className="hidden"
7479
type="file"

frontend/src/version.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ interface Change {
99
}
1010

1111
export const CHANGES: Change[] = [
12+
{
13+
name: '0.13.0',
14+
changes: [{ description: 'Configurable max file upload size (default: 5MB)', type: 'feature' }],
15+
},
1216
{
1317
name: '0.12.0',
1418
changes: [

0 commit comments

Comments
 (0)