initial commit

This commit is contained in:
2026-08-04 14:41:21 +03:00
parent 3cb8fcde60
commit 1a1d6186d8
101 changed files with 23239 additions and 0 deletions

317
.editorconfig Normal file
View File

@@ -0,0 +1,317 @@
# Visual Studio generated .editorconfig file with C++ settings.
root = true
# Windows style newlines
[*]
end_of_line = crlf
insert_final_newline = false
[*.{c++,cc,cpp,cppm,cxx,h,h++,hh,hpp,hxx,inl,ipp,ixx,tlh,tli}]
charset = utf-8
indent_style = tab
# Visual C++ Code Style settings
cpp_generate_documentation_comments = xml
# Visual C++ Formatting settings
cpp_indent_braces = false
cpp_indent_multi_line_relative_to = innermost_parenthesis
cpp_indent_within_parentheses = indent
cpp_indent_preserve_within_parentheses = true
cpp_indent_case_contents = true
cpp_indent_case_labels = false
cpp_indent_case_contents_when_block = false
cpp_indent_lambda_braces_when_parameter = true
cpp_indent_goto_labels = one_left
cpp_indent_preprocessor = leftmost_column
cpp_indent_access_specifiers = false
cpp_indent_namespace_contents = true
cpp_indent_preserve_comments = false
cpp_new_line_before_open_brace_namespace = ignore
cpp_new_line_before_open_brace_type = ignore
cpp_new_line_before_open_brace_function = ignore
cpp_new_line_before_open_brace_block = ignore
cpp_new_line_before_open_brace_lambda = ignore
cpp_new_line_scope_braces_on_separate_lines = false
cpp_new_line_close_brace_same_line_empty_type = false
cpp_new_line_close_brace_same_line_empty_function = false
cpp_new_line_before_catch = true
cpp_new_line_before_else = true
cpp_new_line_before_while_in_do_while = false
cpp_space_before_function_open_parenthesis = remove
cpp_space_within_parameter_list_parentheses = true
cpp_space_between_empty_parameter_list_parentheses = true
cpp_space_after_keywords_in_control_flow_statements = true
cpp_space_within_control_flow_statement_parentheses = true
cpp_space_before_lambda_open_parenthesis = false
cpp_space_within_cast_parentheses = false
cpp_space_after_cast_close_parenthesis = false
cpp_space_within_expression_parentheses = true
cpp_space_before_block_open_brace = true
cpp_space_between_empty_braces = true
cpp_space_before_initializer_list_open_brace = false
cpp_space_within_initializer_list_braces = true
cpp_space_preserve_in_initializer_list = false
cpp_space_before_open_square_bracket = false
cpp_space_within_square_brackets = true
cpp_space_before_empty_square_brackets = false
cpp_space_between_empty_square_brackets = true
cpp_space_group_square_brackets = true
cpp_space_within_lambda_brackets = false
cpp_space_between_empty_lambda_brackets = false
cpp_space_before_comma = true
cpp_space_after_comma = true
cpp_space_remove_around_member_operators = true
cpp_space_before_inheritance_colon = true
cpp_space_before_constructor_colon = true
cpp_space_remove_before_semicolon = false
cpp_space_after_semicolon = true
cpp_space_remove_around_unary_operator = true
cpp_space_around_binary_operator = insert
cpp_space_around_assignment_operator = insert
cpp_space_pointer_reference_alignment = ignore
cpp_space_around_ternary_operator = insert
cpp_use_unreal_engine_macro_formatting = true
cpp_wrap_preserve_blocks = one_liners
# Visual C++ Inlcude Cleanup settings
cpp_include_cleanup_add_missing_error_tag_type = suggestion
cpp_include_cleanup_remove_unused_error_tag_type = dimmed
cpp_include_cleanup_sort_after_edits = false
cpp_sort_includes_error_tag_type = none
cpp_sort_includes_priority_case_sensitive = false
cpp_sort_includes_priority_style = quoted
cpp_includes_style = default
cpp_includes_use_forward_slash = true
# C# files
[*.cs]
#### Core EditorConfig Options ####
# Indentation and spacing
indent_size = 4
indent_style = space
tab_width = 4
# New line preferences
end_of_line = crlf
insert_final_newline = false
#### .NET Coding Conventions ####
# Organize usings
dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = false
file_header_template = unset
# this. and Me. preferences
dotnet_style_qualification_for_event = false
dotnet_style_qualification_for_field = false
dotnet_style_qualification_for_method = false
dotnet_style_qualification_for_property = false
# Language keywords vs BCL types preferences
dotnet_style_predefined_type_for_locals_parameters_members = true
dotnet_style_predefined_type_for_member_access = true
# Parentheses preferences
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
dotnet_style_parentheses_in_other_operators = never_if_unnecessary
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
# Modifier preferences
dotnet_style_require_accessibility_modifiers = for_non_interface_members
# Expression-level preferences
dotnet_style_coalesce_expression = true
dotnet_style_collection_initializer = true
dotnet_style_explicit_tuple_names = true
dotnet_style_namespace_match_folder = true
dotnet_style_null_propagation = true
dotnet_style_object_initializer = true
dotnet_style_operator_placement_when_wrapping = beginning_of_line
dotnet_style_prefer_auto_properties = true
dotnet_style_prefer_collection_expression = when_types_loosely_match
dotnet_style_prefer_compound_assignment = true
dotnet_style_prefer_conditional_expression_over_assignment = true
dotnet_style_prefer_conditional_expression_over_return = true
dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed
dotnet_style_prefer_inferred_anonymous_type_member_names = true
dotnet_style_prefer_inferred_tuple_names = true
dotnet_style_prefer_is_null_check_over_reference_equality_method = true
dotnet_style_prefer_simplified_boolean_expressions = true
dotnet_style_prefer_simplified_interpolation = true
# Field preferences
dotnet_style_readonly_field = true
# Parameter preferences
dotnet_code_quality_unused_parameters = all
# Suppression preferences
dotnet_remove_unnecessary_suppression_exclusions = none
# New line preferences
dotnet_style_allow_multiple_blank_lines_experimental = true
dotnet_style_allow_statement_immediately_after_block_experimental = true
#### C# Coding Conventions ####
# var preferences
csharp_style_var_elsewhere = false
csharp_style_var_for_built_in_types = false
csharp_style_var_when_type_is_apparent = false
# Expression-bodied members
csharp_style_expression_bodied_accessors = true
csharp_style_expression_bodied_constructors = false
csharp_style_expression_bodied_indexers = true
csharp_style_expression_bodied_lambdas = true
csharp_style_expression_bodied_local_functions = false
csharp_style_expression_bodied_methods = false
csharp_style_expression_bodied_operators = false
csharp_style_expression_bodied_properties = true
# Pattern matching preferences
csharp_style_pattern_matching_over_as_with_null_check = true
csharp_style_pattern_matching_over_is_with_cast_check = true
csharp_style_prefer_extended_property_pattern = true
csharp_style_prefer_not_pattern = true
csharp_style_prefer_pattern_matching = true
csharp_style_prefer_switch_expression = true
# Null-checking preferences
csharp_style_conditional_delegate_call = true
# Modifier preferences
csharp_prefer_static_local_function = true
csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async
csharp_style_prefer_readonly_struct = true
csharp_style_prefer_readonly_struct_member = true
# Code-block preferences
csharp_prefer_braces = true
csharp_prefer_simple_using_statement = true
csharp_style_namespace_declarations = block_scoped
csharp_style_prefer_method_group_conversion = true
csharp_style_prefer_primary_constructors = true
csharp_style_prefer_top_level_statements = true
# Expression-level preferences
csharp_prefer_simple_default_expression = true
csharp_style_deconstructed_variable_declaration = true
csharp_style_implicit_object_creation_when_type_is_apparent = true
csharp_style_inlined_variable_declaration = true
csharp_style_prefer_index_operator = true
csharp_style_prefer_local_over_anonymous_function = true
csharp_style_prefer_null_check_over_type_check = true
csharp_style_prefer_range_operator = true
csharp_style_prefer_tuple_swap = true
csharp_style_prefer_utf8_string_literals = true
csharp_style_throw_expression = true
csharp_style_unused_value_assignment_preference = discard_variable
csharp_style_unused_value_expression_statement_preference = discard_variable
# 'using' directive preferences
csharp_using_directive_placement = outside_namespace
# New line preferences
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true
csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true
csharp_style_allow_embedded_statements_on_same_line_experimental = true
#### C# Formatting Rules ####
# New line preferences
csharp_new_line_before_catch = true
csharp_new_line_before_else = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_open_brace = all
csharp_new_line_between_query_expression_clauses = true
# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = false
csharp_indent_labels = no_change
csharp_indent_switch_labels = false
# Space preferences
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = ignore
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = true
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = true
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = true
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = true
csharp_space_between_method_declaration_empty_parameter_list_parentheses = true
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = true
csharp_space_between_parentheses = expressions,control_flow_statements
csharp_space_between_square_brackets = false
# Wrapping preferences
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true
#### Naming styles ####
# Naming rules
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
# Symbol specifications
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
# Naming styles
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
.*
!.gitignore
!.editorconfig
appsettings.json
bin/
obj/

180
Api/RepositoryApi.cs Normal file
View File

@@ -0,0 +1,180 @@
using BitMiracle.LibTiff.Classic;
using Microsoft.AspNetCore.Mvc;
using SkiaSharp;
using System.Buffers.Text;
using Microsoft.Data.SqlClient;
using System.IO;
namespace Library.Api;
[Route( "Api/Repository/[action]/{id?}" )]
public class RepositoryApi : APIController
{
public RepositoryApi( ILogger<UserApi> logger , IConfiguration configuration )
: base( logger , configuration )
{
}
[HttpPost]
[DisableRequestSizeLimit]
public IActionResult UploadCoverImage( int id , List<IFormFile>? file )
{
Trace( "/Api/Repository/UploadCoverImage" );
if ( file == null || file.Count <= 0 )
return Error( "Missing file" );
using SKBitmap? bitmap = PreprocessImage( file[0] );
if ( bitmap == null )
return Error( "Invalid image" );
SqlDataReader reader = PrepareSQL(@"update gnm set uuid = REPLACE(NEWID( ),'-',''),data_format='jpg' OUTPUT INSERTED.uuid WHERE id = @id")
.AddParameter( "@id" , id )
.ExecuteReader( );
if ( !reader.Read( ) )
return Error( "No such book!" );
string? new_uuid = reader.String( 0 );
if ( string.IsNullOrEmpty( new_uuid ) )
return Error( "No such book!" );
string original = GetDataDirectory( ) + new_uuid + ".jpg";
using SKData bitmap_data = bitmap.Encode( SKEncodedImageFormat.Jpeg , 80 );
byte [] bitmap_data_array = bitmap_data.ToArray( );
System.IO.File.WriteAllBytes( original , bitmap_data_array );
return Success( new_uuid );
}
[HttpPost]
[DisableRequestSizeLimit]
public IActionResult UploadPDFAttachment( int id , List<IFormFile>? file )
{
Trace( "/Api/Repository/UploadPDFAttachment" );
if ( file == null || file.Count <= 0 )
return Error( "Missing file" );
byte [] ? pdf_data_array = PreprocessPDF( file[0] );
if ( pdf_data_array == null )
return Error( "Invalid file" );
SqlDataReader reader = PrepareSQL(@"update gnm_rec set uuid = REPLACE(NEWID( ),'-',''),data_format='pdf' OUTPUT INSERTED.uuid WHERE id = @id")
.AddParameter( "@id" , id )
.ExecuteReader( );
if ( !reader.Read( ) )
return Error( "No such attachment!" );
string? new_uuid = reader.String( 0 );
if ( string.IsNullOrEmpty( new_uuid ) )
return Error( "No such attachment!" );
string original = GetDataDirectory( ) + new_uuid + ".pdf";
System.IO.File.WriteAllBytes( original , pdf_data_array );
return Success( new_uuid );
}
[HttpPost]
[DisableRequestSizeLimit]
public IActionResult UploadImageAttachment( int id , List<IFormFile>? file )
{
Trace( "/Api/Repository/UploadImageAttachment" );
if ( file == null || file.Count <= 0 )
return Error( "Missing file" );
using SKBitmap? bitmap = PreprocessImage( file[0] );
if ( bitmap == null )
return Error( "Invalid image" );
SqlDataReader reader = PrepareSQL(@"update gnm_rec set uuid = REPLACE(NEWID( ),'-',''),data_format='jpg' OUTPUT INSERTED.uuid WHERE id = @id")
.AddParameter( "@id" , id )
.ExecuteReader( );
if ( !reader.Read( ) )
return Error( "No such attachment!" );
string? new_uuid = reader.String( 0 );
if ( string.IsNullOrEmpty( new_uuid ) )
return Error( "No such attachment!" );
string original = GetDataDirectory( ) + new_uuid + ".jpg";
using SKData bitmap_data = bitmap.Encode( SKEncodedImageFormat.Jpeg , 80 );
byte [] bitmap_data_array = bitmap_data.ToArray( );
System.IO.File.WriteAllBytes( original , bitmap_data_array );
return Success( new_uuid );
}
private static SKBitmap? PreprocessImage( IFormFile form_file )
{
SKBitmap? bitmap = SKBitmap.Decode( form_file.OpenReadStream( ) );
if ( bitmap == null )
{
bitmap = ConvertTiffToSKBitmap( form_file.OpenReadStream( ) );
if ( bitmap == null )
return null;
}
if ( bitmap!.IsEmpty || bitmap.IsNull || bitmap.Width < 16 || bitmap.Height < 16 )
return null;
return bitmap;
}
private static byte[]? PreprocessPDF( IFormFile form_file )
{
using MemoryStream file_contents = new( );
form_file.CopyTo( file_contents );
byte[] file_bytes = file_contents.ToArray( );
if ( file_bytes.Length < 10 )
return null;
if ( file_bytes[0] != (byte)'%' || file_bytes[1] != (byte)'P' || file_bytes[2] != 'D' || file_bytes[3] != 'F' )
return null;
return file_bytes;
}
public static SKBitmap? ConvertTiffToSKBitmap( Stream tifImage )
{
SKColor[] pixels;
int width, height;
// open a Tiff stored in the memory stream, and grab its pixels
using ( Tiff tifImg = Tiff.ClientOpen( "in-memory" , "r" , tifImage , new TiffStream( ) ) )
{
FieldValue[] value = tifImg.GetField(TiffTag.IMAGEWIDTH);
width = value[0].ToInt( );
value = tifImg.GetField( TiffTag.IMAGELENGTH );
height = value[0].ToInt( );
// Read the image into the memory buffer
int[] raster = new int[width * height];
if ( !tifImg.ReadRGBAImageOriented( width , height , raster , Orientation.TOPLEFT ) )
{
// Not a valid TIF image.
}
// store the pixels
pixels = new SKColor[width * height];
for ( int y = 0 ; y < height ; y++ )
{
for ( int x = 0 ; x < width ; x++ )
{
int arrayOffset = y * width + x;
int rgba = raster[arrayOffset];
pixels[arrayOffset] = new SKColor( (byte)Tiff.GetR( rgba ) , (byte)Tiff.GetG( rgba ) , (byte)Tiff.GetB( rgba ) , (byte)Tiff.GetA( rgba ) );
}
}
}
return new SKBitmap( width , height ) {
Pixels = pixels
};
}
}

