Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,25 @@ jobs:
- name: Build the NixOS system for catcolab-next
run: nix build .#nixosConfigurations.catcolab-next.config.system.build.toplevel

check_generated_bindings:
name: check generated bindings
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install Nix
uses: cachix/install-nix-action@v25

- name: Configure Cachix
uses: cachix/cachix-action@v14
with:
name: catcolab-jmoggr
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'

- name: Check generated bindings
run: nix build .#generated-bindings-check

backend_dev_setup:
name: backend dev setup
runs-on: ubuntu-latest
Expand Down
5 changes: 5 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,11 @@
checkMode = true;
};

generated-bindings-check = pkgsLinux.callPackage ./infrastructure/generated-bindings-check.nix {
inherit craneLib cargoArtifacts;
pkgs = pkgsLinux;
};

# VMs built with `nixos-rebuild build-vm` (like `nix build
# .#nixosConfigurations.catcolab-vm.config.system.build.vm`) are not the same
# as "traditional" VMs, which causes deploy-rs to fail when deploying to them.
Expand Down
60 changes: 60 additions & 0 deletions infrastructure/generated-bindings-check.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
craneLib,
cargoArtifacts,
pkgs,
}:
craneLib.mkCargoDerivation {
inherit cargoArtifacts;

pname = "generated-bindings-check";
version = "0.1.0";

nativeBuildInputs = [
pkgs.pkg-config
];

buildInputs = [
pkgs.openssl
];

src = pkgs.lib.fileset.toSource {
root = ../.;
fileset = pkgs.lib.fileset.unions [
../Cargo.toml
../Cargo.lock
(craneLib.fileset.commonCargoSources ../packages/backend)
(craneLib.fileset.commonCargoSources ../packages/migrator)
(craneLib.fileset.commonCargoSources ../packages/notebook-types)
../packages/backend/.sqlx
../packages/backend/pkg
];
};

SQLX_OFFLINE = "true";
# Override crane's default of "false" to match the behavior developers get
# in their shell. TypeId ordering depends on incremental compilation state.
CARGO_BUILD_INCREMENTAL = "true";

buildPhaseCargoCommand = ''
cargo run -p backend -- generate-bindings
'';

checkPhaseCargoCommand = ''
if ! diff -ru packages/backend/pkg.orig packages/backend/pkg --exclude node_modules; then
echo "generate-bindings produced changes to packages/backend/pkg/."
echo "Please run 'cargo run -p backend -- generate-bindings' and commit the result."
exit 1
fi
'';

installPhaseCommand = ''
mkdir -p $out
'';

doCheck = true;

# Save a copy of the original before building so we can diff afterwards
preBuild = ''
cp -r packages/backend/pkg packages/backend/pkg.orig
'';
}
36 changes: 18 additions & 18 deletions packages/backend/pkg/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,40 +13,41 @@

