Compare commits

...

22 Commits

Author SHA1 Message Date
Eric Allam 6881de84ab Fix pnpm lock file 2023-10-26 20:04:03 +01:00
github-actions[bot] 0ca8296b9a chore: Update version for release (#686)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-10-26 20:03:37 +01:00
Eric Allam 55946fcdb7 Update @trigger.dev/nextjs to support Next.js 14 (#690) 2023-10-26 19:45:19 +01:00
Eric Allam c0f9f10b07 Upgrade to Remix v2.1.0 2023-10-26 15:54:21 +01:00
Eric Allam e80aa2826d @trigger.dev/express: Remove web-fetch dependency and use native node Request/Response 2023-10-26 13:42:20 +01:00
NIXEY 1be25d7639 BlankRow centered (#678) 2023-10-26 13:35:15 +01:00
ROMIT PAL 3c3a5e1f28 added a missing CODE OF CONDUCT file (#677)
Incorporating a Code of Conduct in your GitHub repository is crucial for promoting an inclusive, respectful community. It establishes guidelines, minimizes conflicts, safeguards participants, and adheres to legal standards. Additionally, it demonstrates professionalism and aligns with GitHub's best practices, contributing to a welcoming and harmonious environment, which, in turn, enhances collaboration and the project's overall success.
2023-10-26 13:30:14 +01:00
Vineet J Karni 8e741c7218 Added warning ⚠️ - No support for long-running servers (#669) 2023-10-26 13:18:28 +01:00
Hemachandar 83455e3ad4 feat: implement API key regeneration (#649) 2023-10-26 13:15:50 +01:00
Ronit Panda f0f0420280 feat: adds helm charts (#636)
* feat: adds helm charts template and adds postgres deps

* feat: adds all depl (barebones)

* feat: adds all depl (barebones)

* fix: removes print

* fix: image pulling issue

* fix: templatisation complete

* style: cleanup

* feat: works

* feat: adds ingress and some docs

* fix: ingress bug --> routing to cluster works

* feat: completes docs v1 todo --> needs hosting of the helm chart on chosen platform
2023-10-26 11:50:59 +01:00
Hemachandar c4533c36cb fix: set error message in runTask & executeJob (#634)
* fix: set error message in runTask & executeJob

* add changeset
2023-10-26 11:46:09 +01:00
Eric Allam f8e034c190 Fixed import that was causing typecheck to fail 2023-10-26 11:43:23 +01:00
Eric Allam 21f62e780c Added tests and refactored response to use camelCase 2023-10-26 11:30:49 +01:00
hmacr c3564ca0d4 fix doc 2023-10-26 11:18:11 +01:00
hmacr 6e1b8a11d4 feat: allow cancelling job runs for an event-id 2023-10-26 11:18:11 +01:00
Matt Aitken 9900f19b70 Migration: Updates any Endpoint runChunkExecutionLimits that were 60ms to be 60000ms 2023-10-25 15:50:11 +01:00
Matt Aitken 237545754f Indexing errors weren’t being serialized to JSON properly in some cases, so the errors weren’t getting inserted
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 1s
🚀 Publish Trigger.dev Docker / e2e (push) Failing after 0s
🚀 Publish Trigger.dev Docker / units (push) Failing after 21s
🚀 Publish Trigger.dev Docker / publish (push) Has been skipped
2023-10-24 18:08:46 +01:00
Eric Allam d7c1e98e5e Update pnpm lock 2023-10-24 17:49:39 +01:00
github-actions[bot] 71a0afeb8d chore: Update version for release (#684)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-10-24 17:49:06 +01:00
Eric Allam 36a390449a @trigger.dev/openai: Upgrade to openai-node SDK 4.13.0, add support for request options and defaultQuery/defaultHeaders 2023-10-24 17:45:43 +01:00
James Ritchie 375e495423 Fixed accounts table not scrolling in the admin area 2023-10-24 17:34:33 +01:00
D-K-P 866c20cd39 Updated the Slack underlying client example 2023-10-24 16:26:14 +01:00
208 changed files with 3314 additions and 1251 deletions
+43
View File
@@ -0,0 +1,43 @@
# Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery and unwelcome sexual attention or advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic address, without explicit permission
- Other conduct that could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned with this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project email address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the Contributor Covenant, version 2.0, available at [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html](https://www.contributor-covenant.org/version/2/0/code_of_conduct.html).
+2 -1
View File
@@ -36,7 +36,8 @@ export function Feedback({ button, defaultValue = "bug" }: FeedbackProps) {
const [form, { path, feedbackType, message }] = useForm({
id: "accept-invite",
lastSubmission,
// TODO: type this
lastSubmission: lastSubmission as any,
onValidate({ formData }) {
return parse(formData, { schema });
},
@@ -0,0 +1,107 @@
import { Button } from "../primitives/Buttons";
import { Dialog, DialogContent, DialogTrigger } from "~/components/primitives/Dialog";
import { Header1, Header2 } from "../primitives/Headers";
import { Input } from "../primitives/Input";
import { NamedIcon } from "../primitives/NamedIcon";
import { Paragraph } from "../primitives/Paragraph";
import { Spinner } from "../primitives/Spinner";
import { useState } from "react";
import { useFetcher } from "@remix-run/react";
import { generateTwoRandomWords } from "~/utils/randomWords";
type ModalProps = {
id: string;
title: string;
};
type ModalContentProps = ModalProps & {
randomWord: string;
closeModal: () => void;
};
export function RegenerateApiKeyModal({ id, title }: ModalProps) {
const randomWord = generateTwoRandomWords();
const [open, setOpen] = useState(false);
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button variant="tertiary/small" leadingIconClassName="text-dimmed" LeadingIcon="key">
Regenerate
</Button>
</DialogTrigger>
<DialogContent>
<RegenerateApiKeyModalContent
id={id}
title={title}
randomWord={randomWord}
closeModal={() => setOpen(false)}
/>
</DialogContent>
</Dialog>
);
}
const RegenerateApiKeyModalContent = ({ id, title, randomWord, closeModal }: ModalContentProps) => {
const [confirmationText, setConfirmationText] = useState("");
const fetcher = useFetcher();
const isSubmitting = fetcher.state === "submitting";
// form submission completed
if (fetcher.state === "loading") {
closeModal();
}
return (
<div className="flex w-full flex-col items-center gap-y-6">
<div className="flex justify-center">
<Header1>{title} Environment</Header1>
</div>
<Header2 className="rounded border border-rose-500 bg-rose-500/10 px-3.5 py-2 text-center text-bright">
Are you sure you want to regenerate the keys <br />
for this environment?
</Header2>
<Paragraph variant="small" className="px-6 text-center">
<>
This will temporarily break any live jobs in the
<span className="strong text-bright"> {title} Environment</span> until the new API keys
are set in the relevant environment variables.
</>
</Paragraph>
<fetcher.Form
method="post"
action={`/resources/environments/${id}/regenerate-api-key`}
className="mt-2 flex w-full flex-col items-center gap-2"
>
<Paragraph variant="small" className="font-medium">
To confirm, please enter the text:{" "}
<span className="font-bold text-bright">{randomWord}</span>
</Paragraph>
<Input
type="text"
placeholder="Confirmation Text"
fullWidth={false}
value={confirmationText}
onChange={(e) => setConfirmationText(e.target.value)}
/>
<Button
variant="primary/large"
fullWidth
className="mt-4"
disabled={confirmationText !== randomWord}
>
{isSubmitting ? (
<Spinner color="white" />
) : (
<>
<NamedIcon
name="key"
className="mr-1.5 h-4 w-4 text-bright transition group-hover:text-bright"
/>
Regenerate API Keys
</>
)}
</Button>
</fetcher.Form>
</div>
);
};
@@ -46,7 +46,8 @@ export function ConnectToOAuthForm({
const [form, { title, slug, scopes, hasCustomClient, customClientId, customClientSecret }] =
useForm({
lastSubmission: fetcher.data,
// TODO: type this
lastSubmission: fetcher.data as any,
shouldRevalidate: "onSubmit",
onValidate({ formData }) {
return parse(formData, {
@@ -45,7 +45,8 @@ export function UpdateOAuthForm({
const { isManagedCloud } = useFeatures();
const [form, { title, scopes, hasCustomClient, customClientId, customClientSecret }] = useForm({
lastSubmission: fetcher.data,
// TODO: type this
lastSubmission: fetcher.data as any,
onValidate({ formData }) {
return parse(formData, {
schema,
@@ -1,11 +1,12 @@
import { RouteMatch, useMatches } from "@remix-run/react";
import { UIMatch, useMatches } from "@remix-run/react";
import { Fragment, ReactNode } from "react";
import { BreadcrumbIcon } from "../primitives/BreadcrumbIcon";
import { Handle } from "~/utils/handle";
export type BreadcrumbItem = (match: RouteMatch, allMatches: RouteMatch[]) => ReactNode;
export type BreadcrumbItem = (match: UIMatch, allMatches: UIMatch[]) => ReactNode;
export function Breadcrumb() {
const matches = useMatches();
const matches = useMatches() as UIMatch<unknown, Handle>[];
return (
<div className="hidden items-center md:flex">
@@ -1,4 +1,4 @@
import { Link, RouteMatch } from "@remix-run/react";
import { Link, UIMatch } from "@remix-run/react";
import { useState } from "react";
import { useJob } from "~/hooks/useJob";
import { useOrganization } from "~/hooks/useOrganizations";
@@ -15,7 +15,7 @@ import {
PopoverSectionHeader,
} from "../primitives/Popover";
export function JobsMenu({ matches }: { matches: RouteMatch[] }) {
export function JobsMenu({ matches }: { matches: UIMatch[] }) {
const [isOpen, setIsOpen] = useState(false);
const organization = useOrganization(matches);
const project = useProject(matches);
@@ -1,4 +1,4 @@
import { RouteMatch } from "@remix-run/react";
import { UIMatch } from "@remix-run/react";
import { Fragment, useState } from "react";
import simplur from "simplur";
import { Badge } from "~/components/primitives/Badge";
@@ -13,7 +13,7 @@ import {
PopoverSectionHeader,
} from "../primitives/Popover";
export function ProjectsMenu({ matches }: { matches: RouteMatch[] }) {
export function ProjectsMenu({ matches }: { matches: UIMatch[] }) {
const [isOpen, setIsOpen] = useState(false);
const organizations = useOrganizations(matches);
const isNewOrgPage = useIsNewOrganizationPage(matches);
@@ -313,7 +313,8 @@ function RerunPopover({
const [form, { successRedirect }] = useForm({
id: "rerun",
lastSubmission,
// TODO: type this
lastSubmission: lastSubmission as any,
onValidate({ formData }) {
return parse(formData, { schema });
},
@@ -386,7 +387,8 @@ export function CancelRun({ runId }: { runId: string }) {
const [form, { redirectUrl }] = useForm({
id: "cancel-run",
lastSubmission,
// TODO: type this
lastSubmission: lastSubmission as any,
onValidate({ formData }) {
return parse(formData, { schema: cancelSchema });
},
@@ -28,7 +28,7 @@ import {
} from "../primitives/Table";
import { TaskAttemptStatusLabel } from "./TaskAttemptStatus";
import { TaskStatusIcon } from "./TaskStatus";
import { ClientOnly } from "remix-utils";
import { ClientOnly } from "remix-utils/client-only";
import { Spinner } from "../primitives/Spinner";
import type { DetailedTask } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam.tasks.$taskParam/route";
+80 -56
View File
@@ -1,6 +1,9 @@
import { H } from "@highlight-run/node";
import type { DataFunctionArgs, EntryContext, Headers } from "@remix-run/node"; // or cloudflare/deno
import { Response } from "@remix-run/node"; // or cloudflare/deno
import {
createReadableStreamFromReadable,
type DataFunctionArgs,
type EntryContext,
} from "@remix-run/node"; // or cloudflare/deno
import { RemixServer } from "@remix-run/react";
import { parseAcceptLanguage } from "intl-parse-accept-language";
import isbot from "isbot";
@@ -36,7 +39,7 @@ export default function handleRequest(
// response to render before sending it to the client. This
// ensures that bots can see the full page content.
if (isbot(request.headers.get("user-agent"))) {
return serveTheBots(
return handleBotRequest(
request,
responseStatusCode,
responseHeaders,
@@ -46,7 +49,7 @@ export default function handleRequest(
);
}
return serveBrowsers(
return handleBrowserRequest(
request,
responseStatusCode,
responseHeaders,
@@ -56,7 +59,7 @@ export default function handleRequest(
);
}
function serveTheBots(
function handleBotRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
@@ -65,76 +68,97 @@ function serveTheBots(
platform: OperatingSystemPlatform
) {
return new Promise((resolve, reject) => {
let shellRendered = false;
const { pipe, abort } = renderToPipeableStream(
<OperatingSystemContextProvider platform={platform}>
<LocaleContextProvider locales={locales}>
<RemixServer context={remixContext} url={request.url} abortDelay={ABORT_DELAY} />
<RemixServer context={remixContext} url={request.url} abortDelay={ABORT_DELAY} />,
</LocaleContextProvider>
</OperatingSystemContextProvider>,
{
// Use onAllReady to wait for the entire document to be ready
onAllReady() {
responseHeaders.set("Content-Type", "text/html; charset=utf-8");
let body = new PassThrough();
pipe(body);
resolve(
new Response(body, {
status: responseStatusCode,
headers: responseHeaders,
})
);
},
onShellError(err: unknown) {
reject(err);
},
}
);
setTimeout(abort, ABORT_DELAY);
});
}
shellRendered = true;
const body = new PassThrough();
const stream = createReadableStreamFromReadable(body);
responseHeaders.set("Content-Type", "text/html");
function serveBrowsers(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext,
locales: string[],
platform: OperatingSystemPlatform
) {
return new Promise((resolve, reject) => {
let didError = false;
let shellReady = false;
const { pipe, abort } = renderToPipeableStream(
<OperatingSystemContextProvider platform={platform}>
<LocaleContextProvider locales={locales}>
<RemixServer context={remixContext} url={request.url} abortDelay={ABORT_DELAY} />
</LocaleContextProvider>
</OperatingSystemContextProvider>,
{
// use onShellReady to wait until a suspense boundary is triggered
onShellReady() {
shellReady = true;
responseHeaders.set("Content-Type", "text/html; charset=utf-8");
let body = new PassThrough();
pipe(body);
resolve(
new Response(body, {
status: didError ? 500 : responseStatusCode,
new Response(stream, {
headers: responseHeaders,
status: responseStatusCode,
})
);
pipe(body);
},
onShellError(err: unknown) {
reject(err);
onShellError(error: unknown) {
reject(error);
},
onError(error: unknown) {
didError = true;
if (shellReady) {
logError(error, request);
responseStatusCode = 500;
// Log streaming rendering errors from inside the shell. Don't log
// errors encountered during initial shell rendering since they'll
// reject and get logged in handleDocumentRequest.
if (shellRendered) {
console.error(error);
}
},
}
);
setTimeout(abort, ABORT_DELAY);
});
}
function handleBrowserRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext,
locales: string[],
platform: OperatingSystemPlatform
) {
return new Promise((resolve, reject) => {
let shellRendered = false;
const { pipe, abort } = renderToPipeableStream(
<OperatingSystemContextProvider platform={platform}>
<LocaleContextProvider locales={locales}>
<RemixServer context={remixContext} url={request.url} abortDelay={ABORT_DELAY} />
</LocaleContextProvider>
</OperatingSystemContextProvider>,
{
onShellReady() {
shellRendered = true;
const body = new PassThrough();
const stream = createReadableStreamFromReadable(body);
responseHeaders.set("Content-Type", "text/html");
resolve(
new Response(stream, {
headers: responseHeaders,
status: responseStatusCode,
})
);
pipe(body);
},
onShellError(error: unknown) {
reject(error);
},
onError(error: unknown) {
responseStatusCode = 500;
// Log streaming rendering errors from inside the shell. Don't log
// errors encountered during initial shell rendering since they'll
// reject and get logged in handleDocumentRequest.
if (shellRendered) {
console.error(error);
}
},
}
);
setTimeout(abort, ABORT_DELAY);
});
}
+4 -4
View File
@@ -1,17 +1,17 @@
import { RouteMatch } from "@remix-run/react";
import { UIMatch } from "@remix-run/react";
import { MatchedProject, useOptionalProject } from "./useProject";
import { useUser } from "./useUser";
export type ProjectJobEnvironment = MatchedProject["environments"][number];
export function useEnvironments(matches?: RouteMatch[]) {
export function useEnvironments(matches?: UIMatch[]) {
const project = useOptionalProject(matches);
if (!project) return;
return project.environments;
}
export function useDevEnvironment(matches?: RouteMatch[]) {
export function useDevEnvironment(matches?: UIMatch[]) {
const user = useUser();
const environments = useEnvironments(matches);
if (!environments) return;
@@ -21,7 +21,7 @@ export function useDevEnvironment(matches?: RouteMatch[]) {
);
}
export function useProdEnvironment(matches?: RouteMatch[]) {
export function useProdEnvironment(matches?: UIMatch[]) {
const environments = useEnvironments(matches);
if (!environments) return;
@@ -1,4 +1,4 @@
import { RouteMatch } from "@remix-run/react";
import { UIMatch } from "@remix-run/react";
import { UseDataFunctionReturn } from "remix-typedjson";
import invariant from "tiny-invariant";
import type { loader } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam/route";
@@ -6,7 +6,7 @@ import { useTypedMatchesData } from "./useTypedMatchData";
export type MatchedClient = UseDataFunctionReturn<typeof loader>["client"];
export function useOptionalIntegrationClient(matches?: RouteMatch[]) {
export function useOptionalIntegrationClient(matches?: UIMatch[]) {
const routeMatch = useTypedMatchesData<typeof loader>({
id: "routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations_.$clientParam",
matches,
@@ -15,7 +15,7 @@ export function useOptionalIntegrationClient(matches?: RouteMatch[]) {
return routeMatch?.client;
}
export function useIntegrationClient(matches?: RouteMatch[]) {
export function useIntegrationClient(matches?: UIMatch[]) {
const integration = useOptionalIntegrationClient(matches);
invariant(integration, "Integration must be defined");
return integration;
@@ -1,6 +1,6 @@
import { RouteMatch, useMatches } from "@remix-run/react";
import { UIMatch, useMatches } from "@remix-run/react";
export function useIsProjectChildPage(matches?: RouteMatch[]) {
export function useIsProjectChildPage(matches?: UIMatch[]) {
if (!matches) {
matches = useMatches();
}
+3 -3
View File
@@ -2,14 +2,14 @@ import { UseDataFunctionReturn } from "remix-typedjson";
import invariant from "tiny-invariant";
import type { loader } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam/route";
import { useChanged } from "./useChanged";
import { RouteMatch } from "@remix-run/react";
import { UIMatch } from "@remix-run/react";
import { useTypedMatchesData } from "./useTypedMatchData";
export type MatchedJob = UseDataFunctionReturn<typeof loader>["job"];
export const jobMatchId =
"routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam";
export function useOptionalJob(matches?: RouteMatch[]) {
export function useOptionalJob(matches?: UIMatch[]) {
const routeMatch = useTypedMatchesData<typeof loader>({
id: jobMatchId,
matches,
@@ -22,7 +22,7 @@ export function useOptionalJob(matches?: RouteMatch[]) {
return routeMatch.projectJobs.find((j) => j.id === routeMatch.job.id);
}
export function useJob(matches?: RouteMatch[]) {
export function useJob(matches?: UIMatch[]) {
const job = useOptionalJob(matches);
invariant(job, "Job must be defined");
return job;
+3 -3
View File
@@ -1,7 +1,7 @@
import { UseDataFunctionReturn } from "remix-typedjson";
import invariant from "tiny-invariant";
import type { loader } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam/route";
import { RouteMatch } from "@remix-run/react";
import { UIMatch } from "@remix-run/react";
import { useTypedMatchesData } from "./useTypedMatchData";
export type ProjectJob = UseDataFunctionReturn<typeof loader>["projectJobs"][number];
@@ -12,7 +12,7 @@ export const jobsMatchId =
// This is only used in the JobsMenu component, which is the breadcrumb job list dropdown.
// This dropdown is only shown once you have selected a job, so we can assume that
// the route above has loaded and we can use the data from it.
export function useOptionalJobs(matches?: RouteMatch[]) {
export function useOptionalJobs(matches?: UIMatch[]) {
const routeMatch = useTypedMatchesData<typeof loader>({
id: jobsMatchId,
matches,
@@ -21,7 +21,7 @@ export function useOptionalJobs(matches?: RouteMatch[]) {
return routeMatch?.projectJobs;
}
export function useJobs(matches?: RouteMatch[]) {
export function useJobs(matches?: UIMatch[]) {
const jobs = useOptionalJobs(matches);
invariant(jobs, "Jobs must be defined");
return jobs;
+6 -6
View File
@@ -4,12 +4,12 @@ import type { loader as orgLoader } from "~/routes/_app.orgs.$organizationSlug/r
import type { loader as appLoader } from "~/routes/_app/route";
import { hydrateObject, useMatchesData } from "~/utils";
import { useChanged } from "./useChanged";
import { RouteMatch } from "@remix-run/react";
import { UIMatch } from "@remix-run/react";
import { useTypedMatchesData } from "./useTypedMatchData";
export type MatchedOrganization = UseDataFunctionReturn<typeof appLoader>["organizations"][number];
export function useOptionalOrganizations(matches?: RouteMatch[]) {
export function useOptionalOrganizations(matches?: UIMatch[]) {
const data = useTypedMatchesData<typeof appLoader>({
id: "routes/_app",
matches,
@@ -17,13 +17,13 @@ export function useOptionalOrganizations(matches?: RouteMatch[]) {
return data?.organizations;
}
export function useOrganizations(matches?: RouteMatch[]) {
export function useOrganizations(matches?: UIMatch[]) {
const orgs = useOptionalOrganizations(matches);
invariant(orgs, "No organizations found in loader.");
return orgs;
}
export function useOptionalOrganization(matches?: RouteMatch[]) {
export function useOptionalOrganization(matches?: UIMatch[]) {
const orgs = useOptionalOrganizations(matches);
const org = useTypedMatchesData<typeof orgLoader>({
id: "routes/_app.orgs.$organizationSlug",
@@ -37,13 +37,13 @@ export function useOptionalOrganization(matches?: RouteMatch[]) {
return orgs.find((o) => o.id === org.organization.id);
}
export function useOrganization(matches?: RouteMatch[]) {
export function useOrganization(matches?: UIMatch[]) {
const org = useOptionalOrganization(matches);
invariant(org, "No organization found in loader.");
return org;
}
export function useIsNewOrganizationPage(matches?: RouteMatch[]): boolean {
export function useIsNewOrganizationPage(matches?: UIMatch[]): boolean {
const data = useTypedMatchesData<any>({
id: "routes/_app.orgs.new",
matches,
+3 -3
View File
@@ -1,4 +1,4 @@
import { RouteMatch } from "@remix-run/react";
import { UIMatch } from "@remix-run/react";
import { UseDataFunctionReturn } from "remix-typedjson";
import invariant from "tiny-invariant";
import type { loader } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam/route";
@@ -9,7 +9,7 @@ export type MatchedProject = UseDataFunctionReturn<typeof loader>["project"];
export const projectMatchId = "routes/_app.orgs.$organizationSlug.projects.$projectParam";
export function useOptionalProject(matches?: RouteMatch[]) {
export function useOptionalProject(matches?: UIMatch[]) {
const routeMatch = useTypedMatchesData<typeof loader>({
id: projectMatchId,
matches,
@@ -18,7 +18,7 @@ export function useOptionalProject(matches?: RouteMatch[]) {
return routeMatch?.project;
}
export function useProject(matches?: RouteMatch[]) {
export function useProject(matches?: UIMatch[]) {
const project = useOptionalProject(matches);
invariant(project, "Project must be defined");
return project;
@@ -1,5 +1,5 @@
import { useEffect } from "react";
import { useEventSource } from "remix-utils";
import { useEventSource } from "remix-utils/sse/react";
import { projectPath, projectStreamingPath } from "~/utils/pathBuilder";
import { useProject } from "./useProject";
import { useOrganization } from "./useOrganizations";
+3 -3
View File
@@ -1,4 +1,4 @@
import { RouteMatch } from "@remix-run/react";
import { UIMatch } from "@remix-run/react";
import { UseDataFunctionReturn } from "remix-typedjson";
import invariant from "tiny-invariant";
import type { loader as runLoader } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam/route";
@@ -7,7 +7,7 @@ import { useTypedMatchesData } from "./useTypedMatchData";
export type MatchedRun = UseDataFunctionReturn<typeof runLoader>["run"];
export function useOptionalRun(matches?: RouteMatch[]) {
export function useOptionalRun(matches?: UIMatch[]) {
const project = useOptionalProject(matches);
const routeMatch = useTypedMatchesData<typeof runLoader>({
id: "routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam",
@@ -21,7 +21,7 @@ export function useOptionalRun(matches?: RouteMatch[]) {
return routeMatch.run;
}
export function useRun(matches?: RouteMatch[]) {
export function useRun(matches?: UIMatch[]) {
const run = useOptionalRun(matches);
invariant(run, "Run must be present");
return run;
+4 -4
View File
@@ -1,4 +1,4 @@
import { RouteMatch, useMatches } from "@remix-run/react";
import { UIMatch, useMatches } from "@remix-run/react";
import { RemixSerializedType, UseDataFunctionReturn, deserializeRemix } from "remix-typedjson";
type AppData = any;
@@ -8,7 +8,7 @@ function useTypedDataFromMatches<T = AppData>({
matches,
}: {
id: string;
matches: RouteMatch[];
matches: UIMatch[];
}): UseDataFunctionReturn<T> | undefined {
const match = matches.find((m) => m.id === id);
return useTypedMatchData<T>(match);
@@ -19,7 +19,7 @@ export function useTypedMatchesData<T = AppData>({
matches,
}: {
id: string;
matches?: RouteMatch[];
matches?: UIMatch[];
}): UseDataFunctionReturn<T> | undefined {
if (!matches) {
matches = useMatches();
@@ -29,7 +29,7 @@ export function useTypedMatchesData<T = AppData>({
}
export function useTypedMatchData<T = AppData>(
match: RouteMatch | undefined
match: UIMatch | undefined
): UseDataFunctionReturn<T> | undefined {
if (!match) {
return undefined;
+3 -3
View File
@@ -1,11 +1,11 @@
import type { User } from "~/models/user.server";
import { useMatchesData } from "~/utils";
import { useChanged } from "./useChanged";
import { RouteMatch } from "@remix-run/react";
import { UIMatch } from "@remix-run/react";
import { useTypedMatchesData } from "./useTypedMatchData";
import { loader } from "~/root";
export function useOptionalUser(matches?: RouteMatch[]): User | undefined {
export function useOptionalUser(matches?: UIMatch[]): User | undefined {
const routeMatch = useTypedMatchesData<typeof loader>({
id: "root",
matches,
@@ -14,7 +14,7 @@ export function useOptionalUser(matches?: RouteMatch[]): User | undefined {
return routeMatch?.user ?? undefined;
}
export function useUser(matches?: RouteMatch[]): User {
export function useUser(matches?: UIMatch[]): User {
const maybeUser = useOptionalUser(matches);
if (!maybeUser) {
throw new Error(
+101
View File
@@ -0,0 +1,101 @@
import { RuntimeEnvironmentType, type RuntimeEnvironment } from "@trigger.dev/database";
import { prisma } from "~/db.server";
import { customAlphabet } from "nanoid";
const apiKeyId = customAlphabet(
"1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
12
);
type RegenerateAPIKeyInput = {
userId: string;
environmentId: string;
};
export async function regenerateApiKey({ userId, environmentId }: RegenerateAPIKeyInput) {
const environment = await prisma.runtimeEnvironment.findUnique({
where: {
id: environmentId,
},
include: {
organization: true,
project: true,
},
});
if (!environment) {
throw new Error("Environment does not exist");
}
// check if the user is part of the org
const organization = await prisma.organization.findFirst({
where: {
id: environment.organization.id,
members: { some: { userId } },
},
});
if (!organization) {
throw new Error("User does not have permission to regenerate API key");
}
// check if it is the user's dev environment
if (environment.type === RuntimeEnvironmentType.DEVELOPMENT) {
if (!environment.orgMemberId) {
throw new Error("User does not have permission to regenerate API key");
}
const orgMember = await prisma.orgMember.findFirst({
where: {
organizationId: organization.id,
userId: userId,
id: environment.orgMemberId,
},
});
if (!orgMember) {
throw new Error("User does not have permission to regenerate API key");
}
}
// generate and store new keys
const newApiKey = createApiKeyForEnv(environment.type);
const newPkApiKey = createPkApiKeyForEnv(environment.type);
const updatedEnviroment = await prisma.runtimeEnvironment.update({
data: {
apiKey: newApiKey,
pkApiKey: newPkApiKey,
},
where: {
id: environmentId,
},
});
return updatedEnviroment;
}
export function createApiKeyForEnv(envType: RuntimeEnvironment["type"]) {
return `tr_${envSlug(envType)}_${apiKeyId(20)}`;
}
export function createPkApiKeyForEnv(envType: RuntimeEnvironment["type"]) {
return `pk_${envSlug(envType)}_${apiKeyId(20)}`;
}
export function envSlug(environmentType: RuntimeEnvironment["type"]) {
switch (environmentType) {
case "DEVELOPMENT": {
return "dev";
}
case "PRODUCTION": {
return "prod";
}
case "STAGING": {
return "stg";
}
case "PREVIEW": {
return "prev";
}
}
}
+1 -25
View File
@@ -9,6 +9,7 @@ import { customAlphabet } from "nanoid";
import slug from "slug";
import { prisma, PrismaClientOrTransaction } from "~/db.server";
import { createProject } from "./project.server";
import { createApiKeyForEnv, createPkApiKeyForEnv, envSlug } from "./api-key.server";
export type { Organization };
@@ -157,28 +158,3 @@ export async function createEnvironment(
},
});
}
function createApiKeyForEnv(envType: RuntimeEnvironment["type"]) {
return `tr_${envSlug(envType)}_${apiKeyId(20)}`;
}
function createPkApiKeyForEnv(envType: RuntimeEnvironment["type"]) {
return `pk_${envSlug(envType)}_${apiKeyId(20)}`;
}
function envSlug(environmentType: RuntimeEnvironment["type"]) {
switch (environmentType) {
case "DEVELOPMENT": {
return "dev";
}
case "PRODUCTION": {
return "prod";
}
case "STAGING": {
return "stg";
}
case "PREVIEW": {
return "prev";
}
}
}
+13 -10
View File
@@ -1,7 +1,10 @@
import type { LinksFunction, LoaderArgs } from "@remix-run/node";
import { ErrorBoundary as HighlightErrorBoundary } from "@highlight-run/react";
import type { LinksFunction, LoaderFunctionArgs } from "@remix-run/node";
import type { ShouldRevalidateFunction } from "@remix-run/react";
import { Links, LiveReload, Meta, Outlet, Scripts, ScrollRestoration } from "@remix-run/react";
import { metaV1 } from "@remix-run/v1-meta";
import { TypedMetaFunction, typedjson, useTypedLoaderData } from "remix-typedjson";
import { ExternalScripts } from "remix-utils/external-scripts";
import type { ToastMessage } from "~/models/message.server";
import { commitSession, getSession } from "~/models/message.server";
import tailwindStylesheetUrl from "~/tailwind.css";
@@ -11,24 +14,24 @@ import { AppContainer, MainCenteredContainer } from "./components/layout/AppLayo
import { Toast } from "./components/primitives/Toast";
import { env } from "./env.server";
import { featuresForRequest } from "./features.server";
import { useHighlight } from "./hooks/useHighlight";
import { usePostHog } from "./hooks/usePostHog";
import { getUser } from "./services/session.server";
import { appEnvTitleTag } from "./utils";
import { ErrorBoundary as HighlightErrorBoundary } from "@highlight-run/react";
import { useHighlight } from "./hooks/useHighlight";
import { ExternalScripts } from "remix-utils";
export const links: LinksFunction = () => {
return [{ rel: "stylesheet", href: tailwindStylesheetUrl }];
};
export const meta: TypedMetaFunction<typeof loader> = ({ data }) => ({
title: `Trigger.dev${appEnvTitleTag(data?.appEnv)}`,
charset: "utf-8",
viewport: "width=1024, initial-scale=1",
});
export const meta: TypedMetaFunction<typeof loader> = (args) => {
return metaV1(args, {
title: `Trigger.dev${appEnvTitleTag(args.data.appEnv)}`,
charset: "utf-8",
viewport: "width=1024, initial-scale=1",
});
};
export const loader = async ({ request }: LoaderArgs) => {
export const loader = async ({ request }: LoaderFunctionArgs) => {
const session = await getSession(request.headers.get("cookie"));
const toastMessage = session.get("toastMessage") as ToastMessage;
const posthogProjectKey = env.POSTHOG_PROJECT_KEY;
@@ -1,4 +1,4 @@
import { LoaderArgs, json } from "@remix-run/server-runtime";
import { LoaderFunctionArgs, json } from "@remix-run/server-runtime";
import { redirect } from "remix-typedjson";
import { LinkButton } from "~/components/primitives/Buttons";
import { useOptionalOrganizations } from "~/hooks/useOrganizations";
@@ -11,7 +11,7 @@ import { Link } from "@remix-run/react";
import { NamedIcon } from "~/components/primitives/NamedIcon";
import { Paragraph } from "~/components/primitives/Paragraph";
export const loader = async ({ request }: LoaderArgs) => {
export const loader = async ({ request }: LoaderFunctionArgs) => {
const user = await requireUser(request);
//if there are invites then we should redirect to the invites page
@@ -113,7 +113,8 @@ export default function Page() {
const [form, { name, email, marketingEmails }] = useForm({
id: "account",
lastSubmission,
// TODO: type this
lastSubmission: lastSubmission as any,
onValidate({ formData }) {
return parse(formData, { schema: createSchema() });
},
@@ -14,12 +14,12 @@ import { useOrganization } from "~/hooks/useOrganizations";
import { OrganizationParamsSchema, jobPath, organizationTeamPath } from "~/utils/pathBuilder";
import { OrgAdminHeader } from "../_app.orgs.$organizationSlug._index/OrgAdminHeader";
import { Link } from "@remix-run/react/dist/components";
import { LoaderArgs } from "@remix-run/server-runtime";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { OrgUsagePresenter } from "~/presenters/OrgUsagePresenter.server";
import { requireUserId } from "~/services/session.server";
export async function loader({ params, request }: LoaderArgs) {
export async function loader({ params, request }: LoaderFunctionArgs) {
const userId = await requireUserId(request);
const { organizationSlug } = OrganizationParamsSchema.parse(params);
@@ -91,7 +91,8 @@ export default function Page() {
const [form, { emails }] = useForm({
id: "invite-members",
lastSubmission,
// TODO: type this
lastSubmission: lastSubmission as any,
onValidate({ formData }) {
return parse(formData, { schema });
},
@@ -1,5 +1,5 @@
import { ArrowUpIcon } from "@heroicons/react/24/solid";
import { LoaderArgs } from "@remix-run/server-runtime";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { FrameworkSelector } from "~/components/frameworks/FrameworkSelector";
import { JobsTable } from "~/components/jobs/JobsTable";
@@ -34,7 +34,7 @@ import {
trimTrailingSlash,
} from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const { projectParam } = ProjectParamSchema.parse(params);
@@ -1,9 +1,9 @@
import { LoaderArgs } from "@remix-run/server-runtime";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { EnvironmentsStreamPresenter } from "~/presenters/EnvironmentsStreamPresenter.server";
import { requireUserId } from "~/services/session.server";
import { ProjectParamSchema } from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const { projectParam } = ProjectParamSchema.parse(params);
@@ -2,8 +2,12 @@ import { conform, useForm } from "@conform-to/react";
import { parse } from "@conform-to/zod";
import { useFetcher, useRevalidator } from "@remix-run/react";
import { useEffect } from "react";
import { useEventSource } from "remix-utils";
import { useEventSource } from "remix-utils/sse/react";
import { InlineCode } from "~/components/code/InlineCode";
import {
EndpointIndexStatusIcon,
EndpointIndexStatusLabel,
} from "~/components/environments/EndpointIndexStatus";
import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel";
import { Button } from "~/components/primitives/Buttons";
import { Callout, CalloutVariant } from "~/components/primitives/Callout";
@@ -20,12 +24,6 @@ import { ClientEndpoint } from "~/presenters/EnvironmentsPresenter.server";
import { endpointStreamingPath } from "~/utils/pathBuilder";
import { EndpointIndexStatus, RuntimeEnvironmentType } from "../../../../../packages/database/src";
import { bodySchema } from "../resources.environments.$environmentParam.endpoint";
import {
EndpointIndexStatusIcon,
EndpointIndexStatusLabel,
endpointIndexStatusTitle,
} from "~/components/environments/EndpointIndexStatus";
import { CodeBlock } from "~/components/code/CodeBlock";
type ConfigureEndpointSheetProps = {
slug: string;
@@ -39,7 +37,7 @@ export function ConfigureEndpointSheet({ slug, endpoint, onClose }: ConfigureEnd
const [form, { url, clientSlug }] = useForm({
id: "endpoint-url",
lastSubmission: setEndpointUrlFetcher.data,
lastSubmission: setEndpointUrlFetcher.data as any,
onValidate({ formData }) {
return parse(formData, { schema: bodySchema });
},
@@ -40,7 +40,8 @@ export function FirstEndpointSheet({ projectId, environments }: FirstEndpointShe
const setEndpointUrlFetcher = useFetcher();
const [form, { url, environmentId }] = useForm({
id: "new-endpoint-url",
lastSubmission: setEndpointUrlFetcher.data,
// TODO: type this
lastSubmission: setEndpointUrlFetcher.data as any,
onValidate({ formData }) {
return parse(formData, { schema: bodySchema });
},
@@ -1,8 +1,8 @@
import { useRevalidator } from "@remix-run/react";
import { LoaderArgs } from "@remix-run/server-runtime";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { useEffect, useMemo, useState } from "react";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { useEventSource } from "remix-utils";
import { useEventSource } from "remix-utils/sse/react";
import {
EndpointIndexStatusIcon,
EndpointIndexStatusLabel,
@@ -44,8 +44,9 @@ import { requestUrl } from "~/utils/requestUrl.server";
import { RuntimeEnvironmentType } from "../../../../../packages/database/src";
import { ConfigureEndpointSheet } from "./ConfigureEndpointSheet";
import { FirstEndpointSheet } from "./FirstEndpointSheet";
import { RegenerateApiKeyModal } from "~/components/environments/RegenerateApiKeyModal";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const { projectParam } = ProjectParamSchema.parse(params);
@@ -148,11 +149,17 @@ export default function Page() {
</Paragraph>
<div className="mt-4 flex flex-col gap-6">
{environments.map((environment) => (
<div key={environment.id}>
<Header3 className="flex items-center gap-1">
<EnvironmentLabel environment={environment} /> Environment
</Header3>
<div className="mt-2 inline-flex flex-col gap-3">
<div key={environment.id} className="w-[400px]">
<div className="flex justify-between">
<Header3 className="flex items-center gap-1">
<EnvironmentLabel environment={environment} /> Environment
</Header3>
<RegenerateApiKeyModal
id={environment.id}
title={environmentTitle(environment)}
/>
</div>
<div className="mt-3 inline-flex w-full flex-col gap-3">
<ClipboardField
className="w-full max-w-none"
secure
@@ -1,5 +1,5 @@
import { ChevronRightIcon } from "@heroicons/react/24/solid";
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { useCallback, useState } from "react";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { Feedback } from "~/components/Feedback";
@@ -56,7 +56,7 @@ import {
integrationClientPath,
} from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const user = await requireUser(request);
const { organizationSlug, projectParam } = ProjectParamSchema.parse(params);
@@ -1,4 +1,4 @@
import { LoaderArgs } from "@remix-run/server-runtime";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { HowToUseThisIntegration } from "~/components/helpContent/HelpContentText";
import { JobSkeleton } from "~/components/jobs/JobSkeleton";
@@ -21,7 +21,7 @@ import {
trimTrailingSlash,
} from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const { organizationSlug, projectParam, clientParam } =
IntegrationClientParamSchema.parse(params);
@@ -1,4 +1,4 @@
import { LoaderArgs } from "@remix-run/server-runtime";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { connectionType } from "~/components/integrations/connectionType";
import { BreadcrumbLink } from "~/components/navigation/NavBar";
@@ -18,7 +18,7 @@ import { requireUserId } from "~/services/session.server";
import { Handle } from "~/utils/handle";
import { IntegrationClientParamSchema, trimTrailingSlash } from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const { organizationSlug, projectParam, clientParam } =
IntegrationClientParamSchema.parse(params);
@@ -1,4 +1,4 @@
import { LoaderArgs } from "@remix-run/server-runtime";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { BreadcrumbLink } from "~/components/navigation/NavBar";
import { Paragraph } from "~/components/primitives/Paragraph";
@@ -7,7 +7,7 @@ import { requireUserId } from "~/services/session.server";
import { Handle } from "~/utils/handle";
import { IntegrationClientParamSchema, trimTrailingSlash } from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const { organizationSlug, projectParam, clientParam } =
IntegrationClientParamSchema.parse(params);
@@ -1,5 +1,5 @@
import { Outlet } from "@remix-run/react";
import { LoaderArgs } from "@remix-run/server-runtime";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { connectionType } from "~/components/integrations/connectionType";
import { PageBody, PageContainer } from "~/components/layout/AppLayout";
@@ -29,7 +29,7 @@ import {
projectIntegrationsPath,
} from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const user = await requireUser(request);
const { organizationSlug, clientParam, projectParam } =
IntegrationClientParamSchema.parse(params);
@@ -1,5 +1,5 @@
import { useNavigation } from "@remix-run/react";
import { LoaderArgs } from "@remix-run/server-runtime";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { z } from "zod";
import { HowToRunYourJob } from "~/components/helpContent/HelpContentText";
@@ -30,7 +30,7 @@ export const RunListSearchSchema = z.object({
direction: DirectionSchema.optional(),
});
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const { jobParam, projectParam, organizationSlug } = JobParamsSchema.parse(params);
@@ -1,9 +1,9 @@
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { z } from "zod";
import { RunStreamPresenter } from "~/presenters/RunStreamPresenter.server";
import { requireUserId } from "~/services/session.server";
export async function loader({ request, params }: LoaderArgs) {
export async function loader({ request, params }: LoaderFunctionArgs) {
await requireUserId(request);
const { runParam } = z.object({ runParam: z.string() }).parse(params);
@@ -1,5 +1,5 @@
import { Await, useLoaderData } from "@remix-run/react";
import { LoaderArgs, SerializeFrom, defer } from "@remix-run/server-runtime";
import { LoaderFunctionArgs, SerializeFrom, defer } from "@remix-run/server-runtime";
import { Suspense } from "react";
import { Spinner } from "~/components/primitives/Spinner";
import { TaskDetail } from "~/components/run/TaskDetail";
@@ -7,7 +7,7 @@ import { TaskDetailsPresenter } from "~/presenters/TaskDetailsPresenter.server";
import { requireUserId } from "~/services/session.server";
import { TaskParamsSchema } from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const { taskParam } = TaskParamsSchema.parse(params);
@@ -1,4 +1,4 @@
import { LoaderArgs } from "@remix-run/server-runtime";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { TriggerDetail } from "~/components/run/TriggerDetail";
import { useJob } from "~/hooks/useJob";
@@ -6,7 +6,7 @@ import { useRun } from "~/hooks/useRun";
import { TriggerDetailsPresenter } from "~/presenters/TriggerDetailsPresenter.server";
import { RunParamsSchema } from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const { runParam } = RunParamsSchema.parse(params);
const presenter = new TriggerDetailsPresenter();
@@ -1,8 +1,8 @@
import { useRevalidator } from "@remix-run/react";
import { LoaderArgs } from "@remix-run/server-runtime";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { Fragment, useEffect } from "react";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { useEventSource } from "remix-utils";
import { useEventSource } from "remix-utils/sse/react";
import { BreadcrumbLink } from "~/components/navigation/NavBar";
import { BreadcrumbIcon } from "~/components/primitives/BreadcrumbIcon";
import { RunOverview } from "~/components/run/RunOverview";
@@ -22,7 +22,7 @@ import {
trimTrailingSlash,
} from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const { runParam } = RunParamsSchema.parse(params);
@@ -3,7 +3,7 @@ import { parse } from "@conform-to/zod";
import { ClipboardIcon } from "@heroicons/react/20/solid";
import { ClockIcon, CodeBracketIcon } from "@heroicons/react/24/outline";
import { Form, useActionData, useSubmit } from "@remix-run/react";
import { ActionFunction, LoaderArgs, json } from "@remix-run/server-runtime";
import { ActionFunction, LoaderFunctionArgs, json } from "@remix-run/server-runtime";
import { useCallback, useRef, useState } from "react";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { z } from "zod";
@@ -38,7 +38,7 @@ import { Handle } from "~/utils/handle";
import { isValidIcon } from "~/utils/icon";
import { JobParamsSchema, jobRunDashboardPath, trimTrailingSlash } from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const { organizationSlug, projectParam, jobParam } = JobParamsSchema.parse(params);
@@ -166,7 +166,8 @@ export default function Page() {
const [form, { environmentId, payload, accountId }] = useForm({
id: "test-job",
lastSubmission,
// TODO: type this
lastSubmission: lastSubmission as any,
onValidate({ formData }) {
return parse(formData, { schema });
},
@@ -1,5 +1,5 @@
import { Outlet, useLocation } from "@remix-run/react";
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { Fragment } from "react";
import { typedjson } from "remix-typedjson";
import { JobStatusBadge } from "~/components/jobs/JobStatusBadge";
@@ -37,7 +37,7 @@ import {
trimTrailingSlash,
} from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const { jobParam, projectParam, organizationSlug } = JobParamsSchema.parse(params);
@@ -1,5 +1,5 @@
import { CheckCircleIcon, XCircleIcon } from "@heroicons/react/24/solid";
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel";
import { BreadcrumbLink } from "~/components/navigation/NavBar";
@@ -25,7 +25,7 @@ import { cn } from "~/utils/cn";
import { Handle } from "~/utils/handle";
import { ProjectParamSchema, externalTriggerPath, trimTrailingSlash } from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const user = await requireUser(request);
const { organizationSlug, projectParam } = ProjectParamSchema.parse(params);
@@ -138,7 +138,7 @@ export default function Integrations() {
);
})
) : (
<TableBlankRow colSpan={5}>
<TableBlankRow colSpan={100}>
<Paragraph>No External triggers</Paragraph>
</TableBlankRow>
)}
@@ -1,6 +1,6 @@
import { NoSymbolIcon } from "@heroicons/react/20/solid";
import { CheckCircleIcon, XCircleIcon } from "@heroicons/react/24/solid";
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel";
import { BreadcrumbLink } from "~/components/navigation/NavBar";
@@ -25,7 +25,7 @@ import { requireUser } from "~/services/session.server";
import { Handle } from "~/utils/handle";
import { ProjectParamSchema, docsPath, trimTrailingSlash } from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const user = await requireUser(request);
const { organizationSlug, projectParam } = ProjectParamSchema.parse(params);
@@ -143,7 +143,7 @@ export default function Integrations() {
);
})
) : (
<TableBlankRow colSpan={5}>
<TableBlankRow colSpan={100}>
<Paragraph>No Scheduled triggers</Paragraph>
</TableBlankRow>
)}
@@ -1,5 +1,5 @@
import { Response, json } from "@remix-run/node";
import type { ActionFunction, LoaderArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/node";
import type { ActionFunction, LoaderFunctionArgs } from "@remix-run/server-runtime";
import { Fragment } from "react";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel";
@@ -45,7 +45,7 @@ import { ActivateSourceService } from "~/services/sources/activateSource.server"
import { redirectWithSuccessMessage } from "~/models/message.server";
import { nanoid } from "nanoid";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const user = await requireUser(request);
const { organizationSlug, projectParam, triggerParam } = TriggerSourceParamSchema.parse(params);
@@ -135,7 +135,8 @@ export default function Page() {
const [form, { jobId }] = useForm({
id: "trigger-registration-retry",
lastSubmission,
// TODO: type this
lastSubmission: lastSubmission as any,
onValidate({ formData }) {
return parse(formData, { schema });
},
@@ -1,9 +1,9 @@
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { z } from "zod";
import { RunStreamPresenter } from "~/presenters/RunStreamPresenter.server";
import { requireUserId } from "~/services/session.server";
export async function loader({ request, params }: LoaderArgs) {
export async function loader({ request, params }: LoaderFunctionArgs) {
await requireUserId(request);
const { runParam } = z.object({ runParam: z.string() }).parse(params);
@@ -1,5 +1,5 @@
import { Await, useLoaderData } from "@remix-run/react";
import { LoaderArgs, defer } from "@remix-run/server-runtime";
import { LoaderFunctionArgs, defer } from "@remix-run/server-runtime";
import { Suspense } from "react";
import { Spinner } from "~/components/primitives/Spinner";
import { TaskDetail } from "~/components/run/TaskDetail";
@@ -7,7 +7,7 @@ import { TaskDetailsPresenter } from "~/presenters/TaskDetailsPresenter.server";
import { requireUserId } from "~/services/session.server";
import { TriggerSourceRunTaskParamsSchema } from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const { taskParam } = TriggerSourceRunTaskParamsSchema.parse(params);
@@ -1,10 +1,10 @@
import { LoaderArgs } from "@remix-run/server-runtime";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { TriggerDetail } from "~/components/run/TriggerDetail";
import { TriggerDetailsPresenter } from "~/presenters/TriggerDetailsPresenter.server";
import { TriggerSourceRunParamsSchema } from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const { runParam } = TriggerSourceRunParamsSchema.parse(params);
const presenter = new TriggerDetailsPresenter();
@@ -1,8 +1,8 @@
import { useRevalidator } from "@remix-run/react";
import { LoaderArgs } from "@remix-run/server-runtime";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { Fragment, useEffect } from "react";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { useEventSource } from "remix-utils";
import { useEventSource } from "remix-utils/sse/react";
import { BreadcrumbLink } from "~/components/navigation/NavBar";
import { BreadcrumbIcon } from "~/components/primitives/BreadcrumbIcon";
import { RunOverview } from "~/components/run/RunOverview";
@@ -23,7 +23,7 @@ import {
trimTrailingSlash,
} from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const { runParam, triggerParam } = TriggerSourceRunParamsSchema.parse(params);
@@ -1,5 +1,5 @@
import { Outlet } from "@remix-run/react";
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { typedjson } from "remix-typedjson";
import invariant from "tiny-invariant";
import { RouteErrorDisplay } from "~/components/ErrorDisplay";
@@ -13,7 +13,7 @@ import { requireUserId } from "~/services/session.server";
import { Handle } from "~/utils/handle";
import { projectPath } from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const { projectParam } = params;
invariant(projectParam, "projectParam not found");
@@ -1,7 +1,7 @@
import { conform, useForm } from "@conform-to/react";
import { parse } from "@conform-to/zod";
import type { ActionFunction } from "@remix-run/node";
import { json, redirect } from "@remix-run/node";
import { json } from "@remix-run/node";
import { Form, useActionData } from "@remix-run/react";
import invariant from "tiny-invariant";
import { z } from "zod";
@@ -11,7 +11,6 @@ import { Fieldset } from "~/components/primitives/Fieldset";
import { FormButtons } from "~/components/primitives/FormButtons";
import { FormError } from "~/components/primitives/FormError";
import { FormTitle } from "~/components/primitives/FormTitle";
import { Hint } from "~/components/primitives/Hint";
import { Input } from "~/components/primitives/Input";
import { InputGroup } from "~/components/primitives/InputGroup";
import { Label } from "~/components/primitives/Label";
@@ -60,7 +59,8 @@ export default function NewOrganizationPage() {
const [form, { projectName }] = useForm({
id: "create-project",
lastSubmission,
// TODO: type this
lastSubmission: lastSubmission as any,
onValidate({ formData }) {
return parse(formData, { schema });
},
@@ -2,7 +2,7 @@ import { useForm } from "@conform-to/react";
import { parse } from "@conform-to/zod";
import { UserPlusIcon } from "@heroicons/react/20/solid";
import { Form, useActionData } from "@remix-run/react";
import { ActionFunction, LoaderArgs, json } from "@remix-run/server-runtime";
import { ActionFunction, LoaderFunctionArgs, json } from "@remix-run/server-runtime";
import { useState } from "react";
import { UseDataFunctionReturn, typedjson, useTypedLoaderData } from "remix-typedjson";
import invariant from "tiny-invariant";
@@ -33,7 +33,7 @@ import { inviteTeamMemberPath, organizationTeamPath, resendInvitePath } from "~/
import { OrgAdminHeader } from "../_app.orgs.$organizationSlug._index/OrgAdminHeader";
import { DateTime } from "~/components/primitives/DateTime";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const { organizationSlug } = params;
invariant(organizationSlug, "organizationSlug not found");
@@ -235,7 +235,8 @@ function LeaveTeamModal({
const [form, { memberId }] = useForm({
id: "remove-member",
lastSubmission,
// TODO: type this
lastSubmission: lastSubmission as any,
onValidate({ formData }) {
return parse(formData, { schema });
},
@@ -1,6 +1,6 @@
import type { ShouldRevalidateFunction } from "@remix-run/react";
import { Outlet } from "@remix-run/react";
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { typedjson } from "remix-typedjson";
import invariant from "tiny-invariant";
import { RouteErrorDisplay } from "~/components/ErrorDisplay";
@@ -11,7 +11,7 @@ import { commitCurrentOrgSession, setCurrentOrg } from "~/services/currentOrgani
import { requireUserId } from "~/services/session.server";
import { organizationPath } from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const { organizationSlug } = params;
invariant(organizationSlug, "organizationSlug not found");
@@ -56,7 +56,8 @@ export default function NewOrganizationPage() {
const [form, { orgName, projectName }] = useForm({
id: "create-organization",
lastSubmission,
// TODO: type this
lastSubmission: lastSubmission as any,
onValidate({ formData }) {
return parse(formData, { schema });
},
+2 -2
View File
@@ -1,5 +1,5 @@
import { Outlet, ShouldRevalidateFunction } from "@remix-run/react";
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { redirect, typedjson, useTypedLoaderData } from "remix-typedjson";
import { RouteErrorDisplay } from "~/components/ErrorDisplay";
import { ImpersonationBanner } from "~/components/ImpersonationBanner";
@@ -13,7 +13,7 @@ import { clearRedirectTo, commitSession } from "~/services/redirectTo.server";
import { requireUser } from "~/services/session.server";
import { confirmBasicDetailsPath } from "~/utils/pathBuilder";
export const loader = async ({ request }: LoaderArgs) => {
export const loader = async ({ request }: LoaderFunctionArgs) => {
const user = await requireUser(request);
const organizations = await getOrganizations({ userId: user.id });
const impersonationId = await getImpersonationId(request);
+67 -70
View File
@@ -1,5 +1,5 @@
import { Form } from "@remix-run/react";
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { redirect } from "@remix-run/server-runtime";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { z } from "zod";
@@ -15,7 +15,7 @@ export async function loader() {
const FormSchema = z.object({ id: z.string() });
export async function action({ request }: ActionArgs) {
export async function action({ request }: ActionFunctionArgs) {
if (request.method.toLowerCase() !== "post") {
return new Response("Method not allowed", { status: 405 });
}
@@ -37,76 +37,73 @@ export default function AdminDashboardRoute() {
const { users } = useTypedLoaderData<typeof loader>();
return (
<main className="flex flex-1 overflow-hidden">
{/* Primary column */}
<section
aria-labelledby="primary-heading"
className="flex h-full min-w-0 flex-1 flex-col overflow-y-auto p-4 lg:order-last"
>
<h1 className="mb-2 text-2xl">Accounts ({users.length})</h1>
<main
aria-labelledby="primary-heading"
className="flex h-full min-w-0 flex-1 flex-col overflow-y-auto p-4 lg:order-last"
>
<h1 className="mb-2 text-2xl">Accounts ({users.length})</h1>
<table className="w-full divide-y divide-border">
<thead className="sticky top-0 text-left">
<tr>
<th scope="col" className={headerClassName}>
Email
</th>
<th scope="col" className={headerClassName}>
GitHub username
</th>
<th scope="col" className={headerClassName}>
id
</th>
<th scope="col" className={headerClassName}>
Created At
</th>
<th scope="col" className={headerClassName}>
Admin?
</th>
<th scope="col" className={headerClassName}>
Actions
</th>
</tr>
</thead>
<tbody className="divide-y divide-border">
{users.map((user) => {
return (
<tr key={user.id} className="w-full px-4 py-2 text-left hover:bg-slate-900">
<td className={cellClassName}>{user.email}</td>
<td className={cellClassName}>
<a
href={`https://github.com/${user.displayName}`}
target="_blank"
className="text-indigo-500 underline"
rel="noreferrer"
<table className="w-full divide-y divide-border">
<thead className="sticky -top-4 bg-midnight-800 text-left">
<tr>
<th scope="col" className={headerClassName}>
Email
</th>
<th scope="col" className={headerClassName}>
GitHub username
</th>
<th scope="col" className={headerClassName}>
id
</th>
<th scope="col" className={headerClassName}>
Created At
</th>
<th scope="col" className={headerClassName}>
Admin?
</th>
<th scope="col" className={headerClassName}>
Actions
</th>
</tr>
</thead>
<tbody className="divide-y divide-border">
{users.map((user) => {
return (
<tr key={user.id} className="w-full px-4 py-2 text-left hover:bg-slate-900">
<td className={cellClassName}>{user.email}</td>
<td className={cellClassName}>
<a
href={`https://github.com/${user.displayName}`}
target="_blank"
className="text-indigo-500 underline"
rel="noreferrer"
>
{user.displayName}
</a>
</td>
<td className={cellClassName}>{user.id}</td>
<td className={cellClassName}>{user.createdAt.toISOString()}</td>
<td className={cellClassName}>{user.admin ? "✅" : ""}</td>
<td className={cellClassName}>
<Form method="post" reloadDocument>
<input type="hidden" name="id" value={user.id} />
<Button
type="submit"
name="action"
value="impersonate"
className="mr-2"
variant="primary/small"
>
{user.displayName}
</a>
</td>
<td className={cellClassName}>{user.id}</td>
<td className={cellClassName}>{user.createdAt.toISOString()}</td>
<td className={cellClassName}>{user.admin ? "✅" : ""}</td>
<td className={cellClassName}>
<Form method="post" reloadDocument>
<input type="hidden" name="id" value={user.id} />
<Button
type="submit"
name="action"
value="impersonate"
className="mr-2"
variant="primary/small"
>
Impersonate
</Button>
</Form>
</td>
</tr>
);
})}
</tbody>
</table>
</section>
Impersonate
</Button>
</Form>
</td>
</tr>
);
})}
</tbody>
</table>
</main>
);
}
@@ -1,9 +1,9 @@
import { ActionArgs, json, redirect } from "@remix-run/server-runtime";
import { ActionFunctionArgs, json, redirect } from "@remix-run/server-runtime";
import { prisma } from "~/db.server";
import { authenticateApiRequest } from "~/services/apiAuth.server";
import { workerQueue } from "~/services/worker.server";
export async function action({ request }: ActionArgs) {
export async function action({ request }: ActionFunctionArgs) {
// Next authenticate the request
const authenticationResult = await authenticateApiRequest(request);
+2 -2
View File
@@ -1,10 +1,10 @@
import { HomeIcon } from "@heroicons/react/24/outline";
import { Outlet } from "@remix-run/react";
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { redirect, typedjson, useTypedLoaderData } from "remix-typedjson";
import { getUser, requireUserId } from "~/services/session.server";
export async function loader({ request }: LoaderArgs) {
export async function loader({ request }: LoaderFunctionArgs) {
await requireUserId(request);
const user = await getUser(request);
if (user == null) {
@@ -1,4 +1,4 @@
import { ActionArgs } from "@remix-run/server-runtime";
import { ActionFunctionArgs } from "@remix-run/server-runtime";
/*
To use this route, use the stripe CLI to forward events to this route:
@@ -9,7 +9,7 @@ import { ActionArgs } from "@remix-run/server-runtime";
stripe trigger price.created
*/
export async function action({ request }: ActionArgs) {
export async function action({ request }: ActionFunctionArgs) {
const body: any = await request.json();
const response = await fetch("https://jsonhero.io/api/create.json", {
@@ -1,4 +1,4 @@
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { z } from "zod";
import { prisma } from "~/db.server";
@@ -11,7 +11,7 @@ const ParamsSchema = z.object({
id: z.string(),
});
export async function loader({ request, params }: LoaderArgs) {
export async function loader({ request, params }: LoaderFunctionArgs) {
logger.info("Fetching auth", { url: request.url });
const parsedParams = ParamsSchema.safeParse(params);
@@ -1,4 +1,4 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { z } from "zod";
import { authenticateApiRequest } from "~/services/apiAuth.server";
@@ -11,7 +11,7 @@ const ParamsSchema = z.object({
key: z.string(),
});
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
logger.info("Unregistering schedule", { url: request.url });
// Ensure this is a POST request
@@ -1,4 +1,4 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { RegisterScheduleBodySchema, RegisterScheduleResponseBodySchema } from "@trigger.dev/core";
import { z } from "zod";
@@ -11,7 +11,7 @@ const ParamsSchema = z.object({
id: z.string(),
});
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
logger.info("Initializing schedule", { url: request.url });
// Ensure this is a POST request
@@ -1,4 +1,4 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { UpdateTriggerSourceBodyV1Schema } from "@trigger.dev/core";
import { z } from "zod";
@@ -11,7 +11,7 @@ const ParamsSchema = z.object({
id: z.string(),
});
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
logger.info("Updating source", { url: request.url });
// Ensure this is a POST request
@@ -1,4 +1,4 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { RegisterTriggerBodySchemaV1 } from "@trigger.dev/core";
import { z } from "zod";
@@ -12,7 +12,7 @@ const ParamsSchema = z.object({
key: z.string(),
});
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
logger.info("Registering trigger", { url: request.url });
// Ensure this is a POST request
@@ -1,4 +1,4 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { InitializeTriggerBodySchema } from "@trigger.dev/core";
import { z } from "zod";
@@ -11,7 +11,7 @@ const ParamsSchema = z.object({
id: z.string(),
});
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
logger.info("Initializing trigger", { url: request.url });
// Ensure this is a POST request
@@ -1,4 +1,4 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import {
CreateExternalConnectionBody,
@@ -16,7 +16,7 @@ const ParamsSchema = z.object({
clientSlug: z.string(),
});
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
// Ensure this is a POST request
if (request.method.toUpperCase() !== "POST") {
return { status: 405, body: "Method Not Allowed" };
@@ -1,4 +1,4 @@
import { ActionArgs, json } from "@remix-run/server-runtime";
import { ActionFunctionArgs, json } from "@remix-run/server-runtime";
import {
EndpointIndexErrorSchema,
GetEndpointIndexResponse,
@@ -14,7 +14,7 @@ const ParamsSchema = z.object({
indexId: z.string(),
});
export async function loader({ request, params }: ActionArgs) {
export async function loader({ request, params }: ActionFunctionArgs) {
if (request.method.toUpperCase() !== "GET") {
return { status: 405, body: "Method Not Allowed" };
}
@@ -1,4 +1,4 @@
import { ActionArgs, json } from "@remix-run/server-runtime";
import { ActionFunctionArgs, json } from "@remix-run/server-runtime";
import { z } from "zod";
import { prisma } from "~/db.server";
import { authenticateApiRequest } from "~/services/apiAuth.server";
@@ -14,7 +14,7 @@ const BodySchema = z.object({
data: z.any().optional(),
});
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
// Ensure this is a POST request
if (request.method.toUpperCase() !== "POST") {
return { status: 405, body: "Method Not Allowed" };
@@ -1,4 +1,4 @@
import { ActionArgs, LoaderArgs, json } from "@remix-run/server-runtime";
import { ActionFunctionArgs, LoaderFunctionArgs, json } from "@remix-run/server-runtime";
import { RuntimeEnvironmentType } from "@trigger.dev/database";
import { z } from "zod";
import { $transaction, PrismaClient, prisma } from "~/db.server";
@@ -12,7 +12,7 @@ const ParamsSchema = z.object({
indexHookIdentifier: z.string(),
});
export async function loader({ params }: LoaderArgs) {
export async function loader({ params }: LoaderFunctionArgs) {
const parsedParams = ParamsSchema.safeParse(params);
if (!parsedParams.success) {
@@ -39,7 +39,7 @@ export async function loader({ params }: LoaderArgs) {
});
}
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
const parsedParams = ParamsSchema.safeParse(params);
if (!parsedParams.success) {
+2 -2
View File
@@ -1,4 +1,4 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { z } from "zod";
import { generateErrorMessage } from "zod-error";
@@ -11,7 +11,7 @@ const BodySchema = z.object({
id: z.string(),
});
export async function action({ request }: ActionArgs) {
export async function action({ request }: ActionFunctionArgs) {
logger.info("action", { url: request.url });
// Ensure this is a POST request
@@ -0,0 +1,49 @@
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { z } from "zod";
import { authenticateApiRequest } from "~/services/apiAuth.server";
import { CancelEventService } from "~/services/events/cancelEvent.server";
import { logger } from "~/services/logger.server";
import { CancelRunsForEventService } from "~/services/events/cancelRunsForEvent.server";
const ParamsSchema = z.object({
eventId: z.string(),
});
export async function action({ request, params }: ActionFunctionArgs) {
// Ensure this is a POST request
if (request.method.toUpperCase() !== "POST") {
return { status: 405, body: "Method Not Allowed" };
}
// Next authenticate the request
const authenticationResult = await authenticateApiRequest(request);
if (!authenticationResult) {
return json({ error: "Invalid or Missing API key" }, { status: 401 });
}
const authenticatedEnv = authenticationResult.environment;
const parsed = ParamsSchema.safeParse(params);
if (!parsed.success) {
return json({ error: "Invalid or Missing eventId" }, { status: 400 });
}
const { eventId } = parsed.data;
const service = new CancelRunsForEventService();
try {
const res = await service.call(authenticatedEnv, eventId);
if (!res) {
return json({ error: "Event not found" }, { status: 404 });
}
return json(res);
} catch (err) {
logger.error("CancelRunsForEventService.call() error", { error: err });
return json({ error: "Internal Server Error" }, { status: 500 });
}
}
@@ -1,4 +1,4 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { z } from "zod";
import { eventRecordToApiJson } from "~/api.server";
@@ -10,7 +10,7 @@ const ParamsSchema = z.object({
eventId: z.string(),
});
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
// Ensure this is a POST request
if (request.method.toUpperCase() !== "POST") {
return { status: 405, body: "Method Not Allowed" };
@@ -1,4 +1,4 @@
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { GetEvent } from "@trigger.dev/core";
import { z } from "zod";
@@ -10,7 +10,7 @@ const ParamsSchema = z.object({
eventId: z.string(),
});
export async function loader({ request, params }: LoaderArgs) {
export async function loader({ request, params }: LoaderFunctionArgs) {
if (request.method.toUpperCase() === "OPTIONS") {
return apiCors(request, json({}));
}
+2 -2
View File
@@ -1,4 +1,4 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { SendEventBodySchema } from "@trigger.dev/core";
import { generateErrorMessage } from "zod-error";
@@ -6,7 +6,7 @@ import { authenticateApiRequest } from "~/services/apiAuth.server";
import { IngestSendEvent } from "~/services/events/ingestSendEvent.server";
import { eventRecordToApiJson } from "~/api.server";
export async function action({ request }: ActionArgs) {
export async function action({ request }: ActionFunctionArgs) {
// Ensure this is a POST request
if (request.method.toUpperCase() !== "POST") {
return { status: 405, body: "Method Not Allowed" };
@@ -1,4 +1,4 @@
import { LoaderArgs, json } from "@remix-run/server-runtime";
import { LoaderFunctionArgs, json } from "@remix-run/server-runtime";
import { z } from "zod";
import { prisma } from "~/db.server";
import { resolveApiConnection } from "~/models/runConnection.server";
@@ -10,7 +10,7 @@ const ParamsSchema = z.object({
connectionId: z.string(),
});
export async function loader({ request, params }: LoaderArgs) {
export async function loader({ request, params }: LoaderFunctionArgs) {
const authenticationResult = await authenticateApiRequest(request);
if (!authenticationResult) {
return json({ error: "Invalid or Missing API key" }, { status: 401 });
@@ -1,4 +1,4 @@
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { z } from "zod";
import { prisma } from "~/db.server";
@@ -14,7 +14,7 @@ const SearchQuerySchema = z.object({
take: z.coerce.number().default(20),
});
export async function loader({ request, params }: LoaderArgs) {
export async function loader({ request, params }: LoaderFunctionArgs) {
if (request.method.toUpperCase() === "OPTIONS") {
return apiCors(request, json({}));
}
@@ -1,4 +1,4 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { PrismaErrorSchema } from "~/db.server";
import { z } from "zod";
@@ -10,7 +10,7 @@ const ParamsSchema = z.object({
runId: z.string(),
});
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
// Ensure this is a POST request
if (request.method.toUpperCase() !== "POST") {
return { status: 405, body: "Method Not Allowed" };
@@ -1,5 +1,5 @@
import type { Organization, RuntimeEnvironment } from "@trigger.dev/database";
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import type { LogMessage } from "@trigger.dev/core";
import { LogMessageSchema } from "@trigger.dev/core";
@@ -13,7 +13,7 @@ const ParamsSchema = z.object({
runId: z.string(),
});
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
// Ensure this is a POST request
if (request.method.toUpperCase() !== "POST") {
return { status: 405, body: "Method Not Allowed" };
@@ -1,4 +1,4 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import {
JobRunStatusRecordSchema,
@@ -19,7 +19,7 @@ const ParamsSchema = z.object({
id: z.string(),
});
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
// Ensure this is a POST request
if (request.method.toUpperCase() !== "PUT") {
return { status: 405, body: "Method Not Allowed" };
@@ -1,4 +1,4 @@
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { JobRunStatusRecordSchema } from "@trigger.dev/core";
import { z } from "zod";
@@ -13,7 +13,7 @@ const ParamsSchema = z.object({
const RecordsSchema = z.array(JobRunStatusRecordSchema);
export async function loader({ request, params }: LoaderArgs) {
export async function loader({ request, params }: LoaderFunctionArgs) {
if (request.method.toUpperCase() === "OPTIONS") {
return apiCors(request, json({}));
}
@@ -1,4 +1,4 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { RuntimeEnvironmentType } from "@trigger.dev/database";
import { z } from "zod";
@@ -12,7 +12,7 @@ const ParamsSchema = z.object({
secret: z.string(),
});
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
// Ensure this is a POST request
if (request.method.toUpperCase() !== "POST") {
return { status: 405, body: "Method Not Allowed" };
@@ -1,4 +1,4 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import type { CompleteTaskBodyOutput, ServerTask } from "@trigger.dev/core";
import { CompleteTaskBodyInputSchema } from "@trigger.dev/core";
@@ -14,7 +14,7 @@ const ParamsSchema = z.object({
id: z.string(),
});
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
// Ensure this is a POST request
if (request.method.toUpperCase() !== "POST") {
return { status: 405, body: "Method Not Allowed" };
@@ -1,4 +1,4 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { FailTaskBodyInput, FailTaskBodyInputSchema, ServerTask } from "@trigger.dev/core";
import { z } from "zod";
@@ -14,7 +14,7 @@ const ParamsSchema = z.object({
id: z.string(),
});
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
// Ensure this is a POST request
if (request.method.toUpperCase() !== "POST") {
return { status: 405, body: "Method Not Allowed" };
@@ -1,4 +1,4 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { TaskStatus } from "@trigger.dev/database";
import {
@@ -28,7 +28,7 @@ const HeadersSchema = z.object({
"x-cached-tasks-cursor": z.string().optional().nullable(),
});
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
// Ensure this is a POST request
if (request.method.toUpperCase() !== "POST") {
return { status: 405, body: "Method Not Allowed" };
+2 -2
View File
@@ -1,4 +1,4 @@
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { z } from "zod";
import { ApiRunPresenter } from "~/presenters/ApiRunPresenter.server";
@@ -17,7 +17,7 @@ const SearchQuerySchema = z.object({
taskdetails: z.coerce.boolean().default(false),
});
export async function loader({ request, params }: LoaderArgs) {
export async function loader({ request, params }: LoaderFunctionArgs) {
if (request.method.toUpperCase() === "OPTIONS") {
return apiCors(request, json({}));
}
@@ -1,9 +1,9 @@
import type { ActionArgs, LoaderArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/server-runtime";
import { z } from "zod";
import { logger } from "~/services/logger.server";
import { HandleHttpSourceService } from "~/services/sources/handleHttpSource.server";
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
logger.info("Handling http source", { url: request.url });
try {
@@ -26,7 +26,7 @@ export async function action({ request, params }: ActionArgs) {
}
}
export async function loader({ request, params }: LoaderArgs) {
export async function loader({ request, params }: LoaderFunctionArgs) {
logger.info("Handling http source", { url: request.url });
const { id } = z.object({ id: z.string() }).parse(params);
+2 -2
View File
@@ -1,9 +1,9 @@
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { prisma } from "~/db.server";
import { authenticateApiRequest } from "~/services/apiAuth.server";
export async function loader({ request }: LoaderArgs) {
export async function loader({ request }: LoaderFunctionArgs) {
// Next authenticate the request
const authenticationResult = await authenticateApiRequest(request);
@@ -1,4 +1,4 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { UpdateTriggerSourceBodyV2Schema } from "@trigger.dev/core";
import { z } from "zod";
@@ -11,7 +11,7 @@ const ParamsSchema = z.object({
id: z.string(),
});
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
logger.info("Updating source", { url: request.url });
// Ensure this is a POST request
@@ -1,4 +1,4 @@
import type { ActionArgs } from "@remix-run/server-runtime";
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import {
REGISTER_SOURCE_EVENT_V2,
@@ -17,7 +17,7 @@ const ParamsSchema = z.object({
key: z.string(),
});
export async function action({ request, params }: ActionArgs) {
export async function action({ request, params }: ActionFunctionArgs) {
logger.info("Registering trigger", { url: request.url });
// Ensure this is a POST request
@@ -127,7 +127,8 @@ export default function Page() {
const [form, { name, email, confirmEmail }] = useForm({
id: "confirm-basic-details",
lastSubmission,
// TODO: type this
lastSubmission: lastSubmission as any,
onValidate({ formData }) {
return parse(formData, { schema: createSchema() });
},
+2 -2
View File
@@ -1,9 +1,9 @@
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { getInviteFromToken } from "~/models/member.server";
import { redirectWithErrorMessage, redirectWithSuccessMessage } from "~/models/message.server";
import { getUser } from "~/services/session.server";
export async function loader({ request }: LoaderArgs) {
export async function loader({ request }: LoaderFunctionArgs) {
const user = await getUser(request);
const url = new URL(request.url);
+4 -3
View File
@@ -1,6 +1,6 @@
import { conform, useForm } from "@conform-to/react";
import { parse } from "@conform-to/zod";
import { ActionFunction, LoaderArgs, json, redirect } from "@remix-run/node";
import { ActionFunction, LoaderFunctionArgs, json, redirect } from "@remix-run/node";
import { Form, useActionData } from "@remix-run/react";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { z } from "zod";
@@ -18,7 +18,7 @@ import { redirectWithSuccessMessage } from "~/models/message.server";
import { requireUser, requireUserId } from "~/services/session.server";
import { invitesPath, organizationsPath } from "~/utils/pathBuilder";
export const loader = async ({ request }: LoaderArgs) => {
export const loader = async ({ request }: LoaderFunctionArgs) => {
const user = await requireUser(request);
//if there are no invites left we should redirect to the orgs page
@@ -94,7 +94,8 @@ export default function Page() {
const [form, { inviteId }] = useForm({
id: "accept-invite",
lastSubmission,
// TODO: type this
lastSubmission: lastSubmission as any,
onValidate({ formData }) {
return parse(formData, { schema });
},
+23 -6
View File
@@ -1,6 +1,15 @@
import type { LoaderArgs } from "@remix-run/node";
import type { DataFunctionArgs, LoaderFunctionArgs, SerializeFrom } from "@remix-run/node";
import { Form } from "@remix-run/react";
import { TypedMetaFunction, redirect, typedjson, useTypedLoaderData } from "remix-typedjson";
import { ServerRuntimeMetaArgs, ServerRuntimeMetaDescriptor } from "@remix-run/server-runtime";
import { getMatchesData, metaV1 } from "@remix-run/v1-meta";
import {
TypedJsonResponse,
TypedMetaFunction,
UseDataFunctionReturn,
redirect,
typedjson,
useTypedLoaderData,
} from "remix-typedjson";
import { LogoIcon } from "~/components/LogoIcon";
import { AppContainer, MainCenteredContainer } from "~/components/layout/AppLayout";
import { Button, LinkButton } from "~/components/primitives/Buttons";
@@ -16,11 +25,19 @@ import { getUserId } from "~/services/session.server";
import { appEnvTitleTag } from "~/utils";
import { requestUrl } from "~/utils/requestUrl.server";
export const meta: TypedMetaFunction<typeof loader, { root: RootLoader }> = ({ parentsData }) => ({
title: `Login to Trigger.dev${appEnvTitleTag(parentsData?.root.appEnv)}`,
});
export const meta: TypedMetaFunction<typeof loader> = (args) => {
const matchesData = getMatchesData(args) as { root: UseDataFunctionReturn<RootLoader> };
export async function loader({ request }: LoaderArgs) {
return metaV1(args, {
title: `Login to Trigger.dev${appEnvTitleTag(matchesData.root.appEnv)}`,
});
};
export type PromiseReturnType<T extends (...arguments_: any) => Promise<any>> = Awaited<
ReturnType<T>
>;
export async function loader({ request }: LoaderFunctionArgs) {
const userId = await getUserId(request);
if (userId) return redirect("/");
+18 -8
View File
@@ -1,7 +1,12 @@
import type { ActionArgs, LoaderArgs } from "@remix-run/node";
import type { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node";
import { redirect } from "@remix-run/node";
import { Form, useNavigation, useTransition } from "@remix-run/react";
import { TypedMetaFunction, typedjson, useTypedLoaderData } from "remix-typedjson";
import { Form, useNavigation } from "@remix-run/react";
import {
TypedMetaFunction,
UseDataFunctionReturn,
typedjson,
useTypedLoaderData,
} from "remix-typedjson";
import { z } from "zod";
import { LogoIcon } from "~/components/LogoIcon";
import { AppContainer, MainCenteredContainer } from "~/components/layout/AppLayout";
@@ -21,12 +26,17 @@ import type { LoaderType as RootLoader } from "~/root";
import { appEnvTitleTag } from "~/utils";
import { TextLink } from "~/components/primitives/TextLink";
import { FormError } from "~/components/primitives/FormError";
import { getMatchesData, metaV1 } from "@remix-run/v1-meta";
export const meta: TypedMetaFunction<typeof loader, { root: RootLoader }> = ({ parentsData }) => ({
title: `Login to Trigger.dev${appEnvTitleTag(parentsData?.root.appEnv)}`,
});
export const meta: TypedMetaFunction<typeof loader> = (args) => {
const matchesData = getMatchesData(args) as { root: UseDataFunctionReturn<RootLoader> };
export async function loader({ request }: LoaderArgs) {
return metaV1(args, {
title: `Login to Trigger.dev${appEnvTitleTag(matchesData.root.appEnv)}`,
});
};
export async function loader({ request }: LoaderFunctionArgs) {
await authenticator.isAuthenticated(request, {
successRedirect: "/",
});
@@ -54,7 +64,7 @@ export async function loader({ request }: LoaderArgs) {
);
}
export async function action({ request }: ActionArgs) {
export async function action({ request }: ActionFunctionArgs) {
const clonedRequest = request.clone();
const payload = Object.fromEntries(await clonedRequest.formData());
+2 -2
View File
@@ -1,8 +1,8 @@
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { authenticator } from "~/services/auth.server";
import { getRedirectTo } from "~/services/redirectTo.server";
export async function loader({ request }: LoaderArgs) {
export async function loader({ request }: LoaderFunctionArgs) {
const redirectTo = await getRedirectTo(request);
await authenticator.authenticate("email-link", request, {
+2 -2
View File
@@ -1,4 +1,4 @@
import type { LoaderArgs } from "@remix-run/server-runtime";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { redirect } from "@remix-run/server-runtime";
import z from "zod";
import { prisma } from "~/db.server";
@@ -17,7 +17,7 @@ const ParamsSchema = z
})
.passthrough();
export async function loader({ request }: LoaderArgs) {
export async function loader({ request }: LoaderFunctionArgs) {
if (request.method.toUpperCase() !== "GET") {
return { status: 405, body: "Method Not Allowed" };
}

Some files were not shown because too many files have changed in this diff Show More