276
Api/UploadApi.cs Normal file
View File

@@ -0,0 +1,276 @@
using BitMiracle.LibTiff.Classic;
using Microsoft.AspNetCore.Mvc;
using SkiaSharp;
using Microsoft.Data.SqlClient;
namespace Library.Api;
[Route( "Api/Upload/[action]/{id?}" )]
public class UploadApi : APIController
{
private readonly string m_TempDirectory;
public UploadApi( ILogger<UserApi> logger , IConfiguration configuration )
: base( logger , configuration )
{
m_TempDirectory = configuration.GetValue<string>( "TempDirectory" ) ?? string.Empty;
}
[HttpPost]
public IActionResult StartUpload( )
{
Trace( "/Api/Repository/StartUpload" );
string session = Guid.NewGuid( ).ToString( ).Replace( "-" , null );
using FileStream stream = new( m_TempDirectory + session! , FileMode.CreateNew , FileAccess.Write , FileShare.None );
return Success( session! );
}
[HttpPost]
public IActionResult UploadChunk( string? session , string? payload , long offset )
{
if ( session == null )
return Error( "Invalid call. (session)" );
if ( !ValidateOffset( session , offset ) )
return Error( "Invalid call. (Bad offset)" );
byte[]? bytes = Base64DecodeSafe( payload );
if ( bytes == null )
return Error( "Invalid call. (Bad payload)" );
using FileStream stream = new( m_TempDirectory + session! , FileMode.Append , FileAccess.Write , FileShare.None );
stream.Write( bytes , 0 , bytes.Length );
return Success( "OK" );
}
private static byte[]? PreprocessMP4( string form_file )
{
byte[] file_bytes = System.IO.File.ReadAllBytes( form_file );
if ( file_bytes.Length < 100 )
return null;
return file_bytes;
}
[HttpPost]
public IActionResult SaveMP4Attachment( string? session , long offset , int id )
{
if ( session == null )
return Error( "Invalid call. (Invalid parameters)" );
/* if ( !ValidateOffset( session , offset ) )
return Error( "Invalid call. (Bad offset)" );
*/
byte [] ? pdf_data_array = PreprocessMP4( m_TempDirectory + session! );
if ( pdf_data_array == null )
return Error( "Invalid file" );
SqlDataReader reader = PrepareSQL(@"update gnm_rec set uuid = REPLACE(NEWID( ),'-',''),data_format='mp4' OUTPUT INSERTED.uuid WHERE id = @id")
.AddParameter( "@id" , id )
.ExecuteReader( );
if ( !reader.Read( ) )
return Error( "No such attachment!" );
string? new_uuid = reader.String( 0 );
if ( string.IsNullOrEmpty( new_uuid ) )
return Error( "No such attachment!" );
string original = GetDataDirectory( ) + new_uuid + ".mp4";
System.IO.File.WriteAllBytes( original , pdf_data_array );
return Success( new_uuid );
}
[HttpPost]
public IActionResult SaveCoverImage( string? session , long offset , int id )
{
if ( session == null )
return Error( "Invalid call. (Invalid parameters)" );
if ( !ValidateOffset( session , offset ) )
return Error( "Invalid call. (Bad offset)" );
using SKBitmap? bitmap = PreprocessImage( m_TempDirectory + session! );
if ( bitmap == null )
return Error( "Invalid image" );
SqlDataReader reader = PrepareSQL(@"update gnm set uuid = REPLACE(NEWID( ),'-',''),data_format='jpg' OUTPUT INSERTED.uuid WHERE id = @id")
.AddParameter( "@id" , id )
.ExecuteReader( );
if ( !reader.Read( ) )
return Error( "No such book!" );
string? new_uuid = reader.String( 0 );
if ( string.IsNullOrEmpty( new_uuid ) )
return Error( "No such book!" );
string original = GetDataDirectory( ) + new_uuid + ".jpg";
using SKData bitmap_data = bitmap.Encode( SKEncodedImageFormat.Jpeg , 80 );
byte [] bitmap_data_array = bitmap_data.ToArray( );
System.IO.File.WriteAllBytes( original , bitmap_data_array );
return Success( new_uuid );
}
[HttpPost]
public IActionResult SavePDFAttachment( string? session , long offset , int id )
{
if ( session == null )
return Error( "Invalid call. (Invalid parameters)" );
if ( !ValidateOffset( session , offset ) )
return Error( "Invalid call. (Bad offset)" );
byte [] ? pdf_data_array = PreprocessPDF( m_TempDirectory + session! );
if ( pdf_data_array == null )
return Error( "Invalid file" );
SqlDataReader reader = PrepareSQL(@"update gnm_rec set uuid = REPLACE(NEWID( ),'-',''),data_format='pdf' OUTPUT INSERTED.uuid WHERE id = @id")
.AddParameter( "@id" , id )
.ExecuteReader( );
if ( !reader.Read( ) )
return Error( "No such attachment!" );
string? new_uuid = reader.String( 0 );
if ( string.IsNullOrEmpty( new_uuid ) )
return Error( "No such attachment!" );
string original = GetDataDirectory( ) + new_uuid + ".pdf";
System.IO.File.WriteAllBytes( original , pdf_data_array );
return Success( new_uuid );
}
[HttpPost]
public IActionResult SaveImageAttachment( string? session , long offset , int id )
{
if ( session == null )
return Error( "Invalid call. (Invalid parameters)" );
if ( !ValidateOffset( session , offset ) )
return Error( "Invalid call. (Bad offset)" );
using SKBitmap? bitmap = PreprocessImage( m_TempDirectory + session! );
if ( bitmap == null )
return Error( "Invalid image" );
SqlDataReader reader = PrepareSQL(@"update gnm_rec set uuid = REPLACE(NEWID( ),'-',''),data_format='jpg' OUTPUT INSERTED.uuid WHERE id = @id")
.AddParameter( "@id" , id )
.ExecuteReader( );
if ( !reader.Read( ) )
return Error( "No such attachment!" );
string? new_uuid = reader.String( 0 );
if ( string.IsNullOrEmpty( new_uuid ) )
return Error( "No such attachment!" );
string original = GetDataDirectory( ) + new_uuid + ".jpg";
using SKData bitmap_data = bitmap.Encode( SKEncodedImageFormat.Jpeg , 80 );
byte [] bitmap_data_array = bitmap_data.ToArray( );
System.IO.File.WriteAllBytes( original , bitmap_data_array );
return Success( new_uuid );
}
private static byte[]? Base64DecodeSafe( string? b )
{
if ( b == null )
return null;
try
{
return Convert.FromBase64String( b! );
}
catch ( Exception )
{
}
return null;
}
private bool ValidateOffset( string session , long expected )
{
try
{
FileInfo fi = new( m_TempDirectory + session );
return fi.Length == expected;
}
catch ( Exception )
{
}
return false;
}
private static SKBitmap? PreprocessImage( string local_file )
{
SKBitmap? bitmap = SKBitmap.Decode( local_file );
if ( bitmap == null )
{
bitmap = ConvertTiffToSKBitmap( local_file );
if ( bitmap == null )
return null;
}
if ( bitmap!.IsEmpty || bitmap.IsNull || bitmap.Width < 16 || bitmap.Height < 16 )
return null;
return bitmap;
}
private static SKBitmap? ConvertTiffToSKBitmap( string local_file )
{
SKColor[] pixels;
int width, height;
// open a Tiff stored in the memory stream, and grab its pixels
using ( Tiff tifImg = Tiff.ClientOpen( local_file , "r" , null , new TiffStream( ) ) )
{
FieldValue[] value = tifImg.GetField(TiffTag.IMAGEWIDTH);
width = value[0].ToInt( );
value = tifImg.GetField( TiffTag.IMAGELENGTH );
height = value[0].ToInt( );
// Read the image into the memory buffer
int[] raster = new int[width * height];
if ( !tifImg.ReadRGBAImageOriented( width , height , raster , Orientation.TOPLEFT ) )
{
// Not a valid TIF image.
}
// store the pixels
pixels = new SKColor[width * height];
for ( int y = 0 ; y < height ; y++ )
{
for ( int x = 0 ; x < width ; x++ )
{
int arrayOffset = y * width + x;
int rgba = raster[arrayOffset];
pixels[arrayOffset] = new SKColor( (byte)Tiff.GetR( rgba ) , (byte)Tiff.GetG( rgba ) , (byte)Tiff.GetB( rgba ) , (byte)Tiff.GetA( rgba ) );
}
}
}
return new SKBitmap( width , height )
{
Pixels = pixels
};
}
private static byte[]? PreprocessPDF( string form_file )
{
byte[] file_bytes = System.IO.File.ReadAllBytes( form_file );
if ( file_bytes.Length < 10 )
return null;
if ( file_bytes[0] != (byte)'%' || file_bytes[1] != (byte)'P' || file_bytes[2] != 'D' || file_bytes[3] != 'F' )
return null;
return file_bytes;
}
}

178
Api/UserApi.cs Normal file
View File

@@ -0,0 +1,178 @@
using Microsoft.AspNetCore.Mvc;
namespace Library.Api;
[Route( "Api/User/[action]/{id?}" )]
public class UserApi : APIController
{
public UserApi( ILogger<UserApi> logger , IConfiguration configuration )
: base( logger , configuration )
{
}
[HttpPost]
public IActionResult Login( string username , string password )
{
Trace( "/UserAPI/Login" );
string? session_id = PrepareSQL( "EXECUTE [usp_user_login] @user , @pass" )
.AddParameter( "@user" , username )
.AddParameter( "@pass" , password )
.ReadSingleString( );
if ( string.IsNullOrWhiteSpace( session_id ) )
return Error( "Невалидно потребителско име/парола" );
SessionStart( session_id );
return Success( session_id );
}
[HttpPost]
public IActionResult EditDetails( int id , string? name , string? email )
{
Trace( "/UserAPI/EditDetails" );
AssertID( id );
AssertParameter( name , 2 );
AssertEmail( email , false );
AssertLogin( null );
if ( id != GetUserData( )!.ID && !GetUserData( )!.IsAdmin )
return Error( "Access denied" );
if ( email == null )
{
PrepareSQL( "UPDATE [dom] SET [ime]=@name WHERE id=@id" )
.AddParameter( "@name" , name )
.AddParameter( "@id" , id )
.ExecuteNonQuery( );
}
else
{
PrepareSQL( "UPDATE [dom] SET [ime]=@name,[mail]=@email WHERE id=@id" )
.AddParameter( "@name" , name )
.AddParameter( "@email" , email )
.AddParameter( "@id" , id )
.ExecuteNonQuery( );
}
return Success( );
}
[HttpPost]
public IActionResult EditPassword( int id , string? password )
{
Trace( "/UserAPI/EditPassword" );
AssertID( id );
AssertParameter( password , 5 );
AssertLogin( null );
if ( id != GetUserData( )!.ID && !GetUserData( )!.IsAdmin )
return Error( "Access denied" );
PrepareSQL( "UPDATE [dom] SET [prl] = @new_pass WHERE id=@id" )
.AddParameter( "@new_pass" , password )
.AddParameter( "@id" , id )
.ExecuteNonQuery( );
return Success( );
}
[HttpGet]
public IActionResult Activate( int id )
{
Trace( "/UserAPI/Activate" );
AssertID( id );
AssertAdmin( );
PrepareSQL( "UPDATE [dom] SET [is_active]=1 WHERE id=@id" )
.AddParameter( "@id" , id )
.ExecuteNonQuery( );
return Success( );
}
[HttpGet]
public IActionResult Deactivate( int id )
{
Trace( "/UserAPI/Deactivate" );
AssertID( id );
AssertAdmin( );
if ( id == GetUserData( )!.ID )
return Error( "Не може да деактивирате себе си!" );
PrepareSQL( "UPDATE [dom] SET [is_active]=0 WHERE id=@id" )
.AddParameter( "@id" , id )
.ExecuteNonQuery( );
return Success( );
}
[HttpPost]
public IActionResult Register( string? username , string? password , string? name )
{
Trace( "/UserAPI/Register" );
AssertEmail( username , true );
AssertParameter( password , 5 );
AssertParameter( name , 2 );
string? new_user_id = PrepareSQL( "INSERT INTO [dom] ([mail],[prl],[ime]) OUTPUT INSERTED.id VALUES (@user,@pass,@name)" )
.AddParameter( "@user" , username )
.AddParameter( "@pass" , password )
.AddParameter( "@name" , name )
.ReadSingleString( );
if ( new_user_id == null )
return Error( "Емайл адреса вече е регистриран" );
string? session_id = PrepareSQL( "EXECUTE [usp_user_login] @user , @pass" )
.AddParameter( "@user" , username )
.AddParameter( "@pass" , password )
.ReadSingleString( );
if ( session_id == null )
return Error( "Емайл адреса вече е регистриран" );
SessionStart( session_id );
return Success( session_id );
}
[HttpPost]
public IActionResult Forgot( string ? username )
{
Trace( "/UserAPI/Forgot" );
AssertEmail( username , true );
string? password = PrepareSQL( "SELECT [prl] FROM [dom] WHERE [mail]=@user" )
.AddParameter( "@user" , username )
.ReadSingleString( );
if ( string.IsNullOrWhiteSpace( password ) )
return Error( "Невалидно потребителско име" );
return Error( "Тук се очаква да се изпрати код за ресет на мейла. Засега ето това е паролата: " + password );
}
[HttpGet]
public IActionResult Logout( )
{
Trace( "/UserAPI/Logout" );
if ( GetUserData( ) != null )
{
PrepareSQL( "EXECUTE [usp_user_logout] @session_id" )
.AddParameter( "@session_id" , GetUserData( )!.SessionID )
.ExecuteNonQuery( );
}
SessionCancel( );
return Success( "1" );
}
}

View File

