1import { Schema } from "effect"
2import { HttpApiEndpoint, HttpApiError, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/http-api"
3import { User, UserId } from "../domain/User.ts"
4import { SearchQueryTooShort, UserNotFound } from "../domain/UserErrors.ts"
5import { Authorization } from "./Authorization.ts"
6
7export class UsersApiGroup extends HttpApiGroup.make("users")
8 .add(
9 HttpApiEndpoint.get("list", "/", {
10 query: {
11 search: Schema.optional(Schema.String)
12 },
13 // Use the `json` variant of the model for API responses. It shares the
14 // field declarations with the database variants, but can encode values
15 // differently where needed.
16 success: Schema.Array(User.json)
17 }),
18 HttpApiEndpoint.get("search", "/search", {
19 // For get requests, payload uses the query string
20 payload: {
21 search: Schema.String
22 },
23 success: [
24 Schema.Array(User.json),
25 Schema.String.pipe(HttpApiSchema.asText({
26 contentType: "text/csv"
27 }))
28 ],
29 error: [
30 SearchQueryTooShort.pipe(
31 // If you want an error to return no content, you can use
32 // `HttpApiSchema.asNoContent` and provide a decoder that transforms the
33 // error into the appropriate type.
34 HttpApiSchema.asNoContent({
35 decode: () => new SearchQueryTooShort()
36 })
37 ),
38 // You can also add some of the built in `HttpApiError`s to handle common
39 // error cases like bad requests, unauthorized, etc.
40 HttpApiError.RequestTimeoutNoContent
41 ]
42 }),
43 HttpApiEndpoint.get("getById", "/:id", {
44 params: {
45 // Path parameter values are automatically coerced from their string
46 // form using `Schema.toCodecStringTree`, so schemas that decode from
47 // other types (like numbers) work here as well.
48 id: UserId
49 },
50 success: User.json,
51 error: UserNotFound.pipe(
52 // If you want an error to return no content, you can use
53 // `HttpApiSchema.asNoContent` and provide a decoder that transforms the
54 // error into the appropriate type.
55 HttpApiSchema.asNoContent({
56 decode: () => new UserNotFound()
57 })
58 )
59 }),
60 HttpApiEndpoint.post("create", "/", {
61 // For post requests, payload uses the request body. It defaults to JSON,
62 // but you can specify other content types as well using
63 // `HttpApiSchema.asText`, `HttpApiSchema.asMultipart`, etc.
64 //
65 // The `jsonCreate` variant only exposes the fields clients are allowed
66 // to provide, so the generated id and timestamps cannot be set here.
67 payload: User.jsonCreate,
68 success: User.json
69 }),
70 HttpApiEndpoint.patch("update", "/:id", {
71 params: {
72 id: UserId
73 },
74 // The `jsonUpdate` variant similarly excludes the id and the managed
75 // timestamps from the update payload.
76 payload: User.jsonUpdate,
77 success: User.json,
78 error: UserNotFound.pipe(
79 HttpApiSchema.asNoContent({
80 decode: () => new UserNotFound()
81 })
82 )
83 }),
84 HttpApiEndpoint.get("me", "/me", {
85 success: User.json,
86 error: UserNotFound.pipe(HttpApiSchema.status(404))
87 })
88 )
89 // You can apply middleware to entire groups, which is useful for things like
90 // authentication and authorization.
91 //
92 // You can also apply middleware to individual endpoints if you need more
93 // fine-grained control.
94 .middleware(Authorization)
95 // To add a common prefix to all endpoints in a group, you can use the `prefix`
96 // method. This is useful for grouping related endpoints together under a common
97 // path segment. In this case, all endpoints in the `UsersApiGroup` will be
98 // prefixed with `/users`.
99 .prefix("/users")
100 // You can add OpenAPI annotations to groups, endpoints, and even parameters and
101 // request bodies. These will be merged together to generate the final OpenAPI
102 // docs for the API
103 .annotateMerge(OpenApi.annotations({
104 title: "Users",
105 description: "User management endpoints"
106 }))
107{}