initial commit
317
.editorconfig
Normal 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
@@ -0,0 +1,6 @@
|
|||||||
|
.*
|
||||||
|
!.gitignore
|
||||||
|
!.editorconfig
|
||||||
|
appsettings.json
|
||||||
|
bin/
|
||||||
|
obj/
|
||||||
180
Api/RepositoryApi.cs
Normal 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
@@ -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
@@ -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" );
|
||||||
|
}
|
||||||
|
}
|
||||||
41
App_Code/ContextExtensions.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
94
App_Code/Controller/APIController.cs
Normal 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( ) )
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
202
App_Code/Controller/AbstractController.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
App_Code/Controller/PageController.cs
Normal 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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
16
App_Code/Exceptions/APIErrorException.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
16
App_Code/Exceptions/ErrorException.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
72
App_Code/Exceptions/ExceptionMiddleware.cs
Normal 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( ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
16
App_Code/Exceptions/RedirectException.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
21
App_Code/HtmlExtensions.cs
Normal 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
@@ -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
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
60
App_Code/ViewScriptTagHelper.cs
Normal 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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
65
Controllers/HomeController.cs
Normal 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 } );
|
||||||
|
}
|
||||||
|
}
|
||||||
212
Controllers/RepositoryController.cs
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
47
Models/RepositoryDetailsPageModel.cs
Normal 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! );
|
||||||
|
}
|
||||||
|
}
|
||||||
44
Models/RepositoryPageModel.cs
Normal 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
@@ -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
@@ -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();
|
||||||
12
Properties/launchSettings.json
Normal 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
@@ -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
@@ -0,0 +1,8 @@
|
|||||||
|
@model BasePageModel
|
||||||
|
|
||||||
|
<div class="text-center display-6 page-title-color"><b>
|
||||||
|
LOGIN
|
||||||
|
</b></div>
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
</div>
|
||||||
94
Views/Home/Repository.cshtml
Normal 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 )
|
||||||
85
Views/Home/RepositoryDetails.cshtml
Normal 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
@@ -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 )
|
||||||
5
Views/Home/Sitemap.cshtml
Normal 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 )
|
||||||
51
Views/Repository/PDFView.cshtml
Normal 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>
|
||||||
59
Views/Shared/PagingArea.cshtml
Normal 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>
|
||||||
|
}
|
||||||
58
Views/Shared/RepositoryBookElement.cshtml
Normal 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>
|
||||||
|
-->
|
||||||
35
Views/Shared/RepositoryBookElement_1.cshtml
Normal 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>
|
||||||
8
Views/Shared/SiteMapTree.cshtml
Normal 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>
|
||||||
19
Views/Shared/SiteMapTreeItem.cshtml
Normal 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 )
|
||||||
|
}
|
||||||
22
Views/Shared/_DialogForgotPassword.cshtml
Normal 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>
|
||||||
35
Views/Shared/_DialogLogin.cshtml
Normal 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>
|
||||||
38
Views/Shared/_DialogRegister.cshtml
Normal 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>
|
||||||
14
Views/Shared/_DialogWait.cshtml
Normal 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>
|
||||||
98
Views/Shared/_LayoutDefault.cshtml
Normal 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">© 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>
|
||||||
30
Views/Shared/_LayoutEmpty.cshtml
Normal 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>
|
||||||
7
Views/_ViewImports.cshtml
Normal 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
@@ -0,0 +1,3 @@
|
|||||||
|
@{
|
||||||
|
Layout = "_LayoutDefault";
|
||||||
|
}
|
||||||
23
appsettings.json.template
Normal 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
@@ -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
@@ -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
1
wwwroot/css/vendor/bootstrap.css.map
vendored
Normal file
7937
wwwroot/css/vendor/fontawesome/css/all.css
vendored
Normal file
BIN
wwwroot/css/vendor/fontawesome/webfonts/fa-brands-400.ttf
vendored
Normal file
BIN
wwwroot/css/vendor/fontawesome/webfonts/fa-brands-400.woff2
vendored
Normal file
BIN
wwwroot/css/vendor/fontawesome/webfonts/fa-regular-400.ttf
vendored
Normal file
BIN
wwwroot/css/vendor/fontawesome/webfonts/fa-regular-400.woff2
vendored
Normal file
BIN
wwwroot/css/vendor/fontawesome/webfonts/fa-solid-900.ttf
vendored
Normal file
BIN
wwwroot/css/vendor/fontawesome/webfonts/fa-solid-900.woff2
vendored
Normal file
BIN
wwwroot/css/vendor/fontawesome/webfonts/fa-v4compatibility.ttf
vendored
Normal file
BIN
wwwroot/css/vendor/fontawesome/webfonts/fa-v4compatibility.woff2
vendored
Normal file
BIN
wwwroot/img/vtu/book_3.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
wwwroot/img/vtu/book_4.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
wwwroot/img/vtu/book_47.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
wwwroot/img/vtu/book_48.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
wwwroot/img/vtu/logo_sm.png
Normal file
|
After Width: | Height: | Size: 228 KiB |
BIN
wwwroot/img/vtu/logo_sm_with_text.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
wwwroot/img/vtu/logo_sm_with_text_mae.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
wwwroot/img/vtu/logo_sm_with_text_r.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
wwwroot/img/vtu/logo_sm_with_text_r.psd
Normal file
BIN
wwwroot/img/vtu/logo_sm_with_text_rr.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
wwwroot/img/vtu/logo_sm_with_text_rr.psd
Normal file
BIN
wwwroot/img/vtu/main_bairaci_sm.png
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
wwwroot/img/zagore/book_179.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
wwwroot/img/zagore/book_180.png
Normal file
|
After Width: | Height: | Size: 344 KiB |
BIN
wwwroot/img/zagore/book_181.png
Normal file
|
After Width: | Height: | Size: 220 KiB |
BIN
wwwroot/img/zagore/book_182.png
Normal file
|
After Width: | Height: | Size: 810 KiB |
BIN
wwwroot/img/zagore/book_47.png
Normal file
|
After Width: | Height: | Size: 2.9 MiB |
BIN
wwwroot/img/zagore/book_48.png
Normal file
|
After Width: | Height: | Size: 3.4 MiB |
BIN
wwwroot/img/zagore/logo_sm.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
wwwroot/img/zagore/logo_sm_with_text.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
wwwroot/img/zagore/main_bairaci_sm.png
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
wwwroot/img/zagore/z3.jpeg
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
174
wwwroot/js/common.js
Normal 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
@@ -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;
|
||||||
|
});
|
||||||
|
});
|
||||||