diff --git a/Foxnouns.Backend/Controllers/InternalController.cs b/Foxnouns.Backend/Controllers/InternalController.cs
index 0f857ef..c19d456 100644
--- a/Foxnouns.Backend/Controllers/InternalController.cs
+++ b/Foxnouns.Backend/Controllers/InternalController.cs
@@ -15,6 +15,9 @@ public partial class InternalController(DatabaseContext db) : ControllerBase
[GeneratedRegex(@"(\{\w+\})")]
private static partial Regex PathVarRegex();
+ [GeneratedRegex(@"\{id\}")]
+ private static partial Regex IdCountRegex();
+
private static string GetCleanedTemplate(string template)
{
if (template.StartsWith("api/v2"))
@@ -22,8 +25,19 @@ public partial class InternalController(DatabaseContext db) : ControllerBase
template = PathVarRegex()
.Replace(template, "{id}") // Replace all path variables (almost always IDs) with `{id}`
.Replace("@me", "{id}"); // Also replace hardcoded `@me` with `{id}`
+
+ // If there's at least one path parameter, we only return the *first* part of the path.
if (template.Contains("{id}"))
+ {
+ // However, if the path starts with /users/{id} *and* there's another path parameter (such as a member ID)
+ // we ignore the leading /users/{id}. This is because a lot of routes are scoped by user, but should have
+ // separate rate limits from other user-scoped routes.
+ if (template.StartsWith("/users/{id}/") && IdCountRegex().Count(template) >= 2)
+ template = template["/users/{id}".Length..];
+
return template.Split("{id}")[0] + "{id}";
+ }
+
return template;
}
diff --git a/Foxnouns.Frontend/src/lib/api/index.ts b/Foxnouns.Frontend/src/lib/api/index.ts
index 0c8293d..f7a517d 100644
--- a/Foxnouns.Frontend/src/lib/api/index.ts
+++ b/Foxnouns.Frontend/src/lib/api/index.ts
@@ -6,11 +6,31 @@ import log from "$lib/log";
export type Method = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
+/**
+ * Optional arguments for a request. `load` and `action` functions should always pass `fetch` and `cookies`.
+ */
export type RequestArgs = {
+ /**
+ * The token for this request. Where possible, `cookies` should be passed instead.
+ * Will override `cookies` if both are passed.
+ */
token?: string;
+ /**
+ * Whether this request is to an internal endpoint.
+ * Internal requests bypass the rate limiter and are prefixed with /api/internal/ rather than /api/v2/.
+ */
isInternal?: boolean;
+ /**
+ * The body for this request, which will be serialized to JSON. Should be a plain JS object.
+ */
body?: any;
+ /**
+ * The fetch function to use. Should be passed in loader and action functions, but can be safely ignored for client-side requests.
+ */
fetch?: typeof fetch;
+ /**
+ * The cookies object to try to get the token from. Can only be passed in loader and action functions.
+ */
cookies?: Cookies;
};
@@ -19,7 +39,7 @@ export type RequestArgs = {
* @param method The HTTP method for this request
* @param path The path for this request, without the /api/v2 prefix, starting with a slash.
* @param args Optional arguments to the request function.
- * @returns A Promise object.
+ * @returns A Response object.
*/
export async function baseRequest(
method: Method,
@@ -29,7 +49,7 @@ export async function baseRequest(
const token = args.token ?? args.cookies?.get(TOKEN_COOKIE_NAME);
const fetchFn = args.fetch ?? fetch;
- const url = `${PUBLIC_API_BASE}/${args.isInternal ? "internal" : "v2"}${path}`;
+ const url = `/${args.isInternal ? "internal" : "v2"}${path}`;
log.debug("Sending request to %s %s", method, url);
@@ -38,7 +58,7 @@ export async function baseRequest(
...(token ? { Authorization: token } : {}),
};
- return await fetchFn(url, {
+ return await fetchFn(PUBLIC_API_BASE + url, {
method,
headers,
body: args.body ? JSON.stringify(args.body) : undefined,
diff --git a/Foxnouns.Frontend/src/lib/components/editor/FieldEditor.svelte b/Foxnouns.Frontend/src/lib/components/editor/FieldEditor.svelte
index c2536dd..b409290 100644
--- a/Foxnouns.Frontend/src/lib/components/editor/FieldEditor.svelte
+++ b/Foxnouns.Frontend/src/lib/components/editor/FieldEditor.svelte
@@ -65,7 +65,7 @@
onclick={() => move(index, false)}
/>
{$t("editor.field-name")}
-
+
diff --git a/Foxnouns.Frontend/src/lib/components/editor/FieldEntryEditor.svelte b/Foxnouns.Frontend/src/lib/components/editor/FieldEntryEditor.svelte
index 65c2355..5e407ac 100644
--- a/Foxnouns.Frontend/src/lib/components/editor/FieldEntryEditor.svelte
+++ b/Foxnouns.Frontend/src/lib/components/editor/FieldEntryEditor.svelte
@@ -40,7 +40,7 @@
tooltip={$t("editor.move-entry-down")}
onclick={() => moveValue(index, false)}
/>
-
+
diff --git a/Foxnouns.Frontend/src/lib/components/editor/FieldsEditor.svelte b/Foxnouns.Frontend/src/lib/components/editor/FieldsEditor.svelte
index 6bbba9c..bcd3e78 100644
--- a/Foxnouns.Frontend/src/lib/components/editor/FieldsEditor.svelte
+++ b/Foxnouns.Frontend/src/lib/components/editor/FieldsEditor.svelte
@@ -56,6 +56,7 @@
class="form-control"
bind:value={newFieldName}
placeholder={$t("editor.field-name")}
+ autocomplete="off"
/>
diff --git a/Foxnouns.Frontend/src/lib/components/editor/PronounEntryEditor.svelte b/Foxnouns.Frontend/src/lib/components/editor/PronounEntryEditor.svelte
index aee6859..bcf5c15 100644
--- a/Foxnouns.Frontend/src/lib/components/editor/PronounEntryEditor.svelte
+++ b/Foxnouns.Frontend/src/lib/components/editor/PronounEntryEditor.svelte
@@ -50,7 +50,7 @@
tooltip={$t("editor.move-entry-down")}
onclick={() => moveValue(index, true)}
/>
-
+
@@ -88,6 +88,7 @@
type="text"
class="form-control"
bind:value={value.display_text}
+ autocomplete="off"
/>