@@ -0,0 +1,41 @@
namespace Library;
public static class ContextExtensions
{
public static void SetUserID( this HttpContext context , int id )
{
context.Items.Add( "CONTEXT_USER_ID" , id );
}
public static int GetUserID( this HttpContext context )
{
if ( !context.Items.TryGetValue( "CONTEXT_USER_ID" , out object? value ) )
return 0;
if ( value == null )
return 0;
if ( value is int v )
return v;
return 0;
}
public static void SetSystemAdmin( this HttpContext context , bool is_admin )
{
context.Items.Add( "CONTEXT_IS_ADMIN" , is_admin );
}
public static bool IsSystemAdmin( this HttpContext context )
{
if ( !context.Items.TryGetValue( "CONTEXT_IS_ADMIN" , out object? value ) )
return false;
if ( value == null )
return false;
if ( value is bool v )
return v;
return false;
}
}

View File

@@ -0,0 +1,94 @@
using Microsoft.AspNetCore.Mvc;
using System.Text;
using System.Text.Json;
namespace Library;
public class APIController : AbstractController
{
protected APIController( ILogger logger , IConfiguration configuration )
: base( logger , configuration )
{
}
protected void AssertID( int value )
{
if ( value <= 0 )
throw new APIErrorException( "Invalid id parameter" );
}
protected void AssertParameter( int value , int min_value )
{
if ( value < min_value )
throw new APIErrorException( "Invalid int parameter" );
}
protected void AssertParameter( string? value , int min_length )
{
if ( !ValidateString( value , min_length ) )
throw new APIErrorException( "Invalid string parameter" );
}
protected void AssertURL( string? value , bool mandatory )
{
if ( !ValidateURL( value , mandatory ) )
throw new APIErrorException( "Invalid url parameter" );
}
protected void AssertEmail( string? value , bool mandatory )
{
if ( !ValidateEmail( value , mandatory ) )
throw new APIErrorException( "Invalid email parameter" );
}
protected override void OnInvalidLogin( )
{
throw new APIErrorException( "Access denied." );
}
protected override void OnInvalidAdminLogin( )
{
throw new APIErrorException( "Access denied (admin)." );
}
protected ContentResult Error( string message )
{
using MemoryStream stream = new( );
using Utf8JsonWriter writer = new( stream );
writer.WriteStartObject( );
writer.WriteBoolean( "Success" , false );
writer.WriteString( "ErrorMessage" , message );
writer.WriteEndObject( );
writer.Dispose( );
return new ContentResult( )
{
ContentType = System.Net.Mime.MediaTypeNames.Application.Json ,
Content = Encoding.UTF8.GetString( stream.ToArray( ) )
};
}
protected ContentResult Success( )
{
return Success( "true" );
}
protected ContentResult Success( string data )
{
using MemoryStream stream = new( );
using Utf8JsonWriter writer = new( stream );
writer.WriteStartObject( );
writer.WriteBoolean( "Success" , true );
writer.WriteString( "Result" , data );
writer.WriteEndObject( );
writer.Dispose( );
return new ContentResult( )
{
ContentType = System.Net.Mime.MediaTypeNames.Application.Json ,
Content = Encoding.UTF8.GetString( stream.ToArray( ) )
};
}
}

View File

@@ -0,0 +1,202 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.SqlClient;
using System.Text.RegularExpressions;
namespace Library;
public abstract class AbstractController : Controller
{
private const string SESSION_COOKIE_NAME = "X-VTU";
private readonly ILogger m_Logger;
private readonly IConfiguration m_Configuration;
private readonly string m_DataDirectory = string.Empty;
protected AbstractController( ILogger logger , IConfiguration configuration )
{
m_Logger = logger;
m_Configuration = configuration;
m_DataDirectory = configuration.GetValue<string>( "DataDirectory" ) ?? string.Empty;
}
protected string GetDataDirectory( )
{
return m_DataDirectory;
}
protected void Trace( string function )
{
#pragma warning disable CA2254 // Template should be a static expression
m_Logger.Log( LogLevel.Trace , function + " called" );
#pragma warning restore CA2254 // Template should be a static expression
}
private SqlConnection? m_DBConnection = null;
protected SqlConnection GetDBConnection( )
{
if ( m_DBConnection == null )
{
try
{
m_DBConnection = new SqlConnection( m_Configuration.GetValue<string>( "ConnectionString" ) );
m_DBConnection.Open( );
HttpContext.Response.RegisterForDispose( m_DBConnection );
}
catch ( SqlException )
{
if ( m_DBConnection != null )
{
m_DBConnection.Dispose( );
m_DBConnection = null;
}
throw;
}
}
return m_DBConnection;
}
protected void SessionStart( string session_id )
{
CookieOptions cookieOptions = new( )
{
Secure = false, // only when HTTPS
HttpOnly = true,
IsEssential = true,
};
Response.Cookies.Append( SESSION_COOKIE_NAME , session_id , cookieOptions );
}
protected void SessionCancel( )
{
Response.Cookies.Delete( SESSION_COOKIE_NAME );
}
protected string? GetSessionKey( )
{
if ( !Request.Cookies.TryGetValue( SESSION_COOKIE_NAME , out string? session_id ) || string.IsNullOrEmpty( session_id ) )
return null;
return session_id;
}
private UserData? m_CurrentUser = null;
private bool m_bUserValidated = false;
protected UserData? GetUserData( )
{
if ( m_bUserValidated )
return m_CurrentUser;
m_bUserValidated = true;
if ( m_CurrentUser != null )
return m_CurrentUser;
string? session_id = GetSessionKey( );
if ( session_id == null )
return null;
m_CurrentUser = UserData.FromSession( GetDBConnection( ) , session_id! );
if ( m_CurrentUser == null )
return null;
HttpContext.SetUserID( m_CurrentUser!.ID );
HttpContext.Items.Add( "CONTEXT_IS_ADMIN" , m_CurrentUser?.IsAdmin ?? false );
return m_CurrentUser;
}
protected SQLBuilder PrepareSQL( string query , string? order_by = null )
{
return new SQLBuilder( GetDBConnection( ) , query , order_by );
}
protected void AssertLogin( string? id )
{
string? session_id = GetSessionKey( );
if ( session_id != null )
{
string? sql =
@"select rec.idgnm, gnm.idlis1, lis.ime, c.*
from gnm_rec rec
left join gnm on gnm.id = rec.idgnm
left join lis on lis.id = gnm.idlis1
left join tbl_cross c on
c.id1 = (select top 1 iddom from livelog where id_session = @session_id)
and c.id2 = gnm.idlis1
where rec.uuid = @id
and c.id is not null";
SqlDataReader reader_x = PrepareSQL( sql )
.AddParameter( "@id" , id )
.AddParameter( "@session_id" , session_id )
.ExecuteReader( );
bool res_x = reader_x.Read( );
reader_x.Close( );
if ( res_x == false )
OnInvalidLogin( );
}
else
{
if ( id == null )
{
if ( GetUserData( ) == null )
OnInvalidLogin( );
}
else
{
if ( GetUserData( ) != null )
return;
SqlDataReader reader = PrepareSQL( @"SELECT gnm.access FROM gnm_rec left join gnm on gnm.id = gnm_rec.idgnm WHERE gnm.access is not null and gnm_rec.uuid = @uuid and gnm_rec.data_format = 'pdf'" )
.AddParameter( "@uuid" , id )
.ExecuteReader( );
bool res = reader.Read( );
reader.Close( );
if ( res == true )
OnInvalidLogin( );
}
}
}
protected void AssertAdmin( )
{
if ( GetUserData( ) == null || GetUserData( )!.IsAdmin == false )
OnInvalidLogin( );
}
protected abstract void OnInvalidLogin( );
protected abstract void OnInvalidAdminLogin( );
protected bool ValidateString( string? parameter , int min_length )
{
int length = parameter?.Length ?? 0;
return length >= min_length;
}
private const string EMAIL_REGEX = @"^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$";
protected bool ValidateEmail( string? email , bool required )
{
return ValidateRegex( EMAIL_REGEX , email , required );
}
private const string URL_REGEX = @"(?i)(http(s)?:\/\/)?(\w{2,25}\.)+\w{3}([a-z0-9\-?=$-_.+!*()]+)(?i)";
protected bool ValidateURL( string? email , bool required )
{
return ValidateRegex( URL_REGEX , email , required );
}
protected bool ValidateRegex( string regex , string? value , bool is_required )
{
if ( value == null )
return is_required == false;
try
{
return Regex.IsMatch( value! , regex , RegexOptions.IgnoreCase , TimeSpan.FromMilliseconds( 250 ) );
}
catch ( RegexMatchTimeoutException )
{
return false;
}
}
}

View File

@@ -0,0 +1,43 @@
using Microsoft.AspNetCore.Mvc;
using Library.Models;
namespace Library;
public class PageController : AbstractController
{
protected PageController( ILogger logger , IConfiguration configuration )
: base( logger , configuration )
{
}
protected IActionResult View( BasePageModel page_model )
{
page_model.UserData = GetUserData( );
return base.View( page_model );
}
protected IActionResult View( string view_name , BasePageModel page_model)
{
page_model.UserData = GetUserData( );
return base.View( view_name , page_model );
}
protected override void OnInvalidLogin( )
{
throw new ErrorException( "Access denied." );
}
protected override void OnInvalidAdminLogin( )
{
throw new ErrorException( "Access denied." );
}
protected IActionResult RawText( string text )
{
return new ContentResult( )
{
ContentType = System.Net.Mime.MediaTypeNames.Text.Plain ,
Content = text
};
}
}

View File

@@ -0,0 +1,16 @@
namespace Library;
public class APIErrorException : Exception
{
private readonly string m_ErrorMessage;
public APIErrorException( string message )
{
m_ErrorMessage = message;
}
public string ErrorMessage
{
get { return m_ErrorMessage; }
}
}

View File

@@ -0,0 +1,16 @@
namespace Library;
public class ErrorException : Exception
{
private readonly string m_ErrorMessage;
public ErrorException( string message )
{
m_ErrorMessage = message;
}
public string ErrorMessage
{
get { return m_ErrorMessage; }
}
}

View File

@@ -0,0 +1,72 @@
using Microsoft.AspNetCore.Http;
using System.Net;
using System.Text;
using System.Text.Json;
namespace Library;
public class ExceptionMiddleware
{
private readonly RequestDelegate _next;
public ExceptionMiddleware( RequestDelegate next )
{
_next = next;
}
public async Task InvokeAsync( HttpContext httpContext )
{
try
{
await _next( httpContext );
}
catch ( RedirectException exception )
{
HandleExceptionAsync( httpContext , exception );
}
catch ( ErrorException exception )
{
await HandleErrorAsync( httpContext , exception );
}
catch ( APIErrorException exception )
{
await HandleApiErrorAsync( httpContext , exception );
}
catch ( Exception exception )
{
await HandleExceptionAsync( httpContext , exception );
}
}
private void HandleExceptionAsync( HttpContext context , RedirectException exception )
{
context.Response.Redirect( exception.Destination );
}
private static Task HandleErrorAsync( HttpContext context , ErrorException exception )
{
context.Response.StatusCode = 500;
return context.Response.WriteAsync( exception.ErrorMessage );
}
private static Task HandleApiErrorAsync( HttpContext context , APIErrorException exception )
{
using MemoryStream stream = new( );
using Utf8JsonWriter writer = new( stream );
writer.WriteStartObject( );
writer.WriteBoolean( "Success" , false );
writer.WriteString( "ErrorMessage" , exception.ErrorMessage );
writer.WriteEndObject( );
writer.Dispose( );
context.Response.ContentType = System.Net.Mime.MediaTypeNames.Application.Json;
return context.Response.WriteAsync( Encoding.UTF8.GetString( stream.ToArray( ) ) );
}
private static Task HandleExceptionAsync( HttpContext context , Exception exception )
{
context.Response.ContentType = System.Net.Mime.MediaTypeNames.Text.Plain;
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
return context.Response.WriteAsync( exception.ToString( ) );
}
}

View File

@@ -0,0 +1,16 @@
namespace Library;
public class RedirectException : Exception
{
private readonly string m_Destination;
public RedirectException( string destination )
{
m_Destination = destination;
}
public string Destination
{
get { return m_Destination; }
}
}

View File

@@ -0,0 +1,21 @@
using Microsoft.AspNetCore.Mvc.Rendering;
namespace Library;
public static class HtmlExtensions
{
public static string IsNull( this IHtmlHelper htmlHelper , object? attempt , string fallback )
{
return htmlHelper.Encode( attempt?.ToString( ) ?? fallback );
}
public static string IF( this IHtmlHelper htmlHelper , bool test , string value )
{
return htmlHelper.Encode( test ? value : "" );
}
public static string IIF( this IHtmlHelper htmlHelper , bool test , string value_1 , string value_2 )
{
return htmlHelper.Encode( test ? value_1 : value_2 );
}
};

226
App_Code/SQLBuilder.cs Normal file
View File

