Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add file uploads and downloads #804

Merged
merged 4 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion backend/siarnaq/api/teams/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,17 @@ def join(self, request, pk=None, *, episode_id):
logger.debug("team_join", message="User has joined team.", team=team.pk)
return Response(None, status=status.HTTP_204_NO_CONTENT)

@extend_schema(responses={status.HTTP_204_NO_CONTENT: None})
@extend_schema(
responses={status.HTTP_204_NO_CONTENT: None},
request={
"multipart/form-data": {
"type": "object",
"properties": {
"avatar": {"type": "string", "format": "binary"},
},
}
},
)
@action(
detail=False,
methods=["post"],
Expand Down
24 changes: 6 additions & 18 deletions frontend2/schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ paths:
type: array
items:
type: integer
description: A list of teams to filter for. Defaults to just your own team.
description: A list of teams to filter for. Defaults to your own team.
tags:
- compete
security:
Expand Down Expand Up @@ -1290,16 +1290,13 @@ paths:
- team
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/TeamAvatarRequest'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/TeamAvatarRequest'
multipart/form-data:
schema:
$ref: '#/components/schemas/TeamAvatarRequest'
required: true
type: object
properties:
avatar:
type: string
format: binary
security:
- jwtAuth: []
responses:
Expand Down Expand Up @@ -2749,15 +2746,6 @@ components:
type: boolean
required:
- invocation
TeamAvatarRequest:
type: object
properties:
avatar:
type: string
format: binary
writeOnly: true
required:
- avatar
TeamCreate:
type: object
properties:
Expand Down
1 change: 0 additions & 1 deletion frontend2/src/api/_autogen/.openapi-generator/FILES
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ models/StyleEnum.ts
models/Submission.ts
models/SubmissionDownload.ts
models/SubmissionReportRequest.ts
models/TeamAvatarRequest.ts
models/TeamCreate.ts
models/TeamCreateRequest.ts
models/TeamJoinRequest.ts
Expand Down
33 changes: 22 additions & 11 deletions frontend2/src/api/_autogen/apis/TeamApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import type {
PaginatedClassRequirementList,
PaginatedTeamPublicList,
PatchedTeamPrivateRequest,
TeamAvatarRequest,
TeamCreate,
TeamCreateRequest,
TeamJoinRequest,
Expand All @@ -38,8 +37,6 @@ import {
PaginatedTeamPublicListToJSON,
PatchedTeamPrivateRequestFromJSON,
PatchedTeamPrivateRequestToJSON,
TeamAvatarRequestFromJSON,
TeamAvatarRequestToJSON,
TeamCreateFromJSON,
TeamCreateToJSON,
TeamCreateRequestFromJSON,
Expand Down Expand Up @@ -89,7 +86,7 @@ export interface TeamRequirementRetrieveRequest {

export interface TeamTAvatarCreateRequest {
episodeId: string;
teamAvatarRequest: TeamAvatarRequest;
avatar?: Blob;
}

export interface TeamTCreateRequest {
Expand Down Expand Up @@ -394,16 +391,10 @@ export class TeamApi extends runtime.BaseAPI {
throw new runtime.RequiredError('episodeId','Required parameter requestParameters.episodeId was null or undefined when calling teamTAvatarCreate.');
}

if (requestParameters.teamAvatarRequest === null || requestParameters.teamAvatarRequest === undefined) {
throw new runtime.RequiredError('teamAvatarRequest','Required parameter requestParameters.teamAvatarRequest was null or undefined when calling teamTAvatarCreate.');
}

const queryParameters: any = {};

const headerParameters: runtime.HTTPHeaders = {};

headerParameters['Content-Type'] = 'application/json';

if (this.configuration && this.configuration.accessToken) {
const token = this.configuration.accessToken;
const tokenString = await token("jwtAuth", []);
Expand All @@ -412,12 +403,32 @@ export class TeamApi extends runtime.BaseAPI {
headerParameters["Authorization"] = `Bearer ${tokenString}`;
}
}
const consumes: runtime.Consume[] = [
{ contentType: 'multipart/form-data' },
];
// @ts-ignore: canConsumeForm may be unused
const canConsumeForm = runtime.canConsumeForm(consumes);

let formParams: { append(param: string, value: any): any };
let useForm = false;
// use FormData to transmit files using content-type "multipart/form-data"
useForm = canConsumeForm;
if (useForm) {
formParams = new FormData();
} else {
formParams = new URLSearchParams();
}

if (requestParameters.avatar !== undefined) {
formParams.append('avatar', requestParameters.avatar as any);
}

const response = await this.request({
path: `/api/team/{episode_id}/t/avatar/`.replace(`{${"episode_id"}}`, encodeURIComponent(String(requestParameters.episodeId))),
method: 'POST',
headers: headerParameters,
query: queryParameters,
body: TeamAvatarRequestToJSON(requestParameters.teamAvatarRequest),
body: formParams,
}, initOverrides);

return new runtime.VoidApiResponse(response);
Expand Down
66 changes: 0 additions & 66 deletions frontend2/src/api/_autogen/models/TeamAvatarRequest.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export function UserProfilePrivateRequestFromJSONTyped(json: any, ignoreDiscrimi
return json;
}
return {

'gender': GenderEnumFromJSON(json['gender']),
'gender_details': !exists(json, 'gender_details') ? undefined : json['gender_details'],
'school': !exists(json, 'school') ? undefined : json['school'],
Expand All @@ -108,7 +108,7 @@ export function UserProfilePrivateRequestToJSON(value?: UserProfilePrivateReques
return null;
}
return {

'gender': GenderEnumToJSON(value.gender),
'gender_details': value.gender_details,
'school': value.school,
Expand Down
1 change: 0 additions & 1 deletion frontend2/src/api/_autogen/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ export * from './StyleEnum';
export * from './Submission';
export * from './SubmissionDownload';
export * from './SubmissionReportRequest';
export * from './TeamAvatarRequest';
export * from './TeamCreate';
export * from './TeamCreateRequest';
export * from './TeamJoinRequest';
Expand Down
3 changes: 3 additions & 0 deletions frontend2/src/api/compete/competeKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ export const competeMutationKeys = {
uploadSub: ({ episodeId }: { episodeId: string }) =>
["compete", episodeId, "submit"] as const,

downloadSub: ({ episodeId }: { episodeId: string }) =>
["compete", episodeId, "submit", "download"] as const,

// --- SCRIMMAGES --- //
requestScrim: ({ episodeId }: { episodeId: string }) =>
["compete", episodeId, "scrimmage", "request"] as const,
Expand Down
29 changes: 29 additions & 0 deletions frontend2/src/api/compete/useCompete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import type {
CompeteSubmissionCreateRequest,
CompeteSubmissionListRequest,
CompeteSubmissionTournamentListRequest,
CompeteSubmissionDownloadRetrieveRequest,
HistoricalRating,
PaginatedMatchList,
PaginatedScrimmageRequestList,
Expand All @@ -38,6 +39,7 @@ import {
rejectScrimmage,
requestScrimmage,
uploadSubmission,
downloadSubmission,
} from "./competeApi";
import toast from "react-hot-toast";
import { buildKey } from "../helpers";
Expand Down Expand Up @@ -535,3 +537,30 @@ export const useCancelScrimmage = (
});
},
});

/**
* For downloading a submission.
*/
export const useDownloadSubmission = ({
episodeId,
}: {
episodeId: string;
}): UseMutationResult<
void,
Error,
CompeteSubmissionDownloadRetrieveRequest,
unknown
> =>
useMutation({
mutationKey: competeMutationKeys.downloadSub({ episodeId }),
mutationFn: async ({
episodeId,
id,
}: CompeteSubmissionDownloadRetrieveRequest) => {
await toast.promise(downloadSubmission({ episodeId, id }), {
loading: "Downloading submission...",
success: "Downloaded submission!",
error: "Error downloading submission.",
});
},
});
6 changes: 1 addition & 5 deletions frontend2/src/api/loaders/teamProfileLoader.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import type { QueryClient } from "@tanstack/react-query";
import type { LoaderFunction } from "react-router-dom";
import { matchListFactory } from "../compete/competeFactories";
import { buildKey } from "../helpers";
import {
otherTeamInfoFactory,
searchTeamsFactory,
} from "../team/teamFactories";
import { otherTeamInfoFactory } from "../team/teamFactories";

// loader for other team's public profile pages
export const teamProfileLoader =
Expand Down
11 changes: 5 additions & 6 deletions frontend2/src/api/team/teamApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,13 +151,12 @@ export const searchTeams = async ({
/**
* Upload a new avatar for the currently logged in user's team.
* @param episodeId The current episode's ID.
* @param avatar The avatar file.
* @param teamAvatarRequest The avatar file.
*/
export const teamAvatarUpload = async ({
episodeId,
teamAvatarRequest,
}: TeamTAvatarCreateRequest): Promise<void> => {
await API.teamTAvatarCreate({ episodeId, teamAvatarRequest });
export const teamAvatarUpload = async (
teamAvatarRequest: TeamTAvatarCreateRequest,
): Promise<void> => {
await API.teamTAvatarCreate(teamAvatarRequest);
};

/**
Expand Down
2 changes: 1 addition & 1 deletion frontend2/src/api/team/teamKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const teamMutationKeys = {
update: ({ episodeId }: { episodeId: string }) =>
["team", "update", episodeId] as const,

avatar: ({ episodeId }: { episodeId: string }) =>
avatarUpload: ({ episodeId }: { episodeId: string }) =>
["team", "avatar", episodeId] as const,

report: ({ episodeId }: { episodeId: string }) =>
Expand Down
10 changes: 5 additions & 5 deletions frontend2/src/api/team/useTeam.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
import type {
PaginatedTeamPublicList,
PatchedTeamPrivateRequest,
TeamAvatarRequest,
TeamCreate,
TeamJoinRequest,
TeamPrivate,
Expand Down Expand Up @@ -198,11 +197,12 @@ export const useUpdateTeamAvatar = (
episodeId: string;
},
queryClient: QueryClient,
): UseMutationResult<void, Error, TeamAvatarRequest, unknown> =>
): UseMutationResult<void, Error, Blob, unknown> =>
useMutation({
mutationKey: teamMutationKeys.avatar({ episodeId }),
mutationFn: async (teamAvatarRequest: TeamAvatarRequest) => {
await toast.promise(teamAvatarUpload({ episodeId, teamAvatarRequest }), {
mutationKey: teamMutationKeys.avatarUpload({ episodeId }),
// We pass in a Blob because we already have the episodeId
mutationFn: async (avatar: Blob) => {
await toast.promise(teamAvatarUpload({ episodeId, avatar }), {
loading: "Uploading team avatar...",
success: "Uploaded team avatar!",
error: "Error uploading team avatar.",
Expand Down
Loading
Loading