Initial commit

This commit is contained in:
2026-06-24 23:47:55 -07:00
commit d134b480a0
297 changed files with 30726 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
export class ApiError extends Error {
public readonly name = "ApiError"
constructor(
message: string,
public readonly status: number
) {
super(message)
}
}
export default ApiError
+44
View File
@@ -0,0 +1,44 @@
/** Keep in sync with api/src/controllers/base-controller.ts#ModelOrder */
export type ModelOrder =
| [string, string]
| [string, string, string]
| [string, string, string, string]
| [string, string, string, string, string]
| [string, string, string, string, string, string]
export type Policy = {
show: boolean
create: boolean
update: boolean
destroy: boolean
}
export type WhereOptions<Model, Attributes extends keyof Model> = {
[K in Attributes]?: Model[K] | Model[K][]
}
export type FiltersOptions<Options> = Partial<Options>
export type QueryOptions<WhereOptions, FiltersOptions> = Partial<{
where: WhereOptions
filters: FiltersOptions
order: ModelOrder[]
page: number
perPage: number
}>
// Keep in sync with api/src/controllers/base-controller.ts
export const MAX_PER_PAGE = 1000
export type ApiResponseError = { field?: string; text: string }
export type ApiResponseLegacy<T> = {
data: T
errors: ApiResponseError[]
messages?: string[]
}
export type ApiResponse<TPayload = object> = {
errors: ApiResponseError[]
messages?: string[]
} & TPayload
+23
View File
@@ -0,0 +1,23 @@
import http from "@/api/http-client"
import { type Policy } from "@/api/base-api"
import { UserRoles, type User } from "@/api/users-api"
export { UserRoles }
export type UserAsShow = Pick<
User,
"id" | "email" | "firstName" | "lastName" | "displayName" | "roles" | "createdAt" | "updatedAt"
>
export const currentUserApi = {
async get(): Promise<{
user: UserAsShow
policy: Policy
}> {
const { data } = await http.get(`/api/current-user`)
return data
},
}
export default currentUserApi
+46
View File
@@ -0,0 +1,46 @@
import qs from "qs"
import axios from "axios"
import { API_BASE_URL } from "@/config"
import auth0 from "@/plugins/auth0-plugin"
import ApiError from "@/api/api-error"
export const httpClient = axios.create({
baseURL: API_BASE_URL,
headers: {
"Content-Type": "application/json",
},
paramsSerializer: {
serialize: (params) => {
return qs.stringify(params, {
arrayFormat: "indices",
strictNullHandling: true,
})
},
},
})
httpClient.interceptors.request.use(async (config) => {
// Only add the Authorization header to requests that start with "/api"
if (config.url?.startsWith("/api")) {
const accessToken = await auth0.getAccessTokenSilently()
config.headers["Authorization"] = `Bearer ${accessToken}`
}
return config
})
// Any status codes that falls outside the range of 2xx causes this function to trigger
httpClient.interceptors.response.use(null, async (error) => {
if (error?.error === "login_required") {
throw new ApiError("You must be logged in to access this endpoint", 401)
} else if (error?.response?.data?.message) {
throw new ApiError(error.response.data.message, error.response.status)
} else if (error.message) {
throw new ApiError(error.message, error.response?.status || 500)
} else {
throw new ApiError("An unknown error occurred", error.response?.status || 500)
}
})
export default httpClient
+18
View File
@@ -0,0 +1,18 @@
import http from "@/api/http-client"
export type Status = {
RELEASE_TAG: string
GIT_COMMIT_HASH: string
}
export const statusApi = {
/**
* Note: This is a public API route, and not protected by authentication
*/
async get(): Promise<Status> {
const { data } = await http.get("/_status")
return data
},
}
export default statusApi
+81
View File
@@ -0,0 +1,81 @@
import http from "@/api/http-client"
import {
type FiltersOptions,
type ModelOrder,
type Policy,
type WhereOptions,
} from "@/api/base-api"
/** Keep in sync with api/src/models/user.ts */
export enum UserRoles {
SYSTEM_ADMIN = "system_admin",
USER = "user",
}
export type User = {
id: number
email: string
firstName: string
lastName: string
displayName: string
roles: UserRoles[]
createdAt: string
updatedAt: string
}
export type UserAsShow = Omit<User, "organizations"> & {}
export type UserWhereOptions = WhereOptions<User, "email" | "firstName" | "lastName">
export type UserFiltersOptions = FiltersOptions<{
search: string | string[]
}>
export type UserQueryOptions = {
where?: UserWhereOptions
filters?: UserFiltersOptions
order?: ModelOrder[]
page?: number
perPage?: number
}
export const usersApi = {
UserRoles,
async list(params: UserQueryOptions = {}): Promise<{
users: User[]
totalCount: number
}> {
const { data } = await http.get("/api/users", {
params,
})
return data
},
async get(userId: number): Promise<{
user: User
policy: Policy
}> {
const { data } = await http.get(`/api/users/${userId}`)
return data
},
async create(attributes: Partial<User>): Promise<{
user: User
}> {
const { data } = await http.post("/api/users", attributes)
return data
},
async update(
userId: number,
attributes: Partial<User>
): Promise<{
user: User
}> {
const { data } = await http.patch(`/api/users/${userId}`, attributes)
return data
},
async delete(userId: number): Promise<void> {
const { data } = await http.delete(`/api/users/${userId}`)
return data
},
}
export default usersApi