@@ -0,0 +1,226 @@
using Microsoft.Data.SqlClient;
using System.Text;
using System.Data;
using System.Data.SqlTypes;
namespace Library;
public class SQLBuilder
{
private readonly SqlConnection m_DBConnection;
private readonly string m_BaseQuery;
private readonly string? m_OrderBy;
private List<string>? m_Where;
private Dictionary<string,object?>? m_Parameters;
private struct OutputParameter
{
public OutputParameter( string id , SqlDbType type )
{
m_ID = id;
m_Parameter = new SqlParameter
{
ParameterName = id ,
SqlDbType = type ,
Direction = ParameterDirection.Output ,
};
}
public string ID
{
get { return m_ID; }
}
public SqlParameter Parameter
{
get { return m_Parameter; }
}
private readonly string m_ID;
private readonly SqlParameter m_Parameter;
}
private List<OutputParameter>? m_OutputParameters;
private int m_ItemsPerPage = 0;
private int m_CurrentPage = 0;
public SQLBuilder( SqlConnection conn , string query , string? order_by = null )
{
m_DBConnection = conn;
m_BaseQuery = query;
m_OrderBy = order_by;
}
public SQLBuilder SetPaging( int page , int items_per_page )
{
m_ItemsPerPage = items_per_page;
m_CurrentPage = page;
return this;
}
public SQLBuilder AddWhere( string where )
{
if ( m_Where == null )
m_Where = new( );
m_Where.Add( where );
return this;
}
public SQLBuilder AddClause( string where , string parameter , object value )
{
AddWhere( where );
AddParameter( parameter , value );
return this;
}
static private string StripVariable( string input )
{
StringBuilder result = new( );
foreach ( char c in input )
if ( char.IsLetterOrDigit( c ) )
result.Append( c );
return result.ToString( );
}
public SQLBuilder AddLike( string column , string value )
{
if ( value == null || value.Length <= 0 )
return this;
string[] parts = value.Split( ' ' , System.StringSplitOptions.RemoveEmptyEntries );
if ( parts != null && parts.Length > 0 )
{
int part_index = 0;
string variable_prefix = StripVariable( column );
foreach ( string p in parts )
{
string variable = "@" + variable_prefix + (++part_index).ToString( ) + "@";
AddClause( column + " LIKE " + variable , variable , "%" + p + "%" );
}
}
return this;
}
public SQLBuilder AddParameter( string name , object? value )
{
if ( m_Parameters == null )
m_Parameters = new( );
m_Parameters[name] = value;
return this;
}
public SQLBuilder AddOutputParameter( string name , SqlDbType type )
{
if ( m_OutputParameters == null )
m_OutputParameters = new( );
m_OutputParameters.Add( new OutputParameter( name , type ) );
return this;
}
public object? GetOutputParameter( string name )
{
if ( m_OutputParameters == null )
return null;
foreach ( OutputParameter p in m_OutputParameters )
{
if ( p.ID == name )
return p.Parameter.Value;
}
return null;
}
public SqlCommand PrepareCommand( )
{
string sql = m_BaseQuery;
if ( m_Where != null && m_Where.Count > 0 )
{
sql += " WHERE ( " + m_Where[0] + " ) ";
for ( int x = 1 ; x < m_Where.Count ; x++ )
sql += " AND ( " + m_Where[x] + " ) ";
}
if ( m_OrderBy != null )
sql += " ORDER BY " + m_OrderBy;
if ( m_ItemsPerPage > 0 )
sql += " OFFSET " + ( m_CurrentPage * m_ItemsPerPage ).ToString( ) + " ROWS FETCH FIRST " + m_ItemsPerPage.ToString( ) + " ROWS ONLY";
SqlCommand command = new( sql , m_DBConnection );
command.CommandType = CommandType.Text;
if ( m_Parameters != null )
{
foreach ( var it in m_Parameters )
{
if ( it.Value != null )
command.Parameters.AddWithValue( it.Key , it.Value! );
else
command.Parameters.AddWithValue( it.Key , System.DBNull.Value );
}
}
if ( m_OutputParameters != null )
{
foreach ( var it in m_OutputParameters )
command.Parameters.Add( it.Parameter );
}
return command;
}
public SqlDataReader ExecuteReader( )
{
return PrepareCommand( ).ExecuteReader( );
}
public int ExecuteNonQuery( )
{
return PrepareCommand( ).ExecuteNonQuery( );
}
public int ExecuteAndReadInteger( int on_error_value )
{
using SqlDataReader reader = ExecuteReader( );
if ( reader == null || !reader.Read( ) )
return on_error_value;
SqlInt32 value = reader.GetSqlInt32( 0 );
if ( value.IsNull )
return on_error_value;
return value.Value;
}
public int ReadSingleInteger( int on_error_value )
{
using SqlDataReader reader = ExecuteReader( );
if ( reader == null || !reader.Read( ) )
return on_error_value;
SqlInt32 value = reader.GetSqlInt32( 0 );
if ( value.IsNull )
return on_error_value;
return value.Value;
}
public string? ReadSingleStringUnsafe( )
{
using SqlDataReader reader = ExecuteReader( );
if ( reader == null || !reader.Read( ) )
return null;
return reader.String( 0 );
}
public string? ReadSingleString( )
{
try
{
return ReadSingleStringUnsafe( );
}
catch ( Exception )
{
return null;
}
}
}

154
App_Code/SQLExtensions.cs Normal file
View File

@@ -0,0 +1,154 @@
using Microsoft.Data.SqlClient;
using System.Data.SqlTypes;
namespace Library;
public static class SQLExtensions
{
public static string? String( this SqlDataReader reader , string column_id )
{
int column_index = reader.FindColumn( column_id );
if ( column_index < 0 )
return null;
return reader.String( column_index );
}
public static string? String( this SqlDataReader reader , int column_index )
{
if ( reader.IsDBNull( column_index ) )
return null;
string? o = reader[ column_index ].ToString( );
if ( o == null )
return null;
return o!;
}
public static string StringSafe( this SqlDataReader reader , string column_id )
{
int column_index = reader.FindColumn( column_id );
if ( column_index < 0 )
return "";
return reader.StringSafe( column_index );
}
public static string StringSafe( this SqlDataReader reader , int column_index )
{
if ( reader.IsDBNull( column_index ) )
return "";
string? o = reader[ column_index ].ToString( );
if ( o == null ) return "";
return o!;
}
public static DateTime DateTime( this SqlDataReader reader , string column_id )
{
int column_index = reader.FindColumn( column_id );
if ( column_index < 0 )
return System.DateTime.MinValue;
return reader.DateTime( column_index );
}
public static DateTime DateTime( this SqlDataReader reader , int column_index )
{
object data_value = reader[ column_index ];
if ( data_value is DateTime date_time_result )
return date_time_result;
return System.DateTime.MinValue;
}
public static int Int( this SqlDataReader reader , string column_id , int fallback = 0 )
{
int column_index = reader.FindColumn( column_id );
if ( column_index < 0 )
return fallback;
return reader.Int( column_index , fallback );
}
public static int Int( this SqlDataReader reader , int column_index , int fallback = 0 )
{
object data = reader[ column_index ];
if ( data == null )
return fallback;
try
{
return Convert.ToInt32( data );
}
catch
{
}
if ( int.TryParse( data.ToString( ) , out int result ) )
return result;
return fallback;
}
public static long Int64( this SqlDataReader reader , string column_id , long fallback )
{
int column_index = reader.FindColumn( column_id );
if ( column_index < 0 )
return fallback;
return reader.Int64( column_index , fallback );
}
public static long Int64( this SqlDataReader reader , int column_index , long fallback )
{
object data = reader[ column_index ];
if ( data == null )
return fallback;
try
{
return Convert.ToInt64( data );
}
catch
{
}
if ( long.TryParse( data.ToString( ) , out long result ) )
return result;
return fallback;
}
public static decimal Numeric( this SqlDataReader reader , string column_id , decimal fallback )
{
int column_index = reader.FindColumn( column_id );
if ( column_index < 0 )
return fallback;
return reader.Numeric( column_index , fallback );
}
public static decimal Numeric( this SqlDataReader reader , int column_index , decimal fallback )
{
SqlDecimal data = reader.GetSqlDecimal( column_index );
if ( data.IsNull )
return 0;
return data.Value;
}
public static int FindColumn( this SqlDataReader reader , string column_id )
{
try
{
return reader.GetOrdinal( column_id );
}
catch ( Exception )
{
return -1;
}
}
}

View File

@@ -0,0 +1,60 @@
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace Library;
public class ViewScriptTagHelper : TagHelper
{
private readonly IWebHostEnvironment environment;
private readonly IFileVersionProvider fileVersionProvider;
private const string AppendVersionAttributeName = "append-version";
public ViewScriptTagHelper( IWebHostEnvironment environment , IFileVersionProvider fileVersionProvider )
{
this.environment = environment;
this.fileVersionProvider = fileVersionProvider;
}
[ViewContext]
public ViewContext? ViewContext { get; set; }
/// <summary>
/// Value indicating if file version should be appended to src urls.
/// </summary>
/// <remarks>
/// A query string "v" with the encoded content of the file is added.
/// </remarks>
[HtmlAttributeName( AppendVersionAttributeName )]
public bool? AppendVersion { get; set; }
public override void Process( TagHelperContext context , TagHelperOutput output )
{
// remove the `page-script` tag if script doesn't exist
output.TagName = null;
output.TagMode = TagMode.StartTagAndEndTag;
var viewPath = ViewContext?.View.Path;
var src = $"{viewPath}.js";
/* When the app is published, the framework automatically moves the script to the web root.
So we should check both places, with the content root first for development */
var fileInfo = environment.ContentRootFileProvider.GetFileInfo(src) ??
environment.WebRootFileProvider.GetFileInfo(src);
if ( fileInfo is { Exists: true } )
{
// switch it to script now
output.TagName = "script";
output.Content = new DefaultTagHelperContent( );
if ( AppendVersion == true )
{
// people love their cache busting versions
src = fileVersionProvider.AddFileVersionToPath( src , src );
}
output.Attributes.Add( "src" , src );
}
}
}

View File

@@ -0,0 +1,65 @@
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
using Library.Models;
namespace Library.Controllers;
public class HomeController : PageController
{
public HomeController( ILogger<HomeController> logger , IConfiguration configuration )
: base( logger , configuration )
{
}
[Route( "" )]
[Route( "Home/Index" )]
public IActionResult Index( )
{
Trace( "/Home/Index" );
return View( new BasePageModel( GetDBConnection( ) ) );
}
[Route( "Login" )]
public IActionResult Login( )
{
Trace( "/Home/Login" );
return View( new BasePageModel( GetDBConnection( ) ) );
}
[Route( "Sitemap" )]
public IActionResult Sitemap( )
{
Trace( "/Home/Sitemap" );
return View( new BasePageModel( GetDBConnection( ) ) );
}
[Route( "Search" )]
public IActionResult Search( string q , int page )
{
Trace( "/Home/Search" );
return View( new SearchPageModel( GetDBConnection( ) , q , page ) );
}
[Route( "Repository" )]
[Route( "Repository/{id?}" )]
public IActionResult Repository( int id , int page )
{
Trace( "/Home/Repository" );
return View( new RepositoryPageModel( GetDBConnection( ) , id , page ) );
}
[Route( "Repository/Details" )]
[Route( "Repository/Details/{id?}" )]
public IActionResult RepositoryDetails( int id )
{
Trace( "/Home/RepositoryDetails" );
return View( new RepositoryDetailsPageModel( GetDBConnection( ) , id ) );
}
[ResponseCache( Duration = 0 , Location = ResponseCacheLocation.None , NoStore = true )]
public IActionResult Error( )
{
Trace( "/Home/Error" );
return View( new ErrorPageModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier } );
}
}

View File

@@ -0,0 +1,212 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.SqlClient;
using Docnet.Core;
using Docnet.Core.Models;
using Docnet.Core.Readers;
using SkiaSharp;
using System.Runtime.InteropServices;
namespace Library.Controllers;
public class RepositoryController : PageController
{
public RepositoryController( ILogger<RepositoryController> logger , IConfiguration configuration )
: base( logger , configuration )
{
}
public IActionResult Cover( string? id )
{
Trace( "/Repository/Cover" );
if ( id?.Length != 32 )
return RawText( "No such cover!" );
using SqlDataReader reader = PrepareSQL( @"SELECT uuid,data_format FROM gnm WHERE uuid=@uuid" )
.AddParameter( "@uuid" , id )
.ExecuteReader( );
if ( !reader.Read( ) )
return RawText( "No such cover!" );
if ( System.IO.File.Exists( GetDataDirectory( ) + id + ".jpg" ) )
{
byte [] bytes = System.IO.File.ReadAllBytes( GetDataDirectory( ) + id + ".jpg");
HttpContext.Response.Headers.ContentDisposition = "inline;filename=" + id + ".jpg";
return File( bytes , System.Net.Mime.MediaTypeNames.Image.Jpeg );
}
return RawText( "No such cover!" );
}
public IActionResult Video( string? id )
{
Trace( "/Repository/Video" );
if ( id?.Length != 32 )
return RawText( "No such video!" );
if ( GetUserData( ) != null && !GetUserData( )!.IsAdmin )
{
SqlDataReader reader_u = PrepareSQL( @"update gnm_rec set cnt = isnull(cnt,0) + 1 where gnm_rec.uuid = @uuid")
.AddParameter( "@uuid" , id )
.ExecuteReader( );
reader_u.Close( );
}
using SqlDataReader reader = PrepareSQL( @"SELECT uuid,data_format FROM gnm_rec WHERE uuid=@uuid and data_format='mp4'" )
.AddParameter( "@uuid" , id )
.ExecuteReader( );
if ( !reader.Read( ) )
return RawText( "No such file!" );
byte [] bytes = System.IO.File.ReadAllBytes( GetDataDirectory( ) + id + ".mp4");
HttpContext.Response.Headers.ContentDisposition = "inline;filename=" + id + ".mp4";
return File( bytes , "video/mp4" );
}
private string? ReadPDFTitle( string id )
{
AssertLogin( id );
if ( id.Length != 32 )
return null;
using SqlDataReader reader = PrepareSQL( @"SELECT gnm.ime FROM gnm_rec left join gnm on gnm.id = gnm_rec.idgnm WHERE gnm_rec.uuid = @uuid and gnm_rec.data_format = 'pdf'" )
.AddParameter( "@uuid" , id )
.ExecuteReader( );
if ( !reader.Read( ) )
return null;
return reader.String( 0 );
}
private byte[]? ReadPDFBytes( string id )
{
// return System.IO.File.ReadAllBytes( "c:/Users/gbromov/Downloads/3950.pdf" );
return System.IO.File.ReadAllBytes( GetDataDirectory( ) + id + ".pdf" );
}
private IActionResult RenderPDFPage( string id , int page , int width , int height )
{
if ( ReadPDFTitle( id ) == null )
return RawText( "No such file!" );
byte[]? bytes = ReadPDFBytes( id );
if ( bytes == null )
return RawText( "No such file!" );
using IDocReader doc_reader = DocLib.Instance.GetDocReader( bytes , new PageDimensions( width , height ) );
using IPageReader page_reader = doc_reader.GetPageReader( page );
byte [] rgb_bytes = page_reader.GetImage( );
width = page_reader.GetPageWidth( );
height = page_reader.GetPageHeight( );
GCHandle data_handle = GCHandle.Alloc( rgb_bytes , GCHandleType.Pinned );
SKImageInfo info = new( width , height , SKColorType.Bgra8888 , SKAlphaType.Unpremul );
using SKBitmap bitmap = new( );
bitmap.InstallPixels( info , data_handle.AddrOfPinnedObject( ) , info.RowBytes , delegate { data_handle.Free( ); } , null );
using SKBitmap background = new( width , height , SKColorType.Bgra8888 , SKAlphaType.Opaque );
background.Erase( new SKColor( 255 , 255 , 255 ) );
using SKCanvas canvas = new(background);
canvas.DrawBitmap( bitmap , 0 , 0 , SKSamplingOptions.Default );
using SKData bitmap_data = background.Encode( SKEncodedImageFormat.Jpeg , 80 );
byte [] bitmap_data_array = bitmap_data.ToArray( );
return File( bitmap_data_array , System.Net.Mime.MediaTypeNames.Image.Jpeg );
}
[Route( "Repository/PDFPage/{id}/{page?}" )]
public IActionResult PDFPage( string id , int page = 0 )
{
Trace( "/Repository/PDFPage" );
return RenderPDFPage( id , page , 1080 , 1920 );
}
[Route( "Repository/PDFThumb/{id}/{page?}" )]
public IActionResult PDFThumb( string id , int page = 0 )
{
Trace( "/Repository/PDFPage" );
return RenderPDFPage( id , page , 135 , 240 );
}
[Route( "Repository/PDFView/{id}" )]
public IActionResult PDFView( string id )
{
Trace( "/Repository/PDFView" );
string? pdf_title = ReadPDFTitle( id );
if ( pdf_title == null )
throw new RedirectException( "/" );
byte[]? bytes = ReadPDFBytes( id );
if ( bytes == null )
throw new RedirectException( "/" );
// if ( GetUserData( ) != null && !GetUserData( )!.IsAdmin ) --- è àäìèíà, è âñåêè ìîæå äà îòâàðÿ PDF --- increment counter
{
SqlDataReader reader_u = PrepareSQL( @"update gnm_rec set cnt = isnull(cnt,0) + 1 where gnm_rec.uuid = @uuid")
.AddParameter( "@uuid" , id )
.ExecuteReader( );
reader_u.Close( );
}
using IDocReader doc_reader = DocLib.Instance.GetDocReader( bytes , new PageDimensions( 1080 , 1920 ) );
int page_count = doc_reader.GetPageCount( );
return View( new Models.PDFViewModel( GetDBConnection( ) , id , page_count )
{
Title = pdf_title
} );
}
public IActionResult PDF( string? id )
{
Trace( "/Repository/PDF" );
AssertLogin( id );
if ( id?.Length != 32 )
return RawText( "No such file!" );
using SqlDataReader reader = PrepareSQL( @"SELECT gnm.access, gnm_rec.uuid, gnm_rec.data_format FROM gnm_rec left join gnm on gnm.id = gnm_rec.idgnm WHERE gnm_rec.uuid = @uuid and gnm_rec.data_format = 'pdf'" )
.AddParameter( "@uuid" , id )
.ExecuteReader( );
if ( !reader.Read( ) )
return RawText( "No such file!" );
byte [] bytes = System.IO.File.ReadAllBytes( GetDataDirectory( ) + id + ".pdf");
HttpContext.Response.Headers.ContentDisposition = "inline;filename=" + id + ".pdf";
return File( bytes , System.Net.Mime.MediaTypeNames.Application.Pdf );
}
public IActionResult Image( string? id )
{
Trace( "/Repository/Image" );
if ( id?.Length != 32 )
return RawText( "No such image!" );
if ( GetUserData( ) != null && !GetUserData( )!.IsAdmin )
{
SqlDataReader reader_u = PrepareSQL( @"update gnm_rec set cnt = isnull(cnt,0) + 1 where gnm_rec.uuid = @uuid")
.AddParameter( "@uuid" , id )
.ExecuteReader( );
reader_u.Close( );
}
using SqlDataReader reader = PrepareSQL( @"SELECT uuid,data_format FROM gnm_rec WHERE uuid=@uuid and data_format='jpg'" )
.AddParameter( "@uuid" , id )
.ExecuteReader( );
if ( !reader.Read( ) )
return RawText( "No such file!" );
byte [] bytes = System.IO.File.ReadAllBytes( GetDataDirectory( ) + id + ".jpg");
HttpContext.Response.Headers.ContentDisposition = "inline;filename=" + id + ".jpg";
return File( bytes , System.Net.Mime.MediaTypeNames.Image.Jpeg );
}
}