*/
import type { Query, Mutation, Subscription } from "@qubit-rs/client";
export type Permissions = {
export type JsonValue = number | string | boolean | Array<JsonValue> | { [key in string]?: JsonValue } | null;
export type NewPermissions = {
/**
* Base permission level for any person, logged in or not.
*/
anyone: PermissionLevel | null,
/**
* Permission level for the current user.
*/
user: PermissionLevel | null,
/**
* Permission levels for all other users.
* Permission levels for users.
*
* Only owners of the document have access to this information.
* A mapping from user IDs to permission levels.
*/
users: Array<UserPermissions> | null, };
users: { [key in string]?: PermissionLevel }, };
export type UserPermissions = { user: UserSummary, level: PermissionLevel, };
export type RefDoc = { "tag": "Readonly", binaryData: string, isDeleted: boolean, permissions: Permissions, } | { "tag": "Live", docId: string, isDeleted: boolean, permissions: Permissions, };
export type UserProfile = { username: string | null, displayName: string | null, };
export type JsonValue = number | string | boolean | Array<JsonValue> | { [key in string]?: JsonValue } | null;
export type RefQueryParams = { ownerUsernameQuery: string | null, refNameQuery: string | null, searcherMinLevel: PermissionLevel | null, includePublicDocuments: boolean | null, onlyDeleted: boolean | null, limit: number | null, offset: number | null, };
export type RpcResult<T> = { "tag": "Ok", content: T, } | { "tag": "Err", code: number, message: string, };
export type NewPermissions = {
export type Permissions = {
/**
* Base permission level for any person, logged in or not.
*/
anyone: PermissionLevel | null,
/**
* Permission levels for users.
* Permission level for the current user.
*/
user: PermissionLevel | null,
/**
* Permission levels for all other users.
*
* A mapping from user IDs to permission levels.
* Only owners of the document have access to this information.
*/
users: { [key in string]?: PermissionLevel }, };
export type UserSummary = { id: string, username: string | null, displayName: string | null, };
users: Array<UserPermissions> | null, };
export type RefStub = { name: string, typeName: string, refId: string, permissionLevel: PermissionLevel, owner: UserSummary | null, createdAt: string, };
export type RefDoc = { "tag": "Readonly", binaryData: string, isDeleted: boolean, permissions: Permissions, } | { "tag": "Live", docId: string, isDeleted: boolean, permissions: Permissions, };
export type UserSummary = { id: string, username: string | null, displayName: string | null, };
export type RpcResult<T> = { "tag": "Ok", content: T, } | { "tag": "Err", code: number, message: string, };
export type PermissionLevel = "Read" | "Write" | "Maintain" | "Own";
export type RefQueryParams = { ownerUsernameQuery: string | null, refNameQuery: string | null, searcherMinLevel: PermissionLevel | null, includePublicDocuments: boolean | null, onlyDeleted: boolean | null, limit: number | null, offset: number | null, };
export type Paginated<T> = {
/**
* The total number of items matching the query criteria.
Expand All @@ -61,5 +62,4 @@ offset: number,
*/
items: Array<T>, };
export type UsernameStatus = "Available" | "Unavailable" | "Invalid";
export type PermissionLevel = "Read" | "Write" | "Maintain" | "Own";
export type QubitServer = { create_snapshot: Mutation<[ref_id: string], RpcResult<null>>, delete_ref: Mutation<[ref_id: string], RpcResult<null>>, get_active_user_profile: Query<[], RpcResult<UserProfile>>, get_doc: Query<[ref_id: string], RpcResult<RefDoc>>, get_permissions: Query<[ref_id: string], RpcResult<Permissions>>, get_ref_children_stubs: Query<[ref_id: string], RpcResult<Array<RefStub>>>, head_snapshot: Query<[ref_id: string], RpcResult<JsonValue>>, new_ref: Mutation<[content: JsonValue], RpcResult<string>>, restore_ref: Mutation<[ref_id: string], RpcResult<null>>, search_ref_stubs: Query<[query_params: RefQueryParams], RpcResult<Paginated<RefStub>>>, set_active_user_profile: Mutation<[user: UserProfile], RpcResult<null>>, set_permissions: Mutation<[ref_id: string, new: NewPermissions], RpcResult<null>>, sign_up_or_sign_in: Mutation<[], RpcResult<null>>, user_by_username: Query<[username: string], RpcResult<UserSummary | null>>, username_status: Query<[username: string], RpcResult<UsernameStatus>>, validate_session: Query<[], RpcResult<null>>, };
38 changes: 20 additions & 18 deletions packages/backend/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,31 @@ async fn main() {

tracing_subscriber::fmt().with_env_filter(env_filter).init();

let cli = Cli::parse();

if let Some(Command::GenerateBindings) = cli.command {
use qubit::TypeScript;

let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("pkg")
.join("src")
.join("index.ts");

rpc::router()
.as_codegen()
.write_type(&path, TypeScript::new())
.expect("Failed to write TypeScript bindings");

info!("Successfully generated TypeScript bindings to: {}", path.display());
return;
}

let db = PgPoolOptions::new()
.max_connections(10)
.connect(&dotenvy::var("DATABASE_URL").expect("`DATABASE_URL` should be set"))
.await
.expect("Failed to connect to database");

let cli = Cli::parse();

let mut migrator = Migrator::default();
migrator
.add_migrations(migrator::migrations())
Expand All @@ -80,22 +97,7 @@ async fn main() {
return;
}

Command::GenerateBindings => {
use qubit::TypeScript;

let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("pkg")
.join("src")
.join("index.ts");

rpc::router()
.as_codegen()
.write_type(&path, TypeScript::new())
.expect("Failed to write TypeScript bindings");

info!("Successfully generated TypeScript bindings to: {}", path.display());
return;
}
Command::GenerateBindings => unreachable!(),
Comment on lines -83 to +100

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved this so we can run generate-bindings without the DATABASE_URL env variable.


Command::Serve => {
info!("Applying database migrations...");
Expand Down
Loading