feat: list streams and story-mode catchup
This commit is contained in:
@@ -1,10 +1,17 @@
|
||||
import { useSessionStore } from "@/stores/session-store";
|
||||
import type {
|
||||
CreateStreamParticleRequest,
|
||||
CreateStreamRequest,
|
||||
Human,
|
||||
MarkSeenBatchRequest,
|
||||
PrepareUploadRequest,
|
||||
PrepareUploadResponse,
|
||||
RequestCodeRequest,
|
||||
SignInRequest,
|
||||
SignInResponse,
|
||||
StartupResponse,
|
||||
Stream,
|
||||
StreamParticle,
|
||||
} from "./types";
|
||||
|
||||
export class ApiError extends Error {
|
||||
@@ -67,6 +74,8 @@ class ApiClient {
|
||||
return response.json() as Promise<T>;
|
||||
}
|
||||
|
||||
// --- Auth ---
|
||||
|
||||
async requestCode(data: RequestCodeRequest): Promise<void> {
|
||||
await this.request<void>("POST", "/auth/request-code", data);
|
||||
}
|
||||
@@ -83,9 +92,81 @@ class ApiClient {
|
||||
await this.request<void>("POST", "/auth/sign-out");
|
||||
}
|
||||
|
||||
// --- Startup ---
|
||||
|
||||
async startup(): Promise<StartupResponse> {
|
||||
return this.request<StartupResponse>("GET", "/startup");
|
||||
}
|
||||
|
||||
// --- Streams ---
|
||||
|
||||
async createStream(
|
||||
networkId: string,
|
||||
data: CreateStreamRequest,
|
||||
): Promise<Stream> {
|
||||
return this.request<Stream>(
|
||||
"POST",
|
||||
`/networks/${networkId}/streams`,
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
async createStreamParticle(
|
||||
streamId: string,
|
||||
data: CreateStreamParticleRequest,
|
||||
): Promise<StreamParticle> {
|
||||
return this.request<StreamParticle>(
|
||||
"POST",
|
||||
`/streams/${streamId}/particles`,
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
// --- Particles ---
|
||||
|
||||
async markSeen(particleId: string): Promise<void> {
|
||||
await this.request<void>("POST", `/particles/${particleId}/seen`);
|
||||
}
|
||||
|
||||
async markSeenBatch(data: MarkSeenBatchRequest): Promise<void> {
|
||||
await this.request<void>("POST", "/particles/seen", data);
|
||||
}
|
||||
|
||||
async getParticleDownloadUrl(particleId: string): Promise<string> {
|
||||
const token = this.config.getToken();
|
||||
const headers: Record<string, string> = {};
|
||||
if (token) {
|
||||
headers["Authorization"] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
`${this.config.baseUrl}/particles/${particleId}/download`,
|
||||
{ headers, redirect: "follow" },
|
||||
);
|
||||
|
||||
if (response.status === 401) {
|
||||
this.config.onUnauthorized();
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new ApiError(response.status, "Failed to get download URL");
|
||||
}
|
||||
|
||||
return response.url;
|
||||
}
|
||||
|
||||
// --- Depot ---
|
||||
|
||||
async prepareUpload(
|
||||
data: PrepareUploadRequest,
|
||||
): Promise<PrepareUploadResponse> {
|
||||
return this.request<PrepareUploadResponse>("POST", "/depot/upload", data);
|
||||
}
|
||||
|
||||
async confirmUpload(objectId: string): Promise<void> {
|
||||
await this.request<void>("POST", `/depot/objects/${objectId}/confirm`);
|
||||
}
|
||||
}
|
||||
|
||||
export const apiClient = new ApiClient({
|
||||
|
||||
+151
-4
@@ -1,11 +1,156 @@
|
||||
// --- Core entities ---
|
||||
|
||||
export interface Human {
|
||||
id: string;
|
||||
id: string | null;
|
||||
email: string;
|
||||
email_prefix: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
created_at: string | null;
|
||||
}
|
||||
|
||||
export type StreamStatus = "open" | "closed" | "unspecified";
|
||||
|
||||
export interface AckInfo {
|
||||
email: string;
|
||||
acked_at: string;
|
||||
}
|
||||
|
||||
// --- Particle types ---
|
||||
|
||||
export type ParticleType =
|
||||
| "media"
|
||||
| "text"
|
||||
| "quest"
|
||||
| "paper"
|
||||
| "file"
|
||||
| "folder";
|
||||
|
||||
export interface MediaParticleData {
|
||||
object_id: string;
|
||||
duration_ms: number;
|
||||
mime_type: string;
|
||||
}
|
||||
|
||||
export interface TextParticleData {
|
||||
content: string;
|
||||
}
|
||||
|
||||
export interface QuestParticleData {
|
||||
title: string;
|
||||
description: string;
|
||||
status?: string;
|
||||
assigned_to?: string;
|
||||
due_date?: string;
|
||||
}
|
||||
|
||||
export interface PaperParticleData {
|
||||
title: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
export interface FileParticleData {
|
||||
object_id: string;
|
||||
filename: string;
|
||||
mime_type: string;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface FolderParticleData {
|
||||
name: string;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
export interface ParticleDataMap {
|
||||
media: MediaParticleData;
|
||||
text: TextParticleData;
|
||||
quest: QuestParticleData;
|
||||
paper: PaperParticleData;
|
||||
file: FileParticleData;
|
||||
folder: FolderParticleData;
|
||||
}
|
||||
|
||||
export function getParticleData<T extends ParticleType>(
|
||||
particle: StreamParticle,
|
||||
type: T,
|
||||
): ParticleDataMap[T] {
|
||||
return particle.data as ParticleDataMap[T];
|
||||
}
|
||||
|
||||
export interface StreamParticle {
|
||||
id: string;
|
||||
type: ParticleType;
|
||||
data: unknown;
|
||||
created_by_email: string;
|
||||
seen: boolean;
|
||||
acks: AckInfo[];
|
||||
updated_at: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface Stream {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
status: StreamStatus;
|
||||
members?: string[];
|
||||
particles: StreamParticle[];
|
||||
unseen_count: number;
|
||||
}
|
||||
|
||||
export interface Network {
|
||||
id: string;
|
||||
name: string;
|
||||
admin_human: Human;
|
||||
humans: Human[];
|
||||
open_stream_count: number;
|
||||
open_stream_capacity: number;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface NetworkWithStreams extends Network {
|
||||
streams: Stream[];
|
||||
}
|
||||
|
||||
// --- Depot types ---
|
||||
|
||||
export interface PrepareUploadRequest {
|
||||
file_name: string;
|
||||
content_type: string;
|
||||
size_bytes: number;
|
||||
}
|
||||
|
||||
export interface PrepareUploadResponse {
|
||||
object: DepotObject;
|
||||
upload_url: string;
|
||||
}
|
||||
|
||||
export interface DepotObject {
|
||||
id: string;
|
||||
status: string;
|
||||
content_type: string;
|
||||
size_bytes: number;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
// --- Stream mutation types ---
|
||||
|
||||
export interface CreateStreamRequest {
|
||||
name: string;
|
||||
description: string;
|
||||
visibility: "network_all" | "custom";
|
||||
member_emails?: string[];
|
||||
}
|
||||
|
||||
export interface CreateStreamParticleRequest {
|
||||
type: ParticleType;
|
||||
data: unknown;
|
||||
}
|
||||
|
||||
export interface MarkSeenBatchRequest {
|
||||
particle_ids: string[];
|
||||
}
|
||||
|
||||
// --- Auth types ---
|
||||
|
||||
export interface RequestCodeRequest {
|
||||
email: string;
|
||||
}
|
||||
@@ -20,6 +165,8 @@ export interface SignInResponse {
|
||||
token: string;
|
||||
}
|
||||
|
||||
// --- Startup ---
|
||||
|
||||
export interface StartupResponse {
|
||||
networks: unknown[];
|
||||
networks: NetworkWithStreams[];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user