37
Data/AttachmentData.cs Normal file
View File

@@ -0,0 +1,37 @@
using System.Collections.Generic;
using Microsoft.Data.SqlClient;
namespace Library;
public class AttachmentData
{
public int ID { init; get; } = 0;
public string Title { init; get; } = null!;
public string? UUID { init; get; } = null;
public string? Link { init; get; } = null;
public string? Format { init; get; } = null;
public static List<AttachmentData> GetList( SqlConnection connection , int repository_id )
{
using SqlDataReader reader = new SQLBuilder( connection , "SELECT * FROM [v_attachment] WHERE repository_id = @repository_id" )
.AddParameter( "@repository_id" , repository_id )
.ExecuteReader( );
List<AttachmentData> result = new( );
while ( reader.Read( ) )
{
AttachmentData attachment = new( )
{
ID = reader.Int( "id" ) ,
UUID = reader.StringSafe( "uuid" ) ,
Title = reader.StringSafe( "title" ) ,
Link = reader.StringSafe( "link" ) ,
Format = reader.String( "format" ) ,
};
result.Add( attachment );
}
return result;
}
}

94
Data/BookData.cs Normal file
View File

@@ -0,0 +1,94 @@
using Microsoft.Data.SqlClient;
namespace Library;
public class BookData
{
public int ID { init; get; } = 0;
public string UUID { init; get; } = "";
public int CategoryID { init; get; } = 0;
public string CategoryName { init; get; } = "";
public int BookTypeID { init; get; } = 0;
public string BookTypeName { init; get; } = "";
public string Title { init; get; } = "";
public string Author { init; get; } = "";
public string Access { init; get; } = "";
public string Publisher { init; get; } = "";
public string PublishYear { init; get; } = "";
public string Keywords { init; get; } = "";
public string Description { init; get; } = "";
public DateTime CreateDate { init; get; } = DateTime.Now;
public string Opisanie { init; get; } = "";
public string KodDrugi { init; get; } = "";
public static BookData? WithID( SqlConnection connection , int id )
{
using SqlDataReader reader = new SQLBuilder( connection , "SELECT * FROM v_repository WHERE id = @id" )
.AddParameter( "@id" , id )
.ExecuteReader( );
if ( !reader.Read( ) )
return null;
return new BookData( )
{
ID = reader.Int( "id" ) ,
UUID = reader.StringSafe( "uuid" ) ,
CategoryID = reader.Int( "category_id" ) ,
CategoryName = reader.StringSafe( "category_name" ) ,
BookTypeID = reader.Int( "book_type_id" ) ,
BookTypeName = reader.StringSafe( "book_type_name" ) ,
Title = reader.StringSafe( "title" ) ,
Author = reader.StringSafe( "author" ) ,
Access = reader.StringSafe( "access" ) ,
Publisher = reader.StringSafe( "publisher" ) ,
PublishYear = reader.StringSafe( "publish_year" ) ,
Keywords = reader.StringSafe( "keywords" ) ,
Description = reader.StringSafe( "description" ) ,
CreateDate = reader.DateTime( "create_date" ) ,
Opisanie = reader.StringSafe( "opisanie" ) ,
KodDrugi = reader.StringSafe( "koddrugi" ) ,
};
}
}
public class BookDataMini
{
public int ID { init; get; } = 0;
public string UUID { init; get; } = "";
public int CategoryID { init; get; } = 0;
public string CategoryName { init; get; } = "";
public int BookTypeID { init; get; } = 0;
public string BookTypeName { init; get; } = "";
public string Title { init; get; } = "";
public string Author { init; get; } = "";
public string Access { init; get; } = "";
public static BookDataMini? WithID( SqlConnection connection , int id )
{
using SqlDataReader reader = new SQLBuilder( connection , "SELECT * FROM v_repository WHERE id = @id" )
.AddParameter( "@id" , id )
.ExecuteReader( );
if ( !reader.Read( ) )
return null;
return new BookDataMini( )
{
ID = reader.Int( "id" ) ,
UUID = reader.StringSafe( "uuid" ) ,
CategoryID = reader.Int( "category_id" ) ,
CategoryName = reader.StringSafe( "category_name" ) ,
BookTypeID = reader.Int( "book_type_id" ) ,
BookTypeName = reader.StringSafe( "book_type_name" ) ,
Title = reader.StringSafe( "title" ) ,
Author = reader.StringSafe( "author" ) ,
Access = reader.StringSafe( "access" ) ,
};
}
}

108
Data/BookListData.cs Normal file
View File

@@ -0,0 +1,108 @@
using System.Collections;
using Microsoft.Data.SqlClient;
namespace Library;
public class BookListData : IEnumerable<BookData>
{
private readonly List<BookData> m_Elements = new( );
public IEnumerator<BookData> GetEnumerator( )
{
return m_Elements.GetEnumerator( );
}
IEnumerator IEnumerable.GetEnumerator( )
{
return m_Elements.GetEnumerator( );
}
static public int GetCategoryCount( SqlConnection connection , int category_id )
{
return new SQLBuilder( connection , @"SELECT count(*) FROM v_repository WHERE category_id=@category and include_category_id=@category" )
.AddParameter( "@category" , category_id )
.ExecuteAndReadInteger( 0 );
}
public static int GetTotalSearchCount( SqlConnection connection , string term )
{
return new SQLBuilder( connection , "SELECT count(*) FROM v_repository_search" )
.AddLike( "search_index" , term )
.ExecuteAndReadInteger( 0 );
}
public void LoadSearch( SqlConnection connection , string term , int page , int items_per_page )
{
using SqlDataReader reader = new SQLBuilder( connection , "SELECT * FROM v_repository_search" , "title" )
.AddLike( "search_index" , term )
.SetPaging( page , items_per_page )
.ExecuteReader( );
while ( reader.Read( ) )
{
BookData book = new( )
{
ID = reader.Int( "id" ) ,
UUID = reader.StringSafe( "uuid" ) ,
CategoryID = reader.Int( "category_id" ) ,
CategoryName = reader.StringSafe( "category_name" ) ,
BookTypeID = reader.Int( "book_type_id" ) ,
BookTypeName = reader.StringSafe( "book_type_name" ) ,
Title = reader.StringSafe( "title" ) ,
Author = reader.StringSafe( "author" ) ,
Access = reader.StringSafe( "access" ) ,
Keywords = reader.StringSafe( "keywords" ) ,
/* Publisher = reader.StringSafe( "publisher" ) ,
PublishYear = reader.StringSafe( "publish_year" ) ,
Keywords = reader.StringSafe( "keywords" ) ,
Description = reader.StringSafe( "description" ) ,
CreateDate = reader.DateTime( "create_date" ) ,
Opisanie = reader.StringSafe( "opisanie" ) ,
KodDrugi = reader.StringSafe( "koddrugi" ) ,
*/
};
m_Elements.Add( book );
}
}
public void LoadCategory( SqlConnection connection , int category_id , int page , int books_per_page )
{
using SqlDataReader reader = new SQLBuilder( connection ,
// "select * from v_repository where include_category_id=category_id"
@"SELECT * FROM v_repository
WHERE category_id=@category and include_category_id=@category
ORDER BY sort, title
OFFSET @offset ROWS
FETCH NEXT @rows ROWS ONLY"
)
.AddParameter( "@category" , category_id )
.AddParameter( "@offset" , page * books_per_page )
.AddParameter( "@rows" , books_per_page )
.ExecuteReader( );
while ( reader.Read( ) )
{
BookData book = new( )
{
ID = reader.Int( "id" ) ,
UUID = reader.StringSafe( "uuid" ) ,
CategoryID = reader.Int( "category_id" ) ,
CategoryName = reader.StringSafe( "category_name" ) ,
BookTypeID = reader.Int( "book_type_id" ) ,
BookTypeName = reader.StringSafe( "book_type_name" ) ,
Title = reader.StringSafe( "title" ) ,
Author = reader.StringSafe( "author" ) ,
Access = reader.StringSafe( "access" ) ,
/* Publisher = reader.StringSafe( "publisher" ) ,
PublishYear = reader.StringSafe( "publish_year" ) ,
Keywords = reader.StringSafe( "keywords" ) ,
Description = reader.StringSafe( "description" ) ,
CreateDate = reader.DateTime( "create_date" ) ,
Opisanie = reader.StringSafe( "opisanie" ) ,
KodDrugi = reader.StringSafe( "koddrugi" ) , */
};
m_Elements.Add( book );
}
}
}

12
Data/CategoryItemData.cs Normal file
View File

@@ -0,0 +1,12 @@
namespace Library;
public class CategoryItemData
{
public int ID { init; get; } = 0;
public int ParentID { init; get; } = 0;
public int ElementCount { init; get; } = 0;
public int Depth { init; get; } = 0;
public string Name { init; get; } = "";
public string Catego_opis { init; get; } = "";
public List<CategoryItemData> Children { init; get; } = new( );
}

54
Data/CategoryTreeData.cs Normal file
View File

@@ -0,0 +1,54 @@
using Microsoft.Data.SqlClient;
namespace Library;
public class CategoryTreeData
{
private readonly List<CategoryItemData> m_RootElements = new( );
public List<CategoryItemData> RootElements { get => m_RootElements; }
private readonly Dictionary<int,CategoryItemData> m_AllElements = new( );
public int GetRootID( )
{
if ( m_RootElements.Count == 0 )
return 0;
return m_RootElements[0].ID;
}
public CategoryItemData? FindCategory( int id )
{
if ( m_AllElements.TryGetValue( id , out CategoryItemData? value ) )
return value;
return null;
}
public void LoadCategoryTree( SqlConnection connection )
{
using SqlDataReader reader = new SQLBuilder( connection , "select id,idfat,label,ime_x,cnt,lis_txt from [select_razdeli]" )
.ExecuteReader( );
while ( reader.Read( ) )
{
CategoryItemData model = new( )
{
ID = reader.Int( "id" ) ,
ParentID = reader.Int( "idfat" ) ,
ElementCount = reader.Int( "cnt" ) ,
Depth = reader.Int( "label" ) ,
Name = reader.StringSafe( "ime_x" ).Trim( ),
Catego_opis = reader.StringSafe( "lis_txt" ).Trim( )
};
m_AllElements[model.ID] = model;
}
foreach ( CategoryItemData m in m_AllElements.Values )
{
if ( m_AllElements.TryGetValue( m.ParentID , out CategoryItemData? parent ) )
parent.Children.Add( m );
else
m_RootElements.Add( m );
}
}
}

19
Data/PagingData.cs Normal file
View File

@@ -0,0 +1,19 @@
namespace Library;
public class PagingData
{
public int CurrentPage { init; get; } = 0;
public int LastPage { init; get; } = 0;
public int TotalPages { init; get; } = 0;
private readonly string m_BaseURL = "";
public string Url( int page ) => m_BaseURL + page.ToString( );
public PagingData( string base_url , int current_page , int total_items , int items_per_page )
{
m_BaseURL = base_url;
CurrentPage = current_page;
LastPage = ( total_items - 1 ) / items_per_page;
TotalPages = ( total_items + items_per_page - 1 ) / items_per_page;
}
}

65
Data/UserData.cs Normal file
View File

