Skip to content

Commit

Permalink
♻️ Rewrite api package as standalone sdk (#457)
Browse files Browse the repository at this point in the history
* just messing around

* progress with new patterns

* wip more type gen

* working tirlessly before these layover flights 🥵

* library API

* add interceptor and build out new api

* continue building out sdk

* fully ported functions to classes

* start using sdk

* wip: replace api stuff with sdk

* wip: almost done with hooks!

* finish primary hooks

* wip: fix the world

* wip: fix the world

* la la laaaa la la

* its alive!

* fix scan method

* add api tests and fix existing tests

* fix lint

* try fix weird dotenv error
  • Loading branch information
aaronleopold authored Sep 28, 2024
1 parent 4095d1b commit ad84b66
Show file tree
Hide file tree
Showing 179 changed files with 3,783 additions and 2,663 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,11 +182,11 @@ A NextJS application for the Stump documentation site at `/docs` in the root of

Various TypeScript packages, at `/packages` in the root of the project:

- `api`: All of the API functions used by the `client` package
- `client`: React-query config, hooks, and other client-side utilities
- `sdk`: A TypeScript SDK for interfacing with Stump's API
- `client`: React-query config, hooks, and other client-side utilities for React-based applications
- `components`: Shared React components for the web and desktop applications
- `browser`: A React component that is essentially the "main" UI for Stump on browser-based platforms. It is isolated in order to be re-used in the two browser-based apps: `web` and `desktop`
- `types`: Shared TypeScript types for interfacing with Stump's core and API
- `browser`: A React component that is essentially the "main" UI for Stump on browser-based platforms. This package is shared between both the `web` and `desktop` apps
- `types`: Shared TypeScript types generated by the various codegen tools in the project

</details>

Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ mod tests {
where
T: NamedType,
{
export::<T>(&ExportConfiguration::new().bigint(BigIntExportBehavior::BigInt))
export::<T>(&ExportConfiguration::new().bigint(BigIntExportBehavior::Number))
}

#[test]
Expand Down
2 changes: 1 addition & 1 deletion apps/expo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"@react-navigation/bottom-tabs": "^6.5.12",
"@react-navigation/native": "^6.1.10",
"@react-navigation/native-stack": "^6.9.18",
"@stump/api": "*",
"@stump/sdk": "*",
"@tanstack/react-query": "^4.20.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
Expand Down
2 changes: 1 addition & 1 deletion apps/expo/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
import { checkUrl, initializeApi, isAxiosError, isUrl } from '@stump/api'
import { useAuthQuery } from '@stump/client'
import { checkUrl, initializeApi, isAxiosError, isUrl } from '@stump/sdk'
import * as SplashScreen from 'expo-splash-screen'
import { useEffect, useState } from 'react'
import { SafeAreaProvider } from 'react-native-safe-area-context'
Expand Down
38 changes: 21 additions & 17 deletions apps/expo/src/components/book/BookListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getMediaThumbnail } from '@stump/api'
import { useSDK } from '@stump/client'
import { Media } from '@stump/types'
import React from 'react'
import { TouchableOpacity } from 'react-native'
Expand All @@ -12,20 +12,24 @@ type BookListItemProps = {
book: Media
navigate: (id: string) => void
}
export const BookListItem = React.memo(({ book, navigate }: BookListItemProps) => (
<TouchableOpacity
key={book.id}
className="w-full flex-row items-center space-x-3 px-4"
style={{ height: BOOK_LIST_ITEM_HEIGHT, minHeight: BOOK_LIST_ITEM_HEIGHT }}
onPress={() => navigate(book.id)}
>
<EntityImage
url={getMediaThumbnail(book.id)}
style={{ height: 50, objectFit: 'scale-down', width: 50 / (3 / 2) }}
/>
<View className="flex-1">
<Text size="sm">{book.metadata?.title || book.name}</Text>
</View>
</TouchableOpacity>
))
export const BookListItem = React.memo(({ book, navigate }: BookListItemProps) => {
const { sdk } = useSDK()

return (
<TouchableOpacity
key={book.id}
className="w-full flex-row items-center space-x-3 px-4"
style={{ height: BOOK_LIST_ITEM_HEIGHT, minHeight: BOOK_LIST_ITEM_HEIGHT }}
onPress={() => navigate(book.id)}
>
<EntityImage
url={sdk.media.thumbnailURL(book.id)}
style={{ height: 50, objectFit: 'scale-down', width: 50 / (3 / 2) }}
/>
<View className="flex-1">
<Text size="sm">{book.metadata?.title || book.name}</Text>
</View>
</TouchableOpacity>
)
})
BookListItem.displayName = 'BookListItem'
12 changes: 7 additions & 5 deletions apps/expo/src/components/reader/epub/EpubJSReader.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Location, Reader } from '@epubjs-react-native/core'
import { useFileSystem } from '@epubjs-react-native/expo-file-system'
import { API, isAxiosError, updateEpubProgress } from '@stump/api'
import { useSDK } from '@stump/client'
import { isAxiosError } from '@stump/sdk'
import { Media } from '@stump/types'
import { useColorScheme } from 'nativewind'
import React, { useCallback, useEffect, useState } from 'react'
Expand Down Expand Up @@ -30,6 +31,7 @@ type Props = {
* the long run
*/
export default function EpubJSReader({ book, initialCfi, incognito }: Props) {
const { sdk } = useSDK()
/**
* The base64 representation of the book file. The reader component does not accept
* credentials in the fetch, so we just have to fetch manually and pass the base64
Expand All @@ -47,7 +49,7 @@ export default function EpubJSReader({ book, initialCfi, incognito }: Props) {
useEffect(() => {
async function fetchBook() {
try {
const response = await fetch(`${API.getUri()}/media/${book.id}/file`)
const response = await fetch(sdk.media.downloadURL(book.id))
const data = await response.blob()
const reader = new FileReader()
reader.onloadend = () => {
Expand All @@ -64,7 +66,7 @@ export default function EpubJSReader({ book, initialCfi, incognito }: Props) {
}

fetchBook()
}, [book.id])
}, [book.id, sdk.media])

/**
* A callback that updates the read progress of the current location
Expand All @@ -79,7 +81,7 @@ export default function EpubJSReader({ book, initialCfi, incognito }: Props) {
} = currentLocation

try {
await updateEpubProgress({
await sdk.epub.updateProgress({
epubcfi: cfi,
id: book.id,
is_complete: progress >= 1.0,
Expand All @@ -93,7 +95,7 @@ export default function EpubJSReader({ book, initialCfi, incognito }: Props) {
}
}
},
[incognito, book.id],
[incognito, book.id, sdk.epub],
)

if (!base64) {
Expand Down
7 changes: 4 additions & 3 deletions apps/expo/src/components/reader/image/ImageBasedReader.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getMediaPage, isAxiosError } from '@stump/api'
import { useUpdateMediaProgress } from '@stump/client'
import { useSDK, useUpdateMediaProgress } from '@stump/client'
import { isAxiosError } from '@stump/sdk'
import { Media } from '@stump/types'
import { useColorScheme } from 'nativewind'
import React, { useCallback, useMemo, useState } from 'react'
Expand Down Expand Up @@ -166,6 +166,7 @@ const Page = React.memo(
maxHeight,
readingDirection,
}: PageProps) => {
const { sdk } = useSDK()
const insets = useSafeAreaInsets()

const {
Expand Down Expand Up @@ -224,7 +225,7 @@ const Page = React.memo(
}}
>
<EntityImage
url={getMediaPage(id, index + 1)}
url={sdk.media.bookPageURL(id, index + 1)}
style={{
alignSelf: readingDirection === 'horizontal' ? 'center' : undefined,
height,
Expand Down
6 changes: 3 additions & 3 deletions apps/expo/src/screens/authenticated/Home.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { authApi } from '@stump/api'
import { queryClient } from '@stump/client'
import { queryClient, useSDK } from '@stump/client'
import React from 'react'
import { Button } from 'react-native'

import { ScreenRootView, Text } from '@/components'
import { useUserStore } from '@/stores'

export default function Home() {
const { sdk } = useSDK()
const setUser = useUserStore((state) => state.setUser)

const handleLogout = async () => {
try {
await authApi.logout()
await sdk.auth.logout()
setUser(null)
} catch (err) {
console.error(err)
Expand Down
4 changes: 2 additions & 2 deletions apps/expo/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"jsx": "preserve",
"module": "esnext",
"paths": {
"@stump/api": ["../../packages/api/src/index.ts"],
"@stump/api/*": ["../../packages/api/src/*"],
"@stump/sdk": ["../../packages/sdk/src/index.ts"],
"@stump/sdk/*": ["../../packages/sdk/src/*"],
"@stump/client": ["../../packages/client/src/index.ts"],
"@stump/client/*": ["../../packages/client/src/*"],
"@stump/types": ["../../packages/types/index.ts"],
Expand Down
1 change: 1 addition & 0 deletions apps/server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ serde = { workspace = true }
serde_json = { workspace = true }
serde_qs = { version = "0.13.0", features = ["axum"] }
serde-untagged = "0.1.2"
serde_with = { workspace = true }
specta = { workspace = true }
stump_core = { path = "../../core" }
tower-http = { version = "0.5.2", features = [
Expand Down
43 changes: 24 additions & 19 deletions apps/server/src/filter/basic_filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use std::str::FromStr;

use serde::{de, Deserialize, Serialize};
use serde_untagged::UntaggedEnumVisitor;
use serde_with::skip_serializing_none;
use specta::Type;
use stump_core::db::{
entity::{age_rating_deserializer, LogLevel},
query::ordering::QueryOrder,
Expand Down Expand Up @@ -39,7 +41,7 @@ where
}
}

#[derive(Deserialize, Debug, Clone, Serialize)]
#[derive(Deserialize, Debug, Clone, Serialize, Type)]
pub struct Range<T>
where
T: std::str::FromStr,
Expand All @@ -63,7 +65,7 @@ where
}
}

#[derive(Debug, Clone, Serialize, ToSchema)]
#[derive(Debug, Clone, Serialize, ToSchema, Type)]
#[serde(untagged)]
pub enum ValueOrRange<T>
where
Expand Down Expand Up @@ -121,7 +123,7 @@ pub struct BaseFilter {
pub search: Option<String>,
}

#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema)]
#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema, Type)]
pub struct LibraryBaseFilter {
#[serde(default, deserialize_with = "string_or_seq_string")]
pub id: Vec<String>,
Expand All @@ -133,36 +135,38 @@ pub struct LibraryBaseFilter {
pub search: Option<String>,
}

#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema)]
#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema, Type)]
pub struct LibraryRelationFilter {
#[serde(skip_serializing_if = "Option::is_none")]
pub series: Option<SeriesBaseFilter>,
}

#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema)]
#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema, Type)]
pub struct LibraryFilter {
#[serde(flatten)]
pub base_filter: LibraryBaseFilter,
#[serde(flatten)]
pub relation_filter: LibraryRelationFilter,
}

#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema)]
#[skip_serializing_none]
#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema, Type)]
pub struct SeriesQueryRelation {
pub load_media: Option<bool>,
pub load_library: Option<bool>,
pub count_media: Option<bool>,
}

#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema)]
#[skip_serializing_none]
#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema, Type)]
pub struct UserQueryRelation {
pub include_read_progresses: Option<bool>,
pub include_session_count: Option<bool>,
pub include_restrictions: Option<bool>,
}

// TODO: decide what others to include
#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema)]
#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema, Type)]
pub struct SeriesMedataFilter {
#[serde(default, deserialize_with = "string_or_seq_string")]
pub meta_type: Vec<String>,
Expand All @@ -182,7 +186,7 @@ pub struct SeriesMedataFilter {
pub volume: Option<ValueOrRange<i32>>,
}

#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema)]
#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema, Type)]
pub struct SeriesBaseFilter {
#[serde(default, deserialize_with = "string_or_seq_string")]
pub id: Vec<String>,
Expand All @@ -197,23 +201,23 @@ pub struct SeriesBaseFilter {
pub metadata: Option<SeriesMedataFilter>,
}

#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema)]
#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema, Type)]
pub struct SeriesRelationFilter {
#[serde(skip_serializing_if = "Option::is_none")]
pub library: Option<LibraryBaseFilter>,
#[serde(skip_serializing_if = "Option::is_none")]
pub media: Option<MediaBaseFilter>,
}

#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema)]
#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema, Type)]
pub struct SeriesFilter {
#[serde(flatten)]
pub base_filter: SeriesBaseFilter,
#[serde(flatten)]
pub relation_filter: SeriesRelationFilter,
}

#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema)]
#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema, Type)]
pub struct MediaMetadataBaseFilter {
#[serde(default, deserialize_with = "string_or_seq_string")]
pub publisher: Vec<String>,
Expand Down Expand Up @@ -243,13 +247,13 @@ pub struct MediaMetadataBaseFilter {
pub year: Option<ValueOrRange<i32>>,
}

#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema)]
#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema, Type)]
pub struct MediaMetadataRelationFilter {
#[serde(skip_serializing_if = "Option::is_none")]
pub media: Option<MediaFilter>,
}

#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema)]
#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema, Type)]
pub struct MediaMetadataFilter {
#[serde(flatten)]
pub base_filter: MediaMetadataBaseFilter,
Expand All @@ -259,7 +263,7 @@ pub struct MediaMetadataFilter {

/// A user-friendly representation of a media's read_progress. This will map to
/// a query condition that will be used to filter the media.
#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema)]
#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema, Type)]
pub enum ReadStatus {
#[default]
#[serde(alias = "unread")]
Expand All @@ -283,7 +287,7 @@ impl FromStr for ReadStatus {
}
}

#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema)]
#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema, Type)]
pub struct MediaBaseFilter {
#[serde(default, deserialize_with = "string_or_seq_string")]
pub id: Vec<String>,
Expand All @@ -304,13 +308,13 @@ pub struct MediaBaseFilter {
pub metadata: Option<MediaMetadataBaseFilter>,
}

#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema)]
#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema, Type)]
pub struct MediaRelationFilter {
#[serde(skip_serializing_if = "Option::is_none")]
pub series: Option<SeriesFilter>,
}

#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema)]
#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema, Type)]
pub struct MediaFilter {
#[serde(flatten)]
pub base_filter: MediaBaseFilter,
Expand All @@ -330,7 +334,8 @@ impl MediaFilter {
}
}

#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema)]
#[skip_serializing_none]
#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema, Type)]
pub struct LogFilter {
pub level: Option<LogLevel>,
pub job_id: Option<String>,
Expand Down
Loading

0 comments on commit ad84b66

Please sign in to comment.