warp-server 0.2.0__tar.gz → 0.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {warp_server-0.2.0 → warp_server-0.2.3}/PKG-INFO +20 -1
- {warp_server-0.2.0 → warp_server-0.2.3}/README.md +19 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/pyproject.toml +1 -1
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/auth.py +49 -12
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/cli.py +95 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/config.py +4 -1
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/main.py +10 -13
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/routers/commits.py +11 -4
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/routers/functions.py +43 -11
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/routers/search.py +14 -5
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/routers/sources.py +28 -8
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/routers/symbols.py +18 -4
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/routers/targets.py +8 -2
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/schemas.py +1 -1
- {warp_server-0.2.0 → warp_server-0.2.3}/tests/test_commits.py +21 -8
- {warp_server-0.2.0 → warp_server-0.2.3}/tests/test_functions.py +49 -18
- {warp_server-0.2.0 → warp_server-0.2.3}/tests/test_search.py +17 -8
- {warp_server-0.2.0 → warp_server-0.2.3}/tests/test_sources.py +23 -9
- {warp_server-0.2.0 → warp_server-0.2.3}/tests/test_targets.py +9 -5
- {warp_server-0.2.0 → warp_server-0.2.3}/tests/test_warp_parser.py +4 -1
- {warp_server-0.2.0 → warp_server-0.2.3}/uv.lock +1 -1
- {warp_server-0.2.0 → warp_server-0.2.3}/.github/workflows/ci.yml +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/.gitignore +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/.gitmodules +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/.pre-commit-config.yaml +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/Dockerfile +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/__init__.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/database.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/SigBin/BasicBlock.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/SigBin/BasicBlockGUID.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/SigBin/Constraint.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/SigBin/ConstraintGUID.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/SigBin/Function.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/SigBin/FunctionComment.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/SigBin/FunctionGUID.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/SigBin/FunctionVariable.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/SigBin/SignatureChunk.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/SigBin/__init__.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/SymbolBin/Symbol.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/SymbolBin/SymbolClass.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/SymbolBin/SymbolModifiers.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/SymbolBin/__init__.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TargetBin/Target.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TargetBin/__init__.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/Array.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/ArrayModifiers.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/BitOffset.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/BitShift.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/BitSize.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/BitWidth.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/Boolean.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/CallingConvention.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/Character.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/ComputedType.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/Enumeration.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/EnumerationMember.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/Float.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/Function.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/FunctionMember.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/Integer.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/LocationClass.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/MetadataValueType.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/Pointer.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/PointerAddressing.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/Referrer.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/RegisterLocation.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/StackLocation.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/Structure.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/StructureMember.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/StructureMemberModifiers.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/Type.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/TypeChunk.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/TypeClass.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/TypeGUID.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/TypeMetadata.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/TypeModifiers.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/Union.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/UnionMember.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/UnsignedBitOffset.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/Void.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/__init__.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/Warp/Chunk.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/Warp/ChunkHeader.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/Warp/ChunkType.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/Warp/CompressionType.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/Warp/File.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/Warp/FileHeader.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/Warp/__init__.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/__init__.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/models.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/routers/__init__.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/routers/auth.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/routers/files.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/routers/status.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/routers/types.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/routers/users.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/static/index.html +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/warp_parser.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/tests/__init__.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/tests/conftest.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/tests/test_files.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/tests/test_ingest.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/tests/test_lockout.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/tests/test_status.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/tests/test_users.py +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/.git +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/.github/workflows/CI.yaml +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/.gitignore +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/Cargo.toml +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/LICENSE +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/README.md +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/about.hbs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/about.toml +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/Cargo.toml +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/benches/chunk.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/benches/type.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/build.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/examples/dumper.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/examples/random.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/examples/type_builder.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/fixtures/random.warp +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/fuzz/.gitignore +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/fuzz/Cargo.toml +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/fuzz/fuzz_targets/file.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/fuzz/fuzz_targets/function.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/fuzz/fuzz_targets/type.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/cached_builder.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/chunk.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/mod.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/sig_bin/basic_block_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/sig_bin/basic_block_guid_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/sig_bin/constraint_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/sig_bin/constraint_guid_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/sig_bin/function_comment_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/sig_bin/function_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/sig_bin/function_guid_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/sig_bin/function_variable_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/sig_bin/signature_chunk_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/symbol_bin/symbol_class_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/symbol_bin/symbol_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/symbol_bin/symbol_modifiers_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/target_bin/target_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/array_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/array_modifiers_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/bit_offset_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/bit_shift_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/bit_size_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/bit_width_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/boolean_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/calling_convention_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/character_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/computed_type_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/enumeration_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/enumeration_member_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/float_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/function_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/function_member_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/function_member_location_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/integer_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/location_class_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/metadata_value_type_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/pointer_addressing_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/pointer_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/referrer_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/register_location_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/stack_location_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/structure_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/structure_member_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/structure_member_modifiers_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/type_chunk_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/type_class_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/type_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/type_guid_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/type_metadata_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/type_modifiers_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/union_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/union_member_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/unsigned_bit_offset_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/void_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/warp/chunk_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/warp/chunk_header_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/warp/chunk_type_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/warp/compression_type_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/warp/file_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/warp/file_header_generated.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/lib.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/mock.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/signature/basic_block.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/signature/chunk.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/signature/comment.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/signature/constraint.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/signature/function.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/signature/variable.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/signature.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/symbol.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/target.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/type/chunk.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/type/class/array.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/type/class/boolean.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/type/class/character.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/type/class/enumeration.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/type/class/float.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/type/class/function.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/type/class/integer.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/type/class/pointer.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/type/class/referrer.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/type/class/structure.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/type/class/union.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/type/class/void.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/type/class.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/type/guid.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/type.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/tests/chunk.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/tests/file.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/tests/signature.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/tests/snapshots/file__file_format_regression.snap +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/tests/types.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/signature.fbs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/symbol.fbs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/target.fbs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/type.fbs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/warp.fbs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/warp_cli/Cargo.toml +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/warp_cli/src/main.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/warp_cli/src/operation/find.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/warp_cli/src/operation/merge.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/warp_cli/src/operation.rs +0 -0
- {warp_server-0.2.0 → warp_server-0.2.3}/warp/warp_cli/src/utility.rs +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: warp-server
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: WARP signature server API
|
|
5
5
|
Requires-Python: >=3.12
|
|
6
6
|
Requires-Dist: aiosqlite>=0.20
|
|
@@ -145,6 +145,25 @@ Log in with your username and API key. The UI lets you:
|
|
|
145
145
|
- Create API keys
|
|
146
146
|
- Browse functions and commits
|
|
147
147
|
|
|
148
|
+
## Configuration
|
|
149
|
+
|
|
150
|
+
The server is configured via environment variables, all prefixed with `WARP_`.
|
|
151
|
+
|
|
152
|
+
| Variable | Default | Description |
|
|
153
|
+
|----------|---------|-------------|
|
|
154
|
+
| `WARP_DEBUG` | `false` | Enable debug mode: exposes Swagger UI at `/docs`. |
|
|
155
|
+
| `WARP_DATABASE_URL` | `sqlite+aiosqlite:///./warp.db` | SQLAlchemy async database URL. |
|
|
156
|
+
| `WARP_HOST` | `127.0.0.1` | Bind address. |
|
|
157
|
+
| `WARP_PORT` | `8000` | Listen port. |
|
|
158
|
+
| `WARP_LOG_LEVEL` | `info` | Uvicorn log level (`debug`, `info`, `warning`, `error`). |
|
|
159
|
+
| `WARP_RELOAD` | `false` | Enable auto-reload on file changes (dev only). |
|
|
160
|
+
| `WARP_CORS_ORIGINS` | `[]` | JSON list of allowed CORS origins, e.g. `'["https://app.example.com"]'`. |
|
|
161
|
+
| `WARP_TRUSTED_PROXIES` | `[]` | JSON list of trusted reverse-proxy IPs allowed to set `X-Forwarded-For`. |
|
|
162
|
+
| `WARP_MAX_LOGIN_ATTEMPTS` | `5` | Failed attempts before an IP or user account is locked. |
|
|
163
|
+
| `WARP_LOCKOUT_DURATION_MINUTES` | `30` | Minutes before a locked IP or account is automatically unlocked. |
|
|
164
|
+
|
|
165
|
+
> **Warning:** `WARP_DEBUG=true` exposes Swagger UI at `/docs` and ReDoc at `/redoc`. Never enable it in production.
|
|
166
|
+
|
|
148
167
|
## Docker
|
|
149
168
|
|
|
150
169
|
```bash
|
|
@@ -129,6 +129,25 @@ Log in with your username and API key. The UI lets you:
|
|
|
129
129
|
- Create API keys
|
|
130
130
|
- Browse functions and commits
|
|
131
131
|
|
|
132
|
+
## Configuration
|
|
133
|
+
|
|
134
|
+
The server is configured via environment variables, all prefixed with `WARP_`.
|
|
135
|
+
|
|
136
|
+
| Variable | Default | Description |
|
|
137
|
+
|----------|---------|-------------|
|
|
138
|
+
| `WARP_DEBUG` | `false` | Enable debug mode: exposes Swagger UI at `/docs`. |
|
|
139
|
+
| `WARP_DATABASE_URL` | `sqlite+aiosqlite:///./warp.db` | SQLAlchemy async database URL. |
|
|
140
|
+
| `WARP_HOST` | `127.0.0.1` | Bind address. |
|
|
141
|
+
| `WARP_PORT` | `8000` | Listen port. |
|
|
142
|
+
| `WARP_LOG_LEVEL` | `info` | Uvicorn log level (`debug`, `info`, `warning`, `error`). |
|
|
143
|
+
| `WARP_RELOAD` | `false` | Enable auto-reload on file changes (dev only). |
|
|
144
|
+
| `WARP_CORS_ORIGINS` | `[]` | JSON list of allowed CORS origins, e.g. `'["https://app.example.com"]'`. |
|
|
145
|
+
| `WARP_TRUSTED_PROXIES` | `[]` | JSON list of trusted reverse-proxy IPs allowed to set `X-Forwarded-For`. |
|
|
146
|
+
| `WARP_MAX_LOGIN_ATTEMPTS` | `5` | Failed attempts before an IP or user account is locked. |
|
|
147
|
+
| `WARP_LOCKOUT_DURATION_MINUTES` | `30` | Minutes before a locked IP or account is automatically unlocked. |
|
|
148
|
+
|
|
149
|
+
> **Warning:** `WARP_DEBUG=true` exposes Swagger UI at `/docs` and ReDoc at `/redoc`. Never enable it in production.
|
|
150
|
+
|
|
132
151
|
## Docker
|
|
133
152
|
|
|
134
153
|
```bash
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import hashlib
|
|
2
|
-
from datetime import datetime, timezone
|
|
2
|
+
from datetime import datetime, timedelta, timezone
|
|
3
3
|
|
|
4
4
|
from fastapi import Depends, HTTPException, Request, Security, status
|
|
5
5
|
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
@@ -19,11 +19,50 @@ def hash_api_key(raw_key: str) -> str:
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
def _client_ip(request: Request) -> str:
|
|
22
|
-
"""Extract client IP
|
|
23
|
-
|
|
24
|
-
if
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
"""Extract client IP. Only trust X-Forwarded-For from configured trusted proxies."""
|
|
23
|
+
direct_ip = request.client.host if request.client else "unknown"
|
|
24
|
+
if settings.trusted_proxies and direct_ip in settings.trusted_proxies:
|
|
25
|
+
forwarded = request.headers.get("x-forwarded-for")
|
|
26
|
+
if forwarded:
|
|
27
|
+
return forwarded.split(",")[0].strip()
|
|
28
|
+
return direct_ip
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _is_locked(attempt: LoginAttempt) -> bool:
|
|
32
|
+
"""Return True if the attempt record is still within the lockout window."""
|
|
33
|
+
if not attempt.locked:
|
|
34
|
+
return False
|
|
35
|
+
if attempt.locked_at is None:
|
|
36
|
+
return True
|
|
37
|
+
locked_at = attempt.locked_at
|
|
38
|
+
if locked_at.tzinfo is None:
|
|
39
|
+
locked_at = locked_at.replace(tzinfo=timezone.utc)
|
|
40
|
+
unlock_at = locked_at + timedelta(minutes=settings.lockout_duration_minutes)
|
|
41
|
+
if datetime.now(timezone.utc) >= unlock_at:
|
|
42
|
+
# Auto-unlock: lockout window has expired
|
|
43
|
+
attempt.locked = 0
|
|
44
|
+
attempt.failed_count = 0
|
|
45
|
+
attempt.locked_at = None
|
|
46
|
+
return False
|
|
47
|
+
return True
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _is_user_locked(user: User) -> bool:
|
|
51
|
+
"""Return True if the user account is still within the lockout window."""
|
|
52
|
+
if not user.locked:
|
|
53
|
+
return False
|
|
54
|
+
if user.locked_at is None:
|
|
55
|
+
return True
|
|
56
|
+
locked_at = user.locked_at
|
|
57
|
+
if locked_at.tzinfo is None:
|
|
58
|
+
locked_at = locked_at.replace(tzinfo=timezone.utc)
|
|
59
|
+
unlock_at = locked_at + timedelta(minutes=settings.lockout_duration_minutes)
|
|
60
|
+
if datetime.now(timezone.utc) >= unlock_at:
|
|
61
|
+
user.locked = 0
|
|
62
|
+
user.failed_login_count = 0
|
|
63
|
+
user.locked_at = None
|
|
64
|
+
return False
|
|
65
|
+
return True
|
|
27
66
|
|
|
28
67
|
|
|
29
68
|
async def _get_attempt(db: AsyncSession, ip: str) -> LoginAttempt:
|
|
@@ -81,9 +120,9 @@ async def get_current_user(
|
|
|
81
120
|
|
|
82
121
|
ip = _client_ip(request)
|
|
83
122
|
|
|
84
|
-
# Check if IP is locked
|
|
123
|
+
# Check if IP is locked (with auto-unlock)
|
|
85
124
|
attempt = await _get_attempt(db, ip)
|
|
86
|
-
if attempt
|
|
125
|
+
if _is_locked(attempt):
|
|
87
126
|
raise HTTPException(
|
|
88
127
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
89
128
|
detail="Too many failed attempts. IP locked.",
|
|
@@ -99,7 +138,6 @@ async def get_current_user(
|
|
|
99
138
|
status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid API key"
|
|
100
139
|
)
|
|
101
140
|
|
|
102
|
-
# Resolve user early so we can track per-user failures
|
|
103
141
|
result = await db.execute(select(User).where(User.id == api_key.user_id))
|
|
104
142
|
user = result.scalar_one_or_none()
|
|
105
143
|
if user is None:
|
|
@@ -107,8 +145,8 @@ async def get_current_user(
|
|
|
107
145
|
status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found"
|
|
108
146
|
)
|
|
109
147
|
|
|
110
|
-
# Check if user account is locked
|
|
111
|
-
if user
|
|
148
|
+
# Check if user account is locked (with auto-unlock)
|
|
149
|
+
if _is_user_locked(user):
|
|
112
150
|
raise HTTPException(
|
|
113
151
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
114
152
|
detail="User account locked.",
|
|
@@ -124,7 +162,6 @@ async def get_current_user(
|
|
|
124
162
|
status_code=status.HTTP_401_UNAUTHORIZED, detail="API key expired"
|
|
125
163
|
)
|
|
126
164
|
|
|
127
|
-
# Update last_used_at
|
|
128
165
|
api_key.last_used_at = datetime.now(timezone.utc)
|
|
129
166
|
await _record_success(db, ip, user)
|
|
130
167
|
return user
|
|
@@ -300,6 +300,101 @@ def source_list():
|
|
|
300
300
|
_run(_list())
|
|
301
301
|
|
|
302
302
|
|
|
303
|
+
@source.command("add-user")
|
|
304
|
+
@click.option("--source", "source_name", required=True, help="Source name or UUID.")
|
|
305
|
+
@click.option("--user-id", required=True, type=int, help="User ID to add.")
|
|
306
|
+
def source_add_user(source_name, user_id):
|
|
307
|
+
"""Add a user to a source (grants push access)."""
|
|
308
|
+
|
|
309
|
+
async def _add():
|
|
310
|
+
from uuid import UUID as _UUID
|
|
311
|
+
|
|
312
|
+
await _ensure_tables()
|
|
313
|
+
async with async_session() as db:
|
|
314
|
+
# Resolve source
|
|
315
|
+
try:
|
|
316
|
+
source_uuid = _UUID(source_name)
|
|
317
|
+
result = await db.execute(
|
|
318
|
+
select(Source).where(Source.id == source_uuid)
|
|
319
|
+
)
|
|
320
|
+
except ValueError:
|
|
321
|
+
result = await db.execute(
|
|
322
|
+
select(Source).where(Source.name == source_name)
|
|
323
|
+
)
|
|
324
|
+
src = result.scalar_one_or_none()
|
|
325
|
+
if src is None:
|
|
326
|
+
click.echo(f"Source '{source_name}' not found.", err=True)
|
|
327
|
+
raise SystemExit(1)
|
|
328
|
+
|
|
329
|
+
# Check user exists
|
|
330
|
+
result = await db.execute(select(User).where(User.id == user_id))
|
|
331
|
+
if result.scalar_one_or_none() is None:
|
|
332
|
+
click.echo(f"User {user_id} not found.", err=True)
|
|
333
|
+
raise SystemExit(1)
|
|
334
|
+
|
|
335
|
+
# Check not already a member
|
|
336
|
+
result = await db.execute(
|
|
337
|
+
select(SourceUser).where(
|
|
338
|
+
SourceUser.source_id == src.id, SourceUser.user_id == user_id
|
|
339
|
+
)
|
|
340
|
+
)
|
|
341
|
+
if result.scalar_one_or_none() is not None:
|
|
342
|
+
click.echo(
|
|
343
|
+
f"User {user_id} is already a member of source '{src.name}'."
|
|
344
|
+
)
|
|
345
|
+
return
|
|
346
|
+
|
|
347
|
+
db.add(SourceUser(source_id=src.id, user_id=user_id))
|
|
348
|
+
await db.commit()
|
|
349
|
+
click.echo(f"Added user {user_id} to source '{src.name}' ({src.id}).")
|
|
350
|
+
|
|
351
|
+
_run(_add())
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
@source.command("remove-user")
|
|
355
|
+
@click.option("--source", "source_name", required=True, help="Source name or UUID.")
|
|
356
|
+
@click.option("--user-id", required=True, type=int, help="User ID to remove.")
|
|
357
|
+
def source_remove_user(source_name, user_id):
|
|
358
|
+
"""Remove a user from a source (revokes push access)."""
|
|
359
|
+
|
|
360
|
+
async def _remove():
|
|
361
|
+
from uuid import UUID as _UUID
|
|
362
|
+
|
|
363
|
+
await _ensure_tables()
|
|
364
|
+
async with async_session() as db:
|
|
365
|
+
try:
|
|
366
|
+
source_uuid = _UUID(source_name)
|
|
367
|
+
result = await db.execute(
|
|
368
|
+
select(Source).where(Source.id == source_uuid)
|
|
369
|
+
)
|
|
370
|
+
except ValueError:
|
|
371
|
+
result = await db.execute(
|
|
372
|
+
select(Source).where(Source.name == source_name)
|
|
373
|
+
)
|
|
374
|
+
src = result.scalar_one_or_none()
|
|
375
|
+
if src is None:
|
|
376
|
+
click.echo(f"Source '{source_name}' not found.", err=True)
|
|
377
|
+
raise SystemExit(1)
|
|
378
|
+
|
|
379
|
+
result = await db.execute(
|
|
380
|
+
select(SourceUser).where(
|
|
381
|
+
SourceUser.source_id == src.id, SourceUser.user_id == user_id
|
|
382
|
+
)
|
|
383
|
+
)
|
|
384
|
+
su = result.scalar_one_or_none()
|
|
385
|
+
if su is None:
|
|
386
|
+
click.echo(
|
|
387
|
+
f"User {user_id} is not a member of source '{src.name}'.", err=True
|
|
388
|
+
)
|
|
389
|
+
raise SystemExit(1)
|
|
390
|
+
|
|
391
|
+
await db.delete(su)
|
|
392
|
+
await db.commit()
|
|
393
|
+
click.echo(f"Removed user {user_id} from source '{src.name}'.")
|
|
394
|
+
|
|
395
|
+
_run(_remove())
|
|
396
|
+
|
|
397
|
+
|
|
303
398
|
# ── Quick bootstrap ──────────────────────────────────────────────────
|
|
304
399
|
|
|
305
400
|
|
|
@@ -3,13 +3,16 @@ from pydantic_settings import BaseSettings
|
|
|
3
3
|
|
|
4
4
|
class Settings(BaseSettings):
|
|
5
5
|
database_url: str = "sqlite+aiosqlite:///./warp.db"
|
|
6
|
-
secret_key: str = "change-me-in-production"
|
|
7
6
|
host: str = "127.0.0.1"
|
|
8
7
|
port: int = 8000
|
|
9
8
|
log_level: str = "info"
|
|
10
9
|
cors_origins: list[str] = []
|
|
11
10
|
max_login_attempts: int = 5
|
|
11
|
+
lockout_duration_minutes: int = 30
|
|
12
12
|
reload: bool = False
|
|
13
|
+
debug: bool = False
|
|
14
|
+
# IPs of trusted reverse proxies allowed to set X-Forwarded-For
|
|
15
|
+
trusted_proxies: list[str] = []
|
|
13
16
|
|
|
14
17
|
model_config = {"env_prefix": "WARP_"}
|
|
15
18
|
|
|
@@ -37,7 +37,14 @@ async def lifespan(app: FastAPI):
|
|
|
37
37
|
await engine.dispose()
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
app = FastAPI(
|
|
40
|
+
app = FastAPI(
|
|
41
|
+
title="warp-server",
|
|
42
|
+
version="0.1.0",
|
|
43
|
+
lifespan=lifespan,
|
|
44
|
+
docs_url="/docs" if settings.debug else None,
|
|
45
|
+
redoc_url="/redoc" if settings.debug else None,
|
|
46
|
+
openapi_url="/openapi.json" if settings.debug else None,
|
|
47
|
+
)
|
|
41
48
|
|
|
42
49
|
|
|
43
50
|
@app.middleware("http")
|
|
@@ -55,16 +62,6 @@ async def log_request_duration(request: Request, call_next):
|
|
|
55
62
|
return response
|
|
56
63
|
|
|
57
64
|
|
|
58
|
-
@asynccontextmanager
|
|
59
|
-
async def lifespan(app: FastAPI):
|
|
60
|
-
async with engine.begin() as conn:
|
|
61
|
-
await conn.run_sync(Base.metadata.create_all)
|
|
62
|
-
yield
|
|
63
|
-
await engine.dispose()
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
app = FastAPI(title="warp-server", version="0.1.0", lifespan=lifespan)
|
|
67
|
-
|
|
68
65
|
app.include_router(status.router)
|
|
69
66
|
app.include_router(auth.router)
|
|
70
67
|
app.include_router(users.router)
|
|
@@ -83,8 +80,8 @@ _static_dir = Path(__file__).parent / "static"
|
|
|
83
80
|
app.add_middleware(
|
|
84
81
|
CORSMiddleware,
|
|
85
82
|
allow_origins=settings.cors_origins,
|
|
86
|
-
allow_methods=["
|
|
87
|
-
allow_headers=["
|
|
83
|
+
allow_methods=["GET", "POST", "DELETE", "PATCH", "PUT"],
|
|
84
|
+
allow_headers=["Authorization", "Content-Type"],
|
|
88
85
|
)
|
|
89
86
|
|
|
90
87
|
if _static_dir.is_dir():
|
|
@@ -24,7 +24,11 @@ def _commit_mdl(c: Commit) -> CommitMdl:
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
@router.post("/query", response_model=PaginatedResponse[CommitMdl])
|
|
27
|
-
async def query_commits(
|
|
27
|
+
async def query_commits(
|
|
28
|
+
body: CommitQueryRequest,
|
|
29
|
+
db: AsyncSession = Depends(get_db),
|
|
30
|
+
user: User = Depends(get_current_user),
|
|
31
|
+
):
|
|
28
32
|
query = select(Commit)
|
|
29
33
|
if body.source_id:
|
|
30
34
|
query = query.where(Commit.source_id == body.source_id)
|
|
@@ -45,7 +49,11 @@ async def query_commits(body: CommitQueryRequest, db: AsyncSession = Depends(get
|
|
|
45
49
|
|
|
46
50
|
|
|
47
51
|
@router.get("/{commit_id}", response_model=CommitMdl)
|
|
48
|
-
async def get_commit(
|
|
52
|
+
async def get_commit(
|
|
53
|
+
commit_id: int,
|
|
54
|
+
db: AsyncSession = Depends(get_db),
|
|
55
|
+
user: User = Depends(get_current_user),
|
|
56
|
+
):
|
|
49
57
|
result = await db.execute(select(Commit).where(Commit.id == commit_id))
|
|
50
58
|
commit = result.scalar_one_or_none()
|
|
51
59
|
if commit is None:
|
|
@@ -64,7 +72,6 @@ async def delete_commit(
|
|
|
64
72
|
if commit is None:
|
|
65
73
|
raise HTTPException(status_code=404, detail="Commit not found")
|
|
66
74
|
|
|
67
|
-
# Must be admin or in source users
|
|
68
75
|
if user.role != "Admin":
|
|
69
76
|
su = await db.execute(
|
|
70
77
|
select(SourceUser).where(
|
|
@@ -73,7 +80,7 @@ async def delete_commit(
|
|
|
73
80
|
)
|
|
74
81
|
)
|
|
75
82
|
if su.scalar_one_or_none() is None:
|
|
76
|
-
raise HTTPException(status_code=
|
|
83
|
+
raise HTTPException(status_code=403, detail="Not authorized")
|
|
77
84
|
|
|
78
85
|
await db.delete(commit)
|
|
79
86
|
await db.commit()
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
from collections import defaultdict
|
|
2
2
|
|
|
3
|
-
from fastapi import APIRouter, Depends, HTTPException, Response
|
|
3
|
+
from fastapi import APIRouter, Body, Depends, HTTPException, Response
|
|
4
4
|
from sqlalchemy import func, select
|
|
5
5
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
6
6
|
|
|
7
|
+
from warp_server.auth import get_current_user
|
|
7
8
|
from warp_server.database import get_db
|
|
8
9
|
from warp_server.models import (
|
|
9
10
|
Function,
|
|
@@ -11,6 +12,7 @@ from warp_server.models import (
|
|
|
11
12
|
FunctionConstraint,
|
|
12
13
|
SourceTag,
|
|
13
14
|
Symbol,
|
|
15
|
+
User,
|
|
14
16
|
)
|
|
15
17
|
from warp_server.models import Type as TypeModel
|
|
16
18
|
from warp_server.schemas import (
|
|
@@ -64,7 +66,9 @@ def _build_function_query(body: FunctionQueryRequest):
|
|
|
64
66
|
|
|
65
67
|
@router.post("/query")
|
|
66
68
|
async def query_functions(
|
|
67
|
-
body: FunctionQueryRequest,
|
|
69
|
+
body: FunctionQueryRequest,
|
|
70
|
+
db: AsyncSession = Depends(get_db),
|
|
71
|
+
user: User = Depends(get_current_user),
|
|
68
72
|
):
|
|
69
73
|
query = _build_function_query(body)
|
|
70
74
|
|
|
@@ -92,9 +96,11 @@ async def query_functions(
|
|
|
92
96
|
|
|
93
97
|
@router.post("/query/source")
|
|
94
98
|
async def query_functions_source(
|
|
95
|
-
body: FunctionQueryRequest
|
|
99
|
+
body: FunctionQueryRequest | None = Body(default=None),
|
|
100
|
+
db: AsyncSession = Depends(get_db),
|
|
101
|
+
user: User = Depends(get_current_user),
|
|
96
102
|
):
|
|
97
|
-
query = _build_function_query(body)
|
|
103
|
+
query = _build_function_query(body or FunctionQueryRequest())
|
|
98
104
|
results = await db.execute(query)
|
|
99
105
|
funcs = results.scalars().all()
|
|
100
106
|
mapping: dict[str, list[str]] = defaultdict(list)
|
|
@@ -104,7 +110,11 @@ async def query_functions_source(
|
|
|
104
110
|
|
|
105
111
|
|
|
106
112
|
@router.post("/data")
|
|
107
|
-
async def get_function_datas(
|
|
113
|
+
async def get_function_datas(
|
|
114
|
+
body: DataIdsRequest,
|
|
115
|
+
db: AsyncSession = Depends(get_db),
|
|
116
|
+
user: User = Depends(get_current_user),
|
|
117
|
+
):
|
|
108
118
|
if not body.ids:
|
|
109
119
|
return Response(content=b"", media_type="application/octet-stream")
|
|
110
120
|
results = await db.execute(select(Function).where(Function.id.in_(body.ids)))
|
|
@@ -115,7 +125,11 @@ async def get_function_datas(body: DataIdsRequest, db: AsyncSession = Depends(ge
|
|
|
115
125
|
|
|
116
126
|
|
|
117
127
|
@router.get("/{function_id}", response_model=FunctionMdl)
|
|
118
|
-
async def get_function_by_id(
|
|
128
|
+
async def get_function_by_id(
|
|
129
|
+
function_id: int,
|
|
130
|
+
db: AsyncSession = Depends(get_db),
|
|
131
|
+
user: User = Depends(get_current_user),
|
|
132
|
+
):
|
|
119
133
|
result = await db.execute(select(Function).where(Function.id == function_id))
|
|
120
134
|
f = result.scalar_one_or_none()
|
|
121
135
|
if f is None:
|
|
@@ -124,7 +138,11 @@ async def get_function_by_id(function_id: int, db: AsyncSession = Depends(get_db
|
|
|
124
138
|
|
|
125
139
|
|
|
126
140
|
@router.get("/{function_id}/comments", response_model=list[FunctionCommentMdl])
|
|
127
|
-
async def get_function_comments(
|
|
141
|
+
async def get_function_comments(
|
|
142
|
+
function_id: int,
|
|
143
|
+
db: AsyncSession = Depends(get_db),
|
|
144
|
+
user: User = Depends(get_current_user),
|
|
145
|
+
):
|
|
128
146
|
result = await db.execute(
|
|
129
147
|
select(FunctionComment).where(FunctionComment.function_id == function_id)
|
|
130
148
|
)
|
|
@@ -138,7 +156,9 @@ async def get_function_comments(function_id: int, db: AsyncSession = Depends(get
|
|
|
138
156
|
|
|
139
157
|
@router.get("/{function_id}/constraints", response_model=list[FunctionConstraintMdl])
|
|
140
158
|
async def get_function_constraints(
|
|
141
|
-
function_id: int,
|
|
159
|
+
function_id: int,
|
|
160
|
+
db: AsyncSession = Depends(get_db),
|
|
161
|
+
user: User = Depends(get_current_user),
|
|
142
162
|
):
|
|
143
163
|
result = await db.execute(
|
|
144
164
|
select(FunctionConstraint).where(FunctionConstraint.function_id == function_id)
|
|
@@ -152,7 +172,11 @@ async def get_function_constraints(
|
|
|
152
172
|
|
|
153
173
|
|
|
154
174
|
@router.get("/{function_id}/data")
|
|
155
|
-
async def get_function_data(
|
|
175
|
+
async def get_function_data(
|
|
176
|
+
function_id: int,
|
|
177
|
+
db: AsyncSession = Depends(get_db),
|
|
178
|
+
user: User = Depends(get_current_user),
|
|
179
|
+
):
|
|
156
180
|
result = await db.execute(select(Function).where(Function.id == function_id))
|
|
157
181
|
f = result.scalar_one_or_none()
|
|
158
182
|
if f is None:
|
|
@@ -161,7 +185,11 @@ async def get_function_data(function_id: int, db: AsyncSession = Depends(get_db)
|
|
|
161
185
|
|
|
162
186
|
|
|
163
187
|
@router.get("/{function_id}/symbol", response_model=SymbolMdl)
|
|
164
|
-
async def get_function_symbol(
|
|
188
|
+
async def get_function_symbol(
|
|
189
|
+
function_id: int,
|
|
190
|
+
db: AsyncSession = Depends(get_db),
|
|
191
|
+
user: User = Depends(get_current_user),
|
|
192
|
+
):
|
|
165
193
|
result = await db.execute(select(Function).where(Function.id == function_id))
|
|
166
194
|
f = result.scalar_one_or_none()
|
|
167
195
|
if f is None:
|
|
@@ -180,7 +208,11 @@ async def get_function_symbol(function_id: int, db: AsyncSession = Depends(get_d
|
|
|
180
208
|
|
|
181
209
|
|
|
182
210
|
@router.get("/{function_id}/type", response_model=TypeMdl)
|
|
183
|
-
async def get_function_type(
|
|
211
|
+
async def get_function_type(
|
|
212
|
+
function_id: int,
|
|
213
|
+
db: AsyncSession = Depends(get_db),
|
|
214
|
+
user: User = Depends(get_current_user),
|
|
215
|
+
):
|
|
184
216
|
result = await db.execute(select(Function).where(Function.id == function_id))
|
|
185
217
|
f = result.scalar_one_or_none()
|
|
186
218
|
if f is None:
|
|
@@ -4,10 +4,12 @@ from fastapi import APIRouter, Depends
|
|
|
4
4
|
from sqlalchemy import func, select
|
|
5
5
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
6
6
|
|
|
7
|
+
from warp_server.auth import get_current_user
|
|
7
8
|
from warp_server.database import get_db
|
|
8
9
|
from warp_server.models import Function, Source, Type
|
|
9
10
|
from warp_server.models import Symbol
|
|
10
11
|
from warp_server.schemas import SearchIndexRow, SearchResponse
|
|
12
|
+
from warp_server.models import User
|
|
11
13
|
|
|
12
14
|
router = APIRouter(prefix="/api/v1/search", tags=["search"])
|
|
13
15
|
|
|
@@ -15,6 +17,10 @@ DEFAULT_LIMIT = 20
|
|
|
15
17
|
MAX_LIMIT = 100
|
|
16
18
|
|
|
17
19
|
|
|
20
|
+
def _escape_like(s: str) -> str:
|
|
21
|
+
return s.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
|
|
22
|
+
|
|
23
|
+
|
|
18
24
|
@router.get("", response_model=SearchResponse)
|
|
19
25
|
async def search_handler(
|
|
20
26
|
q: str | None = None,
|
|
@@ -27,6 +33,7 @@ async def search_handler(
|
|
|
27
33
|
source_tags: list[str] | None = None,
|
|
28
34
|
retrieve_data: bool | None = None,
|
|
29
35
|
db: AsyncSession = Depends(get_db),
|
|
36
|
+
user: User = Depends(get_current_user),
|
|
30
37
|
):
|
|
31
38
|
actual_limit = min(limit or DEFAULT_LIMIT, MAX_LIMIT)
|
|
32
39
|
actual_offset = offset or 0
|
|
@@ -34,14 +41,15 @@ async def search_handler(
|
|
|
34
41
|
items: list[SearchIndexRow] = []
|
|
35
42
|
total = 0
|
|
36
43
|
|
|
37
|
-
# Search across sources, functions, and types based on kind filter
|
|
38
44
|
kinds_to_search = [kind] if kind else ["source", "function", "type"]
|
|
39
45
|
|
|
40
46
|
for k in kinds_to_search:
|
|
41
47
|
if k == "source":
|
|
42
48
|
query = select(Source)
|
|
43
49
|
if q:
|
|
44
|
-
query = query.where(
|
|
50
|
+
query = query.where(
|
|
51
|
+
Source.name.ilike(f"%{_escape_like(q)}%", escape="\\")
|
|
52
|
+
)
|
|
45
53
|
if source_id:
|
|
46
54
|
query = query.where(Source.id == source_id)
|
|
47
55
|
|
|
@@ -69,9 +77,8 @@ async def search_handler(
|
|
|
69
77
|
if source_id:
|
|
70
78
|
query = query.where(Function.source_id == source_id)
|
|
71
79
|
if q:
|
|
72
|
-
# Join with symbol to search by name
|
|
73
80
|
query = query.join(Symbol, Symbol.id == Function.symbol_id).where(
|
|
74
|
-
Symbol.name.ilike(f"%{q}%")
|
|
81
|
+
Symbol.name.ilike(f"%{_escape_like(q)}%", escape="\\")
|
|
75
82
|
)
|
|
76
83
|
|
|
77
84
|
count = await db.scalar(select(func.count()).select_from(query.subquery()))
|
|
@@ -96,7 +103,9 @@ async def search_handler(
|
|
|
96
103
|
if source_id:
|
|
97
104
|
query = query.where(Type.source_id == source_id)
|
|
98
105
|
if q:
|
|
99
|
-
query = query.where(
|
|
106
|
+
query = query.where(
|
|
107
|
+
Type.name.ilike(f"%{_escape_like(q)}%", escape="\\")
|
|
108
|
+
)
|
|
100
109
|
|
|
101
110
|
count = await db.scalar(select(func.count()).select_from(query.subquery()))
|
|
102
111
|
total += count
|
|
@@ -22,6 +22,10 @@ router = APIRouter(prefix="/api/v1/sources", tags=["sources"])
|
|
|
22
22
|
DEFAULT_LIMIT = 20
|
|
23
23
|
|
|
24
24
|
|
|
25
|
+
def _escape_like(s: str) -> str:
|
|
26
|
+
return s.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
|
|
27
|
+
|
|
28
|
+
|
|
25
29
|
def _source_mdl(s: Source) -> SourceMdl:
|
|
26
30
|
return SourceMdl(id=s.id, created_at=s.created_at, name=s.name)
|
|
27
31
|
|
|
@@ -36,7 +40,6 @@ async def create_source(
|
|
|
36
40
|
db.add(source)
|
|
37
41
|
await db.flush()
|
|
38
42
|
|
|
39
|
-
# Add requesting user if not in the list
|
|
40
43
|
user_ids = set(body.user_ids)
|
|
41
44
|
user_ids.add(user.id)
|
|
42
45
|
for uid in user_ids:
|
|
@@ -50,13 +53,19 @@ async def create_source(
|
|
|
50
53
|
|
|
51
54
|
|
|
52
55
|
@router.post("/query", response_model=PaginatedResponse[SourceMdl])
|
|
53
|
-
async def query_sources(
|
|
56
|
+
async def query_sources(
|
|
57
|
+
body: SourceQueryRequest,
|
|
58
|
+
db: AsyncSession = Depends(get_db),
|
|
59
|
+
user: User = Depends(get_current_user),
|
|
60
|
+
):
|
|
54
61
|
query = select(Source)
|
|
55
62
|
if body.name:
|
|
56
63
|
if body.exact:
|
|
57
64
|
query = query.where(Source.name == body.name)
|
|
58
65
|
else:
|
|
59
|
-
query = query.where(
|
|
66
|
+
query = query.where(
|
|
67
|
+
Source.name.ilike(f"%{_escape_like(body.name)}%", escape="\\")
|
|
68
|
+
)
|
|
60
69
|
if body.user_id is not None:
|
|
61
70
|
query = query.join(SourceUser).where(SourceUser.user_id == body.user_id)
|
|
62
71
|
|
|
@@ -76,7 +85,11 @@ async def query_sources(body: SourceQueryRequest, db: AsyncSession = Depends(get
|
|
|
76
85
|
|
|
77
86
|
|
|
78
87
|
@router.get("/{source_id}", response_model=SourceMdl)
|
|
79
|
-
async def get_source_by_id(
|
|
88
|
+
async def get_source_by_id(
|
|
89
|
+
source_id: UUID,
|
|
90
|
+
db: AsyncSession = Depends(get_db),
|
|
91
|
+
user: User = Depends(get_current_user),
|
|
92
|
+
):
|
|
80
93
|
result = await db.execute(select(Source).where(Source.id == source_id))
|
|
81
94
|
source = result.scalar_one_or_none()
|
|
82
95
|
if source is None:
|
|
@@ -98,7 +111,7 @@ async def _check_source_access(db: AsyncSession, source_id: UUID, user: User) ->
|
|
|
98
111
|
)
|
|
99
112
|
if su.scalar_one_or_none() is None:
|
|
100
113
|
raise HTTPException(
|
|
101
|
-
status_code=status.
|
|
114
|
+
status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized"
|
|
102
115
|
)
|
|
103
116
|
return source
|
|
104
117
|
|
|
@@ -123,7 +136,6 @@ async def update_source(
|
|
|
123
136
|
db.add(SourceUser(source_id=source_id, user_id=uid))
|
|
124
137
|
|
|
125
138
|
if body.tags is not None:
|
|
126
|
-
# Only admins can set tags.
|
|
127
139
|
if user.role != "Admin":
|
|
128
140
|
raise HTTPException(status_code=403, detail="Only admins can set tags")
|
|
129
141
|
await db.execute(
|
|
@@ -148,7 +160,11 @@ async def delete_source(
|
|
|
148
160
|
|
|
149
161
|
|
|
150
162
|
@router.get("/{source_id}/tags", response_model=list[SourceTagMdl])
|
|
151
|
-
async def list_source_tags(
|
|
163
|
+
async def list_source_tags(
|
|
164
|
+
source_id: UUID,
|
|
165
|
+
db: AsyncSession = Depends(get_db),
|
|
166
|
+
user: User = Depends(get_current_user),
|
|
167
|
+
):
|
|
152
168
|
result = await db.execute(select(SourceTag).where(SourceTag.source_id == source_id))
|
|
153
169
|
return [
|
|
154
170
|
SourceTagMdl(source_id=t.source_id, tag=t.tag) for t in result.scalars().all()
|
|
@@ -156,7 +172,11 @@ async def list_source_tags(source_id: UUID, db: AsyncSession = Depends(get_db)):
|
|
|
156
172
|
|
|
157
173
|
|
|
158
174
|
@router.get("/{source_id}/users", response_model=list[SourceUserMdl])
|
|
159
|
-
async def list_source_users(
|
|
175
|
+
async def list_source_users(
|
|
176
|
+
source_id: UUID,
|
|
177
|
+
db: AsyncSession = Depends(get_db),
|
|
178
|
+
user: User = Depends(get_current_user),
|
|
179
|
+
):
|
|
160
180
|
result = await db.execute(
|
|
161
181
|
select(SourceUser).where(SourceUser.source_id == source_id)
|
|
162
182
|
)
|