diff --git a/backend/build.gradle.kts b/backend/build.gradle.kts
index 9b1f12d..fc44155 100644
--- a/backend/build.gradle.kts
+++ b/backend/build.gradle.kts
@@ -40,6 +40,10 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-mail:3.1.5")
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
+ //Open API
+ implementation("io.swagger.core.v3:swagger-annotations:2.2.37")
+ implementation("io.swagger.core.v3:swagger-models:2.2.37")
+
//AOP
implementation("org.aspectj:aspectjrt:1.9.19")
implementation("org.aspectj:aspectjweaver:1.9.19")
diff --git a/backend/src/main/java/com/alttd/altitudeweb/controllers/forms/AppealController.java b/backend/src/main/java/com/alttd/altitudeweb/controllers/forms/AppealController.java
index 6ba3f6d..2a397d1 100644
--- a/backend/src/main/java/com/alttd/altitudeweb/controllers/forms/AppealController.java
+++ b/backend/src/main/java/com/alttd/altitudeweb/controllers/forms/AppealController.java
@@ -10,8 +10,8 @@ import com.alttd.altitudeweb.database.web_db.forms.AppealMapper;
import com.alttd.altitudeweb.database.web_db.mail.EmailVerification;
import com.alttd.altitudeweb.database.web_db.mail.EmailVerificationMapper;
import com.alttd.altitudeweb.mappers.AppealDataMapper;
-import com.alttd.altitudeweb.model.AppealResponseDto;
import com.alttd.altitudeweb.model.DiscordAppealDto;
+import com.alttd.altitudeweb.model.FormResponseDto;
import com.alttd.altitudeweb.model.MinecraftAppealDto;
import com.alttd.altitudeweb.model.UpdateMailDto;
import com.alttd.altitudeweb.services.limits.RateLimit;
@@ -25,7 +25,6 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;
import java.util.Optional;
-import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@@ -41,13 +40,13 @@ public class AppealController implements AppealsApi {
@RateLimit(limit = 3, timeValue = 1, timeUnit = TimeUnit.HOURS, key = "discordAppeal")
@Override
- public ResponseEntity submitDiscordAppeal(DiscordAppealDto discordAppealDto) {
+ public ResponseEntity submitDiscordAppeal(DiscordAppealDto discordAppealDto) {
throw new ResponseStatusException(HttpStatusCode.valueOf(501), "Discord appeals are not yet supported");
}
@RateLimit(limit = 3, timeValue = 1, timeUnit = TimeUnit.HOURS, key = "minecraftAppeal")
@Override
- public ResponseEntity submitMinecraftAppeal(MinecraftAppealDto minecraftAppealDto) {
+ public ResponseEntity submitMinecraftAppeal(MinecraftAppealDto minecraftAppealDto) {
boolean success = true;
CompletableFuture appealCompletableFuture = new CompletableFuture<>();
@@ -105,7 +104,7 @@ public class AppealController implements AppealsApi {
sqlSession.getMapper(AppealMapper.class)
.markAppealAsSent(appeal.id());
});
- AppealResponseDto appealResponseDto = new AppealResponseDto(
+ FormResponseDto appealResponseDto = new FormResponseDto(
appeal.id().toString(),
"Your appeal has been submitted. You will be notified when it has been reviewed.",
true);
@@ -113,7 +112,8 @@ public class AppealController implements AppealsApi {
}
@Override
- public ResponseEntity updateMail(UpdateMailDto updateMailDto) {
+ public ResponseEntity updateMail(UpdateMailDto updateMailDto) {
+ //TODO move to its own endpoint
throw new ResponseStatusException(HttpStatusCode.valueOf(501), "Updating mail is not yet supported");
}
diff --git a/backend/src/main/java/com/alttd/altitudeweb/controllers/forms/ApplicationController.java b/backend/src/main/java/com/alttd/altitudeweb/controllers/forms/ApplicationController.java
new file mode 100644
index 0000000..dcab6c8
--- /dev/null
+++ b/backend/src/main/java/com/alttd/altitudeweb/controllers/forms/ApplicationController.java
@@ -0,0 +1,15 @@
+package com.alttd.altitudeweb.controllers.forms;
+
+import com.alttd.altitudeweb.api.ApplicationsApi;
+import com.alttd.altitudeweb.model.FormResponseDto;
+import com.alttd.altitudeweb.model.StaffApplicationDto;
+import org.springframework.http.HttpStatusCode;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.server.ResponseStatusException;
+
+public class ApplicationController implements ApplicationsApi {
+ @Override
+ public ResponseEntity submitStaffApplication(StaffApplicationDto staffApplicationDto) {
+ throw new ResponseStatusException(HttpStatusCode.valueOf(501), "Staff applications are not yet supported");
+ }
+}
diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts
index 9dbaac1..7cea8be 100644
--- a/frontend/src/app/app.routes.ts
+++ b/frontend/src/app/app.routes.ts
@@ -126,6 +126,14 @@ export const routes: Routes = [
requiredAuthorizations: ['SCOPE_user']
}
},
+ {
+ path: 'forms/staff-application',
+ loadComponent: () => import('./pages/forms/staff-application/staff-application.component').then(m => m.StaffApplicationComponent),
+ canActivate: [AuthGuard],
+ data: {
+ requiredAuthorizations: ['SCOPE_user']
+ }
+ },
{
path: 'community',
loadComponent: () => import('./pages/altitude/community/community.component').then(m => m.CommunityComponent)
diff --git a/frontend/src/app/pages/forms/forms.component.html b/frontend/src/app/pages/forms/forms.component.html
index 29f2061..cef82a3 100644
--- a/frontend/src/app/pages/forms/forms.component.html
+++ b/frontend/src/app/pages/forms/forms.component.html
@@ -17,6 +17,14 @@
+
diff --git a/frontend/src/app/pages/forms/staff-application/staff-application.component.html b/frontend/src/app/pages/forms/staff-application/staff-application.component.html
new file mode 100644
index 0000000..395e821
--- /dev/null
+++ b/frontend/src/app/pages/forms/staff-application/staff-application.component.html
@@ -0,0 +1,309 @@
+
+
+
+
Staff Application
+
+
+
+
+
+
diff --git a/frontend/src/app/pages/forms/staff-application/staff-application.component.scss b/frontend/src/app/pages/forms/staff-application/staff-application.component.scss
new file mode 100644
index 0000000..535a690
--- /dev/null
+++ b/frontend/src/app/pages/forms/staff-application/staff-application.component.scss
@@ -0,0 +1,164 @@
+:host {
+ display: block;
+}
+
+.staff-application-container {
+ display: flex;
+ flex-direction: column;
+ min-height: 80vh;
+}
+
+main {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+}
+
+.form-container {
+ position: relative;
+ height: 100%;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+}
+
+.formPage {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
+ width: 100%;
+ height: 100%;
+ animation: fadeIn 0.5s ease-in-out;
+}
+
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.navigation-buttons {
+ display: flex;
+ gap: 16px;
+ margin-top: 20px;
+}
+
+.form-navigation {
+ display: flex;
+ justify-content: center;
+ gap: 10px;
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+}
+
+.nav-dot {
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+ background-color: rgba(255, 255, 255, 0.3);
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+ margin-top: auto;
+ margin-bottom: auto;
+
+ &.active {
+ background-color: #fff;
+ }
+}
+
+.nav-button {
+ color: #1f9bde;
+}
+
+.pages {
+ margin-top: auto;
+ margin-bottom: auto;
+}
+
+.description {
+ max-width: 75ch;
+ text-align: left;
+}
+
+.valid-email {
+ display: flex;
+ align-items: center;
+ color: #4CAF50;
+ margin: 10px 0;
+ padding: 8px 12px;
+ border-radius: 4px;
+ background-color: rgba(76, 175, 80, 0.1);
+}
+
+.valid-email mat-icon {
+ color: #4CAF50;
+ margin-right: 10px;
+}
+
+.valid-email span {
+ color: #4CAF50;
+ font-weight: 500;
+}
+
+.checkbox-field {
+ margin: 16px 0;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ width: 100%;
+}
+
+.checkbox-error {
+ color: #f44336;
+ font-size: 12px;
+ margin-top: 4px;
+}
+
+.field-container {
+ margin: 16px 0;
+ width: 100%;
+}
+
+.field-label {
+ font-size: 16px;
+ margin-bottom: 8px;
+ display: block;
+ color: rgba(255, 255, 255, 0.7);
+}
+
+.days-container {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ margin-bottom: 8px;
+}
+
+.day-chip {
+ padding: 8px 12px;
+ border-radius: 16px;
+ background-color: rgba(255, 255, 255, 0.1);
+ cursor: pointer;
+ transition: all 0.3s ease;
+
+ &.selected {
+ background-color: #1f9bde;
+ color: white;
+ }
+
+ &:hover {
+ background-color: rgba(255, 255, 255, 0.2);
+ }
+}
+
+mat-form-field {
+ margin-bottom: 16px;
+}
diff --git a/frontend/src/app/pages/forms/staff-application/staff-application.component.ts b/frontend/src/app/pages/forms/staff-application/staff-application.component.ts
new file mode 100644
index 0000000..5333b28
--- /dev/null
+++ b/frontend/src/app/pages/forms/staff-application/staff-application.component.ts
@@ -0,0 +1,348 @@
+import {
+ AfterViewInit,
+ Component,
+ computed,
+ ElementRef,
+ inject,
+ OnDestroy,
+ OnInit,
+ Renderer2,
+ signal
+} from '@angular/core';
+import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
+import {ApplicationsService, EmailEntry, MailService, StaffApplication} from '@api';
+import {HeaderComponent} from '@header/header.component';
+import {NgOptimizedImage} from '@angular/common';
+import {MatButtonModule} from '@angular/material/button';
+import {MatIconModule} from '@angular/material/icon';
+import {AuthService} from '@services/auth.service';
+import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
+import {MatFormFieldModule} from '@angular/material/form-field';
+import {MatSelectModule} from '@angular/material/select';
+import {MatInputModule} from '@angular/material/input';
+import {MatDialog} from '@angular/material/dialog';
+import {VerifyMailDialogComponent} from '@pages/forms/verify-mail-dialog/verify-mail-dialog.component';
+import {Router} from '@angular/router';
+import {MatCheckboxModule} from '@angular/material/checkbox';
+import {MatDatepickerModule} from '@angular/material/datepicker';
+import {MatNativeDateModule} from '@angular/material/core';
+import {MatChipsModule} from '@angular/material/chips';
+
+@Component({
+ selector: 'app-staff-application',
+ imports: [
+ HeaderComponent,
+ NgOptimizedImage,
+ MatButtonModule,
+ MatIconModule,
+ MatProgressSpinnerModule,
+ MatFormFieldModule,
+ MatSelectModule,
+ MatInputModule,
+ ReactiveFormsModule,
+ MatCheckboxModule,
+ MatDatepickerModule,
+ MatNativeDateModule,
+ MatChipsModule
+ ],
+ templateUrl: './staff-application.component.html',
+ styleUrl: './staff-application.component.scss'
+})
+export class StaffApplicationComponent implements OnInit, OnDestroy, AfterViewInit {
+
+ private mailService = inject(MailService);
+ public authService = inject(AuthService);
+ public staffApplicationService = inject(ApplicationsService)
+ private resizeObserver: ResizeObserver | null = null;
+ private boundHandleResize: any;
+
+ protected form: FormGroup;
+ private emails = signal([]);
+ protected verifiedEmails = computed(() => this.emails()
+ .filter(email => email.verified)
+ .map(email => email.email.toLowerCase()));
+ protected emailIsValid = signal(false);
+ protected dialog = inject(MatDialog);
+ protected availableDays: string[] = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
+ protected selectedDays: string[] = [];
+ protected userTimezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone;
+
+
+ constructor(
+ private elementRef: ElementRef,
+ private renderer: Renderer2
+ ) {
+ const staffApplication: StaffApplicationForm = {
+ email: new FormControl('', {
+ nonNullable: true,
+ validators: [Validators.required, Validators.email, Validators.maxLength(320)]
+ }),
+ age: new FormControl('', {
+ nonNullable: true,
+ validators: [Validators.required, Validators.min(13), Validators.pattern('^[0-9]*$')]
+ }),
+ discordUsername: new FormControl('', {
+ nonNullable: true,
+ validators: [Validators.required, Validators.maxLength(32)]
+ }),
+ meetsRequirements: new FormControl(false, {
+ nonNullable: true,
+ validators: [Validators.requiredTrue]
+ }),
+ pronouns: new FormControl('', {
+ nonNullable: true,
+ validators: [Validators.maxLength(32)]
+ }),
+ joinDate: new FormControl('', {
+ nonNullable: true,
+ validators: [Validators.required]
+ }),
+ weeklyPlaytime: new FormControl('', {
+ nonNullable: true,
+ validators: [Validators.required, Validators.min(1)]
+ }),
+ availableDays: new FormControl([], {
+ nonNullable: true,
+ validators: [Validators.required]
+ }),
+ availableTimes: new FormControl('', {
+ nonNullable: true,
+ validators: [Validators.required, Validators.maxLength(1000)]
+ }),
+ previousExperience: new FormControl('', {
+ nonNullable: true,
+ validators: [Validators.required, Validators.minLength(10), Validators.maxLength(4000)]
+ }),
+ pluginExperience: new FormControl('', {
+ nonNullable: true,
+ validators: [Validators.required, Validators.minLength(10), Validators.maxLength(4000)]
+ }),
+ moderatorExpectations: new FormControl('', {
+ nonNullable: true,
+ validators: [Validators.required, Validators.minLength(10), Validators.maxLength(4000)]
+ }),
+ additionalInfo: new FormControl('', {
+ nonNullable: true,
+ validators: [Validators.maxLength(4000)]
+ })
+ }
+ this.form = new FormGroup(staffApplication);
+
+ this.mailService.getUserEmails().subscribe(emails => {
+ this.emails.set(emails);
+ });
+
+ this.form.valueChanges.subscribe(() => {
+ if (this.verifiedEmails().includes(this.form.getRawValue().email.toLowerCase())) {
+ this.emailIsValid.set(true);
+ } else {
+ this.emailIsValid.set(false);
+ }
+ });
+
+ computed(() => {
+ if (this.verifiedEmails().length > 0) {
+ this.form.get('email')?.setValue(this.verifiedEmails()[0]);
+ this.emailIsValid.set(true);
+ }
+ });
+ }
+
+ ngOnInit() {
+ const uuid = this.authService.getUuid();
+ if (uuid === null) {
+ alert('Error retrieving token, please relog on the website and try again')
+ throw new Error('JWT subject is null, are you logged in?');
+ }
+ }
+
+ ngAfterViewInit() {
+ this.setupResizeObserver();
+ this.updateContainerHeight();
+
+ this.boundHandleResize = this.handleResize.bind(this);
+ window.addEventListener('resize', this.boundHandleResize);
+
+ setTimeout(() => this.updateContainerHeight(), 0);
+ }
+
+ ngOnDestroy() {
+ if (this.resizeObserver) {
+ this.resizeObserver.disconnect();
+ this.resizeObserver = null;
+ }
+
+ if (this.boundHandleResize) {
+ window.removeEventListener('resize', this.boundHandleResize);
+ }
+ }
+
+ private handleResize() {
+ this.updateContainerHeight();
+ }
+
+ private setupResizeObserver() {
+ this.resizeObserver = new ResizeObserver(() => {
+ this.updateContainerHeight();
+ });
+
+ const headerElement = document.querySelector('app-header');
+ if (headerElement) {
+ this.resizeObserver.observe(headerElement);
+ }
+
+ const footerElement = document.querySelector('footer');
+ if (footerElement) {
+ this.resizeObserver.observe(footerElement);
+ }
+ }
+
+ private updateContainerHeight() {
+ const headerElement = document.querySelector('app-header');
+ const footerElement = document.querySelector('footer');
+
+ const container = this.elementRef.nativeElement.querySelector('.staff-application-container');
+
+ if (headerElement && footerElement && container) {
+ const headerHeight = headerElement.getBoundingClientRect().height;
+ const footerHeight = footerElement.getBoundingClientRect().height;
+
+ const calculatedHeight = `calc(100vh - ${headerHeight}px - ${footerHeight}px)`;
+ this.renderer.setStyle(container, 'min-height', calculatedHeight);
+ }
+ }
+
+ public onSubmit() {
+ if (this.form === undefined) {
+ console.error('Form is undefined');
+ return
+ }
+ if (this.form.valid) {
+ this.sendForm()
+ } else {
+ // Mark all fields as touched to show validation errors
+ Object.keys(this.form.controls).forEach(field => {
+ const control = this.form.get(field);
+ control?.markAsTouched();
+ });
+ }
+ }
+
+ private router = inject(Router)
+
+ private sendForm() {
+ const staffApplication: StaffApplication = this.mapToStaffApplication(this.form.getRawValue());
+
+ this.staffApplicationService.submitStaffApplication(staffApplication).subscribe(result => {
+ //TODO route to mail page
+ // Navigate to the sent page
+ this.router.navigate(['/forms/sent'], {
+ state: {message: 'Your staff application has been submitted successfully. We will review your application and get back to you soon.'}
+ }).then();
+ })
+ }
+
+ public currentPageIndex: number = 0;
+ public totalPages: number[] = [0, 1, 2, 3, 4];
+
+ public goToPage(pageIndex: number): void {
+ if (pageIndex >= 0 && pageIndex < this.totalPages.length) {
+ this.currentPageIndex = pageIndex;
+ }
+ }
+
+ public previousPage() {
+ this.goToPage(this.currentPageIndex - 1);
+ }
+
+ public nextPage() {
+ this.goToPage(this.currentPageIndex + 1);
+ }
+
+ public isFirstPage(): boolean {
+ return this.currentPageIndex === 0;
+ }
+
+ public isLastPage(): boolean {
+ return this.currentPageIndex === this.totalPages.length - 1;
+ }
+
+ protected validateMailOrNextPage() {
+ if (this.emailIsValid()) {
+ this.nextPage();
+ return;
+ }
+ const dialogRef = this.dialog.open(VerifyMailDialogComponent, {
+ data: {email: this.form.getRawValue().email},
+ });
+ dialogRef.afterClosed().subscribe(result => {
+ if (result === true) {
+ this.emailIsValid.set(true);
+ this.nextPage();
+ }
+ });
+ }
+
+ toggleDay(day: string) {
+ const availableDaysControl = this.form.get('availableDays');
+ const currentDays = [...(availableDaysControl?.value || [])];
+
+ if (currentDays.includes(day)) {
+ const index = currentDays.indexOf(day);
+ currentDays.splice(index, 1);
+ } else {
+ currentDays.push(day);
+ }
+
+ availableDaysControl?.setValue(currentDays);
+ }
+
+ private mapToStaffApplication(formData: any): StaffApplication {
+ let joinDateString: string;
+
+ if (formData.joinDate instanceof Date) {
+ joinDateString = formData.joinDate.toISOString();
+ } else if (typeof formData.joinDate === 'string' && formData.joinDate.trim() !== '') {
+ const parsedDate = new Date(formData.joinDate);
+ if (isNaN(parsedDate.getTime())) {
+ throw new Error('Invalid date string');
+ }
+ joinDateString = parsedDate.toISOString();
+ } else {
+ throw new Error('Invalid date string');
+ }
+
+ return {
+ email: formData.email,
+ age: Number(formData.age),
+ discordUsername: formData.discordUsername,
+ meetsRequirements: formData.meetsRequirements,
+ pronouns: formData.pronouns || '',
+ joinDate: joinDateString,
+ weeklyPlaytime: Number(formData.weeklyPlaytime),
+ availableDays: formData.availableDays,
+ availableTimes: formData.availableTimes,
+ previousExperience: formData.previousExperience,
+ pluginExperience: formData.pluginExperience,
+ moderatorExpectations: formData.moderatorExpectations,
+ additionalInfo: formData.additionalInfo || ''
+ };
+
+ }
+}
+
+interface StaffApplicationForm {
+ email: FormControl;
+ age: FormControl;
+ discordUsername: FormControl;
+ meetsRequirements: FormControl;
+ pronouns: FormControl;
+ joinDate: FormControl;
+ weeklyPlaytime: FormControl;
+ availableDays: FormControl;
+ availableTimes: FormControl;
+ previousExperience: FormControl;
+ pluginExperience: FormControl;
+ moderatorExpectations: FormControl;
+ additionalInfo: FormControl;
+}
diff --git a/open_api/build.gradle.kts b/open_api/build.gradle.kts
index d02fd1e..335bf55 100644
--- a/open_api/build.gradle.kts
+++ b/open_api/build.gradle.kts
@@ -43,9 +43,9 @@ sourceSets {
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-validation")
- implementation("io.swagger.core.v3:swagger-annotations:2.2.20")
- implementation("io.swagger.core.v3:swagger-models:2.2.8")
- implementation("io.swagger.core.v3:swagger-core:2.2.8")
+ implementation("io.swagger.core.v3:swagger-annotations:2.2.37")
+ implementation("io.swagger.core.v3:swagger-models:2.2.37")
+ implementation("io.swagger.core.v3:swagger-core:2.2.37")
implementation("org.openapitools:jackson-databind-nullable:0.2.6")
implementation("org.springframework.hateoas:spring-hateoas:2.2.0")
diff --git a/open_api/src/main/resources/api.yml b/open_api/src/main/resources/api.yml
index f617581..7fb448e 100644
--- a/open_api/src/main/resources/api.yml
+++ b/open_api/src/main/resources/api.yml
@@ -55,6 +55,8 @@ paths:
$ref: './schemas/forms/appeal/appeal.yml#/MinecraftAppeal'
/api/appeal/discord-appeal:
$ref: './schemas/forms/appeal/appeal.yml#/DiscordAppeal'
+ /api/apply/staff-application:
+ $ref: './schemas/forms/staff_apply/staff_apply.yml#/StaffApply'
/api/login/requestNewUserLogin/{uuid}:
$ref: './schemas/login/login.yml#/RequestNewUserLogin'
/api/login/userLogin/{code}:
diff --git a/open_api/src/main/resources/schemas/forms/appeal/appeal.yml b/open_api/src/main/resources/schemas/forms/appeal/appeal.yml
index 213eb26..176c840 100644
--- a/open_api/src/main/resources/schemas/forms/appeal/appeal.yml
+++ b/open_api/src/main/resources/schemas/forms/appeal/appeal.yml
@@ -23,7 +23,7 @@ UpdateMail:
content:
application/json:
schema:
- $ref: '#/components/schemas/AppealResponse'
+ $ref: '../../generic/components.yml#/components/schemas/FormResponse'
default:
description: Unexpected error
content:
@@ -49,7 +49,7 @@ MinecraftAppeal:
content:
application/json:
schema:
- $ref: '#/components/schemas/AppealResponse'
+ $ref: '../../generic/components.yml#/components/schemas/FormResponse'
default:
description: Unexpected error
content:
@@ -75,7 +75,7 @@ DiscordAppeal:
content:
application/json:
schema:
- $ref: '#/components/schemas/AppealResponse'
+ $ref: '../../generic/components.yml#/components/schemas/FormResponse'
default:
description: Unexpected error
content:
@@ -136,22 +136,6 @@ components:
appeal:
type: string
description: Appeal text explaining why the punishment should be reconsidered
- AppealResponse:
- type: object
- required:
- - id
- - message
- - verified_mail
- properties:
- id:
- type: string
- description: Unique identifier for the submitted appeal for referring to it later
- message:
- type: string
- description: Confirmation message
- verified_mail:
- type: boolean
- description: If this user has verified their mail already
UpdateMail:
type: object
required:
diff --git a/open_api/src/main/resources/schemas/forms/staff_apply/staff_apply.yml b/open_api/src/main/resources/schemas/forms/staff_apply/staff_apply.yml
new file mode 100644
index 0000000..ace07b5
--- /dev/null
+++ b/open_api/src/main/resources/schemas/forms/staff_apply/staff_apply.yml
@@ -0,0 +1,104 @@
+StaffApply:
+ post:
+ tags:
+ - applications
+ summary: Submit a Staff appeal
+ description: Submit an staff application
+ operationId: submitStaffApplication
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/StaffApplication'
+ responses:
+ '201':
+ description: Application created please verify email
+ content:
+ application/json:
+ schema:
+ $ref: '../../generic/components.yml#/components/schemas/FormResponse'
+ default:
+ description: Unexpected error
+ content:
+ application/json:
+ schema:
+ $ref: '../../generic/errors.yml#/components/schemas/ApiError'
+components:
+ schemas:
+ StaffApplication:
+ type: object
+ description: Schema for staff application
+ required:
+ - email
+ - age
+ - discordUsername
+ - meetsRequirements
+ - pronouns
+ - joinDate
+ - weeklyPlaytime
+ - availableDays
+ - availableTimes
+ - previousExperience
+ - pluginExperience
+ - moderatorExpectations
+ - additionalInfo
+ properties:
+ email:
+ type: string
+ format: email
+ maxLength: 320
+ description: Email address of the applicant
+ age:
+ type: integer
+ minimum: 13
+ description: Age of the applicant, must be 13 or older
+ discordUsername:
+ type: string
+ maxLength: 32
+ description: Discord username of the applicant
+ meetsRequirements:
+ type: boolean
+ description: Confirmation that the applicant meets all requirements
+ pronouns:
+ type: string
+ maxLength: 32
+ description: Preferred pronouns of the applicant
+ joinDate:
+ type: string
+ maxLength: 256
+ format: date
+ description: Date when the applicant joined the service
+ weeklyPlaytime:
+ type: integer
+ minimum: 1
+ description: Average weekly playtime in hours
+ availableDays:
+ type: array
+ items:
+ type: string
+ maxLength: 256
+ description: Days of the week when the applicant is available
+ availableTimes:
+ type: string
+ maxLength: 1000
+ description: Time ranges when the applicant is available
+ previousExperience:
+ type: string
+ minLength: 10
+ maxLength: 4000
+ description: Description of previous relevant experience
+ pluginExperience:
+ type: string
+ minLength: 10
+ maxLength: 4000
+ description: Description of experience with plugins
+ moderatorExpectations:
+ type: string
+ minLength: 10
+ maxLength: 4000
+ description: Applicant's expectations and understanding of moderator responsibilities
+ additionalInfo:
+ type: string
+ maxLength: 4000
+ description: Any additional information the applicant wishes to provide
diff --git a/open_api/src/main/resources/schemas/generic/components.yml b/open_api/src/main/resources/schemas/generic/components.yml
new file mode 100644
index 0000000..a8f7cb4
--- /dev/null
+++ b/open_api/src/main/resources/schemas/generic/components.yml
@@ -0,0 +1,18 @@
+components:
+ schemas:
+ FormResponse:
+ type: object
+ required:
+ - id
+ - message
+ - verified_mail
+ properties:
+ id:
+ type: string
+ description: Unique identifier for the submitted form for referring to it later
+ message:
+ type: string
+ description: Confirmation message
+ verified_mail:
+ type: boolean
+ description: If this user has verified their mail already