68 Commits

Author SHA1 Message Date
Wanjohi
c5b3043436 dont remember 2025-06-18 13:40:55 +03:00
Wanjohi
623a0db97f fix: Use public 2025-06-13 14:38:20 +03:00
Wanjohi
9827100d19 fix: Use images 2025-06-13 13:57:43 +03:00
Wanjohi
88f499ba5e feat: Add home 2025-06-12 12:43:52 +03:00
Wanjohi
0a1a4cd9b6 🧹 chore(functions): Remove Containerfile 2025-06-12 11:30:38 +03:00
Wanjohi
d7cb47fdd6 🧹 chore: remove dead code 2025-06-10 16:03:16 +03:00
Wanjohi
8b07adb0fc 🐜 fix: Add asynchronous bus for Steam account 2025-06-10 16:01:59 +03:00
Wanjohi
661d9d2e56 fix: Fix cookie issue 2025-06-10 15:34:21 +03:00
Wanjohi
6eee40fcbe 🐜 fix(api): Fix double CORS issue 2025-06-09 12:08:24 +03:00
Wanjohi
69d728c4a9 🐜 fix(api): Fix double CORS issue 2025-06-09 12:08:04 +03:00
Wanjohi
be85594bdc feat(infra): Migrate to serverless Lambda architecture (#291)
## Description
<!-- Briefly describe the purpose and scope of your changes -->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Introduced serverless API and authentication endpoints, improving
scalability and reliability.
- Added rate limiting to the API, providing protection against excessive
requests and returning custom error responses.

- **Improvements**
- Simplified infrastructure for both API and authentication, reducing
complexity and improving maintainability.
- Updated resource allocations for backend services to optimize
performance and cost.

- **Bug Fixes**
- Removed unused scripts and configuration, resulting in a cleaner
development environment.

- **Other**
  - Updated type declarations to reflect new infrastructure changes.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-09 10:06:58 +03:00
Kristian Ollikainen
6e82eff9e2 feat: Migrate from WebSocket to libp2p for peer-to-peer connectivity (#286)
## Description
Whew, some stuff is still not re-implemented, but it's working!

Rabbit's gonna explode with the amount of changes I reckon 😅



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced a peer-to-peer relay system using libp2p with enhanced
stream forwarding, room state synchronization, and mDNS peer discovery.
- Added decentralized room and participant management, metrics
publishing, and safe, size-limited, concurrent message streaming with
robust framing and callback dispatching.
- Implemented asynchronous, callback-driven message handling over custom
libp2p streams replacing WebSocket signaling.
- **Improvements**
- Migrated signaling and stream protocols from WebSocket to libp2p,
improving reliability and scalability.
- Simplified configuration and environment variables, removing
deprecated flags and adding persistent data support.
- Enhanced logging, error handling, and connection management for better
observability and robustness.
- Refined RTP header extension registration and NAT IP handling for
improved WebRTC performance.
- **Bug Fixes**
- Improved ICE candidate buffering and SDP negotiation in WebRTC
connections.
  - Fixed NAT IP and UDP port range configuration issues.
- **Refactor**
- Modularized codebase, reorganized relay and server logic, and removed
deprecated WebSocket-based components.
- Streamlined message structures, removed obsolete enums and message
types, and simplified SafeMap concurrency.
- Replaced WebSocket signaling with libp2p stream protocols in server
and relay components.
- **Chores**
- Updated and cleaned dependencies across Go, Rust, and JavaScript
packages.
  - Added `.gitignore` for persistent data directory in relay package.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com>
Co-authored-by: Philipp Neumann <3daquawolf@gmail.com>
2025-06-06 16:48:49 +03:00
Wanjohi
e67a8d2b32 feat: Upgrade to asynchronous event bus with retry queue and backoff strategy (#290)
## Description
<!-- Briefly describe the purpose and scope of your changes -->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced a retry and dead-letter queue system for more robust event
processing.
- Added a retry handler for processing failed Lambda invocations with
exponential backoff.
- Enhanced event handling to support retry logic and improved error
management.

- **Refactor**
- Replaced SQS-based library event processing with an event bus-based
approach.
- Updated event names and structure for improved clarity and
consistency.
  - Removed legacy library queue and related infrastructure.

- **Chores**
  - Updated dependencies to include the AWS Lambda client.
  - Cleaned up unused code and removed deprecated event handling logic.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-04 07:53:30 +03:00
Wanjohi
8f4bb05143 🐜 fix(infra): Reduce DB ACUs 2025-06-03 08:10:41 +03:00
Wanjohi
84357ac5bf 🐜 fix(zero): Remove team and members schemas (#289)
## Description
<!-- Briefly describe the purpose and scope of your changes -->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Introduced a new database schema with tables for games, users, Steam
accounts, categories, friends lists, images, and game libraries.
- Added new enumerated types for compatibility, controller support,
category type, image type, and Steam status.

- **Refactor**
- Removed all team and membership-related features, including tables,
relationships, and access permissions.
  - Simplified the primary key structure of the images table.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-02 09:44:58 +03:00
Wanjohi
e11012e8d9 🐜 fix(db): Remove all team associations (#288)
## Description
<!-- Briefly describe the purpose and scope of your changes -->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Introduced a new database schema supporting tables for games,
categories, friends lists, images, game libraries, Steam accounts, and
users, with improved relationships and constraints.
- Added new enum types to enhance data consistency for game
compatibility, controller support, category type, image type, and Steam
status.

- **Chores**
  - Updated migration history to reflect the latest schema changes.

- **Revert**
- Removed the previous "members" and "teams" tables and related enum
types from the database.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-02 09:35:11 +03:00
Wanjohi
c0194ecef4 🔄 refactor(steam): Migrate to Steam OpenID authentication and official Web API (#282)
## Description
<!-- Briefly describe the purpose and scope of your changes -->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Added support for managing multiple Steam profiles per user, including
a new profiles page with avatar selection and profile management.
- Introduced a streamlined Steam authentication flow using a popup
window, replacing the previous QR code and team-based login.
- Added utilities for Steam image handling and metadata, including
avatar preloading and static Steam metadata mappings.
  - Enhanced OpenID verification for Steam login.
- Added new image-related events and expanded event handling for Steam
account updates and image processing.

- **Improvements**
- Refactored the account structure from teams to profiles, updating
related UI, context, and storage.
- Updated API headers and authentication logic to use Steam IDs instead
of team IDs.
- Expanded game metadata with new fields for categories, franchises, and
social links.
- Improved library and category schemas for richer game and profile
data.
- Simplified and improved Steam API client methods for fetching user
info, friends, and game libraries using Steam Web API.
- Updated queue processing to handle individual game updates and publish
image events.
- Adjusted permissions and queue configurations for better message
handling and dead-letter queue support.
  - Improved slug creation and rating estimation utilities.

- **Bug Fixes**
- Fixed avatar image loading to display higher quality images after
initial load.

- **Removals**
- Removed all team, member, and credential management functionality and
related database schemas.
  - Eliminated the QR code-based login and related UI components.
  - Deleted legacy team and member database tables and related code.
- Removed encryption utilities and deprecated secret keys in favor of
new secret management.

- **Chores**
- Updated dependencies and internal configuration for new features and
schema changes.
- Cleaned up unused code and updated database migrations for new data
structures.
- Adjusted import orders and removed unused imports across multiple
modules.
- Added new resource declarations and updated service link
configurations.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-02 09:22:18 +03:00
Kristian Ollikainen
ae364f69bd feat(runner): Improve robustness and argument handling (#285)
## Description
Made argument parsing and handling much nicer with clap features.
Changed to tracing package for logging and made other improvements
around to hopefully make things more robust and logical.

Default audio-capture-method is now PipeWire since it seems to work
perfectly fine with latest gstreamer 🎉

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Improved command-line argument parsing with stricter validation, type
safety, and clearer help messages.
- Enhanced GPU selection and logging, including explicit GPU info
logging and support for negative GPU indices for auto-selection.
- Added support for new audio and video codec and encoder enums,
providing safer and more flexible codec handling.

- **Bug Fixes**
- Improved error handling and logging throughout the application,
unifying logs under the `tracing` system for better diagnostics.
- Fixed issues with directory ownership and environment variable
handling in startup scripts.

- **Refactor**
- Replaced string-based parsing and manual conversions with strongly
typed enums and value parsers.
- Updated logging from `println!` and `log` macros to the `tracing`
crate for consistency.
- Simplified and unified the handling of pipeline and element references
in the signaling and data channel logic.

- **Chores**
- Updated and cleaned up dependencies, including switching from `log` to
`tracing` and upgrading the `webrtc` crate.
- Removed unused or redundant code and environment variables for
improved maintainability.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com>
2025-05-23 11:33:40 +03:00
Kristian Ollikainen
d7e6da12ac feat(runner): DMA-BUF support for Intel/AMD GPUs (#283)
## Description
Adds DMA-BUF support for non-NVIDIA GPUs using GL elements as conversion
workaround.

Tested with QSV and VA encoders, note that H.264 seems to only work for
QSV encoder, for `vah264(lp)enc` theres major CPU usage with DMA-BUF
enabled.

Don't mind the branch name, I was working on relay before and changed
gears to runner after noticing some DMA-BUF stuff 😅

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **Chores**
- Added a `.dockerignore` file to exclude the `target` directory from
Docker builds.
	- Updated `.gitignore` to ignore the `target` directory.

- **New Features**
- Enhanced video processing pipeline with updated handling of DMA-BUF
support, including improved compatibility for different GPU vendors and
refined video element configurations.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com>
2025-05-20 08:50:24 +03:00
Wanjohi
6e19b2e9a0 🐜 fix(zero): Remove unnecessary StorageKey value 2025-05-19 06:26:11 +03:00
Wanjohi
dd20c0049d 🐜 fix(zero): Fix zero throwing error about tables being undefined (#281)
## Description
<!-- Briefly describe the purpose and scope of your changes -->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced a new environment variable for improved configuration
options.

- **Chores**
  - Updated and locked dependency versions for enhanced stability.
- Marked certain packages as private to prevent accidental publication.
- Updated package metadata and trusted dependencies for better
dependency management.

- **Refactor**
- Adjusted provider structure in the app to wrap children components
with an additional context provider.
  - Simplified and cleaned up provider context code for maintainability.
  - Improved import statements for clarity and type safety.

- **Style**
  - Reorganized import order for consistency.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-19 06:23:50 +03:00
Wanjohi
14e4176344 🐜 fix(www): Uncomment the SST parts 2025-05-17 23:32:15 +03:00
Kristian Ollikainen
baf178afc5 🐜 fix(runner): Improve NVIDIA driver handling, switch to gamescope (#279)
## Description
- Made it so failed NVIDIA driver install won't quit entrypoint script
if other GPU vendors are present (fixes mixed GPU cases).
- Switch to gamescope as compositor, with optional SYS_NICE cap handling
for higher priority.
- Use mangohud preset 2 for stats, which is more compact.
- Fixes to nestri-server lspci regex, to deal with AMD naming scheme.
- Added missing radeon vulkan driver packages.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
	- Added support for additional AMD Vulkan drivers.
- Integrated Steam launch directly within the gamescope compositor for a
streamlined startup.

- **Bug Fixes**
- Improved GPU driver fallback handling to ensure smoother operation on
systems without NVIDIA GPUs.
	- Enhanced PCI device parsing for more accurate GPU detection.

- **Chores**
- Updated environment configuration to use X11 session type and set
MangoHud preset.
- Removed unused packages and legacy compositor/resolution management
logic.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com>
2025-05-17 04:19:00 +03:00
Wanjohi
80deb82d25 🐜 fix(www): Fix bg colors on light mode 2025-05-17 01:08:38 +03:00
Wanjohi
e1a903a7c9 feat(core): Implement Steam library sync with metadata extraction and image processing (#278)
## Description
<!-- Briefly describe the purpose and scope of your changes -->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Added AWS queue infrastructure and SQS handler for processing Steam
game libraries and images.
- Introduced event-driven handling for new credentials and game
additions, including image uploads to S3.
- Added client functions to fetch Steam user libraries, friends lists,
app info, and related images.
- Added new database columns and schema updates to track game
acquisition, playtime, and family sharing.
  - Added utility function for chunking arrays.
- Added new event notifications for library queue processing and game
creation.
  - Added new lookup functions for categories and teams by slug.
- Introduced a new Team API with endpoints to list and fetch teams by
slug.
  - Added a new Steam library page displaying game images.

- **Enhancements**
  - Improved game creation with event notifications and upsert logic.
  - Enhanced category and team retrieval with new lookup functions.
  - Renamed and refined image categories for clearer classification.
  - Expanded dependencies for image processing and AWS SDK integration.
- Improved image processing utilities with caching, ranking, and
metadata extraction.
  - Refined Steam client utilities for concurrency and error handling.

- **Bug Fixes**
- Fixed event publishing timing and removed deprecated credential
retrieval methods.

- **Chores**
- Updated infrastructure configurations with increased timeouts, memory,
and resource linking.
- Added new dependencies for image processing, caching, and AWS SDK
clients.
  - Refined internal code structure and imports for clarity.
  - Removed Steam provider and related UI components from the frontend.
- Disabled authentication providers and Steam-related routes in the
frontend.
  - Updated API fetch handler to accept environment bindings.

- **Refactor**
- Simplified query result handling and renamed functions for better
clarity.
- Removed outdated event handler in favor of consolidated event
subscriber.
- Consolidated and simplified database relationships and permission
queries.

- **Tests**
  - No explicit test changes included in this release.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-17 00:51:18 +03:00
Wanjohi
cc2065299d 🐜 fix(db): Add partial controller_support 2025-05-11 05:03:57 +03:00
Wanjohi
0cc9effdec 🐜 fix(db): Make primary_genre nullable 2025-05-11 04:23:05 +03:00
Wanjohi
82dfd6506d 🐜 fix(db): Make controller_support an enum 2025-05-11 03:58:30 +03:00
Wanjohi
6051e11921 🐜 fix(zero): Tidy up the schema 2025-05-11 01:04:45 +03:00
Wanjohi
86670d5931 🐜 fix(zero): Tidy up the schema 2025-05-11 01:03:59 +03:00
Wanjohi
35f009e925 🐜 fix: Games should only be visible to logged in users 2025-05-11 01:02:28 +03:00
Wanjohi
5806dc6e86 feat: Implement Game Image Support with Metadata & Schema Updates (#277)
## Description
<!-- Briefly describe the purpose and scope of your changes -->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced support for associating rich image metadata (color,
dimensions, file size) with games, organized by categories like
screenshots, box art, posters, hero art, backgrounds, logos, and icons.
- Game and library listings now include related image collections for
enhanced browsing and detail views.

- **Improvements**
- Updated game library management to use a consistent base game
identifier, improving data consistency and reliability.
- Enhanced data schemas and access permissions to allow public viewing
of game images and refined access control for game libraries.
- Added comprehensive database schema updates for games, categories,
images, and libraries to support new features and ensure data integrity.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-10 22:47:28 +03:00
Wanjohi
38ad74d14a 🐜 fix(zero): Keep Steam ID consistent thru out 2025-05-10 08:16:08 +03:00
Wanjohi
0b995fa540 feat: Add Games (#276)
## Description
<!-- Briefly describe the purpose and scope of your changes -->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced comprehensive management of game libraries, including
adding, removing, and listing games in a user's Steam library.
- Added new API endpoints for retrieving detailed game information by ID
and listing all games in a user's library.
- Enabled friend-related API endpoints to list friends and fetch friend
details by SteamID.
- Added category and base game data structures with validation and
serialization for enriched game metadata.
- Introduced ownership update functionality for Steam accounts during
login.
- Added new game and category linking to support detailed game metadata
and categorization.
- Introduced member retrieval functions for enhanced team and user
management.

- **Improvements**
- Enhanced authentication to enforce team membership checks and provide
member-level access control.
- Improved Steam account ownership handling to ensure accurate user
association.
  - Added indexes to friend relationships for optimized querying.
  - Refined API routing structure with added game and friend routes.
- Improved friend listing queries for efficiency and data completeness.

- **Bug Fixes**
  - Fixed formatting issues in permissions related to Steam accounts.

- **Other**
- Refined event handling for user account refresh based on user ID
instead of email.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-10 08:11:00 +03:00
Wanjohi
d933c1e61d feat: Make sure friends can see their friends 2025-05-09 16:22:28 +03:00
Wanjohi
b86fc625ba 🐜 fix: Fix user relations in zero-sync schema 2025-05-09 07:22:59 +03:00
Wanjohi
c250fd557c feat: Expand zero-sync schema with users, teams, and Steam integration (#275)
## Description
<!-- Briefly describe the purpose and scope of your changes -->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Expanded data model to include users, Steam accounts, teams, members,
and friends lists for richer user and team management.
- Introduced detailed relationships and row-level permissions for
enhanced access control.

- **Chores**
  - Updated dependency version for improved compatibility.
- Adjusted environment variables and configuration for improved
performance and reliability.
- Updated development scripts for clearer SQL permissions generation and
workflow separation.
  - Enhanced .gitignore to exclude SQL files from version control.

- **Refactor**
- Restructured schema and permissions logic for greater flexibility and
security.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-09 06:58:13 +03:00
Wanjohi
1923cdf2a3 🐜 fix: Typo in the Steam login page 2025-05-09 01:24:46 +03:00
Wanjohi
7e69af977b feat: Add Steam account linking with team creation (#274)
## Description
<!-- Briefly describe the purpose and scope of your changes -->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced a real-time Steam login flow using QR codes and server-sent
events (SSE) for team creation and authentication.
- Added Steam account and friend management, including secure credential
storage and friend list synchronization.
- Integrated Steam login endpoints into the API, enabling QR code-based
login and automated team setup.

- **Improvements**
- Enhanced data security by implementing encrypted storage for sensitive
tokens.
- Updated database schema to support Steam accounts, teams, memberships,
and social connections.
- Refined type definitions and consolidated account-related information
for improved consistency.

- **Bug Fixes**
  - Fixed trade ban status representation for Steam accounts.

- **Chores**
- Removed legacy C# Steam authentication service and related
configuration files.
  - Updated and cleaned up package dependencies and development tooling.
  - Streamlined type declaration files and resource definitions.

- **Style**
- Redesigned the team creation page UI with a modern, animated QR code
login interface.

- **Documentation**
  - Updated OpenAPI documentation for new Steam login endpoints.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-09 01:13:44 +03:00
Wanjohi
70d629227a feat: New account system with improved team management (#273)
Description
<!-- Briefly describe the purpose and scope of your changes -->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced comprehensive account management with combined user and
team info.
  - Added advanced, context-aware logging utilities.
- Implemented invite code generation for teams with uniqueness
guarantees.
- Expanded example data for users, teams, subscriptions, sessions, and
games.

- **Enhancements**
- Refined user, team, member, and Steam account schemas for richer data
and validation.
  - Streamlined user creation, login acknowledgment, and error handling.
  - Improved API authentication and unified actor context management.
- Added persistent shared temporary volume support to API and auth
services.
- Enhanced Steam account management with create, update, and event
notifications.
- Refined team listing and serialization integrating Steam accounts as
members.
  - Simplified event, context, and logging systems.
- Updated API and auth middleware for better token handling and actor
provisioning.

- **Bug Fixes**
  - Fixed multiline log output to prefix each line with log level.

- **Removals**
- Removed machine and subscription management features, including
schemas and DB tables.
- Disabled machine-based authentication and removed related subject
schemas.
- Removed deprecated fields and legacy logic from member and team
management.
- Removed legacy event and error handling related to teams and members.

- **Chores**
  - Reorganized and cleaned exports across utility and API modules.
- Updated database schemas for users, teams, members, and Steam
accounts.
  - Improved internal code structure, imports, and error messaging.
- Moved logger patching to earlier initialization for consistent
logging.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-06 07:26:59 +03:00
Wanjohi
a0dc353561 🐜 fix: Fix an issue where ts-server is taking forever to load (#272)
## Description
<!-- Briefly describe the purpose and scope of your changes -->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
  - Centralized and standardized error response schemas for APIs.
- Utility functions for result formatting and enhanced validation error
handling.
  - New utility modules for authentication and OAuth provider handling.
  - Added Discord OAuth user data fetching with email verification.

- **Bug Fixes**
- Improved error safety in cloud task creation by preventing potential
runtime errors.

- **Refactor**
- Major simplification and reorganization of API routes and
authentication logic.
  - Migration from valibot to zod for schema validation.
  - Streamlined import paths and consolidated utility exports.
- Simplified TypeScript and .gitignore configuration for easier
maintenance.
  - Disabled machine authentication provider and related logic.

- **Chores**
- Removal of unused or deprecated API endpoints, database migration, and
permissions deployment code.
- Updated package dependencies and scripts for improved reliability and
performance.
  - Enhanced documentation and updated project metadata.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-06 05:22:26 +03:00
Wanjohi
47e61599bb feat(api): Add payments with Polar.sh (#264)
## Description
<!-- Briefly describe the purpose and scope of your changes -->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced a new subscription API endpoint for managing subscriptions
and products.
- Enhanced subscription management with new entities and
functionalities.
- Added functionality to retrieve current timestamps in both local and
UTC formats.
- Added Polar.sh integration with customer portal and checkout session
creation APIs.

- **Refactor**
- Redesigned team details to now present members and subscription
information instead of a plan type.
  - Enhanced member management by incorporating role assignments.
- Streamlined user data handling and removed legacy subscription event
logic.
  - Simplified error handling in actor functions for better clarity.
  - Updated plan types and UI labels to reflect new subscription tiers.
  - Improved database indexing for Steam user data.

- **Chores**
- Updated the database schema with new tables and fields to support
subscription, team, and member enhancements.
  - Extended identifier prefixes to broaden system integration.
- Added new secrets related to pricing plans in infrastructure
configuration.
  - Configured API and auth routing with new domain and routing rules.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-04-18 14:24:19 +03:00
dependabot[bot]
76d27e4708 build(deps): bump golang.org/x/net from 0.34.0 to 0.38.0 in /packages/maitred in the go_modules group across 1 directory (#266)
Bumps the go_modules group with 1 update in the /packages/maitred
directory: [golang.org/x/net](https://github.com/golang/net).

Updates `golang.org/x/net` from 0.34.0 to 0.38.0
<details>
<summary>Commits</summary>
<ul>
<li><a
href="e1fcd82abb"><code>e1fcd82</code></a>
html: properly handle trailing solidus in unquoted attribute value in
foreign...</li>
<li><a
href="ebed060e8f"><code>ebed060</code></a>
internal/http3: fix build of tests with GOEXPERIMENT=nosynctest</li>
<li><a
href="1f1fa29e0a"><code>1f1fa29</code></a>
publicsuffix: regenerate table</li>
<li><a
href="12150816f7"><code>1215081</code></a>
http2: improve error when server sends HTTP/1</li>
<li><a
href="312450e473"><code>312450e</code></a>
html: ensure &lt;search&gt; tag closes &lt;p&gt; and update tests</li>
<li><a
href="09731f9bf9"><code>09731f9</code></a>
http2: improve handling of lost PING in Server</li>
<li><a
href="55989e24b9"><code>55989e2</code></a>
http2/h2c: use ResponseController for hijacking connections</li>
<li><a
href="2914f46773"><code>2914f46</code></a>
websocket: re-recommend gorilla/websocket</li>
<li><a
href="99b3ae0643"><code>99b3ae0</code></a>
go.mod: update golang.org/x dependencies</li>
<li><a
href="85d1d54551"><code>85d1d54</code></a>
go.mod: update golang.org/x dependencies</li>
<li>Additional commits viewable in <a
href="https://github.com/golang/net/compare/v0.34.0...v0.38.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=golang.org/x/net&package-manager=go_modules&previous-version=0.34.0&new-version=0.38.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/nestrilabs/nestri/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-17 19:53:52 +03:00
Wanjohi
896832b89c 🐜 fix: Remove Steam account info repetition (#263)
## Description
<!-- Briefly describe the purpose and scope of your changes -->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- User profiles now display integrated Steam account information for a
more consolidated view.
- Accounts can now include associated teams and Steam account
information.

- **Refactor**
- Streamlined the underlying data structures for user, machine, and
Steam information to improve consistency and performance.

- **Chores**
- Updated database schemas and upgraded core dependencies, including the
`remeda` and `vite` packages, while refining authentication settings for
smoother operation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-14 10:51:40 +03:00
dependabot[bot]
492013d610 build(deps): bump the npm_and_yarn group across 3 directories with 1 update (#260)
Bumps the npm_and_yarn group with 1 update in the /apps/docs directory:
[vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).
Bumps the npm_and_yarn group with 1 update in the /apps/www directory:
[vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).
Bumps the npm_and_yarn group with 1 update in the /packages/www
directory:
[vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).

Updates `vite` from 6.2.5 to 6.2.6
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/releases">vite's
releases</a>.</em></p>
<blockquote>
<h2>v6.2.6</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.2.6/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/blob/v6.2.6/packages/vite/CHANGELOG.md">vite's
changelog</a>.</em></p>
<blockquote>
<h2><!-- raw HTML omitted -->6.2.6 (2025-04-10)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: reject requests with <code>#</code> in request-target (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19830">#19830</a>)
(<a
href="3bb0883d22">3bb0883</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19830">#19830</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="d3dbf25fd5"><code>d3dbf25</code></a>
release: v6.2.6</li>
<li><a
href="3bb0883d22"><code>3bb0883</code></a>
fix: reject requests with <code>#</code> in request-target (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19830">#19830</a>)</li>
<li>See full diff in <a
href="https://github.com/vitejs/vite/commits/v6.2.6/packages/vite">compare
view</a></li>
</ul>
</details>
<br />

Updates `vite` from 6.0.14 to 6.0.15
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/releases">vite's
releases</a>.</em></p>
<blockquote>
<h2>v6.2.6</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.2.6/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/blob/v6.2.6/packages/vite/CHANGELOG.md">vite's
changelog</a>.</em></p>
<blockquote>
<h2><!-- raw HTML omitted -->6.2.6 (2025-04-10)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: reject requests with <code>#</code> in request-target (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19830">#19830</a>)
(<a
href="3bb0883d22">3bb0883</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19830">#19830</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="d3dbf25fd5"><code>d3dbf25</code></a>
release: v6.2.6</li>
<li><a
href="3bb0883d22"><code>3bb0883</code></a>
fix: reject requests with <code>#</code> in request-target (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19830">#19830</a>)</li>
<li>See full diff in <a
href="https://github.com/vitejs/vite/commits/v6.2.6/packages/vite">compare
view</a></li>
</ul>
</details>
<br />

Updates `vite` from 6.0.14 to 6.0.15
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/releases">vite's
releases</a>.</em></p>
<blockquote>
<h2>v6.2.6</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.2.6/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/blob/v6.2.6/packages/vite/CHANGELOG.md">vite's
changelog</a>.</em></p>
<blockquote>
<h2><!-- raw HTML omitted -->6.2.6 (2025-04-10)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: reject requests with <code>#</code> in request-target (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19830">#19830</a>)
(<a
href="3bb0883d22">3bb0883</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19830">#19830</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="d3dbf25fd5"><code>d3dbf25</code></a>
release: v6.2.6</li>
<li><a
href="3bb0883d22"><code>3bb0883</code></a>
fix: reject requests with <code>#</code> in request-target (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19830">#19830</a>)</li>
<li>See full diff in <a
href="https://github.com/vitejs/vite/commits/v6.2.6/packages/vite">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/nestrilabs/nestri/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-14 10:33:56 +03:00
Wanjohi
e93099784c feat(api): Connect Steam to main user account (#262)
## Description
This attempts to connect the Steam account to user account... for easier
management

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Enhanced user profiles and account views now display integrated Steam
account details and enriched team associations for a more comprehensive
experience.
- **Chores**
- Backend and database refinements have been implemented to improve
system stability, data integrity, and overall performance.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-04-14 10:32:21 +03:00
Kristian Ollikainen
9a6826b069 feat(runner): Fixes and improvements (#259)
## Description

- Improves latency for runner
- Fixes bugs in entrypoint bash scripts
- Package updates, gstreamer 1.26 and workaround for it

Modified runner workflow to hopefully pull latest cachyos base image on
nightlies. This will cause a full build but for nightlies should be
fine?

Also removed the duplicate key-down workaround as we've enabled ordered
datachannels now. Increased retransmit to 2 from 0 to see if it'll help
with some network issues.

Marked as draft as I need to do bug testing still, I'll do it after
fever calms down 😅



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Enhanced deployment workflows with optimized container image
management.
- Improved audio and video processing for lower latency and better
synchronization.
  - Consolidated debugging options to ease command-line monitoring.

- **Refactor**
- Streamlined internal script flow and process handling for smoother
performance.
- Updated dependency management and communication protocols to boost
overall stability.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com>
2025-04-13 23:13:09 +03:00
Wanjohi
f408ec56cb feat(www): Add logic to the homepage and Steam integration (#258)
## Description
<!-- Briefly describe the purpose and scope of your changes -->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Upgraded API and authentication services with dynamic scaling,
enhanced load balancing, and real-time interaction endpoints.
- Introduced new commands to streamline local development and container
builds.
- Added new endpoints for retrieving Steam account information and
managing connections.
- Implemented a QR code authentication interface for Steam, enhancing
user login experiences.

- **Database Updates**
- Rolled out comprehensive schema migrations that improve data integrity
and indexing.
- Introduced new tables for managing Steam user credentials and machine
information.

- **UI Enhancements**
- Added refreshed animated assets and an improved QR code login flow for
a more engaging experience.
	- Introduced new styled components for displaying friends and games.

- **Maintenance**
- Completed extensive refactoring and configuration updates to optimize
performance and development workflows.
- Updated logging configurations and improved error handling mechanisms.
	- Streamlined resource definitions in the configuration files.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-04-13 14:30:45 +03:00
Wanjohi
8394bb4259 🐜 fix(dependabot): Remove the goddamn file (#257)
## Description
<!-- Briefly describe the purpose and scope of your changes -->
2025-04-12 13:50:35 +03:00
dependabot[bot]
0305a14fdd build(deps-dev): bump @nuxt/devtools from 1.7.0 to 2.3.2 in /apps/docs (#256)
Bumps
[@nuxt/devtools](https://github.com/nuxt/devtools/tree/HEAD/packages/devtools)
from 1.7.0 to 2.3.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/nuxt/devtools/releases"><code>@​nuxt/devtools</code>'s
releases</a>.</em></p>
<blockquote>
<h2>v2.3.2</h2>
<h3>   🐞 Bug Fixes</h3>
<ul>
<li>Prioritize vue-devtools plugin registation, fix <a
href="https://github.com/nuxt/devtools/tree/HEAD/packages/devtools/issues/823">#823</a>,
fix <a
href="https://github.com/nuxt/devtools/tree/HEAD/packages/devtools/issues/822">#822</a>
 -  by <a href="https://github.com/antfu"><code>@​antfu</code></a> in <a
href="https://redirect.github.com/nuxt/devtools/issues/823">nuxt/devtools#823</a>
and <a
href="https://redirect.github.com/nuxt/devtools/issues/822">nuxt/devtools#822</a>
<a href="https://github.com/nuxt/devtools/commit/259853b9"><!-- raw HTML
omitted -->(25985)<!-- raw HTML omitted --></a></li>
<li>Update <code>vite-plugin-vue-tracer</code>  -  by <a
href="https://github.com/antfu"><code>@​antfu</code></a> <a
href="https://github.com/nuxt/devtools/commit/0c1740cc"><!-- raw HTML
omitted -->(0c174)<!-- raw HTML omitted --></a></li>
</ul>
<h5>    <a
href="https://github.com/nuxt/devtools/compare/v2.3.1...v2.3.2">View
changes on GitHub</a></h5>
<h2>v2.3.1</h2>
<h3>   🐞 Bug Fixes</h3>
<ul>
<li>Downgrade <code>execa</code> to be compatible with Node v18, fix <a
href="https://github.com/nuxt/devtools/tree/HEAD/packages/devtools/issues/821">#821</a>
 -  by <a href="https://github.com/antfu"><code>@​antfu</code></a> in <a
href="https://redirect.github.com/nuxt/devtools/issues/821">nuxt/devtools#821</a>
<a href="https://github.com/nuxt/devtools/commit/f15c7dca"><!-- raw HTML
omitted -->(f15c7)<!-- raw HTML omitted --></a></li>
</ul>
<h5>    <a
href="https://github.com/nuxt/devtools/compare/v2.3.0...v2.3.1">View
changes on GitHub</a></h5>
<h2>v2.3.0</h2>
<h3>   🚀 Features</h3>
<ul>
<li>Debug page of module mutation  -  by <a
href="https://github.com/antfu"><code>@​antfu</code></a> in <a
href="https://redirect.github.com/nuxt/devtools/issues/770">nuxt/devtools#770</a>
<a href="https://github.com/nuxt/devtools/commit/f7e9ab55"><!-- raw HTML
omitted -->(f7e9a)<!-- raw HTML omitted --></a></li>
</ul>
<h5>    <a
href="https://github.com/nuxt/devtools/compare/v2.2.1...v2.3.0">View
changes on GitHub</a></h5>
<h2>v2.2.1</h2>
<h3>   🐞 Bug Fixes</h3>
<ul>
<li><strong>inspector</strong>: Do not register instapector events if
there is already any  -  by <a
href="https://github.com/antfu"><code>@​antfu</code></a> <a
href="https://github.com/nuxt/devtools/commit/db01e1b5"><!-- raw HTML
omitted -->(db01e)<!-- raw HTML omitted --></a></li>
</ul>
<h5>    <a
href="https://github.com/nuxt/devtools/compare/v2.2.0...v2.2.1">View
changes on GitHub</a></h5>
<h2>v2.2.0</h2>
<h3>   🚀 Features</h3>
<ul>
<li>Migrate to <code>vite-plugin-vue-tracer</code>  -  by <a
href="https://github.com/antfu"><code>@​antfu</code></a> in <a
href="https://redirect.github.com/nuxt/devtools/issues/803">nuxt/devtools#803</a>
<a href="https://github.com/nuxt/devtools/commit/faa08d39"><!-- raw HTML
omitted -->(faa08)<!-- raw HTML omitted --></a></li>
<li>Component graph node name toogle  -  by <a
href="https://github.com/runyasak"><code>@​runyasak</code></a> and <a
href="https://github.com/antfu"><code>@​antfu</code></a> in <a
href="https://redirect.github.com/nuxt/devtools/issues/797">nuxt/devtools#797</a>
<a href="https://github.com/nuxt/devtools/commit/2eb2a37e"><!-- raw HTML
omitted -->(2eb2a)<!-- raw HTML omitted --></a></li>
</ul>
<h5>    <a
href="https://github.com/nuxt/devtools/compare/v2.1.3...v2.2.0">View
changes on GitHub</a></h5>
<h2>v2.1.3</h2>
<p><em>No significant changes</em></p>
<h5>    <a
href="https://github.com/nuxt/devtools/compare/v2.1.2...v2.1.3">View
changes on GitHub</a></h5>
<h2>v2.1.2</h2>
<p><em>No significant changes</em></p>
<h5>    <a
href="https://github.com/nuxt/devtools/compare/v2.1.1...v2.1.2">View
changes on GitHub</a></h5>
<h2>v2.1.1</h2>
<h3>   🚀 Features</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/nuxt/devtools/blob/main/CHANGELOG.md"><code>@​nuxt/devtools</code>'s
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/nuxt/devtools/compare/v2.3.1...v2.3.2">2.3.2</a>
(2025-03-26)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>prioritize vue-devtools plugin registation, fix <a
href="https://redirect.github.com/nuxt/devtools/issues/823">#823</a>,
fix <a
href="https://redirect.github.com/nuxt/devtools/issues/822">#822</a> (<a
href="259853b94c">259853b</a>)</li>
<li>update <code>vite-plugin-vue-tracer</code> (<a
href="0c1740cc1d">0c1740c</a>)</li>
</ul>
<h2><a
href="https://github.com/nuxt/devtools/compare/v2.3.0...v2.3.1">2.3.1</a>
(2025-03-20)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>downgrade <code>execa</code> to be compatible with Node v18, fix <a
href="https://redirect.github.com/nuxt/devtools/issues/821">#821</a> (<a
href="f15c7dca3a">f15c7dc</a>)</li>
</ul>
<h1><a
href="https://github.com/nuxt/devtools/compare/v2.2.1...v2.3.0">2.3.0</a>
(2025-03-13)</h1>
<h3>Features</h3>
<ul>
<li>debug page of module mutation (<a
href="https://redirect.github.com/nuxt/devtools/issues/770">#770</a>)
(<a
href="f7e9ab555a">f7e9ab5</a>)</li>
</ul>
<h2><a
href="https://github.com/nuxt/devtools/compare/v2.2.0...v2.2.1">2.2.1</a>
(2025-03-05)</h2>
<h3>Bug Fixes</h3>
<ul>
<li><strong>inspector:</strong> do not register instapector events if
there is already any (<a
href="db01e1b561">db01e1b</a>)</li>
</ul>
<h1><a
href="https://github.com/nuxt/devtools/compare/v2.1.3...v2.2.0">2.2.0</a>
(2025-03-05)</h1>
<h3>Features</h3>
<ul>
<li>component graph node name toogle (<a
href="https://redirect.github.com/nuxt/devtools/issues/797">#797</a>)
(<a
href="2eb2a37e79">2eb2a37</a>)</li>
<li>migrate to <code>vite-plugin-vue-tracer</code> (<a
href="https://redirect.github.com/nuxt/devtools/issues/803">#803</a>)
(<a
href="faa08d3949">faa08d3</a>)</li>
</ul>
<h2><a
href="https://github.com/nuxt/devtools/compare/v2.1.2...v2.1.3">2.1.3</a>
(2025-03-03)</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="f919dbc5a7"><code>f919dbc</code></a>
chore: release v2.3.2</li>
<li><a
href="068173338c"><code>0681733</code></a>
Add Windsurf to the list of editors (<a
href="https://github.com/nuxt/devtools/tree/HEAD/packages/devtools/issues/820">#820</a>)</li>
<li><a
href="259853b94c"><code>259853b</code></a>
fix: prioritize vue-devtools plugin registation, fix <a
href="https://github.com/nuxt/devtools/tree/HEAD/packages/devtools/issues/823">#823</a>,
fix <a
href="https://github.com/nuxt/devtools/tree/HEAD/packages/devtools/issues/822">#822</a></li>
<li><a
href="11812abcc0"><code>11812ab</code></a>
chore: release v2.3.1</li>
<li><a
href="4e9da7fd36"><code>4e9da7f</code></a>
chore: release v2.3.0</li>
<li><a
href="f7e9ab555a"><code>f7e9ab5</code></a>
feat: debug page of module mutation (<a
href="https://github.com/nuxt/devtools/tree/HEAD/packages/devtools/issues/770">#770</a>)</li>
<li><a
href="68df38ed21"><code>68df38e</code></a>
build: only apply build output customisation to client build (<a
href="https://github.com/nuxt/devtools/tree/HEAD/packages/devtools/issues/806">#806</a>)</li>
<li><a
href="f0d804f945"><code>f0d804f</code></a>
chore: add missing type deps</li>
<li><a
href="a6448d95d6"><code>a6448d9</code></a>
chore: use named catalogs</li>
<li><a
href="eda0e673b4"><code>eda0e67</code></a>
chore: release v2.2.1</li>
<li>Additional commits viewable in <a
href="https://github.com/nuxt/devtools/commits/v2.3.2/packages/devtools">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@nuxt/devtools&package-manager=npm_and_yarn&previous-version=1.7.0&new-version=2.3.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-12 13:47:07 +03:00
Wanjohi
6b1521d7d4 feat(dependabot): Put a leash on it (#231)
## Description
This attempts to limit the dependabot alerts to a week, plus make sure
it works on all the projects

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **Chores**
- Introduced an automated dependency update configuration to help keep
all package ecosystems current.
- **Bug Fixes**
- Adjusted the email sender address configuration to ensure that
outgoing communications display the intended sender details.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-04-12 13:41:42 +03:00
dependabot[bot]
39e187832a build(deps): bump the npm_and_yarn group across 3 directories with 2 updates (#229)
Bumps the npm_and_yarn group with 1 update in the /apps/docs directory:
[koa](https://github.com/koajs/koa).
Bumps the npm_and_yarn group with 1 update in the /apps/www directory:
[vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).
Bumps the npm_and_yarn group with 1 update in the /packages/www
directory:
[vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).

Updates `koa` from 2.15.4 to 2.16.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/koajs/koa/releases">koa's
releases</a>.</em></p>
<blockquote>
<h2>v2.16.1</h2>
<p>fix: don't render redirect values in anchor ref</p>
<h2>2.16.0</h2>
<p>This is a backported release to fix core underlying issue with
<code>HEAD</code> requests when using
<code>http2.createSecureServer</code>. See discussion at <a
href="https://redirect.github.com/koajs/koa/pull/1593">koajs/koa#1593</a>
and <a
href="https://redirect.github.com/koajs/koa/issues/1547">koajs/koa#1547</a>.</p>
<ul>
<li>fix missing cleanup, if response socket is no longer writeable
(issue 1547) (<a
href="https://redirect.github.com/koajs/koa/pull/1593">koajs/koa#1593</a>)
399cb6b0dd2104224c0ef0ce8e92f84e4f7faf42</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="ba14822069"><code>ba14822</code></a>
2.16.1</li>
<li><a
href="2ff6c3fb80"><code>2ff6c3f</code></a>
2.16.0</li>
<li><a
href="3d51d034af"><code>3d51d03</code></a>
ci: allow codecov to fail</li>
<li><a
href="eb84d890b8"><code>eb84d89</code></a>
fix: don't render redirect values in anchor ref</li>
<li>See full diff in <a
href="https://github.com/koajs/koa/compare/2.15.4...v2.16.1">compare
view</a></li>
</ul>
</details>
<br />

Updates `vite` from 5.4.16 to 6.0.14
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/releases">vite's
releases</a>.</em></p>
<blockquote>
<h2>v6.0.14</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.0.14/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.0.13</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.0.13/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.0.12</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.0.12/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.0.11</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.0.11/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.0.10</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.0.10/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.0.9</h2>
<p>This version contains a breaking change due to security fixes. See <a
href="https://github.com/vitejs/vite/security/advisories/GHSA-vg6x-rcgg-rjx6">https://github.com/vitejs/vite/security/advisories/GHSA-vg6x-rcgg-rjx6</a>
for more details.</p>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.0.9/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.0.8</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.0.8/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.0.7</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.0.7/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.0.6</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.0.6/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.0.5</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.0.5/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.0.4</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.0.4/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.0.3</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.0.3/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.0.2</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.0.2/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>plugin-legacy@6.0.2</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/plugin-legacy@6.0.2/packages/plugin-legacy/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>create-vite@6.0.1</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/create-vite@6.0.1/packages/create-vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.0.1</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.0.1/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/blob/v6.0.14/packages/vite/CHANGELOG.md">vite's
changelog</a>.</em></p>
<blockquote>
<h2><!-- raw HTML omitted -->6.0.14 (2025-04-03)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: backport <a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19782">#19782</a>,
fs check with svg and relative paths (<a
href="48ee91df38">48ee91d</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19782">#19782</a></li>
</ul>
<h2><!-- raw HTML omitted -->6.0.13 (2025-03-31)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: fs check in transform middleware (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19761">#19761</a>)
(<a
href="1487f393f3">1487f39</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19761">#19761</a></li>
</ul>
<h2><!-- raw HTML omitted -->6.0.12 (2025-03-24)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: fs raw query with query separators (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19702">#19702</a>)
(<a
href="92ca12dc79">92ca12d</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19702">#19702</a></li>
</ul>
<h2><!-- raw HTML omitted -->6.0.11 (2025-01-21)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: <code>preview.allowedHosts</code> with specific values was not
respected (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19246">#19246</a>)
(<a
href="aeb3ec84a2">aeb3ec8</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19246">#19246</a></li>
<li>fix: allow CORS from loopback addresses by default (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19249">#19249</a>)
(<a
href="3d03899737">3d03899</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19249">#19249</a></li>
</ul>
<h2><!-- raw HTML omitted -->6.0.10 (2025-01-20)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: try parse <code>server.origin</code> URL (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19241">#19241</a>)
(<a
href="2495022420">2495022</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19241">#19241</a></li>
</ul>
<h2><!-- raw HTML omitted -->6.0.9 (2025-01-20)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix!: check host header to prevent DNS rebinding attacks and
introduce <code>server.allowedHosts</code> (<a
href="bd896fb5f3">bd896fb</a>)</li>
<li>fix!: default <code>server.cors: false</code> to disallow fetching
from untrusted origins (<a
href="b09572acc9">b09572a</a>)</li>
<li>fix: verify token for HMR WebSocket connection (<a
href="029dcd6d77">029dcd6</a>)</li>
</ul>
<h2><!-- raw HTML omitted -->6.0.8 (2025-01-20)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: avoid SSR HMR for HTML files (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19193">#19193</a>)
(<a
href="3bd55bcb7e">3bd55bc</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19193">#19193</a></li>
<li>fix: build time display 7m 60s (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19108">#19108</a>)
(<a
href="cf0d2c8e23">cf0d2c8</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19108">#19108</a></li>
<li>fix: don't resolve URL starting with double slash (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19059">#19059</a>)
(<a
href="35942cde11">35942cd</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19059">#19059</a></li>
<li>fix: ensure <code>server.close()</code> only called once (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19204">#19204</a>)
(<a
href="db81c2dada">db81c2d</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19204">#19204</a></li>
<li>fix: resolve.conditions in ResolvedConfig was
<code>defaultServerConditions</code> (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19174">#19174</a>)
(<a
href="ad75c56dce">ad75c56</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19174">#19174</a></li>
<li>fix: tree shake stringified JSON imports (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19189">#19189</a>)
(<a
href="f2aed62d0b">f2aed62</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19189">#19189</a></li>
<li>fix: use shared sigterm callback (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19203">#19203</a>)
(<a
href="47039f4643">47039f4</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19203">#19203</a></li>
<li>fix(deps): update all non-major dependencies (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19098">#19098</a>)
(<a
href="8639538e64">8639538</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19098">#19098</a></li>
<li>fix(optimizer): use correct default install state path for yarn PnP
(<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19119">#19119</a>)
(<a
href="e690d8bb1e">e690d8b</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19119">#19119</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="f678baacaf"><code>f678baa</code></a>
release: v6.0.14</li>
<li><a
href="48ee91df38"><code>48ee91d</code></a>
fix: backport <a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19782">#19782</a>,
fs check with svg and relative paths</li>
<li><a
href="0eaadcf952"><code>0eaadcf</code></a>
release: v6.0.13</li>
<li><a
href="1487f393f3"><code>1487f39</code></a>
fix: fs check in transform middleware (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19761">#19761</a>)</li>
<li><a
href="9d981f9d38"><code>9d981f9</code></a>
release: v6.0.12</li>
<li><a
href="92ca12dc79"><code>92ca12d</code></a>
fix: fs raw query with query separators (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19702">#19702</a>)</li>
<li><a
href="a0ed4057c9"><code>a0ed405</code></a>
release: v6.0.11</li>
<li><a
href="3d03899737"><code>3d03899</code></a>
fix: allow CORS from loopback addresses by default (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19249">#19249</a>)</li>
<li><a
href="aeb3ec84a2"><code>aeb3ec8</code></a>
fix: <code>preview.allowedHosts</code> with specific values was not
respected (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19246">#19246</a>)</li>
<li><a
href="9654348258"><code>9654348</code></a>
release: v6.0.10</li>
<li>Additional commits viewable in <a
href="https://github.com/vitejs/vite/commits/v6.0.14/packages/vite">compare
view</a></li>
</ul>
</details>
<br />

Updates `vite` from 5.4.16 to 6.0.14
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/releases">vite's
releases</a>.</em></p>
<blockquote>
<h2>v6.0.14</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.0.14/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.0.13</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.0.13/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.0.12</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.0.12/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.0.11</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.0.11/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.0.10</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.0.10/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.0.9</h2>
<p>This version contains a breaking change due to security fixes. See <a
href="https://github.com/vitejs/vite/security/advisories/GHSA-vg6x-rcgg-rjx6">https://github.com/vitejs/vite/security/advisories/GHSA-vg6x-rcgg-rjx6</a>
for more details.</p>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.0.9/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.0.8</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.0.8/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.0.7</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.0.7/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.0.6</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.0.6/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.0.5</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.0.5/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.0.4</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.0.4/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.0.3</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.0.3/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.0.2</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.0.2/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>plugin-legacy@6.0.2</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/plugin-legacy@6.0.2/packages/plugin-legacy/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>create-vite@6.0.1</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/create-vite@6.0.1/packages/create-vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.0.1</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.0.1/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/blob/v6.0.14/packages/vite/CHANGELOG.md">vite's
changelog</a>.</em></p>
<blockquote>
<h2><!-- raw HTML omitted -->6.0.14 (2025-04-03)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: backport <a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19782">#19782</a>,
fs check with svg and relative paths (<a
href="48ee91df38">48ee91d</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19782">#19782</a></li>
</ul>
<h2><!-- raw HTML omitted -->6.0.13 (2025-03-31)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: fs check in transform middleware (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19761">#19761</a>)
(<a
href="1487f393f3">1487f39</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19761">#19761</a></li>
</ul>
<h2><!-- raw HTML omitted -->6.0.12 (2025-03-24)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: fs raw query with query separators (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19702">#19702</a>)
(<a
href="92ca12dc79">92ca12d</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19702">#19702</a></li>
</ul>
<h2><!-- raw HTML omitted -->6.0.11 (2025-01-21)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: <code>preview.allowedHosts</code> with specific values was not
respected (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19246">#19246</a>)
(<a
href="aeb3ec84a2">aeb3ec8</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19246">#19246</a></li>
<li>fix: allow CORS from loopback addresses by default (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19249">#19249</a>)
(<a
href="3d03899737">3d03899</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19249">#19249</a></li>
</ul>
<h2><!-- raw HTML omitted -->6.0.10 (2025-01-20)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: try parse <code>server.origin</code> URL (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19241">#19241</a>)
(<a
href="2495022420">2495022</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19241">#19241</a></li>
</ul>
<h2><!-- raw HTML omitted -->6.0.9 (2025-01-20)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix!: check host header to prevent DNS rebinding attacks and
introduce <code>server.allowedHosts</code> (<a
href="bd896fb5f3">bd896fb</a>)</li>
<li>fix!: default <code>server.cors: false</code> to disallow fetching
from untrusted origins (<a
href="b09572acc9">b09572a</a>)</li>
<li>fix: verify token for HMR WebSocket connection (<a
href="029dcd6d77">029dcd6</a>)</li>
</ul>
<h2><!-- raw HTML omitted -->6.0.8 (2025-01-20)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: avoid SSR HMR for HTML files (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19193">#19193</a>)
(<a
href="3bd55bcb7e">3bd55bc</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19193">#19193</a></li>
<li>fix: build time display 7m 60s (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19108">#19108</a>)
(<a
href="cf0d2c8e23">cf0d2c8</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19108">#19108</a></li>
<li>fix: don't resolve URL starting with double slash (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19059">#19059</a>)
(<a
href="35942cde11">35942cd</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19059">#19059</a></li>
<li>fix: ensure <code>server.close()</code> only called once (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19204">#19204</a>)
(<a
href="db81c2dada">db81c2d</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19204">#19204</a></li>
<li>fix: resolve.conditions in ResolvedConfig was
<code>defaultServerConditions</code> (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19174">#19174</a>)
(<a
href="ad75c56dce">ad75c56</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19174">#19174</a></li>
<li>fix: tree shake stringified JSON imports (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19189">#19189</a>)
(<a
href="f2aed62d0b">f2aed62</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19189">#19189</a></li>
<li>fix: use shared sigterm callback (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19203">#19203</a>)
(<a
href="47039f4643">47039f4</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19203">#19203</a></li>
<li>fix(deps): update all non-major dependencies (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19098">#19098</a>)
(<a
href="8639538e64">8639538</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19098">#19098</a></li>
<li>fix(optimizer): use correct default install state path for yarn PnP
(<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19119">#19119</a>)
(<a
href="e690d8bb1e">e690d8b</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19119">#19119</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="f678baacaf"><code>f678baa</code></a>
release: v6.0.14</li>
<li><a
href="48ee91df38"><code>48ee91d</code></a>
fix: backport <a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19782">#19782</a>,
fs check with svg and relative paths</li>
<li><a
href="0eaadcf952"><code>0eaadcf</code></a>
release: v6.0.13</li>
<li><a
href="1487f393f3"><code>1487f39</code></a>
fix: fs check in transform middleware (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19761">#19761</a>)</li>
<li><a
href="9d981f9d38"><code>9d981f9</code></a>
release: v6.0.12</li>
<li><a
href="92ca12dc79"><code>92ca12d</code></a>
fix: fs raw query with query separators (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19702">#19702</a>)</li>
<li><a
href="a0ed4057c9"><code>a0ed405</code></a>
release: v6.0.11</li>
<li><a
href="3d03899737"><code>3d03899</code></a>
fix: allow CORS from loopback addresses by default (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19249">#19249</a>)</li>
<li><a
href="aeb3ec84a2"><code>aeb3ec8</code></a>
fix: <code>preview.allowedHosts</code> with specific values was not
respected (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19246">#19246</a>)</li>
<li><a
href="9654348258"><code>9654348</code></a>
release: v6.0.10</li>
<li>Additional commits viewable in <a
href="https://github.com/vitejs/vite/commits/v6.0.14/packages/vite">compare
view</a></li>
</ul>
</details>
<br />

<details>
<summary>Most Recent Ignore Conditions Applied to This Pull
Request</summary>

| Dependency Name | Ignore Conditions |
| --- | --- |
| vite | [< 5.5, > 5.4.16] |
</details>


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/nestrilabs/nestri/network/alerts).

</details>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Wanjohi <elviswanjohi47@gmail.com>
2025-04-12 13:03:10 +03:00
Wanjohi
de80f3e6ab feat(maitred): Update maitred - hookup to the API (#198)
## Description
We are attempting to hookup maitred to the API
Maitred duties will be:
- [ ] Hookup to the API
- [ ]  Wait for signal (from the API) to start Steam
- [ ] Stop signal to stop the gaming session, clean up Steam... and
maybe do the backup

## Summary by CodeRabbit

- **New Features**
- Introduced Docker-based deployment configurations for both the main
and relay applications.
- Added new API endpoints enabling real-time machine messaging and
enhanced IoT operations.
- Expanded database schema and actor types to support improved machine
tracking.

- **Improvements**
- Enhanced real-time communication and relay management with streamlined
room handling.
- Upgraded dependencies, logging, and error handling for greater
stability and performance.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com>
Co-authored-by: Kristian Ollikainen <14197772+DatCaptainHorse@users.noreply.github.com>
2025-04-07 23:23:53 +03:00
Wanjohi
6990494b34 🐜 fix(infra): Fix the Web path 2025-04-07 23:14:22 +03:00
Wanjohi
5a3fdf25ff 🧹 chore(infra): Upgrade to sst v3.11.21 2025-04-07 22:45:58 +03:00
dependabot[bot]
18b14a4261 build(deps): bump the npm_and_yarn group across 3 directories with 1 update (#224)
Bumps the npm_and_yarn group with 1 update in the /apps/docs directory:
[vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).
Bumps the npm_and_yarn group with 1 update in the /apps/www directory:
[vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).
Bumps the npm_and_yarn group with 1 update in the /packages/www
directory:
[vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).

Updates `vite` from 6.2.3 to 6.2.5
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/releases">vite's
releases</a>.</em></p>
<blockquote>
<h2>v6.2.5</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.2.5/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.2.4</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.2.4/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/blob/v6.2.5/packages/vite/CHANGELOG.md">vite's
changelog</a>.</em></p>
<blockquote>
<h2><!-- raw HTML omitted -->6.2.5 (2025-04-03)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: backport <a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19782">#19782</a>,
fs check with svg and relative paths (<a
href="fdb196e9f8">fdb196e</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19782">#19782</a></li>
</ul>
<h2><!-- raw HTML omitted -->6.2.4 (2025-03-31)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: fs check in transform middleware (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19761">#19761</a>)
(<a
href="7a4fabab6a">7a4faba</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19761">#19761</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="c176acf70a"><code>c176acf</code></a>
release: v6.2.5</li>
<li><a
href="fdb196e9f8"><code>fdb196e</code></a>
fix: backport <a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19782">#19782</a>,
fs check with svg and relative paths</li>
<li><a
href="037f801075"><code>037f801</code></a>
release: v6.2.4</li>
<li><a
href="7a4fabab6a"><code>7a4faba</code></a>
fix: fs check in transform middleware (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19761">#19761</a>)</li>
<li>See full diff in <a
href="https://github.com/vitejs/vite/commits/v6.2.5/packages/vite">compare
view</a></li>
</ul>
</details>
<br />

Updates `vite` from 5.4.12 to 5.4.16
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/releases">vite's
releases</a>.</em></p>
<blockquote>
<h2>v6.2.5</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.2.5/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.2.4</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.2.4/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/blob/v6.2.5/packages/vite/CHANGELOG.md">vite's
changelog</a>.</em></p>
<blockquote>
<h2><!-- raw HTML omitted -->6.2.5 (2025-04-03)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: backport <a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19782">#19782</a>,
fs check with svg and relative paths (<a
href="fdb196e9f8">fdb196e</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19782">#19782</a></li>
</ul>
<h2><!-- raw HTML omitted -->6.2.4 (2025-03-31)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: fs check in transform middleware (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19761">#19761</a>)
(<a
href="7a4fabab6a">7a4faba</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19761">#19761</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="c176acf70a"><code>c176acf</code></a>
release: v6.2.5</li>
<li><a
href="fdb196e9f8"><code>fdb196e</code></a>
fix: backport <a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19782">#19782</a>,
fs check with svg and relative paths</li>
<li><a
href="037f801075"><code>037f801</code></a>
release: v6.2.4</li>
<li><a
href="7a4fabab6a"><code>7a4faba</code></a>
fix: fs check in transform middleware (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19761">#19761</a>)</li>
<li>See full diff in <a
href="https://github.com/vitejs/vite/commits/v6.2.5/packages/vite">compare
view</a></li>
</ul>
</details>
<br />

Updates `vite` from 5.4.12 to 5.4.16
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/releases">vite's
releases</a>.</em></p>
<blockquote>
<h2>v6.2.5</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.2.5/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v6.2.4</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.2.4/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/blob/v6.2.5/packages/vite/CHANGELOG.md">vite's
changelog</a>.</em></p>
<blockquote>
<h2><!-- raw HTML omitted -->6.2.5 (2025-04-03)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: backport <a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19782">#19782</a>,
fs check with svg and relative paths (<a
href="fdb196e9f8">fdb196e</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19782">#19782</a></li>
</ul>
<h2><!-- raw HTML omitted -->6.2.4 (2025-03-31)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: fs check in transform middleware (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19761">#19761</a>)
(<a
href="7a4fabab6a">7a4faba</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19761">#19761</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="c176acf70a"><code>c176acf</code></a>
release: v6.2.5</li>
<li><a
href="fdb196e9f8"><code>fdb196e</code></a>
fix: backport <a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19782">#19782</a>,
fs check with svg and relative paths</li>
<li><a
href="037f801075"><code>037f801</code></a>
release: v6.2.4</li>
<li><a
href="7a4fabab6a"><code>7a4faba</code></a>
fix: fs check in transform middleware (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19761">#19761</a>)</li>
<li>See full diff in <a
href="https://github.com/vitejs/vite/commits/v6.2.5/packages/vite">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/nestrilabs/nestri/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-03 18:50:19 +03:00
dependabot[bot]
f4aa2ca4a4 build(deps): bump vite from 6.2.2 to 6.2.3 in /apps/docs in the npm_and_yarn group across 1 directory (#213)
Bumps the npm_and_yarn group with 1 update in the /apps/docs directory:
[vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).

Updates `vite` from 6.2.2 to 6.2.3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/releases">vite's
releases</a>.</em></p>
<blockquote>
<h2>v6.2.3</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v6.2.3/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/blob/v6.2.3/packages/vite/CHANGELOG.md">vite's
changelog</a>.</em></p>
<blockquote>
<h2><!-- raw HTML omitted -->6.2.3 (2025-03-24)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix: fs raw query with query separators (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19702">#19702</a>)
(<a
href="f234b5744d">f234b57</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/19702">#19702</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="16869d7c99"><code>16869d7</code></a>
release: v6.2.3</li>
<li><a
href="f234b5744d"><code>f234b57</code></a>
fix: fs raw query with query separators (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19702">#19702</a>)</li>
<li>See full diff in <a
href="https://github.com/vitejs/vite/commits/v6.2.3/packages/vite">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=vite&package-manager=npm_and_yarn&previous-version=6.2.2&new-version=6.2.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/nestrilabs/nestri/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Wanjohi <71614375+wanjohiryan@users.noreply.github.com>
2025-04-03 18:44:46 +03:00
Wanjohi
6092c4e4f8 🐜 fix(www): Change CTA for now (#223)
## Description
Change the CTA for the landing page
2025-04-03 18:29:21 +03:00
Wanjohi
f3d7ea2663 🧹 chore: Use simpler PR template (#220)
## Description
Use a simpler template
2025-03-26 08:01:50 +03:00
Wanjohi
7ff4ff8c90 🐜 fix(infra): Make sure all stages are in sync (#219)
## Description
Syncs all branches to the current changes
2025-03-26 07:59:40 +03:00
Wanjohi
7ecc068466 feat(infra): Use a shared VPC (#218)
## Description
The scope of this PR is to add a shared VPC for everyone on the team.
2025-03-26 06:29:25 +03:00
Wanjohi
633b332700 🏇🏾ci(docs): Deploy docs to cloudflare (#217)
## Description
This deploys docs to cloudflare pages

## Related Issues
<!-- List any related issues (e.g., "Closes #123", "Fixes #456") -->

## Type of Change

- [ ] Bug fix (non-breaking change)
- [ ] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that changes existing
functionality)
- [ ] Documentation update
- [ ] Other (please describe):

## Checklist

- [ ] I have updated relevant documentation
- [ ] My code follows the project's coding style
- [ ] My changes generate no new warnings/errors

## Notes for Reviewers
<!-- Point out areas you'd like reviewers to focus on, questions you
have, or decisions that need discussion -->

## Screenshots/Demo
<!-- If applicable, add screenshots or a GIF demo of your changes
(especially for UI changes) -->

## Additional Context
<!-- Add any other context about the pull request here -->
2025-03-26 04:03:17 +03:00
Wanjohi
cacdae79c0 🐜 fix(ci): Remove unnecessary code 2025-03-26 03:35:16 +03:00
Wanjohi
ca4432bcde 🐜 fix(ci): Fix issue where main does not deploy to prod (#216)
## Description
Fix an issue where the `apps/www` CI does not push to main

## Related Issues
<!-- List any related issues (e.g., "Closes #123", "Fixes #456") -->

## Type of Change

- [x] Bug fix (non-breaking change)
- [ ] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that changes existing
functionality)
- [ ] Documentation update
- [ ] Other (please describe):

## Checklist

- [ ] I have updated relevant documentation
- [ ] My code follows the project's coding style
- [ ] My changes generate no new warnings/errors

## Notes for Reviewers
<!-- Point out areas you'd like reviewers to focus on, questions you
have, or decisions that need discussion -->

## Screenshots/Demo
<!-- If applicable, add screenshots or a GIF demo of your changes
(especially for UI changes) -->

## Additional Context
<!-- Add any other context about the pull request here -->
2025-03-26 03:31:09 +03:00
Wanjohi
261a1276f5 🏇 ci(www): Test whether it is working (#215)
## Description
This fixes issues with the `apps/www` ci
## Related Issues
<!-- List any related issues (e.g., "Closes #123", "Fixes #456") -->

## Type of Change

- [x] Bug fix (non-breaking change)
- [ ] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that changes existing
functionality)
- [ ] Documentation update
- [ ] Other (please describe):

## Checklist

- [ ] I have updated relevant documentation
- [ ] My code follows the project's coding style
- [ ] My changes generate no new warnings/errors

## Notes for Reviewers
<!-- Point out areas you'd like reviewers to focus on, questions you
have, or decisions that need discussion -->

## Screenshots/Demo
<!-- If applicable, add screenshots or a GIF demo of your changes
(especially for UI changes) -->

## Additional Context
<!-- Add any other context about the pull request here -->
2025-03-26 03:20:40 +03:00
Wanjohi
a45b2bf9b7 feat(ci): Deploy nestri landing page (#214)
## Description
This attempts to deploy `apps/www` to the new CF account using
cloudflare pages project

## Related Issues
<!-- List any related issues (e.g., "Closes #123", "Fixes #456") -->

## Type of Change

- [ ] Bug fix (non-breaking change)
- [x] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that changes existing
functionality)
- [ ] Documentation update
- [ ] Other (please describe):

## Checklist

- [ ] I have updated relevant documentation
- [ ] My code follows the project's coding style
- [ ] My changes generate no new warnings/errors

## Notes for Reviewers
<!-- Point out areas you'd like reviewers to focus on, questions you
have, or decisions that need discussion -->

## Screenshots/Demo
<!-- If applicable, add screenshots or a GIF demo of your changes
(especially for UI changes) -->

## Additional Context
<!-- Add any other context about the pull request here -->
2025-03-26 03:04:28 +03:00
Wanjohi
f62fc1fb4b feat(www): Finish up on the onboarding (#210)
Merging this prematurely to make sure the team is on the same boat... like dang! We need to find a better way to do this. 

Plus it has become too big
2025-03-26 02:21:53 +03:00
dependabot[bot]
957eca7794 build(deps-dev): bump nuxt from 3.15.4 to 3.16.1 in /apps/docs in the npm_and_yarn group across 1 directory (#211)
Bumps the npm_and_yarn group with 1 update in the /apps/docs directory:
[nuxt](https://github.com/nuxt/nuxt/tree/HEAD/packages/nuxt).

Updates `nuxt` from 3.15.4 to 3.16.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/nuxt/nuxt/releases">nuxt's
releases</a>.</em></p>
<blockquote>
<h2>v3.16.1</h2>
<p><a
href="https://github.com/nuxt/nuxt/compare/v3.16.0...v3.16.1">compare
changes</a></p>
<h3>🔥 Performance</h3>
<ul>
<li><strong>nuxt:</strong> Use browser cache for payloads (<a
href="https://redirect.github.com/nuxt/nuxt/pull/31379">#31379</a>)</li>
</ul>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>nuxt:</strong> Restore nuxt aliases to nitro
compilerOptions.paths (<a
href="https://redirect.github.com/nuxt/nuxt/pull/31278">#31278</a>)</li>
<li><strong>nuxt:</strong> Use new <code>mocked-exports</code> (<a
href="https://redirect.github.com/nuxt/nuxt/pull/31295">#31295</a>)</li>
<li><strong>nuxt:</strong> Check resolved options for polyfills (<a
href="https://redirect.github.com/nuxt/nuxt/pull/31307">#31307</a>)</li>
<li><strong>nuxt:</strong> Render style component html (<a
href="https://redirect.github.com/nuxt/nuxt/pull/31337">#31337</a>)</li>
<li><strong>nuxt:</strong> Add missing lazy hydration prop in regex (<a
href="https://redirect.github.com/nuxt/nuxt/pull/31359">#31359</a>)</li>
<li><strong>nuxt:</strong> Fully resolve nuxt dependencies (<a
href="https://redirect.github.com/nuxt/nuxt/pull/31436">#31436</a>)</li>
<li><strong>vite:</strong> Don't show interim vite build output files
(<a
href="https://redirect.github.com/nuxt/nuxt/pull/31439">#31439</a>)</li>
<li><strong>nuxt:</strong> Ignore prerendering unprefixed public assets
(<a
href="https://github.com/nuxt/nuxt/commit/151912ec3">151912ec3</a>)</li>
<li><strong>nuxt:</strong> Use more performant router catchall pattern
(<a
href="https://redirect.github.com/nuxt/nuxt/pull/31450">#31450</a>)</li>
<li><strong>nuxt:</strong> Prevent param duplication in
<code>typedPages</code> implementation (<a
href="https://redirect.github.com/nuxt/nuxt/pull/31331">#31331</a>)</li>
<li><strong>nuxt:</strong> Sort route paths before creating route tree
(<a
href="https://redirect.github.com/nuxt/nuxt/pull/31454">#31454</a>)</li>
</ul>
<h3>📖 Documentation</h3>
<ul>
<li>Update link to vercel edge network (<a
href="https://github.com/nuxt/nuxt/commit/ec20802a5">ec20802a5</a>)</li>
<li>Improve HMR performance note for Windows users (<a
href="https://redirect.github.com/nuxt/nuxt/pull/31301">#31301</a>)</li>
<li>Correct WSL note phrasing (<a
href="https://redirect.github.com/nuxt/nuxt/pull/31322">#31322</a>)</li>
<li>Fix typo (<a
href="https://redirect.github.com/nuxt/nuxt/pull/31341">#31341</a>)</li>
<li>Adjust <code>app.head</code> example (<a
href="https://redirect.github.com/nuxt/nuxt/pull/31350">#31350</a>)</li>
<li>Include package manager options in update script (<a
href="https://redirect.github.com/nuxt/nuxt/pull/31346">#31346</a>)</li>
<li>Add missing comma (<a
href="https://redirect.github.com/nuxt/nuxt/pull/31362">#31362</a>)</li>
<li>Add mention of <code>addServerTemplate</code> to modules guide (<a
href="https://redirect.github.com/nuxt/nuxt/pull/31369">#31369</a>)</li>
<li>Add <code>rspack</code> and remove <code>test-utils</code> for
monorepo guide (<a
href="https://redirect.github.com/nuxt/nuxt/pull/31371">#31371</a>)</li>
<li>Adjust example (<a
href="https://redirect.github.com/nuxt/nuxt/pull/31372">#31372</a>)</li>
<li>Update experimental docs (<a
href="https://redirect.github.com/nuxt/nuxt/pull/31388">#31388</a>)</li>
<li>Use <code>ini</code> syntax block highlighting for <code>.env</code>
files (<a
href="https://github.com/nuxt/nuxt/commit/f79fabe46">f79fabe46</a>)</li>
<li>Improve <code>useHydration</code> docs (<a
href="https://redirect.github.com/nuxt/nuxt/pull/31427">#31427</a>)</li>
<li>Update broken docs links (<a
href="https://redirect.github.com/nuxt/nuxt/pull/31430">#31430</a>)</li>
<li>Mention possibility of prerendering api routes (<a
href="https://redirect.github.com/nuxt/nuxt/pull/31234">#31234</a>)</li>
</ul>
<h3>🏡 Chore</h3>
<ul>
<li>Fix gitignore (<a
href="https://github.com/nuxt/nuxt/commit/6fe9dff7e">6fe9dff7e</a>)</li>
<li>Bump axios dependency in lockfile (<a
href="https://github.com/nuxt/nuxt/commit/c3352e80b">c3352e80b</a>)</li>
<li>Lint repo (<a
href="https://github.com/nuxt/nuxt/commit/2ab20bfdc">2ab20bfdc</a>)</li>
<li>Add scorecard badge (<a
href="https://redirect.github.com/nuxt/nuxt/pull/31302">#31302</a>)</li>
<li>Dedupe (<a
href="https://github.com/nuxt/nuxt/commit/be5d85f2b">be5d85f2b</a>)</li>
</ul>
<h3> Tests</h3>
<ul>
<li>Migrate runtime compiler test to playwright (+ add test cases) (<a
href="https://redirect.github.com/nuxt/nuxt/pull/31405">#31405</a>)</li>
<li>Migrate spa preloader tests to playwright (<a
href="https://redirect.github.com/nuxt/nuxt/pull/31273">#31273</a>)</li>
<li>Use <code>srvx</code> and random port for remote provider (<a
href="https://redirect.github.com/nuxt/nuxt/pull/31432">#31432</a>)</li>
</ul>
<h3>🤖 CI</h3>
<ul>
<li>Automate release on merge of of v3/v4 (<a
href="https://github.com/nuxt/nuxt/commit/6ae5b5fdb">6ae5b5fdb</a>)</li>
<li>Fix workflow quoting (<a
href="https://github.com/nuxt/nuxt/commit/fef39cf3c">fef39cf3c</a>)</li>
</ul>
<h3>❤️ Contributors</h3>
<ul>
<li>Daniel Roe (<a
href="https://github.com/danielroe"><code>@​danielroe</code></a>)</li>
<li>Anoesj Sadraee (<a
href="https://github.com/Anoesj"><code>@​Anoesj</code></a>)</li>
<li>Peter Radko (<a
href="https://github.com/Gwynerva"><code>@​Gwynerva</code></a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="cd38de24c1"><code>cd38de2</code></a>
v3.16.1</li>
<li><a
href="ea9025ea01"><code>ea9025e</code></a>
fix(nuxt): sort route paths before creating route tree (<a
href="https://github.com/nuxt/nuxt/tree/HEAD/packages/nuxt/issues/31454">#31454</a>)</li>
<li><a
href="7bbc794b19"><code>7bbc794</code></a>
fix(nuxt): prevent param duplication in <code>typedPages</code>
implementation (<a
href="https://github.com/nuxt/nuxt/tree/HEAD/packages/nuxt/issues/31331">#31331</a>)</li>
<li><a
href="10a4f8ee6b"><code>10a4f8e</code></a>
fix(nuxt): use more performant router catchall pattern (<a
href="https://github.com/nuxt/nuxt/tree/HEAD/packages/nuxt/issues/31450">#31450</a>)</li>
<li><a
href="502720f4c8"><code>502720f</code></a>
chore(deps): update all non-major dependencies (3.x) (<a
href="https://github.com/nuxt/nuxt/tree/HEAD/packages/nuxt/issues/31452">#31452</a>)</li>
<li><a
href="151912ec38"><code>151912e</code></a>
fix(nuxt): ignore prerendering unprefixed public assets</li>
<li><a
href="a752be862a"><code>a752be8</code></a>
docs: update broken docs links (<a
href="https://github.com/nuxt/nuxt/tree/HEAD/packages/nuxt/issues/31430">#31430</a>)</li>
<li><a
href="19ac2e76ff"><code>19ac2e7</code></a>
fix(nuxt): fully resolve nuxt dependencies (<a
href="https://github.com/nuxt/nuxt/tree/HEAD/packages/nuxt/issues/31436">#31436</a>)</li>
<li><a
href="9374ceb420"><code>9374ceb</code></a>
chore(deps): update devdependency nitropack to v2.11.7 (3.x) (<a
href="https://github.com/nuxt/nuxt/tree/HEAD/packages/nuxt/issues/31441">#31441</a>)</li>
<li><a
href="e26dd61e2e"><code>e26dd61</code></a>
chore(deps): update all non-major dependencies (3.x) (<a
href="https://github.com/nuxt/nuxt/tree/HEAD/packages/nuxt/issues/31421">#31421</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/nuxt/nuxt/commits/v3.16.1/packages/nuxt">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=nuxt&package-manager=npm_and_yarn&previous-version=3.15.4&new-version=3.16.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/nestrilabs/nestri/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-21 10:05:48 +03:00
371 changed files with 57938 additions and 8554 deletions

View File

@@ -1,28 +1,2 @@
## Description
<!-- Briefly describe the purpose and scope of your changes -->
## Related Issues
<!-- List any related issues (e.g., "Closes #123", "Fixes #456") -->
## Type of Change
- [ ] Bug fix (non-breaking change)
- [ ] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that changes existing functionality)
- [ ] Documentation update
- [ ] Other (please describe):
## Checklist
- [ ] I have updated relevant documentation
- [ ] My code follows the project's coding style
- [ ] My changes generate no new warnings/errors
## Notes for Reviewers
<!-- Point out areas you'd like reviewers to focus on, questions you have, or decisions that need discussion -->
## Screenshots/Demo
<!-- If applicable, add screenshots or a GIF demo of your changes (especially for UI changes) -->
## Additional Context
<!-- Add any other context about the pull request here -->

40
.github/workflows/docs.yml vendored Normal file
View File

@@ -0,0 +1,40 @@
name: Build docs
on:
pull_request:
paths:
- "apps/docs/**"
- ".github/workflows/docs.yml"
push:
branches: [main]
paths:
- "apps/docs/**"
- ".github/workflows/docs.yml"
jobs:
deploy-docs:
name: Build and deploy docs
runs-on: ubuntu-latest
defaults:
run:
working-directory: "apps/docs"
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- name: Install dependencies
run: bun install
- name: Build Project Artifacts
run: bun run build
- name: Deploy Project Artifacts to Cloudflare
uses: cloudflare/wrangler-action@v3
with:
packageManager: bun
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
wranglerVersion: "3.93.0"
workingDirectory: "apps/docs"
command: pages deploy ./dist --project-name=${{ vars.CF_DOCS_PAGES_PROJECT_NAME }} --commit-dirty=true
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

View File

@@ -27,6 +27,7 @@ env:
REGISTRY: ghcr.io
IMAGE_NAME: nestrilabs/nestri
BASE_TAG_PREFIX: runner
BASE_IMAGE: docker.io/cachyos/cachyos:latest
# This makes our release ci quit prematurely
# concurrency:
@@ -55,7 +56,7 @@ jobs:
swap-size-gb: 20
-
name: Build Docker image
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
with:
file: containers/runner.Containerfile
context: ./
@@ -107,7 +108,7 @@ jobs:
swap-size-gb: 20
-
name: Build Docker image
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
with:
file: containers/runner.Containerfile
context: ./
@@ -116,3 +117,4 @@ jobs:
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha,mode=max
cache-to: type=gha,mode=max
pull: ${{ github.event_name == 'schedule' }} # Pull base image for scheduled builds

40
.github/workflows/www.yml vendored Normal file
View File

@@ -0,0 +1,40 @@
name: Build www
on:
pull_request:
paths:
- "apps/www/**"
- ".github/workflows/www.yml"
push:
branches: [main]
paths:
- "apps/www/**"
- ".github/workflows/www.yml"
jobs:
deploy-www:
name: Build and deploy www
runs-on: ubuntu-latest
defaults:
run:
working-directory: "apps/www"
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- name: Install dependencies
run: bun install
- name: Build Project Artifacts
run: bun run build
- name: Deploy Project Artifacts to Cloudflare
uses: cloudflare/wrangler-action@v3
with:
packageManager: bun
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
wranglerVersion: "3.93.0"
workingDirectory: "apps/www"
command: pages deploy ./dist --project-name=${{ vars.CF_WWW_PAGES_PROJECT_NAME }} --commit-dirty=true
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}

File diff suppressed because it is too large Load Diff

View File

@@ -4,19 +4,19 @@
"private": true,
"scripts": {
"nestri.dev": "nuxi dev",
"build": "nuxi build",
"build": "nuxi build --preset=cloudflare_pages",
"generate": "nuxi generate",
"preview": "nuxi preview",
"lint": "eslint ."
},
"devDependencies": {
"@nuxt-themes/docus": "latest",
"@nuxt/devtools": "^1.4.1",
"@nuxt/devtools": "^2.3.2",
"@nuxt/eslint-config": "^0.5.6",
"@nuxt/ui": "^2.19.2",
"@nuxtjs/plausible": "^1.0.2",
"@types/node": "^20.16.5",
"eslint": "^9.10.0",
"nuxt": "^3.15.4"
"nuxt": "^3.16.1"
}
}

View File

@@ -44,7 +44,7 @@
"@nestri/libmoq": "*",
"@nestri/sdk": "0.1.0-alpha.14",
"@nestri/ui": "*",
"@openauthjs/openauth": "^0.2.6",
"@openauthjs/openauth": "*",
"@polar-sh/checkout": "^0.1.8",
"@polar-sh/sdk": "^0.21.1",
"@qwik-ui/headless": "^0.6.4",
@@ -67,7 +67,7 @@
"typescript": "5.4.5",
"undici": "*",
"valibot": "^0.42.1",
"vite": "5.4.12",
"vite": "6.0.15",
"vite-tsconfig-paths": "^4.2.1",
"wrangler": "^3.0.0"
},

View File

@@ -72,7 +72,7 @@ export default component$(() => {
});
const lockPlay = $(async () => {
if (!canvas.value || !playState.hasStream) return;
if (!canvas.value || !playState.hasStream || playState.nestriLock) return;
try {
await canvas.value.requestPointerLock();
@@ -156,18 +156,22 @@ export default component$(() => {
});
});
// eslint-disable-next-line qwik/no-use-visible-task
useVisibleTask$(({ track }) => {
track(() => canvas.value);
if (!canvas.value) return; // Ensure canvas is available
// Get query parameter "peerURL" from the URL
let peerURL = new URLSearchParams(window.location.search).get("peerURL");
if (!peerURL || peerURL.length <= 0) {
peerURL = "/dnsaddr/relay.dathorse.com/p2p/12D3KooWPK4v5wKYNYx9oXWjqLM8Xix6nm13o91j1Feqq98fLBsw";
}
setupPointerLockListener();
try {
if (!playState.video) {
playState.video = document.createElement("video") as HTMLVideoElement
playState.video = document.createElement("video") as HTMLVideoElement;
playState.video.style.visibility = "hidden";
playState.webrtc = noSerialize(new WebRTCStream("https://relay.dathorse.com", id, async (mediaStream) => {
playState.webrtc = noSerialize(new WebRTCStream(peerURL, id, async (mediaStream) => {
if (playState.video && mediaStream && playState.video.srcObject === null) {
console.log("Setting mediastream");
playState.video.srcObject = mediaStream;

View File

@@ -16,8 +16,8 @@ export default component$(() => {
<div class="w-screen relative">
<HeroSection client:load>
<div class="sm:w-full flex gap-3 justify-center pt-4 sm:flex-row flex-col w-auto items-center">
<Link href="/auth/login" prefetch={false} class="flex ring-2 ring-primary-500 font-bricolage text-sm sm:text-base rounded-full bg-primary-500 px-5 py-4 font-semibold text-white transition-all hover:scale-105 active:scale-95 sm:px-6" >
Get early access
<Link href="https://discord.gg/6um5K6jrYj" prefetch={false} class="flex ring-2 ring-primary-500 font-bricolage text-sm sm:text-base rounded-full bg-primary-500 px-5 py-4 font-semibold text-white transition-all hover:scale-105 active:scale-95 sm:px-6" >
Join our Discord
</Link>
<Link href="/links/github" prefetch={false} class="sm:flex text-sm sm:text-base hidden font-bricolage items-center gap-2 rounded-full font-semibold text-gray-900/70 dark:text-gray-100/70 bg-white dark:bg-black px-5 py-4 ring-2 ring-gray-300 dark:ring-gray-700 transition-all hover:scale-105 active:scale-95 sm:px-6" >
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" class="h-5 w-5 fill-content3-light"><path fill-rule="evenodd" d="M4.25 5.5a.75.75 0 00-.75.75v8.5c0 .414.336.75.75.75h8.5a.75.75 0 00.75-.75v-4a.75.75 0 011.5 0v4A2.25 2.25 0 0112.75 17h-8.5A2.25 2.25 0 012 14.75v-8.5A2.25 2.25 0 014.25 4h5a.75.75 0 010 1.5h-5z" clip-rule="evenodd"></path><path fill-rule="evenodd" d="M6.194 12.753a.75.75 0 001.06.053L16.5 4.44v2.81a.75.75 0 001.5 0v-4.5a.75.75 0 00-.75-.75h-4.5a.75.75 0 000 1.5h2.553l-9.056 8.194a.75.75 0 00-.053 1.06z" clip-rule="evenodd"></path></svg>
@@ -570,8 +570,8 @@ export default component$(() => {
</section>
<Footer client:load>
<div class="w-full flex justify-center flex-col items-center gap-3">
<Link href="/auth/login" prefetch={false} class="ring-2 ring-primary-500 flex font-bricolage text-sm sm:text-base rounded-full bg-primary-500 px-5 py-4 font-semibold text-white transition-all hover:scale-105 active:scale-95 sm:px-6" >
Get early access
<Link href="https://discord.gg/6um5K6jrYj" prefetch={false} class="ring-2 ring-primary-500 flex font-bricolage text-sm sm:text-base rounded-full bg-primary-500 px-5 py-4 font-semibold text-white transition-all hover:scale-105 active:scale-95 sm:px-6" >
Join our Discord
</Link>
<div class="mt-6 flex w-full items-center justify-center gap-2 text-xs sm:text-sm font-medium text-neutral-600 dark:text-neutral-400">
<span class="hover:text-primary-500 transition-colors duration-200">

View File

@@ -521,8 +521,8 @@ export default component$(() => {
</MotionComponent>
<Footer client:load>
<div class="w-full flex justify-center flex-col items-center gap-3">
<Link href="/auth/login" prefetch={false} class="flex font-bricolage text-sm sm:text-base rounded-full bg-primary-500 px-5 py-4 font-semibold text-white transition-all hover:scale-105 active:scale-95 sm:px-6" >
Get early access
<Link href="https://discord.gg/6um5K6jrYj" prefetch={false} class="flex font-bricolage text-sm sm:text-base rounded-full bg-primary-500 px-5 py-4 font-semibold text-white transition-all hover:scale-105 active:scale-95 sm:px-6" >
Join our Discord
</Link>
<div class="mt-6 flex w-full items-center justify-center gap-2 text-xs sm:text-sm font-medium text-neutral-600 dark:text-neutral-400">
<span class="hover:text-primary-500 transition-colors duration-200">

4010
bun.lock

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
FROM docker.io/golang:1.24-bookworm AS go-build
WORKDIR /builder
COPY packages/maitred/ /builder/
RUN go build
FROM docker.io/golang:1.24-bookworm
COPY --from=go-build /builder/maitred /maitred/maitred
WORKDIR /maitred
RUN apt update && apt install -y --no-install-recommends pciutils
ENTRYPOINT ["/maitred/maitred"]

View File

@@ -10,17 +10,17 @@ WORKDIR /relay
# TODO: Switch running layer to just alpine (doesn't need golang dev stack)
# ENV flags
ENV REGEN_IDENTITY=false
ENV VERBOSE=false
ENV DEBUG=false
ENV ENDPOINT_PORT=8088
ENV WEBRTC_UDP_START=10000
ENV WEBRTC_UDP_END=20000
ENV WEBRTC_UDP_START=0
ENV WEBRTC_UDP_END=0
ENV STUN_SERVER="stun.l.google.com:19302"
ENV WEBRTC_UDP_MUX=8088
ENV WEBRTC_NAT_IPS=""
ENV AUTO_ADD_LOCAL_IP=true
ENV TLS_CERT=""
ENV TLS_KEY=""
ENV PERSIST_DIR="./persist-data"
EXPOSE $ENDPOINT_PORT
EXPOSE $WEBRTC_UDP_START-$WEBRTC_UDP_END/udp

View File

@@ -85,8 +85,8 @@ RUN --mount=type=cache,target=/var/cache/pacman/pkg \
pacman -Sy --noconfirm meson pkgconf cmake git gcc make \
libxkbcommon wayland gstreamer gst-plugins-base gst-plugins-good libinput
# Clone repository with proper directory structure
RUN git clone -b dev-dmabuf https://github.com/games-on-whales/gst-wayland-display.git
# Clone repository
RUN git clone -b dev-dmabuf https://github.com/DatCaptainHorse/gst-wayland-display.git
#--------------------------------------------------------------------
FROM gst-wayland-deps AS gst-wayland-planner
@@ -132,9 +132,11 @@ RUN sed -i \
# Core system components
RUN --mount=type=cache,target=/var/cache/pacman/pkg \
pacman -Sy --needed --noconfirm \
vulkan-intel lib32-vulkan-intel vpl-gpu-rt mesa \
steam steam-native-runtime \
sudo xorg-xwayland seatd libinput labwc wlr-randr mangohud \
vulkan-intel lib32-vulkan-intel vpl-gpu-rt \
vulkan-radeon lib32-vulkan-radeon \
mesa \
steam steam-native-runtime gtk3 lib32-gtk3 \
sudo xorg-xwayland seatd libinput gamescope mangohud \
libssh2 curl wget \
pipewire pipewire-pulse pipewire-alsa wireplumber \
noto-fonts-cjk supervisor jq chwd lshw pacman-contrib && \
@@ -144,6 +146,9 @@ RUN --mount=type=cache,target=/var/cache/pacman/pkg \
gst-plugins-bad gst-plugin-pipewire \
gst-plugin-webrtchttp gst-plugin-rswebrtc gst-plugin-rsrtp \
gst-plugin-va gst-plugin-qsv && \
# lib32 GStreamer stack to fix some games with videos
pacman -Sy --needed --noconfirm \
lib32-gstreamer lib32-gst-plugins-base lib32-gst-plugins-good && \
# Cleanup
paccache -rk1 && \
rm -rf /usr/share/{info,man,doc}/*
@@ -163,8 +168,7 @@ ENV USER="nestri" \
USER_PWD="nestri1234" \
XDG_RUNTIME_DIR=/run/user/1000 \
HOME=/home/nestri \
NVIDIA_DRIVER_CAPABILITIES=all \
NVIDIA_VISIBLE_DEVICES=all
NVIDIA_DRIVER_CAPABILITIES=all
RUN mkdir -p /home/${USER} && \
groupadd -g ${GID} ${USER} && \
@@ -185,6 +189,30 @@ RUN mkdir -p /run/dbus && \
-e '/wants = \[/{s/hooks\.node\.suspend\s*//; s/,\s*\]/]/}' \
/usr/share/wireplumber/wireplumber.conf
### PipeWire Latency Optimizations (1-5ms instead of 20ms) ###
RUN mkdir -p /etc/pipewire/pipewire.conf.d && \
echo "[audio]\
\n default.clock.rate = 48000\
\n default.clock.quantum = 128\
\n default.clock.min-quantum = 128\
\n default.clock.max-quantum = 256" > /etc/pipewire/pipewire.conf.d/low-latency.conf && \
mkdir -p /etc/wireplumber/main.lua.d && \
echo 'table.insert(default_nodes.rules, {\
\n matches = { { { "node.name", "matches", ".*" } } },\
\n apply_properties = {\
\n ["audio.format"] = "S16LE",\
\n ["audio.rate"] = 48000,\
\n ["audio.channels"] = 2,\
\n ["api.alsa.period-size"] = 128,\
\n ["api.alsa.headroom"] = 0,\
\n ["session.suspend-timeout-seconds"] = 0\
\n }\
\n})' > /etc/wireplumber/main.lua.d/50-low-latency.lua && \
echo "default-fragments = 2\
\ndefault-fragment-size-msec = 2" >> /etc/pulse/daemon.conf && \
echo "load-module module-loopback latency_msec=1" >> /etc/pipewire/pipewire.conf.d/loopback.conf
### Artifacts and Verification ###
COPY --from=nestri-server-cached-builder /artifacts/nestri-server /usr/bin/
COPY --from=gst-wayland-cached-builder /artifacts/lib/ /usr/lib/

View File

@@ -1 +0,0 @@
#FIXME: A simple docker-compose file for running the MoQ relay and the cachyos server

View File

@@ -1,82 +1,120 @@
import { bus } from "./bus";
import { vpc } from "./vpc";
import { auth } from "./auth";
import { domain } from "./dns";
import { email } from "./email";
import { secret } from "./secret";
import { database } from "./database";
import { postgres } from "./postgres";
sst.Linkable.wrap(random.RandomString, (resource) => ({
const urls = new sst.Linkable("Urls", {
properties: {
value: resource.result,
},
}));
export const urls = new sst.Linkable("Urls", {
properties: {
api: "https://api." + domain,
auth: "https://auth." + domain,
site: $dev ? "http://localhost:4321" : "https://" + domain,
},
});
export const authFingerprintKey = new random.RandomString(
"AuthFingerprintKey",
{
length: 32,
},
);
export const auth = new sst.aws.Auth("Auth", {
issuer: {
timeout: "3 minutes",
handler: "./packages/functions/src/auth.handler",
link: [
bus,
email,
database,
authFingerprintKey,
secret.PolarSecret,
secret.GithubClientID,
secret.DiscordClientID,
secret.GithubClientSecret,
secret.DiscordClientSecret,
],
permissions: [
{
actions: ["ses:SendEmail"],
resources: ["*"],
},
],
},
domain: {
name: "auth." + domain,
dns: sst.cloudflare.dns(),
},
api: `https://api.${domain}`,
auth: `https://auth.${domain}`,
site: $dev ? "http://localhost:3000" : `https://console.${domain}`,
}
})
export const apiFunction = new sst.aws.Function("ApiFn", {
const apiFn = new sst.aws.Function("ApiFn", {
vpc,
handler: "packages/functions/src/api/index.handler",
streaming: !$dev,
link: [
bus,
urls,
database,
auth,
postgres,
secret.SteamApiKey,
secret.PolarSecret,
secret.PolarWebhookSecret,
secret.NestriFamilyMonthly,
secret.NestriFamilyYearly,
secret.NestriFreeMonthly,
secret.NestriProMonthly,
secret.NestriProYearly,
],
timeout: "3 minutes",
streaming: !$dev,
url: true
})
url: true,
});
const provider = new aws.Provider("UsEast1", { region: "us-east-1" });
const webAcl = new aws.wafv2.WebAcl(
"ApiWaf",
{
scope: "CLOUDFRONT",
defaultAction: {
allow: {},
},
visibilityConfig: {
cloudwatchMetricsEnabled: true,
metricName: "api-rate-limit-metric",
sampledRequestsEnabled: true,
},
rules: [
{
name: "rate-limit-rule",
priority: 1,
action: {
block: {
customResponse: {
responseCode: 429,
customResponseBodyKey: "rate-limit-response",
},
},
},
statement: {
rateBasedStatement: {
limit: 2 * 60, // 2 rps per authorization header
evaluationWindowSec: 60,
aggregateKeyType: "CUSTOM_KEYS",
customKeys: [
{
header: {
name: "Authorization",
textTransformations: [{ priority: 0, type: "NONE" }],
},
},
],
},
},
visibilityConfig: {
cloudwatchMetricsEnabled: true,
metricName: "rate-limit-rule-metric",
sampledRequestsEnabled: true,
},
},
],
customResponseBodies: [
{
key: "rate-limit-response",
content: JSON.stringify({
type: "rate_limit",
code: "too_many_requests",
message: "Rate limit exceeded. Please try again later.",
}),
contentType: "APPLICATION_JSON",
},
],
},
{ provider },
);
export const api = new sst.aws.Router("Api", {
routes: {
"/*": apiFunction.url
"/*": apiFn.url,
},
domain: {
name: "api." + domain,
dns: sst.cloudflare.dns(),
},
})
export const outputs = {
auth: auth.url,
api: api.url,
};
transform: {
cdn(args) {
if (!args.transform) {
args.transform = {
distribution: {},
};
}
args.transform!.distribution = {
webAclId: webAcl.arn,
};
},
},
});

32
infra/auth.ts Normal file
View File

@@ -0,0 +1,32 @@
import { bus } from "./bus";
import { vpc } from "./vpc";
import { domain } from "./dns";
import { secret } from "./secret";
import { postgres } from "./postgres";
export const auth = new sst.aws.Auth("Auth", {
authorizer: {
vpc,
link: [
bus,
postgres,
secret.PolarSecret,
secret.GithubClientID,
secret.DiscordClientID,
secret.GithubClientSecret,
secret.DiscordClientSecret,
],
permissions: [
{
actions: ["ses:SendEmail"],
resources: ["*"],
},
],
handler: "packages/functions/src/auth/index.handler",
},
domain: {
name: "auth." + domain,
dns: sst.cloudflare.dns(),
},
forceUpgrade: "v2",
});

View File

@@ -1,20 +1,70 @@
import { email } from "./email";
import { allSecrets } from "./secret";
import { database } from "./database";
import { vpc } from "./vpc";
import { secret } from "./secret";
import { storage } from "./storage";
import { postgres } from "./postgres";
export const dlq = new sst.aws.Queue("Dlq");
export const retryQueue = new sst.aws.Queue("RetryQueue");
export const bus = new sst.aws.Bus("Bus");
bus.subscribe("Event", {
handler: "./packages/functions/src/event/event.handler",
export const eventSub = bus.subscribe("Event", {
vpc,
handler: "packages/functions/src/events/index.handler",
link: [
database,
email,
...allSecrets],
timeout: "5 minutes",
// email,
bus,
storage,
postgres,
retryQueue,
secret.PolarSecret,
secret.SteamApiKey
],
environment: {
RETRIES: "2",
},
memory: "3002 MB",// For faster processing of large(r) images
timeout: "10 minutes",
});
new aws.lambda.FunctionEventInvokeConfig("EventConfig", {
functionName: $resolve([eventSub.nodes.function.name]).apply(
([name]) => name,
),
maximumRetryAttempts: 1,
destinationConfig: {
onFailure: {
destination: retryQueue.arn,
},
},
});
retryQueue.subscribe({
vpc,
handler: "packages/functions/src/queues/retry.handler",
timeout: "30 seconds",
environment: {
RETRIER_QUEUE_URL: retryQueue.url,
},
link: [
dlq,
retryQueue,
eventSub.nodes.function,
],
permissions: [
{
actions: ["ses:SendEmail"],
resources: ["*"],
actions: ["lambda:GetFunction", "lambda:InvokeFunction"],
resources: [
$interpolate`arn:aws:lambda:${aws.getRegionOutput().name}:${aws.getCallerIdentityOutput().accountId}:function:*`,
],
},
],
transform: {
function: {
deadLetterConfig: {
targetArn: dlq.arn,
},
},
},
});

18
infra/cdn.ts Normal file
View File

@@ -0,0 +1,18 @@
import { domain } from "./dns";
import { storage } from "./storage";
export const cdn = new sst.aws.Router("CDNRouter", {
routes: {
"/*": {
bucket: storage,
rewrite: {
regex: "^/([a-zA-Z0-9_-]+)$",
to: "/public/$1"
},
},
},
domain: {
name: "cdn." + domain,
dns: sst.cloudflare.dns()
}
});

6
infra/cluster.ts Normal file
View File

@@ -0,0 +1,6 @@
import { vpc } from "./vpc";
export const cluster = new sst.aws.Cluster("Cluster", {
vpc,
forceUpgrade: "v2"
});

View File

@@ -1,40 +0,0 @@
//Created manually from the dashboard and shared with the whole team/org
const dbProject = neon.getProjectOutput({
id: "black-sky-26872933"
})
const dbBranchId = $app.stage !== "production" ?
new neon.Branch("NeonBranch", {
parentId: dbProject.defaultBranchId,
projectId: dbProject.id,
name: $app.stage,
}).id : dbProject.defaultBranchId
const dbEndpoint = new neon.Endpoint("NeonEndpoint", {
projectId: dbProject.id,
branchId: dbBranchId,
poolerEnabled: true,
type: "read_write",
})
const dbRole = new neon.Role("NeonRole", {
name: "admin",
branchId: dbBranchId,
projectId: dbProject.id,
})
const db = new neon.Database("NeonDatabase", {
branchId: dbBranchId,
projectId: dbProject.id,
ownerName: dbRole.name,
name: `nestri-${$app.stage}`,
})
export const database = new sst.Linkable("Database", {
properties: {
name: db.name,
user: dbRole.name,
host: dbEndpoint.host,
password: dbRole.password,
},
});

View File

@@ -1,6 +1,6 @@
import { domain } from "./dns";
export const email = new sst.aws.Email("Mail",{
export const email = new sst.aws.Email("Email",{
sender: domain,
dns: sst.cloudflare.dns(),
})

64
infra/postgres.ts Normal file
View File

@@ -0,0 +1,64 @@
import { vpc } from "./vpc";
export const postgres = new sst.aws.Aurora("Database", {
vpc,
engine: "postgres",
scaling: {
min: "0 ACU",
max: "1 ACU",
},
transform: {
clusterParameterGroup: {
parameters: [
{
name: "rds.logical_replication",
value: "1",
applyMethod: "pending-reboot",
},
{
name: "max_slot_wal_keep_size",
value: "10240",
applyMethod: "pending-reboot",
},
{
name: "rds.force_ssl",
value: "0",
applyMethod: "pending-reboot",
},
{
name: "max_connections",
value: "1000",
applyMethod: "pending-reboot",
},
],
},
},
});
new sst.x.DevCommand("Studio", {
link: [postgres],
dev: {
command: "bun db:dev studio",
directory: "packages/core",
autostart: true,
},
});
// const migrator = new sst.aws.Function("DatabaseMigrator", {
// handler: "packages/functions/src/migrator.handler",
// link: [postgres],
// copyFiles: [
// {
// from: "packages/core/migrations",
// to: "./migrations",
// },
// ],
// });
// if (!$dev) {
// new aws.lambda.Invocation("DatabaseMigratorInvocation", {
// input: Date.now().toString(),
// functionName: migrator.name,
// });
// }

9
infra/realtime.ts Normal file
View File

@@ -0,0 +1,9 @@
// import { auth } from "./auth";
import { postgres } from "./postgres";
export const device = new sst.aws.Realtime("Realtime", {
authorizer: {
link: [ postgres],
handler: "packages/functions/src/realtime/authorizer.handler"
}
})

View File

@@ -1,11 +1,18 @@
export const secret = {
// InstantAppId: new sst.Secret("InstantAppId"),
PolarSecret: new sst.Secret("PolarSecret", process.env.POLAR_API_KEY),
SteamApiKey: new sst.Secret("SteamApiKey"),
GithubClientID: new sst.Secret("GithubClientID"),
DiscordClientID: new sst.Secret("DiscordClientID"),
PolarWebhookSecret: new sst.Secret("PolarWebhookSecret"),
GithubClientSecret: new sst.Secret("GithubClientSecret"),
// InstantAdminToken: new sst.Secret("InstantAdminToken"),
DiscordClientSecret: new sst.Secret("DiscordClientSecret"),
// Pricing
NestriFreeMonthly: new sst.Secret("NestriFreeMonthly"),
NestriProMonthly: new sst.Secret("NestriProMonthly"),
NestriProYearly: new sst.Secret("NestriProYearly"),
NestriFamilyMonthly: new sst.Secret("NestriFamilyMonthly"),
NestriFamilyYearly: new sst.Secret("NestriFamilyYearly"),
};
export const allSecrets = Object.values(secret);

2
infra/stage.ts Normal file
View File

@@ -0,0 +1,2 @@
export const isPermanentStage =
$app.stage === "production" || $app.stage === "dev";

5
infra/storage.ts Normal file
View File

@@ -0,0 +1,5 @@
export const storage = new sst.aws.Bucket("Storage", {
access: "cloudfront"
});
export const zeroStorage = new sst.aws.Bucket("ZeroStorage");

11
infra/vpc.ts Normal file
View File

@@ -0,0 +1,11 @@
import { isPermanentStage } from "./stage";
export const vpc = isPermanentStage
? new sst.aws.Vpc("VPC", {
az: 2,
// For lambdas to work in this VPC
nat: "ec2",
// For SST tunnel to work
bastion: true,
})
: sst.aws.Vpc.get("VPC", "vpc-0beb1cdc21a725748");

View File

@@ -1,9 +1,12 @@
// This is the website part where people play and connect
import { api } from "./api";
import { cdn } from "./cdn";
import { auth } from "./auth";
import { zero } from "./zero";
import { domain } from "./dns";
import { auth, api } from "./api";
new sst.aws.StaticSite("Web", {
path: "./packages/www",
path: "packages/www",
build: {
output: "./dist",
command: "bun run build",
@@ -14,7 +17,9 @@ new sst.aws.StaticSite("Web", {
},
environment: {
VITE_API_URL: api.url,
VITE_AUTH_URL: auth.url,
VITE_CDN_URL: cdn.url,
VITE_STAGE: $app.stage,
VITE_AUTH_URL: auth.url,
VITE_ZERO_URL: zero.url,
},
})

186
infra/zero.ts Normal file
View File

@@ -0,0 +1,186 @@
import { auth } from "./auth";
import { domain } from "./dns";
import { readFileSync } from "fs";
import { cluster } from "./cluster";
import { postgres } from "./postgres";
import { zeroStorage } from "./storage";
const connectionString = $interpolate`postgresql://${postgres.username}:${postgres.password}@${postgres.host}:${postgres.port}/${postgres.database}`;
const tag = $dev
? `latest`
: JSON.parse(
readFileSync("./node_modules/@rocicorp/zero/package.json").toString(),
).version.replace("+", "-");
const zeroEnv = {
FORCE: "1",
NO_COLOR: "1",
ZERO_LOG_LEVEL: "info",
ZERO_LITESTREAM_LOG_LEVEL: "info",
ZERO_UPSTREAM_DB: connectionString,
ZERO_IMAGE_URL: `rocicorp/zero:${tag}`,
ZERO_CVR_DB: connectionString,
ZERO_CHANGE_DB: connectionString,
ZERO_REPLICA_FILE: "/tmp/nestri.db",
ZERO_LITESTREAM_RESTORE_PARALLELISM: "64",
ZERO_APP_ID: $app.stage,
ZERO_AUTH_JWKS_URL: $interpolate`${auth.url}/.well-known/jwks.json`,
ZERO_INITIAL_SYNC_ROW_BATCH_SIZE: "30000",
NODE_OPTIONS: "--max-old-space-size=8192",
...($dev
? {
}
: {
ZERO_LITESTREAM_BACKUP_URL: $interpolate`s3://${zeroStorage.name}/zero/0`,
}),
};
// Replication Manager Service
const replicationManager = !$dev
? new sst.aws.Service(`ZeroReplication`, {
cluster,
wait: true,
cpu: "0.5 vCPU",
memory: "1 GB",
capacity: "spot",
architecture: "arm64",
image: zeroEnv.ZERO_IMAGE_URL,
link: [zeroStorage, postgres],
health: {
command: ["CMD-SHELL", "curl -f http://localhost:4849/ || exit 1"],
interval: "5 seconds",
retries: 3,
startPeriod: "300 seconds",
},
environment: {
...zeroEnv,
ZERO_CHANGE_MAX_CONNS: "3",
ZERO_NUM_SYNC_WORKERS: "0",
},
logging: {
retention: "1 month",
},
loadBalancer: {
public: false,
ports: [
{
listen: "80/http",
forward: "4849/http",
},
],
},
transform: {
loadBalancer: {
idleTimeout: 3600,
},
service: {
healthCheckGracePeriodSeconds: 900,
},
},
}) : undefined;
// Permissions deployment
// const permissions = new sst.aws.Function(
// "ZeroPermissions",
// {
// vpc,
// link: [postgres],
// handler: "packages/functions/src/zero.handler",
// // environment: { ["ZERO_UPSTREAM_DB"]: connectionString },
// copyFiles: [{
// from: "packages/zero/permissions.sql",
// to: "./.permissions.sql"
// }],
// }
// );
// if (replicationManager) {
// new aws.lambda.Invocation(
// "ZeroPermissionsInvocation",
// {
// input: Date.now().toString(),
// functionName: permissions.name,
// },
// { dependsOn: replicationManager }
// );
// // new command.local.Command(
// // "ZeroPermission",
// // {
// // dir: process.cwd() + "/packages/zero",
// // environment: {
// // ZERO_UPSTREAM_DB: connectionString,
// // },
// // create: "bun run zero-deploy-permissions",
// // triggers: [Date.now()],
// // },
// // {
// // dependsOn: [replicationManager],
// // },
// // );
// }
export const zero = new sst.aws.Service("Zero", {
cluster,
image: zeroEnv.ZERO_IMAGE_URL,
link: [zeroStorage, postgres],
architecture: "arm64",
cpu: "0.5 vCPU",
memory: "1 GB",
capacity: "spot",
environment: {
...zeroEnv,
...($dev
? {
ZERO_NUM_SYNC_WORKERS: "1",
}
: {
ZERO_CHANGE_STREAMER_URI: replicationManager.url.apply((val) =>
val.replace("http://", "ws://"),
),
ZERO_UPSTREAM_MAX_CONNS: "15",
ZERO_CVR_MAX_CONNS: "160",
}),
},
health: {
retries: 3,
command: ["CMD-SHELL", "curl -f http://localhost:4848/ || exit 1"],
interval: "5 seconds",
startPeriod: "300 seconds",
},
loadBalancer: {
domain: {
name: "zero." + domain,
dns: sst.cloudflare.dns()
},
rules: [
{ listen: "443/https", forward: "4848/http" },
{ listen: "80/http", forward: "4848/http" },
],
},
scaling: {
min: 1,
max: 4,
},
logging: {
retention: "1 month",
},
transform: {
service: {
healthCheckGracePeriodSeconds: 900,
},
// taskDefinition: {
// ephemeralStorage: {
// sizeInGib: 200,
// },
// },
loadBalancer: {
idleTimeout: 3600,
},
},
dev: {
command: "bun dev",
directory: "packages/zero",
url: "http://localhost:4848",
},
});

View File

@@ -3,6 +3,7 @@
"devDependencies": {
"@cloudflare/workers-types": "4.20240821.1",
"@pulumi/pulumi": "^3.134.0",
"@tsconfig/node22": "^22.0.1",
"@types/aws-lambda": "8.10.147",
"prettier": "^3.2.5",
"typescript": "^5.4.5"
@@ -16,9 +17,19 @@
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
"sso": "aws sso login --sso-session=nestri --no-browser --use-device-code"
},
"overrides": {
"@openauthjs/openauth": "0.4.3",
"steam-session": "1.9.3"
},
"patchedDependencies": {
"@macaron-css/solid@1.5.3": "patches/@macaron-css%2Fsolid@1.5.3.patch",
"drizzle-orm@0.36.1": "patches/drizzle-orm@0.36.1.patch",
"steam-session@1.9.3": "patches/steam-session@1.9.3.patch"
},
"trustedDependencies": [
"core-js-pure",
"esbuild",
"protobufjs",
"workerd"
],
"workspaces": [
@@ -26,6 +37,6 @@
"packages/*"
],
"dependencies": {
"sst": "3.9.1"
"sst": "^3.11.21"
}
}
}

View File

@@ -1,20 +1,19 @@
import { Resource } from "sst";
import { defineConfig } from "drizzle-kit";
function addPoolerSuffix(original: string): string {
const firstDotIndex = original.indexOf('.');
if (firstDotIndex === -1) return original + '-pooler';
return original.slice(0, firstDotIndex) + '-pooler' + original.slice(firstDotIndex);
}
const dbHost = addPoolerSuffix(Resource.Database.host)
const connection = {
user: Resource.Database.username,
password: Resource.Database.password,
host: Resource.Database.host,
};
export default defineConfig({
schema: "./src/**/*.sql.ts",
verbose: true,
strict: true,
out: "./migrations",
dialect: "postgresql",
verbose: true,
dbCredentials: {
url: `postgresql://${Resource.Database.user}:${Resource.Database.password}@${dbHost}/${Resource.Database.name}?sslmode=require`,
url: `postgres://${connection.user}:${connection.password}@${connection.host}/nestri`,
},
schema: "./src/**/*.sql.ts",
});

View File

@@ -14,8 +14,9 @@ CREATE TABLE "team" (
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
"time_deleted" timestamp with time zone,
"name" varchar(255) NOT NULL,
"slug" varchar(255) NOT NULL,
"name" varchar(255) NOT NULL
"plan_type" text NOT NULL
);
--> statement-breakpoint
CREATE TABLE "user" (
@@ -24,14 +25,15 @@ CREATE TABLE "user" (
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
"time_deleted" timestamp with time zone,
"avatar_url" text,
"email" varchar(255) NOT NULL,
"name" varchar(255) NOT NULL,
"discriminator" integer NOT NULL,
"polar_customer_id" varchar(255) NOT NULL,
"email" varchar(255) NOT NULL,
"polar_customer_id" varchar(255),
"flags" json DEFAULT '{}'::json,
CONSTRAINT "user_polar_customer_id_unique" UNIQUE("polar_customer_id")
);
--> statement-breakpoint
CREATE UNIQUE INDEX "member_email" ON "member" USING btree ("team_id","email");--> statement-breakpoint
CREATE INDEX "email_global" ON "member" USING btree ("email");--> statement-breakpoint
CREATE UNIQUE INDEX "slug" ON "team" USING btree ("slug");--> statement-breakpoint
CREATE UNIQUE INDEX "member_email" ON "member" USING btree ("team_id","email");--> statement-breakpoint
CREATE UNIQUE INDEX "team_slug" ON "team" USING btree ("slug");--> statement-breakpoint
CREATE UNIQUE INDEX "user_email" ON "user" USING btree ("email");

View File

@@ -1 +0,0 @@
ALTER TABLE "user" ALTER COLUMN "polar_customer_id" DROP NOT NULL;

View File

@@ -0,0 +1,2 @@
DROP INDEX "team_slug";--> statement-breakpoint
CREATE UNIQUE INDEX "slug" ON "team" USING btree ("slug");

View File

@@ -0,0 +1,17 @@
CREATE TABLE "steam" (
"id" char(30) NOT NULL,
"user_id" char(30) NOT NULL,
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
"time_deleted" timestamp with time zone,
"avatar_url" text NOT NULL,
"access_token" text NOT NULL,
"email" varchar(255) NOT NULL,
"country" varchar(255) NOT NULL,
"username" varchar(255) NOT NULL,
"persona_name" varchar(255) NOT NULL,
CONSTRAINT "steam_user_id_id_pk" PRIMARY KEY("user_id","id")
);
--> statement-breakpoint
CREATE INDEX "global_steam_email" ON "steam" USING btree ("email");--> statement-breakpoint
CREATE UNIQUE INDEX "steam_email" ON "steam" USING btree ("user_id","email");

View File

@@ -0,0 +1,13 @@
CREATE TABLE "machine" (
"id" char(30) PRIMARY KEY NOT NULL,
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
"time_deleted" timestamp with time zone,
"country" text NOT NULL,
"timezone" text NOT NULL,
"location" "point" NOT NULL,
"fingerprint" varchar(32) NOT NULL,
"country_code" varchar(2) NOT NULL
);
--> statement-breakpoint
CREATE UNIQUE INDEX "machine_fingerprint" ON "machine" USING btree ("fingerprint");

View File

@@ -0,0 +1,22 @@
CREATE TABLE "machine" (
"id" char(30) PRIMARY KEY NOT NULL,
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
"time_deleted" timestamp with time zone,
"country" text NOT NULL,
"timezone" text NOT NULL,
"location" "point" NOT NULL,
"fingerprint" varchar(32) NOT NULL,
"country_code" varchar(2) NOT NULL
);
--> statement-breakpoint
ALTER TABLE "steam" RENAME COLUMN "country" TO "country_code";--> statement-breakpoint
DROP INDEX "global_steam_email";--> statement-breakpoint
ALTER TABLE "steam" ADD COLUMN "time_seen" timestamp with time zone;--> statement-breakpoint
ALTER TABLE "steam" ADD COLUMN "steam_id" integer NOT NULL;--> statement-breakpoint
ALTER TABLE "steam" ADD COLUMN "last_game" json NOT NULL;--> statement-breakpoint
ALTER TABLE "steam" ADD COLUMN "steam_email" varchar(255) NOT NULL;--> statement-breakpoint
ALTER TABLE "steam" ADD COLUMN "limitation" json NOT NULL;--> statement-breakpoint
CREATE UNIQUE INDEX "machine_fingerprint" ON "machine" USING btree ("fingerprint");--> statement-breakpoint
ALTER TABLE "steam" DROP COLUMN "access_token";--> statement-breakpoint
ALTER TABLE "user" DROP COLUMN "flags";

View File

@@ -0,0 +1,8 @@
ALTER TABLE "steam" RENAME COLUMN "time_seen" TO "last_seen";--> statement-breakpoint
DROP INDEX "steam_email";--> statement-breakpoint
ALTER TABLE "steam" DROP CONSTRAINT "steam_user_id_id_pk";--> statement-breakpoint
ALTER TABLE "steam" ADD PRIMARY KEY ("id");--> statement-breakpoint
ALTER TABLE "machine" ADD CONSTRAINT "machine_user_id_id_pk" PRIMARY KEY("user_id","id");--> statement-breakpoint
ALTER TABLE "machine" ADD COLUMN "user_id" char(30);--> statement-breakpoint
ALTER TABLE "steam" ADD CONSTRAINT "steam_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "steam" DROP COLUMN "email";

View File

@@ -0,0 +1,2 @@
ALTER TABLE "machine" DROP CONSTRAINT "machine_user_id_id_pk";--> statement-breakpoint
ALTER TABLE "machine" DROP COLUMN "user_id";

View File

@@ -0,0 +1,2 @@
ALTER TABLE "member" ADD COLUMN "role" text NOT NULL;--> statement-breakpoint
ALTER TABLE "team" DROP COLUMN "plan_type";

View File

@@ -0,0 +1,15 @@
CREATE TABLE "subscription" (
"id" char(30) NOT NULL,
"user_id" char(30) NOT NULL,
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
"time_deleted" timestamp with time zone,
"team_id" char(30) NOT NULL,
"standing" text NOT NULL,
"plan_type" text NOT NULL,
"tokens" integer NOT NULL,
"product_id" varchar(255),
"subscription_id" varchar(255)
);
--> statement-breakpoint
ALTER TABLE "subscription" ADD CONSTRAINT "subscription_team_id_team_id_fk" FOREIGN KEY ("team_id") REFERENCES "public"."team"("id") ON DELETE cascade ON UPDATE no action;

View File

@@ -0,0 +1,3 @@
ALTER TABLE "subscription" ADD CONSTRAINT "subscription_id_team_id_pk" PRIMARY KEY("id","team_id");--> statement-breakpoint
CREATE UNIQUE INDEX "subscription_id" ON "subscription" USING btree ("id");--> statement-breakpoint
CREATE INDEX "subscription_user_id" ON "subscription" USING btree ("user_id");

View File

@@ -0,0 +1,2 @@
CREATE UNIQUE INDEX "steam_id" ON "steam" USING btree ("steam_id");--> statement-breakpoint
CREATE INDEX "steam_user_id" ON "steam" USING btree ("user_id");

View File

@@ -0,0 +1,94 @@
CREATE TYPE "public"."member_role" AS ENUM('child', 'adult');--> statement-breakpoint
CREATE TYPE "public"."steam_status" AS ENUM('online', 'offline', 'dnd', 'playing');--> statement-breakpoint
CREATE TABLE "steam_account_credentials" (
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
"time_deleted" timestamp with time zone,
"steam_id" varchar(255) PRIMARY KEY NOT NULL,
"refresh_token" text NOT NULL,
"expiry" timestamp with time zone NOT NULL,
"username" varchar(255) NOT NULL
);
--> statement-breakpoint
CREATE TABLE "friends_list" (
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
"time_deleted" timestamp with time zone,
"steam_id" varchar(255) NOT NULL,
"friend_steam_id" varchar(255) NOT NULL,
CONSTRAINT "friends_list_steam_id_friend_steam_id_pk" PRIMARY KEY("steam_id","friend_steam_id")
);
--> statement-breakpoint
CREATE TABLE "members" (
"id" char(30) NOT NULL,
"team_id" char(30) NOT NULL,
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
"time_deleted" timestamp with time zone,
"user_id" char(30),
"steam_id" varchar(255) NOT NULL,
"role" "member_role" NOT NULL,
CONSTRAINT "members_id_team_id_pk" PRIMARY KEY("id","team_id")
);
--> statement-breakpoint
CREATE TABLE "steam_accounts" (
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
"time_deleted" timestamp with time zone,
"steam_id" varchar(255) PRIMARY KEY NOT NULL,
"user_id" char(30),
"status" "steam_status" NOT NULL,
"last_synced_at" timestamp with time zone NOT NULL,
"real_name" varchar(255),
"member_since" timestamp with time zone NOT NULL,
"name" varchar(255) NOT NULL,
"profile_url" varchar(255),
"username" varchar(255) NOT NULL,
"avatar_hash" varchar(255) NOT NULL,
"limitations" json NOT NULL,
CONSTRAINT "idx_steam_username" UNIQUE("username")
);
--> statement-breakpoint
CREATE TABLE "teams" (
"id" char(30) PRIMARY KEY NOT NULL,
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
"time_deleted" timestamp with time zone,
"name" varchar(255) NOT NULL,
"owner_id" char(30) NOT NULL,
"invite_code" varchar(10) NOT NULL,
"slug" varchar(255) NOT NULL,
"max_members" bigint NOT NULL,
CONSTRAINT "idx_team_invite_code" UNIQUE("invite_code")
);
--> statement-breakpoint
CREATE TABLE "users" (
"id" char(30) PRIMARY KEY NOT NULL,
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
"time_deleted" timestamp with time zone,
"email" varchar(255) NOT NULL,
"avatar_url" text,
"last_login" timestamp with time zone NOT NULL,
"name" varchar(255) NOT NULL,
"polar_customer_id" varchar(255),
CONSTRAINT "idx_user_email" UNIQUE("email")
);
--> statement-breakpoint
DROP TABLE "machine" CASCADE;--> statement-breakpoint
DROP TABLE "member" CASCADE;--> statement-breakpoint
DROP TABLE "steam" CASCADE;--> statement-breakpoint
DROP TABLE "subscription" CASCADE;--> statement-breakpoint
DROP TABLE "team" CASCADE;--> statement-breakpoint
DROP TABLE "user" CASCADE;--> statement-breakpoint
ALTER TABLE "steam_account_credentials" ADD CONSTRAINT "steam_account_credentials_steam_id_steam_accounts_steam_id_fk" FOREIGN KEY ("steam_id") REFERENCES "public"."steam_accounts"("steam_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "friends_list" ADD CONSTRAINT "friends_list_steam_id_steam_accounts_steam_id_fk" FOREIGN KEY ("steam_id") REFERENCES "public"."steam_accounts"("steam_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "friends_list" ADD CONSTRAINT "friends_list_friend_steam_id_steam_accounts_steam_id_fk" FOREIGN KEY ("friend_steam_id") REFERENCES "public"."steam_accounts"("steam_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "members" ADD CONSTRAINT "members_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "members" ADD CONSTRAINT "members_steam_id_steam_accounts_steam_id_fk" FOREIGN KEY ("steam_id") REFERENCES "public"."steam_accounts"("steam_id") ON DELETE cascade ON UPDATE restrict;--> statement-breakpoint
ALTER TABLE "steam_accounts" ADD CONSTRAINT "steam_accounts_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "teams" ADD CONSTRAINT "teams_owner_id_users_id_fk" FOREIGN KEY ("owner_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "teams" ADD CONSTRAINT "teams_slug_steam_accounts_username_fk" FOREIGN KEY ("slug") REFERENCES "public"."steam_accounts"("username") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE UNIQUE INDEX "idx_member_steam_id" ON "members" USING btree ("team_id","steam_id");--> statement-breakpoint
CREATE UNIQUE INDEX "idx_member_user_id" ON "members" USING btree ("team_id","user_id") WHERE "members"."user_id" is not null;--> statement-breakpoint
CREATE UNIQUE INDEX "idx_team_slug" ON "teams" USING btree ("slug");

View File

@@ -0,0 +1,89 @@
CREATE TYPE "public"."compatibility" AS ENUM('high', 'mid', 'low', 'unknown');--> statement-breakpoint
CREATE TYPE "public"."category_type" AS ENUM('tag', 'genre', 'publisher', 'developer');--> statement-breakpoint
CREATE TYPE "public"."image_type" AS ENUM('heroArt', 'icon', 'logo', 'superHeroArt', 'poster', 'boxArt', 'screenshot', 'background');--> statement-breakpoint
CREATE TABLE "base_games" (
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
"time_deleted" timestamp with time zone,
"id" varchar(255) PRIMARY KEY NOT NULL,
"slug" varchar(255) NOT NULL,
"name" text NOT NULL,
"release_date" timestamp with time zone NOT NULL,
"size" json NOT NULL,
"description" text NOT NULL,
"primary_genre" text NOT NULL,
"controller_support" text,
"compatibility" "compatibility" DEFAULT 'unknown' NOT NULL,
"score" numeric(2, 1) NOT NULL,
CONSTRAINT "idx_base_games_slug" UNIQUE("slug")
);
--> statement-breakpoint
CREATE TABLE "categories" (
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
"time_deleted" timestamp with time zone,
"slug" varchar(255) NOT NULL,
"type" "category_type" NOT NULL,
"name" text NOT NULL,
CONSTRAINT "categories_slug_type_pk" PRIMARY KEY("slug","type")
);
--> statement-breakpoint
CREATE TABLE "games" (
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
"time_deleted" timestamp with time zone,
"base_game_id" varchar(255) NOT NULL,
"category_slug" varchar(255) NOT NULL,
"type" "category_type" NOT NULL,
CONSTRAINT "games_base_game_id_category_slug_type_pk" PRIMARY KEY("base_game_id","category_slug","type")
);
--> statement-breakpoint
CREATE TABLE "images" (
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
"time_deleted" timestamp with time zone,
"type" "image_type" NOT NULL,
"image_hash" varchar(255) NOT NULL,
"base_game_id" varchar(255) NOT NULL,
"source_url" text NOT NULL,
"position" integer DEFAULT 0 NOT NULL,
"file_size" integer NOT NULL,
"dimensions" json NOT NULL,
"extracted_color" json NOT NULL,
CONSTRAINT "images_image_hash_type_base_game_id_position_pk" PRIMARY KEY("image_hash","type","base_game_id","position")
);
--> statement-breakpoint
CREATE TABLE "game_libraries" (
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
"time_deleted" timestamp with time zone,
"base_game_id" varchar(255) NOT NULL,
"owner_id" varchar(255) NOT NULL,
CONSTRAINT "game_libraries_base_game_id_owner_id_pk" PRIMARY KEY("base_game_id","owner_id")
);
--> statement-breakpoint
ALTER TABLE "steam_accounts" RENAME COLUMN "steam_id" TO "id";--> statement-breakpoint
ALTER TABLE "steam_account_credentials" DROP CONSTRAINT "steam_account_credentials_steam_id_steam_accounts_steam_id_fk";
--> statement-breakpoint
ALTER TABLE "friends_list" DROP CONSTRAINT "friends_list_steam_id_steam_accounts_steam_id_fk";
--> statement-breakpoint
ALTER TABLE "friends_list" DROP CONSTRAINT "friends_list_friend_steam_id_steam_accounts_steam_id_fk";
--> statement-breakpoint
ALTER TABLE "members" DROP CONSTRAINT "members_steam_id_steam_accounts_steam_id_fk";
--> statement-breakpoint
ALTER TABLE "games" ADD CONSTRAINT "games_base_game_id_base_games_id_fk" FOREIGN KEY ("base_game_id") REFERENCES "public"."base_games"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "games" ADD CONSTRAINT "games_categories_fkey" FOREIGN KEY ("category_slug","type") REFERENCES "public"."categories"("slug","type") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "images" ADD CONSTRAINT "images_base_game_id_base_games_id_fk" FOREIGN KEY ("base_game_id") REFERENCES "public"."base_games"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "game_libraries" ADD CONSTRAINT "game_libraries_base_game_id_base_games_id_fk" FOREIGN KEY ("base_game_id") REFERENCES "public"."base_games"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "game_libraries" ADD CONSTRAINT "game_libraries_owner_id_steam_accounts_id_fk" FOREIGN KEY ("owner_id") REFERENCES "public"."steam_accounts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "idx_categories_type" ON "categories" USING btree ("type");--> statement-breakpoint
CREATE INDEX "idx_games_category_slug" ON "games" USING btree ("category_slug");--> statement-breakpoint
CREATE INDEX "idx_games_category_type" ON "games" USING btree ("type");--> statement-breakpoint
CREATE INDEX "idx_images_type" ON "images" USING btree ("type");--> statement-breakpoint
CREATE INDEX "idx_images_game_id" ON "images" USING btree ("base_game_id");--> statement-breakpoint
CREATE INDEX "idx_game_libraries_owner_id" ON "game_libraries" USING btree ("owner_id");--> statement-breakpoint
ALTER TABLE "steam_account_credentials" ADD CONSTRAINT "steam_account_credentials_steam_id_steam_accounts_id_fk" FOREIGN KEY ("steam_id") REFERENCES "public"."steam_accounts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "friends_list" ADD CONSTRAINT "friends_list_steam_id_steam_accounts_id_fk" FOREIGN KEY ("steam_id") REFERENCES "public"."steam_accounts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "friends_list" ADD CONSTRAINT "friends_list_friend_steam_id_steam_accounts_id_fk" FOREIGN KEY ("friend_steam_id") REFERENCES "public"."steam_accounts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "members" ADD CONSTRAINT "members_steam_id_steam_accounts_id_fk" FOREIGN KEY ("steam_id") REFERENCES "public"."steam_accounts"("id") ON DELETE cascade ON UPDATE restrict;--> statement-breakpoint
CREATE INDEX "idx_friends_list_friend_steam_id" ON "friends_list" USING btree ("friend_steam_id");

View File

@@ -0,0 +1,4 @@
ALTER TABLE "games" DROP CONSTRAINT "games_categories_fkey";
--> statement-breakpoint
ALTER TABLE "games" ADD CONSTRAINT "games_categories_fkey" FOREIGN KEY ("category_slug","type") REFERENCES "public"."categories"("slug","type") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "idx_games_category_slug_type" ON "games" USING btree ("category_slug","type");

View File

@@ -0,0 +1,3 @@
CREATE TYPE "public"."controller_support" AS ENUM('full', 'unknown');--> statement-breakpoint
ALTER TABLE "base_games" ALTER COLUMN "controller_support" SET DATA TYPE controller_support;--> statement-breakpoint
ALTER TABLE "base_games" ALTER COLUMN "controller_support" SET NOT NULL;

View File

@@ -0,0 +1 @@
ALTER TABLE "base_games" ALTER COLUMN "primary_genre" DROP NOT NULL;

View File

@@ -0,0 +1 @@
ALTER TYPE "public"."controller_support" ADD VALUE 'partial' BEFORE 'unknown';

View File

@@ -0,0 +1,4 @@
ALTER TABLE "game_libraries" ADD COLUMN "time_acquired" timestamp with time zone NOT NULL;--> statement-breakpoint
ALTER TABLE "game_libraries" ADD COLUMN "last_played" timestamp with time zone NOT NULL;--> statement-breakpoint
ALTER TABLE "game_libraries" ADD COLUMN "total_playtime" integer NOT NULL;--> statement-breakpoint
ALTER TABLE "game_libraries" ADD COLUMN "is_family_shared" boolean NOT NULL;

View File

@@ -0,0 +1,4 @@
ALTER TABLE "public"."images" ALTER COLUMN "type" SET DATA TYPE text;--> statement-breakpoint
DROP TYPE "public"."image_type";--> statement-breakpoint
CREATE TYPE "public"."image_type" AS ENUM('heroArt', 'icon', 'logo', 'superHeroArt', 'poster', 'boxArt', 'screenshot', 'backdrop');--> statement-breakpoint
ALTER TABLE "public"."images" ALTER COLUMN "type" SET DATA TYPE "public"."image_type" USING "type"::"public"."image_type";

View File

@@ -0,0 +1,4 @@
ALTER TABLE "public"."images" ALTER COLUMN "type" SET DATA TYPE text;--> statement-breakpoint
DROP TYPE "public"."image_type";--> statement-breakpoint
CREATE TYPE "public"."image_type" AS ENUM('heroArt', 'icon', 'logo', 'banner', 'poster', 'boxArt', 'screenshot', 'backdrop');--> statement-breakpoint
ALTER TABLE "public"."images" ALTER COLUMN "type" SET DATA TYPE "public"."image_type" USING "type"::"public"."image_type";

View File

@@ -0,0 +1,19 @@
/*
Unfortunately in current drizzle-kit version we can't automatically get name for primary key.
We are working on making it available!
Meanwhile you can:
1. Check pk name in your database, by running
SELECT constraint_name FROM information_schema.table_constraints
WHERE table_schema = 'public'
AND table_name = 'steam_account_credentials'
AND constraint_type = 'PRIMARY KEY';
2. Uncomment code below and paste pk name manually
Hope to release this update as soon as possible
*/
-- ALTER TABLE "steam_account_credentials" DROP CONSTRAINT "<constraint_name>";--> statement-breakpoint
ALTER TABLE "images" ALTER COLUMN "source_url" DROP NOT NULL;--> statement-breakpoint
ALTER TABLE "steam_account_credentials" ADD CONSTRAINT "steam_account_credentials_steam_id_id_pk" PRIMARY KEY("steam_id","id");--> statement-breakpoint
ALTER TABLE "steam_account_credentials" ADD COLUMN "id" char(30) NOT NULL;

View File

@@ -0,0 +1,23 @@
ALTER TABLE "steam_account_credentials" DISABLE ROW LEVEL SECURITY;--> statement-breakpoint
DROP TABLE "steam_account_credentials" CASCADE;--> statement-breakpoint
ALTER TABLE "game_libraries" RENAME COLUMN "owner_id" TO "owner_steam_id";--> statement-breakpoint
ALTER TABLE "teams" RENAME COLUMN "owner_id" TO "owner_steam_id";--> statement-breakpoint
ALTER TABLE "steam_accounts" DROP CONSTRAINT "idx_steam_username";--> statement-breakpoint
ALTER TABLE "game_libraries" DROP CONSTRAINT "game_libraries_owner_id_steam_accounts_id_fk";
--> statement-breakpoint
ALTER TABLE "teams" DROP CONSTRAINT "teams_owner_id_users_id_fk";
--> statement-breakpoint
ALTER TABLE "teams" DROP CONSTRAINT "teams_slug_steam_accounts_username_fk";
--> statement-breakpoint
DROP INDEX "idx_team_slug";--> statement-breakpoint
DROP INDEX "idx_game_libraries_owner_id";--> statement-breakpoint
ALTER TABLE "game_libraries" DROP CONSTRAINT "game_libraries_base_game_id_owner_id_pk";--> statement-breakpoint
ALTER TABLE "game_libraries" ALTER COLUMN "last_played" DROP NOT NULL;--> statement-breakpoint
ALTER TABLE "game_libraries" ADD CONSTRAINT "game_libraries_base_game_id_owner_steam_id_pk" PRIMARY KEY("base_game_id","owner_steam_id");--> statement-breakpoint
ALTER TABLE "game_libraries" ADD CONSTRAINT "game_libraries_owner_steam_id_steam_accounts_id_fk" FOREIGN KEY ("owner_steam_id") REFERENCES "public"."steam_accounts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "teams" ADD CONSTRAINT "teams_owner_steam_id_steam_accounts_id_fk" FOREIGN KEY ("owner_steam_id") REFERENCES "public"."steam_accounts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "idx_game_libraries_owner_id" ON "game_libraries" USING btree ("owner_steam_id");--> statement-breakpoint
ALTER TABLE "game_libraries" DROP COLUMN "time_acquired";--> statement-breakpoint
ALTER TABLE "game_libraries" DROP COLUMN "is_family_shared";--> statement-breakpoint
ALTER TABLE "steam_accounts" DROP COLUMN "username";--> statement-breakpoint
ALTER TABLE "teams" DROP COLUMN "slug";

View File

@@ -0,0 +1,2 @@
ALTER TYPE "public"."category_type" ADD VALUE 'category';--> statement-breakpoint
ALTER TYPE "public"."category_type" ADD VALUE 'franchise';

View File

@@ -0,0 +1,6 @@
ALTER TABLE "public"."categories" ALTER COLUMN "type" SET DATA TYPE text;--> statement-breakpoint
ALTER TABLE "public"."games" ALTER COLUMN "type" SET DATA TYPE text;--> statement-breakpoint
DROP TYPE "public"."category_type";--> statement-breakpoint
CREATE TYPE "public"."category_type" AS ENUM('tag', 'genre', 'publisher', 'developer', 'categorie', 'franchise');--> statement-breakpoint
ALTER TABLE "public"."categories" ALTER COLUMN "type" SET DATA TYPE "public"."category_type" USING "type"::"public"."category_type";--> statement-breakpoint
ALTER TABLE "public"."games" ALTER COLUMN "type" SET DATA TYPE "public"."category_type" USING "type"::"public"."category_type";

View File

@@ -0,0 +1,2 @@
ALTER TABLE "base_games" ALTER COLUMN "description" DROP NOT NULL;--> statement-breakpoint
ALTER TABLE "base_games" ADD COLUMN "links" text[];

View File

@@ -0,0 +1 @@
ALTER TABLE "base_games" ALTER COLUMN "links" SET DATA TYPE json;

View File

@@ -0,0 +1,3 @@
DROP TABLE "members" CASCADE;--> statement-breakpoint
DROP TABLE "teams" CASCADE;--> statement-breakpoint
DROP TYPE "public"."member_role";

View File

@@ -1,5 +1,5 @@
{
"id": "08ba0262-ce0a-4d87-b4e2-0d17dc0ee28c",
"id": "f09034df-208a-42b3-b61f-f842921c6e24",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
@@ -54,6 +54,21 @@
}
},
"indexes": {
"email_global": {
"name": "email_global",
"columns": [
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"member_email": {
"name": "member_email",
"columns": [
@@ -74,21 +89,6 @@
"concurrently": false,
"method": "btree",
"with": {}
},
"email_global": {
"name": "email_global",
"columns": [
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
@@ -136,22 +136,28 @@
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"slug": {
"name": "slug",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(255)",
"plan_type": {
"name": "plan_type",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"slug": {
"name": "slug",
"team_slug": {
"name": "team_slug",
"columns": [
{
"expression": "slug",
@@ -209,12 +215,6 @@
"primaryKey": false,
"notNull": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(255)",
@@ -227,11 +227,24 @@
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"polar_customer_id": {
"name": "polar_customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
"notNull": false
},
"flags": {
"name": "flags",
"type": "json",
"primaryKey": false,
"notNull": false,
"default": "'{}'::json"
}
},
"indexes": {

View File

@@ -1,6 +1,6 @@
{
"id": "c09359df-19fe-4246-9a41-43b3a429c12f",
"prevId": "08ba0262-ce0a-4d87-b4e2-0d17dc0ee28c",
"id": "6f428226-b5d8-4182-a676-d04f842f9ded",
"prevId": "f09034df-208a-42b3-b61f-f842921c6e24",
"version": "7",
"dialect": "postgresql",
"tables": {
@@ -54,6 +54,21 @@
}
},
"indexes": {
"email_global": {
"name": "email_global",
"columns": [
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"member_email": {
"name": "member_email",
"columns": [
@@ -74,21 +89,6 @@
"concurrently": false,
"method": "btree",
"with": {}
},
"email_global": {
"name": "email_global",
"columns": [
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
@@ -136,15 +136,21 @@
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"slug": {
"name": "slug",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(255)",
"plan_type": {
"name": "plan_type",
"type": "text",
"primaryKey": false,
"notNull": true
}
@@ -209,12 +215,6 @@
"primaryKey": false,
"notNull": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(255)",
@@ -227,11 +227,24 @@
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"polar_customer_id": {
"name": "polar_customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"flags": {
"name": "flags",
"type": "json",
"primaryKey": false,
"notNull": false,
"default": "'{}'::json"
}
},
"indexes": {

View File

@@ -0,0 +1,420 @@
{
"id": "227c54d2-b643-48d5-964b-af6fe004369a",
"prevId": "6f428226-b5d8-4182-a676-d04f842f9ded",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.member": {
"name": "member",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"team_id": {
"name": "team_id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"time_seen": {
"name": "time_seen",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"email_global": {
"name": "email_global",
"columns": [
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"member_email": {
"name": "member_email",
"columns": [
{
"expression": "team_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"member_team_id_id_pk": {
"name": "member_team_id_id_pk",
"columns": [
"team_id",
"id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.steam": {
"name": "steam",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"avatar_url": {
"name": "avatar_url",
"type": "text",
"primaryKey": false,
"notNull": true
},
"access_token": {
"name": "access_token",
"type": "text",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"country": {
"name": "country",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"username": {
"name": "username",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"persona_name": {
"name": "persona_name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"global_steam_email": {
"name": "global_steam_email",
"columns": [
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"steam_email": {
"name": "steam_email",
"columns": [
{
"expression": "user_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"steam_user_id_id_pk": {
"name": "steam_user_id_id_pk",
"columns": [
"user_id",
"id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.team": {
"name": "team",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"slug": {
"name": "slug",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"plan_type": {
"name": "plan_type",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"slug": {
"name": "slug",
"columns": [
{
"expression": "slug",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.user": {
"name": "user",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"avatar_url": {
"name": "avatar_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"discriminator": {
"name": "discriminator",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"polar_customer_id": {
"name": "polar_customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"flags": {
"name": "flags",
"type": "json",
"primaryKey": false,
"notNull": false,
"default": "'{}'::json"
}
},
"indexes": {
"user_email": {
"name": "user_email",
"columns": [
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"user_polar_customer_id_unique": {
"name": "user_polar_customer_id_unique",
"nullsNotDistinct": false,
"columns": [
"polar_customer_id"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -0,0 +1,507 @@
{
"id": "eb5d41aa-5f85-4b2d-8633-fc021b211241",
"prevId": "227c54d2-b643-48d5-964b-af6fe004369a",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.machine": {
"name": "machine",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"country": {
"name": "country",
"type": "text",
"primaryKey": false,
"notNull": true
},
"timezone": {
"name": "timezone",
"type": "text",
"primaryKey": false,
"notNull": true
},
"location": {
"name": "location",
"type": "point",
"primaryKey": false,
"notNull": true
},
"fingerprint": {
"name": "fingerprint",
"type": "varchar(32)",
"primaryKey": false,
"notNull": true
},
"country_code": {
"name": "country_code",
"type": "varchar(2)",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"machine_fingerprint": {
"name": "machine_fingerprint",
"columns": [
{
"expression": "fingerprint",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.member": {
"name": "member",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"team_id": {
"name": "team_id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"time_seen": {
"name": "time_seen",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"email_global": {
"name": "email_global",
"columns": [
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"member_email": {
"name": "member_email",
"columns": [
{
"expression": "team_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"member_team_id_id_pk": {
"name": "member_team_id_id_pk",
"columns": [
"team_id",
"id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.steam": {
"name": "steam",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"time_seen": {
"name": "time_seen",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"steam_id": {
"name": "steam_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"avatar_url": {
"name": "avatar_url",
"type": "text",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"last_game": {
"name": "last_game",
"type": "json",
"primaryKey": false,
"notNull": true
},
"username": {
"name": "username",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"country_code": {
"name": "country_code",
"type": "varchar(2)",
"primaryKey": false,
"notNull": true
},
"steam_email": {
"name": "steam_email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"persona_name": {
"name": "persona_name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"limitation": {
"name": "limitation",
"type": "json",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"steam_email": {
"name": "steam_email",
"columns": [
{
"expression": "user_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"steam_user_id_id_pk": {
"name": "steam_user_id_id_pk",
"columns": [
"user_id",
"id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.team": {
"name": "team",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"slug": {
"name": "slug",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"plan_type": {
"name": "plan_type",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"slug": {
"name": "slug",
"columns": [
{
"expression": "slug",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.user": {
"name": "user",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"avatar_url": {
"name": "avatar_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"discriminator": {
"name": "discriminator",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"polar_customer_id": {
"name": "polar_customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
}
},
"indexes": {
"user_email": {
"name": "user_email",
"columns": [
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"user_polar_customer_id_unique": {
"name": "user_polar_customer_id_unique",
"nullsNotDistinct": false,
"columns": [
"polar_customer_id"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -0,0 +1,499 @@
{
"id": "65574f71-e0d3-4363-9449-394e7c376a30",
"prevId": "eb5d41aa-5f85-4b2d-8633-fc021b211241",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.machine": {
"name": "machine",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"user_id": {
"name": "user_id",
"type": "char(30)",
"primaryKey": false,
"notNull": false
},
"country": {
"name": "country",
"type": "text",
"primaryKey": false,
"notNull": true
},
"timezone": {
"name": "timezone",
"type": "text",
"primaryKey": false,
"notNull": true
},
"location": {
"name": "location",
"type": "point",
"primaryKey": false,
"notNull": true
},
"fingerprint": {
"name": "fingerprint",
"type": "varchar(32)",
"primaryKey": false,
"notNull": true
},
"country_code": {
"name": "country_code",
"type": "varchar(2)",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"machine_fingerprint": {
"name": "machine_fingerprint",
"columns": [
{
"expression": "fingerprint",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"machine_user_id_id_pk": {
"name": "machine_user_id_id_pk",
"columns": [
"user_id",
"id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.member": {
"name": "member",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"team_id": {
"name": "team_id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"time_seen": {
"name": "time_seen",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"email_global": {
"name": "email_global",
"columns": [
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"member_email": {
"name": "member_email",
"columns": [
{
"expression": "team_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"member_team_id_id_pk": {
"name": "member_team_id_id_pk",
"columns": [
"team_id",
"id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.steam": {
"name": "steam",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"user_id": {
"name": "user_id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"last_seen": {
"name": "last_seen",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
},
"steam_id": {
"name": "steam_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"avatar_url": {
"name": "avatar_url",
"type": "text",
"primaryKey": false,
"notNull": true
},
"last_game": {
"name": "last_game",
"type": "json",
"primaryKey": false,
"notNull": true
},
"username": {
"name": "username",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"country_code": {
"name": "country_code",
"type": "varchar(2)",
"primaryKey": false,
"notNull": true
},
"steam_email": {
"name": "steam_email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"persona_name": {
"name": "persona_name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"limitation": {
"name": "limitation",
"type": "json",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"steam_user_id_user_id_fk": {
"name": "steam_user_id_user_id_fk",
"tableFrom": "steam",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.team": {
"name": "team",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"slug": {
"name": "slug",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"plan_type": {
"name": "plan_type",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"slug": {
"name": "slug",
"columns": [
{
"expression": "slug",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.user": {
"name": "user",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"avatar_url": {
"name": "avatar_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"discriminator": {
"name": "discriminator",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"polar_customer_id": {
"name": "polar_customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
}
},
"indexes": {
"user_email": {
"name": "user_email",
"columns": [
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"user_polar_customer_id_unique": {
"name": "user_polar_customer_id_unique",
"nullsNotDistinct": false,
"columns": [
"polar_customer_id"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -0,0 +1,485 @@
{
"id": "0b04858c-a7e3-43b6-98a4-1dc2f6f97488",
"prevId": "65574f71-e0d3-4363-9449-394e7c376a30",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.machine": {
"name": "machine",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"country": {
"name": "country",
"type": "text",
"primaryKey": false,
"notNull": true
},
"timezone": {
"name": "timezone",
"type": "text",
"primaryKey": false,
"notNull": true
},
"location": {
"name": "location",
"type": "point",
"primaryKey": false,
"notNull": true
},
"fingerprint": {
"name": "fingerprint",
"type": "varchar(32)",
"primaryKey": false,
"notNull": true
},
"country_code": {
"name": "country_code",
"type": "varchar(2)",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"machine_fingerprint": {
"name": "machine_fingerprint",
"columns": [
{
"expression": "fingerprint",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.member": {
"name": "member",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"team_id": {
"name": "team_id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"time_seen": {
"name": "time_seen",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"email_global": {
"name": "email_global",
"columns": [
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"member_email": {
"name": "member_email",
"columns": [
{
"expression": "team_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"member_team_id_id_pk": {
"name": "member_team_id_id_pk",
"columns": [
"team_id",
"id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.steam": {
"name": "steam",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"user_id": {
"name": "user_id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"last_seen": {
"name": "last_seen",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
},
"steam_id": {
"name": "steam_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"avatar_url": {
"name": "avatar_url",
"type": "text",
"primaryKey": false,
"notNull": true
},
"last_game": {
"name": "last_game",
"type": "json",
"primaryKey": false,
"notNull": true
},
"username": {
"name": "username",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"country_code": {
"name": "country_code",
"type": "varchar(2)",
"primaryKey": false,
"notNull": true
},
"steam_email": {
"name": "steam_email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"persona_name": {
"name": "persona_name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"limitation": {
"name": "limitation",
"type": "json",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"steam_user_id_user_id_fk": {
"name": "steam_user_id_user_id_fk",
"tableFrom": "steam",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.team": {
"name": "team",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"slug": {
"name": "slug",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"plan_type": {
"name": "plan_type",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"slug": {
"name": "slug",
"columns": [
{
"expression": "slug",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.user": {
"name": "user",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"avatar_url": {
"name": "avatar_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"discriminator": {
"name": "discriminator",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"polar_customer_id": {
"name": "polar_customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
}
},
"indexes": {
"user_email": {
"name": "user_email",
"columns": [
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"user_polar_customer_id_unique": {
"name": "user_polar_customer_id_unique",
"nullsNotDistinct": false,
"columns": [
"polar_customer_id"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -0,0 +1,485 @@
{
"id": "69827225-1351-4709-a9b2-facb0f569215",
"prevId": "0b04858c-a7e3-43b6-98a4-1dc2f6f97488",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.machine": {
"name": "machine",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"country": {
"name": "country",
"type": "text",
"primaryKey": false,
"notNull": true
},
"timezone": {
"name": "timezone",
"type": "text",
"primaryKey": false,
"notNull": true
},
"location": {
"name": "location",
"type": "point",
"primaryKey": false,
"notNull": true
},
"fingerprint": {
"name": "fingerprint",
"type": "varchar(32)",
"primaryKey": false,
"notNull": true
},
"country_code": {
"name": "country_code",
"type": "varchar(2)",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"machine_fingerprint": {
"name": "machine_fingerprint",
"columns": [
{
"expression": "fingerprint",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.member": {
"name": "member",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"team_id": {
"name": "team_id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"role": {
"name": "role",
"type": "text",
"primaryKey": false,
"notNull": true
},
"time_seen": {
"name": "time_seen",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"email_global": {
"name": "email_global",
"columns": [
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"member_email": {
"name": "member_email",
"columns": [
{
"expression": "team_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"member_team_id_id_pk": {
"name": "member_team_id_id_pk",
"columns": [
"team_id",
"id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.steam": {
"name": "steam",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"user_id": {
"name": "user_id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"last_seen": {
"name": "last_seen",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
},
"steam_id": {
"name": "steam_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"avatar_url": {
"name": "avatar_url",
"type": "text",
"primaryKey": false,
"notNull": true
},
"last_game": {
"name": "last_game",
"type": "json",
"primaryKey": false,
"notNull": true
},
"username": {
"name": "username",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"country_code": {
"name": "country_code",
"type": "varchar(2)",
"primaryKey": false,
"notNull": true
},
"steam_email": {
"name": "steam_email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"persona_name": {
"name": "persona_name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"limitation": {
"name": "limitation",
"type": "json",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"steam_user_id_user_id_fk": {
"name": "steam_user_id_user_id_fk",
"tableFrom": "steam",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.team": {
"name": "team",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"slug": {
"name": "slug",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"slug": {
"name": "slug",
"columns": [
{
"expression": "slug",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.user": {
"name": "user",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"avatar_url": {
"name": "avatar_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"discriminator": {
"name": "discriminator",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"polar_customer_id": {
"name": "polar_customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
}
},
"indexes": {
"user_email": {
"name": "user_email",
"columns": [
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"user_polar_customer_id_unique": {
"name": "user_polar_customer_id_unique",
"nullsNotDistinct": false,
"columns": [
"polar_customer_id"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -0,0 +1,580 @@
{
"id": "fff2b73d-85ab-48bc-86de-69d3caf317f0",
"prevId": "69827225-1351-4709-a9b2-facb0f569215",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.machine": {
"name": "machine",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"country": {
"name": "country",
"type": "text",
"primaryKey": false,
"notNull": true
},
"timezone": {
"name": "timezone",
"type": "text",
"primaryKey": false,
"notNull": true
},
"location": {
"name": "location",
"type": "point",
"primaryKey": false,
"notNull": true
},
"fingerprint": {
"name": "fingerprint",
"type": "varchar(32)",
"primaryKey": false,
"notNull": true
},
"country_code": {
"name": "country_code",
"type": "varchar(2)",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"machine_fingerprint": {
"name": "machine_fingerprint",
"columns": [
{
"expression": "fingerprint",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.member": {
"name": "member",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"team_id": {
"name": "team_id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"role": {
"name": "role",
"type": "text",
"primaryKey": false,
"notNull": true
},
"time_seen": {
"name": "time_seen",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"email_global": {
"name": "email_global",
"columns": [
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"member_email": {
"name": "member_email",
"columns": [
{
"expression": "team_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"member_team_id_id_pk": {
"name": "member_team_id_id_pk",
"columns": [
"team_id",
"id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.steam": {
"name": "steam",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"user_id": {
"name": "user_id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"last_seen": {
"name": "last_seen",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
},
"steam_id": {
"name": "steam_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"avatar_url": {
"name": "avatar_url",
"type": "text",
"primaryKey": false,
"notNull": true
},
"last_game": {
"name": "last_game",
"type": "json",
"primaryKey": false,
"notNull": true
},
"username": {
"name": "username",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"country_code": {
"name": "country_code",
"type": "varchar(2)",
"primaryKey": false,
"notNull": true
},
"steam_email": {
"name": "steam_email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"persona_name": {
"name": "persona_name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"limitation": {
"name": "limitation",
"type": "json",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"steam_user_id_user_id_fk": {
"name": "steam_user_id_user_id_fk",
"tableFrom": "steam",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.subscription": {
"name": "subscription",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"team_id": {
"name": "team_id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"standing": {
"name": "standing",
"type": "text",
"primaryKey": false,
"notNull": true
},
"plan_type": {
"name": "plan_type",
"type": "text",
"primaryKey": false,
"notNull": true
},
"tokens": {
"name": "tokens",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"product_id": {
"name": "product_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"subscription_id": {
"name": "subscription_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"subscription_team_id_team_id_fk": {
"name": "subscription_team_id_team_id_fk",
"tableFrom": "subscription",
"tableTo": "team",
"columnsFrom": [
"team_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.team": {
"name": "team",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"slug": {
"name": "slug",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"slug": {
"name": "slug",
"columns": [
{
"expression": "slug",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.user": {
"name": "user",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"avatar_url": {
"name": "avatar_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"discriminator": {
"name": "discriminator",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"polar_customer_id": {
"name": "polar_customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
}
},
"indexes": {
"user_email": {
"name": "user_email",
"columns": [
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"user_polar_customer_id_unique": {
"name": "user_polar_customer_id_unique",
"nullsNotDistinct": false,
"columns": [
"polar_customer_id"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -0,0 +1,619 @@
{
"id": "17b9c14f-ff15-44a5-9aaf-3f3b7dd7d294",
"prevId": "fff2b73d-85ab-48bc-86de-69d3caf317f0",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.machine": {
"name": "machine",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"country": {
"name": "country",
"type": "text",
"primaryKey": false,
"notNull": true
},
"timezone": {
"name": "timezone",
"type": "text",
"primaryKey": false,
"notNull": true
},
"location": {
"name": "location",
"type": "point",
"primaryKey": false,
"notNull": true
},
"fingerprint": {
"name": "fingerprint",
"type": "varchar(32)",
"primaryKey": false,
"notNull": true
},
"country_code": {
"name": "country_code",
"type": "varchar(2)",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"machine_fingerprint": {
"name": "machine_fingerprint",
"columns": [
{
"expression": "fingerprint",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.member": {
"name": "member",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"team_id": {
"name": "team_id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"role": {
"name": "role",
"type": "text",
"primaryKey": false,
"notNull": true
},
"time_seen": {
"name": "time_seen",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"email_global": {
"name": "email_global",
"columns": [
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"member_email": {
"name": "member_email",
"columns": [
{
"expression": "team_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"member_team_id_id_pk": {
"name": "member_team_id_id_pk",
"columns": [
"team_id",
"id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.steam": {
"name": "steam",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"user_id": {
"name": "user_id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"last_seen": {
"name": "last_seen",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
},
"steam_id": {
"name": "steam_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"avatar_url": {
"name": "avatar_url",
"type": "text",
"primaryKey": false,
"notNull": true
},
"last_game": {
"name": "last_game",
"type": "json",
"primaryKey": false,
"notNull": true
},
"username": {
"name": "username",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"country_code": {
"name": "country_code",
"type": "varchar(2)",
"primaryKey": false,
"notNull": true
},
"steam_email": {
"name": "steam_email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"persona_name": {
"name": "persona_name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"limitation": {
"name": "limitation",
"type": "json",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"steam_user_id_user_id_fk": {
"name": "steam_user_id_user_id_fk",
"tableFrom": "steam",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.subscription": {
"name": "subscription",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"team_id": {
"name": "team_id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"standing": {
"name": "standing",
"type": "text",
"primaryKey": false,
"notNull": true
},
"plan_type": {
"name": "plan_type",
"type": "text",
"primaryKey": false,
"notNull": true
},
"tokens": {
"name": "tokens",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"product_id": {
"name": "product_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"subscription_id": {
"name": "subscription_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
}
},
"indexes": {
"subscription_id": {
"name": "subscription_id",
"columns": [
{
"expression": "id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
},
"subscription_user_id": {
"name": "subscription_user_id",
"columns": [
{
"expression": "user_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"subscription_team_id_team_id_fk": {
"name": "subscription_team_id_team_id_fk",
"tableFrom": "subscription",
"tableTo": "team",
"columnsFrom": [
"team_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"subscription_id_team_id_pk": {
"name": "subscription_id_team_id_pk",
"columns": [
"id",
"team_id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.team": {
"name": "team",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"slug": {
"name": "slug",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"slug": {
"name": "slug",
"columns": [
{
"expression": "slug",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.user": {
"name": "user",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"avatar_url": {
"name": "avatar_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"discriminator": {
"name": "discriminator",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"polar_customer_id": {
"name": "polar_customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
}
},
"indexes": {
"user_email": {
"name": "user_email",
"columns": [
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"user_polar_customer_id_unique": {
"name": "user_polar_customer_id_unique",
"nullsNotDistinct": false,
"columns": [
"polar_customer_id"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -0,0 +1,650 @@
{
"id": "1717c769-cee0-4242-bcbb-9538c80d985c",
"prevId": "17b9c14f-ff15-44a5-9aaf-3f3b7dd7d294",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.machine": {
"name": "machine",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"country": {
"name": "country",
"type": "text",
"primaryKey": false,
"notNull": true
},
"timezone": {
"name": "timezone",
"type": "text",
"primaryKey": false,
"notNull": true
},
"location": {
"name": "location",
"type": "point",
"primaryKey": false,
"notNull": true
},
"fingerprint": {
"name": "fingerprint",
"type": "varchar(32)",
"primaryKey": false,
"notNull": true
},
"country_code": {
"name": "country_code",
"type": "varchar(2)",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"machine_fingerprint": {
"name": "machine_fingerprint",
"columns": [
{
"expression": "fingerprint",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.member": {
"name": "member",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"team_id": {
"name": "team_id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"role": {
"name": "role",
"type": "text",
"primaryKey": false,
"notNull": true
},
"time_seen": {
"name": "time_seen",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"email_global": {
"name": "email_global",
"columns": [
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"member_email": {
"name": "member_email",
"columns": [
{
"expression": "team_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"member_team_id_id_pk": {
"name": "member_team_id_id_pk",
"columns": [
"team_id",
"id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.steam": {
"name": "steam",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"user_id": {
"name": "user_id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"last_seen": {
"name": "last_seen",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
},
"steam_id": {
"name": "steam_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"avatar_url": {
"name": "avatar_url",
"type": "text",
"primaryKey": false,
"notNull": true
},
"last_game": {
"name": "last_game",
"type": "json",
"primaryKey": false,
"notNull": true
},
"username": {
"name": "username",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"country_code": {
"name": "country_code",
"type": "varchar(2)",
"primaryKey": false,
"notNull": true
},
"steam_email": {
"name": "steam_email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"persona_name": {
"name": "persona_name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"limitation": {
"name": "limitation",
"type": "json",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"steam_id": {
"name": "steam_id",
"columns": [
{
"expression": "steam_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
},
"steam_user_id": {
"name": "steam_user_id",
"columns": [
{
"expression": "user_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"steam_user_id_user_id_fk": {
"name": "steam_user_id_user_id_fk",
"tableFrom": "steam",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.subscription": {
"name": "subscription",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"team_id": {
"name": "team_id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"standing": {
"name": "standing",
"type": "text",
"primaryKey": false,
"notNull": true
},
"plan_type": {
"name": "plan_type",
"type": "text",
"primaryKey": false,
"notNull": true
},
"tokens": {
"name": "tokens",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"product_id": {
"name": "product_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"subscription_id": {
"name": "subscription_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
}
},
"indexes": {
"subscription_id": {
"name": "subscription_id",
"columns": [
{
"expression": "id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
},
"subscription_user_id": {
"name": "subscription_user_id",
"columns": [
{
"expression": "user_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"subscription_team_id_team_id_fk": {
"name": "subscription_team_id_team_id_fk",
"tableFrom": "subscription",
"tableTo": "team",
"columnsFrom": [
"team_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"subscription_id_team_id_pk": {
"name": "subscription_id_team_id_pk",
"columns": [
"id",
"team_id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.team": {
"name": "team",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"slug": {
"name": "slug",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"slug": {
"name": "slug",
"columns": [
{
"expression": "slug",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.user": {
"name": "user",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"avatar_url": {
"name": "avatar_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"discriminator": {
"name": "discriminator",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"polar_customer_id": {
"name": "polar_customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
}
},
"indexes": {
"user_email": {
"name": "user_email",
"columns": [
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"user_polar_customer_id_unique": {
"name": "user_polar_customer_id_unique",
"nullsNotDistinct": false,
"columns": [
"polar_customer_id"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -0,0 +1,651 @@
{
"id": "56a4d60a-c062-47e5-a97e-625443411ad8",
"prevId": "1717c769-cee0-4242-bcbb-9538c80d985c",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.steam_account_credentials": {
"name": "steam_account_credentials",
"schema": "",
"columns": {
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"steam_id": {
"name": "steam_id",
"type": "varchar(255)",
"primaryKey": true,
"notNull": true
},
"refresh_token": {
"name": "refresh_token",
"type": "text",
"primaryKey": false,
"notNull": true
},
"expiry": {
"name": "expiry",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
},
"username": {
"name": "username",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"steam_account_credentials_steam_id_steam_accounts_steam_id_fk": {
"name": "steam_account_credentials_steam_id_steam_accounts_steam_id_fk",
"tableFrom": "steam_account_credentials",
"tableTo": "steam_accounts",
"columnsFrom": [
"steam_id"
],
"columnsTo": [
"steam_id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.friends_list": {
"name": "friends_list",
"schema": "",
"columns": {
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"steam_id": {
"name": "steam_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"friend_steam_id": {
"name": "friend_steam_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"friends_list_steam_id_steam_accounts_steam_id_fk": {
"name": "friends_list_steam_id_steam_accounts_steam_id_fk",
"tableFrom": "friends_list",
"tableTo": "steam_accounts",
"columnsFrom": [
"steam_id"
],
"columnsTo": [
"steam_id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"friends_list_friend_steam_id_steam_accounts_steam_id_fk": {
"name": "friends_list_friend_steam_id_steam_accounts_steam_id_fk",
"tableFrom": "friends_list",
"tableTo": "steam_accounts",
"columnsFrom": [
"friend_steam_id"
],
"columnsTo": [
"steam_id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"friends_list_steam_id_friend_steam_id_pk": {
"name": "friends_list_steam_id_friend_steam_id_pk",
"columns": [
"steam_id",
"friend_steam_id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.members": {
"name": "members",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"team_id": {
"name": "team_id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"user_id": {
"name": "user_id",
"type": "char(30)",
"primaryKey": false,
"notNull": false
},
"steam_id": {
"name": "steam_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"role": {
"name": "role",
"type": "member_role",
"typeSchema": "public",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"idx_member_steam_id": {
"name": "idx_member_steam_id",
"columns": [
{
"expression": "team_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "steam_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
},
"idx_member_user_id": {
"name": "idx_member_user_id",
"columns": [
{
"expression": "team_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "user_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"where": "\"members\".\"user_id\" is not null",
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"members_user_id_users_id_fk": {
"name": "members_user_id_users_id_fk",
"tableFrom": "members",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"members_steam_id_steam_accounts_steam_id_fk": {
"name": "members_steam_id_steam_accounts_steam_id_fk",
"tableFrom": "members",
"tableTo": "steam_accounts",
"columnsFrom": [
"steam_id"
],
"columnsTo": [
"steam_id"
],
"onDelete": "cascade",
"onUpdate": "restrict"
}
},
"compositePrimaryKeys": {
"members_id_team_id_pk": {
"name": "members_id_team_id_pk",
"columns": [
"id",
"team_id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.steam_accounts": {
"name": "steam_accounts",
"schema": "",
"columns": {
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"steam_id": {
"name": "steam_id",
"type": "varchar(255)",
"primaryKey": true,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "char(30)",
"primaryKey": false,
"notNull": false
},
"status": {
"name": "status",
"type": "steam_status",
"typeSchema": "public",
"primaryKey": false,
"notNull": true
},
"last_synced_at": {
"name": "last_synced_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
},
"real_name": {
"name": "real_name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"member_since": {
"name": "member_since",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"profile_url": {
"name": "profile_url",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"username": {
"name": "username",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"avatar_hash": {
"name": "avatar_hash",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"limitations": {
"name": "limitations",
"type": "json",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"steam_accounts_user_id_users_id_fk": {
"name": "steam_accounts_user_id_users_id_fk",
"tableFrom": "steam_accounts",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"idx_steam_username": {
"name": "idx_steam_username",
"nullsNotDistinct": false,
"columns": [
"username"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.teams": {
"name": "teams",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"owner_id": {
"name": "owner_id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"invite_code": {
"name": "invite_code",
"type": "varchar(10)",
"primaryKey": false,
"notNull": true
},
"slug": {
"name": "slug",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"max_members": {
"name": "max_members",
"type": "bigint",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"idx_team_slug": {
"name": "idx_team_slug",
"columns": [
{
"expression": "slug",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"teams_owner_id_users_id_fk": {
"name": "teams_owner_id_users_id_fk",
"tableFrom": "teams",
"tableTo": "users",
"columnsFrom": [
"owner_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"teams_slug_steam_accounts_username_fk": {
"name": "teams_slug_steam_accounts_username_fk",
"tableFrom": "teams",
"tableTo": "steam_accounts",
"columnsFrom": [
"slug"
],
"columnsTo": [
"username"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"idx_team_invite_code": {
"name": "idx_team_invite_code",
"nullsNotDistinct": false,
"columns": [
"invite_code"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"avatar_url": {
"name": "avatar_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"last_login": {
"name": "last_login",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"polar_customer_id": {
"name": "polar_customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"idx_user_email": {
"name": "idx_user_email",
"nullsNotDistinct": false,
"columns": [
"email"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {
"public.member_role": {
"name": "member_role",
"schema": "public",
"values": [
"child",
"adult"
]
},
"public.steam_status": {
"name": "steam_status",
"schema": "public",
"values": [
"online",
"offline",
"dnd",
"playing"
]
}
},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,930 @@
{
"id": "735d315b-40e1-46c1-814d-0fd3619b65de",
"prevId": "d35aa09b-5739-46a5-86f3-3050913dc2f7",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.base_games": {
"name": "base_games",
"schema": "",
"columns": {
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"id": {
"name": "id",
"type": "varchar(255)",
"primaryKey": true,
"notNull": true
},
"links": {
"name": "links",
"type": "json",
"primaryKey": false,
"notNull": false
},
"slug": {
"name": "slug",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"release_date": {
"name": "release_date",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
},
"size": {
"name": "size",
"type": "json",
"primaryKey": false,
"notNull": true
},
"primary_genre": {
"name": "primary_genre",
"type": "text",
"primaryKey": false,
"notNull": false
},
"controller_support": {
"name": "controller_support",
"type": "controller_support",
"typeSchema": "public",
"primaryKey": false,
"notNull": true
},
"compatibility": {
"name": "compatibility",
"type": "compatibility",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'unknown'"
},
"score": {
"name": "score",
"type": "numeric(2, 1)",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"idx_base_games_slug": {
"name": "idx_base_games_slug",
"nullsNotDistinct": false,
"columns": [
"slug"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.categories": {
"name": "categories",
"schema": "",
"columns": {
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"slug": {
"name": "slug",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"type": {
"name": "type",
"type": "category_type",
"typeSchema": "public",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"idx_categories_type": {
"name": "idx_categories_type",
"columns": [
{
"expression": "type",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"categories_slug_type_pk": {
"name": "categories_slug_type_pk",
"columns": [
"slug",
"type"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.friends_list": {
"name": "friends_list",
"schema": "",
"columns": {
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"steam_id": {
"name": "steam_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"friend_steam_id": {
"name": "friend_steam_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"idx_friends_list_friend_steam_id": {
"name": "idx_friends_list_friend_steam_id",
"columns": [
{
"expression": "friend_steam_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"friends_list_steam_id_steam_accounts_id_fk": {
"name": "friends_list_steam_id_steam_accounts_id_fk",
"tableFrom": "friends_list",
"tableTo": "steam_accounts",
"columnsFrom": [
"steam_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"friends_list_friend_steam_id_steam_accounts_id_fk": {
"name": "friends_list_friend_steam_id_steam_accounts_id_fk",
"tableFrom": "friends_list",
"tableTo": "steam_accounts",
"columnsFrom": [
"friend_steam_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"friends_list_steam_id_friend_steam_id_pk": {
"name": "friends_list_steam_id_friend_steam_id_pk",
"columns": [
"steam_id",
"friend_steam_id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.games": {
"name": "games",
"schema": "",
"columns": {
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"base_game_id": {
"name": "base_game_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"category_slug": {
"name": "category_slug",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"type": {
"name": "type",
"type": "category_type",
"typeSchema": "public",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"idx_games_category_slug": {
"name": "idx_games_category_slug",
"columns": [
{
"expression": "category_slug",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"idx_games_category_type": {
"name": "idx_games_category_type",
"columns": [
{
"expression": "type",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"idx_games_category_slug_type": {
"name": "idx_games_category_slug_type",
"columns": [
{
"expression": "category_slug",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "type",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"games_base_game_id_base_games_id_fk": {
"name": "games_base_game_id_base_games_id_fk",
"tableFrom": "games",
"tableTo": "base_games",
"columnsFrom": [
"base_game_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"games_categories_fkey": {
"name": "games_categories_fkey",
"tableFrom": "games",
"tableTo": "categories",
"columnsFrom": [
"category_slug",
"type"
],
"columnsTo": [
"slug",
"type"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"games_base_game_id_category_slug_type_pk": {
"name": "games_base_game_id_category_slug_type_pk",
"columns": [
"base_game_id",
"category_slug",
"type"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.images": {
"name": "images",
"schema": "",
"columns": {
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"type": {
"name": "type",
"type": "image_type",
"typeSchema": "public",
"primaryKey": false,
"notNull": true
},
"image_hash": {
"name": "image_hash",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"base_game_id": {
"name": "base_game_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"source_url": {
"name": "source_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"position": {
"name": "position",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 0
},
"file_size": {
"name": "file_size",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"dimensions": {
"name": "dimensions",
"type": "json",
"primaryKey": false,
"notNull": true
},
"extracted_color": {
"name": "extracted_color",
"type": "json",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"idx_images_type": {
"name": "idx_images_type",
"columns": [
{
"expression": "type",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"idx_images_game_id": {
"name": "idx_images_game_id",
"columns": [
{
"expression": "base_game_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"images_base_game_id_base_games_id_fk": {
"name": "images_base_game_id_base_games_id_fk",
"tableFrom": "images",
"tableTo": "base_games",
"columnsFrom": [
"base_game_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"images_image_hash_type_base_game_id_position_pk": {
"name": "images_image_hash_type_base_game_id_position_pk",
"columns": [
"image_hash",
"type",
"base_game_id",
"position"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.game_libraries": {
"name": "game_libraries",
"schema": "",
"columns": {
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"base_game_id": {
"name": "base_game_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"owner_steam_id": {
"name": "owner_steam_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"last_played": {
"name": "last_played",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"total_playtime": {
"name": "total_playtime",
"type": "integer",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"idx_game_libraries_owner_id": {
"name": "idx_game_libraries_owner_id",
"columns": [
{
"expression": "owner_steam_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"game_libraries_base_game_id_base_games_id_fk": {
"name": "game_libraries_base_game_id_base_games_id_fk",
"tableFrom": "game_libraries",
"tableTo": "base_games",
"columnsFrom": [
"base_game_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"game_libraries_owner_steam_id_steam_accounts_id_fk": {
"name": "game_libraries_owner_steam_id_steam_accounts_id_fk",
"tableFrom": "game_libraries",
"tableTo": "steam_accounts",
"columnsFrom": [
"owner_steam_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"game_libraries_base_game_id_owner_steam_id_pk": {
"name": "game_libraries_base_game_id_owner_steam_id_pk",
"columns": [
"base_game_id",
"owner_steam_id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.steam_accounts": {
"name": "steam_accounts",
"schema": "",
"columns": {
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"id": {
"name": "id",
"type": "varchar(255)",
"primaryKey": true,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "char(30)",
"primaryKey": false,
"notNull": false
},
"status": {
"name": "status",
"type": "steam_status",
"typeSchema": "public",
"primaryKey": false,
"notNull": true
},
"last_synced_at": {
"name": "last_synced_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
},
"real_name": {
"name": "real_name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"member_since": {
"name": "member_since",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"profile_url": {
"name": "profile_url",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"avatar_hash": {
"name": "avatar_hash",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"limitations": {
"name": "limitations",
"type": "json",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"steam_accounts_user_id_users_id_fk": {
"name": "steam_accounts_user_id_users_id_fk",
"tableFrom": "steam_accounts",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"avatar_url": {
"name": "avatar_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"last_login": {
"name": "last_login",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"polar_customer_id": {
"name": "polar_customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"idx_user_email": {
"name": "idx_user_email",
"nullsNotDistinct": false,
"columns": [
"email"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {
"public.compatibility": {
"name": "compatibility",
"schema": "public",
"values": [
"high",
"mid",
"low",
"unknown"
]
},
"public.controller_support": {
"name": "controller_support",
"schema": "public",
"values": [
"full",
"partial",
"unknown"
]
},
"public.category_type": {
"name": "category_type",
"schema": "public",
"values": [
"tag",
"genre",
"publisher",
"developer",
"categorie",
"franchise"
]
},
"public.image_type": {
"name": "image_type",
"schema": "public",
"values": [
"heroArt",
"icon",
"logo",
"banner",
"poster",
"boxArt",
"screenshot",
"backdrop"
]
},
"public.steam_status": {
"name": "steam_status",
"schema": "public",
"values": [
"online",
"offline",
"dnd",
"playing"
]
}
},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -5,15 +5,183 @@
{
"idx": 0,
"version": "7",
"when": 1740345380808,
"tag": "0000_wise_black_widow",
"when": 1741759978256,
"tag": "0000_flaky_matthew_murdock",
"breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1740487217291,
"tag": "0001_flaky_tomorrow_man",
"when": 1741955636085,
"tag": "0001_nifty_sauron",
"breakpoints": true
},
{
"idx": 2,
"version": "7",
"when": 1743794969007,
"tag": "0002_simple_outlaw_kid",
"breakpoints": true
},
{
"idx": 3,
"version": "7",
"when": 1744287542918,
"tag": "0003_first_big_bertha",
"breakpoints": true
},
{
"idx": 4,
"version": "7",
"when": 1744614629788,
"tag": "0004_amused_mattie_franklin",
"breakpoints": true
},
{
"idx": 5,
"version": "7",
"when": 1744614896792,
"tag": "0005_aspiring_stature",
"breakpoints": true
},
{
"idx": 6,
"version": "7",
"when": 1744634229644,
"tag": "0006_worthless_dreadnoughts",
"breakpoints": true
},
{
"idx": 7,
"version": "7",
"when": 1744634322996,
"tag": "0007_warm_secret_warriors",
"breakpoints": true
},
{
"idx": 8,
"version": "7",
"when": 1744651530530,
"tag": "0008_third_mindworm",
"breakpoints": true
},
{
"idx": 9,
"version": "7",
"when": 1744651817581,
"tag": "0009_luxuriant_wraith",
"breakpoints": true
},
{
"idx": 10,
"version": "7",
"when": 1746726715456,
"tag": "0010_certain_dust",
"breakpoints": true
},
{
"idx": 11,
"version": "7",
"when": 1746904821461,
"tag": "0011_simple_azazel",
"breakpoints": true
},
{
"idx": 12,
"version": "7",
"when": 1746905730079,
"tag": "0012_glorious_jetstream",
"breakpoints": true
},
{
"idx": 13,
"version": "7",
"when": 1746925065142,
"tag": "0013_neat_colleen_wing",
"breakpoints": true
},
{
"idx": 14,
"version": "7",
"when": 1746926498096,
"tag": "0014_thin_groot",
"breakpoints": true
},
{
"idx": 15,
"version": "7",
"when": 1746928882281,
"tag": "0015_handy_giant_man",
"breakpoints": true
},
{
"idx": 16,
"version": "7",
"when": 1747032794033,
"tag": "0016_melted_johnny_storm",
"breakpoints": true
},
{
"idx": 17,
"version": "7",
"when": 1747034424687,
"tag": "0017_zippy_nico_minoru",
"breakpoints": true
},
{
"idx": 18,
"version": "7",
"when": 1747073173196,
"tag": "0018_solid_enchantress",
"breakpoints": true
},
{
"idx": 19,
"version": "7",
"when": 1747202158003,
"tag": "0019_charming_namorita",
"breakpoints": true
},
{
"idx": 20,
"version": "7",
"when": 1747795508868,
"tag": "0020_vengeful_wallop",
"breakpoints": true
},
{
"idx": 21,
"version": "7",
"when": 1747975397543,
"tag": "0021_real_skreet",
"breakpoints": true
},
{
"idx": 22,
"version": "7",
"when": 1748099972605,
"tag": "0022_clean_living_lightning",
"breakpoints": true
},
{
"idx": 23,
"version": "7",
"when": 1748411845939,
"tag": "0023_flawless_steel_serpent",
"breakpoints": true
},
{
"idx": 24,
"version": "7",
"when": 1748414049463,
"tag": "0024_damp_cerise",
"breakpoints": true
},
{
"idx": 25,
"version": "7",
"when": 1748845818197,
"tag": "0025_bitter_jack_flag",
"breakpoints": true
}
]

View File

@@ -4,37 +4,46 @@
"sideEffects": false,
"type": "module",
"scripts": {
"db:dev": "drizzle-kit",
"typecheck": "tsc --noEmit",
"db": "sst shell drizzle-kit",
"db:push": "sst shell drizzle-kit push",
"db:migrate": "sst shell drizzle-kit migrate",
"db:generate": "sst shell drizzle-kit generate",
"db:connect": "sst shell ../scripts/src/psql.ts",
"db:move": "sst shell drizzle-kit generate && sst shell drizzle-kit migrate && sst shell drizzle-kit push"
"db:exec": "sst shell ../scripts/src/psql.sh",
"db:reset": "sst shell ../scripts/src/db-reset.sh"
},
"exports": {
"./*": "./src/*.ts"
},
"devDependencies": {
"@tsconfig/node20": "^20.1.4",
"@types/pngjs": "^6.0.5",
"@types/sanitize-html": "^2.16.0",
"@types/xml2js": "^0.4.14",
"aws-iot-device-sdk-v2": "^1.21.1",
"aws4fetch": "^1.0.20",
"drizzle-kit": "^0.30.4",
"loops": "^3.4.1",
"mqtt": "^5.10.3",
"remeda": "^2.19.0",
"remeda": "^2.21.2",
"ulid": "^2.3.0",
"uuid": "^11.0.3",
"zod": "^3.24.1",
"zod-openapi": "^4.2.2"
},
"dependencies": {
"@aws-sdk/client-iot-data-plane": "^3.758.0",
"@aws-sdk/client-sesv2": "^3.753.0",
"@instantdb/admin": "^0.17.7",
"@neondatabase/serverless": "^0.10.4",
"@openauthjs/openauth": "0.4.3",
"@openauthjs/openauth": "*",
"@openauthjs/openevent": "^0.0.27",
"@polar-sh/sdk": "^0.26.1",
"drizzle-orm": "^0.39.3",
"ws": "^8.18.1"
"drizzle-kit": "^0.30.5",
"drizzle-orm": "^0.40.0",
"drizzle-zod": "^0.7.1",
"fast-average-color": "^9.5.0",
"lru-cache": "^11.1.0",
"p-limit": "^6.2.0",
"pixelmatch": "^7.1.0",
"pngjs": "^7.0.0",
"postgres": "^3.4.5",
"sanitize-html": "^2.16.0",
"sharp": "^0.34.1",
"steam-session": "*",
"xml2js": "^0.6.2"
}
}

View File

@@ -0,0 +1,47 @@
import { z } from "zod"
import { User } from "../user";
import { Steam } from "../steam";
import { Actor } from "../actor";
import { Examples } from "../examples";
import { ErrorCodes, VisibleError } from "../error";
export namespace Account {
export const Info =
User.Info
.extend({
profiles: Steam.Info
.array()
.openapi({
description: "The Steam accounts this user owns",
example: [Examples.SteamAccount]
})
})
.openapi({
ref: "Account",
description: "Represents an account's information stored on Nestri",
example: { ...Examples.User, profiles: [Examples.SteamAccount] },
});
export type Info = z.infer<typeof Info>;
export const list = async (): Promise<Info> => {
const [userResult, steamResult] =
await Promise.allSettled([
User.fromID(Actor.userID()),
Steam.list()
])
if (userResult.status === "rejected" || !userResult.value)
throw new VisibleError(
"not_found",
ErrorCodes.NotFound.RESOURCE_NOT_FOUND,
"User not found",
);
return {
...userResult.value,
profiles: steamResult.status === "rejected" ? [] : steamResult.value
}
}
}

View File

@@ -1,92 +1,130 @@
import { z } from "zod";
import { eq } from "./drizzle";
import { VisibleError } from "./error";
import { Log } from "./utils";
import { createContext } from "./context";
import { UserFlags, userTable } from "./user/user.sql";
import { useTransaction } from "./drizzle/transaction";
import { ErrorCodes, VisibleError } from "./error";
export const PublicActor = z.object({
type: z.literal("public"),
properties: z.object({}),
});
export type PublicActor = z.infer<typeof PublicActor>;
export namespace Actor {
export const UserActor = z.object({
type: z.literal("user"),
properties: z.object({
userID: z.string(),
email: z.string().nonempty(),
}),
});
export type UserActor = z.infer<typeof UserActor>;
export const MemberActor = z.object({
type: z.literal("member"),
properties: z.object({
memberID: z.string(),
teamID: z.string(),
}),
});
export type MemberActor = z.infer<typeof MemberActor>;
export const SystemActor = z.object({
type: z.literal("system"),
properties: z.object({
teamID: z.string(),
}),
});
export type SystemActor = z.infer<typeof SystemActor>;
export const Actor = z.discriminatedUnion("type", [
MemberActor,
UserActor,
PublicActor,
SystemActor,
]);
export type Actor = z.infer<typeof Actor>;
const ActorContext = createContext<Actor>("actor");
export const useActor = ActorContext.use;
export const withActor = ActorContext.with;
export function useUserID() {
const actor = ActorContext.use();
if (actor.type === "user") return actor.properties.userID;
throw new VisibleError(
"unauthorized",
`You don't have permission to access this resource`,
);
}
export function assertActor<T extends Actor["type"]>(type: T) {
const actor = useActor();
if (actor.type !== type) {
throw new Error(`Expected actor type ${type}, got ${actor.type}`);
export interface User {
type: "user";
properties: {
userID: string;
email: string;
};
}
export interface Steam {
type: "steam";
properties: {
steamID: string;
};
}
return actor as Extract<Actor, { type: T }>;
}
export interface Machine {
type: "machine";
properties: {
machineID: string;
fingerprint: string;
};
}
export function useTeam() {
const actor = useActor();
if ("teamID" in actor.properties) return actor.properties.teamID;
throw new Error(`Expected actor to have teamID`);
}
export interface Token {
type: "member";
properties: {
userID: string;
steamID: string;
};
}
export async function assertUserFlag(flag: keyof UserFlags) {
return useTransaction((tx) =>
tx
.select({ flags: userTable.flags })
.from(userTable)
.where(eq(userTable.id, useUserID()))
.then((rows) => {
const flags = rows[0]?.flags;
if (!flags)
throw new VisibleError(
"user.flags",
"Actor does not have " + flag + " flag",
);
}),
);
export interface Public {
type: "public";
properties: {};
}
export type Info = User | Public | Token | Machine | Steam;
export const Context = createContext<Info>();
export function userID() {
const actor = Context.use();
if ("userID" in actor.properties) return actor.properties.userID;
throw new VisibleError(
"authentication",
ErrorCodes.Authentication.UNAUTHORIZED,
`You don't have permission to access this resource.`,
);
}
export function steamID() {
const actor = Context.use();
if ("steamID" in actor.properties) return actor.properties.steamID;
throw new VisibleError(
"authentication",
ErrorCodes.Authentication.UNAUTHORIZED,
`You don't have permission to access this resource.`,
);
}
export function user() {
const actor = Context.use();
if (actor.type == "user") return actor.properties;
throw new VisibleError(
"authentication",
ErrorCodes.Authentication.UNAUTHORIZED,
`You don't have permission to access this resource.`,
);
}
export function teamID() {
const actor = Context.use();
if ("teamID" in actor.properties) return actor.properties.teamID;
throw new VisibleError(
"authentication",
ErrorCodes.Authentication.UNAUTHORIZED,
`You don't have permission to access this resource.`,
);
}
export function fingerprint() {
const actor = Context.use();
if ("fingerprint" in actor.properties) return actor.properties.fingerprint;
throw new VisibleError(
"authentication",
ErrorCodes.Authentication.UNAUTHORIZED,
`You don't have permission to access this resource.`,
);
}
export function use() {
try {
return Context.use();
} catch {
return { type: "public", properties: {} } as Public;
}
}
export function assert<T extends Info["type"]>(type: T) {
const actor = use();
if (actor.type !== type)
throw new VisibleError(
"authentication",
ErrorCodes.Authentication.UNAUTHORIZED,
`Actor is not "${type}"`,
);
return actor as Extract<Info, { type: T }>;
}
export function provide<
T extends Info["type"],
Next extends (...args: any) => any,
>(type: T, properties: Extract<Info, { type: T }>["properties"], fn: Next) {
return Context.provide({ type, properties } as any, () =>
Log.provide(
{
actor: type,
...properties,
},
fn,
),
);
}
}

View File

@@ -0,0 +1,44 @@
import { z } from "zod";
import { timestamps, utc } from "../drizzle/types";
import { json, numeric, pgEnum, pgTable, text, unique, varchar } from "drizzle-orm/pg-core";
export const CompatibilityEnum = pgEnum("compatibility", ["high", "mid", "low", "unknown"])
export const ControllerEnum = pgEnum("controller_support", ["full", "partial", "unknown"])
export const Size =
z.object({
downloadSize: z.number().positive().int(),
sizeOnDisk: z.number().positive().int()
});
export const Links = z.string().array();
export type Size = z.infer<typeof Size>;
export type Links = z.infer<typeof Links>;
export const baseGamesTable = pgTable(
"base_games",
{
...timestamps,
id: varchar("id", { length: 255 })
.primaryKey()
.notNull(),
links: json("links").$type<Links>(),
slug: varchar("slug", { length: 255 })
.notNull(),
name: text("name").notNull(),
description: text("description"),
releaseDate: utc("release_date").notNull(),
size: json("size").$type<Size>().notNull(),
primaryGenre: text("primary_genre"),
controllerSupport: ControllerEnum("controller_support").notNull(),
compatibility: CompatibilityEnum("compatibility").notNull().default("unknown"),
// Score ranges from 0.0 to 5.0
score: numeric("score", { precision: 2, scale: 1 })
.$type<number>()
.notNull()
},
(table) => [
unique("idx_base_games_slug").on(table.slug),
]
)

View File

@@ -0,0 +1,162 @@
import { z } from "zod";
import { fn } from "../utils";
import { Common } from "../common";
import { Examples } from "../examples";
import { createEvent } from "../event";
import { eq, isNull, and } from "drizzle-orm";
import { ImageTypeEnum } from "../images/images.sql";
import { createTransaction, useTransaction } from "../drizzle/transaction";
import { CompatibilityEnum, baseGamesTable, Size, ControllerEnum, Links } from "./base-game.sql";
export namespace BaseGame {
export const Info = z.object({
id: z.string().openapi({
description: Common.IdDescription,
example: Examples.BaseGame.id
}),
slug: z.string().openapi({
description: "A URL-friendly unique identifier for the game, used in web addresses and API endpoints",
example: Examples.BaseGame.slug
}),
name: z.string().openapi({
description: "The official title of the game as listed on Steam",
example: Examples.BaseGame.name
}),
size: Size.openapi({
description: "Storage requirements in bytes: downloadSize represents the compressed download, and sizeOnDisk represents the installed size",
example: Examples.BaseGame.size
}),
releaseDate: z.date().openapi({
description: "The initial public release date of the game on Steam",
example: Examples.BaseGame.releaseDate
}),
description: z.string().nullable().openapi({
description: "A comprehensive overview of the game, including its features, storyline, and gameplay elements",
example: Examples.BaseGame.description
}),
score: z.number().openapi({
description: "The aggregate user review score on Steam, represented as a percentage of positive reviews",
example: Examples.BaseGame.score
}),
links: Links
.nullable()
.openapi({
description: "The social links of this game",
example: Examples.BaseGame.links
}),
primaryGenre: z.string().nullable().openapi({
description: "The main category or genre that best represents the game's content and gameplay style",
example: Examples.BaseGame.primaryGenre
}),
controllerSupport: z.enum(ControllerEnum.enumValues).openapi({
description: "Indicates the level of gamepad/controller compatibility: 'Full', 'Partial', or 'Unkown' for no support",
example: Examples.BaseGame.controllerSupport
}),
compatibility: z.enum(CompatibilityEnum.enumValues).openapi({
description: "Steam Deck/Proton compatibility rating indicating how well the game runs on Linux systems",
example: Examples.BaseGame.compatibility
}),
}).openapi({
ref: "BaseGame",
description: "Detailed information about a game available in the Nestri library, including technical specifications and metadata",
example: Examples.BaseGame
})
export type Info = z.infer<typeof Info>;
export const Events = {
New: createEvent(
"new_image.save",
z.object({
appID: Info.shape.id,
url: z.string().url(),
type: z.enum(ImageTypeEnum.enumValues)
}),
),
NewBoxArt: createEvent(
"new_box_art_image.save",
z.object({
appID: Info.shape.id,
logoUrl: z.string().url(),
backgroundUrl: z.string().url(),
}),
),
NewHeroArt: createEvent(
"new_hero_art_image.save",
z.object({
appID: Info.shape.id,
backdropUrl: z.string().url(),
screenshots: z.string().url().array(),
}),
),
};
export const create = fn(
Info,
(input) =>
createTransaction(async (tx) => {
const result = await tx
.select()
.from(baseGamesTable)
.where(
and(
eq(baseGamesTable.id, input.id),
isNull(baseGamesTable.timeDeleted)
)
)
.limit(1)
.execute()
.then(rows => rows.at(0))
if (result) return result.id
await tx
.insert(baseGamesTable)
.values(input)
.onConflictDoUpdate({
target: baseGamesTable.id,
set: {
timeDeleted: null
}
})
return input.id
})
)
export const fromID = fn(
Info.shape.id,
(id) =>
useTransaction(async (tx) =>
tx
.select()
.from(baseGamesTable)
.where(
and(
eq(baseGamesTable.id, id),
isNull(baseGamesTable.timeDeleted)
)
)
.limit(1)
.then(rows => rows.map(serialize).at(0))
)
)
export function serialize(
input: typeof baseGamesTable.$inferSelect,
): z.infer<typeof Info> {
return {
id: input.id,
name: input.name,
slug: input.slug,
size: input.size,
links: input.links,
score: input.score,
description: input.description,
releaseDate: input.releaseDate,
primaryGenre: input.primaryGenre,
compatibility: input.compatibility,
controllerSupport: input.controllerSupport,
};
}
}

View File

@@ -0,0 +1,22 @@
import { timestamps } from "../drizzle/types";
import { index, pgEnum, pgTable, primaryKey, text, varchar } from "drizzle-orm/pg-core";
// Intentional grammatical error on category
export const CategoryTypeEnum = pgEnum("category_type", ["tag", "genre", "publisher", "developer", "categorie", "franchise"])
export const categoriesTable = pgTable(
"categories",
{
...timestamps,
slug: varchar("slug", { length: 255 })
.notNull(),
type: CategoryTypeEnum("type").notNull(),
name: text("name").notNull(),
},
(table) => [
primaryKey({
columns: [table.slug, table.type]
}),
index("idx_categories_type").on(table.type),
]
)

View File

@@ -0,0 +1,128 @@
import { z } from "zod";
import { fn } from "../utils";
import { Examples } from "../examples";
import { eq, isNull, and } from "drizzle-orm";
import { createSelectSchema } from "drizzle-zod";
import { categoriesTable } from "./categories.sql";
import { createTransaction, useTransaction } from "../drizzle/transaction";
export namespace Categories {
const Category = z.object({
slug: z.string().openapi({
description: "A URL-friendly unique identifier for the category",
example: "action-adventure"
}),
name: z.string().openapi({
description: "The human-readable display name of the category",
example: "Action Adventure"
})
})
export const Info =
z.object({
publishers: Category.array().openapi({
description: "List of companies or organizations responsible for publishing and distributing the game",
example: Examples.Categories.publishers
}),
developers: Category.array().openapi({
description: "List of studios, teams, or individuals who created and developed the game",
example: Examples.Categories.developers
}),
tags: Category.array().openapi({
description: "User-defined labels that describe specific features, themes, or characteristics of the game",
example: Examples.Categories.tags
}),
genres: Category.array().openapi({
description: "Primary classification categories that define the game's style and type of gameplay",
example: Examples.Categories.genres
}),
categories: Category.array().openapi({
description: "Primary classification categories that define the game's categorisation on Steam",
example: Examples.Categories.genres
}),
franchises: Category.array().openapi({
description: "The franchise this game belongs belongs to on Steam",
example: Examples.Categories.genres
}),
}).openapi({
ref: "Categories",
description: "A comprehensive categorization system for games, including publishing details, development credits, and content classification",
example: Examples.Categories
})
export type Info = z.infer<typeof Info>;
export const InputInfo = createSelectSchema(categoriesTable)
.omit({ timeCreated: true, timeDeleted: true, timeUpdated: true })
export const create = fn(
InputInfo,
(input) =>
createTransaction(async (tx) => {
const result = await tx
.select()
.from(categoriesTable)
.where(
and(
eq(categoriesTable.slug, input.slug),
eq(categoriesTable.type, input.type),
isNull(categoriesTable.timeDeleted)
)
)
.limit(1)
.execute()
.then(rows => rows.at(0))
if (result) return result.slug
await tx
.insert(categoriesTable)
.values(input)
.onConflictDoUpdate({
target: [categoriesTable.slug, categoriesTable.type],
set: { timeDeleted: null }
})
return input.slug
})
)
export const get = fn(
InputInfo.pick({ slug: true, type: true }),
(input) =>
useTransaction((tx) =>
tx
.select()
.from(categoriesTable)
.where(
and(
eq(categoriesTable.slug, input.slug),
eq(categoriesTable.type, input.type),
isNull(categoriesTable.timeDeleted)
)
)
.limit(1)
.execute()
.then(rows => serialize(rows))
)
)
export function serialize(
input: typeof categoriesTable.$inferSelect[],
): z.infer<typeof Info> {
return input.reduce<Record<`${typeof categoriesTable.$inferSelect["type"]}s`, { slug: string; name: string }[]>>((acc, cat) => {
const key = `${cat.type}s` as `${typeof cat.type}s`
acc[key]!.push({ slug: cat.slug, name: cat.name })
return acc
}, {
tags: [],
genres: [],
publishers: [],
developers: [],
categories: [],
franchises: []
})
}
}

View File

@@ -0,0 +1,316 @@
import type {
Shot,
AppInfo,
ImageInfo,
ImageType,
SteamAccount,
GameTagsResponse,
GameDetailsResponse,
SteamAppDataResponse,
SteamOwnedGamesResponse,
SteamPlayerBansResponse,
SteamFriendsListResponse,
SteamPlayerSummaryResponse,
SteamStoreResponse,
} from "./types";
import { z } from "zod";
import { fn } from "../utils";
import { Resource } from "sst";
import { Steam } from "./steam";
import { Utils } from "./utils";
import { ImageTypeEnum } from "../images/images.sql";
export namespace Client {
export const getUserLibrary = fn(
z.string(),
async (steamID) =>
await Utils.fetchApi<SteamOwnedGamesResponse>(`https://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?key=${Resource.SteamApiKey.value}&steamid=${steamID}&include_appinfo=1&format=json&include_played_free_games=1&skip_unvetted_apps=0`)
)
export const getFriendsList = fn(
z.string(),
async (steamID) =>
await Utils.fetchApi<SteamFriendsListResponse>(`https://api.steampowered.com/ISteamUser/GetFriendList/v0001/?key=${Resource.SteamApiKey.value}&steamid=${steamID}&relationship=friend`)
);
export const getUserInfo = fn(
z.string().array(),
async (steamIDs) => {
const [userInfo, banInfo, profileInfo] = await Promise.all([
Utils.fetchApi<SteamPlayerSummaryResponse>(`https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=${Resource.SteamApiKey.value}&steamids=${steamIDs.join(",")}`),
Utils.fetchApi<SteamPlayerBansResponse>(`https://api.steampowered.com/ISteamUser/GetPlayerBans/v1/?key=${Resource.SteamApiKey.value}&steamids=${steamIDs.join(",")}`),
Utils.fetchProfilesInfo(steamIDs)
])
// Create a map of bans by steamID for fast lookup
const bansBySteamID = new Map(
banInfo.players.map((b) => [b.SteamId, b])
);
// Map userInfo.players to your desired output using Promise.allSettled
// to prevent one error from closing down the whole pipeline
const steamAccounts = await Promise.allSettled(
userInfo.response.players.map(async (player) => {
const ban = bansBySteamID.get(player.steamid);
const info = profileInfo.get(player.steamid);
if (!info) {
throw new Error(`[userInfo] profile info missing for ${player.steamid}`)
}
if ('error' in info) {
throw new Error(`error handling profile info for: ${player.steamid}:${info.error}`)
} else {
return {
id: player.steamid,
name: player.personaname,
realName: player.realname ?? null,
steamMemberSince: new Date(player.timecreated * 1000),
avatarHash: player.avatarhash,
limitations: {
isLimited: info.isLimited,
privacyState: info.privacyState,
isVacBanned: ban?.VACBanned ?? false,
tradeBanState: ban?.EconomyBan ?? "none",
visibilityState: player.communityvisibilitystate,
},
lastSyncedAt: new Date(),
profileUrl: player.profileurl,
};
}
})
);
steamAccounts
.filter(result => result.status === 'rejected')
.forEach(result => console.warn('[userInfo] failed:', (result as PromiseRejectedResult).reason))
return steamAccounts.filter(result => result.status === "fulfilled").map(result => (result as PromiseFulfilledResult<SteamAccount>).value)
})
export const getAppInfo = fn(
z.string(),
async (appid) => {
try {
const info = await Promise.all([
Utils.fetchApi<SteamAppDataResponse>(`https://api.steamcmd.net/v1/info/${appid}`),
Utils.fetchApi<SteamStoreResponse>(`https://api.steampowered.com/IStoreBrowseService/GetItems/v1/?key=${Resource.SteamApiKey.value}&input_json={"ids":[{"appid":"${appid}"}],"context":{"language":"english","country_code":"US","steam_realm":"1"},"data_request":{"include_assets":true,"include_release":true,"include_platforms":true,"include_all_purchase_options":true,"include_screenshots":true,"include_trailers":true,"include_ratings":true,"include_tag_count":"40","include_reviews":true,"include_basic_info":true,"include_supported_languages":true,"include_full_description":true,"include_included_items":true,"include_assets_without_overrides":true,"apply_user_filters":true,"include_links":true}}`),
]);
const cmd = info[0].data[appid]
const store = info[1].response.store_items[0]
if (!cmd) {
throw new Error(`App data not found for appid: ${appid}`)
}
if (!store || store.success !== 1) {
throw new Error(`Could not get store information or appid: ${appid}`)
}
const tags = store.tagids
.map(id => Steam.tags[id.toString() as keyof typeof Steam.tags])
.filter((name): name is string => typeof name === 'string')
const publishers = store.basic_info.publishers
.map(i => i.name)
const developers = store.basic_info.developers
.map(i => i.name)
const franchises = store.basic_info.franchises
?.map(i => i.name)
const genres = cmd?.common.genres &&
Object.keys(cmd?.common.genres)
.map(id => Steam.genres[id.toString() as keyof typeof Steam.genres])
.filter((name): name is string => typeof name === 'string')
const categories = [
...(store.categories?.controller_categoryids?.map(i => Steam.categories[i.toString() as keyof typeof Steam.categories]) ?? []),
...(store.categories?.supported_player_categoryids?.map(i => Steam.categories[i.toString() as keyof typeof Steam.categories]) ?? [])
].filter((name): name is string => typeof name === 'string')
const assetUrls = Utils.getAssetUrls(cmd?.common.library_assets_full, appid, cmd?.common.header_image.english);
const screenshots = store.screenshots.all_ages_screenshots?.map(i => `https://shared.cloudflare.steamstatic.com/store_item_assets/${i.filename}`) ?? [];
const icon = `https://cdn.cloudflare.steamstatic.com/steamcommunity/public/images/apps/${appid}/${cmd?.common.icon}.jpg`;
const data: AppInfo = {
id: appid,
name: cmd?.common.name.trim(),
tags: Utils.createType(tags, "tag"),
images: { screenshots, icon, ...assetUrls },
size: Utils.getPublicDepotSizes(cmd?.depots!),
slug: Utils.createSlug(cmd?.common.name.trim()),
publishers: Utils.createType(publishers, "publisher"),
developers: Utils.createType(developers, "developer"),
categories: Utils.createType(categories, "categorie"),
links: store.links ? store.links.map(i => i.url) : null,
genres: genres ? Utils.createType(genres, "genre") : [],
franchises: franchises ? Utils.createType(franchises, "franchise") : [],
description: store.basic_info.short_description ? Utils.cleanDescription(store.basic_info.short_description) : null,
controllerSupport: cmd?.common.controller_support ?? "unknown" as any,
releaseDate: new Date(Number(cmd?.common.steam_release_date) * 1000),
primaryGenre: !!cmd?.common.primary_genre && Steam.genres[cmd?.common.primary_genre as keyof typeof Steam.genres] ? Steam.genres[cmd?.common.primary_genre as keyof typeof Steam.genres] : null,
compatibility: store?.platforms.steam_os_compat_category ? Utils.compatibilityType(store?.platforms.steam_os_compat_category.toString() as any).toLowerCase() : "unknown" as any,
score: Utils.estimateRatingFromSummary(store.reviews.summary_filtered.review_count, store.reviews.summary_filtered.percent_positive)
}
return data
} catch (err) {
console.log(`Error handling: ${appid}`)
throw err
}
}
)
export const getImageUrls = fn(
z.string(),
async (appid) => {
const [appData, details] = await Promise.all([
Utils.fetchApi<SteamAppDataResponse>(`https://api.steamcmd.net/v1/info/${appid}`),
Utils.fetchApi<GameDetailsResponse>(
`https://store.steampowered.com/apphover/${appid}?full=1&review_score_preference=1&pagev6=true&json=1`
),
]);
const game = appData.data[appid]?.common;
if (!game) throw new Error('Game info missing');
// 2. Prepare URLs
const screenshots = Utils.getScreenshotUrls(details.rgScreenshots || []);
const assetUrls = Utils.getAssetUrls(game.library_assets_full, appid, game.header_image.english);
const icon = `https://cdn.cloudflare.steamstatic.com/steamcommunity/public/images/apps/${appid}/${game.icon}.jpg`;
return { screenshots, icon, ...assetUrls }
}
)
export const getImageInfo = fn(
z.object({
type: z.enum(ImageTypeEnum.enumValues),
url: z.string()
}),
async (input) =>
Utils.fetchBuffer(input.url)
.then(buf => Utils.getImageMetadata(buf))
.then(meta => ({ ...meta, position: 0, sourceUrl: input.url, type: input.type } as ImageInfo))
)
export const createBoxArt = fn(
z.object({
backgroundUrl: z.string(),
logoUrl: z.string(),
}),
async (input) =>
Utils.createBoxArtBuffer(input.logoUrl, input.backgroundUrl)
.then(buf => Utils.getImageMetadata(buf))
.then(meta => ({ ...meta, position: 0, sourceUrl: null, type: 'boxArt' as const }) as ImageInfo)
)
export const createHeroArt = fn(
z.object({
screenshots: z.string().array(),
backdropUrl: z.string()
}),
async (input) => {
// Download screenshot buffers in parallel
const shots: Shot[] = await Promise.all(
input.screenshots.map(async url => ({ url, buffer: await Utils.fetchBuffer(url) }))
);
const baselineBuffer = await Utils.fetchBuffer(input.backdropUrl);
// 4. Score screenshots (or pick single)
const scores =
shots.length === 1
? [{ url: shots[0].url, score: 0 }]
: (await Utils.rankScreenshots(baselineBuffer, shots, {
threshold: 0.08,
}))
// Build url->rank map
const rankMap = new Map<string, number>();
scores.forEach((s, i) => rankMap.set(s.url, i));
// 5. Create tasks for all images
const tasks: Array<Promise<ImageInfo>> = [];
// 5a. Screenshots and heroArt metadata (top 4)
for (const { url, buffer } of shots) {
const rank = rankMap.get(url);
if (rank === undefined || rank >= 4) continue;
const type: ImageType = rank === 0 ? 'heroArt' : 'screenshot';
tasks.push(
Utils.getImageMetadata(buffer).then(meta => ({ ...meta, sourceUrl: url, position: type == "screenshot" ? rank - 1 : rank, type } as ImageInfo))
);
}
const settled = await Promise.allSettled(tasks);
settled
.filter(r => r.status === "rejected")
.forEach(r => console.warn("[getHeroArt] failed:", (r as PromiseRejectedResult).reason));
// Await all and return
return settled.filter(s => s.status === "fulfilled").map(r => (r as PromiseFulfilledResult<ImageInfo>).value)
}
)
/**
* Verifies a Steam OpenID response by sending a request back to Steam
* with mode=check_authentication
*/
export async function verifyOpenIDResponse(params: URLSearchParams): Promise<string | null> {
try {
// Create a new URLSearchParams with all the original parameters
const verificationParams = new URLSearchParams();
// Copy all parameters from the original request
for (const [key, value] of params.entries()) {
verificationParams.append(key, value);
}
// Change mode to check_authentication for verification
verificationParams.set('openid.mode', 'check_authentication');
// Send verification request to Steam
const verificationResponse = await fetch('https://steamcommunity.com/openid/login', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: verificationParams.toString()
});
const responseText = await verificationResponse.text();
// Check if verification was successful
if (!responseText.includes('is_valid:true')) {
console.error('OpenID verification failed: Invalid response from Steam', responseText);
return null;
}
// Extract steamID from the claimed_id
const claimedId = params.get('openid.claimed_id');
if (!claimedId) {
console.error('OpenID verification failed: Missing claimed_id');
return null;
}
// Extract the Steam ID from the claimed_id
const steamID = claimedId.split('/').pop();
if (!steamID || !/^\d+$/.test(steamID)) {
console.error('OpenID verification failed: Invalid steamID format', steamID);
return null;
}
return steamID;
} catch (error) {
console.error('OpenID verification error:', error);
return null;
}
}
}

View File

@@ -0,0 +1,544 @@
export namespace Steam {
//Source: https://github.com/woctezuma/steam-api/blob/master/data/genres.json
export const genres = {
"1": "Action",
"2": "Strategy",
"3": "RPG",
"4": "Casual",
"9": "Racing",
"18": "Sports",
"23": "Indie",
"25": "Adventure",
"28": "Simulation",
"29": "Massively Multiplayer",
"37": "Free to Play",
"50": "Accounting",
"51": "Animation & Modeling",
"52": "Audio Production",
"53": "Design & Illustration",
"54": "Education",
"55": "Photo Editing",
"56": "Software Training",
"57": "Utilities",
"58": "Video Production",
"59": "Web Publishing",
"60": "Game Development",
"70": "Early Access",
"71": "Sexual Content",
"72": "Nudity",
"73": "Violent",
"74": "Gore",
"80": "Movie",
"81": "Documentary",
"82": "Episodic",
"83": "Short",
"84": "Tutorial",
"85": "360 Video"
}
//Source: https://github.com/woctezuma/steam-api/blob/master/data/categories.json
export const categories = {
"1": "Multi-player",
"2": "Single-player",
"6": "Mods (require HL2)",
"7": "Mods (require HL1)",
"8": "Valve Anti-Cheat enabled",
"9": "Co-op",
"10": "Demos",
"12": "HDR available",
"13": "Captions available",
"14": "Commentary available",
"15": "Stats",
"16": "Includes Source SDK",
"17": "Includes level editor",
"18": "Partial Controller Support",
"19": "Mods",
"20": "MMO",
"21": "Downloadable Content",
"22": "Steam Achievements",
"23": "Steam Cloud",
"24": "Shared/Split Screen",
"25": "Steam Leaderboards",
"27": "Cross-Platform Multiplayer",
"28": "Full controller support",
"29": "Steam Trading Cards",
"30": "Steam Workshop",
"31": "VR Support",
"32": "Steam Turn Notifications",
"33": "Native Steam Controller",
"35": "In-App Purchases",
"36": "Online PvP",
"37": "Shared/Split Screen PvP",
"38": "Online Co-op",
"39": "Shared/Split Screen Co-op",
"40": "SteamVR Collectibles",
"41": "Remote Play on Phone",
"42": "Remote Play on Tablet",
"43": "Remote Play on TV",
"44": "Remote Play Together",
"45": "Cloud Gaming",
"46": "Cloud Gaming (NVIDIA)",
"47": "LAN PvP",
"48": "LAN Co-op",
"49": "PvP",
"50": "Additional High-Quality Audio",
"51": "Steam Workshop",
"52": "Tracked Controller Support",
"53": "VR Supported",
"54": "VR Only"
}
// Source: https://files.catbox.moe/96bty7.json
export const tags = {
"9": "Strategy",
"19": "Action",
"21": "Adventure",
"84": "Design & Illustration",
"87": "Utilities",
"113": "Free to Play",
"122": "RPG",
"128": "Massively Multiplayer",
"492": "Indie",
"493": "Early Access",
"597": "Casual",
"599": "Simulation",
"699": "Racing",
"701": "Sports",
"784": "Video Production",
"809": "Photo Editing",
"872": "Animation & Modeling",
"1027": "Audio Production",
"1036": "Education",
"1038": "Web Publishing",
"1445": "Software Training",
"1616": "Trains",
"1621": "Music",
"1625": "Platformer",
"1628": "Metroidvania",
"1638": "Dog",
"1643": "Building",
"1644": "Driving",
"1645": "Tower Defense",
"1646": "Hack and Slash",
"1647": "Western",
"1649": "GameMaker",
"1651": "Satire",
"1654": "Relaxing",
"1659": "Zombies",
"1662": "Survival",
"1663": "FPS",
"1664": "Puzzle",
"1665": "Match 3",
"1666": "Card Game",
"1667": "Horror",
"1669": "Moddable",
"1670": "4X",
"1671": "Superhero",
"1673": "Aliens",
"1674": "Typing",
"1676": "RTS",
"1677": "Turn-Based",
"1678": "War",
"1680": "Heist",
"1681": "Pirates",
"1684": "Fantasy",
"1685": "Co-op",
"1687": "Stealth",
"1688": "Ninja",
"1693": "Classic",
"1695": "Open World",
"1697": "Third Person",
"1698": "Point & Click",
"1702": "Crafting",
"1708": "Tactical",
"1710": "Surreal",
"1714": "Psychedelic",
"1716": "Roguelike",
"1717": "Hex Grid",
"1718": "MOBA",
"1719": "Comedy",
"1720": "Dungeon Crawler",
"1721": "Psychological Horror",
"1723": "Action RTS",
"1730": "Sokoban",
"1732": "Voxel",
"1733": "Unforgiving",
"1734": "Fast-Paced",
"1736": "LEGO",
"1738": "Hidden Object",
"1741": "Turn-Based Strategy",
"1742": "Story Rich",
"1743": "Fighting",
"1746": "Basketball",
"1751": "Comic Book",
"1752": "Rhythm",
"1753": "Skateboarding",
"1754": "MMORPG",
"1755": "Space",
"1756": "Great Soundtrack",
"1759": "Perma Death",
"1770": "Board Game",
"1773": "Arcade",
"1774": "Shooter",
"1775": "PvP",
"1777": "Steampunk",
"3796": "Based On A Novel",
"3798": "Side Scroller",
"3799": "Visual Novel",
"3810": "Sandbox",
"3813": "Real Time Tactics",
"3814": "Third-Person Shooter",
"3834": "Exploration",
"3835": "Post-apocalyptic",
"3839": "First-Person",
"3841": "Local Co-Op",
"3843": "Online Co-Op",
"3854": "Lore-Rich",
"3859": "Multiplayer",
"3871": "2D",
"3877": "Precision Platformer",
"3878": "Competitive",
"3916": "Old School",
"3920": "Cooking",
"3934": "Immersive",
"3942": "Sci-fi",
"3952": "Gothic",
"3955": "Character Action Game",
"3959": "Roguelite",
"3964": "Pixel Graphics",
"3965": "Epic",
"3968": "Physics",
"3978": "Survival Horror",
"3987": "Historical",
"3993": "Combat",
"4004": "Retro",
"4018": "Vampire",
"4026": "Difficult",
"4036": "Parkour",
"4046": "Dragons",
"4057": "Magic",
"4064": "Thriller",
"4085": "Anime",
"4094": "Minimalist",
"4102": "Combat Racing",
"4106": "Action-Adventure",
"4115": "Cyberpunk",
"4136": "Funny",
"4137": "Transhumanism",
"4145": "Cinematic",
"4150": "World War II",
"4155": "Class-Based",
"4158": "Beat 'em up",
"4161": "Real-Time",
"4166": "Atmospheric",
"4168": "Military",
"4172": "Medieval",
"4175": "Realistic",
"4182": "Singleplayer",
"4184": "Chess",
"4190": "Addictive",
"4191": "3D",
"4195": "Cartoony",
"4202": "Trading",
"4231": "Action RPG",
"4234": "Short",
"4236": "Loot",
"4242": "Episodic",
"4252": "Stylized",
"4255": "Shoot 'Em Up",
"4291": "Spaceships",
"4295": "Futuristic",
"4305": "Colorful",
"4325": "Turn-Based Combat",
"4328": "City Builder",
"4342": "Dark",
"4345": "Gore",
"4364": "Grand Strategy",
"4376": "Assassin",
"4400": "Abstract",
"4434": "JRPG",
"4474": "CRPG",
"4486": "Choose Your Own Adventure",
"4508": "Co-op Campaign",
"4520": "Farming",
"4559": "Quick-Time Events",
"4562": "Cartoon",
"4598": "Alternate History",
"4604": "Dark Fantasy",
"4608": "Swordplay",
"4637": "Top-Down Shooter",
"4667": "Violent",
"4684": "Wargame",
"4695": "Economy",
"4700": "Movie",
"4711": "Replay Value",
"4726": "Cute",
"4736": "2D Fighter",
"4747": "Character Customization",
"4754": "Politics",
"4758": "Twin Stick Shooter",
"4777": "Spectacle fighter",
"4791": "Top-Down",
"4821": "Mechs",
"4835": "6DOF",
"4840": "4 Player Local",
"4845": "Capitalism",
"4853": "Political",
"4878": "Parody",
"4885": "Bullet Hell",
"4947": "Romance",
"4975": "2.5D",
"4994": "Naval Combat",
"5030": "Dystopian",
"5055": "eSports",
"5094": "Narration",
"5125": "Procedural Generation",
"5153": "Kickstarter",
"5154": "Score Attack",
"5160": "Dinosaurs",
"5179": "Cold War",
"5186": "Psychological",
"5228": "Blood",
"5230": "Sequel",
"5300": "God Game",
"5310": "Games Workshop",
"5348": "Mod",
"5350": "Family Friendly",
"5363": "Destruction",
"5372": "Conspiracy",
"5379": "2D Platformer",
"5382": "World War I",
"5390": "Time Attack",
"5395": "3D Platformer",
"5407": "Benchmark",
"5411": "Beautiful",
"5432": "Programming",
"5502": "Hacking",
"5537": "Puzzle Platformer",
"5547": "Arena Shooter",
"5577": "RPGMaker",
"5608": "Emotional",
"5611": "Mature",
"5613": "Detective",
"5652": "Collectathon",
"5673": "Modern",
"5708": "Remake",
"5711": "Team-Based",
"5716": "Mystery",
"5727": "Baseball",
"5752": "Robots",
"5765": "Gun Customization",
"5794": "Science",
"5796": "Bullet Time",
"5851": "Isometric",
"5900": "Walking Simulator",
"5914": "Tennis",
"5923": "Dark Humor",
"5941": "Reboot",
"5981": "Mining",
"5984": "Drama",
"6041": "Horses",
"6052": "Noir",
"6129": "Logic",
"6214": "Birds",
"6276": "Inventory Management",
"6310": "Diplomacy",
"6378": "Crime",
"6426": "Choices Matter",
"6506": "3D Fighter",
"6621": "Pinball",
"6625": "Time Manipulation",
"6650": "Nudity",
"6691": "1990's",
"6702": "Mars",
"6730": "PvE",
"6815": "Hand-drawn",
"6869": "Nonlinear",
"6910": "Naval",
"6915": "Martial Arts",
"6948": "Rome",
"6971": "Multiple Endings",
"7038": "Golf",
"7107": "Real-Time with Pause",
"7108": "Party",
"7113": "Crowdfunded",
"7178": "Party Game",
"7208": "Female Protagonist",
"7250": "Linear",
"7309": "Skiing",
"7328": "Bowling",
"7332": "Base Building",
"7368": "Local Multiplayer",
"7423": "Sniper",
"7432": "Lovecraftian",
"7478": "Illuminati",
"7481": "Controller",
"7556": "Dice",
"7569": "Grid-Based Movement",
"7622": "Offroad",
"7702": "Narrative",
"7743": "1980s",
"7782": "Cult Classic",
"7918": "Dwarf",
"7926": "Artificial Intelligence",
"7948": "Soundtrack",
"8013": "Software",
"8075": "TrackIR",
"8093": "Minigames",
"8122": "Level Editor",
"8253": "Music-Based Procedural Generation",
"8369": "Investigation",
"8461": "Well-Written",
"8666": "Runner",
"8945": "Resource Management",
"9130": "Hentai",
"9157": "Underwater",
"9204": "Immersive Sim",
"9271": "Trading Card Game",
"9541": "Demons",
"9551": "Dating Sim",
"9564": "Hunting",
"9592": "Dynamic Narration",
"9803": "Snow",
"9994": "Experience",
"10235": "Life Sim",
"10383": "Transportation",
"10397": "Memes",
"10437": "Trivia",
"10679": "Time Travel",
"10695": "Party-Based RPG",
"10808": "Supernatural",
"10816": "Split Screen",
"11014": "Interactive Fiction",
"11095": "Boss Rush",
"11104": "Vehicular Combat",
"11123": "Mouse only",
"11333": "Villain Protagonist",
"11634": "Vikings",
"12057": "Tutorial",
"12095": "Sexual Content",
"12190": "Boxing",
"12286": "Warhammer 40K",
"12472": "Management",
"13070": "Solitaire",
"13190": "America",
"13276": "Tanks",
"13382": "Archery",
"13577": "Sailing",
"13782": "Experimental",
"13906": "Game Development",
"14139": "Turn-Based Tactics",
"14153": "Dungeons & Dragons",
"14720": "Nostalgia",
"14906": "Intentionally Awkward Controls",
"15045": "Flight",
"15172": "Conversation",
"15277": "Philosophical",
"15339": "Documentary",
"15564": "Fishing",
"15868": "Motocross",
"15954": "Silent Protagonist",
"16094": "Mythology",
"16250": "Gambling",
"16598": "Space Sim",
"16689": "Time Management",
"17015": "Werewolves",
"17305": "Strategy RPG",
"17337": "Lemmings",
"17389": "Tabletop",
"17770": "Asynchronous Multiplayer",
"17894": "Cats",
"17927": "Pool",
"18594": "FMV",
"19568": "Cycling",
"19780": "Submarine",
"19995": "Dark Comedy",
"21006": "Underground",
"21491": "Demo Available",
"21725": "Tactical RPG",
"21978": "VR",
"22602": "Agriculture",
"22955": "Mini Golf",
"24003": "Word Game",
"24904": "NSFW",
"25085": "Touch-Friendly",
"26921": "Political Sim",
"27758": "Voice Control",
"28444": "Snowboarding",
"29363": "3D Vision",
"29482": "Souls-like",
"29855": "Ambient",
"30358": "Nature",
"30927": "Fox",
"31275": "Text-Based",
"31579": "Otome",
"32322": "Deckbuilding",
"33572": "Mahjong",
"35079": "Job Simulator",
"42089": "Jump Scare",
"42329": "Coding",
"42804": "Action Roguelike",
"44868": "LGBTQ+",
"47827": "Wrestling",
"49213": "Rugby",
"51306": "Foreign",
"56690": "On-Rails Shooter",
"61357": "Electronic Music",
"65443": "Adult Content",
"71389": "Spelling",
"87918": "Farming Sim",
"91114": "Shop Keeper",
"92092": "Jet",
"96359": "Skating",
"97376": "Cozy",
"102530": "Elf",
"117648": "8-bit Music",
"123332": "Bikes",
"129761": "ATV",
"143739": "Electronic",
"150626": "Gaming",
"158638": "Cricket",
"176981": "Battle Royale",
"180368": "Faith",
"189941": "Instrumental Music",
"198631": "Mystery Dungeon",
"198913": "Motorbike",
"220585": "Colony Sim",
"233824": "Feature Film",
"252854": "BMX",
"255534": "Automation",
"323922": "Musou",
"324176": "Hockey",
"337964": "Rock Music",
"348922": "Steam Machine",
"353880": "Looter Shooter",
"363767": "Snooker",
"379975": "Clicker",
"454187": "Traditional Roguelike",
"552282": "Wholesome",
"603297": "Hardware",
"615955": "Idler",
"620519": "Hero Shooter",
"745697": "Social Deduction",
"769306": "Escape Room",
"776177": "360 Video",
"791774": "Card Battler",
"847164": "Volleyball",
"856791": "Asymmetric VR",
"916648": "Creature Collector",
"922563": "Roguevania",
"1003823": "Profile Features Limited",
"1023537": "Boomer Shooter",
"1084988": "Auto Battler",
"1091588": "Roguelike Deckbuilder",
"1100686": "Outbreak Sim",
"1100687": "Automobile Sim",
"1100688": "Medical Sim",
"1100689": "Open World Survival Craft",
"1199779": "Extraction Shooter",
"1220528": "Hobby Sim",
"1254546": "Football (Soccer)",
"1254552": "Football (American)",
"1368160": "AI Content Disclosed",
}
}

View File

@@ -0,0 +1,600 @@
export interface SteamApp {
/** Steam application ID */
appid: number;
/** Array of Steam IDs that own this app */
owner_steamids: string[];
/** Name of the game/application */
name: string;
/** Filename of the game's capsule image */
capsule_filename: string;
/** Hash value for the game's icon */
img_icon_hash: string;
/** Reason code for exclusion (0 indicates no exclusion) */
exclude_reason: number;
/** Unix timestamp when the app was acquired */
rt_time_acquired: number;
/** Unix timestamp when the app was last played */
rt_last_played: number;
/** Total playtime in seconds */
rt_playtime: number;
/** Type identifier for the app (1 = game) */
app_type: number;
/** Array of content descriptor IDs */
content_descriptors?: number[];
}
export interface SteamApiResponse {
response: {
apps: SteamApp[];
owner_steamid: string;
};
}
export interface SteamAppDataResponse {
data: Record<string, SteamAppEntry>;
status: string;
}
export interface SteamAppEntry {
_change_number: number;
_missing_token: boolean;
_sha: string;
_size: number;
appid: string;
common: CommonData;
config: AppConfig;
depots: AppDepots;
extended: AppExtended;
ufs: UFSData;
}
export interface CommonData {
associations: Record<string, { name: string; type: string }>;
category: Record<string, string>;
clienticon: string;
clienttga: string;
community_hub_visible: string;
community_visible_stats: string;
content_descriptors: Record<string, string>;
controller_support?: string;
controllertagwizard: string;
gameid: string;
genres: Record<string, string>;
header_image: Record<string, string>;
icon: string;
languages: Record<string, string>;
library_assets: LibraryAssets;
library_assets_full: LibraryAssetsFull;
metacritic_fullurl: string;
metacritic_name: string;
metacritic_score: string;
name: string;
name_localized: Partial<Record<LanguageCode, string>>;
osarch: string;
osextended: string;
oslist: string;
primary_genre: string;
releasestate: string;
review_percentage: string;
review_score: string;
small_capsule: Record<string, string>;
steam_deck_compatibility: SteamDeckCompatibility;
steam_release_date: string;
store_asset_mtime: string;
store_tags: Record<string, string>;
supported_languages: Record<
string,
{
full_audio?: string;
subtitles?: string;
supported?: string;
}
>;
type: string;
}
export interface LibraryAssets {
library_capsule: string;
library_header: string;
library_hero: string;
library_logo: string;
logo_position: LogoPosition;
}
export interface LogoPosition {
height_pct: string;
pinned_position: string;
width_pct: string;
}
export interface LibraryAssetsFull {
library_capsule: ImageSet;
library_header: ImageSet;
library_hero: ImageSet;
library_logo: ImageSet & { logo_position: LogoPosition };
[key: string]: any
}
export interface ImageSet {
image: Record<string, string>;
image2x?: Record<string, string>;
}
export interface SteamDeckCompatibility {
category: string;
configuration: Record<string, string>;
test_timestamp: string;
tested_build_id: string;
tests: Record<string, { display: string; token: string }>;
}
export interface AppConfig {
installdir: string;
launch: Record<
string,
{
executable: string;
type: string;
arguments?: string;
description?: string;
description_loc?: Record<string, string>;
config?: {
betakey: string;
};
}
>;
steamcontrollertemplateindex: string;
steamdecktouchscreen: string;
}
export interface AppDepots {
branches: AppDepotBranches;
privatebranches: Record<string, AppDepotBranches>;
[depotId: string]: DepotEntry
| AppDepotBranches
| Record<string, AppDepotBranches>;
}
export interface DepotEntry {
manifests: {
public: {
download: string;
gid: string;
size: string;
};
};
}
export interface AppDepotBranches {
[branchName: string]: {
buildid: string;
timeupdated: string;
};
}
export interface AppExtended {
additional_dependencies: Array<{
dest_os: string;
h264: string;
src_os: string;
}>;
developer: string;
dlcavailableonstore: string;
homepage: string;
listofdlc: string;
publisher: string;
}
export interface UFSData {
maxnumfiles: string;
quota: string;
savefiles: Array<{
path: string;
pattern: string;
recursive: string;
root: string;
}>;
}
export type LanguageCode =
| "english"
| "french"
| "german"
| "italian"
| "japanese"
| "koreana"
| "polish"
| "russian"
| "schinese"
| "tchinese"
| "brazilian"
| "spanish";
export interface Screenshot {
appid: number;
id: number;
filename: string;
all_ages: string;
normalized_name: string;
}
export interface Category {
strDisplayName: string;
}
export interface ReviewSummary {
strReviewSummary: string;
cReviews: number;
cRecommendationsPositive: number;
cRecommendationsNegative: number;
nReviewScore: number;
}
export interface GameDetailsResponse {
strReleaseDate: string;
strDescription: string;
rgScreenshots: Screenshot[];
rgCategories: Category[];
strGenres?: string;
strFullDescription: string;
strMicroTrailerURL: string;
ReviewSummary: ReviewSummary;
}
// Define the TypeScript interfaces
export interface Tag {
tagid: number;
name: string;
}
export interface TagWithSlug {
name: string;
slug: string;
type: string;
}
export interface StoreTags {
[key: string]: string; // Index signature for numeric string keys to tag ID strings
}
export interface GameTagsResponse {
tags: Tag[];
success: number;
rwgrsn: number;
}
export type GenreType = {
type: 'genre';
name: string;
slug: string;
};
export interface AppInfo {
name: string;
slug: string;
images: {
logo: string;
backdrop: string;
poster: string;
banner: string;
screenshots: string[];
icon: string;
}
links: string[] | null;
score: number;
id: string;
releaseDate: Date;
description: string | null;
compatibility: "low" | "mid" | "high" | "unknown";
controllerSupport: "partial" | "full" | "unknown";
primaryGenre: string | null;
size: { downloadSize: number; sizeOnDisk: number };
tags: Array<{ name: string; slug: string; type: "tag" }>;
genres: Array<{ type: "genre"; name: string; slug: string }>;
categories: Array<{ name: string; slug: string; type: "categorie" }>;
franchises: Array<{ name: string; slug: string; type: "franchise" }>;
developers: Array<{ name: string; slug: string; type: "developer" }>;
publishers: Array<{ name: string; slug: string; type: "publisher" }>;
}
export type ImageType =
| 'screenshot'
| 'boxArt'
| 'banner'
| 'backdrop'
| 'icon'
| 'logo'
| 'poster'
| 'heroArt';
export interface ImageInfo {
type: ImageType;
position: number;
hash: string;
sourceUrl: string | null;
format?: string;
averageColor: { hex: string; isDark: boolean };
dimensions: { width: number; height: number };
fileSize: number;
buffer: Buffer;
}
export interface CompareOpts {
/** Pixelmatch color threshold (01). Default: 0.1 */
threshold?: number;
/** If true, return an image buffer of the diff map. Default: false */
diffOutput?: boolean;
}
export interface CompareResult {
diffRatio: number;
/** Present only if `diffOutput: true` */
diffBuffer?: Buffer;
}
export interface Shot {
url: string;
buffer: Buffer;
}
export interface RankedShot {
url: string;
score: number;
}
export interface SteamPlayerSummaryResponse {
response: {
players: SteamPlayerSummary[];
};
}
export interface SteamPlayerSummary {
steamid: string;
communityvisibilitystate: number;
profilestate?: number;
personaname: string;
profileurl: string;
avatar: string;
avatarmedium: string;
avatarfull: string;
avatarhash: string;
lastlogoff?: number;
personastate: number;
realname?: string;
primaryclanid?: string;
timecreated: number;
personastateflags?: number;
loccountrycode?: string;
}
export interface SteamPlayerBansResponse {
players: SteamPlayerBan[];
}
export interface SteamPlayerBan {
SteamId: string;
CommunityBanned: boolean;
VACBanned: boolean;
NumberOfVACBans: number;
DaysSinceLastBan: number;
NumberOfGameBans: number;
EconomyBan: 'none' | 'probation' | 'banned'; // Enum based on known possible values
}
export type SteamAccount = {
id: string;
name: string;
realName: string | null;
steamMemberSince: Date;
avatarHash: string;
limitations: {
isLimited: boolean;
tradeBanState: 'none' | 'probation' | 'banned';
isVacBanned: boolean;
visibilityState: number;
privacyState: 'public' | 'private' | 'friendsonly';
};
profileUrl: string;
lastSyncedAt: Date;
};
export interface SteamFriendsListResponse {
friendslist: {
friends: SteamFriend[];
};
}
export interface SteamFriend {
steamid: string;
relationship: 'friend'; // could expand this if Steam ever adds more types
friend_since: number; // Unix timestamp (seconds)
}
export interface SteamOwnedGamesResponse {
response: {
game_count: number;
games: SteamOwnedGame[];
};
}
export interface SteamOwnedGame {
appid: number;
name: string;
playtime_forever: number;
img_icon_url: string;
playtime_windows_forever?: number;
playtime_mac_forever?: number;
playtime_linux_forever?: number;
playtime_deck_forever?: number;
rtime_last_played?: number; // Unix timestamp
content_descriptorids?: number[];
playtime_disconnected?: number;
has_community_visible_stats?: boolean;
}
/**
* The shape of the parsed Steam profile information.
*/
export interface ProfileInfo {
steamID64: string;
isLimited: boolean;
privacyState: 'public' | 'private' | 'friendsonly' | string;
visibility: string;
}
export interface SteamStoreResponse {
response: {
store_items: SteamStoreItem[];
};
}
export interface SteamStoreItem {
item_type: number;
id: number;
success: number;
visible: boolean;
name: string;
store_url_path: string;
appid: number;
type: number;
tagids: number[];
categories: {
supported_player_categoryids?: number[];
feature_categoryids?: number[];
controller_categoryids?: number[];
};
reviews: {
summary_filtered: {
review_count: number;
percent_positive: number;
review_score: number;
review_score_label: string;
};
};
basic_info: {
short_description?: string;
publishers: SteamCreator[];
developers: SteamCreator[];
franchises?: SteamCreator[];
};
tags: {
tagid: number;
weight: number;
}[];
assets: SteamAssets;
assets_without_overrides: SteamAssets;
release: {
steam_release_date: number;
};
platforms: {
windows: boolean;
mac: boolean;
steamos_linux: boolean;
vr_support: Record<string, never>;
steam_deck_compat_category?: number;
steam_os_compat_category?: number;
};
best_purchase_option: PurchaseOption;
purchase_options: PurchaseOption[];
screenshots: {
all_ages_screenshots: {
filename: string;
ordinal: number;
}[];
};
trailers: {
highlights: Trailer[];
};
supported_languages: SupportedLanguage[];
full_description: string;
links?: {
link_type: number;
url: string;
}[];
}
export interface SteamCreator {
name: string;
creator_clan_account_id: number;
}
export interface SteamAssets {
asset_url_format: string;
main_capsule: string;
small_capsule: string;
header: string;
page_background: string;
hero_capsule: string;
hero_capsule_2x: string;
library_capsule: string;
library_capsule_2x: string;
library_hero: string;
library_hero_2x: string;
community_icon: string;
page_background_path: string;
raw_page_background: string;
}
export interface PurchaseOption {
packageid?: number;
bundleid?: number;
purchase_option_name: string;
final_price_in_cents: string;
original_price_in_cents: string;
formatted_final_price: string;
formatted_original_price: string;
discount_pct: number;
active_discounts: ActiveDiscount[];
user_can_purchase_as_gift: boolean;
hide_discount_pct_for_compliance: boolean;
included_game_count: number;
bundle_discount_pct?: number;
price_before_bundle_discount?: string;
formatted_price_before_bundle_discount?: string;
}
export interface ActiveDiscount {
discount_amount: string;
discount_description: string;
discount_end_date: number;
}
export interface Trailer {
trailer_name: string;
trailer_url_format: string;
trailer_category: number;
trailer_480p: TrailerFile[];
trailer_max: TrailerFile[];
microtrailer: TrailerFile[];
screenshot_medium: string;
screenshot_full: string;
trailer_base_id: number;
all_ages: boolean;
}
export interface TrailerFile {
filename: string;
type: string;
}
export interface SupportedLanguage {
elanguage: number;
eadditionallanguage: number;
supported: boolean;
full_audio: boolean;
subtitles: boolean;
}

View File

@@ -0,0 +1,524 @@
import type {
Tag,
StoreTags,
AppDepots,
GenreType,
LibraryAssetsFull,
DepotEntry,
CompareOpts,
CompareResult,
RankedShot,
Shot,
ProfileInfo,
} from "./types";
import crypto from 'crypto';
import pLimit from 'p-limit';
import { PNG } from 'pngjs';
import pixelmatch from 'pixelmatch';
import { LRUCache } from 'lru-cache';
import sanitizeHtml from 'sanitize-html';
import { Agent as HttpAgent } from 'http';
import { Agent as HttpsAgent } from 'https';
import { parseStringPromise } from "xml2js";
import sharp, { type Metadata } from 'sharp';
import AbortController from 'abort-controller';
import fetch, { RequestInit } from 'node-fetch';
import { FastAverageColor } from 'fast-average-color';
const fac = new FastAverageColor()
// --- Configuration ---
const httpAgent = new HttpAgent({ keepAlive: true, maxSockets: 50 });
const httpsAgent = new HttpsAgent({ keepAlive: true, maxSockets: 50 });
const downloadCache = new LRUCache<string, Buffer>({
max: 100,
ttl: 1000 * 60 * 30, // 30-minute expiry
allowStale: false,
});
const downloadLimit = pLimit(10); // max concurrent downloads
const compareCache = new LRUCache<string, CompareResult>({
max: 50,
ttl: 1000 * 60 * 10, // 10-minute expiry
});
export namespace Utils {
export async function fetchBuffer(url: string, retries = 3): Promise<Buffer> {
if (downloadCache.has(url)) {
return downloadCache.get(url)!;
}
let lastError: Error | null = null;
for (let attempt = 0; attempt < retries; attempt++) {
try {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), 15_000);
const res = await fetch(url, {
signal: controller.signal,
agent: (_parsed) => _parsed.protocol === 'http:' ? httpAgent : httpsAgent
} as RequestInit);
clearTimeout(id);
if (!res.ok) throw new Error(`Failed to fetch ${url}: ${res.status}`);
const buf = Buffer.from(await res.arrayBuffer());
downloadCache.set(url, buf);
return buf;
} catch (error: any) {
lastError = error as Error;
console.warn(`Attempt ${attempt + 1} failed for ${url}: ${error.message}`);
if (attempt < retries - 1) {
await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, attempt)));
}
}
}
throw lastError || new Error(`Failed to fetch ${url} after ${retries} attempts`);
}
export async function getImageMetadata(buffer: Buffer) {
const hash = crypto.createHash('sha256').update(buffer).digest('hex');
const { width, height, format, size: fileSize } = await sharp(buffer).metadata();
if (!width || !height) throw new Error('Invalid dimensions');
const slice = await sharp(buffer)
.resize({ width: Math.min(width, 256) }) // cheap shrink
.ensureAlpha()
.raw()
.toBuffer();
const pixelArray = new Uint8Array(slice.buffer);
const { hex, isDark } = fac.prepareResult(fac.getColorFromArray4(pixelArray, { mode: "precision" }));
return { hash, format, averageColor: { hex, isDark }, dimensions: { width, height }, fileSize, buffer };
}
// --- Optimized Box Art creation ---
export async function createBoxArtBuffer(
logoUrl: string,
backgroundUrl: string,
logoPercent = 0.9
): Promise<Buffer> {
const [bgBuf, logoBuf] = await Promise.all([
downloadLimit(() =>
fetchBuffer(backgroundUrl)
.catch(error => {
console.error(`Failed to download hero image from ${backgroundUrl}:`, error);
throw new Error(`Failed to create box art: hero image unavailable`);
}),
),
downloadLimit(() => fetchBuffer(logoUrl)
.catch(error => {
console.error(`Failed to download logo image from ${logoUrl}:`, error);
throw new Error(`Failed to create box art: logo image unavailable`);
}),
),
]);
const bgImage = sharp(bgBuf);
const meta = await bgImage.metadata();
if (!meta.width || !meta.height) throw new Error('Invalid background dimensions');
const size = Math.min(meta.width, meta.height);
const left = Math.floor((meta.width - size) / 2);
const top = Math.floor((meta.height - size) / 2);
const squareBg = bgImage.extract({ left, top, width: size, height: size });
// Resize logo
const logoTarget = Math.floor(size * logoPercent);
const logoResized = await sharp(logoBuf).resize({ width: logoTarget }).toBuffer();
const logoMeta = await sharp(logoResized).metadata();
if (!logoMeta.width || !logoMeta.height) throw new Error('Invalid logo dimensions');
const logoLeft = Math.floor((size - logoMeta.width) / 2);
const logoTop = Math.floor((size - logoMeta.height) / 2);
return await squareBg
.composite([{ input: logoResized, left: logoLeft, top: logoTop }])
.jpeg({ quality: 100 })
.toBuffer();
}
/**
* Fetch JSON from the given URL, with Steam-like headers
*/
export async function fetchApi<T>(url: string, retries = 3): Promise<T> {
let lastError: Error | null = null;
for (let attempt = 0; attempt < retries; attempt++) {
try {
const response = await fetch(url, {
agent: (_parsed) => _parsed.protocol === 'http:' ? httpAgent : httpsAgent,
method: "GET",
headers: {
"User-Agent": "Steam 1291812 / iPhone",
"Accept-Language": "en-us",
},
} as RequestInit);
if (!response.ok) {
throw new Error(`API error: ${response.status} ${response.statusText}`);
}
return (await response.json()) as T;
} catch (error: any) {
lastError = error as Error;
// Only retry on network errors or 5xx status codes
if (error.message.includes('API error: 5') || !error.message.includes('API error')) {
console.warn(`Attempt ${attempt + 1} failed for ${url}: ${error.message}`);
await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, attempt)));
continue;
}
throw error;
}
}
throw lastError || new Error(`Failed to fetch ${url} after ${retries} attempts`);
}
/**
* Generate a slug from a name
*/
export function createSlug(name: string): string {
return name
.toLowerCase()
.normalize("NFKD") // Normalize to decompose accented characters
.replace(/[^\p{L}\p{N}\s-]/gu, '') // Keep Unicode letters, numbers, spaces, and hyphens
.replace(/\s+/g, '-') // Replace spaces with hyphens
.replace(/-+/g, '-') // Collapse multiple hyphens
.replace(/^-+|-+$/g, '') // Trim leading/trailing hyphens
.trim();
}
/**
* Compare a candidate screenshot against a UI-free baseline to find how much UI/HUD remains.
*
* @param baselineBuffer - PNG/JPEG buffer of the clean background.
* @param candidateBuffer - PNG/JPEG buffer of the screenshot to test.
* @param opts - Options.
* @returns Promise resolving to diff ratio (and optional diff image).
*/
export async function compareWithBaseline(
baselineBuffer: Buffer,
candidateBuffer: Buffer,
opts: CompareOpts = {}
): Promise<CompareResult> {
// Generate cache key from buffer hashes
const baseHash = crypto.createHash('md5').update(baselineBuffer).digest('hex');
const candHash = crypto.createHash('md5').update(candidateBuffer).digest('hex');
const optsKey = JSON.stringify(opts);
const cacheKey = `${baseHash}:${candHash}:${optsKey}`;
// Check cache
if (compareCache.has(cacheKey)) {
return compareCache.get(cacheKey)!;
}
const { threshold = 0.1, diffOutput = false } = opts;
// Get dimensions of baseline
const baseMeta: Metadata = await sharp(baselineBuffer).metadata();
if (!baseMeta.width || !baseMeta.height) {
throw new Error('Invalid baseline dimensions');
}
// Produce PNG buffers of same size
const [pngBaseBuf, pngCandBuf] = await Promise.all([
sharp(baselineBuffer).png().toBuffer(),
sharp(candidateBuffer)
.resize(baseMeta.width, baseMeta.height)
.png()
.toBuffer(),
]);
const imgBase = PNG.sync.read(pngBaseBuf);
const imgCand = PNG.sync.read(pngCandBuf);
const diffImg = new PNG({ width: baseMeta.width, height: baseMeta.height });
const numDiff = pixelmatch(
imgBase.data,
imgCand.data,
diffImg.data,
baseMeta.width,
baseMeta.height,
{ threshold }
);
const total = baseMeta.width * baseMeta.height;
const diffRatio = numDiff / total;
const result: CompareResult = { diffRatio };
if (diffOutput) {
result.diffBuffer = PNG.sync.write(diffImg);
}
compareCache.set(cacheKey, result);
return result;
}
/**
* Given a baseline buffer and an array of screenshots, returns them sorted
* ascending by diffRatio (least UI first).
*/
export async function rankScreenshots(
baselineBuffer: Buffer,
shots: Shot[],
opts: CompareOpts = {}
): Promise<RankedShot[]> {
// Process up to 5 comparisons in parallel
const compareLimit = pLimit(5);
// Run all comparisons with limited concurrency
const results = await Promise.all(
shots.map(shot =>
compareLimit(async () => {
const { diffRatio } = await compareWithBaseline(
baselineBuffer,
shot.buffer,
opts
);
return { url: shot.url, score: diffRatio };
})
)
);
return results.sort((a, b) => a.score - b.score);
}
// --- Helpers for URLs ---
export function getScreenshotUrls(screenshots: { appid: number; filename: string }[]): string[] {
return screenshots.map(s => `https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/${s.appid}/${s.filename}`);
}
export function getAssetUrls(assets: LibraryAssetsFull, appid: number | string, header: string) {
const base = `https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/${appid}`;
return {
logo: `${base}/${assets.library_logo?.image2x?.english || assets.library_logo?.image?.english}`,
backdrop: `${base}/${assets.library_hero?.image2x?.english || assets.library_hero?.image?.english}`,
poster: `${base}/${assets.library_capsule?.image2x?.english || assets.library_capsule?.image?.english}`,
banner: `${base}/${assets.library_header?.image2x?.english || assets.library_header?.image?.english || header}`,
};
}
/**
* Compute a 05 score from positive/negative votes using a Wilson score confidence interval.
* This formula adjusts the raw ratio based on the total number of votes to account for
* statistical confidence. With few votes, the score regresses toward 2.5 (neutral).
*
* Compute a 05 score from positive/negative votes
*/
export function getRating(positive: number, negative: number): number {
const total = positive + negative;
if (!total) return 0;
const avg = positive / total;
// Apply Wilson score confidence adjustment and scale to 0-5 range
const score = avg - (avg - 0.5) * Math.pow(2, -Math.log10(total + 1));
return Math.round(score * 5 * 10) / 10;
}
export function getAssociationsByTypeWithSlug<
T extends "developer" | "publisher"
>(
associations: Record<string, { name: string; type: string }>,
type: T
): Array<{ name: string; slug: string; type: T }> {
return Object.values(associations)
.filter((a) => a.type === type)
.map((a) => ({ name: a.name.trim(), slug: createSlug(a.name.trim()), type }));
}
export function compatibilityType(type?: string): "low" | "mid" | "high" | "unknown" {
switch (type) {
case "1":
return "high";
case "2":
return "mid";
case "3":
return "low";
default:
return "unknown";
}
}
export function estimateRatingFromSummary(
reviewCount: number,
percentPositive: number
): number {
const positiveVotes = Math.round((percentPositive / 100) * reviewCount);
const negativeVotes = reviewCount - positiveVotes;
return getRating(positiveVotes, negativeVotes);
}
export function mapGameTags<
T extends string = "tag"
>(
available: Tag[],
storeTags: StoreTags,
): Array<{ name: string; slug: string; type: T }> {
const tagMap = new Map<number, Tag>(available.map((t) => [t.tagid, t]));
const result: Array<{ name: string; slug: string; type: T }> = Object.values(storeTags)
.map((id) => tagMap.get(Number(id)))
.filter((t): t is Tag => Boolean(t))
.map((t) => ({ name: t.name.trim(), slug: createSlug(t.name), type: 'tag' as T }));
return result;
}
export function createType<
T extends "developer" | "publisher" | "franchise" | "tag" | "categorie" | "genre"
>(
names: string[],
type: T
) {
return names
.map(name => ({
type,
name: name.trim(),
slug: createSlug(name.trim())
}));
}
/**
* Create a tag object with name, slug, and type
* @typeparam T Literal type of the `type` field (defaults to 'tag')
*/
export function createTag<
T extends string = 'tag'
>(
name: string,
type?: T
): { name: string; slug: string; type: T } {
const tagType = (type ?? 'tag') as T;
return {
name: name.trim(),
slug: createSlug(name),
type: tagType,
};
}
export function capitalise(name: string) {
return name
.charAt(0) // first character
.toUpperCase() // make it uppercase
+ name
.slice(1) // rest of the string
.toLowerCase();
}
function isDepotEntry(e: any): e is DepotEntry {
return (
e != null &&
typeof e === 'object' &&
'manifests' in e &&
e.manifests != null &&
typeof e.manifests.public?.download === 'string'
);
}
export function getPublicDepotSizes(depots: AppDepots) {
let download = 0;
let size = 0;
for (const key of Object.keys(depots)) {
if (key === 'branches' || key === 'privatebranches') continue;
const entry = depots[key] as DepotEntry;
if (!isDepotEntry(entry)) {
continue;
}
const dl = Number(entry.manifests.public.download);
const sz = Number(entry.manifests.public.size);
if (!Number.isFinite(dl) || !Number.isFinite(sz)) {
console.warn(`[getPublicDepotSizes] non-numeric size for depot ${key}`);
continue;
}
download += dl;
size += sz;
}
return { downloadSize: download, sizeOnDisk: size };
}
export function parseGenres(str: string): GenreType[] {
return str.split(',')
.map((g) => g.trim())
.filter(Boolean)
.map((g) => ({ type: 'genre', name: g.trim(), slug: createSlug(g) }));
}
export function getPrimaryGenre(
genres: GenreType[],
map: Record<string, string>,
primaryId: string
): string | null {
const idx = Object.keys(map).find((k) => map[k] === primaryId);
return idx !== undefined ? genres[Number(idx)]?.name : null;
}
export function cleanDescription(input: string): string {
const cleaned = sanitizeHtml(input, {
allowedTags: [], // no tags allowed
allowedAttributes: {}, // no attributes anywhere
textFilter: (text) => text.replace(/\s+/g, ' '), // collapse runs of whitespace
});
return cleaned.trim()
}
/**
* Fetches and parses a single Steam community profile XML.
* @param steamIdOrVanity - The 64-bit SteamID or vanity name.
* @returns Promise resolving to ProfileInfo.
*/
export async function fetchProfileInfo(
steamIdOrVanity: string
): Promise<ProfileInfo> {
const isNumericId = /^\d+$/.test(steamIdOrVanity);
const path = isNumericId ? `profiles/${steamIdOrVanity}` : `id/${steamIdOrVanity}`;
const url = `https://steamcommunity.com/${path}/?xml=1`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch ${steamIdOrVanity}: HTTP ${response.status}`);
}
const xml = await response.text();
const { profile } = await parseStringPromise(xml, {
explicitArray: false,
trim: true,
mergeAttrs: true
}) as { profile: any };
// Extract fields (fall back to limitedAccount tag if needed)
const limitedFlag = profile.isLimitedAccount ?? profile.limitedAccount;
const isLimited = limitedFlag === '1';
return {
isLimited,
steamID64: profile.steamID64,
privacyState: profile.privacyState,
visibility: profile.visibilityState
};
}
/**
* Batch-fetches multiple Steam profiles in parallel.
* @param idsOrVanities - Array of SteamID64 strings or vanity names.
* @returns Promise resolving to a record mapping each input to its ProfileInfo or an error.
*/
export async function fetchProfilesInfo(
idsOrVanities: string[]
): Promise<Map<string, ProfileInfo | { error: string }>> {
const results = await Promise.all(
idsOrVanities.map(async (input) => {
try {
const info = await fetchProfileInfo(input);
return { input, result: info };
} catch (err) {
return { input, result: { error: (err as Error).message } };
}
})
);
return new Map(
results.map(({ input, result }) => [input, result] as [string, ProfileInfo | { error: string }])
);
}
}

View File

@@ -1,7 +1,10 @@
import { z } from "zod";
import "zod-openapi/extend";
import { sql } from "drizzle-orm";
export module Common {
export namespace Common {
export const IdDescription = `Unique object identifier.
The format and length of IDs may change over time.`;
export const now = () => sql`now()`;
export const utc = () => sql`now() at time zone 'utc'`;
}

Some files were not shown because too many files have changed in this diff Show More