@@ -0,0 +1,65 @@
using Microsoft.Data.SqlClient;
namespace Library;
public class UserData
{
public int ID = 0;
public string Name = default!;
public string Email = default!;
public string SessionID = default!;
public bool IsActive = true;
public bool IsAdmin = false;
public static UserData? FromSession( SqlConnection connection , string session_id )
{
using SqlDataReader reader = new SQLBuilder( connection , "EXEC [usp_user_session_validate] @session_id" )
.AddParameter( "@session_id" , session_id )
.ExecuteReader( );
if ( reader == null || !reader.Read( ) )
return null;
int user_id = reader.Int( "id" , 0 );
if ( user_id <= 0 )
return null;
return new( )
{
ID = user_id ,
Name = reader.String( "display_name" ) ?? "" ,
Email = reader.String( "email" ) ?? "" ,
SessionID = session_id ,
IsAdmin = reader.Int( "is_admin" , 0 ) == 1 ,
IsActive = true
};
}
public static UserData? FromID( SqlConnection connection , int id )
{
using SqlDataReader reader = new SQLBuilder( connection , "SELECT [id],[ime],[user_tip],[mail],[is_active] FROM [dom] WHERE [id]=@id" )
.AddParameter( "@id" , id )
.ExecuteReader( );
if ( reader == null || !reader.Read( ) )
return null;
return FromReader( reader );
}
public static UserData? FromReader( SqlDataReader reader )
{
int user_id = reader.Int( "id" , 0 );
if ( user_id <= 0 )
return null;
return new( )
{
ID = user_id ,
Name = reader.String( "ime" ) ?? "" ,
Email = reader.String( "mail" ) ?? "" ,
IsAdmin = ( reader.String( "user_tip" ) ?? "" ) == "admin" ,
IsActive = reader.Int( "is_active" , 0 ) != 0
};
}
}

14
GlobalSuppressions.cs Normal file
View File

@@ -0,0 +1,14 @@
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage( "Style" , "IDE0130:Namespace does not match folder structure" ,
Justification = "<Pending>" ,
Scope = "module" )]
[assembly: SuppressMessage( "Style" , "IDE0290:Use primary constructor" ,
Justification = "<Pending>" ,
Scope = "module" )]

30
LibraryWeb.csproj Normal file
View File

@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<Content Remove="appsettings.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BitMiracle.LibTiff.NET" Version="2.4.660" />
<PackageReference Include="Docnet.Core" Version="2.6.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="10.0.10" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="7.0.2" />
<PackageReference Include="SkiaSharp" Version="4.151.0" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="4.151.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="wwwroot\NewFolder\" />
</ItemGroup>
<ItemGroup>
<None Include="appsettings.json" />
</ItemGroup>
</Project>

25
LibraryWeb.sln Normal file
View File

@@ -0,0 +1,25 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.3.32929.385
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibraryWeb", "LibraryWeb.csproj", "{CBC258EA-359C-49BD-9E2C-71E6C317A7C2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{CBC258EA-359C-49BD-9E2C-71E6C317A7C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CBC258EA-359C-49BD-9E2C-71E6C317A7C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CBC258EA-359C-49BD-9E2C-71E6C317A7C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CBC258EA-359C-49BD-9E2C-71E6C317A7C2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {92464CCA-A46F-456C-A15A-6B6B9BA5D749}
EndGlobalSection
EndGlobal

19
Models/BasePageModel.cs Normal file
View File

@@ -0,0 +1,19 @@
using Microsoft.Data.SqlClient;
namespace Library.Models;
public class BasePageModel
{
public string PageTitle = "";
public UserData? UserData = null;
public CategoryTreeData CategoryTree { init => m_CategoryTree = value; get => m_CategoryTree; }
private CategoryTreeData m_CategoryTree = new( );
public BasePageModel( SqlConnection? db )
{
if ( db != null )
{
m_CategoryTree.LoadCategoryTree( db! );
}
}
}

13
Models/ErrorPageModel.cs Normal file
View File

@@ -0,0 +1,13 @@
namespace Library.Models;
public class ErrorPageModel : BasePageModel
{
public string? RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
public ErrorPageModel( )
: base( null )
{
}
}

17
Models/PDFViewModel.cs Normal file
View File

@@ -0,0 +1,17 @@
using Microsoft.Data.SqlClient;
namespace Library.Models;
public class PDFViewModel : BasePageModel
{
public readonly int PageCount = 4;
public string UUID { init; get; } = null!;
public string Title { init; get; } = "Áåç çàãëàâèå";
public PDFViewModel( SqlConnection? db , string uuid , int page_count )
: base( db )
{
PageCount = page_count;
UUID = uuid;
}
}

View File

@@ -0,0 +1,47 @@
using Microsoft.Data.SqlClient;
using System.Net.Mail;
namespace Library.Models;
public class RepositoryDetailsPageModel : BasePageModel
{
public BookData BookData { get; } = null!;
public List<CategoryItemData> CategoryPath { init; get; } = new( );
public List<AttachmentData> Attachments { init; get; } = new( );
// public CategoryItemData CategoryData { init; get; } = null!;
// public PagingData PagingData { init; get; } = null!;
public RepositoryDetailsPageModel( SqlConnection? db , int element_id )
: base( db )
{
BookData = BookData.WithID( db! , element_id )!;
Attachments = AttachmentData.GetList( db! , element_id );
BuildCategoryPath( CategoryTree.FindCategory( BookData.CategoryID ) );
/* CategoryData = LookupCategoryData( category_index );
BuildCategoryPath( CategoryTree.FindCategory( CategoryData.ParentID ) );
Books.LoadCategory( db! , CategoryData.ID , page_index , 20 );
int books_in_category = BookListData.GetCategoryCount( db! , category_index );
PagingData = new PagingData( "/Repository/" + category_index + "?page=" , page_index , books_in_category , 20 ); */
}
/* private CategoryItemData LookupCategoryData( int category_index )
{
CategoryItemData? category_data = CategoryTree.FindCategory( category_index );
if ( category_data != null )
return category_data!;
return CategoryTree.FindCategory( CategoryTree.GetRootID( ) )!;
}
*/
private void BuildCategoryPath( CategoryItemData? item )
{
if ( item == null )
return;
CategoryItemData? parent_item = CategoryTree.FindCategory( item!.ParentID );
if ( parent_item != null )
BuildCategoryPath( parent_item );
CategoryPath.Add( item! );
}
}

View File

@@ -0,0 +1,44 @@
using Microsoft.Data.SqlClient;
namespace Library.Models;
public class RepositoryPageModel : BasePageModel
{
public BookListData Books { get; } = new( );
public CategoryItemData CategoryData { init; get; } = null!;
public List<CategoryItemData> CategoryPath { init; get; } = new( );
public PagingData PagingData { init; get; } = null!;
public RepositoryPageModel( SqlConnection? db , int category_index , int page_index )
: base( db )
{
CategoryData = LookupCategoryData( category_index );
BuildCategoryPath( CategoryTree.FindCategory( CategoryData.ParentID ) );
Books.LoadCategory( db! , CategoryData.ID , page_index , 20 );
int books_in_category = BookListData.GetCategoryCount( db! , category_index );
PagingData = new PagingData( "/Repository/" + category_index + "?page=" , page_index , books_in_category , 20 );
}
private CategoryItemData LookupCategoryData( int category_index )
{
CategoryItemData? category_data = CategoryTree.FindCategory( category_index );
if ( category_data != null )
return category_data!;
return CategoryTree.FindCategory( CategoryTree.GetRootID( ) )!;
}
private void BuildCategoryPath( CategoryItemData? item )
{
if ( item == null )
return;
CategoryItemData? parent_item = CategoryTree.FindCategory( item!.ParentID );
if ( parent_item != null )
BuildCategoryPath( parent_item );
CategoryPath.Add( item! );
}
}

49
Models/SearchPageModel.cs Normal file
View File

@@ -0,0 +1,49 @@
using Microsoft.Data.SqlClient;
namespace Library.Models;
public class SearchPageModel : BasePageModel
{
public BookListData Books { get; } = new( );
public PagingData PagingData { init; get; } = null!;
public string SearchTerm { init; get; } = null!;
public SearchPageModel( SqlConnection? db , string term , int page )
: base( db )
{
SearchTerm = term;
Books.LoadSearch( db! , term , page , 20 );
int books_in_search = BookListData.GetTotalSearchCount( db! , term );
PagingData = new PagingData( "/Search?q=" + term + "&page=" , page , books_in_search , 20 );
/* CategoryData = LookupCategoryData( category_index );
BuildCategoryPath( CategoryTree.FindCategory( CategoryData.ParentID ) );
Books.LoadCategory( db! , CategoryData.ID , page_index , 20 );
int books_in_category = BookListData.GetCategoryCount( db! , category_index );
PagingData = new PagingData( "/Repository/" + category_index + "?page=" , page_index , books_in_category , 20 );*/
}
/* private CategoryItemData LookupCategoryData( int category_index )
{
CategoryItemData? category_data = CategoryTree.FindCategory( category_index );
if ( category_data != null )
return category_data!;
return CategoryTree.FindCategory( CategoryTree.GetRootID( ) )!;
}
private void BuildCategoryPath( CategoryItemData? item )
{
if ( item == null )
return;
CategoryItemData? parent_item = CategoryTree.FindCategory( item!.ParentID );
if ( parent_item != null )
BuildCategoryPath( parent_item );
CategoryPath.Add( item! );
} */
}

32
Program.cs Normal file
View File

@@ -0,0 +1,32 @@
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Library;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services
.Configure<KestrelServerOptions>( builder.Configuration.GetSection( "Kestrel" ) )
// .Configure<FormOptions>( x => { x.ValueLengthLimit = int.MaxValue; x.MultipartBodyLengthLimit = int.MaxValue; x.MultipartHeadersLengthLimit = int.MaxValue; } )
.AddControllersWithViews( )
.AddRazorRuntimeCompilation( );
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseMiddleware<ExceptionMiddleware>( );
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();

View File

@@ -0,0 +1,12 @@
{
"profiles": {
"Library": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:5000"
}
}
}

13
Views/Home/Index.cshtml Normal file
View File

@@ -0,0 +1,13 @@
@model BasePageModel
<div class="text-center display-6 page-title-color"><b>
Дигитална библиотека
</b></div>
<div class="text-center">
<img src="~/@Configuration["Images"]/logo_sm.png" alt="Дигитална библиотека">
</div>
<div class="text-center">
<br/>
<img src="~/@Configuration["Images"]/main_bairaci_sm.png">
</div>

8
Views/Home/Login.cshtml Normal file
View File

@@ -0,0 +1,8 @@
@model BasePageModel
<div class="text-center display-6 page-title-color"><b>
LOGIN
</b></div>
<div class="text-center">
</div>

View File

@@ -0,0 +1,94 @@
@model RepositoryPageModel
<div class="text-center display-6 page-title-color"><b>
ДИГИТАЛНИ КОЛЕКЦИИ
</b></div>
<style>
.grid-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); /* Подрежда елементите в грид с ширина от поне 200px, но равномерно разпределя останалото пространство */
grid-gap: 50px; /* Разстояние между елементите */
}
.grid-item {
border: 0px solid #ccc;
padding: 10px;
text-align: center;
}
</style>
@if (Model.CategoryData.Depth == 0)
{
<div>
<div class="grid-container">
@foreach (CategoryItemData category in Model.CategoryData.Children)
{
int q = category.Depth;
<div class="grid-item">
<div>
<img src="~/@Configuration["Images"]/book_@(@category.ID.ToString()).png" ) width="320" height="370">
</div>
<div>
<a href="/Repository/@category.ID">@category.Name</a>
</div>
</div>
}
</div>
</div>
}
else
{
<div class="container row mt-3 mb-3">
<div class="nav_tree">
@foreach (CategoryItemData category in Model.CategoryPath)
{
<div class="nav_tree_item_container">
@for (int x = 0; x < category.Depth; x++)
{
<span class="indent"></span>
}
<span class="indent"><i class="fa fa-caret-down"></i></span>
<a href="/Repository/@category.ID">@category.Name </a>
</div>
}
<div class="nav_tree_item_container">
@for (int x = 0; x < Model.CategoryData.Depth; x++)
{
<span class="indent"></span>
}
<span class="indent"><i class="fa fa-caret-down"></i></span>
<span><b>@Model.CategoryData.Name</b></span>
</div>
@foreach (CategoryItemData category in Model.CategoryData.Children)
{
<div class="nav_tree_item_container">
@for (int x = 0; x < category.Depth; x++)
{
<span class="indent"></span>
}
<span class="indent"><i class="fa fa-caret-right"></i></span>
<a href="/Repository/@category.ID">@category.Name</a>
</div>
}
</div>
</div>
<div class="text-center display-6 page-title-color mt-3">
<font size=4><b> Публикации в категория </b></font>
<font size=5><b> @Model.CategoryData.Name </b></font>
<div class="container row mt-3 mb-3">
<font size=3 color="#000000" align=left>
<b><i>
@Model.CategoryData.Catego_opis
</i></b>
</font>
</div>
</div>
}
<div class="container row mt-3 mb-3">
@foreach ( BookData book in Model.Books ) {
@await Html.PartialAsync("~/Views/Shared/RepositoryBookElement.cshtml", book);
}
</div>
@await Html.PartialAsync( "~/Views/Shared/PagingArea.cshtml" , Model.PagingData )

View File

@@ -0,0 +1,85 @@
@model RepositoryDetailsPageModel
<div class="text-center display-6 page-title-color"><b>
ДИГИТАЛНИ КОЛЕКЦИИ
</b></div>
<div class="col-12 py-1">
<div class="nav_tree">
@foreach (CategoryItemData category in Model.CategoryPath)
{
<div class="nav_tree_item_container">
@for (int x = 0; x < category.Depth; x++)
{
<span class="indent"></span>
}
<span class="indent"><i class="fa fa-caret-down"></i></span>
<a href="/Repository/@category.ID">@category.Name</a>
</div>
}
<div class="nav_tree_item_container">
@for (int x = 0; x <= Model.CategoryPath[Model.CategoryPath.Count-1].Depth ; x++)
{
<span class="indent"></span>
}
<span class="indent"><i class="fa fa-caret-down"></i></span>
<b>@Model.BookData.Title</b>
</div>
</div>
</div>
@await Html.PartialAsync("~/Views/Shared/RepositoryBookElement_1.cshtml", Model.BookData)
<div class="col-12 py-1">
<div class="card">
<div class="card-header">
Съдържание
</div>
<div class="card-body">
@foreach ( AttachmentData attachment in Model.Attachments )
{
if ( attachment.Format == "pdf")
{
<a href="/Repository/PDFView/@attachment.UUID" target="preview_frame">
<h5 class="card-title display-7"><font size="2">@attachment.Title</font></h5>
</a>
}
else if (attachment.Format == "jpg")
{
<a href="/Repository/Image/@attachment.UUID" target="preview_frame">
<h5 class="card-title display-7"><font size="2">@attachment.Title</font></h5>
</a>
}
else if (attachment.Format == "mp4")
{
<a href="/Repository/Video/@attachment.UUID" target="preview_frame">
<h5 class="card-title display-7"><font size="2">@attachment.Title</font></h5>
</a>
}
else
{
<a href="@attachment.Link" target="_blank">
<h5 class="card-title display-7"><font size="2">@attachment.Title</font></h5>
</a>
}
}
</div>
</div>
</div>
<div class="col-12 py-1">
<div class="card">
<div class="card-header">
Преглед
</div>
<div class="card-body">
<div class="w-100 ratio ratio-1x1">
<iframe name="preview_frame" title="Заглавие"></iframe>
</div>
</div>
</div>
</div>

