Compare commits
No commits in common. "3af17928e8af8da5b6a63181f769cef8f433c301" and "a2a5f7877bef6fce69b43fd9f65db030cc897a40" have entirely different histories.
3af17928e8
...
a2a5f7877b
17 changed files with 163 additions and 23849 deletions
|
|
@ -134,8 +134,7 @@ RUN bun run build
|
||||||
# `node --import tsx src/bot/index.ts` (tsx resolves the @/ + @payload-config
|
# `node --import tsx src/bot/index.ts` (tsx resolves the @/ + @payload-config
|
||||||
# tsconfig path aliases that plain Node type-stripping cannot).
|
# tsconfig path aliases that plain Node type-stripping cannot).
|
||||||
FROM node:22-alpine AS runner
|
FROM node:22-alpine AS runner
|
||||||
# ffmpeg: voiceover submissions are transcoded to MP3 in submitVoiceRecording.
|
RUN apk add --no-cache libc6-compat wget
|
||||||
RUN apk add --no-cache libc6-compat wget ffmpeg
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
ENV NODE_ENV=production \
|
ENV NODE_ENV=production \
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "polaris-task-force",
|
"name": "polaris-task-force",
|
||||||
"version": "0.2.6",
|
"version": "0.2.5",
|
||||||
"description": "Web app for the Arma 3 unit Polaris Task Force. Uses Next.js for client- and server-side rendering, with a Postgres database and some other neat libraries.",
|
"description": "Web app for the Arma 3 unit Polaris Task Force. Uses Next.js for client- and server-side rendering, with a Postgres database and some other neat libraries.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|
|
||||||
|
|
@ -72,50 +72,40 @@ export default async function RootLayout(props: { children: React.ReactNode }) {
|
||||||
let isIntelligence = false;
|
let isIntelligence = false;
|
||||||
let isLogistics = false;
|
let isLogistics = false;
|
||||||
let currentRank = "";
|
let currentRank = "";
|
||||||
let hasPersonalBankAccount = false;
|
|
||||||
|
|
||||||
const shimsGlobal = await payload.findGlobal({ slug: "shims" });
|
const shimsGlobal = await payload.findGlobal({ slug: "shims" });
|
||||||
const shimEntries = (shimsGlobal.shims ?? []) as NonNullable<Shim["shims"]>;
|
const shimEntries = (shimsGlobal.shims ?? []) as NonNullable<Shim["shims"]>;
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
const [intelligenceQual, logisticsQual, profileRes, experienceRes, personalAccountRes] =
|
[isIntelligence, isLogistics] = await Promise.all([
|
||||||
await Promise.all([
|
hasIntelligenceQualification(payload, user).catch(() => false),
|
||||||
hasIntelligenceQualification(payload, user).catch(() => false),
|
hasLogisticsQualification(payload, user).catch(() => false),
|
||||||
hasLogisticsQualification(payload, user).catch(() => false),
|
]);
|
||||||
payload.find({
|
const [profileRes, experienceRes] = await Promise.all([
|
||||||
collection: "profiles",
|
payload.find({
|
||||||
where: { user: { equals: user.id } },
|
collection: "profiles",
|
||||||
select: {
|
where: { user: { equals: user.id } },
|
||||||
rank: true,
|
select: {
|
||||||
progression: {
|
rank: true,
|
||||||
awards: true,
|
progression: {
|
||||||
scoreboard: true,
|
awards: true,
|
||||||
xp: true,
|
scoreboard: true,
|
||||||
minigames: true,
|
xp: true,
|
||||||
},
|
minigames: true,
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
payload.find({
|
}),
|
||||||
collection: "experience",
|
payload.find({
|
||||||
sort: "requiredPoints",
|
collection: "experience",
|
||||||
limit: 100,
|
sort: "requiredPoints",
|
||||||
select: {
|
limit: 100,
|
||||||
name: true,
|
select: {
|
||||||
description: true,
|
name: true,
|
||||||
requiredPoints: true,
|
description: true,
|
||||||
},
|
requiredPoints: true,
|
||||||
}),
|
},
|
||||||
payload.find({
|
}),
|
||||||
collection: "bank-accounts",
|
]);
|
||||||
where: {
|
|
||||||
and: [{ accountType: { equals: "personal" } }, { ownerUser: { equals: user.id } }],
|
|
||||||
},
|
|
||||||
limit: 1,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
isIntelligence = intelligenceQual;
|
|
||||||
isLogistics = logisticsQual;
|
|
||||||
hasPersonalBankAccount = personalAccountRes.docs.length > 0;
|
|
||||||
|
|
||||||
const userXp = profileRes.docs[0]?.progression?.xp ?? 0;
|
const userXp = profileRes.docs[0]?.progression?.xp ?? 0;
|
||||||
xpLevel = resolveLevel(userXp, experienceRes.docs);
|
xpLevel = resolveLevel(userXp, experienceRes.docs);
|
||||||
|
|
@ -165,8 +155,6 @@ export default async function RootLayout(props: { children: React.ReactNode }) {
|
||||||
hasIntelligence={isIntelligence}
|
hasIntelligence={isIntelligence}
|
||||||
hasLogistics={isLogistics}
|
hasLogistics={isLogistics}
|
||||||
username={user.username}
|
username={user.username}
|
||||||
preferences={user.preferences}
|
|
||||||
hasPersonalBankAccount={hasPersonalBankAccount}
|
|
||||||
/>
|
/>
|
||||||
</CommandPaletteProvider>
|
</CommandPaletteProvider>
|
||||||
</KeyboardShortcutsProvider>
|
</KeyboardShortcutsProvider>
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import { broadcastToUser } from "@/lib/realtime/bus";
|
||||||
import { notifyUser } from "@/lib/notifications";
|
import { notifyUser } from "@/lib/notifications";
|
||||||
import hasRoles from "@/utils/access-control/hasRoles";
|
import hasRoles from "@/utils/access-control/hasRoles";
|
||||||
import { isPayloadUser } from "@/utils/access-control/isPayloadUser";
|
import { isPayloadUser } from "@/utils/access-control/isPayloadUser";
|
||||||
import { transcodeToMp3 } from "@/lib/voiceover/transcode";
|
|
||||||
import type { VoiceScript, VoiceSubmission } from "@/payload-types";
|
import type { VoiceScript, VoiceSubmission } from "@/payload-types";
|
||||||
|
|
||||||
interface ActionResult<T = undefined> {
|
interface ActionResult<T = undefined> {
|
||||||
|
|
@ -20,10 +19,6 @@ interface ActionResult<T = undefined> {
|
||||||
/** Server actions body limit is 10mb — enforce a 9 MB cap server-side with a clear error. */
|
/** Server actions body limit is 10mb — enforce a 9 MB cap server-side with a clear error. */
|
||||||
const MAX_AUDIO_BYTES = 9 * 1024 * 1024;
|
const MAX_AUDIO_BYTES = 9 * 1024 * 1024;
|
||||||
|
|
||||||
const MAX_TAKES_PER_SCRIPT = 5;
|
|
||||||
|
|
||||||
const MAX_DESCRIPTION_CHARS = 300;
|
|
||||||
|
|
||||||
async function authenticate() {
|
async function authenticate() {
|
||||||
const payloadConfig = await config;
|
const payloadConfig = await config;
|
||||||
const payload = await getPayload({ config: payloadConfig });
|
const payload = await getPayload({ config: payloadConfig });
|
||||||
|
|
@ -88,26 +83,6 @@ export async function submitVoiceRecording(
|
||||||
return { success: false, error: "This script is closed to new submissions." };
|
return { success: false, error: "This script is closed to new submissions." };
|
||||||
}
|
}
|
||||||
|
|
||||||
const takeCount = await payload.count({
|
|
||||||
collection: "voice-submissions",
|
|
||||||
where: {
|
|
||||||
and: [{ user: { equals: user.id } }, { script: { equals: scriptId } }],
|
|
||||||
},
|
|
||||||
overrideAccess: true,
|
|
||||||
});
|
|
||||||
if (takeCount.totalDocs >= MAX_TAKES_PER_SCRIPT) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: `You have reached the limit of ${MAX_TAKES_PER_SCRIPT} takes for this script.`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const rawDescription = formData.get("description");
|
|
||||||
const description =
|
|
||||||
typeof rawDescription === "string" && rawDescription.trim().length > 0
|
|
||||||
? rawDescription.trim().slice(0, MAX_DESCRIPTION_CHARS)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
const file = formData.get("file");
|
const file = formData.get("file");
|
||||||
if (!(file instanceof File)) {
|
if (!(file instanceof File)) {
|
||||||
return { success: false, error: "No audio file provided." };
|
return { success: false, error: "No audio file provided." };
|
||||||
|
|
@ -122,41 +97,18 @@ export async function submitVoiceRecording(
|
||||||
return { success: false, error: "Audio recordings must be 9 MB or smaller." };
|
return { success: false, error: "Audio recordings must be 9 MB or smaller." };
|
||||||
}
|
}
|
||||||
|
|
||||||
const originalBuffer = Buffer.from(await file.arrayBuffer());
|
|
||||||
// Normalize every submission to MP3 (mono 96 kbps) so stored takes play
|
|
||||||
// everywhere regardless of which codec the browser recorded with. If the
|
|
||||||
// transcode fails (no ffmpeg binary, unsupported input, timeout) the
|
|
||||||
// original file is stored untouched instead of rejecting the submission.
|
|
||||||
const mp3 = await transcodeToMp3(originalBuffer, file.name);
|
|
||||||
if (mp3) {
|
|
||||||
payload.logger.info(
|
|
||||||
`Voiceover submission for script ${scriptId}: converted ${file.name} (${originalBuffer.length} bytes) to ${mp3.name} (${mp3.size} bytes)`,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
payload.logger.warn(
|
|
||||||
`Voiceover submission for script ${scriptId}: MP3 conversion failed, storing original file ${file.name}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const audio = mp3 ?? {
|
|
||||||
buffer: originalBuffer,
|
|
||||||
name: file.name,
|
|
||||||
mimetype,
|
|
||||||
size: originalBuffer.length,
|
|
||||||
};
|
|
||||||
|
|
||||||
const submission = await payload.create({
|
const submission = await payload.create({
|
||||||
collection: "voice-submissions",
|
collection: "voice-submissions",
|
||||||
data: {
|
data: {
|
||||||
script: scriptId,
|
script: scriptId,
|
||||||
user: user.id,
|
user: user.id,
|
||||||
status: "pending",
|
status: "pending",
|
||||||
description,
|
|
||||||
},
|
},
|
||||||
file: {
|
file: {
|
||||||
data: audio.buffer,
|
data: Buffer.from(await file.arrayBuffer()),
|
||||||
mimetype: audio.mimetype,
|
mimetype,
|
||||||
name: audio.name,
|
name: file.name,
|
||||||
size: audio.size,
|
size: file.size,
|
||||||
},
|
},
|
||||||
overrideAccess: true,
|
overrideAccess: true,
|
||||||
depth: 0,
|
depth: 0,
|
||||||
|
|
|
||||||
|
|
@ -52,14 +52,6 @@ export const VoiceSubmissions: CollectionConfig = {
|
||||||
description: "Submitter",
|
description: "Submitter",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "description",
|
|
||||||
type: "textarea",
|
|
||||||
maxLength: 300,
|
|
||||||
admin: {
|
|
||||||
description: "Optional short note from the submitter about this take.",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "status",
|
name: "status",
|
||||||
type: "select",
|
type: "select",
|
||||||
|
|
@ -75,7 +67,7 @@ export const VoiceSubmissions: CollectionConfig = {
|
||||||
name: "reviewNote",
|
name: "reviewNote",
|
||||||
type: "textarea",
|
type: "textarea",
|
||||||
admin: {
|
admin: {
|
||||||
description: "Optional reviewer feedback. Shown to the submitter alongside their take.",
|
description: "Optional admin review note.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -24,17 +24,12 @@ import {
|
||||||
} from "./CommandPaletteContext";
|
} from "./CommandPaletteContext";
|
||||||
import { markAllNotificationsRead } from "@/app/(frontend)/notifications/actions";
|
import { markAllNotificationsRead } from "@/app/(frontend)/notifications/actions";
|
||||||
import { autoArrangeLocker } from "@/app/(frontend)/locker/actions";
|
import { autoArrangeLocker } from "@/app/(frontend)/locker/actions";
|
||||||
import { ensureMyAccount } from "@/app/(frontend)/logistics/banking/actions";
|
|
||||||
import { updatePreferences } from "@/app/(frontend)/account/actions";
|
|
||||||
import type { User as PayloadUser } from "@/payload-types";
|
|
||||||
|
|
||||||
interface CommandPaletteProps {
|
interface CommandPaletteProps {
|
||||||
userRoles: string[];
|
userRoles: string[];
|
||||||
hasIntelligence: boolean;
|
hasIntelligence: boolean;
|
||||||
hasLogistics: boolean;
|
hasLogistics: boolean;
|
||||||
username?: string;
|
username?: string;
|
||||||
preferences?: NonNullable<PayloadUser["preferences"]>;
|
|
||||||
hasPersonalBankAccount?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CommandPalette({
|
export function CommandPalette({
|
||||||
|
|
@ -42,8 +37,6 @@ export function CommandPalette({
|
||||||
hasIntelligence,
|
hasIntelligence,
|
||||||
hasLogistics,
|
hasLogistics,
|
||||||
username,
|
username,
|
||||||
preferences,
|
|
||||||
hasPersonalBankAccount,
|
|
||||||
}: CommandPaletteProps) {
|
}: CommandPaletteProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
|
|
@ -97,64 +90,6 @@ export function CommandPalette({
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "ensureBankAccount": {
|
|
||||||
const result = await ensureMyAccount();
|
|
||||||
if (result.success) {
|
|
||||||
toast.success("Personal bank account ready");
|
|
||||||
} else {
|
|
||||||
toast.error(result.error ?? "Failed to provision bank account");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "toggleBackgroundTexture": {
|
|
||||||
const result = await updatePreferences({
|
|
||||||
mutedAll: preferences?.notifications?.mutedAll ?? false,
|
|
||||||
mutedTypes: preferences?.notifications?.mutedTypes ?? [],
|
|
||||||
showCallsign: preferences?.display?.showCallsign ?? false,
|
|
||||||
showVersionOverlay: preferences?.display?.showVersionOverlay ?? true,
|
|
||||||
showBackgroundTexture: !(preferences?.display?.showBackgroundTexture ?? true),
|
|
||||||
leadOrActual: preferences?.display?.leadOrActual ?? "lead",
|
|
||||||
discordEnabled: preferences?.discord?.enabled ?? false,
|
|
||||||
discordMutedTypes: preferences?.discord?.mutedTypes ?? [],
|
|
||||||
xpDisplayMini: preferences?.display?.xpDisplayMini ?? false,
|
|
||||||
});
|
|
||||||
if (result.success) {
|
|
||||||
toast.success("Background texture toggled");
|
|
||||||
router.refresh();
|
|
||||||
} else {
|
|
||||||
toast.error(result.error ?? "Failed to update preferences");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "toggleCallsign": {
|
|
||||||
const result = await updatePreferences({
|
|
||||||
mutedAll: preferences?.notifications?.mutedAll ?? false,
|
|
||||||
mutedTypes: preferences?.notifications?.mutedTypes ?? [],
|
|
||||||
showCallsign: !(preferences?.display?.showCallsign ?? false),
|
|
||||||
showVersionOverlay: preferences?.display?.showVersionOverlay ?? true,
|
|
||||||
showBackgroundTexture: preferences?.display?.showBackgroundTexture ?? true,
|
|
||||||
leadOrActual: preferences?.display?.leadOrActual ?? "lead",
|
|
||||||
discordEnabled: preferences?.discord?.enabled ?? false,
|
|
||||||
discordMutedTypes: preferences?.discord?.mutedTypes ?? [],
|
|
||||||
xpDisplayMini: preferences?.display?.xpDisplayMini ?? false,
|
|
||||||
});
|
|
||||||
if (result.success) {
|
|
||||||
toast.success("Callsign display toggled");
|
|
||||||
router.refresh();
|
|
||||||
} else {
|
|
||||||
toast.error(result.error ?? "Failed to update preferences");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "copyPageLink": {
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(window.location.href);
|
|
||||||
toast.success("Page link copied to clipboard");
|
|
||||||
} catch {
|
|
||||||
toast.error("Failed to copy page link");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "refreshPage": {
|
case "refreshPage": {
|
||||||
router.refresh();
|
router.refresh();
|
||||||
toast.success("Page refreshed");
|
toast.success("Page refreshed");
|
||||||
|
|
@ -170,7 +105,7 @@ export function CommandPalette({
|
||||||
setLoading(null);
|
setLoading(null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[router, preferences],
|
[router],
|
||||||
);
|
);
|
||||||
|
|
||||||
const filteredNavCommands = React.useMemo<NavigationCommand[]>(() => {
|
const filteredNavCommands = React.useMemo<NavigationCommand[]>(() => {
|
||||||
|
|
@ -219,12 +154,9 @@ export function CommandPalette({
|
||||||
if (cmd.requires === "logistics" && !hasLogistics && !userRoles.includes("developer")) {
|
if (cmd.requires === "logistics" && !hasLogistics && !userRoles.includes("developer")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (cmd.actionKey === "ensureBankAccount" && hasPersonalBankAccount) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}, [userRoles, hasIntelligence, hasLogistics, hasPersonalBankAccount]);
|
}, [userRoles, hasIntelligence, hasLogistics]);
|
||||||
|
|
||||||
const groupedContextual = React.useMemo(() => {
|
const groupedContextual = React.useMemo(() => {
|
||||||
const groups = new Map<string, ContextualCommand[]>();
|
const groups = new Map<string, ContextualCommand[]>();
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,10 @@ import {
|
||||||
Bell,
|
Bell,
|
||||||
Backpack,
|
Backpack,
|
||||||
LogOut,
|
LogOut,
|
||||||
|
PlaneTakeoff,
|
||||||
|
User,
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
Wallet,
|
Shield,
|
||||||
Wallpaper,
|
|
||||||
Tag,
|
|
||||||
Link2,
|
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
export interface ActionCommand {
|
export interface ActionCommand {
|
||||||
|
|
@ -29,37 +28,39 @@ export interface ActionCommand {
|
||||||
|
|
||||||
export const actionCommands: ActionCommand[] = [
|
export const actionCommands: ActionCommand[] = [
|
||||||
{
|
{
|
||||||
id: "action:provision-bank-account",
|
id: "action:flight-simulator",
|
||||||
label: "Provision Personal Bank Account",
|
label: "Flight Simulator",
|
||||||
icon: Wallet,
|
icon: PlaneTakeoff,
|
||||||
keywords: ["banking", "wallet", "money", "create", "open"],
|
keywords: ["flappy", "game", "minigame", "fly"],
|
||||||
actionType: "callback",
|
actionType: "navigate",
|
||||||
actionKey: "ensureBankAccount",
|
href: "/flappy",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "action:toggle-background-texture",
|
id: "action:view-notifications",
|
||||||
label: "Toggle Background Texture",
|
label: "View Notifications",
|
||||||
icon: Wallpaper,
|
icon: Bell,
|
||||||
keywords: ["display", "appearance", "theme", "texture", "background"],
|
keywords: ["alerts", "messages", "inbox"],
|
||||||
actionType: "callback",
|
actionType: "navigate",
|
||||||
actionKey: "toggleBackgroundTexture",
|
href: "/notifications",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "action:toggle-callsign",
|
id: "action:create-ticket",
|
||||||
label: "Toggle Callsign Display",
|
label: "Create Support Ticket",
|
||||||
icon: Tag,
|
icon: UserPlus,
|
||||||
keywords: ["display", "callsign", "name", "sidebar"],
|
keywords: ["help", "support", "report"],
|
||||||
actionType: "callback",
|
actionType: "navigate",
|
||||||
actionKey: "toggleCallsign",
|
href: "/helpdesk",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "action:copy-page-link",
|
id: "action:admin-panel",
|
||||||
label: "Copy Page Link",
|
label: "Open Admin Panel",
|
||||||
icon: Link2,
|
icon: Shield,
|
||||||
keywords: ["share", "url", "clipboard", "copy", "link"],
|
keywords: ["cms", "manage", "backend"],
|
||||||
actionType: "callback",
|
actionType: "navigate",
|
||||||
actionKey: "copyPageLink",
|
href: "/admin",
|
||||||
|
roles: ["admin", "developer"],
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
id: "action:mark-all-read",
|
id: "action:mark-all-read",
|
||||||
label: "Mark All Notifications Read",
|
label: "Mark All Notifications Read",
|
||||||
|
|
@ -92,12 +93,4 @@ export const actionCommands: ActionCommand[] = [
|
||||||
actionType: "callback",
|
actionType: "callback",
|
||||||
actionKey: "logout",
|
actionKey: "logout",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: "action:create-ticket",
|
|
||||||
label: "Create Support Ticket",
|
|
||||||
icon: UserPlus,
|
|
||||||
keywords: ["help", "support", "report"],
|
|
||||||
actionType: "navigate",
|
|
||||||
href: "/helpdesk",
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -11,19 +11,6 @@ import {
|
||||||
UserCog,
|
UserCog,
|
||||||
Bell,
|
Bell,
|
||||||
Shield,
|
Shield,
|
||||||
ArrowLeftRight,
|
|
||||||
CalendarDays,
|
|
||||||
BookOpen,
|
|
||||||
FilePlus2,
|
|
||||||
Contact,
|
|
||||||
BarChart3,
|
|
||||||
FolderKanban,
|
|
||||||
Search,
|
|
||||||
Award,
|
|
||||||
Mic,
|
|
||||||
PlaneTakeoff,
|
|
||||||
Radio,
|
|
||||||
Bomb,
|
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
export interface NavigationCommand {
|
export interface NavigationCommand {
|
||||||
|
|
@ -40,13 +27,6 @@ export interface NavigationCommand {
|
||||||
export const navigationCommands: NavigationCommand[] = [
|
export const navigationCommands: NavigationCommand[] = [
|
||||||
{ id: "nav:dashboard", label: "Dashboard", icon: Bolt, href: "/", keywords: ["home", "main"], shortcut: "G D" },
|
{ id: "nav:dashboard", label: "Dashboard", icon: Bolt, href: "/", keywords: ["home", "main"], shortcut: "G D" },
|
||||||
{ id: "nav:roster", label: "Roster", icon: Users, href: "/roster", keywords: ["members", "personnel"], shortcut: "G R" },
|
{ id: "nav:roster", label: "Roster", icon: Users, href: "/roster", keywords: ["members", "personnel"], shortcut: "G R" },
|
||||||
{
|
|
||||||
id: "nav:transfers",
|
|
||||||
label: "Transfers",
|
|
||||||
icon: ArrowLeftRight,
|
|
||||||
href: "/transfers",
|
|
||||||
keywords: ["assignments", "requests", "moves"],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "nav:helpdesk",
|
id: "nav:helpdesk",
|
||||||
label: "Helpdesk",
|
label: "Helpdesk",
|
||||||
|
|
@ -55,20 +35,6 @@ export const navigationCommands: NavigationCommand[] = [
|
||||||
keywords: ["support", "tickets"],
|
keywords: ["support", "tickets"],
|
||||||
shortcut: "G H",
|
shortcut: "G H",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: "nav:projects",
|
|
||||||
label: "Projects",
|
|
||||||
icon: FolderKanban,
|
|
||||||
href: "/projects",
|
|
||||||
keywords: ["boards", "sprints", "releases"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "nav:project-search",
|
|
||||||
label: "Project Search",
|
|
||||||
icon: Search,
|
|
||||||
href: "/projects/search",
|
|
||||||
keywords: ["find", "query", "issues"],
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
id: "nav:campaigns",
|
id: "nav:campaigns",
|
||||||
|
|
@ -96,30 +62,6 @@ export const navigationCommands: NavigationCommand[] = [
|
||||||
keywords: ["groups", "teams"],
|
keywords: ["groups", "teams"],
|
||||||
requires: "intelligence",
|
requires: "intelligence",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: "nav:friday-ops",
|
|
||||||
label: "Friday Ops",
|
|
||||||
icon: CalendarDays,
|
|
||||||
href: "/intelligence/missions/calendar",
|
|
||||||
keywords: ["calendar", "side ops", "schedule", "events"],
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
id: "nav:wiki",
|
|
||||||
label: "Wiki",
|
|
||||||
icon: BookOpen,
|
|
||||||
href: "/wiki",
|
|
||||||
keywords: ["guide", "lore", "field manual", "pages"],
|
|
||||||
requires: "intelligence",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "nav:wiki-new",
|
|
||||||
label: "Create Wiki Page",
|
|
||||||
icon: FilePlus2,
|
|
||||||
href: "/wiki/new",
|
|
||||||
keywords: ["write", "new page", "documentation"],
|
|
||||||
requires: "intelligence",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
id: "nav:structures",
|
id: "nav:structures",
|
||||||
|
|
@ -182,21 +124,6 @@ export const navigationCommands: NavigationCommand[] = [
|
||||||
shortcut: "G K",
|
shortcut: "G K",
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
|
||||||
id: "nav:contacts",
|
|
||||||
label: "NPC Contacts",
|
|
||||||
icon: Contact,
|
|
||||||
href: "/personnel/contacts",
|
|
||||||
keywords: ["personnel", "directory", "npcs", "vendors"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "nav:npc-statistics",
|
|
||||||
label: "NPC Statistics",
|
|
||||||
icon: BarChart3,
|
|
||||||
href: "/personnel/statistics",
|
|
||||||
keywords: ["personnel", "stats", "vendors", "distribution"],
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
id: "nav:locker",
|
id: "nav:locker",
|
||||||
label: "Locker",
|
label: "Locker",
|
||||||
|
|
@ -205,48 +132,6 @@ export const navigationCommands: NavigationCommand[] = [
|
||||||
keywords: ["inventory", "gear", "equipment"],
|
keywords: ["inventory", "gear", "equipment"],
|
||||||
shortcut: "G L",
|
shortcut: "G L",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: "nav:awards",
|
|
||||||
label: "Awards",
|
|
||||||
icon: Award,
|
|
||||||
href: "/awards",
|
|
||||||
keywords: ["medals", "decorations", "commendations"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "nav:voiceover",
|
|
||||||
label: "Voiceover Scripts",
|
|
||||||
icon: Mic,
|
|
||||||
href: "/voiceover",
|
|
||||||
keywords: ["teleprompter", "recordings", "voice acting"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "nav:qualification-course",
|
|
||||||
label: "Qualification Course",
|
|
||||||
icon: Crosshair,
|
|
||||||
href: "/qualification",
|
|
||||||
keywords: ["aim trainer", "minigame", "shooting", "practice"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "nav:radio-traffic",
|
|
||||||
label: "Radio Traffic",
|
|
||||||
icon: Radio,
|
|
||||||
href: "/radio",
|
|
||||||
keywords: ["minigame", "comms", "messages", "training"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "nav:flight-simulator",
|
|
||||||
label: "Flight Simulator",
|
|
||||||
icon: PlaneTakeoff,
|
|
||||||
href: "/flappy",
|
|
||||||
keywords: ["minigame", "flappy", "fly", "training"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "nav:minefield-clearance",
|
|
||||||
label: "Minefield Clearance",
|
|
||||||
icon: Bomb,
|
|
||||||
href: "/eod",
|
|
||||||
keywords: ["minigame", "eod", "mines", "training"],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "nav:account",
|
id: "nav:account",
|
||||||
label: "Account",
|
label: "Account",
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const MAX_AUDIO_BYTES = 9 * 1024 * 1024;
|
const MAX_AUDIO_BYTES = 9 * 1024 * 1024;
|
||||||
|
|
@ -17,7 +16,7 @@ const PREFERRED_MIME_TYPE = "audio/webm;codecs=opus";
|
||||||
export type VoiceCaptureSource = Blob | File;
|
export type VoiceCaptureSource = Blob | File;
|
||||||
|
|
||||||
interface VoiceCaptureProps {
|
interface VoiceCaptureProps {
|
||||||
readonly onSubmitTake: (source: VoiceCaptureSource, description: string) => Promise<boolean>;
|
readonly onSubmitTake: (source: VoiceCaptureSource) => Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDuration(seconds: number): string {
|
function formatDuration(seconds: number): string {
|
||||||
|
|
@ -57,7 +56,6 @@ export function VoiceCapture({ onSubmitTake }: VoiceCaptureProps) {
|
||||||
const [reviewBlob, setReviewBlob] = useState<Blob | null>(null);
|
const [reviewBlob, setReviewBlob] = useState<Blob | null>(null);
|
||||||
const [reviewUrl, setReviewUrl] = useState<string | null>(null);
|
const [reviewUrl, setReviewUrl] = useState<string | null>(null);
|
||||||
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
||||||
const [description, setDescription] = useState("");
|
|
||||||
|
|
||||||
const stopLevelMeter = useCallback(() => {
|
const stopLevelMeter = useCallback(() => {
|
||||||
if (meterAnimationRef.current !== null) {
|
if (meterAnimationRef.current !== null) {
|
||||||
|
|
@ -223,13 +221,12 @@ export function VoiceCapture({ onSubmitTake }: VoiceCaptureProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
const submitted = await onSubmitTake(source, description.trim());
|
const submitted = await onSubmitTake(source);
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
if (!submitted) return;
|
if (!submitted) return;
|
||||||
|
|
||||||
clearReview();
|
clearReview();
|
||||||
setSelectedFile(null);
|
setSelectedFile(null);
|
||||||
setDescription("");
|
|
||||||
if (fileInputRef.current) fileInputRef.current.value = "";
|
if (fileInputRef.current) fileInputRef.current.value = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -280,19 +277,6 @@ export function VoiceCapture({ onSubmitTake }: VoiceCaptureProps) {
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex flex-col gap-5">
|
<CardContent className="flex flex-col gap-5">
|
||||||
<div className="flex flex-col gap-1.5">
|
|
||||||
<Label htmlFor="voiceover-take-description">Describe your take (optional)</Label>
|
|
||||||
<Textarea
|
|
||||||
id="voiceover-take-description"
|
|
||||||
value={description}
|
|
||||||
onChange={(event) => setDescription(event.target.value)}
|
|
||||||
maxLength={300}
|
|
||||||
rows={2}
|
|
||||||
placeholder="Anything the reviewer should know about this take."
|
|
||||||
disabled={isSubmitting || isRecording}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{support === "unsupported" && (
|
{support === "unsupported" && (
|
||||||
<div
|
<div
|
||||||
role="alert"
|
role="alert"
|
||||||
|
|
|
||||||
|
|
@ -19,17 +19,13 @@ export function VoiceoverRecorder({ script }: { readonly script: VoiceoverScript
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const isOpen = script.status === "open";
|
const isOpen = script.status === "open";
|
||||||
|
|
||||||
async function handleSubmitTake(
|
async function handleSubmitTake(source: VoiceCaptureSource): Promise<boolean> {
|
||||||
source: VoiceCaptureSource,
|
|
||||||
description: string,
|
|
||||||
): Promise<boolean> {
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
if (source instanceof File) {
|
if (source instanceof File) {
|
||||||
formData.append("file", source, source.name);
|
formData.append("file", source, source.name);
|
||||||
} else {
|
} else {
|
||||||
formData.append("file", source, "take.webm");
|
formData.append("file", source, "take.webm");
|
||||||
}
|
}
|
||||||
if (description) formData.append("description", description);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await submitVoiceRecording(script.id, formData);
|
const result = await submitVoiceRecording(script.id, formData);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { CheckIcon, Clock3Icon, MessageSquareQuoteIcon, Trash2Icon, XIcon } from "lucide-react";
|
import { CheckIcon, Clock3Icon, Trash2Icon, XIcon } from "lucide-react";
|
||||||
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
@ -81,10 +81,6 @@ export function VoiceoverTakes({ submissions, onWithdraw }: VoiceoverTakesProps)
|
||||||
</time>
|
</time>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{submission.description && (
|
|
||||||
<p className="text-sm text-muted-foreground">{submission.description}</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{submission.audioUrl ? (
|
{submission.audioUrl ? (
|
||||||
<audio
|
<audio
|
||||||
controls
|
controls
|
||||||
|
|
@ -97,18 +93,6 @@ export function VoiceoverTakes({ submissions, onWithdraw }: VoiceoverTakesProps)
|
||||||
<p className="text-sm text-muted-foreground">Audio playback is unavailable.</p>
|
<p className="text-sm text-muted-foreground">Audio playback is unavailable.</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{submission.reviewNote && (
|
|
||||||
<div className="flex gap-2 rounded-md border border-border bg-background/50 p-3">
|
|
||||||
<MessageSquareQuoteIcon className="mt-0.5 size-3.5 shrink-0 text-muted-foreground" />
|
|
||||||
<div className="flex min-w-0 flex-col gap-0.5">
|
|
||||||
<span className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
|
||||||
Reviewer feedback
|
|
||||||
</span>
|
|
||||||
<p className="text-sm">{submission.reviewNote}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{canWithdraw && (
|
{canWithdraw && (
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,6 @@ export interface VoiceoverSubmissionSummary {
|
||||||
status: string;
|
status: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
audioUrl: string;
|
audioUrl: string;
|
||||||
description: string | null;
|
|
||||||
reviewNote: string | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VoiceoverScriptView {
|
export interface VoiceoverScriptView {
|
||||||
|
|
@ -40,8 +38,6 @@ function toSubmissionSummary(submission: VoiceSubmission): VoiceoverSubmissionSu
|
||||||
status: submission.status,
|
status: submission.status,
|
||||||
createdAt: submission.createdAt,
|
createdAt: submission.createdAt,
|
||||||
audioUrl: submissionAudioUrl(submission),
|
audioUrl: submissionAudioUrl(submission),
|
||||||
description: submission.description ?? null,
|
|
||||||
reviewNote: submission.reviewNote ?? null,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,117 +0,0 @@
|
||||||
import { spawn } from "node:child_process";
|
|
||||||
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
||||||
import { tmpdir } from "node:os";
|
|
||||||
import path from "node:path";
|
|
||||||
|
|
||||||
export const MP3_MIMETYPE = "audio/mpeg";
|
|
||||||
|
|
||||||
/** Voice-appropriate MP3 settings: mono, 44.1 kHz, 96 kbps. */
|
|
||||||
const FFMPEG_ARGS = [
|
|
||||||
"-hide_banner",
|
|
||||||
"-loglevel",
|
|
||||||
"error",
|
|
||||||
"-y",
|
|
||||||
"-i",
|
|
||||||
"input",
|
|
||||||
"-vn",
|
|
||||||
"-ac",
|
|
||||||
"1",
|
|
||||||
"-ar",
|
|
||||||
"44100",
|
|
||||||
"-codec:a",
|
|
||||||
"libmp3lame",
|
|
||||||
"-b:a",
|
|
||||||
"96k",
|
|
||||||
"-f",
|
|
||||||
"mp3",
|
|
||||||
"output.mp3",
|
|
||||||
];
|
|
||||||
|
|
||||||
const FFMPEG_TIMEOUT_MS = 30_000;
|
|
||||||
|
|
||||||
export interface Mp3Result {
|
|
||||||
buffer: Buffer;
|
|
||||||
name: string;
|
|
||||||
mimetype: string;
|
|
||||||
size: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert an uploaded audio recording (WebM/Opus, MP4/AAC, etc.) to MP3 using
|
|
||||||
* the system ffmpeg binary. Returns null when ffmpeg is unavailable or fails,
|
|
||||||
* so callers can fall back to storing the original file untouched.
|
|
||||||
*/
|
|
||||||
export async function transcodeToMp3(
|
|
||||||
buffer: Buffer,
|
|
||||||
originalName: string,
|
|
||||||
): Promise<Mp3Result | null> {
|
|
||||||
let dir: string | null = null;
|
|
||||||
try {
|
|
||||||
dir = await mkdtemp(path.join(tmpdir(), "voiceover-"));
|
|
||||||
const inputPath = path.join(dir, "input");
|
|
||||||
const outputPath = path.join(dir, "output.mp3");
|
|
||||||
await writeFile(inputPath, buffer);
|
|
||||||
|
|
||||||
const ok = await runFfmpeg(inputPath, outputPath);
|
|
||||||
if (!ok) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mp3 = await readFile(outputPath);
|
|
||||||
if (mp3.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
buffer: mp3,
|
|
||||||
name: `${stripExtension(originalName)}.mp3`,
|
|
||||||
mimetype: MP3_MIMETYPE,
|
|
||||||
size: mp3.length,
|
|
||||||
};
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
} finally {
|
|
||||||
if (dir) {
|
|
||||||
await rm(dir, { recursive: true, force: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function stripExtension(name: string): string {
|
|
||||||
const dot = name.lastIndexOf(".");
|
|
||||||
return dot > 0 ? name.slice(0, dot) : name;
|
|
||||||
}
|
|
||||||
|
|
||||||
function runFfmpeg(inputPath: string, outputPath: string): Promise<boolean> {
|
|
||||||
// Rewrite the two path placeholders into the real temp paths.
|
|
||||||
const args = FFMPEG_ARGS.map((arg) =>
|
|
||||||
arg === "input" ? inputPath : arg === "output.mp3" ? outputPath : arg,
|
|
||||||
);
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
let child: ReturnType<typeof spawn>;
|
|
||||||
try {
|
|
||||||
child = spawn("ffmpeg", args, { stdio: "ignore" });
|
|
||||||
} catch {
|
|
||||||
resolve(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let settled = false;
|
|
||||||
const finish = (result: boolean) => {
|
|
||||||
if (!settled) {
|
|
||||||
settled = true;
|
|
||||||
clearTimeout(timer);
|
|
||||||
resolve(result);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const timer = setTimeout(() => {
|
|
||||||
child.kill("SIGKILL");
|
|
||||||
finish(false);
|
|
||||||
}, FFMPEG_TIMEOUT_MS);
|
|
||||||
|
|
||||||
child.on("error", () => finish(false));
|
|
||||||
child.on("close", (code) => finish(code === 0));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,11 +0,0 @@
|
||||||
import { MigrateUpArgs, MigrateDownArgs, sql } from "@payloadcms/db-postgres";
|
|
||||||
|
|
||||||
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
|
|
||||||
await db.execute(sql`
|
|
||||||
ALTER TABLE "voice_submissions" ADD COLUMN "description" varchar;`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function down({ db, payload, req }: MigrateDownArgs): Promise<void> {
|
|
||||||
await db.execute(sql`
|
|
||||||
ALTER TABLE "voice_submissions" DROP COLUMN "description";`);
|
|
||||||
}
|
|
||||||
|
|
@ -1,261 +1,255 @@
|
||||||
import * as migration_20260820_004157 from "./20260820_004157";
|
import * as migration_20260820_004157 from './20260820_004157';
|
||||||
import * as migration_20260825_220000 from "./20260825_220000";
|
import * as migration_20260825_220000 from './20260825_220000';
|
||||||
import * as migration_20260826_050000_repair_shims_auxiliary_tables from "./20260826_050000_repair_shims_auxiliary_tables";
|
import * as migration_20260826_050000_repair_shims_auxiliary_tables from './20260826_050000_repair_shims_auxiliary_tables';
|
||||||
import * as migration_20260826_060000_add_game_servers_event_target from "./20260826_060000_add_game_servers_event_target";
|
import * as migration_20260826_060000_add_game_servers_event_target from './20260826_060000_add_game_servers_event_target';
|
||||||
import * as migration_20260827_005757 from "./20260827_005757";
|
import * as migration_20260827_005757 from './20260827_005757';
|
||||||
import * as migration_20260827_022334_add_eod_minesweeper_best_flags from "./20260827_022334_add_eod_minesweeper_best_flags";
|
import * as migration_20260827_022334_add_eod_minesweeper_best_flags from './20260827_022334_add_eod_minesweeper_best_flags';
|
||||||
import * as migration_20260827_213857_add_evaluations_collection from "./20260827_213857_add_evaluations_collection";
|
import * as migration_20260827_213857_add_evaluations_collection from './20260827_213857_add_evaluations_collection';
|
||||||
import * as migration_20260827_233224_add_mission_completed_at from "./20260827_233224_add_mission_completed_at";
|
import * as migration_20260827_233224_add_mission_completed_at from './20260827_233224_add_mission_completed_at';
|
||||||
import * as migration_20260828_011500_add_background_texture_preference from "./20260828_011500_add_background_texture_preference";
|
import * as migration_20260828_011500_add_background_texture_preference from './20260828_011500_add_background_texture_preference';
|
||||||
import * as migration_20260828_041615_add_campaign_mode from "./20260828_041615_add_campaign_mode";
|
import * as migration_20260828_041615_add_campaign_mode from './20260828_041615_add_campaign_mode';
|
||||||
import * as migration_20260828_044506 from "./20260828_044506";
|
import * as migration_20260828_044506 from './20260828_044506';
|
||||||
import * as migration_20260831_142503 from "./20260831_142503";
|
import * as migration_20260831_142503 from './20260831_142503';
|
||||||
import * as migration_20260831_180233_assignment_transfers from "./20260831_180233_assignment_transfers";
|
import * as migration_20260831_180233_assignment_transfers from './20260831_180233_assignment_transfers';
|
||||||
import * as migration_20260901_014006_add_assignment_transfer_event_target from "./20260901_014006_add_assignment_transfer_event_target";
|
import * as migration_20260901_014006_add_assignment_transfer_event_target from './20260901_014006_add_assignment_transfer_event_target';
|
||||||
import * as migration_20260901_213108_add_ticket_votes_and_edit_history from "./20260901_213108_add_ticket_votes_and_edit_history";
|
import * as migration_20260901_213108_add_ticket_votes_and_edit_history from './20260901_213108_add_ticket_votes_and_edit_history';
|
||||||
import * as migration_20260901_222200_add_admin_page_permissions from "./20260901_222200_add_admin_page_permissions";
|
import * as migration_20260901_222200_add_admin_page_permissions from './20260901_222200_add_admin_page_permissions';
|
||||||
import * as migration_20260902_195336_add_medal_backgrounds from "./20260902_195336_add_medal_backgrounds";
|
import * as migration_20260902_195336_add_medal_backgrounds from './20260902_195336_add_medal_backgrounds';
|
||||||
import * as migration_20260903_035544 from "./20260903_035544";
|
import * as migration_20260903_035544 from './20260903_035544';
|
||||||
import * as migration_20260903_044047 from "./20260903_044047";
|
import * as migration_20260903_044047 from './20260903_044047';
|
||||||
import * as migration_20260903_053419 from "./20260903_053419";
|
import * as migration_20260903_053419 from './20260903_053419';
|
||||||
import * as migration_20260903_060000_add_radio_mode_stats from "./20260903_060000_add_radio_mode_stats";
|
import * as migration_20260903_060000_add_radio_mode_stats from './20260903_060000_add_radio_mode_stats';
|
||||||
import * as migration_20260903_061000_add_radio_incorrect_counts from "./20260903_061000_add_radio_incorrect_counts";
|
import * as migration_20260903_061000_add_radio_incorrect_counts from './20260903_061000_add_radio_incorrect_counts';
|
||||||
import * as migration_20260903_062000_add_aim_trainer_stats from "./20260903_062000_add_aim_trainer_stats";
|
import * as migration_20260903_062000_add_aim_trainer_stats from './20260903_062000_add_aim_trainer_stats';
|
||||||
import * as migration_20260904_230838_add_wiki_collections from "./20260904_230838_add_wiki_collections";
|
import * as migration_20260904_230838_add_wiki_collections from './20260904_230838_add_wiki_collections';
|
||||||
import * as migration_20260905_183940_base_management from "./20260905_183940_base_management";
|
import * as migration_20260905_183940_base_management from './20260905_183940_base_management';
|
||||||
import * as migration_20260905_195456_labor_headcount from "./20260905_195456_labor_headcount";
|
import * as migration_20260905_195456_labor_headcount from './20260905_195456_labor_headcount';
|
||||||
import * as migration_20260905_210310_labor_classifications from "./20260905_210310_labor_classifications";
|
import * as migration_20260905_210310_labor_classifications from './20260905_210310_labor_classifications';
|
||||||
import * as migration_20260905_213824 from "./20260905_213824";
|
import * as migration_20260905_213824 from './20260905_213824';
|
||||||
import * as migration_20260905_215658 from "./20260905_215658";
|
import * as migration_20260905_215658 from './20260905_215658';
|
||||||
import * as migration_20260906_045502 from "./20260906_045502";
|
import * as migration_20260906_045502 from './20260906_045502';
|
||||||
import * as migration_20260906_050900_add_evaluation_reminders_field from "./20260906_050900_add_evaluation_reminders_field";
|
import * as migration_20260906_050900_add_evaluation_reminders_field from './20260906_050900_add_evaluation_reminders_field';
|
||||||
import * as migration_20260906_061451_add_wiki_template_css from "./20260906_061451_add_wiki_template_css";
|
import * as migration_20260906_061451_add_wiki_template_css from './20260906_061451_add_wiki_template_css';
|
||||||
import * as migration_20260906_063753_add_personal_excerpt_richtext from "./20260906_063753_add_personal_excerpt_richtext";
|
import * as migration_20260906_063753_add_personal_excerpt_richtext from './20260906_063753_add_personal_excerpt_richtext';
|
||||||
import * as migration_20260906_065405_add_wiki_template_html from "./20260906_065405_add_wiki_template_html";
|
import * as migration_20260906_065405_add_wiki_template_html from './20260906_065405_add_wiki_template_html';
|
||||||
import * as migration_20260906_100908_add_market_economy_state from "./20260906_100908_add_market_economy_state";
|
import * as migration_20260906_100908_add_market_economy_state from './20260906_100908_add_market_economy_state';
|
||||||
import * as migration_20260906_165837_add_market_state_event_target from "./20260906_165837_add_market_state_event_target";
|
import * as migration_20260906_165837_add_market_state_event_target from './20260906_165837_add_market_state_event_target';
|
||||||
import * as migration_20260906_202925_add_voice_submissions from "./20260906_202925_add_voice_submissions";
|
import * as migration_20260906_202925_add_voice_submissions from './20260906_202925_add_voice_submissions';
|
||||||
import * as migration_20260907_012326_add_missions_created_by from "./20260907_012326_add_missions_created_by";
|
import * as migration_20260907_012326_add_missions_created_by from './20260907_012326_add_missions_created_by';
|
||||||
import * as migration_20260907_013724_make_missions_campaign_optional from "./20260907_013724_make_missions_campaign_optional";
|
import * as migration_20260907_013724_make_missions_campaign_optional from './20260907_013724_make_missions_campaign_optional';
|
||||||
import * as migration_20260907_041051_add_voice_script_spoiler from "./20260907_041051_add_voice_script_spoiler";
|
import * as migration_20260907_041051_add_voice_script_spoiler from './20260907_041051_add_voice_script_spoiler';
|
||||||
import * as migration_20260907_043339_add_voice_script_description from "./20260907_043339_add_voice_script_description";
|
import * as migration_20260907_043339_add_voice_script_description from './20260907_043339_add_voice_script_description';
|
||||||
import * as migration_20260907_044934_add_missions_discord_modlist_hash from "./20260907_044934_add_missions_discord_modlist_hash";
|
import * as migration_20260907_044934_add_missions_discord_modlist_hash from './20260907_044934_add_missions_discord_modlist_hash';
|
||||||
import * as migration_20260907_083010_add_voice_submission_description from "./20260907_083010_add_voice_submission_description";
|
|
||||||
|
|
||||||
export const migrations = [
|
export const migrations = [
|
||||||
{
|
{
|
||||||
up: migration_20260820_004157.up,
|
up: migration_20260820_004157.up,
|
||||||
down: migration_20260820_004157.down,
|
down: migration_20260820_004157.down,
|
||||||
name: "20260820_004157",
|
name: '20260820_004157',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260825_220000.up,
|
up: migration_20260825_220000.up,
|
||||||
down: migration_20260825_220000.down,
|
down: migration_20260825_220000.down,
|
||||||
name: "20260825_220000",
|
name: '20260825_220000',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260826_050000_repair_shims_auxiliary_tables.up,
|
up: migration_20260826_050000_repair_shims_auxiliary_tables.up,
|
||||||
down: migration_20260826_050000_repair_shims_auxiliary_tables.down,
|
down: migration_20260826_050000_repair_shims_auxiliary_tables.down,
|
||||||
name: "20260826_050000_repair_shims_auxiliary_tables",
|
name: '20260826_050000_repair_shims_auxiliary_tables',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260826_060000_add_game_servers_event_target.up,
|
up: migration_20260826_060000_add_game_servers_event_target.up,
|
||||||
down: migration_20260826_060000_add_game_servers_event_target.down,
|
down: migration_20260826_060000_add_game_servers_event_target.down,
|
||||||
name: "20260826_060000_add_game_servers_event_target",
|
name: '20260826_060000_add_game_servers_event_target',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260827_005757.up,
|
up: migration_20260827_005757.up,
|
||||||
down: migration_20260827_005757.down,
|
down: migration_20260827_005757.down,
|
||||||
name: "20260827_005757",
|
name: '20260827_005757',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260827_022334_add_eod_minesweeper_best_flags.up,
|
up: migration_20260827_022334_add_eod_minesweeper_best_flags.up,
|
||||||
down: migration_20260827_022334_add_eod_minesweeper_best_flags.down,
|
down: migration_20260827_022334_add_eod_minesweeper_best_flags.down,
|
||||||
name: "20260827_022334_add_eod_minesweeper_best_flags",
|
name: '20260827_022334_add_eod_minesweeper_best_flags',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260827_213857_add_evaluations_collection.up,
|
up: migration_20260827_213857_add_evaluations_collection.up,
|
||||||
down: migration_20260827_213857_add_evaluations_collection.down,
|
down: migration_20260827_213857_add_evaluations_collection.down,
|
||||||
name: "20260827_213857_add_evaluations_collection",
|
name: '20260827_213857_add_evaluations_collection',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260827_233224_add_mission_completed_at.up,
|
up: migration_20260827_233224_add_mission_completed_at.up,
|
||||||
down: migration_20260827_233224_add_mission_completed_at.down,
|
down: migration_20260827_233224_add_mission_completed_at.down,
|
||||||
name: "20260827_233224_add_mission_completed_at",
|
name: '20260827_233224_add_mission_completed_at',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260828_011500_add_background_texture_preference.up,
|
up: migration_20260828_011500_add_background_texture_preference.up,
|
||||||
down: migration_20260828_011500_add_background_texture_preference.down,
|
down: migration_20260828_011500_add_background_texture_preference.down,
|
||||||
name: "20260828_011500_add_background_texture_preference",
|
name: '20260828_011500_add_background_texture_preference',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260828_041615_add_campaign_mode.up,
|
up: migration_20260828_041615_add_campaign_mode.up,
|
||||||
down: migration_20260828_041615_add_campaign_mode.down,
|
down: migration_20260828_041615_add_campaign_mode.down,
|
||||||
name: "20260828_041615_add_campaign_mode",
|
name: '20260828_041615_add_campaign_mode',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260828_044506.up,
|
up: migration_20260828_044506.up,
|
||||||
down: migration_20260828_044506.down,
|
down: migration_20260828_044506.down,
|
||||||
name: "20260828_044506",
|
name: '20260828_044506',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260831_142503.up,
|
up: migration_20260831_142503.up,
|
||||||
down: migration_20260831_142503.down,
|
down: migration_20260831_142503.down,
|
||||||
name: "20260831_142503",
|
name: '20260831_142503',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260831_180233_assignment_transfers.up,
|
up: migration_20260831_180233_assignment_transfers.up,
|
||||||
down: migration_20260831_180233_assignment_transfers.down,
|
down: migration_20260831_180233_assignment_transfers.down,
|
||||||
name: "20260831_180233_assignment_transfers",
|
name: '20260831_180233_assignment_transfers',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260901_014006_add_assignment_transfer_event_target.up,
|
up: migration_20260901_014006_add_assignment_transfer_event_target.up,
|
||||||
down: migration_20260901_014006_add_assignment_transfer_event_target.down,
|
down: migration_20260901_014006_add_assignment_transfer_event_target.down,
|
||||||
name: "20260901_014006_add_assignment_transfer_event_target",
|
name: '20260901_014006_add_assignment_transfer_event_target',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260901_213108_add_ticket_votes_and_edit_history.up,
|
up: migration_20260901_213108_add_ticket_votes_and_edit_history.up,
|
||||||
down: migration_20260901_213108_add_ticket_votes_and_edit_history.down,
|
down: migration_20260901_213108_add_ticket_votes_and_edit_history.down,
|
||||||
name: "20260901_213108_add_ticket_votes_and_edit_history",
|
name: '20260901_213108_add_ticket_votes_and_edit_history',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260901_222200_add_admin_page_permissions.up,
|
up: migration_20260901_222200_add_admin_page_permissions.up,
|
||||||
down: migration_20260901_222200_add_admin_page_permissions.down,
|
down: migration_20260901_222200_add_admin_page_permissions.down,
|
||||||
name: "20260901_222200_add_admin_page_permissions",
|
name: '20260901_222200_add_admin_page_permissions',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260902_195336_add_medal_backgrounds.up,
|
up: migration_20260902_195336_add_medal_backgrounds.up,
|
||||||
down: migration_20260902_195336_add_medal_backgrounds.down,
|
down: migration_20260902_195336_add_medal_backgrounds.down,
|
||||||
name: "20260902_195336_add_medal_backgrounds",
|
name: '20260902_195336_add_medal_backgrounds',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260903_035544.up,
|
up: migration_20260903_035544.up,
|
||||||
down: migration_20260903_035544.down,
|
down: migration_20260903_035544.down,
|
||||||
name: "20260903_035544",
|
name: '20260903_035544',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260903_044047.up,
|
up: migration_20260903_044047.up,
|
||||||
down: migration_20260903_044047.down,
|
down: migration_20260903_044047.down,
|
||||||
name: "20260903_044047",
|
name: '20260903_044047',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260903_053419.up,
|
up: migration_20260903_053419.up,
|
||||||
down: migration_20260903_053419.down,
|
down: migration_20260903_053419.down,
|
||||||
name: "20260903_053419",
|
name: '20260903_053419',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260903_060000_add_radio_mode_stats.up,
|
up: migration_20260903_060000_add_radio_mode_stats.up,
|
||||||
down: migration_20260903_060000_add_radio_mode_stats.down,
|
down: migration_20260903_060000_add_radio_mode_stats.down,
|
||||||
name: "20260903_060000_add_radio_mode_stats",
|
name: '20260903_060000_add_radio_mode_stats',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260903_061000_add_radio_incorrect_counts.up,
|
up: migration_20260903_061000_add_radio_incorrect_counts.up,
|
||||||
down: migration_20260903_061000_add_radio_incorrect_counts.down,
|
down: migration_20260903_061000_add_radio_incorrect_counts.down,
|
||||||
name: "20260903_061000_add_radio_incorrect_counts",
|
name: '20260903_061000_add_radio_incorrect_counts',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260903_062000_add_aim_trainer_stats.up,
|
up: migration_20260903_062000_add_aim_trainer_stats.up,
|
||||||
down: migration_20260903_062000_add_aim_trainer_stats.down,
|
down: migration_20260903_062000_add_aim_trainer_stats.down,
|
||||||
name: "20260903_062000_add_aim_trainer_stats",
|
name: '20260903_062000_add_aim_trainer_stats',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260904_230838_add_wiki_collections.up,
|
up: migration_20260904_230838_add_wiki_collections.up,
|
||||||
down: migration_20260904_230838_add_wiki_collections.down,
|
down: migration_20260904_230838_add_wiki_collections.down,
|
||||||
name: "20260904_230838_add_wiki_collections",
|
name: '20260904_230838_add_wiki_collections',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260905_183940_base_management.up,
|
up: migration_20260905_183940_base_management.up,
|
||||||
down: migration_20260905_183940_base_management.down,
|
down: migration_20260905_183940_base_management.down,
|
||||||
name: "20260905_183940_base_management",
|
name: '20260905_183940_base_management',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260905_195456_labor_headcount.up,
|
up: migration_20260905_195456_labor_headcount.up,
|
||||||
down: migration_20260905_195456_labor_headcount.down,
|
down: migration_20260905_195456_labor_headcount.down,
|
||||||
name: "20260905_195456_labor_headcount",
|
name: '20260905_195456_labor_headcount',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260905_210310_labor_classifications.up,
|
up: migration_20260905_210310_labor_classifications.up,
|
||||||
down: migration_20260905_210310_labor_classifications.down,
|
down: migration_20260905_210310_labor_classifications.down,
|
||||||
name: "20260905_210310_labor_classifications",
|
name: '20260905_210310_labor_classifications',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260905_213824.up,
|
up: migration_20260905_213824.up,
|
||||||
down: migration_20260905_213824.down,
|
down: migration_20260905_213824.down,
|
||||||
name: "20260905_213824",
|
name: '20260905_213824',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260905_215658.up,
|
up: migration_20260905_215658.up,
|
||||||
down: migration_20260905_215658.down,
|
down: migration_20260905_215658.down,
|
||||||
name: "20260905_215658",
|
name: '20260905_215658',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260906_045502.up,
|
up: migration_20260906_045502.up,
|
||||||
down: migration_20260906_045502.down,
|
down: migration_20260906_045502.down,
|
||||||
name: "20260906_045502",
|
name: '20260906_045502',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260906_050900_add_evaluation_reminders_field.up,
|
up: migration_20260906_050900_add_evaluation_reminders_field.up,
|
||||||
down: migration_20260906_050900_add_evaluation_reminders_field.down,
|
down: migration_20260906_050900_add_evaluation_reminders_field.down,
|
||||||
name: "20260906_050900_add_evaluation_reminders_field",
|
name: '20260906_050900_add_evaluation_reminders_field',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260906_061451_add_wiki_template_css.up,
|
up: migration_20260906_061451_add_wiki_template_css.up,
|
||||||
down: migration_20260906_061451_add_wiki_template_css.down,
|
down: migration_20260906_061451_add_wiki_template_css.down,
|
||||||
name: "20260906_061451_add_wiki_template_css",
|
name: '20260906_061451_add_wiki_template_css',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260906_063753_add_personal_excerpt_richtext.up,
|
up: migration_20260906_063753_add_personal_excerpt_richtext.up,
|
||||||
down: migration_20260906_063753_add_personal_excerpt_richtext.down,
|
down: migration_20260906_063753_add_personal_excerpt_richtext.down,
|
||||||
name: "20260906_063753_add_personal_excerpt_richtext",
|
name: '20260906_063753_add_personal_excerpt_richtext',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260906_065405_add_wiki_template_html.up,
|
up: migration_20260906_065405_add_wiki_template_html.up,
|
||||||
down: migration_20260906_065405_add_wiki_template_html.down,
|
down: migration_20260906_065405_add_wiki_template_html.down,
|
||||||
name: "20260906_065405_add_wiki_template_html",
|
name: '20260906_065405_add_wiki_template_html',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260906_100908_add_market_economy_state.up,
|
up: migration_20260906_100908_add_market_economy_state.up,
|
||||||
down: migration_20260906_100908_add_market_economy_state.down,
|
down: migration_20260906_100908_add_market_economy_state.down,
|
||||||
name: "20260906_100908_add_market_economy_state",
|
name: '20260906_100908_add_market_economy_state',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260906_165837_add_market_state_event_target.up,
|
up: migration_20260906_165837_add_market_state_event_target.up,
|
||||||
down: migration_20260906_165837_add_market_state_event_target.down,
|
down: migration_20260906_165837_add_market_state_event_target.down,
|
||||||
name: "20260906_165837_add_market_state_event_target",
|
name: '20260906_165837_add_market_state_event_target',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260906_202925_add_voice_submissions.up,
|
up: migration_20260906_202925_add_voice_submissions.up,
|
||||||
down: migration_20260906_202925_add_voice_submissions.down,
|
down: migration_20260906_202925_add_voice_submissions.down,
|
||||||
name: "20260906_202925_add_voice_submissions",
|
name: '20260906_202925_add_voice_submissions',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260907_012326_add_missions_created_by.up,
|
up: migration_20260907_012326_add_missions_created_by.up,
|
||||||
down: migration_20260907_012326_add_missions_created_by.down,
|
down: migration_20260907_012326_add_missions_created_by.down,
|
||||||
name: "20260907_012326_add_missions_created_by",
|
name: '20260907_012326_add_missions_created_by',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260907_013724_make_missions_campaign_optional.up,
|
up: migration_20260907_013724_make_missions_campaign_optional.up,
|
||||||
down: migration_20260907_013724_make_missions_campaign_optional.down,
|
down: migration_20260907_013724_make_missions_campaign_optional.down,
|
||||||
name: "20260907_013724_make_missions_campaign_optional",
|
name: '20260907_013724_make_missions_campaign_optional',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260907_041051_add_voice_script_spoiler.up,
|
up: migration_20260907_041051_add_voice_script_spoiler.up,
|
||||||
down: migration_20260907_041051_add_voice_script_spoiler.down,
|
down: migration_20260907_041051_add_voice_script_spoiler.down,
|
||||||
name: "20260907_041051_add_voice_script_spoiler",
|
name: '20260907_041051_add_voice_script_spoiler',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260907_043339_add_voice_script_description.up,
|
up: migration_20260907_043339_add_voice_script_description.up,
|
||||||
down: migration_20260907_043339_add_voice_script_description.down,
|
down: migration_20260907_043339_add_voice_script_description.down,
|
||||||
name: "20260907_043339_add_voice_script_description",
|
name: '20260907_043339_add_voice_script_description',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_20260907_044934_add_missions_discord_modlist_hash.up,
|
up: migration_20260907_044934_add_missions_discord_modlist_hash.up,
|
||||||
down: migration_20260907_044934_add_missions_discord_modlist_hash.down,
|
down: migration_20260907_044934_add_missions_discord_modlist_hash.down,
|
||||||
name: "20260907_044934_add_missions_discord_modlist_hash",
|
name: '20260907_044934_add_missions_discord_modlist_hash'
|
||||||
},
|
|
||||||
{
|
|
||||||
up: migration_20260907_083010_add_voice_submission_description.up,
|
|
||||||
down: migration_20260907_083010_add_voice_submission_description.down,
|
|
||||||
name: "20260907_083010_add_voice_submission_description",
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -598,7 +598,7 @@ export interface Structure {
|
||||||
*/
|
*/
|
||||||
maxCapacityMass?: number | null;
|
maxCapacityMass?: number | null;
|
||||||
/**
|
/**
|
||||||
* When enabled, ONLY the items listed in Allowed Storage can be stored here. Everything else is rejected. When disabled, the Allowed Storage list still enforces per-item quantity caps, but other items are permitted up to the mass capacity.
|
* When enabled, ONLY the items listed in Allowed Storage can be stored here — everything else is rejected. When disabled, the Allowed Storage list still enforces per-item quantity caps, but other items are permitted up to the mass capacity.
|
||||||
*/
|
*/
|
||||||
restrictToAllowed?: boolean | null;
|
restrictToAllowed?: boolean | null;
|
||||||
xSize?: number | null;
|
xSize?: number | null;
|
||||||
|
|
@ -1315,7 +1315,7 @@ export interface User {
|
||||||
payloadDisplayName?: string | null;
|
payloadDisplayName?: string | null;
|
||||||
steamId: string;
|
steamId: string;
|
||||||
/**
|
/**
|
||||||
* Legacy role enum, being replaced by the dynamic RBAC system (see 'Role Assignments' below).
|
* Legacy role enum — being replaced by the dynamic RBAC system (see 'Role Assignments' below).
|
||||||
*/
|
*/
|
||||||
roles?: ('guest' | 'user' | 'trusted' | 'admin' | 'developer')[] | null;
|
roles?: ('guest' | 'user' | 'trusted' | 'admin' | 'developer')[] | null;
|
||||||
/**
|
/**
|
||||||
|
|
@ -1693,7 +1693,7 @@ export interface Role {
|
||||||
*/
|
*/
|
||||||
isSystem?: boolean | null;
|
isSystem?: boolean | null;
|
||||||
/**
|
/**
|
||||||
* Superuser roles bypass ALL permission checks. Use with extreme caution: this grants unrestricted access.
|
* Superuser roles bypass ALL permission checks. Use with extreme caution — this grants unrestricted access.
|
||||||
*/
|
*/
|
||||||
isSuperuser?: boolean | null;
|
isSuperuser?: boolean | null;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
|
|
@ -2330,7 +2330,7 @@ export interface UserNotification {
|
||||||
export interface Evaluation {
|
export interface Evaluation {
|
||||||
id: number;
|
id: number;
|
||||||
/**
|
/**
|
||||||
* Who submitted this evaluation. Private, only visible to users with evaluations:read.
|
* Who submitted this evaluation. Private — only visible to users with evaluations:read.
|
||||||
*/
|
*/
|
||||||
rater: number | User;
|
rater: number | User;
|
||||||
ratee: number | User;
|
ratee: number | User;
|
||||||
|
|
@ -2405,7 +2405,7 @@ export interface Mission {
|
||||||
status: 'Concept' | 'Planning' | 'Ready' | 'Scheduled' | 'Active' | 'Completed' | 'Cancelled';
|
status: 'Concept' | 'Planning' | 'Ready' | 'Scheduled' | 'Active' | 'Completed' | 'Cancelled';
|
||||||
visibility: 'unit' | 'leadership' | 'intel' | 'private';
|
visibility: 'unit' | 'leadership' | 'intel' | 'private';
|
||||||
/**
|
/**
|
||||||
* When the mission transitioned to Completed. Set automatically by the lifecycle (auto-complete tick or manual status change); managed here, not editable in the form.
|
* When the mission transitioned to Completed. Set automatically by the lifecycle (auto-complete tick or manual status change) — managed here, not editable in the form.
|
||||||
*/
|
*/
|
||||||
completedAt?: string | null;
|
completedAt?: string | null;
|
||||||
};
|
};
|
||||||
|
|
@ -3130,13 +3130,9 @@ export interface VoiceSubmission {
|
||||||
* Submitter
|
* Submitter
|
||||||
*/
|
*/
|
||||||
user: number | User;
|
user: number | User;
|
||||||
/**
|
|
||||||
* Optional short note from the submitter about this take.
|
|
||||||
*/
|
|
||||||
description?: string | null;
|
|
||||||
status: 'pending' | 'accepted' | 'rejected';
|
status: 'pending' | 'accepted' | 'rejected';
|
||||||
/**
|
/**
|
||||||
* Optional reviewer feedback. Shown to the submitter alongside their take.
|
* Optional admin review note.
|
||||||
*/
|
*/
|
||||||
reviewNote?: string | null;
|
reviewNote?: string | null;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
|
|
@ -3476,11 +3472,11 @@ export interface WikiTemplate {
|
||||||
body: string;
|
body: string;
|
||||||
description?: string | null;
|
description?: string | null;
|
||||||
/**
|
/**
|
||||||
* Optional CSS injected on wiki pages while this template exists. Write plain selectors, which are automatically prefixed with a hash-based scope class (e.g. .ptf-tpl-3afb1c .my-class) so templates never collide with each other or the app shell.
|
* Optional CSS injected on wiki pages while this template exists. Write plain selectors — they are automatically prefixed with a hash-based scope class (e.g. .ptf-tpl-3afb1c .my-class) so templates never collide with each other or the app shell.
|
||||||
*/
|
*/
|
||||||
css?: string | null;
|
css?: string | null;
|
||||||
/**
|
/**
|
||||||
* Developer-only. Raw HTML rendered verbatim (sanitized) when this template is transcluded, replacing the markdown body.
|
* Developer-only. Raw HTML rendered verbatim (sanitized) when this template is transcluded — replaces the markdown body.
|
||||||
*/
|
*/
|
||||||
html?: string | null;
|
html?: string | null;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
|
|
@ -5239,7 +5235,6 @@ export interface VoiceScriptsSelect<T extends boolean = true> {
|
||||||
export interface VoiceSubmissionsSelect<T extends boolean = true> {
|
export interface VoiceSubmissionsSelect<T extends boolean = true> {
|
||||||
script?: T;
|
script?: T;
|
||||||
user?: T;
|
user?: T;
|
||||||
description?: T;
|
|
||||||
status?: T;
|
status?: T;
|
||||||
reviewNote?: T;
|
reviewNote?: T;
|
||||||
updatedAt?: T;
|
updatedAt?: T;
|
||||||
|
|
@ -5689,7 +5684,7 @@ export interface GameRule {
|
||||||
*/
|
*/
|
||||||
coldStartObservations?: number | null;
|
coldStartObservations?: number | null;
|
||||||
/**
|
/**
|
||||||
* Suggested economy tick cadence in minutes, for cron documentation only. The tick itself is triggered externally.
|
* Suggested economy tick cadence in minutes, for cron documentation only — the tick itself is triggered externally.
|
||||||
*/
|
*/
|
||||||
tickIntervalHintMinutes?: number | null;
|
tickIntervalHintMinutes?: number | null;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue