warp-server 0.1.0__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.1.0/.github/workflows/ci.yml +39 -0
- warp_server-0.1.0/.gitignore +43 -0
- warp_server-0.1.0/.gitmodules +3 -0
- warp_server-0.1.0/.pre-commit-config.yaml +16 -0
- warp_server-0.1.0/DpNC5NEDJL42WlYjCyyNIGhdNHmLEtJ5h73TqVBYdM8 +1 -0
- warp_server-0.1.0/PKG-INFO +104 -0
- warp_server-0.1.0/README.md +88 -0
- warp_server-0.1.0/err.txt +0 -0
- warp_server-0.1.0/pyproject.toml +46 -0
- warp_server-0.1.0/src/warp_server/__init__.py +0 -0
- warp_server-0.1.0/src/warp_server/auth.py +66 -0
- warp_server-0.1.0/src/warp_server/cli.py +278 -0
- warp_server-0.1.0/src/warp_server/config.py +11 -0
- warp_server-0.1.0/src/warp_server/database.py +13 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/SigBin/BasicBlock.py +54 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/SigBin/BasicBlockGUID.py +46 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/SigBin/Constraint.py +67 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/SigBin/ConstraintGUID.py +46 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/SigBin/Function.py +199 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/SigBin/FunctionComment.py +63 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/SigBin/FunctionGUID.py +46 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/SigBin/FunctionVariable.py +109 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/SigBin/SignatureChunk.py +74 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/SigBin/__init__.py +0 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/SymbolBin/Symbol.py +76 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/SymbolBin/SymbolClass.py +8 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/SymbolBin/SymbolModifiers.py +7 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/SymbolBin/__init__.py +0 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TargetBin/Target.py +63 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TargetBin/__init__.py +0 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/Array.py +80 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/ArrayModifiers.py +6 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/BitOffset.py +26 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/BitShift.py +26 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/BitSize.py +26 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/BitWidth.py +26 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/Boolean.py +54 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/CallingConvention.py +50 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/Character.py +54 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/ComputedType.py +71 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/Enumeration.py +91 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/EnumerationMember.py +63 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/Float.py +54 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/Function.py +128 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/FunctionMember.py +96 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/Integer.py +67 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/LocationClass.py +8 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/MetadataValueType.py +7 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/Pointer.py +118 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/PointerAddressing.py +8 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/Referrer.py +67 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/RegisterLocation.py +50 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/StackLocation.py +54 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/Structure.py +74 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/StructureMember.py +97 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/StructureMemberModifiers.py +7 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/Type.py +191 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/TypeChunk.py +74 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/TypeClass.py +18 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/TypeGUID.py +46 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/TypeMetadata.py +102 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/TypeModifiers.py +7 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/Union.py +74 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/UnionMember.py +67 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/UnsignedBitOffset.py +26 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/Void.py +37 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/TypeBin/__init__.py +0 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/Warp/Chunk.py +97 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/Warp/ChunkHeader.py +110 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/Warp/ChunkType.py +7 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/Warp/CompressionType.py +7 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/Warp/File.py +95 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/Warp/FileHeader.py +54 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/Warp/__init__.py +0 -0
- warp_server-0.1.0/src/warp_server/gen_flatbuffers/__init__.py +2 -0
- warp_server-0.1.0/src/warp_server/main.py +51 -0
- warp_server-0.1.0/src/warp_server/models.py +237 -0
- warp_server-0.1.0/src/warp_server/routers/__init__.py +0 -0
- warp_server-0.1.0/src/warp_server/routers/auth.py +26 -0
- warp_server-0.1.0/src/warp_server/routers/commits.py +80 -0
- warp_server-0.1.0/src/warp_server/routers/files.py +225 -0
- warp_server-0.1.0/src/warp_server/routers/functions.py +201 -0
- warp_server-0.1.0/src/warp_server/routers/search.py +122 -0
- warp_server-0.1.0/src/warp_server/routers/sources.py +166 -0
- warp_server-0.1.0/src/warp_server/routers/status.py +10 -0
- warp_server-0.1.0/src/warp_server/routers/symbols.py +52 -0
- warp_server-0.1.0/src/warp_server/routers/targets.py +40 -0
- warp_server-0.1.0/src/warp_server/routers/types.py +86 -0
- warp_server-0.1.0/src/warp_server/routers/users.py +241 -0
- warp_server-0.1.0/src/warp_server/schemas.py +280 -0
- warp_server-0.1.0/src/warp_server/warp_parser.py +1129 -0
- warp_server-0.1.0/tests/__init__.py +0 -0
- warp_server-0.1.0/tests/conftest.py +74 -0
- warp_server-0.1.0/tests/test_commits.py +83 -0
- warp_server-0.1.0/tests/test_files.py +77 -0
- warp_server-0.1.0/tests/test_functions.py +147 -0
- warp_server-0.1.0/tests/test_search.py +77 -0
- warp_server-0.1.0/tests/test_sources.py +151 -0
- warp_server-0.1.0/tests/test_status.py +9 -0
- warp_server-0.1.0/tests/test_targets.py +41 -0
- warp_server-0.1.0/tests/test_users.py +148 -0
- warp_server-0.1.0/tests/test_warp_parser.py +647 -0
- warp_server-0.1.0/uv.lock +843 -0
- warp_server-0.1.0/warp/.git +1 -0
- warp_server-0.1.0/warp/.github/workflows/CI.yaml +60 -0
- warp_server-0.1.0/warp/.gitignore +66 -0
- warp_server-0.1.0/warp/Cargo.toml +18 -0
- warp_server-0.1.0/warp/LICENSE +13 -0
- warp_server-0.1.0/warp/README.md +182 -0
- warp_server-0.1.0/warp/about.hbs +72 -0
- warp_server-0.1.0/warp/about.toml +17 -0
- warp_server-0.1.0/warp/rust/Cargo.toml +32 -0
- warp_server-0.1.0/warp/rust/benches/chunk.rs +64 -0
- warp_server-0.1.0/warp/rust/benches/type.rs +45 -0
- warp_server-0.1.0/warp/rust/build.rs +22 -0
- warp_server-0.1.0/warp/rust/examples/dumper.rs +43 -0
- warp_server-0.1.0/warp/rust/examples/random.rs +44 -0
- warp_server-0.1.0/warp/rust/examples/type_builder.rs +132 -0
- warp_server-0.1.0/warp/rust/fixtures/random.warp +0 -0
- warp_server-0.1.0/warp/rust/fuzz/.gitignore +4 -0
- warp_server-0.1.0/warp/rust/fuzz/Cargo.toml +36 -0
- warp_server-0.1.0/warp/rust/fuzz/fuzz_targets/file.rs +13 -0
- warp_server-0.1.0/warp/rust/fuzz/fuzz_targets/function.rs +11 -0
- warp_server-0.1.0/warp/rust/fuzz/fuzz_targets/type.rs +10 -0
- warp_server-0.1.0/warp/rust/src/cached_builder.rs +51 -0
- warp_server-0.1.0/warp/rust/src/chunk.rs +317 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/mod.rs +127 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/sig_bin/basic_block_generated.rs +109 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/sig_bin/basic_block_guid_generated.rs +92 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/sig_bin/constraint_generated.rs +126 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/sig_bin/constraint_guid_generated.rs +92 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/sig_bin/function_comment_generated.rs +126 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/sig_bin/function_generated.rs +194 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/sig_bin/function_guid_generated.rs +92 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/sig_bin/function_variable_generated.rs +229 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/sig_bin/signature_chunk_generated.rs +180 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/symbol_bin/symbol_class_generated.rs +100 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/symbol_bin/symbol_generated.rs +142 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/symbol_bin/symbol_modifiers_generated.rs +65 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/target_bin/target_generated.rs +125 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/array_generated.rs +143 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/array_modifiers_generated.rs +64 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/bit_offset_generated.rs +107 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/bit_shift_generated.rs +107 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/bit_size_generated.rs +107 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/bit_width_generated.rs +107 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/boolean_generated.rs +108 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/calling_convention_generated.rs +108 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/character_generated.rs +108 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/computed_type_generated.rs +126 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/enumeration_generated.rs +126 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/enumeration_member_generated.rs +125 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/float_generated.rs +108 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/function_generated.rs +142 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/function_member_generated.rs +214 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/function_member_location_generated.rs +179 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/integer_generated.rs +125 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/location_class_generated.rs +102 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/metadata_value_type_generated.rs +96 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/pointer_addressing_generated.rs +100 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/pointer_generated.rs +176 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/referrer_generated.rs +125 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/register_location_generated.rs +108 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/stack_location_generated.rs +109 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/structure_generated.rs +108 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/structure_member_generated.rs +161 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/structure_member_modifiers_generated.rs +65 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/type_chunk_generated.rs +180 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/type_class_generated.rs +142 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/type_generated.rs +500 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/type_guid_generated.rs +92 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/type_metadata_generated.rs +153 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/type_modifiers_generated.rs +65 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/union_generated.rs +108 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/union_member_generated.rs +126 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/unsigned_bit_offset_generated.rs +107 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/type_bin/void_generated.rs +90 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/warp/chunk_generated.rs +126 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/warp/chunk_header_generated.rs +176 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/warp/chunk_type_generated.rs +96 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/warp/compression_type_generated.rs +96 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/warp/file_generated.rs +211 -0
- warp_server-0.1.0/warp/rust/src/gen_flatbuffers/warp/file_header_generated.rs +108 -0
- warp_server-0.1.0/warp/rust/src/lib.rs +239 -0
- warp_server-0.1.0/warp/rust/src/mock.rs +183 -0
- warp_server-0.1.0/warp/rust/src/signature/basic_block.rs +105 -0
- warp_server-0.1.0/warp/rust/src/signature/chunk.rs +186 -0
- warp_server-0.1.0/warp/rust/src/signature/comment.rs +35 -0
- warp_server-0.1.0/warp/rust/src/signature/constraint.rs +145 -0
- warp_server-0.1.0/warp/rust/src/signature/function.rs +195 -0
- warp_server-0.1.0/warp/rust/src/signature/variable.rs +62 -0
- warp_server-0.1.0/warp/rust/src/signature.rs +6 -0
- warp_server-0.1.0/warp/rust/src/symbol.rs +97 -0
- warp_server-0.1.0/warp/rust/src/target.rs +36 -0
- warp_server-0.1.0/warp/rust/src/type/chunk.rs +155 -0
- warp_server-0.1.0/warp/rust/src/type/class/array.rs +80 -0
- warp_server-0.1.0/warp/rust/src/type/class/boolean.rs +37 -0
- warp_server-0.1.0/warp/rust/src/type/class/character.rs +38 -0
- warp_server-0.1.0/warp/rust/src/type/class/enumeration.rs +97 -0
- warp_server-0.1.0/warp/rust/src/type/class/float.rs +37 -0
- warp_server-0.1.0/warp/rust/src/type/class/function.rs +257 -0
- warp_server-0.1.0/warp/rust/src/type/class/integer.rs +83 -0
- warp_server-0.1.0/warp/rust/src/type/class/pointer.rs +101 -0
- warp_server-0.1.0/warp/rust/src/type/class/referrer.rs +44 -0
- warp_server-0.1.0/warp/rust/src/type/class/structure.rs +139 -0
- warp_server-0.1.0/warp/rust/src/type/class/union.rs +200 -0
- warp_server-0.1.0/warp/rust/src/type/class/void.rs +59 -0
- warp_server-0.1.0/warp/rust/src/type/class.rs +165 -0
- warp_server-0.1.0/warp/rust/src/type/guid.rs +68 -0
- warp_server-0.1.0/warp/rust/src/type.rs +415 -0
- warp_server-0.1.0/warp/rust/tests/chunk.rs +237 -0
- warp_server-0.1.0/warp/rust/tests/file.rs +61 -0
- warp_server-0.1.0/warp/rust/tests/signature.rs +16 -0
- warp_server-0.1.0/warp/rust/tests/snapshots/file__file_format_regression.snap +6047 -0
- warp_server-0.1.0/warp/rust/tests/types.rs +142 -0
- warp_server-0.1.0/warp/signature.fbs +60 -0
- warp_server-0.1.0/warp/symbol.fbs +21 -0
- warp_server-0.1.0/warp/target.fbs +10 -0
- warp_server-0.1.0/warp/type.fbs +249 -0
- warp_server-0.1.0/warp/warp.fbs +51 -0
- warp_server-0.1.0/warp/warp_cli/Cargo.toml +10 -0
- warp_server-0.1.0/warp/warp_cli/src/main.rs +24 -0
- warp_server-0.1.0/warp/warp_cli/src/operation/find.rs +81 -0
- warp_server-0.1.0/warp/warp_cli/src/operation/merge.rs +29 -0
- warp_server-0.1.0/warp/warp_cli/src/operation.rs +24 -0
- warp_server-0.1.0/warp/warp_cli/src/utility.rs +9 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
lint:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
with:
|
|
15
|
+
submodules: true
|
|
16
|
+
- uses: astral-sh/setup-uv@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: "3.12"
|
|
19
|
+
enable-cache: false
|
|
20
|
+
- uses: astral-sh/ruff-action@v3
|
|
21
|
+
|
|
22
|
+
test:
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v4
|
|
26
|
+
with:
|
|
27
|
+
submodules: true
|
|
28
|
+
- uses: astral-sh/setup-uv@v5
|
|
29
|
+
with:
|
|
30
|
+
python-version: "3.12"
|
|
31
|
+
- name: Install flatbuffers compiler
|
|
32
|
+
run: |
|
|
33
|
+
curl -Lo flatc.zip "https://github.com/google/flatbuffers/releases/download/v25.12.19/Linux.flatc.binary.clang%2B%2B-18.zip"
|
|
34
|
+
sudo unzip -o flatc.zip flatc -d /usr/local/bin
|
|
35
|
+
sudo chmod +x /usr/local/bin/flatc
|
|
36
|
+
- name: Generate FlatBuffer bindings
|
|
37
|
+
run: flatc --python -o src/warp_server/gen_flatbuffers warp/*.fbs
|
|
38
|
+
- run: uv sync
|
|
39
|
+
- run: uv run pytest -v
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# macOS
|
|
2
|
+
.DS_Store
|
|
3
|
+
.AppleDouble
|
|
4
|
+
.LSOverride
|
|
5
|
+
._*
|
|
6
|
+
|
|
7
|
+
# Python
|
|
8
|
+
__pycache__/
|
|
9
|
+
*.py[cod]
|
|
10
|
+
*.pyo
|
|
11
|
+
*.pyd
|
|
12
|
+
*.so
|
|
13
|
+
*.egg
|
|
14
|
+
*.egg-info/
|
|
15
|
+
dist/
|
|
16
|
+
build/
|
|
17
|
+
.eggs/
|
|
18
|
+
.mypy_cache/
|
|
19
|
+
.ruff_cache/
|
|
20
|
+
.pytest_cache/
|
|
21
|
+
.coverage
|
|
22
|
+
htmlcov/
|
|
23
|
+
|
|
24
|
+
# uv / virtualenvs
|
|
25
|
+
.venv/
|
|
26
|
+
venv/
|
|
27
|
+
.python-version
|
|
28
|
+
|
|
29
|
+
# Editors
|
|
30
|
+
.vscode/
|
|
31
|
+
.idea/
|
|
32
|
+
*.swp
|
|
33
|
+
*.swo
|
|
34
|
+
*~
|
|
35
|
+
|
|
36
|
+
# Xcode
|
|
37
|
+
*.xcuserstate
|
|
38
|
+
xcuserdata/
|
|
39
|
+
*.xcworkspace/xcuserdata/
|
|
40
|
+
DerivedData/
|
|
41
|
+
|
|
42
|
+
warp-plugin-binja
|
|
43
|
+
warp.db
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: v5.0.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: trailing-whitespace
|
|
6
|
+
- id: end-of-file-fixer
|
|
7
|
+
- id: check-yaml
|
|
8
|
+
- id: check-toml
|
|
9
|
+
- id: check-added-large-files
|
|
10
|
+
|
|
11
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
12
|
+
rev: v0.11.4
|
|
13
|
+
hooks:
|
|
14
|
+
- id: ruff
|
|
15
|
+
args: [--fix]
|
|
16
|
+
- id: ruff-format
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
key
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: warp-server
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: WARP signature server API
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Requires-Dist: aiosqlite>=0.20
|
|
7
|
+
Requires-Dist: click>=8.1
|
|
8
|
+
Requires-Dist: fastapi>=0.115
|
|
9
|
+
Requires-Dist: flatbuffers>=25.12.19
|
|
10
|
+
Requires-Dist: pydantic-settings>=2.0
|
|
11
|
+
Requires-Dist: pydantic>=2.0
|
|
12
|
+
Requires-Dist: python-multipart>=0.0.9
|
|
13
|
+
Requires-Dist: sqlalchemy[asyncio]>=2.0
|
|
14
|
+
Requires-Dist: uvicorn[standard]>=0.30
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# warp-server
|
|
18
|
+
|
|
19
|
+
FastAPI implementation of the WARP signature server API.
|
|
20
|
+
|
|
21
|
+
## Development
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
git submodule update --init # fetch warp/ schemas
|
|
25
|
+
uv sync
|
|
26
|
+
uv run warp-server # start dev server on :8000
|
|
27
|
+
uv run pytest # run tests
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Regenerating FlatBuffer bindings
|
|
31
|
+
|
|
32
|
+
The Python FlatBuffer bindings are generated from the `.fbs` schemas in `warp/`. To regenerate:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
brew install flatbuffers # if not already installed
|
|
36
|
+
flatc --python -o src/warp_server/gen_flatbuffers warp/*.fbs
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Bootstrap: Create a User with an API Key
|
|
40
|
+
|
|
41
|
+
The server uses Bearer token auth via API keys. Use the `warp-ctl` CLI to manage users and keys.
|
|
42
|
+
|
|
43
|
+
### Quick bootstrap (admin + key in one step)
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
uv run warp-ctl bootstrap
|
|
47
|
+
# Created admin user id=1 username=admin
|
|
48
|
+
# API key: <key>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Options: `--email`, `--username` to customize the admin account.
|
|
52
|
+
|
|
53
|
+
### Managing users
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# create a regular user
|
|
57
|
+
uv run warp-ctl user create --email alice@example.com --username alice
|
|
58
|
+
|
|
59
|
+
# create an admin
|
|
60
|
+
uv run warp-ctl user create --email ops@example.com --username ops --role Admin
|
|
61
|
+
|
|
62
|
+
# list all users
|
|
63
|
+
uv run warp-ctl user list
|
|
64
|
+
|
|
65
|
+
# delete a user
|
|
66
|
+
uv run warp-ctl user delete 2
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Managing API keys
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# create a key for user id 1
|
|
73
|
+
uv run warp-ctl key create --user-id 1 --name dev-key
|
|
74
|
+
|
|
75
|
+
# list all keys (or filter by --user-id)
|
|
76
|
+
uv run warp-ctl key list
|
|
77
|
+
uv run warp-ctl key list --user-id 1
|
|
78
|
+
|
|
79
|
+
# revoke a key
|
|
80
|
+
uv run warp-ctl key revoke 3
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Managing sources
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# create a source owned by user 1
|
|
87
|
+
uv run warp-ctl source create --name my-signatures --user-id 1
|
|
88
|
+
|
|
89
|
+
# list sources
|
|
90
|
+
uv run warp-ctl source list
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Using the API key
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# verify auth
|
|
97
|
+
curl -H "Authorization: Bearer <key>" http://localhost:8000/api/v1/users/me
|
|
98
|
+
|
|
99
|
+
# create a source via the API
|
|
100
|
+
curl -X POST http://localhost:8000/api/v1/sources \
|
|
101
|
+
-H "Authorization: Bearer <key>" \
|
|
102
|
+
-H "Content-Type: application/json" \
|
|
103
|
+
-d '{"name": "my-source", "user_ids": []}'
|
|
104
|
+
```
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# warp-server
|
|
2
|
+
|
|
3
|
+
FastAPI implementation of the WARP signature server API.
|
|
4
|
+
|
|
5
|
+
## Development
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
git submodule update --init # fetch warp/ schemas
|
|
9
|
+
uv sync
|
|
10
|
+
uv run warp-server # start dev server on :8000
|
|
11
|
+
uv run pytest # run tests
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
### Regenerating FlatBuffer bindings
|
|
15
|
+
|
|
16
|
+
The Python FlatBuffer bindings are generated from the `.fbs` schemas in `warp/`. To regenerate:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
brew install flatbuffers # if not already installed
|
|
20
|
+
flatc --python -o src/warp_server/gen_flatbuffers warp/*.fbs
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Bootstrap: Create a User with an API Key
|
|
24
|
+
|
|
25
|
+
The server uses Bearer token auth via API keys. Use the `warp-ctl` CLI to manage users and keys.
|
|
26
|
+
|
|
27
|
+
### Quick bootstrap (admin + key in one step)
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
uv run warp-ctl bootstrap
|
|
31
|
+
# Created admin user id=1 username=admin
|
|
32
|
+
# API key: <key>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Options: `--email`, `--username` to customize the admin account.
|
|
36
|
+
|
|
37
|
+
### Managing users
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# create a regular user
|
|
41
|
+
uv run warp-ctl user create --email alice@example.com --username alice
|
|
42
|
+
|
|
43
|
+
# create an admin
|
|
44
|
+
uv run warp-ctl user create --email ops@example.com --username ops --role Admin
|
|
45
|
+
|
|
46
|
+
# list all users
|
|
47
|
+
uv run warp-ctl user list
|
|
48
|
+
|
|
49
|
+
# delete a user
|
|
50
|
+
uv run warp-ctl user delete 2
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Managing API keys
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# create a key for user id 1
|
|
57
|
+
uv run warp-ctl key create --user-id 1 --name dev-key
|
|
58
|
+
|
|
59
|
+
# list all keys (or filter by --user-id)
|
|
60
|
+
uv run warp-ctl key list
|
|
61
|
+
uv run warp-ctl key list --user-id 1
|
|
62
|
+
|
|
63
|
+
# revoke a key
|
|
64
|
+
uv run warp-ctl key revoke 3
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Managing sources
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# create a source owned by user 1
|
|
71
|
+
uv run warp-ctl source create --name my-signatures --user-id 1
|
|
72
|
+
|
|
73
|
+
# list sources
|
|
74
|
+
uv run warp-ctl source list
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Using the API key
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# verify auth
|
|
81
|
+
curl -H "Authorization: Bearer <key>" http://localhost:8000/api/v1/users/me
|
|
82
|
+
|
|
83
|
+
# create a source via the API
|
|
84
|
+
curl -X POST http://localhost:8000/api/v1/sources \
|
|
85
|
+
-H "Authorization: Bearer <key>" \
|
|
86
|
+
-H "Content-Type: application/json" \
|
|
87
|
+
-d '{"name": "my-source", "user_ids": []}'
|
|
88
|
+
```
|
|
File without changes
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "warp-server"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "WARP signature server API"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"fastapi>=0.115",
|
|
9
|
+
"uvicorn[standard]>=0.30",
|
|
10
|
+
"sqlalchemy[asyncio]>=2.0",
|
|
11
|
+
"aiosqlite>=0.20",
|
|
12
|
+
"pydantic>=2.0",
|
|
13
|
+
"pydantic-settings>=2.0",
|
|
14
|
+
"python-multipart>=0.0.9",
|
|
15
|
+
"click>=8.1",
|
|
16
|
+
"flatbuffers>=25.12.19",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.scripts]
|
|
20
|
+
warp-server = "warp_server.main:run"
|
|
21
|
+
warp-ctl = "warp_server.cli:app"
|
|
22
|
+
|
|
23
|
+
[dependency-groups]
|
|
24
|
+
dev = [
|
|
25
|
+
"pytest>=8.0",
|
|
26
|
+
"pytest-asyncio>=0.24",
|
|
27
|
+
"httpx>=0.27",
|
|
28
|
+
"pre-commit>=4.5.1",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[build-system]
|
|
32
|
+
requires = ["hatchling"]
|
|
33
|
+
build-backend = "hatchling.build"
|
|
34
|
+
|
|
35
|
+
[tool.hatch.build.targets.wheel]
|
|
36
|
+
packages = ["src/warp_server"]
|
|
37
|
+
|
|
38
|
+
[tool.pytest.ini_options]
|
|
39
|
+
asyncio_mode = "auto"
|
|
40
|
+
testpaths = ["tests"]
|
|
41
|
+
|
|
42
|
+
[tool.ruff]
|
|
43
|
+
target-version = "py312"
|
|
44
|
+
|
|
45
|
+
[tool.ruff.lint.per-file-ignores]
|
|
46
|
+
"src/warp_server/warp_parser.py" = ["E402"]
|
|
File without changes
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from datetime import datetime, timezone
|
|
2
|
+
|
|
3
|
+
from fastapi import Depends, HTTPException, Security, status
|
|
4
|
+
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
5
|
+
from sqlalchemy import select
|
|
6
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
7
|
+
|
|
8
|
+
from warp_server.database import get_db
|
|
9
|
+
from warp_server.models import ApiKey, User
|
|
10
|
+
|
|
11
|
+
security = HTTPBearer(auto_error=False)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
async def get_current_user(
|
|
15
|
+
credentials: HTTPAuthorizationCredentials | None = Security(security),
|
|
16
|
+
db: AsyncSession = Depends(get_db),
|
|
17
|
+
) -> User:
|
|
18
|
+
if credentials is None:
|
|
19
|
+
raise HTTPException(
|
|
20
|
+
status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
token = credentials.credentials
|
|
24
|
+
result = await db.execute(select(ApiKey).where(ApiKey.key == token))
|
|
25
|
+
api_key = result.scalar_one_or_none()
|
|
26
|
+
if api_key is None:
|
|
27
|
+
raise HTTPException(
|
|
28
|
+
status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid API key"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
if api_key.expires_at and api_key.expires_at < datetime.now(timezone.utc):
|
|
32
|
+
raise HTTPException(
|
|
33
|
+
status_code=status.HTTP_401_UNAUTHORIZED, detail="API key expired"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Update last_used_at
|
|
37
|
+
api_key.last_used_at = datetime.now(timezone.utc)
|
|
38
|
+
await db.commit()
|
|
39
|
+
|
|
40
|
+
result = await db.execute(select(User).where(User.id == api_key.user_id))
|
|
41
|
+
user = result.scalar_one_or_none()
|
|
42
|
+
if user is None:
|
|
43
|
+
raise HTTPException(
|
|
44
|
+
status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found"
|
|
45
|
+
)
|
|
46
|
+
return user
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
async def get_optional_user(
|
|
50
|
+
credentials: HTTPAuthorizationCredentials | None = Security(security),
|
|
51
|
+
db: AsyncSession = Depends(get_db),
|
|
52
|
+
) -> User | None:
|
|
53
|
+
if credentials is None:
|
|
54
|
+
return None
|
|
55
|
+
try:
|
|
56
|
+
return await get_current_user(credentials, db)
|
|
57
|
+
except HTTPException:
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def require_admin(user: User = Depends(get_current_user)) -> User:
|
|
62
|
+
if user.role != "Admin":
|
|
63
|
+
raise HTTPException(
|
|
64
|
+
status_code=status.HTTP_403_FORBIDDEN, detail="Admin required"
|
|
65
|
+
)
|
|
66
|
+
return user
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import secrets
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from sqlalchemy import select
|
|
7
|
+
|
|
8
|
+
from warp_server.database import async_session, engine
|
|
9
|
+
from warp_server.models import ApiKey, Base, Source, SourceTag, SourceUser, User
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.group(name="warp-ctl", help="WARP server management CLI.")
|
|
13
|
+
def app():
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@app.group(help="Manage users.")
|
|
18
|
+
def user():
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@app.group(help="Manage API keys.")
|
|
23
|
+
def key():
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@app.group(help="Manage sources.")
|
|
28
|
+
def source():
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _run(coro):
|
|
33
|
+
return asyncio.run(coro)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
async def _ensure_tables():
|
|
37
|
+
async with engine.begin() as conn:
|
|
38
|
+
await conn.run_sync(Base.metadata.create_all)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# ── Users ────────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@user.command("create")
|
|
45
|
+
@click.option("--email", required=True, help="User email address.")
|
|
46
|
+
@click.option(
|
|
47
|
+
"--username", default=None, help="Username (defaults to email local part)."
|
|
48
|
+
)
|
|
49
|
+
@click.option("--role", default="User", help="Role: User or Admin.")
|
|
50
|
+
def user_create(email, username, role):
|
|
51
|
+
"""Create a new user."""
|
|
52
|
+
|
|
53
|
+
async def _create():
|
|
54
|
+
await _ensure_tables()
|
|
55
|
+
async with async_session() as db:
|
|
56
|
+
name = username or email.split("@")[0]
|
|
57
|
+
u = User(email=email, username=name, role=role)
|
|
58
|
+
db.add(u)
|
|
59
|
+
await db.flush()
|
|
60
|
+
raw_key = secrets.token_urlsafe(32)
|
|
61
|
+
db.add(ApiKey(user_id=u.id, key=raw_key, name="default"))
|
|
62
|
+
await db.commit()
|
|
63
|
+
await db.refresh(u)
|
|
64
|
+
click.echo(f"Created user id={u.id} username={u.username} role={u.role}")
|
|
65
|
+
click.echo(f"API key: {raw_key}")
|
|
66
|
+
click.echo("Store this key securely — it cannot be retrieved later.")
|
|
67
|
+
|
|
68
|
+
_run(_create())
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@user.command("list")
|
|
72
|
+
def user_list():
|
|
73
|
+
"""List all users."""
|
|
74
|
+
|
|
75
|
+
async def _list():
|
|
76
|
+
await _ensure_tables()
|
|
77
|
+
async with async_session() as db:
|
|
78
|
+
result = await db.execute(select(User).order_by(User.id))
|
|
79
|
+
users = result.scalars().all()
|
|
80
|
+
if not users:
|
|
81
|
+
click.echo("No users found.")
|
|
82
|
+
return
|
|
83
|
+
click.echo(
|
|
84
|
+
f"{'ID':<6} {'Username':<20} {'Email':<30} {'Role':<10} {'Created'}"
|
|
85
|
+
)
|
|
86
|
+
click.echo("-" * 80)
|
|
87
|
+
for u in users:
|
|
88
|
+
click.echo(
|
|
89
|
+
f"{u.id:<6} {u.username:<20} {u.email:<30} {u.role:<10} {u.created_at}"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
_run(_list())
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@user.command("delete")
|
|
96
|
+
@click.argument("user_id", type=int)
|
|
97
|
+
def user_delete(user_id):
|
|
98
|
+
"""Delete a user by ID."""
|
|
99
|
+
|
|
100
|
+
async def _delete():
|
|
101
|
+
await _ensure_tables()
|
|
102
|
+
async with async_session() as db:
|
|
103
|
+
result = await db.execute(select(User).where(User.id == user_id))
|
|
104
|
+
u = result.scalar_one_or_none()
|
|
105
|
+
if u is None:
|
|
106
|
+
click.echo(f"User {user_id} not found.", err=True)
|
|
107
|
+
raise SystemExit(1)
|
|
108
|
+
await db.delete(u)
|
|
109
|
+
await db.commit()
|
|
110
|
+
click.echo(f"Deleted user id={user_id} username={u.username}")
|
|
111
|
+
|
|
112
|
+
_run(_delete())
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# ── API Keys ─────────────────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@key.command("create")
|
|
119
|
+
@click.option(
|
|
120
|
+
"--user-id", required=True, type=int, help="User ID to create the key for."
|
|
121
|
+
)
|
|
122
|
+
@click.option("--name", default="default", help="Key name/label.")
|
|
123
|
+
def key_create(user_id, name):
|
|
124
|
+
"""Create an API key for a user."""
|
|
125
|
+
|
|
126
|
+
async def _create():
|
|
127
|
+
await _ensure_tables()
|
|
128
|
+
async with async_session() as db:
|
|
129
|
+
result = await db.execute(select(User).where(User.id == user_id))
|
|
130
|
+
u = result.scalar_one_or_none()
|
|
131
|
+
if u is None:
|
|
132
|
+
click.echo(f"User {user_id} not found.", err=True)
|
|
133
|
+
raise SystemExit(1)
|
|
134
|
+
raw_key = secrets.token_urlsafe(32)
|
|
135
|
+
api_key = ApiKey(
|
|
136
|
+
user_id=u.id,
|
|
137
|
+
key=raw_key,
|
|
138
|
+
name=name,
|
|
139
|
+
created_at=datetime.now(timezone.utc),
|
|
140
|
+
)
|
|
141
|
+
db.add(api_key)
|
|
142
|
+
await db.commit()
|
|
143
|
+
await db.refresh(api_key)
|
|
144
|
+
click.echo(f"Created API key for user={u.username}")
|
|
145
|
+
click.echo(f"Key: {raw_key}")
|
|
146
|
+
click.echo("Store this key securely — it cannot be retrieved later.")
|
|
147
|
+
|
|
148
|
+
_run(_create())
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@key.command("list")
|
|
152
|
+
@click.option("--user-id", default=None, type=int, help="Filter keys by user ID.")
|
|
153
|
+
def key_list(user_id):
|
|
154
|
+
"""List API keys."""
|
|
155
|
+
|
|
156
|
+
async def _list():
|
|
157
|
+
await _ensure_tables()
|
|
158
|
+
async with async_session() as db:
|
|
159
|
+
query = select(ApiKey).order_by(ApiKey.id)
|
|
160
|
+
if user_id is not None:
|
|
161
|
+
query = query.where(ApiKey.user_id == user_id)
|
|
162
|
+
result = await db.execute(query)
|
|
163
|
+
keys = result.scalars().all()
|
|
164
|
+
if not keys:
|
|
165
|
+
click.echo("No API keys found.")
|
|
166
|
+
return
|
|
167
|
+
click.echo(
|
|
168
|
+
f"{'ID':<6} {'User ID':<8} {'Name':<16} {'Key (prefix)':<14} {'Created':<26} {'Last Used'}"
|
|
169
|
+
)
|
|
170
|
+
click.echo("-" * 90)
|
|
171
|
+
for k in keys:
|
|
172
|
+
click.echo(
|
|
173
|
+
f"{k.id:<6} {k.user_id:<8} {k.name:<16} {k.key[:12] + '…':<14} {str(k.created_at):<26} {k.last_used_at or '—'}"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
_run(_list())
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@key.command("revoke")
|
|
180
|
+
@click.argument("key_id", type=int)
|
|
181
|
+
def key_revoke(key_id):
|
|
182
|
+
"""Revoke (delete) an API key."""
|
|
183
|
+
|
|
184
|
+
async def _revoke():
|
|
185
|
+
await _ensure_tables()
|
|
186
|
+
async with async_session() as db:
|
|
187
|
+
result = await db.execute(select(ApiKey).where(ApiKey.id == key_id))
|
|
188
|
+
api_key = result.scalar_one_or_none()
|
|
189
|
+
if api_key is None:
|
|
190
|
+
click.echo(f"API key {key_id} not found.", err=True)
|
|
191
|
+
raise SystemExit(1)
|
|
192
|
+
await db.delete(api_key)
|
|
193
|
+
await db.commit()
|
|
194
|
+
click.echo(f"Revoked API key id={key_id}")
|
|
195
|
+
|
|
196
|
+
_run(_revoke())
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
# ── Sources ──────────────────────────────────────────────────────────
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@source.command("create")
|
|
203
|
+
@click.option("--name", required=True, help="Source name.")
|
|
204
|
+
@click.option("--user-id", default=None, type=int, help="User ID to add as owner.")
|
|
205
|
+
def source_create(name, user_id):
|
|
206
|
+
"""Create a new source."""
|
|
207
|
+
|
|
208
|
+
async def _create():
|
|
209
|
+
await _ensure_tables()
|
|
210
|
+
async with async_session() as db:
|
|
211
|
+
src = Source(name=name)
|
|
212
|
+
db.add(src)
|
|
213
|
+
await db.flush()
|
|
214
|
+
if user_id is not None:
|
|
215
|
+
db.add(SourceUser(source_id=src.id, user_id=user_id))
|
|
216
|
+
db.add(SourceTag(source_id=src.id, tag="trusted"))
|
|
217
|
+
await db.commit()
|
|
218
|
+
await db.refresh(src)
|
|
219
|
+
click.echo(f"Created source id={src.id} name={src.name}")
|
|
220
|
+
|
|
221
|
+
_run(_create())
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
@source.command("list")
|
|
225
|
+
def source_list():
|
|
226
|
+
"""List all sources."""
|
|
227
|
+
|
|
228
|
+
async def _list():
|
|
229
|
+
await _ensure_tables()
|
|
230
|
+
async with async_session() as db:
|
|
231
|
+
result = await db.execute(select(Source).order_by(Source.name))
|
|
232
|
+
sources = result.scalars().all()
|
|
233
|
+
if not sources:
|
|
234
|
+
click.echo("No sources found.")
|
|
235
|
+
return
|
|
236
|
+
click.echo(f"{'ID':<6} {'Name':<30} {'Created'}")
|
|
237
|
+
click.echo("-" * 60)
|
|
238
|
+
for s in sources:
|
|
239
|
+
click.echo(f"{s.id:<6} {s.name:<30} {s.created_at}")
|
|
240
|
+
|
|
241
|
+
_run(_list())
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
# ── Quick bootstrap ──────────────────────────────────────────────────
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
@app.command("bootstrap")
|
|
248
|
+
@click.option("--email", default="admin@localhost", help="Admin email.")
|
|
249
|
+
@click.option("--username", default="admin", help="Admin username.")
|
|
250
|
+
def bootstrap(email, username):
|
|
251
|
+
"""Create an admin user with an API key in one step."""
|
|
252
|
+
|
|
253
|
+
async def _bootstrap():
|
|
254
|
+
await _ensure_tables()
|
|
255
|
+
async with async_session() as db:
|
|
256
|
+
result = await db.execute(select(User).where(User.username == username))
|
|
257
|
+
existing = result.scalar_one_or_none()
|
|
258
|
+
if existing is not None:
|
|
259
|
+
click.echo(
|
|
260
|
+
f"User '{username}' already exists (id={existing.id}), skipping creation."
|
|
261
|
+
)
|
|
262
|
+
return
|
|
263
|
+
u = User(email=email, username=username, role="Admin")
|
|
264
|
+
db.add(u)
|
|
265
|
+
await db.flush()
|
|
266
|
+
raw_key = secrets.token_urlsafe(32)
|
|
267
|
+
db.add(ApiKey(user_id=u.id, key=raw_key, name="bootstrap"))
|
|
268
|
+
await db.commit()
|
|
269
|
+
await db.refresh(u)
|
|
270
|
+
click.echo(f"Created admin user id={u.id} username={u.username}")
|
|
271
|
+
click.echo(f"API key: {raw_key}")
|
|
272
|
+
click.echo("Store this key securely — it cannot be retrieved later.")
|
|
273
|
+
|
|
274
|
+
_run(_bootstrap())
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
if __name__ == "__main__":
|
|
278
|
+
app()
|