25
Views/Home/Search.cshtml Normal file
View File

@@ -0,0 +1,25 @@
@model SearchPageModel
<div class="text-center display-6 page-title-color"><b>
Търсене
</b></div>
<form action="/Search">
<input type="text" name="q" value="@Model.SearchTerm" />
<input type="submit" />
</form>
<div class="text-center display-6 page-title-color"><font size=5><b>
Резултати за : @Model.SearchTerm
</font></b></div>
<div class="container row mt-3 mb-3">
@foreach (BookData book in Model.Books)
{
@await Html.PartialAsync("~/Views/Shared/RepositoryBookElement.cshtml", book)
}
</div>
@await Html.PartialAsync( "~/Views/Shared/PagingArea.cshtml" , Model.PagingData )

View File

@@ -0,0 +1,5 @@
@model BasePageModel
<div class="text-center display-6 page-title-color"><b>
Карта на сайта
</b></div>
@await Html.PartialAsync( "~/Views/Shared/SiteMapTree.cshtml" , Model.CategoryTree )

View File

@@ -0,0 +1,51 @@
@model PDFViewModel
@{
Layout = null;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="~/css/pdf_view.css" />
<title>Виртуална библиотека</title>
</head>
<body class="full-view-height">
<main role="main" class="full-view-height">
<div class="full-width full-height no-padding">
<div class="vlayout full-height">
<div class="header">
<div class="header-info-bar">@Model.Title</div>
<div class="header-paging-bar">Страница: <span id="current_page">1</span> / @Model.PageCount</div>
</div>
<div class="full-width hlayout layout-scale-1 overflow-auto">
<div class="navigation-bar">
<div class="vlayout">
@for ( int p = 0 ; p < Model.PageCount ; p++ )
{
<a class="pdf-page-link @Html.IF( p == 0 , "selected" )" href="/Repository/PDFPage/@Model.UUID/@p" data-page="@(p+1)">
<img class="pdf-page-thumb" src="/Repository/PDFThumb/@Model.UUID/@p" />
<div class="pdf-page-label">#@(p+1) / @Model.PageCount</div>
</a>
}
</div>
</div>
<div id="content_div" class="content-area overflow-auto">
<img id="page_preview" style="max-width: 100%;" src="/Repository/PDFPage/@Model.UUID/0" />
</div>
</div>
</div>
</div>
</main>
<script src="~/js/vendor/jquery-3.6.0.min.js"></script>
<script src="~/js/pdf_view.js"></script>
</body>
</html>

View File

@@ -0,0 +1,59 @@
@model PagingData
@{
bool has_previous = Model.CurrentPage > 0;
bool has_next = Model.CurrentPage < Model.LastPage;
int first_page = Math.Max( 0 , Model.CurrentPage - 3 );
int last_page = Math.Min( first_page + 6 , Model.LastPage );
if ( last_page - first_page < 6 )
first_page = Math.Max( 0 , last_page - 6 );
bool show_dots_before = first_page > 0;
if ( show_dots_before )
first_page += 2; // shift first page to make room for [1][...]
bool show_dots_after = last_page < Model.LastPage;
if (show_dots_after)
last_page -= 2; // shift last page to make room for [...][last]
}
@if ( Model.LastPage > 0 )
{
<nav>
<ul class="pagination justify-content-center pagination-sm">
<li class="page-item @(has_previous ? "" : "disabled")">
<a class="page-link" href="@Model.Url( Model.CurrentPage - 1 )" tabindex="-1">Previous</a>
</li>
@if ( show_dots_before )
{
<li class="page-item"><a class="page-link" href="@Model.Url( 0 )">1</a></li>
<li class="page-item disabled"><span class="page-link">...</span></li>
}
@for ( int p = first_page ; p <= last_page; p++ )
{
if ( Model.CurrentPage == p )
{
<li class="page-item active"><span class="page-link">@(p+1)</span></li>
}
else
{
<li class="page-item"><a class="page-link" href="@Model.Url( p )">@(p+1)</a></li>
}
}
@if ( show_dots_after )
{
<li class="page-item disabled"><span class="page-link">...</span></li>
<li class="page-item"><a class="page-link" href="@Model.Url( Model.LastPage )">@( Model.LastPage + 1 )</a></li>
}
<li class="page-item @(has_next ? "" : "disabled")">
<a class="page-link" href="@Model.Url( Model.CurrentPage + 1 )" tabindex="-1">Next</a>
</li>
</ul>
</nav>
}

View File

@@ -0,0 +1,58 @@
@model BookData
<!-- <div class="col-xs-12 col-sm-12 col-md-4 col-lg-4 px-1 py-1"> -->
<div class="col-xs-12 col-sm-12 col-md-6 col-lg-6 px-1 py-1">
<div class="card">
<div class="card-header">
@Model.BookTypeName
</div>
<div class="card-body">
@if (!string.IsNullOrEmpty(Model.UUID))
{
<img src="/Repository/Cover/@Model.UUID" style="width: 8em;" />
}
<a href="/Repository/Details/@Model.ID">
<h7 class="card-title display-7"><br><b>@Model.Title</b></h7>
</a>
<p><font size="2">
<b>Автор : </b>@Model.Author<br>
<b>Ключови думи : </b>@Model.Keywords<br>
@if (!string.IsNullOrEmpty(Model.Publisher))
{ <b>Издат.данни : </b> @Model.Publisher<br> }
@if (!string.IsNullOrEmpty(Model.PublishYear))
{ <b>Год.издаване : </b>@Model.PublishYear<br>}
@if (!string.IsNullOrEmpty(Model.Description))
{ <b>Размер : </b>@Model.Description<br> }
@if (!string.IsNullOrEmpty(Model.Opisanie))
{ <b>Описание : </b>@Model.Opisanie<br> }
@if (!string.IsNullOrEmpty(Model.KodDrugi))
{ <b>Език : </b>@Model.KodDrugi<br> }
@if (!string.IsNullOrEmpty(Model.Access))
{ <b>Access : </b>@Model.Access <br> }
</font>
</p>
</div>
</div>
</div>
<!--
<a href="/Repository/Details/@Model.ID">
<h7 class="card-title display-7"><b>@Model.Title</b></h7>
</a>
<p><font size="2"><b>Автор : </b>@Model.Author</font></p>
<p><font size="2"><b>Ключови думи : </b><small class="card-text">@Model.Keywords</small></font></p>
<p><font size="2"><b>Издат.данни : </b><small class="card-text">@Model.Publisher</small></font></p>
<p><font size="2"><b>Год.издаване : </b><small class="card-text">@Model.PublishYear</small></font></p>
<p><font size="2"><b>Размер : </b><small class="card-text">@Model.Description</small></font></p>
<p><font size="2"><b>Описание : </b><small class="card-text">@Model.Opisanie</small></font></p>
<p><font size="2"><b>Език : </b><small class="card-text">@Model.KodDrugi</small></font></p>
<p><b>Автор : </b><class ="h6">@Model.Author</p>
<p><b>Ключови думи : </b><small class="card-text">@Model.Keywords</small></p>
<p><b>Издат.данни : </b><small class="card-text">@Model.Publisher</small></p>
<p><b>Год.издаване : </b><small class="card-text">@Model.PublishYear</small></p>
<p><b>Размер : </b><small class="card-text">@Model.Description</small></p>
<p><b>Описание : </b><small class="card-text">@Model.Opisanie</small></p>
<p><b>Език : </b><small class="card-text">@Model.KodDrugi</small></p>
-->

View File

@@ -0,0 +1,35 @@
@model BookData
<div class="col-12 py-1">
<div class="card">
<div class="card-header">
@Model.BookTypeName
</div>
<div class="card-body">
@if (!string.IsNullOrEmpty(Model.UUID))
{
<img src="./Repository/Cover/@Model.UUID" style="width: 8em;" />
}
<a href="/Repository/Details/@Model.ID">
<h7 class="card-title display-7"><br><b>@Model.Title</b></h7>
</a>
<p><font size="2">
<b>Автор : </b>@Model.Author<br>
<b>Ключови думи : </b>@Model.Keywords<br>
@if (!string.IsNullOrEmpty(Model.Publisher))
{ <b>Издат.данни : </b> @Model.Publisher<br> }
@if (!string.IsNullOrEmpty(Model.PublishYear))
{ <b>Год.издаване : </b>@Model.PublishYear<br>}
@if (!string.IsNullOrEmpty(Model.Description))
{ <b>Размер : </b>@Model.Description<br> }
@if (!string.IsNullOrEmpty(Model.Opisanie))
{ <b>Описание : </b>@Model.Opisanie<br> }
@if (!string.IsNullOrEmpty(Model.KodDrugi))
{ <b>Език : </b>@Model.KodDrugi<br> }
@if (!string.IsNullOrEmpty(Model.Access))
{ <b>Access : </b>@Model.Access<br> }
</font>
</p>
</div>
</div>
</div>

View File

@@ -0,0 +1,8 @@
@model CategoryTreeData
<div class="nav_tree">
@foreach (CategoryItemData root_item in Model.RootElements)
{
@await Html.PartialAsync( "~/Views/Shared/SiteMapTreeItem.cshtml" , root_item )
}
</div>

View File

@@ -0,0 +1,19 @@
@model CategoryItemData
<div class="nav_tree_item_container">
@for (int x = 0; x < Model.Depth; x++)
{
<span class="indent"></span>
}
<span class="indent"><i class="fa fa-caret-right"></i></span>
<div class="nav_tree_item_data">
<a href="/Repository/@Model.ID">@Model.Name</a>
<span>Публикации: @Model.ElementCount</span>
</div>
</div>
@foreach ( CategoryItemData child in Model.Children )
{
@await Html.PartialAsync( "~/Views/Shared/SiteMapTreeItem.cshtml" , child )
}

View File

@@ -0,0 +1,22 @@
<div class="modal" id="forgotPasswordDialog" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<form class="js_service" data-service="/Api/User/Forgot">
<div class="modal-header">
<h5 class="modal-title">Забравена парола</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="Username">Потребител (e-mail)<span class="text-danger">*</span></label>
<input type="text" name="username" class="form-control" placeholder="Потребител" autocomplete="off" autofocus required>
</div>
<div class="form-error-message mb-3"></div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-success mx-auto w-100">Изпрати</button>
</div>
</form>
</div>
</div>
</div>

View File

@@ -0,0 +1,35 @@
<div class="modal" id="loginDialog" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<form class="js_service" data-service="/Api/User/Login">
<div class="modal-header">
<h5 class="modal-title">Вход в системата</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="Username">Потребител<span class="---text-danger">*</span></label>
<input type="text" name="username" >
<!-- <input type="email" name="username" class="form-control" placeholder="Потребител" autocomplete="current-user" autofocus required>-->
</div>
<div class="mb-3">
<label for="Password">Парола<span class="---text-danger">*</span></label>
<input type="password" name="password" class="form-control" placeholder="Парола" autocomplete="current-password" required minlength="5">
<div class="d-flex flex-column align-items-end justify-content-end">
<a class="toggle_dialog link-success" data-hide="loginDialog" data-show="forgotPasswordDialog" href="#"><small>Забравена парола?</small></a>
</div>
</div>
<div class="form-error-message mb-3"></div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-success mx-auto w-100">Влез</button>
</div>
<!--<div class="modal-footer d-flex flex-column align-items-center justify-content-center">
Нямате регистрация?
<a class="toggle_dialog link-success" data-hide="loginDialog" data-show="registerDialog" href="#">Регистрирай се</a>
</div>
-->
</form>
</div>
</div>
</div>

View File

@@ -0,0 +1,38 @@
<div class="modal" id="registerDialog" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<form class="js_service" data-service="/Api/User/Register">
<div class="modal-header">
<h5 class="modal-title">Регистрация</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="Username">Потребител (e-mail)<span class="text-danger">*</span></label>
<input type="email" name="username" class="form-control" placeholder="Потребител" autocomplete="off" autofocus required>
</div>
<div class="mb-3">
<label for="Username">Име<span class="text-danger">*</span></label>
<input type="text" name="name" class="form-control" placeholder="Име" autocomplete="off" required minlength="5">
</div>
<div class="mb-3">
<label for="Password">Парола<span class="text-danger">*</span></label>
<input type="password" id="password" name="password" class="form-control" placeholder="Парола" autocomplete="off" required minlength="5">
</div>
<div class="mb-3">
<label for="Password">Парола (отново)<span class="text-danger">*</span></label>
<input type="password" name="repeat" class="form-control" placeholder="Парола" data-rule-equalTo="#password" autocomplete="off" required minlength="5">
</div>
<div class="form-error-message mb-3"></div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-success mx-auto w-100">Регистрация</button>
</div>
<div class="modal-footer d-flex flex-column align-items-center justify-content-center">
Вече сте регистриран?
<a class="toggle_dialog link-success" data-hide="registerDialog" data-show="loginDialog" href="#">Влез в системата</a>
</div>
</form>
</div>
</div>
</div>

View File

@@ -0,0 +1,14 @@
<div class="modal" id="pleaseWaitDialog" data-backdrop="static" data-keyboard="true" role="dialog">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Моля изчакайте...</h4>
</div>
<div class="modal-body">
<div class="text-center py-5">
<div class="spinner-border text-success" role="status" style="width: 5rem; height: 5rem;"></div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,98 @@
@model BasePageModel
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Виртуална библиотека</title>
<link rel="stylesheet" href="~/css/vendor/bootstrap.css" />
<link rel="stylesheet" href="~/css/vendor/fontawesome/css/all.css" />
<link rel="stylesheet" href="~/css/common.css" />
@await RenderSectionAsync("Styles", required: false)
</head>
<body class="d-flex flex-column min-vh-100">
<header>
<nav class="navbar navbar-expand-lg navbar navbar-dark">
<div class="container-fluid">
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarTogglerDemo03" aria-controls="navbarTogglerDemo03" aria-expanded="false" aria-label="Покажи">
<span class="navbar-toggler-icon"></span>
</button>
<a class="navbar-brand hstack" href="/Home/Index">
<img src="~/@Configuration["Images"]/logo_sm.png" alt="Дигитална библиотека" width="40" height="80">
<span>
<font color="#ffffff">
<b> Дигитална библиотека</b>
<br />
@Configuration["OB_project"]
<br />
Университетска библиотека
</font>
</span>
</a>
<div class="collapse navbar-collapse" id="navbarTogglerDemo03">
<div class="flex-grow-1"></div>
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" href="/Repository"><font color="#ffffff"><b><u>ДИГИТАЛНИ КОЛЕКЦИИ</u></b></font></a>
<a class="nav-link" href="/Search"><font color="#ffffff"><b><u>ТЪРСЕНЕ</u></b></font></a>
</li>
<li class="nav-item">
@if (Model.UserData == null)
{
<a class="nav-link"><font color="#ffffff">Непознат потребител</font></a>
<a class="nav-link js_dialog" href="#" data-target="loginDialog"><font color="#ffffff"><b><u>ВХОД</u></b></font></a>
}
else
{
<div class="btn-group flex-wrap">
<button class="btn btn-success btn-sm dropdown-toggle" type="button" id="dropdownAdminMenu" data-bs-toggle="dropdown" aria-expanded="false">
@Model.UserData.Name
</button>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="dropdownAdminMenu">
<li><a class="link-danger dropdown-item js_service" href="#" data-navigate="/" data-service="/Api/User/Logout">Излез</a></li>
</ul>
</div>
}
</li>
</ul>
</div>
</div>
</nav>
</header>
<main role="main" class="my-3">
<div class="container-xl">
@RenderBody( )
</div>
</main>
<footer class="mt-auto border-top text-muted">
<div class="footer">
<div class="text-center">&copy; 2022 - Дигитална библиотека InexLib/INEX-Project Ltd.</div>
<div class="text-center">@Configuration["SN_project"]</div>
</div>
</footer>
@if (Model.UserData == null)
{
@await Html.PartialAsync( "~/Views/Shared/_DialogLogin.cshtml" );
@await Html.PartialAsync( "~/Views/Shared/_DialogRegister.cshtml" );
@await Html.PartialAsync( "~/Views/Shared/_DialogForgotPassword.cshtml" );
@await Html.PartialAsync( "~/Views/Shared/_DialogWait.cshtml" );
}
<script src="~/js/vendor/jquery-3.6.0.min.js"></script>
<script src="~/js/vendor/jquery.validate.min.js"></script>
<script src="~/js/vendor/messages_bg.min.js"></script>
<script src="~/js/vendor/bootstrap.bundle.min.js"></script>
<script src="~/js/common.js"></script>
<view-script append-version="false" />
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>

View File

@@ -0,0 +1,30 @@
@model BasePageModel
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Виртуална библиотека</title>
<link rel="stylesheet" href="~/css/vendor/bootstrap.css" />
<link rel="stylesheet" href="~/css/vendor/fontawesome/css/all.css" />
<link rel="stylesheet" href="~/css/common.css" />
@await RenderSectionAsync("Styles", required: false)
</head>
<body class="vh-100">
<main role="main" class="vh-100">
@RenderBody( )
</main>
<script src="~/js/vendor/jquery-3.6.0.min.js"></script>
<script src="~/js/vendor/jquery.validate.min.js"></script>
<script src="~/js/vendor/messages_bg.min.js"></script>
<script src="~/js/vendor/bootstrap.bundle.min.js"></script>
<script src="~/js/common.js"></script>
<view-script append-version="false" />
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>

View File

@@ -0,0 +1,7 @@
@using Library
@using Library.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, Library
@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration

3
Views/_ViewStart.cshtml Normal file
View File

@@ -0,0 +1,3 @@
@{
Layout = "_LayoutDefault";
}

23
appsettings.json.template Normal file
View File

@@ -0,0 +1,23 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"Kestrel": {
"EndpointDefaults": {
"Protocols": "Http1AndHttp2"
},
"Endpoints": {
"Http": {
"Url": "http://localhost:52557"
}
}
},
"AllowedHosts": "*",
"ConnectionString": "Server=tcp:<IP>,<PORT>;UID=<USERNAME>;PWD=<PASSWORD>;Database=<DBNAME>",
"DataDirectory": "<LOCAL_DIRECTORY_ENDING_WITH_SLASH>",
"TempDirectory": "<LOCAL_DIRECTORY_ENDING_WITH_SLASH>",
"ImagesPath": "img/"
}

