aboutsummaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-rw-r--r--packages/contracts/src/index.ts2
-rw-r--r--packages/contracts/src/public.ts70
-rw-r--r--packages/contracts/src/schemas/api.ts8
-rw-r--r--packages/contracts/src/schemas/public.ts15
-rw-r--r--packages/contracts/src/schemas/shared.ts14
-rw-r--r--packages/contracts/src/schemas/util.ts16
-rw-r--r--packages/shared-types/src/index.ts11
7 files changed, 119 insertions, 17 deletions
diff --git a/packages/contracts/src/index.ts b/packages/contracts/src/index.ts
index e7f383639..1224a0e19 100644
--- a/packages/contracts/src/index.ts
+++ b/packages/contracts/src/index.ts
@@ -4,6 +4,7 @@ import { apeKeysContract } from "./ape-keys";
import { configsContract } from "./configs";
import { presetsContract } from "./presets";
import { psasContract } from "./psas";
+import { publicContract } from "./public";
const c = initContract();
@@ -13,4 +14,5 @@ export const contract = c.router({
configs: configsContract,
presets: presetsContract,
psas: psasContract,
+ public: publicContract,
});
diff --git a/packages/contracts/src/public.ts b/packages/contracts/src/public.ts
new file mode 100644
index 000000000..93b2b81f9
--- /dev/null
+++ b/packages/contracts/src/public.ts
@@ -0,0 +1,70 @@
+import { initContract } from "@ts-rest/core";
+import { z } from "zod";
+import {
+ CommonResponses,
+ EndpointMetadata,
+ responseWithData,
+} from "./schemas/api";
+import { SpeedHistogramSchema, TypingStatsSchema } from "./schemas/public";
+import { Mode2Schema, ModeSchema } from "./schemas/shared";
+import { LanguageSchema } from "./schemas/util";
+
+export const GetSpeedHistogramQuerySchema = z
+ .object({
+ language: LanguageSchema,
+ mode: ModeSchema,
+ mode2: Mode2Schema,
+ })
+ .strict();
+export type GetSpeedHistogramQuery = z.infer<
+ typeof GetSpeedHistogramQuerySchema
+>;
+
+export const GetSpeedHistogramResponseSchema =
+ responseWithData(SpeedHistogramSchema);
+export type GetSpeedHistogramResponse = z.infer<
+ typeof GetSpeedHistogramResponseSchema
+>;
+
+export const GetTypingStatsResponseSchema = responseWithData(TypingStatsSchema);
+export type GetTypingStatsResponse = z.infer<
+ typeof GetTypingStatsResponseSchema
+>;
+
+const c = initContract();
+export const publicContract = c.router(
+ {
+ getSpeedHistogram: {
+ summary: "get speed histogram",
+ description:
+ "get number of users personal bests grouped by wpm level (multiples of ten)",
+ method: "GET",
+ path: "/speedHistogram",
+ query: GetSpeedHistogramQuerySchema,
+ responses: {
+ 200: GetSpeedHistogramResponseSchema,
+ },
+ },
+
+ getTypingStats: {
+ summary: "get typing stats",
+ description: "get number of tests and time users spend typing.",
+ method: "GET",
+ path: "/typingStats",
+ responses: {
+ 200: GetTypingStatsResponseSchema,
+ },
+ },
+ },
+ {
+ pathPrefix: "/public",
+ strictStatusCodes: true,
+ metadata: {
+ openApiTags: "public",
+ authenticationOptions: {
+ isPublic: true,
+ },
+ } as EndpointMetadata,
+ commonResponses: CommonResponses,
+ }
+);
diff --git a/packages/contracts/src/schemas/api.ts b/packages/contracts/src/schemas/api.ts
index f90e47dbd..305d6100a 100644
--- a/packages/contracts/src/schemas/api.ts
+++ b/packages/contracts/src/schemas/api.ts
@@ -1,6 +1,12 @@
import { z, ZodSchema } from "zod";
-export type OpenApiTag = "configs" | "presets" | "ape-keys" | "admin" | "psas";
+export type OpenApiTag =
+ | "configs"
+ | "presets"
+ | "ape-keys"
+ | "admin"
+ | "psas"
+ | "public";
export type EndpointMetadata = {
/** Authentication options, by default a bearer token is required. */
diff --git a/packages/contracts/src/schemas/public.ts b/packages/contracts/src/schemas/public.ts
new file mode 100644
index 000000000..5a89122eb
--- /dev/null
+++ b/packages/contracts/src/schemas/public.ts
@@ -0,0 +1,15 @@
+import { z } from "zod";
+import { StringNumberSchema } from "./util";
+
+export const SpeedHistogramSchema = z.record(
+ StringNumberSchema,
+ z.number().int()
+);
+export type SpeedHistogram = z.infer<typeof SpeedHistogramSchema>;
+
+export const TypingStatsSchema = z.object({
+ timeTyping: z.number().nonnegative(),
+ testsCompleted: z.number().int().nonnegative(),
+ testsStarted: z.number().int().nonnegative(),
+});
+export type TypingStats = z.infer<typeof TypingStatsSchema>;
diff --git a/packages/contracts/src/schemas/shared.ts b/packages/contracts/src/schemas/shared.ts
index 0826839a8..5eb97b5d4 100644
--- a/packages/contracts/src/schemas/shared.ts
+++ b/packages/contracts/src/schemas/shared.ts
@@ -1,4 +1,4 @@
-import { z } from "zod";
+import { literal, z } from "zod";
import { StringNumberSchema } from "./util";
//used by config and shared
@@ -33,9 +33,19 @@ export const PersonalBestsSchema = z.object({
});
export type PersonalBests = z.infer<typeof PersonalBestsSchema>;
-//used by user and config
+//used by user, config, public
export const ModeSchema = PersonalBestsSchema.keyof();
export type Mode = z.infer<typeof ModeSchema>;
+
+export const Mode2Schema = z.union(
+ [StringNumberSchema, literal("zen"), literal("custom")],
+ {
+ errorMap: () => ({
+ message: 'Needs to be either a number, "zen" or "custom."',
+ }),
+ }
+);
+
export type Mode2<M extends Mode> = M extends M
? keyof PersonalBests[M]
: never;
diff --git a/packages/contracts/src/schemas/util.ts b/packages/contracts/src/schemas/util.ts
index 74e5ed963..25d9a4b77 100644
--- a/packages/contracts/src/schemas/util.ts
+++ b/packages/contracts/src/schemas/util.ts
@@ -1,8 +1,12 @@
import { z, ZodString } from "zod";
-export const StringNumberSchema = z.custom<`${number}`>((val) => {
- return typeof val === "string" ? /^\d+$/.test(val) : false;
-});
+export const StringNumberSchema = z
+
+ .custom<`${number}`>((val) => {
+ if (typeof val === "number") val = val.toString();
+ return typeof val === "string" ? /^\d+$/.test(val) : false;
+ }, 'Needs to be a number or a number represented as a string e.g. "10".')
+ .transform(String);
export type StringNumber = z.infer<typeof StringNumberSchema>;
@@ -13,3 +17,9 @@ export type Id = z.infer<typeof IdSchema>;
export const TagSchema = token().max(50);
export type Tag = z.infer<typeof TagSchema>;
+
+export const LanguageSchema = z
+ .string()
+ .max(50)
+ .regex(/^[a-zA-Z0-9_+]+$/);
+export type Language = z.infer<typeof LanguageSchema>;
diff --git a/packages/shared-types/src/index.ts b/packages/shared-types/src/index.ts
index fe8d50ea6..d069ecd3c 100644
--- a/packages/shared-types/src/index.ts
+++ b/packages/shared-types/src/index.ts
@@ -301,17 +301,6 @@ export type ResultFilters = {
} & Record<string, boolean>;
};
-export type SpeedHistogram = {
- [key: string]: number;
-};
-
-export type PublicTypingStats = {
- type: string;
- timeTyping: number;
- testsCompleted: number;
- testsStarted: number;
-};
-
export type LeaderboardEntry = {
_id: string;
wpm: number;