first commit

This commit is contained in:
Anurag Jangid 2026-10-08 02:44:15 +05:30
commit 6e354ba041
109 changed files with 12110 additions and 0 deletions

261
frontend/src/lib/api.ts Normal file
View file

@ -0,0 +1,261 @@
const API_BASE = import.meta.env.VITE_API_URL || '/api/v1'
export interface User {
id: number
email: string
username: string
full_name: string
employee_id: string | null
phone: string | null
occupation: string | null
is_active: boolean
created_at: string
updated_at: string
}
export interface AuthTokens {
access_token: string
refresh_token: string
token_type: string
user: User
}
export interface RegisterPayload {
email: string
username: string
password: string
full_name: string
employee_id?: string
phone?: string
occupation?: string
}
export interface LoginPayload {
username_or_email: string
password: string
remember_me?: boolean
}
export interface UserUpdatePayload {
full_name?: string
employee_id?: string
phone?: string
occupation?: string
}
class ApiError extends Error {
status: number
constructor(message: string, status: number) {
super(message)
this.status = status
}
}
function parseErrorMessage(data: unknown, status: number): string {
if (!data || typeof data !== 'object') {
if (status >= 500) return 'Server error. Please ensure the backend is running.'
return 'Something went wrong'
}
const record = data as Record<string, unknown>
const detail = record.detail
if (typeof detail === 'string') return detail
if (Array.isArray(detail)) {
return detail
.map((item) => {
if (item && typeof item === 'object' && 'msg' in item) {
return String((item as { msg: unknown }).msg)
}
return JSON.stringify(item)
})
.join(', ')
}
if (typeof record.message === 'string') return record.message
if (status >= 500) return 'Server error. Please ensure the backend is running.'
return 'Something went wrong'
}
async function request<T>(path: string, options: RequestInit = {}): Promise<T> {
const token = localStorage.getItem('silos_access_token')
const headers: Record<string, string> = {
'Content-Type': 'application/json',
...(options.headers as Record<string, string>),
}
if (token) {
headers.Authorization = `Bearer ${token}`
}
let response: Response
try {
response = await fetch(`${API_BASE}${path}`, {
...options,
headers,
})
} catch {
throw new ApiError('Cannot reach the server. Is the backend running on port 8000?', 0)
}
const data = await response.json().catch(() => ({}))
if (!response.ok) {
throw new ApiError(parseErrorMessage(data, response.status), response.status)
}
if (response.status === 204) {
return undefined as T
}
return data as T
}
export const authApi = {
register: (payload: RegisterPayload) =>
request<User>('/auth/register', {
method: 'POST',
body: JSON.stringify(payload),
}),
login: (payload: LoginPayload) =>
request<AuthTokens>('/auth/login', {
method: 'POST',
body: JSON.stringify(payload),
}),
me: () => request<User>('/auth/me'),
updateMe: (payload: UserUpdatePayload) =>
request<User>('/auth/me', {
method: 'PATCH',
body: JSON.stringify(payload),
}),
forgotPassword: (email: string) =>
request<{ message: string; reset_token?: string }>('/auth/forgot-password', {
method: 'POST',
body: JSON.stringify({ email }),
}),
resetPassword: (token: string, new_password: string) =>
request<{ message: string }>('/auth/reset-password', {
method: 'POST',
body: JSON.stringify({ token, new_password }),
}),
}
export function saveAuth(tokens: AuthTokens) {
localStorage.setItem('silos_access_token', tokens.access_token)
localStorage.setItem('silos_refresh_token', tokens.refresh_token)
localStorage.setItem('silos_user', JSON.stringify(tokens.user))
}
export function clearAuth() {
localStorage.removeItem('silos_access_token')
localStorage.removeItem('silos_refresh_token')
localStorage.removeItem('silos_user')
}
export function getStoredUser(): User | null {
const raw = localStorage.getItem('silos_user')
if (!raw) return null
try {
return JSON.parse(raw) as User
} catch {
return null
}
}
export { ApiError }
export interface DocumentMember {
user_id: number
role: 'editor' | 'viewer'
name: string
email: string
}
export interface ApiDocument {
id: string
type: 'shed' | 'silo' | 'sketch' | 'stead'
title: string
template_id?: string | null
stead_id?: string | null
content: Record<string, unknown>
created_by: string
created_at: string
updated_at: string
starred: boolean
last_visited_at: string | null
owner_id?: number | null
visibility?: 'private' | 'members'
members?: DocumentMember[]
}
export interface DocumentCreatePayload {
type: 'shed' | 'silo' | 'sketch' | 'stead'
title: string
content?: Record<string, unknown>
template_id?: string
stead_id?: string
}
export interface DocumentUpdatePayload {
title?: string
content?: Record<string, unknown>
starred?: boolean
}
export const documentsApi = {
list: (params?: {
type?: string
stead_id?: string
starred?: boolean
sort?: 'updated_at' | 'last_visited_at'
}) => {
const search = new URLSearchParams()
if (params?.type) search.set('type', params.type)
if (params?.stead_id) search.set('stead_id', params.stead_id)
if (params?.starred !== undefined) search.set('starred', String(params.starred))
if (params?.sort) search.set('sort', params.sort)
const qs = search.toString()
return request<ApiDocument[]>(`/documents${qs ? `?${qs}` : ''}`)
},
search: (q: string) =>
request<ApiDocument[]>(`/documents/search?q=${encodeURIComponent(q)}`),
get: (id: string) => request<ApiDocument>(`/documents/${id}`),
create: (payload: DocumentCreatePayload) =>
request<ApiDocument>('/documents', {
method: 'POST',
body: JSON.stringify(payload),
}),
update: (id: string, payload: DocumentUpdatePayload) =>
request<ApiDocument>(`/documents/${id}`, {
method: 'PATCH',
body: JSON.stringify(payload),
}),
visit: (id: string) =>
request<ApiDocument>(`/documents/${id}/visit`, { method: 'POST' }),
share: (id: string, email_or_username: string, role: 'editor' | 'viewer' = 'editor') =>
request<ApiDocument>(`/documents/${id}/share`, {
method: 'POST',
body: JSON.stringify({ email_or_username, role }),
}),
unshare: (id: string, userId: number) =>
request<ApiDocument>(`/documents/${id}/share/${userId}`, { method: 'DELETE' }),
delete: (id: string) =>
request<void>(`/documents/${id}`, { method: 'DELETE' }),
}