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.
Files changed (228) hide show
  1. {warp_server-0.2.0 → warp_server-0.2.3}/PKG-INFO +20 -1
  2. {warp_server-0.2.0 → warp_server-0.2.3}/README.md +19 -0
  3. {warp_server-0.2.0 → warp_server-0.2.3}/pyproject.toml +1 -1
  4. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/auth.py +49 -12
  5. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/cli.py +95 -0
  6. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/config.py +4 -1
  7. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/main.py +10 -13
  8. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/routers/commits.py +11 -4
  9. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/routers/functions.py +43 -11
  10. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/routers/search.py +14 -5
  11. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/routers/sources.py +28 -8
  12. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/routers/symbols.py +18 -4
  13. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/routers/targets.py +8 -2
  14. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/schemas.py +1 -1
  15. {warp_server-0.2.0 → warp_server-0.2.3}/tests/test_commits.py +21 -8
  16. {warp_server-0.2.0 → warp_server-0.2.3}/tests/test_functions.py +49 -18
  17. {warp_server-0.2.0 → warp_server-0.2.3}/tests/test_search.py +17 -8
  18. {warp_server-0.2.0 → warp_server-0.2.3}/tests/test_sources.py +23 -9
  19. {warp_server-0.2.0 → warp_server-0.2.3}/tests/test_targets.py +9 -5
  20. {warp_server-0.2.0 → warp_server-0.2.3}/tests/test_warp_parser.py +4 -1
  21. {warp_server-0.2.0 → warp_server-0.2.3}/uv.lock +1 -1
  22. {warp_server-0.2.0 → warp_server-0.2.3}/.github/workflows/ci.yml +0 -0
  23. {warp_server-0.2.0 → warp_server-0.2.3}/.gitignore +0 -0
  24. {warp_server-0.2.0 → warp_server-0.2.3}/.gitmodules +0 -0
  25. {warp_server-0.2.0 → warp_server-0.2.3}/.pre-commit-config.yaml +0 -0
  26. {warp_server-0.2.0 → warp_server-0.2.3}/Dockerfile +0 -0
  27. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/__init__.py +0 -0
  28. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/database.py +0 -0
  29. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/SigBin/BasicBlock.py +0 -0
  30. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/SigBin/BasicBlockGUID.py +0 -0
  31. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/SigBin/Constraint.py +0 -0
  32. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/SigBin/ConstraintGUID.py +0 -0
  33. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/SigBin/Function.py +0 -0
  34. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/SigBin/FunctionComment.py +0 -0
  35. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/SigBin/FunctionGUID.py +0 -0
  36. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/SigBin/FunctionVariable.py +0 -0
  37. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/SigBin/SignatureChunk.py +0 -0
  38. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/SigBin/__init__.py +0 -0
  39. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/SymbolBin/Symbol.py +0 -0
  40. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/SymbolBin/SymbolClass.py +0 -0
  41. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/SymbolBin/SymbolModifiers.py +0 -0
  42. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/SymbolBin/__init__.py +0 -0
  43. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TargetBin/Target.py +0 -0
  44. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TargetBin/__init__.py +0 -0
  45. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/Array.py +0 -0
  46. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/ArrayModifiers.py +0 -0
  47. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/BitOffset.py +0 -0
  48. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/BitShift.py +0 -0
  49. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/BitSize.py +0 -0
  50. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/BitWidth.py +0 -0
  51. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/Boolean.py +0 -0
  52. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/CallingConvention.py +0 -0
  53. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/Character.py +0 -0
  54. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/ComputedType.py +0 -0
  55. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/Enumeration.py +0 -0
  56. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/EnumerationMember.py +0 -0
  57. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/Float.py +0 -0
  58. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/Function.py +0 -0
  59. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/FunctionMember.py +0 -0
  60. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/Integer.py +0 -0
  61. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/LocationClass.py +0 -0
  62. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/MetadataValueType.py +0 -0
  63. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/Pointer.py +0 -0
  64. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/PointerAddressing.py +0 -0
  65. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/Referrer.py +0 -0
  66. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/RegisterLocation.py +0 -0
  67. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/StackLocation.py +0 -0
  68. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/Structure.py +0 -0
  69. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/StructureMember.py +0 -0
  70. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/StructureMemberModifiers.py +0 -0
  71. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/Type.py +0 -0
  72. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/TypeChunk.py +0 -0
  73. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/TypeClass.py +0 -0
  74. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/TypeGUID.py +0 -0
  75. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/TypeMetadata.py +0 -0
  76. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/TypeModifiers.py +0 -0
  77. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/Union.py +0 -0
  78. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/UnionMember.py +0 -0
  79. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/UnsignedBitOffset.py +0 -0
  80. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/Void.py +0 -0
  81. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/TypeBin/__init__.py +0 -0
  82. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/Warp/Chunk.py +0 -0
  83. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/Warp/ChunkHeader.py +0 -0
  84. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/Warp/ChunkType.py +0 -0
  85. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/Warp/CompressionType.py +0 -0
  86. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/Warp/File.py +0 -0
  87. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/Warp/FileHeader.py +0 -0
  88. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/Warp/__init__.py +0 -0
  89. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/gen_flatbuffers/__init__.py +0 -0
  90. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/models.py +0 -0
  91. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/routers/__init__.py +0 -0
  92. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/routers/auth.py +0 -0
  93. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/routers/files.py +0 -0
  94. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/routers/status.py +0 -0
  95. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/routers/types.py +0 -0
  96. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/routers/users.py +0 -0
  97. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/static/index.html +0 -0
  98. {warp_server-0.2.0 → warp_server-0.2.3}/src/warp_server/warp_parser.py +0 -0
  99. {warp_server-0.2.0 → warp_server-0.2.3}/tests/__init__.py +0 -0
  100. {warp_server-0.2.0 → warp_server-0.2.3}/tests/conftest.py +0 -0
  101. {warp_server-0.2.0 → warp_server-0.2.3}/tests/test_files.py +0 -0
  102. {warp_server-0.2.0 → warp_server-0.2.3}/tests/test_ingest.py +0 -0
  103. {warp_server-0.2.0 → warp_server-0.2.3}/tests/test_lockout.py +0 -0
  104. {warp_server-0.2.0 → warp_server-0.2.3}/tests/test_status.py +0 -0
  105. {warp_server-0.2.0 → warp_server-0.2.3}/tests/test_users.py +0 -0
  106. {warp_server-0.2.0 → warp_server-0.2.3}/warp/.git +0 -0
  107. {warp_server-0.2.0 → warp_server-0.2.3}/warp/.github/workflows/CI.yaml +0 -0
  108. {warp_server-0.2.0 → warp_server-0.2.3}/warp/.gitignore +0 -0
  109. {warp_server-0.2.0 → warp_server-0.2.3}/warp/Cargo.toml +0 -0
  110. {warp_server-0.2.0 → warp_server-0.2.3}/warp/LICENSE +0 -0
  111. {warp_server-0.2.0 → warp_server-0.2.3}/warp/README.md +0 -0
  112. {warp_server-0.2.0 → warp_server-0.2.3}/warp/about.hbs +0 -0
  113. {warp_server-0.2.0 → warp_server-0.2.3}/warp/about.toml +0 -0
  114. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/Cargo.toml +0 -0
  115. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/benches/chunk.rs +0 -0
  116. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/benches/type.rs +0 -0
  117. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/build.rs +0 -0
  118. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/examples/dumper.rs +0 -0
  119. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/examples/random.rs +0 -0
  120. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/examples/type_builder.rs +0 -0
  121. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/fixtures/random.warp +0 -0
  122. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/fuzz/.gitignore +0 -0
  123. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/fuzz/Cargo.toml +0 -0
  124. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/fuzz/fuzz_targets/file.rs +0 -0
  125. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/fuzz/fuzz_targets/function.rs +0 -0
  126. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/fuzz/fuzz_targets/type.rs +0 -0
  127. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/cached_builder.rs +0 -0
  128. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/chunk.rs +0 -0
  129. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/mod.rs +0 -0
  130. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/sig_bin/basic_block_generated.rs +0 -0
  131. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/sig_bin/basic_block_guid_generated.rs +0 -0
  132. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/sig_bin/constraint_generated.rs +0 -0
  133. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/sig_bin/constraint_guid_generated.rs +0 -0
  134. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/sig_bin/function_comment_generated.rs +0 -0
  135. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/sig_bin/function_generated.rs +0 -0
  136. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/sig_bin/function_guid_generated.rs +0 -0
  137. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/sig_bin/function_variable_generated.rs +0 -0
  138. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/sig_bin/signature_chunk_generated.rs +0 -0
  139. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/symbol_bin/symbol_class_generated.rs +0 -0
  140. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/symbol_bin/symbol_generated.rs +0 -0
  141. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/symbol_bin/symbol_modifiers_generated.rs +0 -0
  142. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/target_bin/target_generated.rs +0 -0
  143. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/array_generated.rs +0 -0
  144. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/array_modifiers_generated.rs +0 -0
  145. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/bit_offset_generated.rs +0 -0
  146. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/bit_shift_generated.rs +0 -0
  147. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/bit_size_generated.rs +0 -0
  148. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/bit_width_generated.rs +0 -0
  149. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/boolean_generated.rs +0 -0
  150. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/calling_convention_generated.rs +0 -0
  151. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/character_generated.rs +0 -0
  152. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/computed_type_generated.rs +0 -0
  153. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/enumeration_generated.rs +0 -0
  154. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/enumeration_member_generated.rs +0 -0
  155. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/float_generated.rs +0 -0
  156. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/function_generated.rs +0 -0
  157. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/function_member_generated.rs +0 -0
  158. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/function_member_location_generated.rs +0 -0
  159. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/integer_generated.rs +0 -0
  160. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/location_class_generated.rs +0 -0
  161. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/metadata_value_type_generated.rs +0 -0
  162. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/pointer_addressing_generated.rs +0 -0
  163. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/pointer_generated.rs +0 -0
  164. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/referrer_generated.rs +0 -0
  165. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/register_location_generated.rs +0 -0
  166. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/stack_location_generated.rs +0 -0
  167. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/structure_generated.rs +0 -0
  168. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/structure_member_generated.rs +0 -0
  169. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/structure_member_modifiers_generated.rs +0 -0
  170. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/type_chunk_generated.rs +0 -0
  171. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/type_class_generated.rs +0 -0
  172. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/type_generated.rs +0 -0
  173. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/type_guid_generated.rs +0 -0
  174. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/type_metadata_generated.rs +0 -0
  175. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/type_modifiers_generated.rs +0 -0
  176. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/union_generated.rs +0 -0
  177. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/union_member_generated.rs +0 -0
  178. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/unsigned_bit_offset_generated.rs +0 -0
  179. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/type_bin/void_generated.rs +0 -0
  180. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/warp/chunk_generated.rs +0 -0
  181. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/warp/chunk_header_generated.rs +0 -0
  182. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/warp/chunk_type_generated.rs +0 -0
  183. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/warp/compression_type_generated.rs +0 -0
  184. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/warp/file_generated.rs +0 -0
  185. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/gen_flatbuffers/warp/file_header_generated.rs +0 -0
  186. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/lib.rs +0 -0
  187. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/mock.rs +0 -0
  188. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/signature/basic_block.rs +0 -0
  189. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/signature/chunk.rs +0 -0
  190. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/signature/comment.rs +0 -0
  191. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/signature/constraint.rs +0 -0
  192. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/signature/function.rs +0 -0
  193. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/signature/variable.rs +0 -0
  194. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/signature.rs +0 -0
  195. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/symbol.rs +0 -0
  196. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/target.rs +0 -0
  197. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/type/chunk.rs +0 -0
  198. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/type/class/array.rs +0 -0
  199. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/type/class/boolean.rs +0 -0
  200. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/type/class/character.rs +0 -0
  201. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/type/class/enumeration.rs +0 -0
  202. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/type/class/float.rs +0 -0
  203. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/type/class/function.rs +0 -0
  204. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/type/class/integer.rs +0 -0
  205. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/type/class/pointer.rs +0 -0
  206. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/type/class/referrer.rs +0 -0
  207. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/type/class/structure.rs +0 -0
  208. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/type/class/union.rs +0 -0
  209. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/type/class/void.rs +0 -0
  210. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/type/class.rs +0 -0
  211. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/type/guid.rs +0 -0
  212. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/src/type.rs +0 -0
  213. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/tests/chunk.rs +0 -0
  214. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/tests/file.rs +0 -0
  215. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/tests/signature.rs +0 -0
  216. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/tests/snapshots/file__file_format_regression.snap +0 -0
  217. {warp_server-0.2.0 → warp_server-0.2.3}/warp/rust/tests/types.rs +0 -0
  218. {warp_server-0.2.0 → warp_server-0.2.3}/warp/signature.fbs +0 -0
  219. {warp_server-0.2.0 → warp_server-0.2.3}/warp/symbol.fbs +0 -0
  220. {warp_server-0.2.0 → warp_server-0.2.3}/warp/target.fbs +0 -0
  221. {warp_server-0.2.0 → warp_server-0.2.3}/warp/type.fbs +0 -0
  222. {warp_server-0.2.0 → warp_server-0.2.3}/warp/warp.fbs +0 -0
  223. {warp_server-0.2.0 → warp_server-0.2.3}/warp/warp_cli/Cargo.toml +0 -0
  224. {warp_server-0.2.0 → warp_server-0.2.3}/warp/warp_cli/src/main.rs +0 -0
  225. {warp_server-0.2.0 → warp_server-0.2.3}/warp/warp_cli/src/operation/find.rs +0 -0
  226. {warp_server-0.2.0 → warp_server-0.2.3}/warp/warp_cli/src/operation/merge.rs +0 -0
  227. {warp_server-0.2.0 → warp_server-0.2.3}/warp/warp_cli/src/operation.rs +0 -0
  228. {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.0
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,6 +1,6 @@
1
1
  [project]
2
2
  name = "warp-server"
3
- version = "0.2.0"
3
+ version = "0.2.3"
4
4
  description = "WARP signature server API"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -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, respecting X-Forwarded-For behind a reverse proxy."""
23
- forwarded = request.headers.get("x-forwarded-for")
24
- if forwarded:
25
- return forwarded.split(",")[0].strip()
26
- return request.client.host if request.client else "unknown"
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.locked:
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.locked:
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(title="warp-server", version="0.1.0", lifespan=lifespan)
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(body: CommitQueryRequest, db: AsyncSession = Depends(get_db)):
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(commit_id: int, db: AsyncSession = Depends(get_db)):
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=401, detail="Not authorized")
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, db: AsyncSession = Depends(get_db)
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, db: AsyncSession = Depends(get_db)
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(body: DataIdsRequest, db: AsyncSession = Depends(get_db)):
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(function_id: int, db: AsyncSession = Depends(get_db)):
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(function_id: int, db: AsyncSession = Depends(get_db)):
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, db: AsyncSession = Depends(get_db)
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(function_id: int, db: AsyncSession = Depends(get_db)):
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(function_id: int, db: AsyncSession = Depends(get_db)):
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(function_id: int, db: AsyncSession = Depends(get_db)):
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(Source.name.ilike(f"%{q}%"))
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(Type.name.ilike(f"%{q}%"))
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(body: SourceQueryRequest, db: AsyncSession = Depends(get_db)):
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(Source.name.ilike(f"%{body.name}%"))
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(source_id: UUID, db: AsyncSession = Depends(get_db)):
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.HTTP_401_UNAUTHORIZED, detail="Not authorized"
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(source_id: UUID, db: AsyncSession = Depends(get_db)):
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(source_id: UUID, db: AsyncSession = Depends(get_db)):
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
  )