Compare commits

..

8 Commits

22 changed files with 383 additions and 306 deletions

110
Cargo.lock generated
View File

@@ -493,11 +493,12 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "canvas"
version = "0.5.0"
version = "0.5.10"
dependencies = [
"anyhow",
"async-trait",
"crossterm",
"derivative",
"once_cell",
"ratatui",
"regex",
@@ -584,7 +585,7 @@ dependencies = [
[[package]]
name = "client"
version = "0.5.0"
version = "0.5.10"
dependencies = [
"anyhow",
"async-trait",
@@ -601,6 +602,8 @@ dependencies = [
"rstest",
"serde",
"serde_json",
"strum 0.27.2",
"strum_macros 0.27.2",
"time",
"tokio",
"tokio-test",
@@ -635,7 +638,7 @@ dependencies = [
[[package]]
name = "common"
version = "0.5.0"
version = "0.5.10"
dependencies = [
"prost 0.13.5",
"prost-build 0.14.1",
@@ -936,6 +939,17 @@ dependencies = [
"serde",
]
[[package]]
name = "derivative"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "digest"
version = "0.10.7"
@@ -1959,11 +1973,11 @@ checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a"
[[package]]
name = "matchers"
version = "0.1.0"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
dependencies = [
"regex-automata 0.1.10",
"regex-automata",
]
[[package]]
@@ -2080,12 +2094,11 @@ dependencies = [
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
"overload",
"winapi",
"windows-sys 0.60.2",
]
[[package]]
@@ -2243,12 +2256,6 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "ownedbytes"
version = "0.9.0"
@@ -2756,7 +2763,7 @@ dependencies = [
"itertools 0.13.0",
"lru",
"paste",
"strum",
"strum 0.26.3",
"unicode-segmentation",
"unicode-truncate",
"unicode-width 0.2.0",
@@ -2810,17 +2817,8 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata 0.4.9",
"regex-syntax 0.8.5",
]
[[package]]
name = "regex-automata"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
dependencies = [
"regex-syntax 0.6.29",
"regex-automata",
"regex-syntax",
]
[[package]]
@@ -2831,15 +2829,9 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax 0.8.5",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "regex-syntax"
version = "0.8.5"
@@ -3100,7 +3092,7 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]]
name = "search"
version = "0.5.0"
version = "0.5.10"
dependencies = [
"anyhow",
"common",
@@ -3199,7 +3191,7 @@ dependencies = [
[[package]]
name = "server"
version = "0.5.0"
version = "0.5.10"
dependencies = [
"anyhow",
"bcrypt",
@@ -3210,6 +3202,7 @@ dependencies = [
"futures",
"jsonwebtoken",
"lazy_static",
"once_cell",
"prost 0.13.5",
"prost-build 0.14.1",
"prost-types 0.13.5",
@@ -3234,6 +3227,7 @@ dependencies = [
"tonic",
"tonic-reflection",
"tracing",
"tracing-subscriber",
"uuid",
"validator",
]
@@ -3756,9 +3750,15 @@ version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
dependencies = [
"strum_macros",
"strum_macros 0.26.4",
]
[[package]]
name = "strum"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
[[package]]
name = "strum_macros"
version = "0.26.4"
@@ -3772,6 +3772,18 @@ dependencies = [
"syn 2.0.104",
]
[[package]]
name = "strum_macros"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.104",
]
[[package]]
name = "subtle"
version = "2.6.1"
@@ -3830,7 +3842,7 @@ dependencies = [
"fnv",
"once_cell",
"plist",
"regex-syntax 0.8.5",
"regex-syntax",
"serde",
"serde_derive",
"serde_json",
@@ -3936,7 +3948,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d60769b80ad7953d8a7b2c70cdfe722bbcdcac6bccc8ac934c40c034d866fc18"
dependencies = [
"byteorder",
"regex-syntax 0.8.5",
"regex-syntax",
"utf8-ranges",
]
@@ -4318,9 +4330,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
[[package]]
name = "tracing"
version = "0.1.41"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
dependencies = [
"log",
"pin-project-lite",
@@ -4330,9 +4342,9 @@ dependencies = [
[[package]]
name = "tracing-attributes"
version = "0.1.30"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [
"proc-macro2",
"quote",
@@ -4341,9 +4353,9 @@ dependencies = [
[[package]]
name = "tracing-core"
version = "0.1.34"
version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
dependencies = [
"once_cell",
"valuable",
@@ -4362,14 +4374,14 @@ dependencies = [
[[package]]
name = "tracing-subscriber"
version = "0.3.19"
version = "0.3.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
dependencies = [
"matchers",
"nu-ansi-term",
"once_cell",
"regex",
"regex-automata",
"sharded-slab",
"smallvec",
"thread_local",

View File

@@ -5,7 +5,7 @@ resolver = "2"
[workspace.package]
# TODO: idk how to do the name, fix later
# name = "komp_ac"
version = "0.5.0"
version = "0.5.10"
edition = "2021"
license = "GPL-3.0-or-later"
authors = ["Filip Priečinský <filippriec@gmail.com>"]

View File

@@ -16,10 +16,5 @@ cargo watch -x 'run --package client -- client'
Client with tracing:
```
ENABLE_TRACING=1 RUST_LOG=client=debug cargo watch -x 'run --package client -- client'
```
Client with debug that cant be traced
```
cargo run --package client --features ui-debug -- client
```

2
canvas

Submodule canvas updated: 29fdc5a6c7...2c03fc4814

2
client

Submodule client updated: c1839bd960...6cba369adb

View File

@@ -20,6 +20,18 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
".komp_ac.table_validation.TableValidationResponse",
"#[derive(serde::Serialize, serde::Deserialize)]",
)
.type_attribute(
".komp_ac.table_validation.PatternRule",
"#[derive(serde::Serialize, serde::Deserialize)]",
)
.type_attribute(
".komp_ac.table_validation.PatternRules",
"#[derive(serde::Serialize, serde::Deserialize)]",
)
.type_attribute(
".komp_ac.table_validation.CustomFormatter",
"#[derive(serde::Serialize, serde::Deserialize)]",
)
.type_attribute(
".komp_ac.table_validation.UpdateFieldValidationRequest",
"#[derive(serde::Serialize, serde::Deserialize)]",

View File

@@ -6,78 +6,78 @@ import "common.proto";
// import "table_structure.proto";
service Adresar {
rpc PostAdresar (PostAdresarRequest) returns (AdresarResponse);
rpc GetAdresar (GetAdresarRequest) returns (AdresarResponse);
rpc PutAdresar (PutAdresarRequest) returns (AdresarResponse);
rpc DeleteAdresar (DeleteAdresarRequest) returns (DeleteAdresarResponse);
rpc GetAdresarCount (common.Empty) returns (common.CountResponse);
rpc GetAdresarByPosition (common.PositionRequest) returns (AdresarResponse);
rpc PostAdresar(PostAdresarRequest) returns (AdresarResponse);
rpc GetAdresar(GetAdresarRequest) returns (AdresarResponse);
rpc PutAdresar(PutAdresarRequest) returns (AdresarResponse);
rpc DeleteAdresar(DeleteAdresarRequest) returns (DeleteAdresarResponse);
rpc GetAdresarCount(common.Empty) returns (common.CountResponse);
rpc GetAdresarByPosition(common.PositionRequest) returns (AdresarResponse);
}
message GetAdresarRequest {
int64 id = 1;
int64 id = 1;
}
message DeleteAdresarRequest {
int64 id = 1;
int64 id = 1;
}
message PostAdresarRequest {
string firma = 1;
string kz = 2;
string drc = 3;
string ulica = 4;
string psc = 5;
string mesto = 6;
string stat = 7;
string banka = 8;
string ucet = 9;
string skladm = 10;
string ico = 11;
string kontakt = 12;
string telefon = 13;
string skladu = 14;
string fax = 15;
string firma = 1;
string kz = 2;
string drc = 3;
string ulica = 4;
string psc = 5;
string mesto = 6;
string stat = 7;
string banka = 8;
string ucet = 9;
string skladm = 10;
string ico = 11;
string kontakt = 12;
string telefon = 13;
string skladu = 14;
string fax = 15;
}
message AdresarResponse {
int64 id = 1;
string firma = 2;
string kz = 3;
string drc = 4;
string ulica = 5;
string psc = 6;
string mesto = 7;
string stat = 8;
string banka = 9;
string ucet = 10;
string skladm = 11;
string ico = 12;
string kontakt = 13;
string telefon = 14;
string skladu = 15;
string fax = 16;
int64 id = 1;
string firma = 2;
string kz = 3;
string drc = 4;
string ulica = 5;
string psc = 6;
string mesto = 7;
string stat = 8;
string banka = 9;
string ucet = 10;
string skladm = 11;
string ico = 12;
string kontakt = 13;
string telefon = 14;
string skladu = 15;
string fax = 16;
}
message PutAdresarRequest {
int64 id = 1;
string firma = 2;
string kz = 3;
string drc = 4;
string ulica = 5;
string psc = 6;
string mesto = 7;
string stat = 8;
string banka = 9;
string ucet = 10;
string skladm = 11;
string ico = 12;
string kontakt = 13;
string telefon = 14;
string skladu = 15;
string fax = 16;
int64 id = 1;
string firma = 2;
string kz = 3;
string drc = 4;
string ulica = 5;
string psc = 6;
string mesto = 7;
string stat = 8;
string banka = 9;
string ucet = 10;
string skladm = 11;
string ico = 12;
string kontakt = 13;
string telefon = 14;
string skladu = 15;
string fax = 16;
}
message DeleteAdresarResponse {
bool success = 1;
bool success = 1;
}

View File

@@ -5,35 +5,35 @@ package komp_ac.auth;
import "common.proto";
service AuthService {
rpc Register(RegisterRequest) returns (AuthResponse);
rpc Login(LoginRequest) returns (LoginResponse);
rpc Register(RegisterRequest) returns (AuthResponse);
rpc Login(LoginRequest) returns (LoginResponse);
}
message RegisterRequest {
string username = 1;
string email = 2;
string password = 3;
string password_confirmation = 4;
string role = 5;
string username = 1;
string email = 2;
string password = 3;
string password_confirmation = 4;
string role = 5;
}
message AuthResponse {
string id = 1; // UUID in string format
string username = 2; // Registered username
string email = 3; // Registered email (if provided)
string role = 4; // Default role: 'accountant'
string id = 1; // UUID in string format
string username = 2; // Registered username
string email = 3; // Registered email (if provided)
string role = 4; // Default role: 'accountant'
}
message LoginRequest {
string identifier = 1; // Can be username or email
string password = 2;
string identifier = 1; // Can be username or email
string password = 2;
}
message LoginResponse {
string access_token = 1; // JWT token
string token_type = 2; // Usually "Bearer"
int32 expires_in = 3; // Expiration in seconds (86400 for 24 hours)
string user_id = 4; // User's UUID in string format
string role = 5; // User's role
string username = 6;
string access_token = 1; // JWT token
string token_type = 2; // Usually "Bearer"
int32 expires_in = 3; // Expiration in seconds (86400 for 24 hours)
string user_id = 4; // User's UUID in string format
string role = 5; // User's role
string username = 6;
}

View File

@@ -3,5 +3,9 @@ syntax = "proto3";
package komp_ac.common;
message Empty {}
message CountResponse { int64 count = 1; }
message PositionRequest { int64 position = 1; }
message CountResponse {
int64 count = 1;
}
message PositionRequest {
int64 position = 1;
}

View File

@@ -3,18 +3,18 @@ syntax = "proto3";
package komp_ac.search;
service Searcher {
rpc SearchTable(SearchRequest) returns (SearchResponse);
rpc SearchTable(SearchRequest) returns (SearchResponse);
}
message SearchRequest {
string table_name = 1;
string query = 2;
string table_name = 1;
string query = 2;
}
message SearchResponse {
message Hit {
int64 id = 1; // PostgreSQL row ID
float score = 2;
string content_json = 3;
}
repeated Hit hits = 1;
message Hit {
int64 id = 1; // PostgreSQL row ID
float score = 2;
string content_json = 3;
}
repeated Hit hits = 1;
}

View File

@@ -3,44 +3,44 @@ syntax = "proto3";
package komp_ac.search2;
service Search2 {
rpc SearchTable(Search2Request) returns (Search2Response);
rpc SearchTable(Search2Request) returns (Search2Response);
}
message Search2Request {
string profile_name = 1;
string table_name = 2;
repeated ColumnFilter column_filters = 3;
optional string text_query = 4; // Optional fallback text search
optional string text_query = 4; // Optional fallback text search
optional int32 limit = 5;
optional string order_by = 6;
optional bool order_desc = 7;
}
message ColumnFilter {
string column_name = 1;
FilterType filter_type = 2;
string value = 3;
optional string value2 = 4; // For range queries
string column_name = 1;
FilterType filter_type = 2;
string value = 3;
optional string value2 = 4; // For range queries
}
enum FilterType {
EQUALS = 0;
CONTAINS = 1;
STARTS_WITH = 2;
ENDS_WITH = 3;
RANGE = 4;
GREATER_THAN = 5;
LESS_THAN = 6;
IS_NULL = 7;
IS_NOT_NULL = 8;
EQUALS = 0;
CONTAINS = 1;
STARTS_WITH = 2;
ENDS_WITH = 3;
RANGE = 4;
GREATER_THAN = 5;
LESS_THAN = 6;
IS_NULL = 7;
IS_NOT_NULL = 8;
}
message Search2Response {
message Hit {
int64 id = 1;
string content_json = 2; // No score - this is SQL-based
optional string match_info = 3; // Info about which columns matched
}
repeated Hit hits = 1;
int32 total_count = 2; // Total matching records (for pagination)
message Hit {
int64 id = 1;
string content_json = 2; // No score - this is SQL-based
optional string match_info = 3; // Info about which columns matched
}
repeated Hit hits = 1;
int32 total_count = 2; // Total matching records (for pagination)
}

View File

@@ -12,17 +12,14 @@ service TableDefinition {
// Creates a new table (and schema if missing) with system columns,
// linked-table foreign keys, user-defined columns, and optional indexes.
// Also inserts metadata and default validation rules. Entirely transactional.
rpc PostTableDefinition(PostTableDefinitionRequest)
returns (TableDefinitionResponse);
rpc PostTableDefinition(PostTableDefinitionRequest) returns (TableDefinitionResponse);
// Lists all profiles (schemas) and their tables with declared dependencies.
// This provides a tree-like overview of table relationships.
rpc GetProfileTree(komp_ac.common.Empty)
returns (ProfileTreeResponse);
rpc GetProfileTree(komp_ac.common.Empty) returns (ProfileTreeResponse);
// Drops a table and its metadata, then deletes the profile if it becomes empty.
rpc DeleteTable(DeleteTableRequest)
returns (DeleteTableResponse);
rpc DeleteTable(DeleteTableRequest) returns (DeleteTableResponse);
}
// A single link to another table within the same profile (schema).

View File

@@ -23,8 +23,7 @@ service TableStructureService {
// - Normalizes data_type text (details under TableColumn.data_type)
// - Returns an empty list if the table is validated but has no visible
// columns in information_schema (e.g., physical table missing)
rpc GetTableStructure(GetTableStructureRequest)
returns (TableStructureResponse);
rpc GetTableStructure(GetTableStructureRequest) returns (TableStructureResponse);
}
// Request identifying the profile (schema) and table to inspect.

View File

@@ -22,7 +22,8 @@ message FieldValidation {
// Current: only CharacterLimits. More rules can be added later.
CharacterLimits limits = 10;
// Future expansion:
// PatternRules pattern = 11;
PatternRules pattern = 11; // Validation 2
optional CustomFormatter formatter = 14; // Validation 4 custom formatting logic
DisplayMask mask = 3;
// ExternalValidation external = 13;
// CustomFormatter formatter = 14;
@@ -52,18 +53,50 @@ message CharacterLimits {
// Mask for pretty display
message DisplayMask {
string pattern = 1; // e.g., "(###) ###-####" or "####-##-##"
string input_char = 2; // e.g., "#"
string pattern = 1; // e.g., "(###) ###-####" or "####-##-##"
string input_char = 2; // e.g., "#"
optional string template_char = 3; // e.g., "_"
}
// One positionbased validation rule, similar to CharacterFilter + PositionRange
message PatternRule {
// Range descriptor: how far the rule applies
// Examples:
// - "0" → Single position 0
// - "0-3" → Range 0..3 inclusive
// - "from:5" → From position 5 onward
// - "0,2,5" → Multiple discrete positions
string range = 1;
// Character filter type, caseinsensitive keywords:
// "ALPHABETIC", "NUMERIC", "ALPHANUMERIC",
// "ONEOF(<chars>)", "EXACT(:)", "CUSTOM(<name>)"
string filter = 2;
}
message CustomFormatter {
// Formatter type identifier; handled clientside.
// Examples: "PSCFormatter", "PhoneFormatter", "CreditCardFormatter", "DateFormatter"
string type = 1;
// Optional freetext note or parameters (e.g. locale, pattern)
optional string description = 2;
}
// Collection of pattern rules for one field
message PatternRules {
// All rules that make up the validation logic
repeated PatternRule rules = 1;
// Optional humanreadable description for UI/debug purposes
optional string description = 2;
}
// Service to fetch validations for a table
service TableValidationService {
rpc GetTableValidation(GetTableValidationRequest)
returns (TableValidationResponse);
rpc GetTableValidation(GetTableValidationRequest) returns (TableValidationResponse);
rpc UpdateFieldValidation(UpdateFieldValidationRequest)
returns (UpdateFieldValidationResponse);
rpc UpdateFieldValidation(UpdateFieldValidationRequest) returns (UpdateFieldValidationResponse);
}
message UpdateFieldValidationRequest {

View File

@@ -5,57 +5,57 @@ package komp_ac.uctovnictvo;
import "common.proto";
service Uctovnictvo {
rpc PostUctovnictvo (PostUctovnictvoRequest) returns (UctovnictvoResponse);
rpc GetUctovnictvo (GetUctovnictvoRequest) returns (UctovnictvoResponse);
rpc GetUctovnictvoCount (common.Empty) returns (common.CountResponse);
rpc GetUctovnictvoByPosition (common.PositionRequest) returns (UctovnictvoResponse);
rpc PutUctovnictvo (PutUctovnictvoRequest) returns (UctovnictvoResponse);
rpc PostUctovnictvo(PostUctovnictvoRequest) returns (UctovnictvoResponse);
rpc GetUctovnictvo(GetUctovnictvoRequest) returns (UctovnictvoResponse);
rpc GetUctovnictvoCount(common.Empty) returns (common.CountResponse);
rpc GetUctovnictvoByPosition(common.PositionRequest) returns (UctovnictvoResponse);
rpc PutUctovnictvo(PutUctovnictvoRequest) returns (UctovnictvoResponse);
}
message PostUctovnictvoRequest {
int64 adresar_id = 1;
string c_dokladu = 2;
string datum = 3; // Use string for simplicity, or use google.protobuf.Timestamp for better date handling
string c_faktury = 4;
string obsah = 5;
string stredisko = 6;
string c_uctu = 7;
string md = 8;
string identif = 9;
string poznanka = 10;
string firma = 11;
int64 adresar_id = 1;
string c_dokladu = 2;
string datum = 3; // Use string for simplicity, or use google.protobuf.Timestamp for better date handling
string c_faktury = 4;
string obsah = 5;
string stredisko = 6;
string c_uctu = 7;
string md = 8;
string identif = 9;
string poznanka = 10;
string firma = 11;
}
message UctovnictvoResponse {
int64 id = 1;
int64 adresar_id = 2;
string c_dokladu = 3;
string datum = 4;
string c_faktury = 5;
string obsah = 6;
string stredisko = 7;
string c_uctu = 8;
string md = 9;
string identif = 10;
string poznanka = 11;
string firma = 12;
int64 id = 1;
int64 adresar_id = 2;
string c_dokladu = 3;
string datum = 4;
string c_faktury = 5;
string obsah = 6;
string stredisko = 7;
string c_uctu = 8;
string md = 9;
string identif = 10;
string poznanka = 11;
string firma = 12;
}
message PutUctovnictvoRequest {
int64 id = 1;
int64 adresar_id = 2;
string c_dokladu = 3;
string datum = 4;
string c_faktury = 5;
string obsah = 6;
string stredisko = 7;
string c_uctu = 8;
string md = 9;
string identif = 10;
string poznanka = 11;
string firma = 12;
int64 id = 1;
int64 adresar_id = 2;
string c_dokladu = 3;
string datum = 4;
string c_faktury = 5;
string obsah = 6;
string stredisko = 7;
string c_uctu = 8;
string md = 9;
string identif = 10;
string poznanka = 11;
string firma = 12;
}
message GetUctovnictvoRequest {
int64 id = 1;
int64 id = 1;
}

View File

@@ -37,7 +37,6 @@ pub mod proto {
pub mod table_validation {
include!("proto/komp_ac.table_validation.rs");
}
pub const FILE_DESCRIPTOR_SET: &[u8] =
include_bytes!("proto/descriptor.bin");
pub const FILE_DESCRIPTOR_SET: &[u8] = include_bytes!("proto/descriptor.bin");
}
}

Binary file not shown.

View File

@@ -26,7 +26,13 @@ pub struct FieldValidation {
#[prost(message, optional, tag = "10")]
pub limits: ::core::option::Option<CharacterLimits>,
/// Future expansion:
/// PatternRules pattern = 11;
///
/// Validation 2
#[prost(message, optional, tag = "11")]
pub pattern: ::core::option::Option<PatternRules>,
/// Validation 4 custom formatting logic
#[prost(message, optional, tag = "14")]
pub formatter: ::core::option::Option<CustomFormatter>,
#[prost(message, optional, tag = "3")]
pub mask: ::core::option::Option<DisplayMask>,
/// ExternalValidation external = 13;
@@ -65,6 +71,46 @@ pub struct DisplayMask {
#[prost(string, optional, tag = "3")]
pub template_char: ::core::option::Option<::prost::alloc::string::String>,
}
/// One positionbased validation rule, similar to CharacterFilter + PositionRange
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PatternRule {
/// Range descriptor: how far the rule applies
/// Examples:
/// - "0" → Single position 0
/// - "0-3" → Range 0..3 inclusive
/// - "from:5" → From position 5 onward
/// - "0,2,5" → Multiple discrete positions
#[prost(string, tag = "1")]
pub range: ::prost::alloc::string::String,
/// Character filter type, caseinsensitive keywords:
/// "ALPHABETIC", "NUMERIC", "ALPHANUMERIC",
/// "ONEOF(<chars>)", "EXACT(:)", "CUSTOM(<name>)"
#[prost(string, tag = "2")]
pub filter: ::prost::alloc::string::String,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct CustomFormatter {
/// Formatter type identifier; handled clientside.
/// Examples: "PSCFormatter", "PhoneFormatter", "CreditCardFormatter", "DateFormatter"
#[prost(string, tag = "1")]
pub r#type: ::prost::alloc::string::String,
/// Optional freetext note or parameters (e.g. locale, pattern)
#[prost(string, optional, tag = "2")]
pub description: ::core::option::Option<::prost::alloc::string::String>,
}
/// Collection of pattern rules for one field
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PatternRules {
/// All rules that make up the validation logic
#[prost(message, repeated, tag = "1")]
pub rules: ::prost::alloc::vec::Vec<PatternRule>,
/// Optional humanreadable description for UI/debug purposes
#[prost(string, optional, tag = "2")]
pub description: ::core::option::Option<::prost::alloc::string::String>,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct UpdateFieldValidationRequest {

View File

@@ -48,30 +48,27 @@ pub fn register_slovak_tokenizers(index: &Index) -> tantivy::Result<()> {
let tokenizer_manager = index.tokenizers();
// TOKENIZER for `prefix_edge`: Edge N-gram (1-4 chars)
let edge_tokenizer =
TextAnalyzer::builder(NgramTokenizer::new(1, 4, true)?)
.filter(RemoveLongFilter::limit(40))
.filter(LowerCaser)
.filter(AsciiFoldingFilter)
.build();
let edge_tokenizer = TextAnalyzer::builder(NgramTokenizer::new(1, 4, true)?)
.filter(RemoveLongFilter::limit(40))
.filter(LowerCaser)
.filter(AsciiFoldingFilter)
.build();
tokenizer_manager.register("slovak_prefix_edge", edge_tokenizer);
// TOKENIZER for `prefix_full`: Simple word tokenizer
let full_tokenizer =
TextAnalyzer::builder(SimpleTokenizer::default())
.filter(RemoveLongFilter::limit(40))
.filter(LowerCaser)
.filter(AsciiFoldingFilter)
.build();
let full_tokenizer = TextAnalyzer::builder(SimpleTokenizer::default())
.filter(RemoveLongFilter::limit(40))
.filter(LowerCaser)
.filter(AsciiFoldingFilter)
.build();
tokenizer_manager.register("slovak_prefix_full", full_tokenizer);
// NGRAM TOKENIZER: For substring matching.
let ngram_tokenizer =
TextAnalyzer::builder(NgramTokenizer::new(3, 3, false)?)
.filter(RemoveLongFilter::limit(40))
.filter(LowerCaser)
.filter(AsciiFoldingFilter)
.build();
let ngram_tokenizer = TextAnalyzer::builder(NgramTokenizer::new(3, 3, false)?)
.filter(RemoveLongFilter::limit(40))
.filter(LowerCaser)
.filter(AsciiFoldingFilter)
.build();
tokenizer_manager.register("slovak_ngram", ngram_tokenizer);
Ok(())

View File

@@ -14,12 +14,17 @@
{
devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [
mermaid-cli
# Rust toolchain
rustc
cargo
rustfmt
clippy
cargo-watch
rust-analyzer
cargo-tarpaulin
cargo-flamegraph
rust-code-analysis
# C build tools (for your linker issue)
gcc
@@ -37,6 +42,7 @@
# Protocol Buffers compiler for gRPC
protobuf
protoc-gen-doc
buf
];
shellHook = ''

View File

@@ -4,18 +4,15 @@ use std::collections::HashMap;
use std::path::Path;
use tantivy::collector::TopDocs;
use tantivy::query::{
BooleanQuery, BoostQuery, FuzzyTermQuery, Occur, Query, QueryParser,
TermQuery,
BooleanQuery, BoostQuery, FuzzyTermQuery, Occur, Query, QueryParser, TermQuery,
};
use tantivy::schema::{IndexRecordOption, Value};
use tantivy::{Index, TantivyDocument, Term};
use tonic::{Request, Response, Status};
use common::proto::komp_ac::search::{
search_response::Hit, SearchRequest, SearchResponse,
};
pub use common::proto::komp_ac::search::searcher_server::SearcherServer;
use common::proto::komp_ac::search::searcher_server::Searcher;
pub use common::proto::komp_ac::search::searcher_server::SearcherServer;
use common::proto::komp_ac::search::{search_response::Hit, SearchRequest, SearchResponse};
use common::search::register_slovak_tokenizers;
use sqlx::{PgPool, Row};
use tracing::info;
@@ -86,22 +83,15 @@ impl Searcher for SearcherService {
qualified_table
);
let rows = sqlx::query(&sql)
.fetch_all(&self.pool)
.await
.map_err(|e| {
Status::internal(format!(
"DB query for default results failed: {}",
e
))
})?;
let rows = sqlx::query(&sql).fetch_all(&self.pool).await.map_err(|e| {
Status::internal(format!("DB query for default results failed: {}", e))
})?;
let hits: Vec<Hit> = rows
.into_iter()
.map(|row| {
let id: i64 = row.try_get("id").unwrap_or_default();
let json_data: serde_json::Value =
row.try_get("data").unwrap_or_default();
let json_data: serde_json::Value = row.try_get("data").unwrap_or_default();
Hit {
id,
// Score is 0.0 as this is not a relevance-ranked search
@@ -111,7 +101,10 @@ impl Searcher for SearcherService {
})
.collect();
info!("--- SERVER: Successfully processed empty query. Returning {} default hits. ---", hits.len());
info!(
"--- SERVER: Successfully processed empty query. Returning {} default hits. ---",
hits.len()
);
return Ok(Response::new(SearchResponse { hits }));
}
// --- END OF MODIFIED LOGIC ---
@@ -131,15 +124,15 @@ impl Searcher for SearcherService {
Status::internal(format!("Failed to register Slovak tokenizers: {}", e))
})?;
let reader = index.reader().map_err(|e| {
Status::internal(format!("Failed to create index reader: {}", e))
})?;
let reader = index
.reader()
.map_err(|e| Status::internal(format!("Failed to create index reader: {}", e)))?;
let searcher = reader.searcher();
let schema = index.schema();
let pg_id_field = schema.get_field("pg_id").map_err(|_| {
Status::internal("Schema is missing the 'pg_id' field.")
})?;
let pg_id_field = schema
.get_field("pg_id")
.map_err(|_| Status::internal("Schema is missing the 'pg_id' field."))?;
// --- Query Building Logic (no changes here) ---
let prefix_edge_field = schema.get_field("prefix_edge").unwrap();
@@ -158,25 +151,17 @@ impl Searcher for SearcherService {
{
let mut must_clauses: Vec<(Occur, Box<dyn Query>)> = Vec::new();
for word in &words {
let edge_term =
Term::from_field_text(prefix_edge_field, word);
let full_term =
Term::from_field_text(prefix_full_field, word);
let edge_term = Term::from_field_text(prefix_edge_field, word);
let full_term = Term::from_field_text(prefix_full_field, word);
let per_word_query = BooleanQuery::new(vec![
(
Occur::Should,
Box::new(TermQuery::new(
edge_term,
IndexRecordOption::Basic,
)),
Box::new(TermQuery::new(edge_term, IndexRecordOption::Basic)),
),
(
Occur::Should,
Box::new(TermQuery::new(
full_term,
IndexRecordOption::Basic,
)),
Box::new(TermQuery::new(full_term, IndexRecordOption::Basic)),
),
]);
must_clauses.push((Occur::Must, Box::new(per_word_query) as Box<dyn Query>));
@@ -184,8 +169,7 @@ impl Searcher for SearcherService {
if !must_clauses.is_empty() {
let prefix_query = BooleanQuery::new(must_clauses);
let boosted_query =
BoostQuery::new(Box::new(prefix_query), 4.0);
let boosted_query = BoostQuery::new(Box::new(prefix_query), 4.0);
query_layers.push((Occur::Should, Box::new(boosted_query)));
}
}
@@ -195,8 +179,7 @@ impl Searcher for SearcherService {
// ===============================
{
let last_word = words.last().unwrap();
let fuzzy_term =
Term::from_field_text(prefix_full_field, last_word);
let fuzzy_term = Term::from_field_text(prefix_full_field, last_word);
let fuzzy_query = FuzzyTermQuery::new(fuzzy_term, 2, true);
let boosted_query = BoostQuery::new(Box::new(fuzzy_query), 3.0);
query_layers.push((Occur::Should, Box::new(boosted_query)));
@@ -206,8 +189,7 @@ impl Searcher for SearcherService {
// LAYER 3: PHRASE MATCHING WITH SLOP (MEDIUM PRIORITY, Boost: 2.0)
// ===============================
if words.len() > 1 {
let slop_parser =
QueryParser::for_index(&index, vec![prefix_full_field]);
let slop_parser = QueryParser::for_index(&index, vec![prefix_full_field]);
let slop_query_str = format!("\"{}\"~3", normalized_query);
if let Ok(slop_query) = slop_parser.parse_query(&slop_query_str) {
let boosted_query = BoostQuery::new(slop_query, 2.0);
@@ -219,11 +201,8 @@ impl Searcher for SearcherService {
// LAYER 4: NGRAM SUBSTRING MATCHING (LOWEST PRIORITY, Boost: 1.0)
// ===============================
{
let ngram_parser =
QueryParser::for_index(&index, vec![text_ngram_field]);
if let Ok(ngram_query) =
ngram_parser.parse_query(&normalized_query)
{
let ngram_parser = QueryParser::for_index(&index, vec![text_ngram_field]);
if let Ok(ngram_query) = ngram_parser.parse_query(&normalized_query) {
let boosted_query = BoostQuery::new(ngram_query, 1.0);
query_layers.push((Occur::Should, Box::new(boosted_query)));
}
@@ -244,9 +223,9 @@ impl Searcher for SearcherService {
// Step 1: Extract (score, pg_id) from Tantivy results.
let mut scored_ids: Vec<(f32, u64)> = Vec::new();
for (score, doc_address) in top_docs {
let doc: TantivyDocument = searcher.doc(doc_address).map_err(|e| {
Status::internal(format!("Failed to retrieve document: {}", e))
})?;
let doc: TantivyDocument = searcher
.doc(doc_address)
.map_err(|e| Status::internal(format!("Failed to retrieve document: {}", e)))?;
if let Some(pg_id_value) = doc.get_first(pg_id_field) {
if let Some(pg_id) = pg_id_value.as_u64() {
scored_ids.push((score, pg_id));
@@ -255,8 +234,7 @@ impl Searcher for SearcherService {
}
// Step 2: Fetch all corresponding rows from Postgres in a single query.
let pg_ids: Vec<i64> =
scored_ids.iter().map(|(_, id)| *id as i64).collect();
let pg_ids: Vec<i64> = scored_ids.iter().map(|(_, id)| *id as i64).collect();
let qualified_table = format!("gen.\"{}\"", table_name);
let query_str = format!(
"SELECT id, to_jsonb(t) AS data FROM {} t WHERE id = ANY($1)",
@@ -267,9 +245,7 @@ impl Searcher for SearcherService {
.bind(&pg_ids)
.fetch_all(&self.pool)
.await
.map_err(|e| {
Status::internal(format!("Database query failed: {}", e))
})?;
.map_err(|e| Status::internal(format!("Database query failed: {}", e)))?;
// Step 3: Map the database results by ID for quick lookup.
let mut content_map: HashMap<i64, String> = HashMap::new();
@@ -284,17 +260,18 @@ impl Searcher for SearcherService {
let hits: Vec<Hit> = scored_ids
.into_iter()
.filter_map(|(score, pg_id)| {
content_map
.get(&(pg_id as i64))
.map(|content_json| Hit {
id: pg_id as i64,
score,
content_json: content_json.clone(),
})
content_map.get(&(pg_id as i64)).map(|content_json| Hit {
id: pg_id as i64,
score,
content_json: content_json.clone(),
})
})
.collect();
info!("--- SERVER: Successfully processed search. Returning {} hits. ---", hits.len());
info!(
"--- SERVER: Successfully processed search. Returning {} hits. ---",
hits.len()
);
let response = SearchResponse { hits };
Ok(Response::new(response))

2
server

Submodule server updated: e497676789...515f9932f8