100
wwwroot/css/common.css Normal file
View File

@@ -0,0 +1,100 @@
:focus {
outline: none;
}
.page-title-color {
color: #b32d00; /* #b74040; <20>.<2E><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> */
}
/* Footer styles */
.footer {
display: flex;
flex-grow: 1;
align-items: center;
flex-flow: column nowrap;
}
/* Header styles */
.navbar {
background-color: #355b9c; /* <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> = title <20>1d4a9b, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> #e3f2fd*/
}
.navbar-brand > img {
height: 2em;
}
.navbar-brand > span {
font-size: smaller;
margin-left: 10px;
color: #04763a; /* <20>. <20><><EFBFBD><EFBFBD><EFBFBD> */
text-decoration: none;
}
.navbar-brand:hover > span {
font-size: smaller;
margin-left: 10px;
color: #1560a1; /* <20>.<2E><><EFBFBD> */
text-decoration: underline;
}
.nav-link, .nav-link:visited, .nav-link:active {
color: #04763a !important; /* <20>.<2E><><EFBFBD><EFBFBD><EFBFBD> */
text-decoration: none;
}
.nav-link:hover {
color: #1560a1 !important; /* <20>.<2E><><EFBFBD> */
text-decoration: underline;
}
/* Navigation tree (sitemap) styles */
.nav_tree .nav_tree_item_container:first-child {
border-top: 1px solid rgba(0, 0, 0, 0.125);
}
.nav_tree_item_container:hover {
background-color: antiquewhite; /* #faebd7 - <20><>.<2E><><EFBFBD><EFBFBD><EFBFBD> */
}
.nav_tree_item_container {
display: flex;
align-items: center;
border: 1px solid rgba(0, 0, 0, 0.125);
border-top: none;
}
.nav_tree_item_container span.indent {
margin-left: 1em;
margin-right: 1em;
}
.nav_tree_item_data {
display: flex;
flex-grow: 1;
align-items: flex-start;
flex-flow: column nowrap;
padding: 0.25rem 0.5rem;
border-left: 1px solid rgba(0, 0, 0, 0.125);
}
.nav_tree_item_data span {
color: #033;
font-size: smaller;
font-weight: bold;
float: right;
}
.nav_tree_item_data a,
.nav_tree_item_data a:visited {
color: #033;
text-decoration: underline;
}
.nav_tree_item_data a:hover {
color: #099;
text-decoration: underline;
}

141
wwwroot/css/pdf_view.css Normal file
View File

@@ -0,0 +1,141 @@
body, main {
padding: 0;
margin: 0;
border: 0;
}
a {
text-decoration: none;
}
img {
vertical-align: middle;
}
.pdf-page-link {
display: block !important;
width: 135px;
height: 250px;
position: relative;
border: 1px solid #ccc;
margin: 5px;
}
.pdf-page-link.selected,
.pdf-page-link.selected:active,
.pdf-page-link.selected:hover {
border: 1px solid #f96;
cursor: auto;
}
.pdf-page-link:hover {
border: 1px solid #9f9;
}
.pdf-page-link:active {
border: 1px solid #cfc;
}
.pdf-page-link > .pdf-page-thumb {
margin: 0;
position: absolute;
top: 50%;
left: 50%;
-ms-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
.pdf-page-link > .pdf-page-label {
display: inline;
position: absolute;
width: 100%;
z-index: 1;
text-align: center;
color: #000;
background-color: #ccc;
margin: 0;
padding: 0;
}
.pdf-page-link.selected > .pdf-page-label,
.pdf-page-link.selected:active > .pdf-page-label,
.pdf-page-link.selected:hover > .pdf-page-label {
background-color: #f96;
}
.pdf-page-link:hover > .pdf-page-label {
background-color: #9f9;
}
.pdf-page-link:active > .pdf-page-label {
background-color: #cfc;
}
.full-width {
width: 100%;
}
.full-height {
height: 100%;
}
.full-view-height {
height: 100vh;
}
.no-padding {
padding: 0;
}
.vlayout {
display: flex;
flex-direction: column;
}
.hlayout {
display: flex;
flex-direction: row;
}
.layout-scale-1 {
flex-grow: 1;
}
.header {
align-self: stretch;
background-color: #444;
color: #fff;
display: flex;
padding: 10px;
flex-direction: row;
}
.header-info-bar {
flex-grow: 1;
align-self: center;
}
.header-paging-bar {
text-align: center;
align-self: center;
}
.overflow-auto {
overflow: auto;
}
.navigation-bar {
align-self: stretch;
overflow: auto;
flex-grow: 0;
width: 160px;
min-width: 160px;
background-color: #444;
}
.content-area {
background-color: #999;
align-self: stretch;
flex-grow: 1;
text-align: center;
}

11228
wwwroot/css/vendor/bootstrap.css vendored Normal file

File diff suppressed because it is too large Load Diff

1
wwwroot/css/vendor/bootstrap.css.map vendored Normal file

File diff suppressed because one or more lines are too long

7937
wwwroot/css/vendor/fontawesome/css/all.css vendored Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
wwwroot/img/vtu/book_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
wwwroot/img/vtu/book_4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
wwwroot/img/vtu/book_47.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
wwwroot/img/vtu/book_48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

BIN
wwwroot/img/vtu/logo_sm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 810 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

BIN
wwwroot/img/zagore/z3.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

174
wwwroot/js/common.js Normal file
View File

@@ -0,0 +1,174 @@
'use strict';
$.defaultHandlers = {
disableSubmitButton: function( form , disable) {
form.find(":submit").attr("disabled", disable);
},
startWait: function( ) {
$('#pleaseWaitDialog').modal('show');
},
endWait: function( ) {
$('#pleaseWaitDialog').modal('hide');
},
showErrorMessage: function( form , message ) {
var div = form.find(".form-error-message");
if (div.length) {
div.show();
div.html(message);
} else {
alert(message);
}
},
hideErrorMessage: function( form ) {
var div = form.find(".form-error-message");
if (div.length)
div.hide();
},
validateResponse: function( response ) {
if (!response)
return { Success: false, ErrorMessage: "Системна грешка." };
return {
Success: response.Success ?? false,
Result: response.Result ,
ErrorMessage: response.ErrorMessage ?? "Системна грешка."
};
},
postProcessSuccess: function( caller , response ) {
// if data-navigate="" is set, go there
var navigate_url = caller.data("navigate");
if (navigate_url) {
window.location = navigate_url.replace(':response', response);
return;
}
// fallback: refresh the page
window.location.reload();
} ,
processResponse: function( form , response ) {
var result = $.defaultHandlers.validateResponse( response );
if (!result.Success) {
$.defaultHandlers.showErrorMessage(form, result.ErrorMessage);
return;
}
$.defaultHandlers.postProcessSuccess(form, result.Result);
},
submitHandler: function( element , event ) {
event.preventDefault();
var form = $(element);
$.defaultHandlers.hideErrorMessage(form);
$.defaultHandlers.disableSubmitButton(form, true);
$.defaultHandlers.startWait();
$.ajax({
url: form.data("service"),
type: 'POST',
data: new FormData(element),
async: true,
cache: false,
contentType: false,
enctype: 'multipart/form-data',
processData: false,
success: function( response ) {
$.defaultHandlers.disableSubmitButton(form, false);
$.defaultHandlers.processResponse(form, response);
$.defaultHandlers.endWait();
},
error: function( xhr , status , error ) {
$.defaultHandlers.disableSubmitButton(form, false);
$.defaultHandlers.showErrorMessage(form, "XHR:" + xhr.responseText + "\n<br>Status: " + status + "\n<br>Error: " + error);
$.defaultHandlers.endWait();
}
});
return false;
} ,
processRestLinkResponse: function( sender , response ) {
var result = $.defaultHandlers.validateResponse( response );
if (!result.Success) {
alert( result.ErrorMessage );
return;
}
$.defaultHandlers.postProcessSuccess(sender, result.Result);
} ,
restLinkHandler: function( event ) {
event.preventDefault();
var sender = $(event.target);
sender.attr("disabled", true);
$.defaultHandlers.startWait();
$.ajax({
url: sender.data("service"),
type: 'GET',
async: true,
cache: false,
processData: false,
success: function( response ) {
sender.attr("disabled", false);
$.defaultHandlers.processRestLinkResponse(sender, response);
$.defaultHandlers.endWait();
},
error: function( xhr , status , error ) {
sender.attr("disabled", false);
alert("XHR:" + xhr.responseText + "\n<br>Status: " + status + "\n<br>Error: " + error);
$.defaultHandlers.endWait();
}
});
return false;
}
};
$(document).ready(function( ) {
//$("#pleaseWaitDialog").modal({ backdrop: 'static', keyboard: false });
//$("#pleaseWaitDialog").modal({ show:true, backdrop: 'static', keyboard: false });
// $("#pleaseWaitDialog").modal("show");
$('#pleaseWaitDialog').modal({ show: true, backdrop: 'static', keyboard: false });
// setTimeout(function () { $('#pleaseWaitDialog').modal('show'); }, 1000);
// setTimeout(function () { $('#pleaseWaitDialog').modal('hide'); }, 3000);
$('form.js_default').each(function( ) {
$(this).validate({});
});
$('form.js_service').each(function () {
$(this).validate({ submitHandler: $.defaultHandlers.submitHandler });
});
$('a.js_service').click(
$.defaultHandlers.restLinkHandler
);
$('button.js_service').click(
$.defaultHandlers.restLinkHandler
);
$('a.toggle_dialog').click(function( e ) {
$("#" + $(this).data('hide')).modal("hide");
var dialog = $("#" + $(this).data('show'));
dialog.modal("show");
dialog.find('[autofocus]').focus();
e.preventDefault();
return false;
});
$('a.js_dialog').click(function( e ) {
var dialog = $('#' + $(this).data('target'));
dialog.modal("toggle");
dialog.find('[autofocus]').focus();
e.preventDefault();
return false;
});
});

23
wwwroot/js/pdf_view.js Normal file
View File

@@ -0,0 +1,23 @@
'use strict';
$(document).ready(function () {
$('a.pdf-page-link').click(function (e) {
e.preventDefault();
if ($(this).hasClass('selected')) {
return false;
}
$('a.pdf-page-link').each(function (a) {
$(this).removeClass("selected");
});
$(this).addClass("selected");
$("#current_page").html($(this).data('page'));
$("#page_preview").attr("src", $(this).attr('href'));
$("#content_div").animate({ scrollTop: 0 });
return false;
});
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
wwwroot/js/vendor/jquery-3.6.0.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More