├── .env ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── app ├── .gitignore ├── LICENSE ├── bin │ ├── init.pl │ ├── migrations │ │ ├── 0.14.0.sql │ │ ├── 0.18.0.sql │ │ ├── 0.18.4.sql │ │ ├── 0.18.5.sql │ │ ├── 0.2.0 │ │ ├── 0.2.1 │ │ ├── 0.2.10 │ │ ├── 0.2.12 │ │ ├── 0.2.13 │ │ ├── 0.2.17 │ │ ├── 0.2.18.sql │ │ ├── 0.2.19.sql │ │ ├── 0.2.22.sql │ │ ├── 0.2.23.sql │ │ ├── 0.2.26.sql │ │ ├── 0.2.8 │ │ ├── 0.20.5.sql │ │ ├── 0.21.1.sql │ │ ├── 0.26.0.sql │ │ ├── 0.31.3.sql │ │ ├── 0.31.4.sql │ │ ├── 0.32.1.sql │ │ ├── 0.34.4.sql │ │ ├── 0.7.0.sql │ │ └── 0.8.0.sql │ └── spool.pl ├── conf │ └── shm.conf ├── lib │ ├── Core │ │ ├── Acts.pm │ │ ├── ActsData.pm │ │ ├── App.pm │ │ ├── Base.pm │ │ ├── Billing.pm │ │ ├── Billing │ │ │ ├── Honest.pm │ │ │ └── Simpler.pm │ │ ├── Bonus.pm │ │ ├── Config.pm │ │ ├── Console.pm │ │ ├── Const.pm │ │ ├── Discounts.pm │ │ ├── Dns.pm │ │ ├── Domain.pm │ │ ├── Domain │ │ │ └── Registrator │ │ │ │ └── Nic.pm │ │ ├── DomainServices.pm │ │ ├── Events.pm │ │ ├── Identities.pm │ │ ├── Jobs.pm │ │ ├── Misc.pm │ │ ├── Orders.pm │ │ ├── Pay.pm │ │ ├── Profile.pm │ │ ├── Report.pm │ │ ├── Server.pm │ │ ├── ServerGroups.pm │ │ ├── Service.pm │ │ ├── Services │ │ │ ├── Dns.pm │ │ │ └── Web.pm │ │ ├── Sessions.pm │ │ ├── Spool.pm │ │ ├── SpoolHistory.pm │ │ ├── Sql │ │ │ └── Data.pm │ │ ├── Storage.pm │ │ ├── System │ │ │ ├── Logger.pm │ │ │ ├── Object.pm │ │ │ ├── Service.pm │ │ │ └── ServiceManager.pm │ │ ├── Task.pm │ │ ├── Template.pm │ │ ├── Test.pm │ │ ├── Transport │ │ │ ├── Http.pm │ │ │ ├── Local.pm │ │ │ ├── Mail.pm │ │ │ ├── Ssh.pm │ │ │ └── Telegram.pm │ │ ├── USObject.pm │ │ ├── User.pm │ │ ├── UserService.pm │ │ ├── Utils.pm │ │ ├── Withdraw.pm │ │ └── Zones.pm │ └── SHM.pm ├── public_html │ ├── 403.html │ ├── 404.html │ └── shm │ │ ├── admin │ │ ├── .htaccess │ │ ├── console.cgi │ │ ├── mail_test.cgi │ │ └── ssh_test.cgi │ │ ├── healthcheck.cgi │ │ ├── object.cgi │ │ ├── pay_systems │ │ ├── cryptocloud.cgi │ │ ├── cryptopay.cgi │ │ ├── freekassa.cgi │ │ ├── wallet.cgi │ │ ├── yookassa.cgi │ │ └── yoomoney.cgi │ │ ├── user │ │ ├── auth.cgi │ │ ├── logout.cgi │ │ └── pay_systems.cgi │ │ └── v1.cgi ├── scripts │ ├── reset_admin_pass.cgi │ └── set_us_settings.pl ├── sql │ └── shm │ │ ├── shm_data.sql │ │ ├── shm_dev_test_data.sql │ │ └── shm_structure.sql └── t │ ├── acts │ └── acts.t │ ├── api │ ├── admin.t │ ├── admin_services.t │ ├── auth.t │ ├── auth_basic.t │ ├── filter.t │ ├── passwd.t │ ├── passwd_reset.t │ ├── payment.t │ ├── services.t │ ├── user_reg.t │ └── user_services.t │ ├── app │ └── app.t │ ├── billing │ ├── calc_withdraw.t │ ├── create_new_service.t │ ├── full_test.t │ ├── full_test_simpler.t │ ├── functions.t │ ├── honest.t │ ├── money_back.t │ ├── next.t │ ├── remove_service.t │ ├── simpler.t │ ├── sub_services.t │ └── sub_services_composite.t │ ├── config │ └── config.t │ ├── domain │ └── domain.t │ ├── identities │ └── keys.t │ ├── logger │ └── logger.t │ ├── pay │ ├── forecast.t │ ├── forecast_test_period.t │ └── pay.t │ ├── report │ └── report.t │ ├── servers │ └── servers.t │ ├── services │ ├── commands.t │ ├── create.t │ ├── discounts.t │ ├── events.t │ ├── filters.t │ ├── service.t │ ├── tariff_change.t │ ├── user_service_object.t │ ├── user_services.t │ ├── utils.t │ └── withdraw.t │ ├── sessions │ └── functions.t │ ├── spool │ ├── make_task.t │ └── spool.t │ ├── sql │ ├── query_filter.t │ ├── query_select.t │ └── query_sort.t │ ├── storage │ └── functions.t │ ├── system │ ├── encoding.t │ └── services.t │ ├── task │ └── cmd.t │ ├── template │ └── parser.t │ ├── transport │ ├── http.t │ ├── mail.t │ ├── ssh.t │ └── telegram.t │ └── user │ ├── api_safe_args.t │ ├── events.t │ ├── functions.t │ └── reg.t ├── build.sh ├── contributing └── docker-compose.yml ├── docker-compose.yml ├── docs ├── index.yaml └── shm-0.0.1.tgz ├── enterprise └── docker-compose.yml ├── entry-api.sh ├── entry-core.sh ├── entry.sh ├── helm ├── README.md ├── k8s-shm │ ├── Chart.lock │ ├── Chart.yaml │ ├── README.md │ ├── charts │ │ ├── helm-common-0.2.12.tgz │ │ ├── helm-common │ │ │ ├── Chart.yaml │ │ │ ├── templates │ │ │ │ ├── _affinities.tpl │ │ │ │ ├── _env.tpl │ │ │ │ ├── _helpers.tpl │ │ │ │ ├── _resources.tpl │ │ │ │ ├── cronjob.yaml │ │ │ │ ├── deployment.yaml │ │ │ │ ├── ingress.yaml │ │ │ │ ├── job.yaml │ │ │ │ ├── pdb.yaml │ │ │ │ ├── service.yaml │ │ │ │ └── tests │ │ │ │ │ └── test.yaml │ │ │ └── values.yaml │ │ └── mysql-9.7.2.tgz │ └── values.yaml └── update.sh ├── nginx ├── default.conf └── uwsgi.ini └── scripts ├── mysql └── cron │ └── mysql-backup └── telegram └── setWebhook.sh /.env: -------------------------------------------------------------------------------- 1 | TZ=Europe/Moscow 2 | MYSQL_USER=shm 3 | MYSQL_PASS=seeL5pieT3odah7z 4 | MYSQL_ROOT_PASS=phi2dah6Cai7OuGo 5 | MYSQL_DATABASE=shm 6 | CORE_VERSION=latest 7 | ADMIN_VERSION=latest 8 | CLIENT_VERSION=latest 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | app/version 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:stable-alpine AS api 2 | EXPOSE 80 3 | CMD ["/entry.sh"] 4 | HEALTHCHECK --interval=10s --timeout=5s --retries=3 CMD curl -f 127.0.0.1/shm/healthcheck.cgi || exit 1 5 | COPY nginx/default.conf /etc/nginx/conf.d/ 6 | COPY entry-api.sh /entry.sh 7 | 8 | 9 | FROM debian:bullseye-slim AS core 10 | WORKDIR /app 11 | 12 | ENV DEBIAN_FRONTEND=noninteractive 13 | 14 | RUN apt-get update && apt-get install -y \ 15 | uwsgi \ 16 | default-libmysqlclient-dev \ 17 | openssh-client \ 18 | curl \ 19 | qrencode 20 | 21 | RUN apt-get install -y \ 22 | perl \ 23 | libdbi-perl \ 24 | libdbd-mysql-perl \ 25 | libcgi-pm-perl \ 26 | libtime-parsedate-perl \ 27 | libdate-calc-perl \ 28 | libjson-perl \ 29 | libtest-mocktime-perl \ 30 | libsql-abstract-perl \ 31 | libnet-openssh-perl \ 32 | libnet-idn-encode-perl \ 33 | libdata-validate-domain-perl \ 34 | libdata-validate-email-perl \ 35 | libdata-validate-ip-perl \ 36 | libdigest-sha-perl \ 37 | libscalar-util-numeric-perl \ 38 | libtemplate-perl \ 39 | libtemplate-plugin-dbi-perl \ 40 | libtie-dbi-perl \ 41 | libemail-sender-perl \ 42 | libwww-perl \ 43 | librouter-simple-perl \ 44 | libcrypt-jwt-perl 45 | 46 | RUN cpan Crypt::Curve25519 47 | 48 | RUN set -x \ 49 | && useradd shm -d /var/shm -m \ 50 | && rm -rf /var/lib/apt/lists/* 51 | 52 | COPY nginx/uwsgi.ini /etc/uwsgi/apps-enabled/shm.ini 53 | 54 | ENV SHM_ROOT_DIR=/app 55 | ENV SHM_DATA_DIR=/var/shm 56 | ENV PERL5LIB=/app/lib:/app/conf 57 | ENV DB_USER=shm 58 | ENV DB_PASS=password 59 | ENV DB_HOST=127.0.0.1 60 | ENV DB_PORT=3306 61 | ENV DB_NAME=shm 62 | 63 | COPY entry-core.sh /entry.sh 64 | CMD ["/entry.sh"] 65 | 66 | COPY app /app 67 | 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # shm 2 | SHM: Universal Billing with external actions 3 | 4 | Открытая, бесплатная биллинговая система. 5 | 6 | ## Документация 7 | 8 | https://docs.myshm.ru 9 | 10 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | *.pl 2 | data/ 3 | test 4 | -------------------------------------------------------------------------------- /app/bin/init.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use v5.14; 4 | use SHM; 5 | use Core::System::ServiceManager qw( get_service ); 6 | use Core::Utils qw( read_file ); 7 | use Core::Sql::Data; 8 | use version; 9 | 10 | my $sql = Core::Sql::Data->new; 11 | 12 | my $version; 13 | my $version_prefix; 14 | if ( -f "$ENV{SHM_ROOT_DIR}/version" ) { 15 | $version = read_file( "$ENV{SHM_ROOT_DIR}/version" ); 16 | chomp $version; 17 | say "SHM version: $version"; 18 | if ( $version =~/(-.+)$/ ) { 19 | $version_prefix = $1; 20 | $version =~s/-.+$//; 21 | } 22 | } 23 | 24 | my $config = get_service('config'); 25 | my $dbh = db_connect( %{ $config->file->{config}{database} } ) or die "Can't connect to DN"; 26 | $config->local('dbh', $dbh ); 27 | 28 | my $tables_count = $sql->do("SHOW TABLES"); 29 | 30 | if ( $ENV{TRUNCATE_DB_ON_START} || $tables_count == 0 ) { 31 | print "Creating structure of database... "; 32 | import_sql_file( $dbh, "$ENV{SHM_ROOT_DIR}/sql/shm/shm_structure.sql" ); 33 | say "done"; 34 | 35 | if ( $ENV{DEV} ) { 36 | print "Loading data for developers... "; 37 | import_sql_file( $dbh, "$ENV{SHM_ROOT_DIR}/sql/shm/shm_dev_test_data.sql" ); 38 | } else { 39 | print "Loading data... "; 40 | import_sql_file( $dbh, "$ENV{SHM_ROOT_DIR}/sql/shm/shm_data.sql" ); 41 | } 42 | $config->id( '_shm' )->set( value => { version => $version . $version_prefix } ) if $version; 43 | say "done"; 44 | } elsif ( $version ) { 45 | # Start migrations 46 | chdir "$ENV{SHM_ROOT_DIR}/bin/migrations"; 47 | 48 | my $config = $config->id( '_shm' ); 49 | my $cur_version = $config->get_data->{version}; 50 | say "Current version: $cur_version"; 51 | $cur_version =~s/-.+$//; 52 | 53 | my @migrations = `ls`; 54 | for ( @migrations ) { 55 | chomp; 56 | ~s/\.sql$//; 57 | } 58 | 59 | my @versions = sort { version->parse( $a ) <=> version->parse( $b ) } @migrations; 60 | 61 | for my $nv ( @versions ) { 62 | next if version->parse( $nv ) <= version->parse( $cur_version ); 63 | next if version->parse( $nv ) > version->parse( $version ); 64 | 65 | say "Applying migration for version: $nv ..."; 66 | if ( version->parse( $nv ) > version->parse( '0.2.17' ) ) { 67 | import_sql_file( $dbh, "$nv.sql" ); 68 | } else { 69 | eval `cat $nv`; 70 | } 71 | $config->set( value => { version => $nv . $version_prefix } ); 72 | $dbh->commit(); 73 | say "done" 74 | } 75 | 76 | $config->set( value => { version => $version . $version_prefix } ); 77 | } 78 | 79 | $dbh->commit(); 80 | $dbh->disconnect(); 81 | 82 | exit 0; 83 | 84 | sub import_sql_file { 85 | my $dbh = shift; 86 | my $file = shift; 87 | 88 | my $data = read_file( $file ) or die "Can't read file: $file"; 89 | 90 | my @sql = sql_split( $data ); 91 | 92 | for ( @sql ) { 93 | $dbh->do( $_ ); 94 | } 95 | } 96 | 97 | sub sql_split { 98 | my $sql = shift; 99 | 100 | my @statements = (""); 101 | my @tokens = grep { ord } split /([\\';])/, $sql; 102 | my $in_string = 0; 103 | my $escape = 0; 104 | 105 | while (@tokens) { 106 | my $token = shift @tokens; 107 | if ($in_string) { 108 | $statements[-1] .= $token; 109 | if ($token eq "\\") { 110 | $escape = 1; 111 | next; 112 | } 113 | $in_string = 0 if not $escape and $token eq "'"; 114 | $escape = 0; 115 | 116 | next; 117 | } 118 | if ($token eq ';') { 119 | push @statements, ""; 120 | next; 121 | } 122 | $statements[-1] .= $token; 123 | $in_string = 1 if $token eq "'"; 124 | } 125 | return grep { /\S/ } @statements; 126 | } 127 | 128 | sub do_sql { 129 | my $data = shift; 130 | 131 | my @sql = sql_split( $data ); 132 | 133 | my $report = get_service('report'); 134 | 135 | for ( @sql ) { 136 | say $_; 137 | $sql->do( $_ ); 138 | unless ( $report->is_success ) { 139 | say $_ for ( $report->errors ); 140 | exit 1; 141 | } 142 | } 143 | } 144 | 145 | -------------------------------------------------------------------------------- /app/bin/migrations/0.14.0.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE pays_history ADD COLUMN `uniq_key` char(255) DEFAULT NULL; 2 | ALTER TABLE pays_history ADD UNIQUE KEY `uniq_key` (`user_id`,`uniq_key`); 3 | -------------------------------------------------------------------------------- /app/bin/migrations/0.18.0.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS `promo_codes` ( 2 | `id` char(32) NOT NULL, 3 | `template_id` char(32) NOT NULL, 4 | `user_id` int(11) DEFAULT NULL, 5 | `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, 6 | `used` datetime DEFAULT NULL, 7 | FOREIGN KEY (user_id) REFERENCES users (user_id), 8 | PRIMARY KEY (`id`) 9 | ) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4; 10 | 11 | -------------------------------------------------------------------------------- /app/bin/migrations/0.18.4.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE promo_codes ADD COLUMN `used_by` int(11) DEFAULT NULL; 2 | UPDATE promo_codes SET used_by = user_id, user_id = 1; 3 | -------------------------------------------------------------------------------- /app/bin/migrations/0.18.5.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE promo_codes ADD COLUMN `settings` json DEFAULT NULL; 2 | -------------------------------------------------------------------------------- /app/bin/migrations/0.2.0: -------------------------------------------------------------------------------- 1 | 2 | do_sql( q( 3 | ALTER TABLE pays_history ADD COLUMN comment_tmp JSON DEFAULT NULL; 4 | UPDATE pays_history SET comment_tmp = JSON_SET( '{}', "$.comment", comment ) WHERE length(comment); 5 | ALTER TABLE pays_history DROP COLUMN comment; 6 | ALTER TABLE pays_history RENAME COLUMN comment_tmp TO comment; 7 | ALTER TABLE services ADD COLUMN deleted tinyint(4) NOT NULL DEFAULT '0'; 8 | )); 9 | 10 | 1; 11 | 12 | -------------------------------------------------------------------------------- /app/bin/migrations/0.2.1: -------------------------------------------------------------------------------- 1 | 2 | do_sql( q( 3 | ALTER TABLE events MODIFY COLUMN name CHAR(32); 4 | DELETE FROM templates WHERE id='user_password_reset'; 5 | INSERT templates VALUES('user_password_reset','Уважаемый клиент.\n\nВаш новый пароль: {{ user.set_new_passwd }}\n\nАдрес кабинета: {{ config.cli.url }}','{\"subject\": \"SHM - Восстановление пароля\"}'); 6 | INSERT events VALUES(default,'UserService','User password reset','user_password_reset',1,'{\"category\": \"%\", \"template_id\": \"user_password_reset\"}'); 7 | )); 8 | 9 | 1; 10 | 11 | -------------------------------------------------------------------------------- /app/bin/migrations/0.2.10: -------------------------------------------------------------------------------- 1 | 2 | do_sql( q( 3 | UPDATE config SET `key`='billing' WHERE `key`='_billing'; 4 | UPDATE config SET value = JSON_SET( value, "$.partner", JSON_SET('{}', "$.income_percent", 0) ) WHERE `key`='billing'; 5 | CREATE TABLE IF NOT EXISTS `bonus_history` ( 6 | `id` int(11) NOT NULL AUTO_INCREMENT, 7 | `user_id` int(11) NOT NULL, 8 | `date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, 9 | `bonus` decimal(10,2) NOT NULL, 10 | `comment` json DEFAULT NULL, 11 | FOREIGN KEY (user_id) REFERENCES users (user_id), 12 | PRIMARY KEY (`id`) 13 | ) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8; 14 | )); 15 | 16 | 1; 17 | 18 | -------------------------------------------------------------------------------- /app/bin/migrations/0.2.12: -------------------------------------------------------------------------------- 1 | 2 | do_sql( q( 3 | UPDATE users SET login = LCASE(login); 4 | ALTER TABLE users ADD COLUMN settings json DEFAULT NULL; 5 | )); 6 | 7 | 1; 8 | 9 | -------------------------------------------------------------------------------- /app/bin/migrations/0.2.13: -------------------------------------------------------------------------------- 1 | 2 | do_sql( q( 3 | INSERT spool (id,status,user_id,event) VALUES(default,'PAUSED',1,'{"title":"cleanup services","kind":"Jobs","method":"job_cleanup","period":"86400","settings":{"days":10}}'); 4 | )); 5 | 6 | 1; 7 | 8 | -------------------------------------------------------------------------------- /app/bin/migrations/0.2.18.sql: -------------------------------------------------------------------------------- 1 | 2 | DELETE FROM `templates` WHERE id='telegram_bot'; 3 | INSERT INTO `templates` VALUES ('telegram_bot','<% SWITCH cmd %>\n<% CASE \'USER_NOT_FOUND\' %>\n{\n \"sendMessage\": {\n \"text\": \"Для работы с Telegram ботом укажите _Telegram логин_ в профиле личного кабинета.\\n\\n*Telegram логин*: {{ message.chat.username }}\\n\\n*Кабинет пользователя*: {{ config.cli.url }}\"\n }\n}\n<% CASE [\'/start\', \'/menu\'] %>\n{{ IF cmd == \'/menu\' }}\n{\n \"deleteMessage\": { \"message_id\": {{ message.message_id }} }\n},\n{{ END }}\n{\n \"sendMessage\": {\n \"text\": \"Создавайте и управляйте своими VPN ключами\",\n \"reply_markup\": {\n \"inline_keyboard\": [\n [\n {\n \"text\": \"💰 Баланс\",\n \"callback_data\": \"/balance\"\n }\n ],\n [\n {\n \"text\": \"🗝 Ключи\",\n \"callback_data\": \"/list\"\n }\n ]\n ]\n }\n }\n}\n<% CASE \'/balance\' %>\n{\n \"deleteMessage\": { \"message_id\": {{ message.message_id }} }\n},\n{\n \"sendMessage\": {\n \"text\": \"💰 *Баланс*: {{ user.balance }}\\n\\nНеобходимо оплатить: * {{ user.pays.forecast.total }}*\",\n \"reply_markup\" : {\n \"inline_keyboard\": [\n [\n {\n \"text\": \"⇦ Назад\",\n \"callback_data\": \"/menu\"\n }\n ]\n ]\n }\n }\n}\n<% CASE \'/list\' %>\n{\n \"deleteMessage\": { \"message_id\": {{ message.message_id }} }\n},\n{\n \"sendMessage\": {\n \"text\": \"🗝 Ключи\",\n \"reply_markup\" : {\n \"inline_keyboard\": [\n {{ FOR item IN user.services.list_for_api( \'category\', \'%\' ) }}\n {{ SWITCH item.status }}\n {{ CASE \'ACTIVE\' }}\n {{ status = \'✅\' }}\n {{ CASE \'BLOCK\' }}\n {{ status = \'❌\' }}\n {{ CASE \'NOT PAID\' }}\n {{ status = \'💰\' }}\n {{ CASE }}\n {{ status = \'⏳\' }}\n {{ END }}\n [\n {\n \"text\": \"{{ status }} - {{ item.name }}\",\n \"callback_data\": \"/service {{ item.user_service_id }}\"\n }\n ],\n {{ END }}\n [\n {\n \"text\": \"⇦ Назад\",\n \"callback_data\": \"/menu\"\n }\n ]\n ]\n }\n }\n}\n<% CASE \'/service\' %>\n{{ us = user.services.list_for_api( \'usi\', args.0 ) }}\n{\n \"deleteMessage\": { \"message_id\": {{ message.message_id }} }\n},\n{\n \"sendMessage\": {\n \"text\": \"*Ключ*: {{ us.name }}\\n\\n*Оплачен до*: {{ us.expire }}\\n\\n*Статус*: {{ us.status }}\",\n \"reply_markup\" : {\n \"inline_keyboard\": [\n {{ IF us.status == \'ACTIVE\' }}\n [\n {\n \"text\": \"🗝 Скачать ключ\",\n \"callback_data\": \"/download_qr {{ args.0 }}\"\n },\n {\n \"text\": \"👀 Показать QR код\",\n \"callback_data\": \"/show_qr {{ args.0 }}\"\n }\n ],\n {{ END }}\n [\n {\n \"text\": \"⇦ Назад\",\n \"callback_data\": \"/list\"\n }\n ]\n ]\n }\n }\n}\n<% CASE \'/download_qr\' %>\n{\n \"uploadDocumentFromStorage\": {\n \"name\": \"vpn{{ args.0 }}\",\n \"filename\": \"vpn{{ args.0 }}.txt\"\n }\n}\n<% CASE \'/show_qr\' %>\n{\n \"uploadPhotoFromStorage\": {\n \"name\": \"vpn{{ args.0 }}\",\n \"format\": \"qr_code_png\"\n }\n}\n<% END %>\n\n',NULL); 4 | -------------------------------------------------------------------------------- /app/bin/migrations/0.2.19.sql: -------------------------------------------------------------------------------- 1 | DELETE FROM `templates` WHERE id='telegram_bot'; 2 | INSERT INTO `templates` VALUES ('telegram_bot','<% SWITCH cmd %>\n<% CASE \'USER_NOT_FOUND\' %>\n{\n \"sendMessage\": {\n \"text\": \"Для работы с Telegram ботом укажите _Telegram логин_ в профиле личного кабинета.\\n\\n*Telegram логин*: {{ message.chat.username }}\\n\\n*Кабинет пользователя*: {{ config.cli.url }}\"\n }\n}\n<% CASE [\'/start\', \'/menu\'] %>\n{{ IF cmd == \'/menu\' }}\n{\n \"deleteMessage\": { \"message_id\": {{ message.message_id }} }\n},\n{{ END }}\n{\n \"sendMessage\": {\n \"text\": \"Создавайте и управляйте своими VPN ключами\",\n \"reply_markup\": {\n \"inline_keyboard\": [\n [\n {\n \"text\": \"💰 Баланс\",\n \"callback_data\": \"/balance\"\n }\n ],\n [\n {\n \"text\": \"🗝 Ключи\",\n \"callback_data\": \"/list\"\n }\n ]\n ]\n }\n }\n}\n<% CASE \'/balance\' %>\n{\n \"deleteMessage\": { \"message_id\": {{ message.message_id }} }\n},\n{\n \"sendMessage\": {\n \"text\": \"💰 *Баланс*: {{ user.balance }}\\n\\nНеобходимо оплатить: * {{ user.pays.forecast.total }}*\",\n \"reply_markup\" : {\n \"inline_keyboard\": [\n [\n {\n \"text\": \"⇦ Назад\",\n \"callback_data\": \"/menu\"\n }\n ]\n ]\n }\n }\n}\n<% CASE \'/list\' %>\n{\n \"deleteMessage\": { \"message_id\": {{ message.message_id }} }\n},\n{\n \"sendMessage\": {\n \"text\": \"🗝 Ключи\",\n \"reply_markup\" : {\n \"inline_keyboard\": [\n {{ FOR item IN ref(user.services.list_for_api( \'category\', \'%\' )) }}\n {{ SWITCH item.status }}\n {{ CASE \'ACTIVE\' }}\n {{ status = \'✅\' }}\n {{ CASE \'BLOCK\' }}\n {{ status = \'❌\' }}\n {{ CASE \'NOT PAID\' }}\n {{ status = \'💰\' }}\n {{ CASE }}\n {{ status = \'⏳\' }}\n {{ END }}\n [\n {\n \"text\": \"{{ status }} - {{ item.name }}\",\n \"callback_data\": \"/service {{ item.user_service_id }}\"\n }\n ],\n {{ END }}\n [\n {\n \"text\": \"⇦ Назад\",\n \"callback_data\": \"/menu\"\n }\n ]\n ]\n }\n }\n}\n<% CASE \'/service\' %>\n{{ us = user.services.list_for_api( \'usi\', args.0 ) }}\n{\n \"deleteMessage\": { \"message_id\": {{ message.message_id }} }\n},\n{\n \"sendMessage\": {\n \"text\": \"*Ключ*: {{ us.name }}\\n\\n*Оплачен до*: {{ us.expire }}\\n\\n*Статус*: {{ us.status }}\",\n \"reply_markup\" : {\n \"inline_keyboard\": [\n {{ IF us.status == \'ACTIVE\' }}\n [\n {\n \"text\": \"🗝 Скачать ключ\",\n \"callback_data\": \"/download_qr {{ args.0 }}\"\n },\n {\n \"text\": \"👀 Показать QR код\",\n \"callback_data\": \"/show_qr {{ args.0 }}\"\n }\n ],\n {{ END }}\n [\n {\n \"text\": \"⇦ Назад\",\n \"callback_data\": \"/list\"\n }\n ]\n ]\n }\n }\n}\n<% CASE \'/download_qr\' %>\n{\n \"uploadDocumentFromStorage\": {\n \"name\": \"vpn{{ args.0 }}\",\n \"filename\": \"vpn{{ args.0 }}.txt\"\n }\n}\n<% CASE \'/show_qr\' %>\n{\n \"uploadPhotoFromStorage\": {\n \"name\": \"vpn{{ args.0 }}\",\n \"format\": \"qr_code_png\"\n }\n}\n<% END %>\n\n',NULL); 3 | -------------------------------------------------------------------------------- /app/bin/migrations/0.2.22.sql: -------------------------------------------------------------------------------- 1 | UPDATE spool SET event='{"title":"send forecasts","kind":"Jobs","method":"job_make_forecasts","period":"86400"}' WHERE event->"$.title"="send forecasts"; 2 | 3 | -------------------------------------------------------------------------------- /app/bin/migrations/0.2.23.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE servers ADD COLUMN services_count int(11) NOT NULL DEFAULT '0'; 2 | UPDATE servers RIGHT JOIN (SELECT settings->"$.server_id" as server_id, COUNT(user_services.settings->"$.server_id") as cnt FROM user_services WHERE status<>'REMOVED' GROUP BY user_services.settings-> "$.server_id") AS settings ON servers.server_id = settings.server_id SET servers.services_count = settings.cnt; 3 | 4 | -------------------------------------------------------------------------------- /app/bin/migrations/0.2.26.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE services ADD COLUMN is_composite tinyint(4) NOT NULL DEFAULT '0'; 2 | 3 | -------------------------------------------------------------------------------- /app/bin/migrations/0.2.8: -------------------------------------------------------------------------------- 1 | 2 | do_sql( q( 3 | ALTER TABLE users RENAME COLUMN owner TO partner_id; 4 | ALTER TABLE users MODIFY partner_id int(11) DEFAULT NULL; 5 | ALTER TABLE users DROP COLUMN partner; 6 | ALTER TABLE users DROP COLUMN partner_disc; 7 | )); 8 | 9 | 1; 10 | 11 | -------------------------------------------------------------------------------- /app/bin/migrations/0.20.5.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `discounts` ENGINE = MyISAM; 2 | ALTER TABLE `dns_services` ENGINE = MyISAM; 3 | ALTER TABLE `domains` ENGINE = MyISAM; 4 | ALTER TABLE `domains_services` ENGINE = MyISAM; 5 | ALTER TABLE `servers_groups` ENGINE = MyISAM; 6 | ALTER TABLE `spool_history` ENGINE = MyISAM; 7 | ALTER TABLE `zones` ENGINE = MyISAM; 8 | ALTER TABLE `identities` ENGINE = MyISAM; 9 | ALTER TABLE `templates` ENGINE = MyISAM; 10 | ALTER TABLE `console` ENGINE = MyISAM; 11 | ALTER TABLE `config` ENGINE = MyISAM; 12 | ALTER TABLE `sessions` ENGINE = MyISAM; 13 | -------------------------------------------------------------------------------- /app/bin/migrations/0.21.1.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE console MODIFY log MEDIUMBLOB NOT NULL; -------------------------------------------------------------------------------- /app/bin/migrations/0.26.0.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE user_services ADD COLUMN `status_before` char(8) DEFAULT NULL; -------------------------------------------------------------------------------- /app/bin/migrations/0.31.3.sql: -------------------------------------------------------------------------------- 1 | DROP INDEX `PRIMARY` ON promo_codes; 2 | ALTER TABLE promo_codes ADD PRIMARY KEY(id, user_id); 3 | ALTER TABLE promo_codes ADD COLUMN `expire` datetime DEFAULT NULL; 4 | -------------------------------------------------------------------------------- /app/bin/migrations/0.31.4.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE pays_history MODIFY COLUMN pay_system_id CHAR(32); -------------------------------------------------------------------------------- /app/bin/migrations/0.32.1.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE pays_history DROP FOREIGN KEY pays_history_ibfk_1; 2 | ALTER TABLE spool DROP FOREIGN KEY spool_ibfk_1; 3 | ALTER TABLE spool_history DROP FOREIGN KEY spool_history_ibfk_1; 4 | ALTER TABLE storage DROP FOREIGN KEY storage_ibfk_1; 5 | ALTER TABLE promo_codes DROP FOREIGN KEY promo_codes_ibfk_1; 6 | 7 | -------------------------------------------------------------------------------- /app/bin/migrations/0.34.4.sql: -------------------------------------------------------------------------------- 1 | DROP INDEX `PRIMARY` ON promo_codes; 2 | ALTER TABLE promo_codes MODIFY user_id INT NOT NULL; 3 | ALTER TABLE promo_codes DROP KEY `user_id`; 4 | ALTER TABLE promo_codes ADD PRIMARY KEY(id, user_id); -------------------------------------------------------------------------------- /app/bin/migrations/0.7.0.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `discounts` MODIFY `months` decimal(10,4) NOT NULL; 2 | ALTER TABLE `services` MODIFY `period_cost` decimal(10,4) NOT NULL; 3 | ALTER TABLE `withdraw_history` MODIFY `months` decimal(10,4) NOT NULL; 4 | -------------------------------------------------------------------------------- /app/bin/migrations/0.8.0.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE services RENAME COLUMN period_cost TO period; 2 | ALTER TABLE `templates` MODIFY `data` MEDIUMTEXT DEFAULT NULL; 3 | ALTER TABLE `storage` MODIFY `data` MEDIUMTEXT DEFAULT NULL; 4 | -------------------------------------------------------------------------------- /app/bin/spool.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use v5.14; 4 | use SHM; 5 | use Core::System::ServiceManager qw( get_service unregister_all ); 6 | use Try::Tiny; 7 | use Core::Const; 8 | use Core::Utils qw( 9 | encode_json 10 | ); 11 | no warnings; 12 | 13 | $| = 1; 14 | 15 | my $user = SHM->new( user_id => 1 ); 16 | $user->dbh->{RaiseError} = 1; 17 | 18 | my ( $status, $task, $info ); 19 | 20 | say "SHM spool started at: " . localtime; 21 | 22 | for (;;) { 23 | do { 24 | try { 25 | my $spool = get_service('spool'); 26 | ( $status, $task, $info ) = $spool->process_one(); 27 | 28 | if ( defined $task ) { 29 | $task->{status} //= $status; 30 | say encode_json( $task ); 31 | } 32 | } catch { 33 | my $error = $_; 34 | warn $error; 35 | 36 | if ( defined $task ) { 37 | $task->finish_task( 38 | status => TASK_STUCK, 39 | response => { error => $error }, 40 | ); 41 | } 42 | }; 43 | $user->commit; 44 | unregister_all(); 45 | } while defined $task; 46 | 47 | sleep 1; 48 | } 49 | 50 | exit 0; 51 | -------------------------------------------------------------------------------- /app/conf/shm.conf: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use v5.14; 4 | use File::Path qw(make_path); 5 | 6 | if ( -f "/etc/environment" ) { 7 | open ( my $fd, "/etc/environment" ) or die $!; 8 | while (<$fd>) { 9 | chomp; 10 | my ($name, $value) = split(/=/); 11 | $ENV{ $name } = $value; 12 | } 13 | close $fd; 14 | } 15 | 16 | our $DATA_DIR = "/tmp/shm"; 17 | 18 | our $config = { 19 | database => { 20 | db_user => $ENV{DB_USER} || 'shm', 21 | db_pass => $ENV{DB_PASS} || (die 'DB_PASS not defined'), 22 | db_host => $ENV{DB_HOST} || 'localhost', 23 | db_name => $ENV{DB_NAME} || 'shm', 24 | db_charset => $ENV{DB_CHARSET} || 'utf8', 25 | }, 26 | log => { 27 | path => "$DATA_DIR/log", 28 | file => "shm.log", 29 | }, 30 | }; 31 | 32 | if ( $config->{log} && ! -d $config->{log}->{path} ) { 33 | make_path( $config->{log}->{path} ); 34 | } 35 | 36 | 1; 37 | -------------------------------------------------------------------------------- /app/lib/Core/Acts.pm: -------------------------------------------------------------------------------- 1 | package Core::Acts; 2 | 3 | use v5.14; 4 | use parent 'Core::Base'; 5 | use Core::Base; 6 | 7 | sub table { return 'acts' }; 8 | 9 | sub structure { 10 | return { 11 | act_id => { 12 | type => 'number', 13 | key => 1, 14 | }, 15 | user_id => { 16 | type => 'number', 17 | auto_fill => 1, 18 | }, 19 | date => { 20 | type => 'date', 21 | required => 1, 22 | }, 23 | show_act => { 24 | type => 'number', 25 | default => 1, 26 | }, 27 | } 28 | } 29 | 30 | 1; 31 | -------------------------------------------------------------------------------- /app/lib/Core/ActsData.pm: -------------------------------------------------------------------------------- 1 | package Core::ActsData; 2 | 3 | use v5.14; 4 | use parent 'Core::Base'; 5 | use Core::Base; 6 | 7 | sub table { return 'acts_data' }; 8 | 9 | sub structure { 10 | return { 11 | id => { 12 | type => 'number', 13 | key => 1, 14 | }, 15 | act_id => { 16 | type => 'number', 17 | }, 18 | user_id => { 19 | type => 'number', 20 | auto_fill => 1, 21 | }, 22 | service_id => { 23 | type => 'number', 24 | }, 25 | user_service_id => { 26 | type => 'number', 27 | }, 28 | withdraw_id => { 29 | type => 'number', 30 | }, 31 | amount => { 32 | type => 'number', 33 | required => 1, 34 | }, 35 | name => { 36 | type => 'text', 37 | }, 38 | start_date => { 39 | type => 'date', 40 | }, 41 | stop_date => { 42 | type => 'date', 43 | }, 44 | } 45 | } 46 | 47 | 48 | 1; 49 | -------------------------------------------------------------------------------- /app/lib/Core/App.pm: -------------------------------------------------------------------------------- 1 | package Core::App; 2 | 3 | use v5.14; 4 | use parent 'Core::Base'; 5 | use Core::Base; 6 | 7 | sub table { return 'apps' }; 8 | 9 | sub structure { 10 | return { 11 | id => { 12 | type => 'number', 13 | key => 1, 14 | }, 15 | user_id => { 16 | type => 'number', 17 | auto_fill => 1, 18 | }, 19 | user_service_id => { 20 | type => 'number', 21 | required => 1, 22 | }, 23 | name => { 24 | type => 'text', 25 | required => 1, 26 | }, 27 | domain_id => { 28 | type => 'number', 29 | }, 30 | settings => { 31 | type => 'json', 32 | value => undef, 33 | }, 34 | } 35 | } 36 | 37 | 38 | 1; 39 | -------------------------------------------------------------------------------- /app/lib/Core/Billing/Simpler.pm: -------------------------------------------------------------------------------- 1 | package Core::Billing::Simpler; 2 | 3 | use v5.14; 4 | 5 | use base qw(Exporter); 6 | 7 | our @EXPORT_OK = qw( 8 | calc_end_date_by_months 9 | calc_total_by_date_range 10 | ); 11 | 12 | use Core::Const; 13 | use Core::Utils qw( 14 | string_to_utime 15 | utime_to_string 16 | start_of_month 17 | end_of_month 18 | parse_date 19 | parse_period 20 | days_in_months 21 | now 22 | ); 23 | use Time::Local 'timelocal_nocheck'; 24 | 25 | use constant DAYS_IN_MONTH => 30; 26 | 27 | use Date::Calc qw( 28 | Delta_DHMS 29 | Add_Delta_DHMS 30 | ); 31 | 32 | # Вычисляет конечную дату путем прибавления периода к заданной дате 33 | sub calc_end_date_by_months { 34 | my $date = shift; 35 | my $period = shift; 36 | 37 | my ( $months, $days, $hours ) = parse_period( $period ); 38 | 39 | $days += $months * DAYS_IN_MONTH; 40 | 41 | my %stop = parse_date( $date ); 42 | @stop{ qw/year month day hour min sec/ } = Add_Delta_DHMS( @stop{ qw/year month day hour min sec/ },$days,$hours,0,-1 ); 43 | 44 | return sprintf("%d-%.2d-%.2d %.2d:%.2d:%.2d", @stop{ qw/year month day hour min sec/ } ); 45 | } 46 | 47 | # Вычисляет стоимость услуги для заданного периода 48 | sub calc_total_by_date_range { 49 | my %wd = ( 50 | cost => undef, 51 | period => 1, 52 | withdraw_date => now, 53 | end_date => undef, 54 | @_, 55 | ); 56 | my $debug = 0; 57 | 58 | my %start = parse_date( $wd{withdraw_date} ); 59 | my %stop = parse_date( $wd{end_date} ); 60 | 61 | my $total = 0; 62 | 63 | if ( $wd{cost} ) { 64 | # Add one second for correct counting days 65 | @stop{ qw/year month day hour min sec/ } = Add_Delta_DHMS( @stop{ qw/year month day hour min sec/ },0,0,0,1 ); 66 | 67 | my %delta; 68 | @delta{ qw/day hour min sec/ } = Delta_DHMS( @start{ qw/year month day hour min sec/ }, @stop{ qw/year month day hour min sec/ } ); 69 | 70 | my $months_cost; 71 | if ( my $pc = $wd{period} ) { 72 | if ( int( $pc ) == $pc ) { 73 | $months_cost = $wd{cost} / $wd{period}; 74 | } else { 75 | my $months_hours = DAYS_IN_MONTH * 24; 76 | my ( $months, $days, $hours ) = parse_period( $pc ); 77 | my $period_hours = $months * DAYS_IN_MONTH * 24 + $days * 24 + $hours; 78 | $months_cost = $months_hours / $period_hours * $wd{cost}; 79 | } 80 | } 81 | 82 | my $cost_day = $months_cost / DAYS_IN_MONTH; 83 | my $cost_hour = $cost_day / 24; 84 | my $cost_min = $cost_hour / 60; 85 | 86 | $total = $delta{day} * $cost_day + $delta{hour} * $cost_hour + $delta{min} * $cost_min; 87 | } 88 | 89 | return { 90 | total => sprintf("%.2f", $total ), 91 | months => calc_months_between_dates(\%start, \%stop), 92 | }; 93 | } 94 | 95 | sub calc_months_between_dates { 96 | my %start = %{ $_[0] }; 97 | my %stop = %{ $_[1] }; 98 | 99 | my %delta; 100 | @delta{ qw/day hour min sec/ } = Delta_DHMS( @start{ qw/year month day hour min sec/ }, @stop{ qw/year month day hour min sec/ } ); 101 | 102 | my $months = int( $delta{day} / DAYS_IN_MONTH ); 103 | my $days = $delta{day} % DAYS_IN_MONTH; 104 | my $hours = $delta{hour}; 105 | 106 | return sprintf('%d.%02d%02d', $months, $days, $hours); 107 | } 108 | 109 | 1; 110 | -------------------------------------------------------------------------------- /app/lib/Core/Bonus.pm: -------------------------------------------------------------------------------- 1 | package Core::Bonus; 2 | 3 | use v5.14; 4 | use parent 'Core::Base'; 5 | use Core::Base; 6 | use Core::Const; 7 | 8 | sub table { return 'bonus_history' }; 9 | 10 | sub structure { 11 | return { 12 | id => { 13 | type => 'number', 14 | key => 1, 15 | }, 16 | user_id => { 17 | type => 'number', 18 | auto_fill => 1, 19 | }, 20 | date => { 21 | type => 'now', 22 | }, 23 | bonus => { 24 | type => 'number', 25 | required => 1, 26 | }, 27 | comment => { 28 | type => 'json', 29 | value => undef, 30 | }, 31 | } 32 | } 33 | 34 | sub amount { 35 | my $self = shift; 36 | return $self->get_bonus; 37 | } 38 | 39 | sub api_add { 40 | my $self = shift; 41 | my %args = ( 42 | @_, 43 | ); 44 | 45 | my $bonus_id = $self->user->set_bonus( %args ); 46 | 47 | return $self->id( $bonus_id )->get; 48 | } 49 | 50 | 1; 51 | -------------------------------------------------------------------------------- /app/lib/Core/Config.pm: -------------------------------------------------------------------------------- 1 | package Core::Config; 2 | use v5.14; 3 | 4 | use parent 'Core::Base'; 5 | use Core::Base; 6 | 7 | our $config; 8 | our $session_config; 9 | require 'shm.conf'; 10 | 11 | sub table { return 'config' }; 12 | 13 | sub structure { 14 | return { 15 | key => { 16 | type => 'text', 17 | key => 1, 18 | }, 19 | value => { 20 | type => 'json', 21 | required => 1, 22 | }, 23 | } 24 | } 25 | 26 | sub _id { 27 | my $self = shift; 28 | my %args = @_; 29 | 30 | if ( $args{key} ) { 31 | $self->{key} = $args{key}; 32 | return "key=$self->{key}"; 33 | } 34 | return undef; 35 | } 36 | 37 | sub table_allow_insert_key { return 1 }; 38 | 39 | sub validate_attributes { 40 | my $self = shift; 41 | my $method = shift; 42 | my %args = @_; 43 | 44 | my $report = get_service('report'); 45 | 46 | unless ( $args{key} || $args{value} ) { 47 | $report->add_error('KeyOrValueNotPresent'); 48 | } 49 | 50 | if ( $args{key} =~/^_/ ) { 51 | $report->add_error('KeyProhibited'); 52 | } 53 | 54 | return $report->is_success; 55 | } 56 | 57 | sub file { 58 | my $self = shift; 59 | 60 | return { 61 | config => $config, 62 | session => $session_config, 63 | }; 64 | } 65 | 66 | sub local { 67 | my $self = shift; 68 | my $section = shift; 69 | my $new_data = shift; 70 | 71 | if ( $new_data ) { 72 | $self->{config}->{local}->{ $section } = $new_data; 73 | } 74 | 75 | return $self->{config}->{local} unless $section; 76 | return $self->{config}->{local}->{ $section }; 77 | } 78 | 79 | sub api_data_by_name { 80 | my $self = shift; 81 | my %args = ( 82 | keys => undef, 83 | @_, 84 | ); 85 | 86 | return $self->data_by_name( $args{key} ); 87 | } 88 | 89 | sub data_by_name { 90 | my $self = shift; 91 | my $key = shift; 92 | 93 | my @list = $self->list( where => { 94 | $key ? ( key => $key ) : (), 95 | }); 96 | 97 | my %ret = map{ $_->{key} => $_->{value} } @list; 98 | 99 | if ( $key ) { 100 | for ( keys %{ $ret{ $key } } ) { 101 | $ret{ $_ } = delete $ret{ $key }->{ $_ }; 102 | } 103 | delete $ret{ $key }; 104 | } 105 | 106 | return \%ret || {}; 107 | } 108 | 109 | sub delete { 110 | my $self = shift; 111 | my %args = @_; 112 | 113 | my $report = get_service('report'); 114 | 115 | if ( $self->id =~/^_/ ) { 116 | $report->add_error('KeyProhibited'); 117 | return undef; 118 | } 119 | 120 | return $self->SUPER::delete( %args ); 121 | } 122 | 123 | sub get_data { 124 | my $self = shift; 125 | 126 | my $config = $self->list( 127 | where => { 128 | key => $self->id, 129 | } 130 | ); 131 | 132 | return $config->{ $self->id }->{value} || {}; 133 | } 134 | 135 | sub list_for_api { 136 | my $self = shift; 137 | my %args = ( 138 | key => undef, 139 | @_, 140 | ); 141 | 142 | return $self->SUPER::list_for_api( where => { 143 | $args{key} ? ( key => $args{key} ) : (), 144 | }); 145 | } 146 | 147 | sub set_value { 148 | my $self = shift; 149 | my $new_data = shift; 150 | 151 | return $self->set_json('value', $new_data ); 152 | } 153 | 154 | 1; 155 | 156 | -------------------------------------------------------------------------------- /app/lib/Core/Console.pm: -------------------------------------------------------------------------------- 1 | package Core::Console; 2 | 3 | use v5.14; 4 | use utf8; 5 | use parent 'Core::Base'; 6 | use Core::Base; 7 | use Core::Utils qw/now/; 8 | 9 | sub table { return 'console' }; 10 | 11 | sub structure { 12 | return { 13 | id => { 14 | type => 'number', 15 | key => 1, 16 | }, 17 | start => { 18 | type => 'now', 19 | }, 20 | stop => { 21 | type => 'date', 22 | }, 23 | log => { 24 | type => 'text', 25 | default => '', 26 | }, 27 | eof => { 28 | type => 'number', 29 | default => 0, 30 | }, 31 | } 32 | } 33 | 34 | sub new_pipe { 35 | my $self = shift; 36 | 37 | return $self->add(); 38 | } 39 | 40 | sub append { 41 | my $self = shift; 42 | my $log = shift; 43 | 44 | utf8::downgrade( $log ); 45 | 46 | $self->do("UPDATE ".$self->table." SET log = CONCAT(log, ?) WHERE id=?", 47 | $log, 48 | $self->id, 49 | ); 50 | } 51 | 52 | sub set_eof { 53 | my $self = shift; 54 | 55 | $self->set( eof => 1, stop => now ); 56 | } 57 | 58 | sub chunk { 59 | my $self = shift; 60 | my %args = ( 61 | offset => 1, 62 | @_, 63 | ); 64 | 65 | my ( $res ) = $self->query("SELECT id,eof,SUBSTRING(log,?) as chunk FROM ".$self->table." WHERE id=?", 66 | $args{offset}, 67 | $self->id, 68 | ); 69 | 70 | $self->res( $res ); 71 | 72 | utf8::decode($res->{chunk}); 73 | return $res->{chunk}; 74 | } 75 | 76 | sub eof { 77 | my $self = shift; 78 | 79 | return $self->get->{eof}; 80 | } 81 | 82 | sub clean { 83 | my $self = shift; 84 | my %args = ( 85 | days => 30, 86 | get_smart_args( @_ ), 87 | ); 88 | 89 | return $self->_delete( where => { 90 | start => { '<', \[ 'NOW() - INTERVAL ? DAY', 30 ] }, 91 | }); 92 | } 93 | 94 | 1; 95 | -------------------------------------------------------------------------------- /app/lib/Core/Const.pm: -------------------------------------------------------------------------------- 1 | package Core::Const; 2 | use v5.14; 3 | 4 | use base qw(Exporter); 5 | 6 | our @EXPORT = qw( 7 | SUCCESS 8 | FAIL 9 | 10 | STATUS_INIT 11 | STATUS_WAIT_FOR_PAY 12 | STATUS_PROGRESS 13 | STATUS_ACTIVE 14 | STATUS_BLOCK 15 | STATUS_REMOVED 16 | STATUS_ERROR 17 | 18 | EVENT_CREATE 19 | EVENT_BLOCK 20 | EVENT_REMOVE 21 | EVENT_PROLONGATE 22 | EVENT_ACTIVATE 23 | EVENT_UPDATE_CHILD_STATUS 24 | EVENT_CHILD_PREFIX 25 | EVENT_NOT_ENOUGH_MONEY 26 | EVENT_CHANGED 27 | EVENT_CHANGED_TARIFF 28 | 29 | TASK_NEW 30 | TASK_SUCCESS 31 | TASK_FAIL 32 | TASK_STUCK 33 | TASK_PAUSED 34 | ); 35 | 36 | use constant { 37 | SUCCESS => 1, 38 | FAIL => 0, 39 | }; 40 | 41 | use constant { 42 | STATUS_INIT => 'INIT', 43 | STATUS_WAIT_FOR_PAY => 'NOT PAID', 44 | STATUS_PROGRESS => 'PROGRESS', 45 | STATUS_ACTIVE => 'ACTIVE', 46 | STATUS_BLOCK => 'BLOCK', 47 | STATUS_REMOVED => 'REMOVED', 48 | STATUS_ERROR => 'ERROR', 49 | }; 50 | 51 | use constant { 52 | CLIENT_FIZ => 0, 53 | CLIENT_JUR => 1, 54 | CLIENT_IP => 2, 55 | CLIENT_FIZ_NR => 3, 56 | CLIENT_JUR_NR => 4, 57 | }; 58 | 59 | use constant { 60 | EVENT_CREATE => 'create', 61 | EVENT_NOT_ENOUGH_MONEY => 'not_enough_money', 62 | EVENT_BLOCK => 'block', 63 | EVENT_REMOVE => 'remove', 64 | EVENT_PROLONGATE => 'prolongate', 65 | EVENT_ACTIVATE => 'activate', 66 | EVENT_CHANGED => 'changed', 67 | EVENT_CHANGED_TARIFF => 'changed_tariff', 68 | }; 69 | 70 | use constant { 71 | TASK_NEW => 'NEW', 72 | TASK_SUCCESS => 'SUCCESS', 73 | TASK_FAIL => 'FAIL', 74 | TASK_STUCK => 'STUCK', 75 | TASK_PAUSED => 'PAUSED', 76 | }; 77 | 78 | 1; 79 | 80 | -------------------------------------------------------------------------------- /app/lib/Core/Discounts.pm: -------------------------------------------------------------------------------- 1 | package Core::Discounts; 2 | 3 | use v5.14; 4 | use parent 'Core::Base'; 5 | use Core::Base; 6 | 7 | sub table { return 'discounts' }; 8 | 9 | sub structure { 10 | return { 11 | discount_id => { 12 | type => 'number', 13 | key => 1, 14 | }, 15 | title => { 16 | type => 'text', 17 | required => 1, 18 | }, 19 | months => { 20 | type => 'number', 21 | required => 1, 22 | }, 23 | percent => { 24 | type => 'number', 25 | required => 1, 26 | }, 27 | } 28 | } 29 | 30 | sub get_by_period { 31 | my $self = shift; 32 | my $args = { 33 | months => undef, 34 | @_, 35 | }; 36 | 37 | die 'Months required' unless defined $args->{months}; 38 | 39 | my @ret = $self->list( 40 | range => { field => 'months', stop => $args->{months} }, 41 | order => [ months => 'desc' ], 42 | limit => 1, 43 | ); 44 | 45 | return $ret[0]; 46 | } 47 | 48 | 1; 49 | -------------------------------------------------------------------------------- /app/lib/Core/Domain/Registrator/Nic.pm: -------------------------------------------------------------------------------- 1 | package Core::Domain::Registrator::Nic; 2 | 3 | use v5.14; 4 | use parent 'Core::Base'; 5 | use Core::Base; 6 | 7 | 8 | 1; 9 | -------------------------------------------------------------------------------- /app/lib/Core/DomainServices.pm: -------------------------------------------------------------------------------- 1 | package Core::DomainServices; 2 | 3 | use v5.14; 4 | use parent 'Core::Base'; 5 | use Core::Base; 6 | 7 | sub table { return 'domains_services' }; 8 | 9 | sub structure { 10 | return { 11 | id => { 12 | type => 'number', 13 | key => 1, 14 | }, 15 | domain_id => { 16 | type => 'number', 17 | required => 1, 18 | }, 19 | user_service_id => { 20 | type => 'number', 21 | required => 1, 22 | }, 23 | created => { 24 | type => 'now', 25 | }, 26 | } 27 | } 28 | 29 | 30 | 1; 31 | -------------------------------------------------------------------------------- /app/lib/Core/Events.pm: -------------------------------------------------------------------------------- 1 | package Core::Events; 2 | 3 | use v5.14; 4 | use parent 'Core::Base'; 5 | use Core::Base; 6 | 7 | sub table { return 'events' }; 8 | 9 | sub structure { 10 | return { 11 | id => { 12 | type => 'number', 13 | key => 1, 14 | }, 15 | kind => { 16 | type => 'text', 17 | default => 'UserService', 18 | }, 19 | title => { 20 | type => 'text', 21 | required => 1, 22 | }, 23 | name => { # create,block,unblock... 24 | type => 'text', 25 | required => 1, 26 | }, 27 | server_gid => { # Group_id of servers 28 | type => 'number', 29 | default => 0, 30 | }, 31 | settings => { 32 | type => 'json', 33 | value => {}, 34 | }, 35 | } 36 | } 37 | 38 | sub get_events { 39 | my $self = shift; 40 | my %args = ( 41 | kind => undef, 42 | name => undef, 43 | category => undef, 44 | @_, 45 | ); 46 | 47 | my @res = $self->list( 48 | where => { 49 | $args{kind} ? ( kind => $args{kind} ) : (), 50 | $args{name} ? ( name => $args{name} ) : (), 51 | $args{category} ? ( sprintf('\'"%s"\'', $args{category}) => \"LIKE settings->'\$.category'" ) : (), 52 | }, 53 | ); 54 | return wantarray ? @res : \@res; 55 | } 56 | 57 | sub make { 58 | my $self = shift; 59 | my %args = @_; 60 | 61 | $self->srv('spool')->add( 62 | %args, 63 | ); 64 | } 65 | 66 | sub data { 67 | my $self = shift; 68 | 69 | unless ( $self->id && $self->{res} ) { 70 | logger->error("Data not loaded"); 71 | } 72 | return wantarray ? @{ $self->{res} } : $self->{res}; 73 | } 74 | 75 | sub command { 76 | my $self = shift; 77 | return $self->data->{command}; 78 | } 79 | 80 | sub exec { 81 | my $self = shift; 82 | my $args = { 83 | server_id => undef, 84 | data => undef, 85 | @_, 86 | }; 87 | 88 | return $self->srv('spool')->push( 89 | server_id => $args->{server_id}, 90 | cmd => $self->command, 91 | data => $args->{data}, 92 | ); 93 | } 94 | 95 | sub list_for_api { 96 | my $self = shift; 97 | my %args = ( 98 | admin => 0, 99 | kind => undef, 100 | id => undef, 101 | @_, 102 | ); 103 | 104 | $args{where} = { 105 | $args{id} ? ( $self->get_table_key => $args{id} ) : (), 106 | $args{kind} ? ( kind => $args{kind} ) : (), 107 | }; 108 | 109 | my @arr = $self->SUPER::list_for_api( %args ); 110 | return @arr; 111 | } 112 | 113 | 114 | 1; 115 | -------------------------------------------------------------------------------- /app/lib/Core/Identities.pm: -------------------------------------------------------------------------------- 1 | package Core::Identities; 2 | 3 | use v5.14; 4 | use parent 'Core::Base'; 5 | use Core::Base; 6 | use File::Temp; 7 | use Core::Utils qw( 8 | file_by_string 9 | read_file 10 | ); 11 | 12 | sub table { return 'identities' }; 13 | 14 | sub structure { 15 | return { 16 | id => { 17 | type => 'number', 18 | key => 1, 19 | }, 20 | name => { 21 | type => 'text', 22 | required => 1, 23 | }, 24 | private_key => { 25 | type => 'text', 26 | required => 1, 27 | }, 28 | public_key => { 29 | type => 'text', 30 | }, 31 | fingerprint => { 32 | type => 'text', 33 | required => 1, 34 | }, 35 | } 36 | } 37 | 38 | sub add { 39 | my $self = shift; 40 | my %args = ( 41 | name => undef, 42 | private_key => undef, 43 | @_, 44 | ); 45 | 46 | $args{fingerprint} ||= $self->make_fingerprint( file_by_string( $args{private_key} ) ); 47 | 48 | unless ( $args{fingerprint} ) { 49 | logger->error("Can't create fingerprint."); 50 | return undef; 51 | } 52 | return $self->SUPER::add( %args ); 53 | } 54 | 55 | sub make_fingerprint { 56 | my $self = shift; 57 | my $file = shift; 58 | 59 | my @ret = `ssh-keygen -l -E MD5 -f $file 2>/dev/null`; 60 | 61 | if ( $? == 0 ) { 62 | chomp $ret[0]; 63 | $ret[0] =~s/^\d+\s//; 64 | return $ret[0]; 65 | } 66 | return undef; 67 | } 68 | 69 | sub private_key_file { 70 | my $self = shift; 71 | 72 | return file_by_string( $self->res->{private_key} ); 73 | } 74 | 75 | sub generate_key_pair { 76 | my $self = shift; 77 | my %args = ( 78 | type => 'ed25519', 79 | @_, 80 | ); 81 | 82 | my $file = `mktemp -u`; 83 | chomp($file); 84 | 85 | my @ret = `ssh-keygen -t $args{type} -E MD5 -N '' -C 'Generated by SHM' -f $file 2>/dev/null`; 86 | 87 | unless ( $? == 0 ) { 88 | logger->warning("Can't generate ssh key pair"); 89 | return undef; 90 | } 91 | 92 | my $fingerprint = $ret[4]; 93 | chomp $fingerprint; 94 | 95 | my %struct = ( 96 | private_key => read_file( "$file" ), 97 | public_key => read_file( "$file.pub" ), 98 | fingerprint => $fingerprint, 99 | ); 100 | 101 | unlink "$file"; 102 | unlink "$file.pub"; 103 | 104 | return \%struct; 105 | } 106 | 107 | sub list_for_api { 108 | my $self = shift; 109 | my %args = ( 110 | @_, 111 | ); 112 | 113 | my @arr = $self->SUPER::list_for_api( %args ); 114 | delete $_->{private_key} for @arr; 115 | 116 | return @arr; 117 | } 118 | 119 | 1; 120 | -------------------------------------------------------------------------------- /app/lib/Core/Jobs.pm: -------------------------------------------------------------------------------- 1 | package Core::Jobs; 2 | 3 | use v5.14; 4 | use parent 'Core::Base'; 5 | use Core::Base; 6 | use Core::Const; 7 | 8 | sub job_prolongate { 9 | my $self = shift; 10 | 11 | my @arr = get_service('UserService')->list_expired_services( admin => 1 ); 12 | 13 | for ( @arr ) { 14 | say sprintf("%d %d %s %s", 15 | $_->{user_id}, 16 | $_->{user_service_id}, 17 | $_->{created}, 18 | $_->{expire}, 19 | ); 20 | 21 | my $user = $self->user->id( $_->{user_id} ); 22 | next unless $user->lock( timeout => 1 ); 23 | 24 | $user->us->id( $_->{user_service_id} )->touch; 25 | } 26 | 27 | return SUCCESS, { msg => 'successful' }; 28 | } 29 | 30 | sub job_cleanup { 31 | my $self = shift; 32 | my $task = shift; 33 | 34 | my $days = $task->event_settings->{days} || 10; 35 | my @arr = get_service('us')->list_for_delete( days => $days );; 36 | 37 | for ( @arr ) { 38 | say sprintf("%d %d %s %s", 39 | $_->{user_id}, 40 | $_->{user_service_id}, 41 | $_->{created}, 42 | $_->{expire}, 43 | ); 44 | 45 | my $user = $self->user->id( $_->{user_id} ); 46 | next unless $user->lock( timeout => 1 ); 47 | 48 | $user->us->id( $_->{user_service_id} )->delete; 49 | } 50 | 51 | return SUCCESS, { msg => 'successful' }; 52 | } 53 | 54 | sub job_make_forecasts { 55 | my $self = shift; 56 | my $task = shift; 57 | 58 | my %settings; 59 | if ( $task ) { 60 | $settings{days_before_notification} = $task->settings->{days_before_notification}; 61 | $settings{blocked} = $task->settings->{blocked}; 62 | } 63 | 64 | my $users = $self->user->items; 65 | 66 | my @affected; 67 | for my $u ( @$users ) { 68 | my $ret = $u->pays->forecast( 69 | $settings{days_before_notification} ? ( days => $settings{days_before_notification} ) : (), 70 | $settings{blocked} ? ( blocked => $settings{blocked} ) : (), 71 | ); 72 | next unless $ret->{total}; 73 | 74 | $u->make_event( 'forecast' ); 75 | push @affected, $u->id, 76 | } 77 | return SUCCESS, { msg => 'successful', user_matches => \@affected }; 78 | } 79 | 80 | sub job_users { 81 | my $self = shift; 82 | my $task = shift; 83 | 84 | my %settings = ( 85 | %{ $task->event_settings }, 86 | %{ $task->settings }, 87 | ); 88 | 89 | my $users = $self->user->items( 90 | where => { 91 | $settings{user_id} ? ( user_id => $settings{user_id} ) : (), 92 | }, 93 | ); 94 | 95 | for my $user ( @$users ) { 96 | $user->srv('spool')->add( 97 | prio => 100, 98 | event => { 99 | title => $task->event->{title}, 100 | server_gid => $task->event->{server_gid}, 101 | }, 102 | settings => \%settings, 103 | ); 104 | } 105 | return SUCCESS, { msg => 'successful' }; 106 | } 107 | 108 | 1; 109 | -------------------------------------------------------------------------------- /app/lib/Core/Misc.pm: -------------------------------------------------------------------------------- 1 | package Core::Misc; 2 | 3 | use v5.14; 4 | use parent 'Core::Base'; 5 | use Core::Base; 6 | use Core::Utils; 7 | 8 | use vars qw($AUTOLOAD); 9 | sub AUTOLOAD { 10 | my $self = shift; 11 | 12 | if ( $AUTOLOAD =~ /^.*::(\w+)$/ ) { 13 | my $method = $1; 14 | 15 | if ( $self->can(sprintf('Core::Utils::%s', $method)) ) { 16 | no strict 'refs'; 17 | my $ret = &{"Core::Utils::$method"}( convert_template_args(@_) ); 18 | return $ret; # always return ref 19 | } 20 | 21 | return undef; 22 | }; 23 | } 24 | 25 | sub convert_template_args { 26 | my @ret; 27 | for ( @_ ) { 28 | if ( ref $_ eq 'HASH' ) { 29 | push @ret, %{ $_ }; 30 | } else { 31 | push @ret, $_; 32 | } 33 | } 34 | return @ret; 35 | } 36 | 37 | 1; 38 | -------------------------------------------------------------------------------- /app/lib/Core/Orders.pm: -------------------------------------------------------------------------------- 1 | package Core::Orders; 2 | 3 | use v5.14; 4 | use parent 'Core::Base'; 5 | use Core::Base; 6 | 7 | sub table { return 'invoices' }; 8 | 9 | sub structure { 10 | return { 11 | id => { 12 | type => 'number', 13 | key => 1, 14 | }, 15 | date => { 16 | type => 'date', 17 | required => 1, 18 | }, 19 | user_id => { 20 | type => 'number', 21 | auto_fill => 1, 22 | }, 23 | total => { 24 | type => 'number', 25 | required => 1, 26 | }, 27 | text => { 28 | type => 'text', 29 | }, 30 | } 31 | } 32 | 33 | 34 | 1; 35 | -------------------------------------------------------------------------------- /app/lib/Core/Profile.pm: -------------------------------------------------------------------------------- 1 | package Core::Profile; 2 | 3 | use v5.14; 4 | use parent 'Core::Base'; 5 | 6 | sub table { return 'profiles' }; 7 | 8 | sub structure { 9 | return { 10 | id => { 11 | type => 'number', 12 | key => 1, 13 | }, 14 | user_id => { 15 | type => 'number', 16 | auto_fill => 1, 17 | }, 18 | data => { type => 'json', value => {} }, 19 | created => { 20 | type => 'now', 21 | }, 22 | } 23 | } 24 | 25 | 1; 26 | 27 | -------------------------------------------------------------------------------- /app/lib/Core/Report.pm: -------------------------------------------------------------------------------- 1 | package Core::Report; 2 | 3 | use v5.14; 4 | use utf8; 5 | use parent 'Core::Base'; 6 | use Core::Base; 7 | 8 | *error = \&add_error; 9 | *fatal = \&add_error; 10 | *warning = \&add_error; 11 | 12 | sub add_error { 13 | my $self = shift; 14 | my @msg = @_; 15 | my $msg; 16 | if (ref $msg[0]) { 17 | $msg = $msg[0]; 18 | } else { 19 | $msg = join(' ', @msg); 20 | } 21 | logger->warning( $msg ); 22 | push @{ $self->{errors}||=[] }, $msg; 23 | return $msg; 24 | } 25 | 26 | sub errors { 27 | my $self = shift; 28 | my $ret = $self->{errors} ? delete $self->{errors} : []; 29 | return wantarray ? @{ $ret } : $ret; 30 | } 31 | 32 | sub is_success { 33 | my $self = shift; 34 | return scalar @{ $self->{errors} || [] } ? 0 : 1; 35 | } 36 | 37 | 1; 38 | -------------------------------------------------------------------------------- /app/lib/Core/Server.pm: -------------------------------------------------------------------------------- 1 | package Core::Server; 2 | 3 | use v5.14; 4 | use parent 'Core::Base'; 5 | use Core::Base; 6 | 7 | sub table { return 'servers' }; 8 | 9 | sub structure { 10 | return { 11 | server_id => { 12 | type => 'number', 13 | key => 1, 14 | }, 15 | server_gid => { 16 | type => 'number', 17 | }, 18 | name => { 19 | type => 'text', 20 | }, 21 | transport => { # ssh,http,etc... 22 | type => 'text', 23 | }, 24 | host => { 25 | type => 'text', 26 | }, 27 | ip => { # ip адрес для построения DNS 28 | type => 'text', 29 | }, 30 | weight => { 31 | type => 'number', 32 | }, 33 | success_count => { 34 | type => 'number', 35 | }, 36 | fail_count => { 37 | type => 'number', 38 | }, 39 | services_count => { 40 | type => 'number', 41 | }, 42 | enabled => { 43 | type => 'number', 44 | }, 45 | settings => { type => 'json', value => {} }, 46 | } 47 | } 48 | 49 | sub servers_by_group_id { 50 | my $self = shift; 51 | my @args = @_; 52 | 53 | my $gid; 54 | 55 | if ( scalar @args == 1 ) { 56 | $gid = $args[0]; 57 | } else { 58 | $gid = $args[1]; # gid => N 59 | } 60 | 61 | return $self->_list( where => { 62 | server_gid => $gid, 63 | enabled => 1, 64 | }); 65 | } 66 | 67 | sub key_id { 68 | my $self = shift; 69 | 70 | my $key_id = $self->res->{settings}->{key_id}; 71 | return undef unless $key_id; 72 | } 73 | 74 | sub key_file { 75 | my $self = shift; 76 | 77 | my $key_id = $self->key_id || return undef; 78 | 79 | if ( my $obj = get_service('Identities', _id => $key_id) ) { 80 | return $obj->private_key_file; 81 | }; 82 | return undef; 83 | } 84 | 85 | sub add { 86 | my $self = shift; 87 | my %args = ( 88 | @_, 89 | ); 90 | 91 | $args{transport} ||= $args{server}->{transport}; 92 | 93 | return $self->SUPER::add( %args ); 94 | } 95 | 96 | sub list_by_transport { 97 | my $self = shift; 98 | my $transport = shift; 99 | 100 | return () unless $transport; 101 | 102 | my @servers = $self->_list( 103 | where => { 104 | transport => $transport, 105 | enabled => 1, 106 | }, 107 | ); 108 | 109 | return @servers; 110 | } 111 | 112 | sub services_count_increase { 113 | my $self = shift; 114 | 115 | my $ret = $self->do("UPDATE servers SET services_count=services_count+1 WHERE server_id=?", $self->id ); 116 | $self->reload() if $ret; 117 | } 118 | 119 | sub services_count_decrease { 120 | my $self = shift; 121 | 122 | my $ret = $self->do("UPDATE servers SET services_count=services_count-1 WHERE server_id=?", $self->id ); 123 | $self->reload() if $ret; 124 | } 125 | 126 | sub groups { 127 | my $self = shift; 128 | return get_service('ServerGroups'); 129 | } 130 | 131 | sub group { 132 | my $self = shift; 133 | 134 | if ( my $group = get_service('ServerGroups', _id => $self->get_server_gid ) ) { 135 | return $group; 136 | } 137 | return undef; 138 | } 139 | 140 | 1; 141 | -------------------------------------------------------------------------------- /app/lib/Core/ServerGroups.pm: -------------------------------------------------------------------------------- 1 | package Core::ServerGroups; 2 | 3 | use v5.14; 4 | use parent 'Core::Base'; 5 | use Core::Base; 6 | 7 | sub table { return 'servers_groups' }; 8 | 9 | sub structure { 10 | return { 11 | group_id => { 12 | type => 'number', 13 | key => 1, 14 | }, 15 | name => { 16 | type => 'text', 17 | }, 18 | type => { # способ выборки серверов из группы 19 | type => 'text', 20 | default => 'random', 21 | }, 22 | transport => { 23 | type => 'text', 24 | default => 'ssh', 25 | }, 26 | settings => { 27 | type => 'text', 28 | }, 29 | } 30 | } 31 | 32 | # Возвращаем сервер (сервера), в зависимости от настоек группы 33 | sub get_servers { 34 | my $self = shift; 35 | 36 | my $group = $self->get(); 37 | unless ( $group ) { 38 | logger->error('ServerGroup not found for id: ' . $self->id ); 39 | return undef; 40 | } 41 | 42 | my @list = get_service('server')->servers_by_group_id( gid => $self->id ); 43 | my @servers; 44 | 45 | for ( @list ) { 46 | if ( $_->{settings}->{max_services} ) { 47 | if ( $_->{services_count} >= $_->{settings}->{max_services} ) { 48 | logger->warning('The server', $_->{server_id}, 'is full' ); 49 | next; 50 | } 51 | } 52 | push @servers, $_; 53 | } 54 | 55 | unless ( scalar @servers ) { 56 | logger->warning('No servers found in the group'); 57 | return undef; 58 | } 59 | 60 | if ( $group->{type} eq 'random' ) { 61 | my $num_server = int rand scalar @servers; 62 | return ( $servers[ $num_server ] ); 63 | } elsif ( $group->{type} eq 'by-one' ) { 64 | my @srv = sort { $a->{services_count} <=> $b->{services_count} } @servers; 65 | return pop @srv; 66 | } elsif ( $group->{type} eq 'evenly' ) { 67 | my @srv = sort { $b->{services_count} <=> $a->{services_count} } @servers; 68 | return pop @srv; 69 | } else { 70 | logger->error('Unknown type: ' . $group->{type} ); 71 | return undef; 72 | } 73 | 74 | return undef; 75 | } 76 | 77 | 1; 78 | -------------------------------------------------------------------------------- /app/lib/Core/Services/Dns.pm: -------------------------------------------------------------------------------- 1 | package Core::Services::Dns; 2 | 3 | use v5.14; 4 | use parent 'Core::Base'; 5 | use Core::Base; 6 | use Core::Const; 7 | 8 | sub _id { return 'Services::Dns' }; 9 | 10 | sub data_for_transport { 11 | my $self = shift; 12 | my %args = ( 13 | user_service_id => undef, 14 | @_, 15 | ); 16 | 17 | my ( $domain_service ) = get_service('domain')->list_services( 18 | user_service_id => $args{user_service_id}, 19 | ); 20 | 21 | unless ( $domain_service ) { 22 | return FAIL, { error => 'domain not exists for user_service: ' . $args{user_service_id} }; 23 | } 24 | 25 | my $domain = get_service('domain', _id => $domain_service->{domain_id} ); 26 | unless ( $domain->real_domain ) { 27 | return FAIL, { error => 'domain not found' }; 28 | } 29 | 30 | return SUCCESS, { 31 | domain => $domain->real_domain, 32 | headers => $domain->dns->headers, 33 | records => [ $domain->dns->records ], 34 | }; 35 | } 36 | 37 | # Сюда приходит ответ от транспорта 38 | sub transport_response_data { 39 | my $self = shift; 40 | 41 | return SUCCESS; 42 | } 43 | 44 | 1; 45 | -------------------------------------------------------------------------------- /app/lib/Core/Services/Web.pm: -------------------------------------------------------------------------------- 1 | package Core::Services::Web; 2 | 3 | use v5.14; 4 | use parent 'Core::Base'; 5 | use Core::Base; 6 | use Core::Const; 7 | 8 | # Имя сервиса в менеджере сервисов. 9 | # Используем один и тот-же экземпляр для сервиса (имя сервиса не содержит идентификатор) 10 | sub _id { return 'Services::Web' }; 11 | 12 | # Готовим данные для транспорта 13 | sub data_for_transport { 14 | my $self = shift; 15 | my %args = ( 16 | user_service_id => undef, 17 | @_, 18 | ); 19 | 20 | my $us = get_service('us', _id => $args{user_service_id}); 21 | 22 | my @domains; 23 | for ( scalar $us->domains->get ) { 24 | push @domains, $_->{punycode} || $_->{domain}; 25 | } 26 | 27 | my $object = $us->data_for_transport; 28 | 29 | return SUCCESS, { 30 | object => $object, 31 | domains => \@domains, 32 | }; 33 | } 34 | 35 | # Сюда приходит ответ от транспорта 36 | sub transport_response_data { 37 | my $self = shift; 38 | 39 | return SUCCESS; 40 | } 41 | 42 | 1; 43 | -------------------------------------------------------------------------------- /app/lib/Core/Sessions.pm: -------------------------------------------------------------------------------- 1 | package Core::Sessions; 2 | 3 | use v5.14; 4 | use parent 'Core::Base'; 5 | use Core::Base; 6 | use Core::Utils qw( now ); 7 | 8 | sub table { return 'sessions' }; 9 | 10 | sub table_allow_insert_key { return 1 }; 11 | 12 | sub structure { 13 | return { 14 | id => { 15 | type => 'text', 16 | key => 1, 17 | }, 18 | user_id => { 19 | type => 'number', 20 | }, 21 | created => { 22 | type => 'text', 23 | }, 24 | updated => { 25 | type => 'text', 26 | }, 27 | settings => { type => 'json', value => {} }, 28 | } 29 | } 30 | 31 | sub _generate_id { 32 | my @chars =('a' .. 'z', 0 .. 9, 'A' .. 'Z', 0 .. 9); 33 | my $session_id = join('', @chars[ map { rand @chars } (1 .. 32) ]); 34 | 35 | return $session_id; 36 | } 37 | 38 | sub add { 39 | my $self = shift; 40 | my %args = ( 41 | id => _generate_id(), 42 | user_id => $self->SUPER::user_id, 43 | @_, 44 | ); 45 | 46 | my $session_id = $self->SUPER::add( %args ); 47 | 48 | $self->_delete_expired; 49 | $self->res->{id} = $session_id; 50 | 51 | return $session_id; 52 | } 53 | 54 | sub validate { 55 | my $self = shift; 56 | my %args = ( 57 | session_id => undef, 58 | @_, 59 | ); 60 | 61 | my $session = $self->id( $args{session_id} ); 62 | return undef unless $session; 63 | 64 | # do not update more than 3 minutes 65 | $self->_set( 66 | updated => now, 67 | where => { 68 | id => $args{session_id}, 69 | updated => { '<', \[ 'NOW() - INTERVAL ? MINUTE', 3 ] }, 70 | }, 71 | ); 72 | 73 | return $session; 74 | } 75 | 76 | sub _delete_expired { 77 | my $self = shift; 78 | 79 | $self->_delete( 80 | where => { 81 | updated => { '<', \[ 'NOW() - INTERVAL ? DAY', 3 ] }, 82 | }, 83 | ); 84 | } 85 | 86 | sub delete { 87 | my $self = shift; 88 | 89 | $self->_delete_expired; 90 | $self->SUPER::delete( @_ ); 91 | } 92 | 93 | sub delete_user_sessions { 94 | my $self = shift; 95 | my %args = ( 96 | user_id => undef, 97 | @_, 98 | ); 99 | 100 | return undef unless $args{user_id}; 101 | 102 | return $self->_delete( 103 | where => { 104 | user_id => $args{user_id}, 105 | }, 106 | ); 107 | } 108 | 109 | sub delete_all { 110 | my $self = shift; 111 | 112 | return $self->SUPER::_delete( 113 | where => { 114 | user_id => $self->SUPER::user_id, 115 | }, 116 | ); 117 | } 118 | 119 | sub user_id { 120 | my $self = shift; 121 | 122 | return $self->res->{user_id}; 123 | } 124 | 125 | 1; 126 | -------------------------------------------------------------------------------- /app/lib/Core/SpoolHistory.pm: -------------------------------------------------------------------------------- 1 | package Core::SpoolHistory; 2 | 3 | use v5.14; 4 | use parent 'Core::Spool'; 5 | use Core::Base; 6 | 7 | sub table { return 'spool_history' }; 8 | 9 | sub structure { 10 | my $self = shift; 11 | return { 12 | spool_id => { 13 | type => 'number', 14 | required => 1, 15 | }, 16 | %{ $self->SUPER::structure }, 17 | created => { # use date of `spool`. Do not use `now` 18 | type => 'date', 19 | required => 1, 20 | }, 21 | } 22 | } 23 | 24 | sub add { 25 | my $self = shift; 26 | my %args = @_; 27 | 28 | $args{spool_id} = delete $args{id}; 29 | 30 | return $self->SUPER::add( %args ); 31 | } 32 | 33 | sub clean { 34 | my $self = shift; 35 | my %args = ( 36 | days => 30, 37 | get_smart_args( @_ ), 38 | ); 39 | 40 | $self->srv('console')->clean( days => $args{days} ); 41 | 42 | return $self->_delete( where => { 43 | executed => { '<', \[ 'NOW() - INTERVAL ? DAY', 30 ] }, 44 | }); 45 | } 46 | 47 | 1; 48 | -------------------------------------------------------------------------------- /app/lib/Core/System/Service.pm: -------------------------------------------------------------------------------- 1 | package Core::System::Service; 2 | use strict; 3 | 4 | use base qw( Core::System::Object ); 5 | 6 | use Core::System::ServiceManager qw($SERVICE_MANAGER); 7 | 8 | sub new { 9 | my $proto = shift; 10 | my %args = ( 11 | id => undef, 12 | @_, 13 | ); 14 | my $class = ref($proto) || $proto; 15 | 16 | return bless(\%args, $class); 17 | } 18 | 19 | sub register { 20 | my $self = shift; 21 | my $id = shift; 22 | return $SERVICE_MANAGER->register_service( $self, $id, @_ ); 23 | } 24 | 25 | sub unregister { 26 | return $SERVICE_MANAGER->unregister_service(shift); 27 | } 28 | 29 | 1; 30 | -------------------------------------------------------------------------------- /app/lib/Core/Test.pm: -------------------------------------------------------------------------------- 1 | package Core::Test; 2 | 3 | use v5.14; 4 | use parent 'Core::Base'; 5 | 6 | sub list_for_api { 7 | my $self = shift; 8 | 9 | return { test => 'OK' }; 10 | } 11 | 12 | sub http_echo { 13 | my $self = shift; 14 | my %args = @_; 15 | 16 | return { 17 | payload => \%args, 18 | }; 19 | } 20 | 21 | 1; 22 | -------------------------------------------------------------------------------- /app/lib/Core/Transport/Local.pm: -------------------------------------------------------------------------------- 1 | package Core::Transport::Local; 2 | 3 | use parent 'Core::Base'; 4 | 5 | use v5.14; 6 | use utf8; 7 | use Core::Base; 8 | use Core::Const; 9 | 10 | sub send { 11 | my $self = shift; 12 | my $task = shift; 13 | 14 | my $template_id = $task->event_settings->{template_id} || 15 | $task->settings->{template_id}; 16 | 17 | my $template = get_service('template', _id => $template_id ); 18 | unless ( $template ) { 19 | return undef, { 20 | error => "template with id `$template_id` not found", 21 | } 22 | } 23 | 24 | my %settings = ( 25 | %{ $template->get_settings || {} }, 26 | %{ $task->event_settings }, 27 | ); 28 | 29 | my $content = $template->parse( 30 | $task->settings->{user_service_id} ? ( usi => $task->settings->{user_service_id} ) : (), 31 | task => $task, 32 | vars => { 33 | SUCCESS => SUCCESS, 34 | FAIL => FAIL, 35 | }, 36 | ); 37 | 38 | my %answer = $task->answer; 39 | my $status = exists $answer{status} ? $answer{status} : SUCCESS; 40 | 41 | return $status, { 42 | result => $content, 43 | %answer, 44 | }; 45 | } 46 | 47 | 1; 48 | -------------------------------------------------------------------------------- /app/lib/Core/Zones.pm: -------------------------------------------------------------------------------- 1 | package Core::Zones; 2 | 3 | use v5.14; 4 | use parent 'Core::Base'; 5 | use Core::Base; 6 | 7 | sub table { return 'zones' }; 8 | 9 | sub structure { 10 | return { 11 | zone_id => { 12 | type => 'number', 13 | key => 1, 14 | }, 15 | name => { 16 | type => 'text', 17 | required => 1, 18 | }, 19 | order => { 20 | type => 'number', 21 | default => 1, 22 | }, 23 | server => { 24 | type => 'text', 25 | }, 26 | query => { 27 | type => 'text', 28 | }, 29 | service_id => { 30 | type => 'number', 31 | }, 32 | min_lenght => { 33 | type => 'number', 34 | }, 35 | disabled => { 36 | type => 'number', 37 | default => 0, 38 | }, 39 | nic_service => { 40 | type => 'number', 41 | }, 42 | nic_template => { 43 | type => 'number', 44 | }, 45 | contract => { 46 | type => 'number', 47 | default => 0, 48 | }, 49 | idn => { 50 | type => 'number', 51 | default => 0, 52 | }, 53 | punycode_only => { 54 | type => 'number', 55 | default => 0, 56 | }, 57 | } 58 | } 59 | 60 | sub list_for_api { 61 | my $self = shift; 62 | 63 | # TODO: this not supported ORDER because UserService used HASH for tree... 64 | #my $res = $self->SUPER::list_for_api( order => [ order => 'asc' ], @_ ); 65 | #return my @ret = get_service('UserService')->res( $res )->with('services')->get; 66 | 67 | my @res = $self->SUPER::list_for_api( order => [ order => 'asc' ], @_ ); 68 | 69 | my @services; 70 | push @services, $_->{service_id} for @res; 71 | 72 | my $services = get_service('service')->list( where => { service_id => { in => \@services } } ); 73 | 74 | for ( @res ) { 75 | $_->{cost} = $services->{ $_->{service_id} }->{cost}; 76 | } 77 | 78 | return @res; 79 | } 80 | 81 | 1; 82 | -------------------------------------------------------------------------------- /app/public_html/403.html: -------------------------------------------------------------------------------- 1 | { 2 | "msg" : "Forbidden", 3 | "status" : "403" 4 | } 5 | 6 | -------------------------------------------------------------------------------- /app/public_html/404.html: -------------------------------------------------------------------------------- 1 | { 2 | "msg" : "Not found", 3 | "status" : "404" 4 | } 5 | 6 | -------------------------------------------------------------------------------- /app/public_html/shm/admin/.htaccess: -------------------------------------------------------------------------------- 1 | RewriteCond %{REQUEST_FILENAME} -f [OR] 2 | RewriteCond %{REQUEST_FILENAME} -d 3 | RewriteRule (.*) - [L] 4 | 5 | RewriteRule (.*) /admin/object.cgi?object=$1 [QSA,L] 6 | 7 | -------------------------------------------------------------------------------- /app/public_html/shm/admin/console.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use v5.14; 4 | 5 | use SHM qw(:all); 6 | my $user = SHM->new(); 7 | 8 | use Core::System::ServiceManager qw( get_service ); 9 | use Core::Utils qw( 10 | parse_args 11 | ); 12 | 13 | our %in = parse_args(); 14 | 15 | unless ( $in{id} ) { 16 | print_header( status => 400 ); 17 | print_json( { error => "id not present" } ); 18 | exit 0; 19 | } 20 | 21 | my $console = get_service( 'Console', _id => $in{id} ); 22 | 23 | my $log = $console->chunk( 24 | offset => $in{offset} || 1, 25 | ); 26 | 27 | print_header( 28 | 'type' => 'text/plain', 29 | 'Access-Control-Expose-Headers' => 'x-console-eof', 30 | 'x-console-eof' => $console->eof, 31 | ); 32 | 33 | print $log; 34 | 35 | exit 0; 36 | 37 | -------------------------------------------------------------------------------- /app/public_html/shm/admin/mail_test.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use v5.14; 4 | 5 | use SHM qw(:all); 6 | my $user = SHM->new(); 7 | 8 | use Core::System::ServiceManager qw( get_service ); 9 | use Core::Utils qw( 10 | parse_args 11 | ); 12 | 13 | our %in = parse_args(); 14 | 15 | my %args = ( 16 | settings => { 17 | server_id => $in{server_id}, 18 | }, 19 | event => { 20 | title => 'test mail', 21 | kind => 'Transport::Mail', 22 | method => 'send', 23 | settings => { 24 | subject => 'Тестовое письмо из SHM', 25 | message => 'Это тестовое письмо отправленное из SHM', 26 | %{ $in{settings} || {} }, 27 | }, 28 | }, 29 | ); 30 | 31 | get_service('Events')->make( %args ); 32 | 33 | print_header(); 34 | print_json( ); 35 | 36 | $user->commit; 37 | 38 | exit 0; 39 | 40 | -------------------------------------------------------------------------------- /app/public_html/shm/admin/ssh_test.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use v5.14; 4 | 5 | use SHM qw(:all); 6 | my $user = SHM->new(); 7 | 8 | use Core::System::ServiceManager qw( get_service ); 9 | use Core::Utils qw( 10 | parse_args 11 | ); 12 | 13 | our %in = parse_args(); 14 | my $ssh = get_service( 'Transport::Ssh' ); 15 | 16 | my $pipeline_id = get_service('console')->new_pipe; 17 | 18 | my (undef, $res ) = $ssh->exec( 19 | host => 'ssm@127.0.0.1', 20 | server_id => 1, 21 | key_id => 1, 22 | pipeline_id => $pipeline_id, 23 | event_name => 'test', 24 | cmd => 'uname -a', 25 | %{ $in{settings} || {} }, 26 | ); 27 | 28 | $user->commit; 29 | 30 | exit 0; 31 | 32 | -------------------------------------------------------------------------------- /app/public_html/shm/healthcheck.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | print "Status: 204\n"; 4 | print "Content-Type: text/plain; charset=utf8\n\n"; 5 | 6 | exit 0; 7 | -------------------------------------------------------------------------------- /app/public_html/shm/object.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use v5.14; 4 | 5 | use SHM qw(:all); 6 | my $user = SHM->new(); 7 | 8 | use Core::System::ServiceManager qw( get_service ); 9 | use Core::Utils qw( 10 | parse_args 11 | start_of_month 12 | http_limit 13 | http_content_range 14 | now 15 | switch_user 16 | decode_json 17 | ); 18 | 19 | my %headers; 20 | our %in = parse_args(); 21 | delete $in{where}; 22 | 23 | my $res; 24 | my $admin = $user->authenticated->is_admin; 25 | 26 | # Switch to user 27 | if ( $admin && $in{user_id} ) { 28 | switch_user( $in{user_id} ); 29 | } 30 | 31 | $in{object} ||= $ENV{PATH_INFO}; 32 | 33 | unless ( $in{object} ) { 34 | print_header( status => 400 ); 35 | print_json( { error => "Unknown object" } ); 36 | exit 0; 37 | } 38 | 39 | $in{object} =~s/.*\///; 40 | $in{object} =~s/\.\w+$//; 41 | # Convert to lamelcase 42 | $in{object} = join('', map( ucfirst $_, split /_/, $in{object} )); 43 | 44 | my $service_name = $in{object}; 45 | 46 | our $service = get_service( $service_name, ( $in{id} ? ( _id => $in{id} ) : () ) ); 47 | unless ( $service ) { 48 | print_header( status => 400 ); 49 | print_json( { error => "`$service_name` not exists" } ); 50 | exit 0; 51 | } 52 | 53 | unless ( $service->can('table') ) { 54 | print_header( status => 400 ); 55 | print_json( { error => "service not supported API" } ); 56 | exit 0; 57 | } 58 | 59 | if ( $in{method} ) { 60 | my $method = "api_$in{method}"; 61 | if ( $service->can( $method ) ) { 62 | $res = $service->$method( %in, admin => $admin ); 63 | if ( !ref $res ) { 64 | $res = [ $res ]; 65 | } 66 | } 67 | else { 68 | %headers = ( status => 404 ); 69 | $res = { error => "Method not found" }; 70 | } 71 | } elsif ( $ENV{REQUEST_METHOD} eq 'PUT' ) { 72 | if ( my $ret = $service->api( 'add', %in, admin => $admin ) ) { 73 | my %data = ref $ret ? $ret->get : $service->id( $ret )->get; 74 | $res = \%data; 75 | } 76 | else { 77 | %headers = ( status => 400 ); 78 | $res = { error => "Can't add new object" }; 79 | } 80 | } 81 | elsif ( $ENV{REQUEST_METHOD} eq 'POST' ) { 82 | if ( $service = $service->id( get_service_id() ) ) { 83 | $service->api( 'set', %in, admin => $admin ); 84 | my %ret = $service->get; 85 | $res = \%ret; 86 | } else { 87 | %headers = ( status => 404 ); 88 | $res = { error => "Object not found" }; 89 | } 90 | } 91 | elsif ( $ENV{REQUEST_METHOD} eq 'DELETE' ) { 92 | if ( my $obj = $service->id( get_service_id() ) ) { 93 | $obj->api( 'delete', %in, admin => $admin ); 94 | %headers = ( status => 204 ); 95 | } else { 96 | %headers = ( status => 404 ); 97 | $res = { error => "Service not found" }; 98 | } 99 | } 100 | else { 101 | $in{filter} = decode_json( $in{filter} ) if $in{filter}; 102 | 103 | my @ret = $service->list_for_api( %in, admin => $admin ); 104 | $res = { 105 | items => $service->found_rows(), 106 | limit => $in{limit} || 25, 107 | offset => $in{offset} || 0, 108 | data => \@ret, 109 | }; 110 | 111 | my $numRows = $service->found_rows; 112 | %headers = http_content_range( http_limit, count => $numRows ); 113 | } 114 | 115 | my $report = get_service('report'); 116 | 117 | unless ( $report->is_success ) { 118 | %headers = ( status => 400 ); 119 | $res = { error => $report->errors }; 120 | } 121 | 122 | print_header( %headers ); 123 | print_json( $res ); 124 | 125 | $user->commit(); 126 | 127 | exit 0; 128 | 129 | sub get_service_id { 130 | my $service_id = $in{ $service->get_table_key } || $in{id}; 131 | 132 | unless ( $service_id ) { 133 | print_header( status => 400 ); 134 | print_json( { error => '`id` not present' } ); 135 | exit 0; 136 | } 137 | return $service_id; 138 | } 139 | -------------------------------------------------------------------------------- /app/public_html/shm/pay_systems/cryptocloud.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use v5.14; 4 | 5 | use Digest::SHA qw(sha1_hex); 6 | use SHM qw(:all); 7 | use Core::Utils qw( 8 | encode_utf8 9 | decode_json 10 | ); 11 | 12 | our %vars = parse_args(); 13 | $vars{amount} ||= 100; 14 | 15 | if ( $vars{action} eq 'create' && $vars{amount} ) { 16 | my $user; 17 | if ( $vars{user_id} ) { 18 | $user = SHM->new( user_id => $vars{user_id} ); 19 | 20 | if ( $vars{message_id} ) { 21 | get_service('Transport::Telegram')->deleteMessage( message_id => $vars{message_id} ); 22 | } 23 | } else { 24 | $user = SHM->new(); 25 | } 26 | 27 | my $config = get_service('config', _id => 'pay_systems'); 28 | my $api_key = $config->get_data->{cryptocloud}->{api_key}; 29 | my $shop_id = $config->get_data->{cryptocloud}->{shop_id}; 30 | 31 | print_json({ status => 400, msg => 'Error: api_key required. Please set it in config' }) unless $api_key; 32 | print_json({ status => 400, msg => 'Error: shop_id required. Please set it in config' }) unless $shop_id; 33 | exit 0 unless( $api_key && $shop_id ); 34 | 35 | use LWP::UserAgent (); 36 | my $ua = LWP::UserAgent->new(timeout => 10); 37 | 38 | $ua->default_header('Authorization' => sprintf("Token %s", $api_key )); 39 | 40 | my $response = $ua->post( 'https://api.cryptocloud.plus/v1/invoice/create', 41 | Content => encode_utf8({ 42 | shop_id => $shop_id, 43 | order_id => $user->id, 44 | amount => $vars{amount}, 45 | }), 46 | ); 47 | 48 | if ( $response->is_success ) { 49 | my $response_data = decode_json( $response->decoded_content ); 50 | print_header( 51 | location => $response_data->{pay_url}, 52 | status => 301, 53 | ); 54 | } else { 55 | print_json({ 56 | status => 503, 57 | decoded_content => $response->decoded_content, 58 | status_line => $response->status_line, 59 | }); 60 | } 61 | 62 | exit 0; 63 | } 64 | 65 | my $user = SHM->new( skip_check_auth => 1 ); 66 | 67 | $user->payment( 68 | user_id => $vars{order_id} || 1, 69 | money => $vars{amount_crypto}, 70 | pay_system_id => 'cryptocloud', 71 | comment => \%vars, 72 | ); 73 | 74 | print_json( { status => 200, msg => "Payment successful" } ); 75 | 76 | $user->commit; 77 | 78 | exit 0; 79 | 80 | -------------------------------------------------------------------------------- /app/public_html/shm/pay_systems/freekassa.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | # https://docs.freekassa.com/#section/2.-Vvedenie 4 | # http://127.0.0.1:8081/shm/pay_systems/freekassa.cgi?action=create&amount=100&t=1 5 | 6 | use CGI::Carp qw(fatalsToBrowser); 7 | use v5.14; 8 | use Core::Base; 9 | use LWP::UserAgent (); 10 | use URI; 11 | use URI::QueryParam; 12 | use Digest::SHA qw(sha1_hex); 13 | use Digest::MD5 qw(md5_hex); 14 | 15 | use SHM qw(:all); 16 | our %vars = parse_args(); 17 | $vars{amount} ||= 100; 18 | 19 | for ( keys %vars ) { 20 | $vars{ lc $_ } = delete $vars{ $_ }; 21 | } 22 | 23 | if ( $vars{action} eq 'create' && $vars{amount} ) { 24 | my $user; 25 | if ( $vars{user_id} ) { 26 | $user = SHM->new( user_id => $vars{user_id} ); 27 | 28 | if ( $vars{message_id} ) { 29 | get_service('Transport::Telegram')->deleteMessage( message_id => $vars{message_id} ); 30 | } 31 | } else { 32 | $user = SHM->new(); 33 | } 34 | 35 | my $config = get_service('config', _id => 'pay_systems'); 36 | unless ( $config ) { 37 | print_json( { status => 400, msg => 'Error: config pay_systems->freekassa not exists' } ); 38 | exit 0; 39 | } 40 | 41 | my $settings = $config->get_data->{freekassa}; 42 | 43 | my %p = ( 44 | merchant_id => $settings->{merchant_id}, 45 | order_amount => $vars{amount}, 46 | secret_word => $settings->{secret_word_1}, 47 | currency => $settings->{currency} || 'RUB', 48 | order_id => $user->id, 49 | ); 50 | 51 | for ( sort keys %p ) { 52 | unless ( $p{ $_ } ) { 53 | print_json( { status => 400, msg => "Error: param '$_' not present" } ); 54 | exit 0; 55 | } 56 | } 57 | 58 | my $sign = md5_hex( join(':', $p{merchant_id}, $p{order_amount}, $p{secret_word}, $p{currency}, $p{order_id} ) ); 59 | 60 | my $uri = URI->new( 'https://pay.freekassa.com' ); 61 | $uri->query_param_append( 'm', $p{merchant_id} ); 62 | $uri->query_param_append( 'oa', $p{order_amount} ); 63 | $uri->query_param_append( 'us_user_id', $user->id ); 64 | $uri->query_param_append( 'currency', $p{currency} ); 65 | $uri->query_param_append( 'o', $p{order_id} ); 66 | $uri->query_param_append( 's', $sign ); 67 | my $url = $uri->as_string; 68 | 69 | print_header( 70 | location => $url, 71 | status => 301, 72 | ); 73 | exit 0; 74 | } 75 | 76 | my $user = SHM->new( skip_check_auth => 1 ); 77 | 78 | if ( $vars{status_check} ) { 79 | $user->payment( 80 | user_id => 1, 81 | money => 0, 82 | pay_system_id => 'freekassa-test', 83 | comment => \%vars, 84 | ); 85 | $user->commit; 86 | print_json( { status => 200, msg => 'Test OK' } ); 87 | exit 0; 88 | } 89 | 90 | my $config = get_service('config', _id => 'pay_systems'); 91 | unless ( $config ) { 92 | print_json( { status => 400, msg => 'Error: config pay_systems not exists' } ); 93 | exit 0; 94 | } 95 | 96 | my $settings = $config->get_data->{freekassa}; 97 | 98 | if ( $settings->{secret_word_2} ) { 99 | my $sign = md5_hex( join(':', 100 | $settings->{merchant_id}, 101 | $vars{amount}, 102 | $settings->{secret_word_2}, 103 | $vars{merchant_order_id}, 104 | )); 105 | 106 | if ( $sign ne $vars{sign} ) { 107 | print_json( { status => 400, msg => 'Error: incorrect signature' } ); 108 | exit 0; 109 | } 110 | } 111 | 112 | my $user_id = $vars{us_user_id}; 113 | unless ( $user_id ) { 114 | print_json( { status => 400, msg => 'User id required' } ); 115 | exit 0; 116 | } 117 | 118 | unless ( $user = $user->id( $user_id ) ) { 119 | print_json( { status => 404, msg => "User [$user_id] not found" } ); 120 | exit 0; 121 | } 122 | 123 | unless ( $user->lock( timeout => 10 )) { 124 | print_json( { status => 408, msg => "The service is locked. Try again later" } ); 125 | exit 0; 126 | } 127 | 128 | $user->payment( 129 | user_id => $user->id, 130 | money => $vars{amount} || 0, 131 | pay_system_id => 'freekassa', 132 | comment => \%vars, 133 | ); 134 | 135 | $user->commit; 136 | 137 | print_header( status => 200, type => 'text/html' ); 138 | print "YES"; 139 | 140 | exit 0; 141 | 142 | -------------------------------------------------------------------------------- /app/public_html/shm/pay_systems/yoomoney.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | # https://yoomoney.ru/transfer/myservices/http-notification 4 | 5 | use v5.14; 6 | use Core::Base; 7 | use LWP::UserAgent (); 8 | use Digest::SHA qw(sha1_hex); 9 | use Core::Utils qw( 10 | encode_utf8 11 | ); 12 | 13 | use SHM qw(:all); 14 | our %vars = parse_args(); 15 | $vars{amount} ||= 100; 16 | 17 | if ( $vars{action} eq 'create' && $vars{amount} ) { 18 | my $user; 19 | if ( $vars{user_id} ) { 20 | $user = SHM->new( user_id => $vars{user_id} ); 21 | 22 | if ( $vars{message_id} ) { 23 | get_service('Transport::Telegram')->deleteMessage( message_id => $vars{message_id} ); 24 | } 25 | 26 | } else { 27 | $user = SHM->new(); 28 | } 29 | 30 | my $config = get_service('config', _id => 'pay_systems'); 31 | unless ( $config ) { 32 | print_json( { status => 400, msg => 'Error: config pay_systems->yoomoney not exists' } ); 33 | exit 0; 34 | } 35 | 36 | my $lwp = LWP::UserAgent->new(timeout => 10); 37 | my $response = $lwp->post( 38 | 'https://yoomoney.ru/quickpay/confirm', 39 | Content_Type => 'form-data', 40 | Content => encode_utf8([ 41 | 'quickpay-form' => 'shop', 42 | receiver => $config->get_data->{yoomoney}->{account}, 43 | label => $user->id, 44 | sum => $vars{amount}, 45 | ]), 46 | ); 47 | 48 | my $location = $response->headers->{location}; 49 | 50 | if ( $location ) { 51 | print_header( 52 | location => $response->headers->{location}, 53 | status => 301, 54 | ); 55 | } else { 56 | print_header( status => 503 ); 57 | print $response->content; 58 | } 59 | exit 0; 60 | } 61 | 62 | my $user = SHM->new( skip_check_auth => 1 ); 63 | 64 | my $config = get_service('config', _id => 'pay_systems'); 65 | unless ( $config ) { 66 | print_json( { status => 400, msg => 'Error: config pay_systems->yoomoney not exists' } ); 67 | exit 0; 68 | } 69 | 70 | my $secret = $config->get_data->{yoomoney}->{secret}; 71 | unless ( $secret ) { 72 | print_json( { status => 400, msg => 'Error: config pay_systems->yoomoney->secret not exists' } ); 73 | exit 0; 74 | } 75 | 76 | my $digest = sha1_hex( join('&', 77 | @vars{ qw/notification_type operation_id amount currency datetime sender codepro/ }, 78 | $secret, 79 | $vars{label}, 80 | )); 81 | 82 | if ( $digest ne $vars{sha1_hash} ) { 83 | print_json( { status => 400 } ); 84 | exit 0; 85 | } 86 | 87 | if ( $vars{test_notification} ) { 88 | $user->payment( 89 | user_id => 1, 90 | money => 0, 91 | pay_system_id => 'yoomoney-test', 92 | comment => \%vars, 93 | ); 94 | $user->commit; 95 | print_json( { status => 200, msg => 'Test OK' } ); 96 | exit 0; 97 | } 98 | 99 | my $date = time; 100 | my ( $user_id, $amount ) = @vars{ qw/label withdraw_amount/ }; 101 | 102 | unless ( $user_id ) { 103 | print_json( { status => 400, msg => 'User (label) required' } ); 104 | exit 0; 105 | } 106 | 107 | unless ( $user = $user->id( $user_id ) ) { 108 | print_json( { status => 404, msg => "User [$user_id] not found" } ); 109 | exit 0; 110 | } 111 | 112 | unless ( $user->lock( timeout => 10 )) { 113 | print_json( { status => 408, msg => "The service is locked. Try again later" } ); 114 | exit 0; 115 | } 116 | 117 | $user->payment( 118 | user_id => $user_id, 119 | money => $amount, 120 | pay_system_id => 'yoomoney', 121 | comment => \%vars, 122 | ); 123 | 124 | $user->commit; 125 | 126 | print_json( { status => 200, msg => "Payment successful" } ); 127 | 128 | exit 0; 129 | 130 | -------------------------------------------------------------------------------- /app/public_html/shm/user/auth.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use v5.14; 5 | 6 | my $cgi = CGI->new; 7 | 8 | use CGI::Carp qw(fatalsToBrowser); 9 | 10 | use Core::System::ServiceManager qw( get_service ); 11 | use Core::Utils qw( 12 | now 13 | parse_args 14 | ); 15 | 16 | use SHM qw(:all); 17 | my $user = SHM->new( skip_check_auth => 1 ); 18 | 19 | my %in = parse_args(); 20 | 21 | if ( $in{session_id} ) { 22 | print_header( 23 | cookie => create_cookie('session_id',$in{session_id}), 24 | location => get_service('config')->data_by_name('cli')->{url}, 25 | status => 302, 26 | ); 27 | exit 0; 28 | } 29 | 30 | 31 | my $session = get_service('sessions'); 32 | if ($session->id( $in{session_id} ) ) { 33 | print_json( { status => 0, msg => 'Already authorized', session_id => $session->id, user_id => $session->user_id } ); 34 | exit 0; 35 | } 36 | 37 | unless ( $in{login} && $in{password} ) { 38 | print_json( { status => 400, msg => 'login or password not present' } ); 39 | exit 0; 40 | } 41 | 42 | unless ( $user = $user->auth( login => trim($in{login}), password => trim($in{password}) )) { 43 | print_json( { status => 401, msg => 'Incorrect login or password' } ); 44 | exit 0; 45 | } 46 | 47 | if ( $in{admin} && !$user->is_admin ) { 48 | print_json( { status => 403, msg => 'Forbidden: user is not admin' } ); 49 | exit 0; 50 | } 51 | 52 | $session->add( 53 | user_id => $user->id, 54 | ); 55 | 56 | my $session_id = $session->id; 57 | 58 | print_header( cookie => create_cookie('session_id',$session_id) ); 59 | print_json( { status => 200, msg => 'Successfully', session_id => $session_id, user_id => $user->id() } ); 60 | 61 | $user->set( last_login => now ); 62 | $user->commit; 63 | 64 | exit 0; 65 | 66 | sub create_cookie { 67 | my $name = shift; 68 | my $value = shift; 69 | 70 | my $cookie = new CGI::Cookie( 71 | -name => $name, 72 | -value => $value, 73 | -expires => '+1M', 74 | -secure => get_service('config')->file->{session}->{'ssl'}, 75 | # -samesite => "Lax", 76 | ); 77 | return $cookie; 78 | } 79 | 80 | -------------------------------------------------------------------------------- /app/public_html/shm/user/logout.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use v5.14; 4 | use SHM qw(:all); 5 | 6 | my $user = SHM->new( skip_check_auth => 1 ); 7 | 8 | if ( my $session = validate_session() ) { 9 | $session->delete(); 10 | } 11 | 12 | print_json( { status => 0, msg => 'Logout sucessfully' } ); 13 | 14 | $user->commit; 15 | 16 | exit 0; 17 | -------------------------------------------------------------------------------- /app/public_html/shm/user/pay_systems.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use v5.14; 4 | 5 | use SHM qw(:all); 6 | my $user = SHM->new(); 7 | 8 | my $config = get_service("config", _id => 'pay_systems'); 9 | my $list = $config ? $config->get_data : {}; 10 | 11 | my @ret; 12 | 13 | for my $item ( keys %{ $list } ) { 14 | my $p = $list->{ $item }; 15 | 16 | if ( $p->{ show_for_client } ) { 17 | if ( exists $p->{template_id} ) { 18 | if ( my $template = get_service('template', _id => $p->{template_id} ) ) { 19 | $p->{template} = $template->parse(); 20 | } 21 | } 22 | push @ret, $p; 23 | } 24 | } 25 | 26 | print_json( \@ret ); 27 | 28 | exit 0; 29 | -------------------------------------------------------------------------------- /app/scripts/reset_admin_pass.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use v5.14; 4 | 5 | use SHM qw(:all); 6 | my $user = SHM->new( user_id => 1 ); 7 | 8 | $user->set( 9 | gid => 1, 10 | block => 0, 11 | ); 12 | 13 | say "Password has changed:"; 14 | say sprintf("Login: %s", $user->get_login ); 15 | say sprintf("Password: %s", $user->set_new_passwd( len => 16 ) ); 16 | 17 | $user->commit; 18 | 19 | exit 0; 20 | 21 | -------------------------------------------------------------------------------- /app/scripts/set_us_settings.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use v5.14; 4 | 5 | use SHM qw(:all); 6 | use Core::System::ServiceManager qw( get_service ); 7 | 8 | my $us = SHM->new()->services; 9 | 10 | my $data = $us->tree->with('settings')->get; 11 | 12 | update( $data ); 13 | 14 | exit; 15 | 16 | sub update { 17 | my $data = shift; 18 | 19 | for my $usi ( keys %{ $data } ) { 20 | 21 | say $usi; 22 | get_service('us', _id => $usi )->settings( $data->{ $usi }->{settings} )->settings_save; 23 | 24 | if ( $data->{ $usi }->{children} ) { 25 | update( $data->{ $usi }->{children} ); 26 | } 27 | } 28 | } 29 | 30 | exit 0; 31 | -------------------------------------------------------------------------------- /app/sql/shm/shm_data.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | INSERT INTO `users` VALUES 4 | (1,0,'admin','0df78fa86a30eca0a918fdd21a94e238133ce7ab',0,NOW(),NULL,0,0,0.00,NULL,NULL,0,1,0,'Admin',0,0.00,NULL,NULL,NULL,NULL) 5 | ; 6 | 7 | INSERT INTO `servers_groups` VALUES 8 | (default,'LOCAL','local','random',NULL), 9 | (default,'Email уведомления','mail','random',NULL), 10 | (default,'Telegram уведомления','telegram','random',NULL), 11 | (default,'Linux servers','ssh','random',NULL) 12 | ; 13 | 14 | INSERT INTO `services` VALUES 15 | (default,'Тестовая услуга',0.00,1.00,'test','[]',NULL,0,NULL,NULL,1,0,NULL,0,NULL,0,0) 16 | ; 17 | 18 | INSERT INTO `events` VALUES 19 | (default,'UserService','User password reset','user_password_reset',1,'{\"category\": \"%\", \"template_id\": \"user_password_reset\"}') 20 | ; 21 | 22 | INSERT INTO `templates` VALUES 23 | ('forecast','Уважаемый {{ user.full_name }}\n\nУведомляем Вас о сроках действия услуг:\n\n{{ FOR item IN user.pays.forecast.items }}\n- Услуга: {{ item.name }}\n Стоимость: {{ item.total }} руб.\n {{ IF item.expire }}\n Истекает: {{ item.expire }}\n {{ END }}\n{{ END }}\n\n{{ IF user.pays.forecast.dept }}\nПогашение задолженности: {{ user.pays.forecast.dept }} руб.\n{{ END }}\n\nИтого к оплате: {{ user.pays.forecast.total }} руб.\n\nУслуги, которые не будут оплачены до срока их истечения, будут приостановлены.\n\nПодробную информацию по Вашим услугам Вы можете посмотреть в вашем личном кабинете: {{ config.api.url }}\n\nЭто письмо сформировано автоматически. Если оно попало к Вам по ошибке,\nпожалуйста, сообщите об этом нам: {{ config.mail.from }}',NULL), 24 | ('user_password_reset','Уважаемый клиент.\n\nВаш новый пароль: {{ user.set_new_passwd }}\n\nАдрес кабинета: {{ config.cli.url }}','{\"subject\": \"SHM - Восстановление пароля\"}') 25 | ; 26 | 27 | INSERT INTO `config` VALUES 28 | ("_shm", '{"version":"0.0.3"}'), 29 | ('billing','{"type": "Simpler", "partner": {"income_percent": 0}}'), 30 | ("company", '{"name":"My Company LTD"}'), 31 | ("telegram", '{"token":""}'), 32 | ("api", '{"url":"https://bill.domain.ru"}'), 33 | ("cli", '{"url":"https://bill.domain.ru"}'), 34 | ("pay_systems",'{"manual":{"name":"Платеж","show_for_client":false},"yoomoney":{"name":"ЮMoney","account":"000000000000000","secret":"","show_for_client":true}}'), 35 | ("mail", '{"from":"mail@domain.ru"}') 36 | ; 37 | 38 | INSERT INTO `spool` (id,status,user_id,event) VALUES 39 | (default,'NEW',1,'{"title":"prolongate services","kind":"Jobs","method":"job_prolongate","period":"600"}'), 40 | (default,'PAUSED',1,'{"title":"cleanup services","kind":"Jobs","method":"job_cleanup","period":"86400","settings":{"days":10}}'), 41 | (default,'PAUSED',1,'{"title":"send forecasts","kind":"Jobs","method":"job_make_forecasts","period":"86400"}') 42 | ; 43 | 44 | COMMIT; 45 | 46 | -------------------------------------------------------------------------------- /app/t/acts/acts.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | use Test::More; 5 | 6 | use Data::Dumper; 7 | use v5.14; 8 | 9 | $ENV{SHM_TEST} = 1; 10 | 11 | use SHM; 12 | use Core::System::ServiceManager qw( get_service ); 13 | 14 | SHM->new( user_id => 40092 ); 15 | 16 | my @list = get_service('acts')->list( limit => 5 ); 17 | 18 | is( $list[0]->{user_id}, 40092, 'Check param in list of acts'); 19 | is( scalar( @list ), 5, 'Check count of acts list'); 20 | 21 | my @data = get_service('ActsData')->list( where => { act_id => 195 } ); 22 | is( $data[0]->{act_id}, 195, 'Check param in list of acts'); 23 | 24 | done_testing(); 25 | -------------------------------------------------------------------------------- /app/t/api/admin.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | use utf8; 3 | 4 | use Core::Utils qw/shm_test_api/; 5 | 6 | use Test::More; 7 | use Test::Deep; 8 | 9 | use SHM; 10 | my $user = SHM->new( user_id => 40094 ); 11 | my $balance_before = $user->balance; 12 | 13 | my $payment = 123; 14 | my %ret = shm_test_api( 15 | url => '/v1/admin/user/payment', 16 | login => 'admin', 17 | password => 'admin', 18 | method => 'PUT', 19 | data => { 20 | user_id => 40094, 21 | money => $payment, 22 | pay_system_id => 'manual', 23 | }, 24 | ); 25 | 26 | cmp_deeply( $ret{json}->{data}->[0], { 27 | id => ignore(), 28 | user_id => 40094, 29 | money => 123, 30 | pay_system_id => 'manual', 31 | date => ignore(), 32 | comment => undef, 33 | uniq_key => undef, 34 | }, 'Test /v1/admin/user/payment'); 35 | 36 | $user->commit; 37 | $user->reload(); 38 | 39 | is $user->balance, $balance_before + $payment, 'Check balance after payment'; 40 | 41 | done_testing(); 42 | 43 | exit 0; 44 | 45 | -------------------------------------------------------------------------------- /app/t/api/admin_services.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | use utf8; 3 | 4 | use Test::More; 5 | use Test::Deep; 6 | use Core::Utils qw/shm_test_api/; 7 | 8 | my %user = ( 9 | login => 'admin', 10 | password => 'admin', 11 | ); 12 | 13 | subtest 'GET /v1/admin/service' => sub { 14 | my %ret = shm_test_api( 15 | url => 'v1/admin/service', 16 | method => 'GET', 17 | %user, 18 | ); 19 | is $ret{json}->{items}, 15, 'Check items field'; 20 | is scalar @{ $ret{json}->{data} }, 15, 'Check count items in data'; 21 | }; 22 | 23 | subtest 'GET /v1/admin/service/110' => sub { 24 | my %ret = shm_test_api( 25 | url => 'v1/admin/service?service_id=110', 26 | method => 'GET', 27 | %user, 28 | ); 29 | is $ret{json}->{items}, 1, 'Check items field'; 30 | is scalar @{ $ret{json}->{data} }, 1, 'Check count items in data'; 31 | is $ret{json}->{data}->[0]->{service_id}, 110, 'Check service data'; 32 | is $ret{json}->{data}->[0]->{cost}, 300, 'Check service cost'; 33 | }; 34 | 35 | subtest 'POST /v1/admin/service/110' => sub { 36 | my %ret = shm_test_api( 37 | url => 'v1/admin/service', 38 | method => 'POST', 39 | data => { 40 | service_id => 110, 41 | cost => 400, 42 | }, 43 | %user, 44 | ); 45 | is $ret{json}->{data}->[0]->{cost}, 400, 'Check service cost (POST)'; 46 | 47 | %ret = shm_test_api( 48 | url => 'v1/admin/service?service_id=110', 49 | method => 'GET', 50 | %user, 51 | ); 52 | is $ret{json}->{data}->[0]->{cost}, 400, 'Check service cost (GET)'; 53 | 54 | %ret = shm_test_api( 55 | url => 'v1/admin/service', 56 | method => 'POST', 57 | data => { 58 | service_id => 110, 59 | cost => 300, 60 | }, 61 | %user, 62 | ); 63 | is $ret{json}->{data}->[0]->{cost}, 300, 'Rollback service cost'; 64 | }; 65 | 66 | subtest 'PUT /v1/admin/service' => sub { 67 | my %ret = shm_test_api( 68 | url => 'v1/admin/service', 69 | method => 'PUT', 70 | data => { 71 | service_id => 1000, 72 | name => 'test 1', 73 | category => 'test', 74 | cost => 123, 75 | }, 76 | %user, 77 | ); 78 | is $ret{json}->{data}->[0]->{cost}, 123, 'Check new service cost'; 79 | is $ret{json}->{data}->[0]->{service_id}, 1000, 'Check new service id'; 80 | 81 | %ret = shm_test_api( 82 | url => 'v1/admin/service?service_id=1000', 83 | method => 'GET', 84 | %user, 85 | ); 86 | is $ret{json}->{data}->[0]->{cost}, 123, 'Check new service cost (GET)'; 87 | 88 | my %ret = shm_test_api( 89 | url => 'v1/admin/service', 90 | method => 'PUT', 91 | data => { 92 | service_id => 1000, 93 | name => 'test 1', 94 | category => 'test', 95 | cost => 123, 96 | }, 97 | %user, 98 | ); 99 | is $ret{json}->{error}, "Can't add new object. Perhaps it already exists?"; 100 | }; 101 | 102 | subtest 'DELETE /v1/admin/service' => sub { 103 | my %ret = shm_test_api( 104 | url => 'v1/admin/service?service_id=1000', 105 | method => 'DELETE', 106 | %user, 107 | ); 108 | is scalar @{ $ret{json}->{data} }, 0; 109 | 110 | %ret = shm_test_api( 111 | url => 'v1/admin/service?service_id=1000', 112 | method => 'DELETE', 113 | %user, 114 | ); 115 | is scalar $ret{json}->{error}, undef; 116 | }; 117 | 118 | subtest 'Realy delete service' => sub { 119 | use SHM; 120 | use Core::System::ServiceManager qw( get_service ); 121 | SHM->new( user_id => 40092 ); 122 | 123 | my $service = get_service('service', _id => 1000 ); 124 | 125 | is( $service->get->{deleted}, 1 ); 126 | $service->_delete( where => { $service->get_table_key => 1000 } ); 127 | $service->commit(); 128 | }; 129 | 130 | done_testing(); 131 | 132 | exit 0; 133 | 134 | -------------------------------------------------------------------------------- /app/t/api/auth.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | use utf8; 3 | 4 | use Test::More; 5 | use Test::Deep; 6 | 7 | use Core::Utils qw/decode_json/; 8 | 9 | subtest 'Check auth with incorrect credentials' => sub { 10 | my $ret = qx( 11 | curl -s \\ 12 | -H "Content-Type: application/json" \\ 13 | -H "login: foo" \\ 14 | -H "password: bar" \\ 15 | -X PUT \\ 16 | http://api/shm/v1/admin/user/payment 17 | ); 18 | 19 | my $json_ret = decode_json( $ret ); 20 | 21 | cmp_deeply( $json_ret, { 22 | msg => 'Incorrect login or password', 23 | status => 401, 24 | }); 25 | }; 26 | 27 | subtest 'Check auth with correct credentials' => sub { 28 | my $ret = qx( 29 | curl -s \\ 30 | -H "Content-Type: application/json" \\ 31 | -H "login: admin" \\ 32 | -H "password: admin" \\ 33 | -X GET \\ 34 | http://api/shm/v1/user 35 | ); 36 | 37 | my $json_ret = decode_json( $ret ); 38 | 39 | is( $json_ret->{items}, 1, 'Check auth status'); 40 | }; 41 | 42 | my $session_id; 43 | 44 | subtest 'Check common auth (auth.cgi)' => sub { 45 | my $ret = qx( 46 | curl -s \\ 47 | -H "Content-Type: application/x-www-form-urlencoded" \\ 48 | -d "login=admin&password=admin&admin=1" \\ 49 | -X POST \\ 50 | http://api/shm/user/auth.cgi 51 | ); 52 | 53 | my $json_ret = decode_json( $ret ); 54 | is( $json_ret->{status}, 200, 'Check auth status'); 55 | 56 | $session_id = $json_ret->{session_id}; 57 | }; 58 | 59 | subtest 'Check auth with cookies' => sub { 60 | my $ret = qx( 61 | curl -s \\ 62 | -b "session_id=$session_id" \\ 63 | -X GET \\ 64 | http://api/shm/v1/user 65 | ); 66 | 67 | my $json_ret = decode_json( $ret ); 68 | is( exists $json_ret->{data}, 1 ); 69 | }; 70 | 71 | subtest 'Check auth with incorrect cookies' => sub { 72 | my $ret = qx( 73 | curl -s \\ 74 | -b "session_id=df2342fsdfs" \\ 75 | -X GET \\ 76 | http://api/shm/v1/user 77 | ); 78 | 79 | my $json_ret = decode_json( $ret ); 80 | is( $json_ret->{status}, 401 ); 81 | }; 82 | 83 | subtest 'Check access with incorrect cookies' => sub { 84 | my $ret = qx( 85 | curl -s \\ 86 | -b "session_id=34fsdffs2" \\ 87 | -X GET \\ 88 | http://api/shm/v1/user 89 | ); 90 | 91 | my $json_ret = decode_json( $ret ); 92 | is( $json_ret->{status}, 401 ); 93 | }; 94 | 95 | subtest 'Check access without cookies' => sub { 96 | my $ret = qx( 97 | curl -s http://api/shm/v1/user 98 | ); 99 | 100 | my $json_ret = decode_json( $ret ); 101 | is( $json_ret->{status}, 401 ); 102 | }; 103 | 104 | done_testing(); 105 | -------------------------------------------------------------------------------- /app/t/api/auth_basic.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | use utf8; 3 | 4 | use Test::More; 5 | use Core::Utils qw/decode_json/; 6 | 7 | subtest 'Check Basic auth for Admin' => sub { 8 | my $ret = qx( 9 | curl -s \\ 10 | -u admin:admin \\ 11 | http://api/shm/v1/user 12 | ); 13 | 14 | my $json_ret = decode_json( $ret ); 15 | 16 | is( exists $json_ret->{data}, 1); 17 | }; 18 | 19 | subtest 'Check Basic auth for User' => sub { 20 | my $ret = qx( 21 | curl -s \\ 22 | -u danuk:danuk \\ 23 | http://api/shm/v1/user 24 | ); 25 | 26 | my $json_ret = decode_json( $ret ); 27 | is( exists $json_ret->{data} , 1); 28 | is( $json_ret->{data}->[0]->{user_id}, 40092); 29 | }; 30 | 31 | done_testing(); 32 | -------------------------------------------------------------------------------- /app/t/api/filter.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | use utf8; 3 | 4 | use Test::More; 5 | use Test::Deep; 6 | use Data::Dumper; 7 | use Core::Utils qw/shm_test_api/; 8 | 9 | my %user = ( 10 | login => 'admin', 11 | password => 'admin', 12 | ); 13 | 14 | my %ret = shm_test_api( 15 | url => '/v1/admin/user/service?filter={"user_id":"40092"}', 16 | method => 'GET', 17 | %user, 18 | ); 19 | 20 | is $ret{json}->{items}, 4, 'Check items field'; 21 | is scalar @{ $ret{json}->{data} }, 4, 'Check count items in data'; 22 | 23 | done_testing(); 24 | 25 | exit 0; 26 | 27 | -------------------------------------------------------------------------------- /app/t/api/passwd.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | use utf8; 3 | 4 | use Core::Utils qw/shm_test_api/; 5 | use Test::More; 6 | 7 | subtest 'Try to change user password' => sub { 8 | my %ret = shm_test_api( 9 | url => 'v1/user/passwd', 10 | method => 'POST', 11 | data => { 12 | password => 'new_password', 13 | }, 14 | login => 'danuk', 15 | password => 'danuk', 16 | ); 17 | 18 | is $ret{success}, 1; 19 | }; 20 | 21 | subtest 'Try to auth with old password' => sub { 22 | my %ret = shm_test_api( 23 | url => 'v1/user/passwd', 24 | method => 'POST', 25 | data => { 26 | password => 'new_password', 27 | }, 28 | login => 'danuk', 29 | password => 'danuk', 30 | ); 31 | is $ret{success}, ''; 32 | }; 33 | 34 | subtest 'Set old password' => sub { 35 | my %ret = shm_test_api( 36 | url => 'v1/user/passwd', 37 | method => 'POST', 38 | data => { 39 | password => 'danuk', 40 | }, 41 | login => 'danuk', 42 | password => 'new_password', 43 | ); 44 | 45 | is $ret{success}, 1; 46 | }; 47 | 48 | done_testing(); 49 | 50 | exit 0; 51 | 52 | -------------------------------------------------------------------------------- /app/t/api/passwd_reset.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | use utf8; 3 | 4 | use Core::Utils qw/shm_test_api/; 5 | use Test::More; 6 | use Test::Deep; 7 | use Core::System::ServiceManager qw( get_service ); 8 | 9 | subtest 'Attempt to send a user password reset request' => sub { 10 | my %ret = shm_test_api( 11 | url => 'v1/user/passwd/reset', 12 | method => 'POST', 13 | data => { 14 | email => 'danuk', 15 | }, 16 | ); 17 | 18 | is $ret{success}, 1; 19 | }; 20 | 21 | subtest 'Delete password reset request from spool' => sub { 22 | use SHM; 23 | my $user = SHM->new( user_id => 40092 ); 24 | 25 | my $spool = get_service('spool'); 26 | my ( $row ) = $spool->list; 27 | 28 | cmp_deeply( $row, superhashof({ 29 | user_id => 40092, 30 | prio => 0, 31 | status => 'NEW', 32 | settings => undef, 33 | user_service_id => undef, 34 | response => undef, 35 | event => { 36 | id => 1, 37 | name => 'user_password_reset', 38 | kind => 'UserService', 39 | server_gid => 3, 40 | settings => { 41 | template_id => 'user_password_reset', 42 | category => '%', 43 | }, 44 | title => 'User password reset', 45 | }, 46 | delayed => 0, 47 | executed => undef, 48 | })); 49 | 50 | $spool->id( $row->{id} )->delete(); 51 | $user->passwd( password => 'danuk' ); 52 | $user->commit(); 53 | }; 54 | 55 | done_testing(); 56 | 57 | exit 0; 58 | 59 | -------------------------------------------------------------------------------- /app/t/api/payment.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | use utf8; 3 | 4 | use Test::More; 5 | use Test::Deep; 6 | 7 | use Core::Utils qw/ 8 | decode_json 9 | /; 10 | 11 | my $ret = qx( 12 | curl -s \\ 13 | -H "Content-Type: application/json" \\ 14 | -H "test: 1" \\ 15 | -H "login: admin" \\ 16 | -H "password: admin" \\ 17 | -X PUT \\ 18 | -d '{"user_id":40092,"pay_system_id":"test","money":123.45,"comment":"Test payment #4"}' \\ 19 | http://api/shm/v1/admin/user/payment 20 | ); 21 | 22 | my $json_ret = decode_json( $ret ); 23 | 24 | cmp_deeply( $json_ret->{data}->[0], { 25 | id => ignore(), 26 | user_id => 40092, 27 | date => ignore(), 28 | pay_system_id => 'test', 29 | money => 123.45, 30 | comment => { 31 | comment => 'Test payment #4', 32 | }, 33 | uniq_key => undef, 34 | }, 'Test API'); 35 | 36 | done_testing(); 37 | -------------------------------------------------------------------------------- /app/t/api/services.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | use utf8; 3 | 4 | use Test::More; 5 | use Test::Deep; 6 | use Data::Dumper; 7 | use Core::Utils qw/shm_test_api/; 8 | 9 | my %user = ( 10 | login => 'danuk', 11 | password => 'danuk', 12 | ); 13 | 14 | subtest 'GET /v1/service/' => sub { 15 | my %ret = shm_test_api( 16 | url => 'v1/service?service_id=5', 17 | method => 'GET', 18 | %user, 19 | ); 20 | is $ret{json}->{items}, 1, 'Check items field'; 21 | is scalar @{ $ret{json}->{data} }, 1, 'Check count items in data'; 22 | is $ret{json}->{data}->[0]->{service_id}, 5, 'Check data'; 23 | }; 24 | 25 | done_testing(); 26 | 27 | exit 0; 28 | 29 | -------------------------------------------------------------------------------- /app/t/api/user_reg.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | use utf8; 3 | 4 | use Core::Utils qw/shm_test_api/; 5 | 6 | use Test::More; 7 | 8 | use SHM; 9 | my $user = SHM->new( user_id => 40094 ); 10 | my $balance_before = $user->balance; 11 | 12 | my $login = sprintf( "login-%d", time ); 13 | 14 | my %ret = shm_test_api( 15 | url => 'v1/user', 16 | method => 'PUT', 17 | data => { 18 | login => $login, 19 | password => '123', 20 | }, 21 | ); 22 | 23 | is( $ret{json}->{data}->[0]->{login}, $login, 'Register new user'); 24 | 25 | my %test = shm_test_api( 26 | login => 'admin', 27 | password => 'admin', 28 | url => 'v1/admin/user?user_id='. $ret{json}->{data}->[0]->{user_id}, 29 | method => 'DELETE', 30 | ); 31 | 32 | is $test{success}, 1, 'Check user delete status'; 33 | 34 | done_testing(); 35 | 36 | exit 0; 37 | 38 | -------------------------------------------------------------------------------- /app/t/api/user_services.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | use utf8; 3 | 4 | use Test::More; 5 | use Test::Deep; 6 | use Data::Dumper; 7 | use Core::Utils qw/shm_test_api/; 8 | 9 | my %user = ( 10 | login => 'danuk', 11 | password => 'danuk', 12 | ); 13 | 14 | subtest 'GET /v1/user/service' => sub { 15 | my %ret = shm_test_api( 16 | url => 'v1/user/service', 17 | method => 'GET', 18 | %user, 19 | ); 20 | is $ret{json}->{items}, 4, 'Check items field'; 21 | is scalar @{ $ret{json}->{data} }, 4, 'Check count items in data'; 22 | }; 23 | 24 | subtest 'GET /v1/user/service' => sub { 25 | my %ret = shm_test_api( 26 | url => 'v1/user/service?usi=99', 27 | method => 'GET', 28 | %user, 29 | ); 30 | is $ret{json}->{items}, 1, 'Check items field'; 31 | is scalar @{ $ret{json}->{data} }, 1, 'Check count items in data'; 32 | is $ret{json}->{data}->[0]->{user_service_id}, 99, 'Check user_service_id'; 33 | is $ret{json}->{data}->[0]->{service_id}, 110, 'Check service_id'; 34 | }; 35 | 36 | done_testing(); 37 | 38 | exit 0; 39 | 40 | -------------------------------------------------------------------------------- /app/t/app/app.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | use Test::More; 5 | 6 | use Data::Dumper; 7 | use v5.14; 8 | 9 | $ENV{SHM_TEST} = 1; 10 | 11 | use SHM; 12 | use Core::System::ServiceManager qw( get_service ); 13 | 14 | SHM->new( user_id => 40092 ); 15 | 16 | my @list = get_service('app')->list; 17 | 18 | is( $list[0]->{user_id}, 40092, 'Check param in list of apps'); 19 | is( scalar( @list ), 2, 'Check count of apps list'); 20 | 21 | 22 | done_testing(); 23 | -------------------------------------------------------------------------------- /app/t/billing/full_test_simpler.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | 3 | use Test::More; 4 | use Test::Deep; 5 | use Test::MockTime; 6 | use Data::Dumper; 7 | use base qw( Core::System::Service ); 8 | use SHM qw( get_service ); 9 | use Core::Billing; 10 | use Core::Const; 11 | use POSIX qw(tzset); 12 | 13 | $ENV{SHM_TEST} = 1; 14 | 15 | my $user = SHM->new( user_id => 40092 ); 16 | 17 | $ENV{TZ} = 'Europe/London'; #UTC+0 18 | tzset; 19 | 20 | my $spool = get_service('spool'); 21 | my $us; 22 | my $user_services = get_service('UserService'); 23 | 24 | # Switch billing to Simpler 25 | my $config = get_service("config", _id => 'billing' ); 26 | $config->set( value => {'type' => 'Simpler' } ); 27 | 28 | subtest 'Prepare user for test billing' => sub { 29 | $user->set( balance => 2000, credit => 0, discount => 0 ); 30 | is( $user->get_balance, 2000, 'Check user balance'); 31 | }; 32 | 33 | # Now date 34 | Test::MockTime::set_fixed_time('2017-01-01T00:00:00Z'); 35 | 36 | subtest 'Check create service' => sub { 37 | 38 | $us = create_service( service_id => 4, cost => 1000, months => 1 ); 39 | 40 | is( $us->get_expire, '2017-01-30 23:59:59', 'Check expire date after create new service' ); 41 | }; 42 | 43 | done_testing(); 44 | -------------------------------------------------------------------------------- /app/t/billing/functions.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | 3 | use Test::More; 4 | use Data::Dumper; 5 | use Core::Billing; 6 | 7 | $ENV{SHM_TEST} = 1; 8 | 9 | use Core::System::ServiceManager qw( get_service ); 10 | use SHM; 11 | my $user = SHM->new( user_id => 40092 ); 12 | 13 | is( Core::Billing::get_service_discount( service_id => 1 ), 0, 'get service discount percent' ); 14 | is( Core::Billing::get_service_discount( months => 2, service_id => 1 ), 0, 'get service discount percent' ); 15 | is( Core::Billing::get_service_discount( months => 3, service_id => 1 ), 10, 'get service discount percent' ); 16 | 17 | is( Core::Billing::get_service_discount( service_id => 11 ), 0, 'get service discount percent for domain' ); 18 | is( Core::Billing::get_service_discount( months => 12, service_id => 11 ), 0, 'get service discount percent for domain' ); 19 | is( Core::Billing::get_service_discount( months => 24, service_id => 11 ), 0, 'get service discount percent for domain' ); 20 | is( Core::Billing::get_service_discount( months => 11, service_id => 11 ), 0, 'get service discount percent for domain' ); 21 | 22 | $user->set( discount => 13 ); 23 | is( Core::Billing::get_service_discount( service_id => 1 ), 13, 'get service discount percent' ); 24 | 25 | done_testing(); 26 | -------------------------------------------------------------------------------- /app/t/billing/next.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | 3 | use Test::More; 4 | use Test::MockTime; 5 | use Test::Deep; 6 | use Core::Billing; 7 | use POSIX qw(tzset); 8 | 9 | $ENV{SHM_TEST} = 1; 10 | 11 | use Core::System::ServiceManager qw( get_service ); 12 | use Core::Utils qw(now); 13 | use SHM; 14 | my $user = SHM->new( user_id => 40092 ); 15 | 16 | $ENV{TZ} = 'Europe/London'; #UTC+0 17 | tzset; 18 | 19 | my $next_service = get_service('service')->add( 20 | name => 'next service', 21 | cost => '100', 22 | period => 1, 23 | category => 'test', 24 | no_discount => 1, 25 | ); 26 | 27 | my $test_service = get_service('service')->add( 28 | name => 'test service', 29 | cost => '0', 30 | period => '0.01', 31 | category => 'test', 32 | no_discount => 1, 33 | next => $next_service->id, 34 | ); 35 | 36 | Test::MockTime::set_fixed_time('2019-04-01T00:00:00Z'); 37 | my $start_balance = $user->get->{balance}; 38 | my $us = create_service( service_id => $test_service->id ); 39 | 40 | is( $us->get_expire, '2019-04-02 00:59:59'); 41 | is( $us->get_next, $next_service->id ); 42 | 43 | subtest 'create test service with next' => sub { 44 | my $wd = $us->withdraw; 45 | cmp_deeply( scalar $wd->get, 46 | { 47 | 'user_id' => 40092, 48 | 'months' => 0.01, 49 | 'qnt' => 1, 50 | 'bonus' => '0', 51 | 'discount' => 0, 52 | 'cost' => '0', 53 | 'total' => 0, 54 | 'create_date' => '2019-04-01 01:00:00', 55 | 'withdraw_date' => '2019-04-01 01:00:00', 56 | 'end_date' => '2019-04-02 00:59:59', 57 | 'user_service_id' => $us->id, 58 | 'service_id' => $test_service->id, 59 | 'withdraw_id' => $wd->id, 60 | } 61 | , 'Check withdraw'); 62 | 63 | my $balance_after_create = $user->get->{balance}; 64 | is ( $balance_after_create, $start_balance, 'Check balance after create'); 65 | }; 66 | 67 | subtest 'check switch test service to next' => sub { 68 | Test::MockTime::set_fixed_time('2019-04-03T00:00:00Z'); 69 | 70 | my $start_balance = $user->get->{balance}; 71 | 72 | $us->touch(); 73 | is( $us->get_expire, '2019-05-02 01:49:57'); 74 | is( !$us->get_next, 1 ); 75 | 76 | my $wd = $us->withdraw; 77 | cmp_deeply( scalar $wd->get, 78 | { 79 | 'user_id' => 40092, 80 | 'months' => 1, 81 | 'qnt' => 1, 82 | 'bonus' => '0', 83 | 'discount' => 0, 84 | 'cost' => 100, 85 | 'total' => 100, 86 | 'create_date' => '2019-04-03 01:00:00', 87 | 'withdraw_date' => '2019-04-03 01:00:00', 88 | 'end_date' => '2019-05-02 01:49:57', 89 | 'user_service_id' => $us->id, 90 | 'service_id' => $next_service->id, 91 | 'withdraw_id' => $wd->id, 92 | } 93 | , 'Check withdraw for next service'); 94 | 95 | my $balance_after_create = $user->get->{balance}; 96 | is ( $balance_after_create, $start_balance - 100, 'Check balance after switch'); 97 | }; 98 | 99 | subtest 'Check switch test service to next (6 months)' => sub { 100 | Test::MockTime::set_fixed_time('2019-05-03T00:00:00Z'); 101 | 102 | my $next_service = get_service('service')->add( 103 | name => 'next service for 6 months', 104 | cost => '600', 105 | period => 6, 106 | category => 'test', 107 | no_discount => 1, 108 | ); 109 | 110 | $us->set( next => $next_service->id ); 111 | 112 | $us->touch(); 113 | is( $us->get_expire, '2019-11-02 00:59:56'); 114 | is( !$us->get_next, 1 ); 115 | 116 | my $wd = $us->withdraw; 117 | cmp_deeply( scalar $wd->get, 118 | { 119 | 'user_id' => 40092, 120 | 'months' => 6, 121 | 'qnt' => 1, 122 | 'bonus' => '0', 123 | 'discount' => 0, 124 | 'cost' => 600, 125 | 'total' => 600, 126 | 'create_date' => '2019-05-03 01:00:00', 127 | 'withdraw_date' => '2019-05-03 01:00:00', 128 | 'end_date' => '2019-11-02 00:59:56', 129 | 'user_service_id' => $us->id, 130 | 'service_id' => $next_service->id, 131 | 'withdraw_id' => $wd->id, 132 | } 133 | , 'Check withdraw for next service'); 134 | }; 135 | 136 | done_testing(); 137 | -------------------------------------------------------------------------------- /app/t/billing/sub_services_composite.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | 3 | use Test::More; 4 | use Test::MockTime; 5 | use Test::Deep; 6 | use Core::Billing; 7 | use POSIX qw(tzset); 8 | use Data::Dumper; 9 | 10 | $ENV{SHM_TEST} = 1; 11 | 12 | use Core::System::ServiceManager qw( get_service ); 13 | use Core::Utils qw(now); 14 | use SHM; 15 | use Core::Base; 16 | my $user = SHM->new( user_id => 40092 ); 17 | 18 | $ENV{TZ} = 'Europe/London'; #UTC+0 19 | tzset; 20 | 21 | 22 | my $cost_sub_service = 10; 23 | my $cost_service = 100; 24 | 25 | my $sub_service = get_service('service')->add( 26 | name => 'paid sub service', 27 | cost => $cost_sub_service, 28 | period => 1, 29 | category => 'test', 30 | no_discount => 1, 31 | pay_always => 1, 32 | ); 33 | 34 | my $service = get_service('service')->add( 35 | name => 'test service', 36 | cost => $cost_service, 37 | period => '1', 38 | category => 'test', 39 | no_discount => 1, 40 | is_composite => 1, 41 | allow_to_order => 1, 42 | children => [ 43 | { 44 | service_id => $sub_service->id, 45 | qnt => 2, 46 | }, 47 | ], 48 | ); 49 | 50 | Test::MockTime::set_fixed_time('2019-04-01T00:00:00Z'); 51 | 52 | $user->set( balance => 100, credit => 0 ); 53 | my $us = create_service( service_id => $service->id ); 54 | 55 | my $child = first_item $us->children; 56 | my $us_child = get_service('us', _id => $child->user_service_id ); 57 | 58 | is( $us->wd_total_composite, 120 ); 59 | 60 | is( $user->get_balance, 100 ); 61 | is( $us->status, 'NOT PAID' ); 62 | is( $us_child->status, 'NOT PAID' ); 63 | is( $us->get_expire, undef); 64 | is( $us_child->get_expire, undef); 65 | 66 | $user->set( balance => 120, credit => 0 ); 67 | $us->touch(); 68 | 69 | is( $user->get_balance, 0 ); 70 | is( $us->status, 'ACTIVE' ); 71 | is( $us_child->status, 'ACTIVE' ); 72 | is( $us->get_expire, '2019-05-01 01:01:59'); 73 | is( $us_child->get_expire, '2019-05-01 01:01:59'); 74 | 75 | 76 | Test::MockTime::set_fixed_time('2019-05-03T00:00:00Z'); 77 | $us->touch(); 78 | 79 | is( $user->get_balance, 0 ); 80 | is( $us->get_expire, '2019-05-01 01:01:59'); 81 | is( $us_child->get_expire, '2019-05-01 01:01:59'); 82 | is( $us->status, 'BLOCK' ); 83 | is( $us_child->status, 'BLOCK' ); 84 | 85 | $user->set( balance => 120, credit => 0 ); 86 | $us->touch(); 87 | 88 | is( $user->get_balance, 0 ); 89 | is( $us->status, 'ACTIVE' ); 90 | is( $us_child->status, 'ACTIVE' ); 91 | is( $us->get_expire, '2019-06-02 23:25:08'); 92 | is( $us_child->get_expire, '2019-06-02 23:25:08'); 93 | 94 | is( get_service('service')->price_list->{ $service->id }->{cost}, 120 ); 95 | 96 | is( get_service('service')->price_list->{ $service->id }->{discount}, 0 ); 97 | is( get_service('service')->price_list->{ $service->id }->{real_cost}, 120 ); 98 | 99 | $user->set( discount => 10 ); 100 | is( get_service('service')->price_list->{ $service->id }->{discount}, 0 ); 101 | is( get_service('service')->price_list->{ $service->id }->{real_cost}, 120 ); 102 | 103 | $service->set( no_discount => 0); 104 | is( get_service('service')->price_list->{ $service->id }->{discount}, 10 ); 105 | is( get_service('service')->price_list->{ $service->id }->{real_cost}, 108 ); 106 | 107 | $service->set( is_composite => 0 ); 108 | is( get_service('service')->price_list->{ $service->id }->{cost}, 100 ); 109 | 110 | done_testing(); 111 | -------------------------------------------------------------------------------- /app/t/config/config.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | use Test::More; 5 | use Test::Deep; 6 | 7 | use v5.14; 8 | use utf8; 9 | 10 | $ENV{SHM_TEST} = 1; 11 | 12 | use SHM; 13 | use Core::System::ServiceManager qw( get_service ); 14 | 15 | SHM->new( user_id => 40092 ); 16 | 17 | my $config = get_service("config"); 18 | 19 | my $data = $config->data_by_name; 20 | 21 | is( $data->{company}->{name}, 'My Company LTD', 'Check company name' ); 22 | is( $config->id('company')->get->{value}->{name}, 'My Company LTD' ); 23 | is( $config->id('company')->get_data->{name}, 'My Company LTD' ); 24 | is( $config->id('company')->get_data->{_name}, undef ); 25 | is( $config->data_by_name('company')->{name}, 'My Company LTD' ); 26 | 27 | my $key = $config->add( 28 | key => 'new_param', 29 | value => {"edc" => 1}, 30 | ); 31 | 32 | is( $key, 'new_param'); 33 | 34 | my $test = get_service("config", _id => 'mail'); 35 | is( $test->get_data->{from}, 'mail@domain.ru' ); 36 | 37 | $test->set( value => { "QAZ" => 1} ); 38 | is( $test->get_data->{"QAZ"}, 1 ); 39 | 40 | my $version = $config->id( '_shm')->get_data; 41 | cmp_deeply( $version, { 42 | version => ignore(), 43 | }); 44 | 45 | is( $config->data_by_name( 'billing')->{partner}->{income_percent}, 20 ); 46 | is( $config->data_by_name( '_billing')->{partner}->{income_percent}, undef ); 47 | 48 | done_testing(); 49 | -------------------------------------------------------------------------------- /app/t/domain/domain.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | use Test::More; 5 | use Test::Deep; 6 | 7 | use Data::Dumper; 8 | use v5.14; 9 | use utf8; 10 | 11 | $ENV{SHM_TEST} = 1; 12 | 13 | use SHM; 14 | use Core::System::ServiceManager qw( get_service ); 15 | 16 | SHM->new( user_id => 40092 ); 17 | 18 | my $domain = get_service('domain', _id => 308 ); 19 | is $domain->get->{domain}, 'umci.ru'; 20 | 21 | my @recs = $domain->get_domain( name => 'umci.ru' )->dns->records; 22 | is $recs[0]->{addr}, '37.46.134.76'; 23 | 24 | my @domains = get_service('domain')->list_services( user_service_id => 100 ); 25 | is scalar @domains, 2, 'Load domains for user_service'; 26 | 27 | my %d = get_service('domain')->get_domain( user_service_id => 16 )->get; 28 | is_deeply( \%d, 29 | { 30 | 'user_service_id' => 16, 31 | 'created' => '2017-01-01 00:00:00', 32 | 'subdomain_for' => undef, 33 | 'domain' => 'danuk.ru', 34 | 'zone_id' => 0, 35 | 'user_id' => 40092, 36 | 'punycode' => undef, 37 | 'domain_id' => 6 38 | } 39 | ,'Check get_domain by user_service_id'); 40 | 41 | my @domain_services = get_service('domain', _id => 6)->list_services; 42 | cmp_deeply( \@domain_services, 43 | bag( 44 | { 45 | 'id' => ignore(), 46 | 'user_service_id' => 16, 47 | 'domain_id' => 6, 48 | 'created' => '2017-09-23 00:00:01' 49 | }, 50 | { 51 | 'id' => ignore(), 52 | 'user_service_id' => 100, 53 | 'domain_id' => 6, 54 | 'created' => '2017-09-23 23:54:23' 55 | }, 56 | { 57 | 'id' => ignore(), 58 | 'domain_id' => 6, 59 | 'user_service_id' => 101, 60 | 'created' => '2017-09-23 23:54:04' 61 | }, 62 | { 63 | 'id' => ignore(), 64 | 'user_service_id' => 2950, 65 | 'domain_id' => 6, 66 | 'created' => '2017-11-05 17:40:30', 67 | }, 68 | { 69 | 'id' => ignore(), 70 | 'created' => '2017-11-05 17:40:33', 71 | 'domain_id' => 6, 72 | 'user_service_id' => 2951 73 | }, 74 | ), 'Check list of services'); 75 | 76 | is ( $domain->check_domain('test.ru'), 1, 'Check domain name' ); 77 | is ( $domain->check_domain('test.r'), 0, 'Check wrong domain name' ); 78 | is ( $domain->to_punycode('привет.рф'), 'xn--b1agh1afp.xn--p1ai', 'Convert domain to punycode' ); 79 | is ( $domain->to_punycode('test.ru'), undef, 'No convert domain to punycode' ); 80 | 81 | done_testing(); 82 | -------------------------------------------------------------------------------- /app/t/identities/keys.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | use warnings; 3 | use utf8; 4 | 5 | use Test::More; 6 | use Test::Deep; 7 | use Data::Dumper; 8 | use Core::Utils qw/read_file/; 9 | 10 | $ENV{SHM_TEST} = 1; 11 | 12 | use SHM; 13 | use Core::System::ServiceManager qw( get_service ); 14 | 15 | SHM->new( user_id => 40092 ); 16 | 17 | my $obj = get_service('Identities'); 18 | 19 | my %key = %{ $obj->generate_key_pair() }; 20 | 21 | my $fingerprint = $key{fingerprint}; 22 | 23 | my $id = $obj->add( 24 | name => 'test', 25 | private_key => $key{private_key}, 26 | ); 27 | 28 | my $data = $obj->id( $id )->get; 29 | 30 | $fingerprint =~s/\s+.*//; 31 | 32 | my $fingerprint_generated = $data->{fingerprint}; 33 | $fingerprint_generated =~s/\s+.*//; 34 | 35 | is( $fingerprint, $fingerprint_generated, 'Check fingerprint' ); 36 | 37 | cmp_deeply( $data, 38 | { 39 | id => $id, 40 | name => 'test', 41 | fingerprint => ignore(), 42 | private_key => $key{private_key}, 43 | public_key => undef, 44 | }, 45 | 'Check new key' 46 | ); 47 | 48 | done_testing(); 49 | -------------------------------------------------------------------------------- /app/t/logger/logger.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | use Test::More; 5 | use v5.14; 6 | 7 | $ENV{SHM_TEST} = 1; 8 | 9 | use SHM; 10 | use Core::System::ServiceManager qw( get_service ); 11 | 12 | my $user = SHM->new( user_id => 40092 ); 13 | 14 | my $logger = get_service('logger'); 15 | 16 | my $ret = $logger->make_message( 17 | tag => 0, 18 | time => 0, 19 | pid => 0, 20 | color => 0, 21 | stacktrace => 0, 22 | msg => [ 23 | 'test', 24 | 'foo', 25 | { 26 | a => 1, 27 | b => 2, 28 | c => [ 0, 'z', d => { e => 1 } ], 29 | }, 30 | ], 31 | ); 32 | 33 | is $ret, sprintf "message: {{ %s }}", 'test foo {"a":1,"b":2,"c":[0,"z","d",{"e":1}]}'; 34 | 35 | 36 | done_testing(); 37 | -------------------------------------------------------------------------------- /app/t/pay/forecast_test_period.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | 3 | use Test::More; 4 | use Test::Deep; 5 | use Core::System::ServiceManager qw( get_service ); 6 | use SHM (); 7 | use Core::Billing; 8 | 9 | $ENV{SHM_TEST} = 1; 10 | my $user = SHM->new( user_id => 40094 ); 11 | 12 | $user->set( balance => 0, bonus => 0, credit => 0, discount => 10 ); 13 | 14 | my $service = get_service('service')->add( 15 | name => 'test service', 16 | cost => 100, 17 | period => 1, 18 | category => 'test', 19 | ); 20 | 21 | my $test_service = get_service('service')->add( 22 | name => 'test period of service', 23 | cost => 0, 24 | period => 0.03, 25 | category => 'test', 26 | next => $service->id, 27 | ); 28 | 29 | my $us = create_service( service_id => $test_service->id ); 30 | 31 | my $ret = get_service('pay')->forecast(); 32 | 33 | cmp_deeply( $ret, { 34 | items => bag( 35 | { 36 | name => 'test period of service', 37 | months => 0.03, 38 | qnt => 1, 39 | cost => 0, 40 | discount => 10, 41 | total => 0, 42 | service_id => $test_service->id, 43 | usi => $us->id, 44 | user_service_id => $us->id, 45 | expire => ignore(), 46 | status => 'ACTIVE', 47 | next => { 48 | name => 'test service', 49 | service_id => $service->id, 50 | months => 1, 51 | cost => 100, 52 | discount => 10, 53 | total => 90, 54 | qnt => 1, 55 | }, 56 | }, 57 | ), 58 | balance => 0, 59 | bonuses => 0, 60 | dept => 0, 61 | total => 90, 62 | }); 63 | 64 | done_testing(); 65 | -------------------------------------------------------------------------------- /app/t/pay/pay.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | 3 | use Test::More; 4 | use Test::Deep; 5 | use Data::Dumper; 6 | use Core::System::ServiceManager qw( get_service ); 7 | use Core::Utils qw( now ); 8 | use SHM (); 9 | 10 | $ENV{SHM_TEST} = 1; 11 | 12 | my $user = SHM->new( user_id => 40092 ); 13 | 14 | subtest 'Check existed last payment' => sub { 15 | cmp_deeply ( scalar $user->pays->last->res, superhashof({ 16 | id => ignore(), 17 | date => ignore(), 18 | user_id => 40092, 19 | pay_system_id => 'manual', 20 | money => 455, 21 | })); 22 | }; 23 | 24 | subtest 'Make new payment and check last' => sub { 25 | my $payment = $user->payment( 26 | money => 14, 27 | pay_system_id => 'test', 28 | comment => { 29 | test => 1, 30 | }, 31 | uniq_key => '123xxx', 32 | ); 33 | 34 | cmp_deeply ( scalar $user->pays->last->res, superhashof({ 35 | id => ignore(), 36 | date => ignore(), 37 | user_id => 40092, 38 | pay_system_id => $payment->{pay_system_id}, 39 | money => $payment->{money}, 40 | comment => $payment->{comment}, 41 | uniq_key => $payment->{uniq_key}, 42 | })); 43 | }; 44 | 45 | 46 | done_testing(); 47 | -------------------------------------------------------------------------------- /app/t/report/report.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | 3 | use Test::More; 4 | use Data::Dumper; 5 | 6 | $ENV{SHM_TEST} = 1; 7 | 8 | use Core::System::ServiceManager qw( get_service ); 9 | use SHM; 10 | 11 | my $report = get_service('report'); 12 | 13 | $report->add_error('my big error'); 14 | is_deeply scalar $report->errors, ['my big error'], 'Check for one recors'; 15 | 16 | is_deeply scalar $report->errors, [], 'Check for empty records'; 17 | 18 | $report->add_error('first error'); 19 | $report->add_error('second error'); 20 | $report->add_error('last error'); 21 | $report->add_error( { foo => 'bar' } ); 22 | $report->add_error('one','two','free'); 23 | 24 | is $report->is_success, 0, 'Check report status: fail'; 25 | 26 | is_deeply scalar $report->errors, [ 27 | 'first error', 28 | 'second error', 29 | 'last error', 30 | { foo => 'bar' }, 31 | 'one two free', 32 | ], 'Check multiple errors'; 33 | 34 | is $report->is_success, 1, 'Check report status: success'; 35 | is_deeply scalar $report->errors, [], 'Check for empty records after get multiple errors'; 36 | 37 | done_testing(); 38 | -------------------------------------------------------------------------------- /app/t/servers/servers.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | use Test::More; 5 | 6 | use Data::Dumper; 7 | use v5.14; 8 | 9 | use Core::System::ServiceManager qw( get_service ); 10 | 11 | $ENV{SHM_TEST} = 1; 12 | 13 | use SHM; 14 | my $us = SHM->new( user_id => 40092 ); 15 | 16 | my $server = get_service('server', _id => 1 ); 17 | 18 | is ( $server->get_services_count, 25 ); 19 | 20 | $server->services_count_increase; 21 | is ( $server->get_services_count, 26 ); 22 | 23 | $server->services_count_decrease; 24 | is ( $server->get_services_count, 25 ); 25 | 26 | is ( $server->group->get_name, 'Сервера Web хостинга' ); 27 | 28 | done_testing(); 29 | -------------------------------------------------------------------------------- /app/t/services/commands.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | use Test::More; 5 | 6 | use Data::Dumper; 7 | use v5.14; 8 | 9 | $ENV{SHM_TEST} = 1; 10 | 11 | use SHM; 12 | SHM->new( user_id => 40092 ); 13 | 14 | use Core::System::ServiceManager qw( get_service ); 15 | 16 | my $obj = get_service('Events'); 17 | 18 | my @ret = $obj->get_events( kind => 'UserService', category => 'mysql', name => 'remove' ); 19 | 20 | is @ret, 1; 21 | 22 | done_testing(); 23 | -------------------------------------------------------------------------------- /app/t/services/create.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | use v5.14; 5 | use Test::More; 6 | use Data::Dumper; 7 | use Core::System::ServiceManager qw( get_service ); 8 | use Core::Const; 9 | 10 | $ENV{SHM_TEST} = 1; 11 | 12 | use SHM; 13 | my $user = SHM->new( user_id => 40092 ); 14 | 15 | my $service = get_service('service')->add( 16 | name => 'test service', 17 | category => 'test-category-1', 18 | cost => 0, 19 | ); 20 | 21 | subtest 'Check allow_to_order' => sub { 22 | my $us = $service->reg( 23 | service_id => $service->id, 24 | ); 25 | 26 | is( $us, undef ); 27 | 28 | $service->set(allow_to_order => 1); 29 | 30 | $us = $service->reg( 31 | service_id => $service->id, 32 | ); 33 | 34 | is( defined $us, 1 ); 35 | $us->delete; 36 | }; 37 | 38 | subtest 'Check check_exists' => sub { 39 | my $us1 = $service->reg( 40 | service_id => $service->id, 41 | ); 42 | is( defined $us1, 1 ); 43 | 44 | my $us2 = $service->reg( 45 | service_id => $service->id, 46 | check_exists => 1, 47 | ); 48 | is( defined $us2, 1 ); 49 | 50 | is( $us1->id, $us2->id ); 51 | $us1->delete; 52 | }; 53 | 54 | subtest 'Check check_exists_unpaid' => sub { 55 | my $us1 = $service->reg( 56 | service_id => $service->id, 57 | ); 58 | is( $us1->status, STATUS_ACTIVE ); 59 | 60 | my $us2 = $service->reg( 61 | service_id => $service->id, 62 | check_exists => 1, 63 | check_exists_unpaid => 1, 64 | ); 65 | is( defined $us2, 1 ); 66 | is( $us1->id != $us2->id, 1 ); 67 | 68 | $user->set( credit => 0 ); 69 | $service->set( cost => 1000 ); 70 | my $us3 = $service->reg( 71 | service_id => $service->id, 72 | ); 73 | is( $us3->status, STATUS_WAIT_FOR_PAY ); 74 | 75 | my $us4 = $service->reg( 76 | service_id => $service->id, 77 | check_exists => 1, 78 | check_exists_unpaid => 1, 79 | ); 80 | is( $us3->id == $us4->id, 1 ); 81 | 82 | $us1->delete; 83 | $us2->delete; 84 | $us3->delete; 85 | }; 86 | 87 | subtest 'Check check_category' => sub { 88 | $service->set( category => 'test-test-1' ); 89 | my $us1 = $service->reg( 90 | service_id => $service->id, 91 | ); 92 | 93 | my $us2 = $service->reg( 94 | service_id => $service->id, 95 | check_category => 'test-test-%', 96 | ); 97 | 98 | is( $us2->category eq 'test-test-1', 1 ); 99 | is( $us1->id == $us2->id, 1 ); 100 | 101 | my $us3 = $service->reg( 102 | service_id => $service->id, 103 | check_category => 'test-new-%', 104 | ); 105 | is( $us1->id != $us3->id, 1 ); 106 | 107 | $us1->delete; 108 | $us3->delete; 109 | }; 110 | 111 | done_testing(); 112 | 113 | -------------------------------------------------------------------------------- /app/t/services/discounts.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | use Test::More; 5 | 6 | use Data::Dumper; 7 | use v5.14; 8 | 9 | use Core::System::ServiceManager qw( get_service ); 10 | 11 | $ENV{SHM_TEST} = 1; 12 | 13 | use SHM; 14 | my $us = SHM->new( user_id => 40092 ); 15 | 16 | my $d = get_service('discounts'); 17 | 18 | is( $d->get_by_period( months => 1 )->{months}, 1 ); 19 | is( $d->get_by_period( months => 2 )->{months}, 1 ); 20 | is( $d->get_by_period( months => 3 )->{months}, 3 ); 21 | is( $d->get_by_period( months => 4 )->{months}, 3 ); 22 | is( $d->get_by_period( months => 5 )->{months}, 3 ); 23 | is( $d->get_by_period( months => 6 )->{months}, 6 ); 24 | is( $d->get_by_period( months => 7 )->{months}, 6 ); 25 | is( $d->get_by_period( months => 8 )->{months}, 6 ); 26 | is( $d->get_by_period( months => 9 )->{months}, 6 ); 27 | is( $d->get_by_period( months => 10 )->{months}, 6 ); 28 | is( $d->get_by_period( months => 11 )->{months}, 6 ); 29 | is( $d->get_by_period( months => 12 )->{months}, 12 ); 30 | is( $d->get_by_period( months => 13 )->{months}, 12 ); 31 | 32 | 33 | done_testing(); 34 | -------------------------------------------------------------------------------- /app/t/services/events.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | use warnings; 3 | use utf8; 4 | 5 | use Test::More; 6 | use Data::Dumper; 7 | 8 | $ENV{SHM_TEST} = 1; 9 | 10 | use SHM; 11 | use Core::Const; 12 | use Core::System::ServiceManager qw( get_service ); 13 | 14 | SHM->new( user_id => 40092 ); 15 | 16 | my $event = get_service('events'); 17 | my @events; 18 | 19 | subtest 'Check exists events' => sub { 20 | @events = $event->get_events( 21 | kind => 'UserService', 22 | name => 'create', 23 | category => 'test', 24 | 25 | ); 26 | 27 | is( scalar @events, 0 ); 28 | }; 29 | 30 | subtest 'Add two new events' => sub { 31 | $event->add( 32 | title => 'event 1', 33 | name => 'create', 34 | server_gid => 1, 35 | settings => { 36 | category => 'test', 37 | }, 38 | ); 39 | 40 | $event->add( 41 | title => 'event 2', 42 | name => 'create', 43 | server_gid => 1, 44 | settings => { 45 | category => 'test', 46 | }, 47 | ); 48 | 49 | $event->add( 50 | title => 'event 3 (always match)', 51 | name => 'create', 52 | server_gid => 1, 53 | settings => { 54 | category => '%', 55 | }, 56 | ); 57 | 58 | $event->add( 59 | title => 'event 4 (always match but another event)', 60 | name => 'delete', 61 | server_gid => 1, 62 | settings => { 63 | category => '%', 64 | }, 65 | ); 66 | 67 | @events = $event->get_events( 68 | kind => 'UserService', 69 | name => 'create', 70 | category => 'test', 71 | ); 72 | 73 | is( scalar @events, 3 ); 74 | }; 75 | 76 | subtest 'Check events for new service' => sub { 77 | my $service_id = get_service('service')->add( 78 | name => 'test service', 79 | cost => 0, 80 | category => 'test', 81 | )->id; 82 | 83 | my $us = get_service('us')->add( 84 | service_id => $service_id, 85 | ); 86 | 87 | is( $us->status, STATUS_INIT ); 88 | is( $us->get_service_id, $service_id ); 89 | is( scalar @{ $us->commands_by_event('create') }, 3 ); 90 | is ( $us->has_spool_command, 0 ); 91 | is ( $us->event( 'create' ), SUCCESS ); 92 | is ( $us->has_spool_command, 3 ); 93 | is( $us->status, STATUS_PROGRESS ); 94 | }; 95 | 96 | subtest 'Check add event when status is PROGRESS' => sub { 97 | my $service_id = get_service('service')->add( 98 | name => 'test service', 99 | cost => 0, 100 | category => 'custom', 101 | )->id; 102 | 103 | my $us = get_service('us')->add( 104 | service_id => $service_id, 105 | ); 106 | 107 | $us->set( status => STATUS_PROGRESS ); 108 | is( $us->status, STATUS_PROGRESS ); 109 | 110 | is ( $us->has_spool_command, 0 ); 111 | is ( $us->event( 'create' ), SUCCESS ); 112 | is ( $us->has_spool_command, 1 ); 113 | is( $us->status, STATUS_PROGRESS ); 114 | }; 115 | 116 | done_testing(); 117 | 118 | -------------------------------------------------------------------------------- /app/t/services/filters.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | use v5.14; 5 | use Test::More; 6 | use Data::Dumper; 7 | use Core::System::ServiceManager qw( get_service ); 8 | 9 | $ENV{SHM_TEST} = 1; 10 | 11 | use SHM; 12 | my $user = SHM->new( user_id => 40092 ); 13 | 14 | subtest 'test category LIKE' => sub { 15 | my $items = $user->us->filter( category => 'web_%' )->items; 16 | is( scalar @$items, 1 ); 17 | }; 18 | 19 | subtest 'test two params' => sub { 20 | my $items = $user->us->filter( category => 'web_%', user_service_id => 99)->items; 21 | is( scalar @$items, 1 ); 22 | }; 23 | 24 | subtest 'test settings exists' => sub { 25 | my $items = $user->us->filter( settings => 'ns' )->items; 26 | is( scalar @$items, 6 ); 27 | }; 28 | 29 | subtest 'test settings EQ param' => sub { 30 | my $items = $user->us->filter( settings => { ns => 'ns1.viphost.ru' } )->items; 31 | is( scalar @$items, 2 ); 32 | }; 33 | 34 | subtest 'test settings NON param' => sub { 35 | my $items = $user->us->filter( settings => { ns => { '!=' => 'ns1.viphost.ru' } } )->items; 36 | is( scalar @$items, 4 ); 37 | }; 38 | 39 | subtest 'test sort and rsort' => sub { 40 | my $items = $user->us->sort('user_id','created')->rsort('category')->items; 41 | is( scalar @$items, 19 ); 42 | }; 43 | 44 | subtest 'test sort ' => sub { 45 | my $items = $user->sort('user_id')->items; 46 | is( $items->[0]->id, 1 ); 47 | }; 48 | 49 | subtest 'test rsort ' => sub { 50 | my $items = $user->rsort('user_id')->items; 51 | is( $items->[0]->id, 40094 ); 52 | }; 53 | 54 | done_testing(); 55 | 56 | -------------------------------------------------------------------------------- /app/t/services/service.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | use Test::More; 5 | 6 | use Data::Dumper; 7 | use v5.14; 8 | 9 | $ENV{SHM_TEST} = 1; 10 | 11 | use SHM; 12 | SHM->new( user_id => 40092 ); 13 | 14 | use Core::System::ServiceManager qw( get_service ); 15 | 16 | my $service = get_service('service', _id => 1); 17 | $service->set( cost => 5 ); 18 | 19 | my $si = get_service('service', _id => 1)->get; 20 | is ( $si->{service_id}, 1 ); 21 | is ( $si->{cost}, 5 ); 22 | is ( $service->get_cost, 5 ); 23 | 24 | $si = get_service('service', _id => 2)->get; 25 | 26 | is ( $si->{service_id}, 2 ); 27 | is ( $si->{cost}, 100 ); 28 | 29 | $si = $service->add( name => 'TEST', cost => 123, category => 'new' )->get; 30 | is ( $si->{name}, 'TEST', 'Check create new service' ); 31 | 32 | is_deeply( scalar $service->categories, [ 33 | 'web_tariff_lock', 34 | 'web_tariff', 35 | 'web', 36 | 'mail', 37 | 'domain', 38 | 'domain_prolong', 39 | 'mysql', 40 | 'dns', 41 | 'domain_add', 42 | 'transfer', 43 | 'new', 44 | ], 'Check categories() function'); 45 | 46 | done_testing(); 47 | -------------------------------------------------------------------------------- /app/t/services/tariff_change.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | use v5.14; 5 | use Test::More; 6 | use Data::Dumper; 7 | use Core::System::ServiceManager qw( get_service ); 8 | use Core::Const; 9 | 10 | $ENV{SHM_TEST} = 1; 11 | 12 | use SHM; 13 | my $user = SHM->new( user_id => 40092 ); 14 | 15 | my $service = get_service('service')->add( 16 | name => 'test service 1', 17 | category => 'test-category-1', 18 | cost => 200, 19 | allow_to_order => 1, 20 | ); 21 | 22 | my $service_next = get_service('service')->add( 23 | name => 'test service 2', 24 | category => 'test-category-1', 25 | cost => 100, 26 | allow_to_order => 1, 27 | ); 28 | 29 | subtest 'Check WAIT_FOR_PAY -> WAIT_FOR_PAY' => sub { 30 | $user->set( balance => 10, credit => 0 ); 31 | 32 | my $us = $service->reg( 33 | service_id => $service->id, 34 | ); 35 | 36 | is ( $us->service_id, $service->id ); 37 | is ( $us->status, STATUS_WAIT_FOR_PAY); 38 | is( $us->withdraw->total, $service->cost ); 39 | 40 | $us->change( service_id => $service_next->id ); 41 | 42 | is ( $us->service_id, $service_next->id ); 43 | is ( $us->status, STATUS_WAIT_FOR_PAY); 44 | is( $us->withdraw->total, $service_next->cost ); 45 | }; 46 | 47 | subtest 'Check WAIT_FOR_PAY -> ACTIVE' => sub { 48 | $user->set( balance => 100, credit => 0 ); 49 | 50 | my $us = $service->reg( 51 | service_id => $service->id, 52 | ); 53 | 54 | is ( $us->service_id, $service->id ); 55 | is ( $us->status, STATUS_WAIT_FOR_PAY); 56 | is( $us->withdraw->total, $service->cost ); 57 | 58 | $us->change( service_id => $service_next->id ); 59 | 60 | is ( $us->service_id, $service_next->id ); 61 | is ( $us->status, STATUS_ACTIVE); 62 | is( $us->withdraw->total, $service_next->cost ); 63 | }; 64 | 65 | subtest 'Check ACTIVE -> BLOCK -> ACTIVE' => sub { 66 | $user->set( balance => 200, credit => 0 ); 67 | 68 | my $us = $service->reg( 69 | service_id => $service->id, 70 | ); 71 | 72 | is ( $us->service_id, $service->id ); 73 | is ( $us->status, STATUS_ACTIVE); 74 | is( $us->withdraw->total, $service->cost ); 75 | is ( $user->balance, 0 ); 76 | 77 | $user->set( balance => -200, credit => 0 ); 78 | $us->change( service_id => $service_next->id ); 79 | 80 | is ( $us->service_id, $service_next->id ); 81 | is ( $us->status, STATUS_BLOCK); 82 | is( $us->withdraw->total, $service_next->cost ); 83 | is ( $user->balance, 0 ); 84 | 85 | $user->set( balance => 200, credit => 0 ); 86 | $us->change( service_id => $service->id ); 87 | 88 | is ( $us->service_id, $service->id ); 89 | is ( $us->status, STATUS_ACTIVE); 90 | is( $us->withdraw->total, $service->cost ); 91 | is ( $user->balance, 0 ); 92 | }; 93 | 94 | subtest 'Check ACTIVE -> ACTIVE' => sub { 95 | $user->set( balance => 200, credit => 0 ); 96 | 97 | my $us = $service->reg( 98 | service_id => $service->id, 99 | ); 100 | 101 | is ( $us->service_id, $service->id ); 102 | is ( $us->status, STATUS_ACTIVE); 103 | is( $us->withdraw->total, $service->cost ); 104 | is ( $user->balance, 0 ); 105 | 106 | $us->change( service_id => $service_next->id ); 107 | 108 | is ( $us->service_id, $service_next->id ); 109 | is ( $us->status, STATUS_ACTIVE); 110 | is( $us->withdraw->total, $service_next->cost ); 111 | is ( $user->balance, 100 ); 112 | }; 113 | 114 | done_testing(); 115 | 116 | -------------------------------------------------------------------------------- /app/t/services/user_service_object.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | use Test::More; 5 | 6 | use Data::Dumper; 7 | use v5.14; 8 | 9 | $ENV{SHM_TEST} = 1; 10 | 11 | use SHM; 12 | my $user = SHM->new( user_id => 40092 ); 13 | 14 | use Core::System::ServiceManager qw( get_service ); 15 | 16 | # Make new object of user_service 17 | my $obj = get_service('us', _id => 99); 18 | 19 | is_deeply( scalar $obj->with_name->get, { 20 | parent => undef, 21 | status_before => 'INIT', 22 | status => 'ACTIVE', 23 | name => 'Тариф X-MAX (10000 мб)', 24 | next => undef, 25 | auto_bill => 1, 26 | service_id => 110, 27 | user_service_id => 99, 28 | settings => { 29 | quota => 10000 30 | }, 31 | created => '2014-10-07 12:56:09', 32 | expire => '2017-01-31 23:59:50', 33 | user_id => 40092, 34 | withdraw_id => 3691, 35 | }, 'get user_service from id (check full structure)'); 36 | 37 | is ( $obj->id, 99, 'Get user_service_id' ); 38 | 39 | is ( $obj->top_parent, $obj, 'Test get top_parent for root service'); 40 | is ( get_service('us', _id => 665 )->top_parent->id, 99, 'Test get top_parent for child' ); 41 | 42 | is ( $obj->get_expire, '2017-01-31 23:59:50', 'Check getter for expire field' ); 43 | 44 | is ( get_service('us', _id => 101 )->parent->get_user_service_id, 99, 'Check load parent service' ); 45 | 46 | is ( $obj->set( auto_bill => 0 ), $obj->get_auto_bill == 0, 'Check service set function with cache: TEST 1'); 47 | is ( $obj->set( auto_bill => 1 ), $obj->get_auto_bill == 1, 'Check service set function with cache: TEST 2'); 48 | 49 | $obj->settings( { 'a' => 22 } ); # Override 'a' 50 | $obj->settings( { 'b' => 33 } ); # Override 'b' 51 | $obj->settings( { danuk => 'New value' } ); # Test add new value 52 | $obj->settings( {} ); # Test on empty add 53 | 54 | is_deeply( $obj->get_settings, { 55 | 'quota' => '10000', 56 | 'a' => 22, 57 | 'danuk' => 'New value', 58 | 'b' => 33 59 | }, 'Check save settings (JSON)' ); 60 | 61 | $obj->settings->{foo}->{bar} = 1; 62 | $obj->settings->{foo}->{biz} = 2; 63 | 64 | $obj->settings_save; 65 | $obj->reload; 66 | 67 | is_deeply( $obj->settings, { 68 | 'quota' => '10000', 69 | 'danuk' => 'New value', 70 | 'a' => 22, 71 | 'b' => 33, 72 | 'foo' => { 73 | 'bar' => 1, 74 | 'biz' => 2 75 | } 76 | }, 'Check union settings'); 77 | 78 | is( $user->us->has_services_active, 1 ); 79 | is( $user->us->has_services_block, 0 ); 80 | is( $user->us->has_services_unpaid, 0 ); 81 | 82 | done_testing(); 83 | 84 | -------------------------------------------------------------------------------- /app/t/services/utils.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | 3 | use Test::More; 4 | use Data::Dumper; 5 | use Core::Utils; 6 | 7 | is( Core::Utils::days_in_months('2016-02'), 29, 'Test days_in_months 1' ); 8 | is( Core::Utils::days_in_months('2017-02'), 28, 'Test days_in_months 2' ); 9 | is( Core::Utils::days_in_months('2017-02-05 10:12:43'), 28, 'Test days_in_months 3' ); 10 | 11 | is_deeply( scalar Core::Utils::parse_date('2017-02-05 10:12:43'), { 12 | year => 2017, 13 | month => 2, 14 | day => 5, 15 | hour => 10, 16 | min => 12, 17 | sec => 43, 18 | }, 'Test parse_date' ); 19 | 20 | is( Core::Utils::start_of_month('2017-01-15 15:14:13'),'2017-01-01 00:00:00','Test start start_of_month'); 21 | is( Core::Utils::start_of_month('2017-01-15'),'2017-01-01 00:00:00','Test start_of_month'); 22 | is( Core::Utils::end_of_month('2017-01-15'),'2017-01-31 23:59:59','Test end_of_month'); 23 | is( Core::Utils::end_of_month('2017-02-13'),'2017-02-28 23:59:59','Test end_of_month'); 24 | 25 | done_testing(); 26 | -------------------------------------------------------------------------------- /app/t/services/withdraw.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | use Test::More; 5 | 6 | use Data::Dumper; 7 | use v5.14; 8 | 9 | $ENV{SHM_TEST} = 1; 10 | 11 | use SHM; 12 | SHM->new( user_id => 40092 ); 13 | 14 | use Core::System::ServiceManager qw( get_service ); 15 | 16 | my $obj = get_service('us', _id => 99); 17 | 18 | my @arr = $obj->withdraw->list; 19 | is ( scalar( @arr ), 1, 'Check count of withdraws' ); 20 | 21 | my $wd = $obj->withdraw->get; 22 | is ( $wd->{cost}, 123.45, 'Get cost of current withdraw (hash mode)' ); 23 | 24 | is ( $obj->withdraw->get->{cost}, 123.45, 'Get cost of current withdraw (ref mode)' ); 25 | 26 | my $new_wd_id = $obj->withdraw->add( %{ $wd } ); 27 | my $new_wd = get_service('wd', _id => $new_wd_id ); 28 | is ( int( $new_wd->{withdraw_id} > $wd->{withdraw_id} ), 1, 'Check add new withdraw' ); 29 | 30 | my %next_wd = $obj->withdraw->next; 31 | is ( exists $new_wd->{withdraw_id}, 1, 'Check get one next withdraw' ); 32 | 33 | my $nexts = $obj->withdraw->next; 34 | is ( ref $nexts eq 'ARRAY', 1, 'Check get all nexts withdraw' ); 35 | 36 | @arr = $obj->withdraw->list; 37 | is ( scalar( @arr ), 2, 'Check count of withdraws' ); 38 | 39 | $obj = get_service('us', _id => 2949); 40 | @arr = $obj->withdraw->list; 41 | is ( scalar( @arr ), 1, 'Check count of withdraws' ); 42 | is ( $arr[0]->{cost}, 590, 'Get cost of list withdraws array' ); 43 | is ( $obj->withdraw->get->{cost}, 590, 'Check cost of 2949 service' ); 44 | 45 | done_testing(); 46 | -------------------------------------------------------------------------------- /app/t/sessions/functions.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | 3 | use Test::More; 4 | use Test::Deep; 5 | use Data::Dumper; 6 | 7 | $ENV{SHM_TEST} = 1; 8 | 9 | use Core::System::ServiceManager qw( get_service ); 10 | use SHM; 11 | my $user = SHM->new( user_id => 40092 ); 12 | 13 | subtest 'Check user gen_session()' => sub { 14 | my $user_session = $user->gen_session(); 15 | my $user_session_id = $user_session->{id}; 16 | 17 | is( defined $user_session_id, 1 , 'Check gen_session()'); 18 | is( length $user_session_id , 32 , 'Check gen_session() length'); 19 | 20 | my $session = get_service('sessions', _id => $user_session_id ); 21 | is ( $session->id eq $user_session_id, 1, 'Check Session module' ); 22 | }; 23 | 24 | subtest 'Check session add' => sub { 25 | my $new_session = get_service('sessions')->add(); 26 | my $session = get_service('sessions', _id => $new_session ); 27 | is ( $session->get_user_id, 40092, 'Check session user_id' ); 28 | }; 29 | 30 | subtest 'Check session add with custom parameters' => sub { 31 | my $new_session = get_service('sessions')->add( 32 | user_id => 1, 33 | settings => {}, 34 | ); 35 | 36 | my $session = get_service('sessions', _id => $new_session ); 37 | is ( $session->get_user_id, 1, 'Check session user_id' ); 38 | }; 39 | 40 | done_testing(); 41 | -------------------------------------------------------------------------------- /app/t/spool/make_task.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | use warnings; 3 | use utf8; 4 | 5 | use Test::More; 6 | use Data::Dumper; 7 | 8 | $ENV{SHM_TEST} = 1; 9 | 10 | use SHM; 11 | use Core::Const; 12 | use Core::System::ServiceManager qw( get_service ); 13 | 14 | SHM->new( user_id => 40092 ); 15 | 16 | my $spool = get_service('spool'); 17 | 18 | no warnings 'once'; 19 | *Core::Spool::make_task = sub { 20 | my $self = shift; 21 | my %args = @_; 22 | return 'MOCK', \%args; 23 | }; 24 | 25 | subtest 'Get server_id by event settings' => sub { 26 | my $task_id = $spool->add( 27 | event => { 28 | kind => 'user_service', 29 | name => 'update', 30 | server_gid => 1, 31 | }, 32 | settings => { 33 | user_service_id => 16, 34 | server_id => 2, 35 | }, 36 | ); 37 | 38 | my %task = $spool->id( $task_id )->get; 39 | is ( $task{id}, $task_id ); 40 | 41 | my ( $status, $info ) = $spool->process_one( \%task ); 42 | 43 | is( $info->{status}, 'MOCK' ); 44 | is( $info->{settings}->{server_id} =~ /^1|2$/, 1 ); 45 | }; 46 | 47 | subtest 'Get server_id by server_gid' => sub { 48 | my $task_id = $spool->add( 49 | event => { 50 | kind => 'user_service', 51 | name => 'update', 52 | server_gid => 5, 53 | }, 54 | settings => { 55 | user_service_id => 16, 56 | }, 57 | ); 58 | 59 | my %task = $spool->id( $task_id )->get; 60 | is ( $task{id}, $task_id ); 61 | 62 | my ( $status, $info ) = $spool->process_one( \%task ); 63 | 64 | is( $info->{status}, 'MOCK' ); 65 | is( $info->{settings}->{server_id}, 25 ); 66 | }; 67 | 68 | done_testing(); 69 | -------------------------------------------------------------------------------- /app/t/spool/spool.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | use warnings; 3 | use utf8; 4 | 5 | use Test::More; 6 | use Data::Dumper; 7 | 8 | $ENV{SHM_TEST} = 1; 9 | 10 | use SHM; 11 | use Core::Const; 12 | use Core::System::ServiceManager qw( get_service ); 13 | 14 | SHM->new( user_id => 40092 ); 15 | 16 | my $spool = get_service('spool'); 17 | 18 | my $t = get_service('Transport::Ssh'); 19 | no warnings 'once'; 20 | no warnings qw(redefine); 21 | *Core::Transport::Ssh::exec = sub { 22 | my $self = shift; 23 | my %args = @_; 24 | 25 | return SUCCESS, { 26 | server => { 27 | id => $args{server_id}, 28 | host => $args{host}, 29 | port => $args{port}, 30 | key_id => $args{key_id}, 31 | }, 32 | cmd => $args{cmd}, 33 | ret_code => 0, 34 | pipeline_id => $args{pipeline_id}, 35 | }; 36 | }; 37 | 38 | my %task1 = ( 39 | event => { 40 | kind => 'user_service', 41 | name => 'update', 42 | settings => { 43 | category => 'dns', 44 | cmd => 'dns update', 45 | }, 46 | }, 47 | settings => { 48 | user_service_id => 16, 49 | server_id => 2, 50 | }, 51 | ); 52 | my $task1_id = $spool->add( %task1 ); 53 | 54 | my %task2 = ( 55 | event => { 56 | kind => 'user_service', 57 | name => 'update', 58 | settings => { 59 | category => 'dns', 60 | cmd => 'dns update', 61 | }, 62 | }, 63 | settings => { 64 | user_service_id => 16, 65 | server_id => 162, 66 | }, 67 | ); 68 | my $task2_id = $spool->add( %task2 ); 69 | 70 | my $task3 = get_service('task')->res({ 71 | event => { 72 | kind => 'user_service', 73 | name => 'update', 74 | settings => { 75 | category => 'dns', 76 | cmd => 'dns update', 77 | }, 78 | }, 79 | settings => { 80 | user_service_id => 16, 81 | server_id => 1, 82 | }, 83 | })->make_task; 84 | 85 | is( $task3->{response}->{ret_code}, 0, 'Check make_task for category `test`' ); 86 | 87 | $spool->process_all( ); 88 | 89 | is( ($spool->list)[0]->{settings}->{server_id}, 162); 90 | is( ($spool->list)[0]->{response}->{error}, 'Server not exists: 162'); 91 | is( ($spool->list)[0]->{status}, TASK_STUCK ); 92 | 93 | my @ret = get_service('SpoolHistory')->list( 94 | where => { spool_id => { -in => [ $task1_id, $task2_id ] } }, 95 | order => [ spool_id => 'ASC' ], 96 | ); 97 | 98 | is( $ret[0]->{status}, TASK_SUCCESS, 'Send test message for test services' ); 99 | 100 | my @list = $spool->list( where => { status => { '!=', TASK_STUCK } } ); 101 | is ( @list, 0, 'Check for empty spool' ); 102 | 103 | done_testing(); 104 | -------------------------------------------------------------------------------- /app/t/sql/query_filter.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | use utf8; 3 | 4 | use SHM; 5 | use Test::More; 6 | use Test::Deep; 7 | use Core::Sql::Data qw/query_for_filtering/; 8 | use Core::System::ServiceManager qw( get_service ); 9 | use Data::Dumper; 10 | 11 | my $user = SHM->new( user_id => 40092 ); 12 | 13 | cmp_deeply ( 14 | $user->query_for_filtering( 15 | user_id => 40092, 16 | full_name => "%hello%", 17 | alien => 'strange', 18 | ), 19 | { 20 | 'user_id' => 40092, 21 | 'full_name' => { 22 | '-like' => '%hello%' 23 | } 24 | }, 25 | ); 26 | 27 | cmp_deeply ( 28 | $user->query_for_filtering( 29 | user_id => 40092, 30 | full_name => { -not_like => "%hello%" }, 31 | ), 32 | { 33 | 'user_id' => 40092, 34 | 'full_name' => { 35 | '-not_like' => '%hello%' 36 | } 37 | }, 38 | ); 39 | 40 | cmp_deeply ( 41 | $user->query_for_filtering( 42 | alien => 'strange', 43 | ), 44 | {}, 45 | ); 46 | 47 | done_testing(); 48 | -------------------------------------------------------------------------------- /app/t/sql/query_select.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | use utf8; 3 | 4 | use Test::More; 5 | use Core::Sql::Data qw/query_select/; 6 | 7 | my @vars; 8 | 9 | is query_select( 10 | undef, 11 | vars => \@vars, 12 | table => 'domains', 13 | ), "SELECT * FROM domains"; 14 | 15 | is query_select( 16 | undef, 17 | vars => \@vars, 18 | table => 'domains', 19 | calc => 1, 20 | ), "SELECT SQL_CALC_FOUND_ROWS * FROM domains"; 21 | 22 | is query_select( 23 | undef, 24 | vars => \@vars, 25 | table => 'domains', 26 | fields => 'id,date', 27 | ), "SELECT id,date FROM domains"; 28 | 29 | is query_select( 30 | undef, 31 | vars => \@vars, 32 | table => 'domains', 33 | ), "SELECT * FROM domains"; 34 | 35 | is query_select( 36 | undef, 37 | vars => \@vars, 38 | table => 'domains', 39 | range => { field => 'date', start => '2016-11-12', stop => '2016-12-31' }, 40 | ), "SELECT * FROM domains WHERE ( ( date BETWEEN ? AND ? ) )"; 41 | 42 | is query_select( 43 | undef, 44 | vars => \@vars, 45 | table => 'domains', 46 | range => { field => 'date', start => '2016-11-12' }, 47 | ), "SELECT * FROM domains WHERE ( date >= ? )"; 48 | 49 | is query_select( 50 | undef, 51 | vars => \@vars, 52 | table => 'domains', 53 | range => { field => 'date', stop => '2016-12-31' }, 54 | ), "SELECT * FROM domains WHERE ( date <= ? )"; 55 | 56 | is query_select( 57 | undef, 58 | vars => \@vars, 59 | table => 'domains', 60 | limit => 5, 61 | ), "SELECT * FROM domains LIMIT ?"; 62 | 63 | is query_select( 64 | undef, 65 | vars => \@vars, 66 | table => 'domains', 67 | limit => 5, 68 | offset => 7, 69 | ), "SELECT * FROM domains LIMIT ? OFFSET ?"; 70 | 71 | is query_select( 72 | undef, 73 | vars => \@vars, 74 | table => 'domains', 75 | limit => '', 76 | offset => 7, 77 | ), "SELECT * FROM domains"; 78 | 79 | is query_select( 80 | undef, 81 | vars => \@vars, 82 | table => 'user_services', 83 | join => { table => 'services', using => ['service_id'] }, 84 | ), "SELECT * FROM user_services JOIN services USING(service_id)"; 85 | 86 | is query_select( 87 | undef, 88 | vars => \@vars, 89 | table => 'user_services', 90 | join => { table => 'services', using => ['service_id'], dir => 'RIGHT' }, 91 | ), "SELECT * FROM user_services RIGHT JOIN services USING(service_id)"; 92 | 93 | is query_select( 94 | undef, 95 | vars => \@vars, 96 | table => 'user_services', 97 | join => { table => 'services', using => ['id','service_id'], dir => 'LEFT' }, 98 | ), "SELECT * FROM user_services LEFT JOIN services USING(id,service_id)"; 99 | 100 | is query_select( 101 | undef, 102 | vars => \@vars, 103 | table => 'user_services', 104 | join => { table => 'services', on => ['id','service_id'] }, 105 | ), "SELECT * FROM user_services JOIN services ON user_services.id=services.service_id"; 106 | 107 | is query_select( 108 | undef, 109 | vars => \@vars, 110 | table => 'user_services', 111 | order => [ a => 'desc', b => 'asc' ], 112 | ), "SELECT * FROM user_services ORDER BY `a` desc,`b` asc"; 113 | 114 | is query_select( 115 | undef, 116 | vars => \@vars, 117 | table => 'test', 118 | where => { -and => [ { user_id => 1 }, { b => 2, c => 3 } ] }, 119 | ), "SELECT * FROM test WHERE ( ( user_id = ? AND ( b = ? AND c = ? ) ) )"; 120 | 121 | is query_select( 122 | undef, 123 | vars => \@vars, 124 | table => 'test', 125 | user_id => 123, 126 | where => { -or => [ { user_service_id => { -in => [1,2] } }, 127 | { parent => { '!=' => undef } }, 128 | ]}, 129 | ), "SELECT * FROM test WHERE ( ( user_id = ? AND ( user_service_id IN ( ?, ? ) OR parent IS NOT NULL ) ) )"; 130 | 131 | is query_select( 132 | undef, 133 | vars => \@vars, 134 | table => 'test', 135 | user_id => 123, 136 | where => { 'table.field' => 1 }, 137 | ), "SELECT * FROM test WHERE ( ( user_id = ? AND table.field = ? ) )"; 138 | 139 | is query_select( 140 | undef, 141 | vars => \@vars, 142 | table => 'test', 143 | user_id => 123, 144 | where => { 145 | 'settings->cmd' => 1, 146 | }, 147 | ), q/SELECT * FROM test WHERE ( ( user_id = ? AND settings->>'$.cmd' = ? ) )/; 148 | 149 | done_testing(); 150 | -------------------------------------------------------------------------------- /app/t/sql/query_sort.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | use utf8; 3 | 4 | use SHM; 5 | use Test::More; 6 | use Test::Deep; 7 | use Core::Sql::Data qw/query_for_order/; 8 | 9 | my $user = SHM->new( user_id => 40092 ); 10 | 11 | cmp_deeply ( 12 | $user->query_for_order( 13 | sort_field => 'user_id', 14 | sort_direction => 'asc', 15 | ), 16 | [ 'user_id' => 'asc' ] 17 | ); 18 | 19 | cmp_deeply ( 20 | $user->query_for_order( 21 | sort_field => 'created', 22 | ), 23 | [ 'created' => 'desc' ] 24 | ); 25 | 26 | cmp_deeply ( 27 | $user->query_for_order( 28 | sort_field => 'alien', 29 | sort_direction => 'asc', 30 | ), 31 | [ 'user_id' => 'asc' ] 32 | ); 33 | 34 | done_testing(); 35 | -------------------------------------------------------------------------------- /app/t/storage/functions.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | 3 | use Test::More; 4 | use Test::Deep; 5 | use Data::Dumper; 6 | 7 | $ENV{SHM_TEST} = 1; 8 | 9 | use Core::System::ServiceManager qw( get_service ); 10 | use SHM; 11 | my $user = SHM->new( user_id => 40092 ); 12 | 13 | my $storage = $user->srv('storage'); 14 | 15 | $storage->add( 16 | name => 'test', 17 | data => { foo => 1 }, 18 | settings => { 19 | json => 1, 20 | }, 21 | ); 22 | 23 | my $data = $storage->read( name => 'test' ); 24 | is( $data->{foo}, 1 ); 25 | 26 | $storage->replace( 27 | name => 'test', 28 | data => { foo => 2 }, 29 | settings => { 30 | json => 1, 31 | }, 32 | ); 33 | 34 | my $data = $storage->read( name => 'test' ); 35 | is( $data->{foo}, 2 ); 36 | 37 | cmp_deeply( scalar $storage->id('test')->get, { 38 | 'data' => '{"foo":2}', 39 | 'created' => ignore(), 40 | 'user_service_id' => undef, 41 | 'user_id' => 40092, 42 | 'name' => 'test', 43 | 'settings' => { 44 | 'json' => 1 45 | } 46 | }); 47 | 48 | done_testing(); 49 | -------------------------------------------------------------------------------- /app/t/system/encoding.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | 3 | use Test::More; 4 | use Test::Deep; 5 | use Data::Dumper; 6 | use SHM; 7 | use Core::Utils qw( 8 | encode_json 9 | ); 10 | 11 | my $user = SHM->new( user_id => 40092 ); 12 | 13 | subtest 'Test encode_json()' => sub { 14 | my $s = 'привет'; 15 | utf8::decode( $s ); 16 | is( $s ne 'привет', 1 ); 17 | 18 | my $data = { 19 | perl_str => $s, 20 | str => 'привет', 21 | }; 22 | 23 | is( utf8::is_utf8( $data->{perl_str}), 1); 24 | is( utf8::is_utf8( $data->{str}), ''); 25 | 26 | my $json = encode_json( $data ); 27 | is( $json, '{"perl_str":"привет","str":"привет"}' ); 28 | }; 29 | 30 | done_testing(); 31 | -------------------------------------------------------------------------------- /app/t/system/services.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | 3 | use Test::More; 4 | use Data::Dumper; 5 | 6 | use SHM; 7 | use Core::System::ServiceManager qw( get_service ); 8 | 9 | my $user = SHM->new( user_id => 40092 ); 10 | is ( $user->id, 40092 ); 11 | 12 | my $us = get_service('us', _id => 101 ); 13 | is ( $us->id, 101 ); 14 | 15 | my $us1 = get_service('us', _id => 9999 ); 16 | is ( $us1, undef, 'Try to get non exist service'); 17 | 18 | my $us2 = get_service('us', _id => '' ); 19 | is ( $us2, undef, 'Try to get unknown service'); 20 | 21 | my $us3 = get_service('us', _id => 0 ); 22 | is ( $us3, undef, 'Try to get zero service'); 23 | 24 | my $us_parent = $us->parent; 25 | is ( $us_parent->id, 99 ); 26 | 27 | my $ss_1 = get_service('service', _id => 1 ); 28 | my $ss_2 = get_service('service', _id => 2 ); 29 | 30 | is ( $ss_1->id, 1 ); 31 | is ( $ss_2->id, 2 ); 32 | is ( get_service('service', _id => 1)->id, 1 ); 33 | 34 | is ( get_service('service', _id => 1)->get->{service_id}, 1 ); 35 | 36 | my $pay = get_service('pay', _id => 1, foo => 1, bar => 2 ); 37 | is ( $pay->{foo} == 1 && $pay->{bar} == 2, 1, 'Check set variables to object' ); 38 | 39 | subtest 'Check us user_id inherit' => sub { 40 | my $user1 = $user->id(40092); 41 | my $user2 = $user->id(40094); 42 | 43 | my $t1 = $user1->us; 44 | my $t2 = $user2->us; 45 | 46 | is( $t1->{user_id}, 40092 ); 47 | is( $t2->{user_id}, 40094 ); 48 | }; 49 | 50 | 51 | done_testing(); 52 | -------------------------------------------------------------------------------- /app/t/task/cmd.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | use warnings; 3 | use utf8; 4 | 5 | use Test::More; 6 | use Test::Deep; 7 | use Data::Dumper; 8 | 9 | $ENV{SHM_TEST} = 1; 10 | 11 | use SHM; 12 | use Core::System::ServiceManager qw( get_service ); 13 | 14 | SHM->new( user_id => 40092 ); 15 | 16 | subtest 'Check task with not exists server' => sub { 17 | my $t = get_service('Task')->res({ 18 | event => { 19 | kind => 'user_service', 20 | name => 'update', 21 | settings => { 22 | category => 'dns', 23 | cmd => 'dns update', 24 | }, 25 | }, 26 | settings => { 27 | user_service_id => 16, 28 | server_id => 123, 29 | }, 30 | }); 31 | 32 | my $payload = $t->payload; 33 | #is( exists $payload->{headers}, 1, 'Check payload' ); 34 | 35 | is( $t->settings->{server_id}, 123 ); 36 | is( $t->server_id, 123 ); 37 | is( $t->event_settings->{category}, 'dns' ); 38 | is( $t->server, undef ); 39 | is( $t->server( transport => 'ssh' ), undef ); 40 | }; 41 | 42 | subtest 'Check task with exists server1' => sub { 43 | my $t = get_service('Task')->res({ 44 | event => { 45 | kind => 'user_service', 46 | name => 'update', 47 | settings => { 48 | category => 'dns', 49 | cmd => 'dns update', 50 | }, 51 | }, 52 | settings => { 53 | user_service_id => 16, 54 | server_id => 1, 55 | }, 56 | }); 57 | 58 | is( $t->settings->{server_id}, 1 ); 59 | is( $t->server_id, 1 ); 60 | is( $t->event_settings->{category}, 'dns' ); 61 | is( $t->server->id, 1 ); 62 | is( $t->server( transport => 'ssh' )->id, 1 ); 63 | }; 64 | 65 | subtest 'Check task with exists server2' => sub { 66 | my $t = get_service('Task')->res({ 67 | event => { 68 | kind => 'user_service', 69 | name => 'update', 70 | settings => { 71 | category => 'dns', 72 | cmd => 'dns update', 73 | }, 74 | }, 75 | settings => { 76 | user_service_id => 16, 77 | server_id => 2, 78 | }, 79 | }); 80 | 81 | is( $t->settings->{server_id}, 2 ); 82 | is( $t->server_id, 2 ); 83 | is( $t->event_settings->{category}, 'dns' ); 84 | is( $t->server->id, 2 ); 85 | is( $t->server( transport => 'ssh' )->id, 2 ); 86 | }; 87 | 88 | subtest 'Check task without server' => sub { 89 | my $t = get_service('Task')->res({ 90 | event => { 91 | kind => 'user_service', 92 | name => 'update', 93 | settings => { 94 | category => 'dns', 95 | cmd => 'dns update', 96 | }, 97 | }, 98 | settings => { 99 | user_service_id => 16, 100 | }, 101 | }); 102 | 103 | is( $t->settings->{server_id}, undef ); 104 | is( $t->server_id, undef ); 105 | is( $t->event_settings->{category}, 'dns' ); 106 | is( $t->server( transport => 'ssh' )->id, 1 ); 107 | }; 108 | 109 | done_testing(); 110 | -------------------------------------------------------------------------------- /app/t/template/parser.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | use warnings; 3 | 4 | use Test::More; 5 | use Data::Dumper; 6 | 7 | $ENV{SHM_TEST} = 1; 8 | 9 | use SHM; 10 | use Core::System::ServiceManager qw( get_service ); 11 | 12 | SHM->new( user_id => 40092 ); 13 | 14 | subtest 'Check template 1' => sub { 15 | my $t = get_service('template', _id => 'web_tariff_create'); 16 | 17 | my $ret = $t->parse( usi => 99 ); 18 | 19 | is $ret, 'Здравствуйте Фирсов Даниил Андреевич 20 | 21 | Вы зарегистрировали новую услугу: Тариф X-MAX (10000 мб) 22 | 23 | Дата истечения услуги: 2017-01-31 23:59:50 24 | 25 | Стоимость услуги: 300 руб. 26 | 27 | Хостинг сайтов: 28 | Хост: host1.domain.ru 29 | Логин: w_101 30 | Пароль: enos1aer 31 | 32 | Желаем успехов.'; 33 | }; 34 | 35 | subtest 'Check toJson function' => sub { 36 | my $t = get_service('template'); 37 | 38 | my $json = $t->parse( 39 | data => '{{ toJson( 40 | { 41 | a => 1, 42 | b => 2, 43 | } 44 | ) }}', 45 | ); 46 | 47 | is( $json, '{"a":1,"b":2}' ); 48 | }; 49 | 50 | subtest 'Check EVAL_PERL' => sub { 51 | my $t = get_service('template'); 52 | 53 | my $perl = $t->parse( 54 | data => ' 55 | {{ PERL }} 56 | use v5.14; 57 | say "My login is: {{ user.login }}"; 58 | {{ END -}} 59 | ', 60 | ); 61 | 62 | is( $perl, 'My login is: danuk' ); 63 | }; 64 | 65 | subtest 'Check template trim' => sub { 66 | my $t = get_service('template'); 67 | 68 | my $data = $t->parse( 69 | data => ' 70 | Hello world 71 | 72 | ', 73 | ); 74 | 75 | is( $data, 'Hello world' ); 76 | }; 77 | 78 | subtest 'Check forecast via template' => sub { 79 | my $t = get_service('template'); 80 | 81 | is( $t->parse( data => '{{ user.id( 1 ).pays.forecast.total }}' ), 0 ); 82 | is( $t->parse( data => '{{ user.id( 40092 ).pays.forecast.total }}' ), 1035.01 ); 83 | is( $t->parse( data => '{{ user.pays.forecast.total }}' ), 1035.01 ); 84 | }; 85 | 86 | done_testing(); 87 | -------------------------------------------------------------------------------- /app/t/transport/http.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | use warnings; 3 | use utf8; 4 | 5 | use Test::More; 6 | use Test::Deep; 7 | 8 | $ENV{SHM_TEST} = 1; 9 | 10 | use Core::System::ServiceManager qw( get_service ); 11 | 12 | my $http = get_service('Transport::Http'); 13 | 14 | subtest '' => sub { 15 | my $response = $http->http(url => 'http://admin/404'); 16 | like( $response->status_line, qr/404 Not Found/ ); 17 | }; 18 | 19 | subtest '' => sub { 20 | my $response = $http->http(url => 'http://admin'); 21 | is( $response->code, 405 ); 22 | like( $response->status_line, qr/405 Not Allowed/ ); 23 | }; 24 | 25 | subtest '' => sub { 26 | my $response = $http->http(url => 'http://admin', method => 'GET'); 27 | is( $response->is_success, 1 ); 28 | is( $response->status_line, '200 OK' ); 29 | }; 30 | 31 | subtest '' => sub { 32 | my $response = $http->http(url => 'http://admin/shm/v1/test/http/echo', content => '{"test":"echo_post"}'); 33 | is( $response->code, 200 ); 34 | is( $response->json_content->{data}[0]->{payload}->{test}, 'echo_post' ); 35 | }; 36 | 37 | subtest '' => sub { 38 | my $response = $http->http(url => 'http://admin/shm/v1/test/http/echo', content => { test => 'echo_post' } ); 39 | is( $response->json_content->{data}[0]->{payload}->{test}, 'echo_post' ); 40 | }; 41 | 42 | subtest '' => sub { 43 | my $response = $http->http(url => 'http://admin/shm/v1/test/http/echo?test3=test4', method => 'GET', content => 'test=echo_get&test1=test2'); 44 | is( $response->json_content->{data}[0]->{payload}->{test}, 'echo_get' ); 45 | is( $response->json_content->{data}[0]->{payload}->{test1}, 'test2' ); 46 | is( $response->json_content->{data}[0]->{payload}->{test3}, 'test4' ); 47 | }; 48 | 49 | subtest '' => sub { 50 | my $response = $http->http(url => 'http://admin/shm/v1/test/http/echo?format=json&test3=test4', method => 'GET', content => 'test=echo_get;test1=test2'); 51 | is( $response->json_content->{payload}->{test}, 'echo_get' ); 52 | is( $response->json_content->{payload}->{test1}, 'test2' ); 53 | is( $response->json_content->{payload}->{test3}, 'test4' ); 54 | }; 55 | 56 | subtest '' => sub { 57 | my $response = $http->http(url => 'http://admin/shm/v1/test/http/echo', method => 'POST', content_type => 'text/plain', content => 'hello world'); 58 | is( $response->json_content->{data}[0]->{payload}->{POSTDATA}, 'hello world' ); 59 | }; 60 | 61 | done_testing(); 62 | -------------------------------------------------------------------------------- /app/t/transport/mail.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | use warnings; 3 | use utf8; 4 | 5 | use Test::More; 6 | use Test::Deep; 7 | use Data::Dumper; 8 | use Core::Utils qw( 9 | is_email 10 | ); 11 | 12 | $ENV{SHM_TEST} = 1; 13 | 14 | use SHM; 15 | use Core::System::ServiceManager qw( get_service ); 16 | 17 | SHM->new( user_id => 40092 ); 18 | 19 | my $mail = get_service('Transport::Mail'); 20 | 21 | my $ret = $mail->send_mail( 22 | host => '127.0.0.1', 23 | from => 'mail@domain.ru', 24 | to => 'test@domain.ru', 25 | subject => 'Test subject', 26 | message => 'example message', 27 | ); 28 | 29 | is 1,1; 30 | 31 | is( is_email('test@server.ru'), 'test@server.ru' ); 32 | is( is_email('server.ru'), undef ); 33 | is( is_email('test@server.ru'), undef ); 34 | 35 | 36 | done_testing(); 37 | 38 | -------------------------------------------------------------------------------- /app/t/transport/ssh.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | use warnings; 3 | use utf8; 4 | 5 | use Test::More; 6 | use Test::Deep; 7 | use Data::Dumper; 8 | 9 | $ENV{SHM_TEST} = 1; 10 | 11 | use SHM; 12 | use Core::System::ServiceManager qw( get_service ); 13 | 14 | SHM->new( user_id => 40092 ); 15 | 16 | my $ssh = get_service('Transport::Ssh'); 17 | 18 | is( Core::Transport::Ssh::get_ssh_host('127.0.0.1'), 'root@127.0.0.1'); 19 | is( Core::Transport::Ssh::get_ssh_host('user@127.0.0.1'), 'user@127.0.0.1'); 20 | is( Core::Transport::Ssh::get_ssh_host('server.ru'), 'root@server.ru'); 21 | is( Core::Transport::Ssh::get_ssh_host('user@server.ru'), 'user@server.ru'); 22 | 23 | is( Core::Transport::Ssh::get_ssh_host('127.0.0.300'), undef); 24 | is( Core::Transport::Ssh::get_ssh_host('server'), undef); 25 | is( Core::Transport::Ssh::get_ssh_host('server@'), undef); 26 | is( Core::Transport::Ssh::get_ssh_host('user@'), undef); 27 | is( Core::Transport::Ssh::get_ssh_host('user@server'), undef); 28 | is( Core::Transport::Ssh::get_ssh_host('server.a'), undef); 29 | 30 | 31 | done_testing(); 32 | 33 | -------------------------------------------------------------------------------- /app/t/transport/telegram.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | use warnings; 3 | use utf8; 4 | 5 | use Test::More; 6 | use Test::Deep; 7 | use Data::Dumper; 8 | 9 | $ENV{SHM_TEST} = 1; 10 | 11 | use SHM; 12 | use Core::System::ServiceManager qw( get_service ); 13 | 14 | my $user = SHM->new( user_id => 40092 ); 15 | $user->set_settings({ telegram => { chat_id => 123 } }); 16 | 17 | no warnings qw(redefine); 18 | no warnings 'once'; 19 | *Core::Transport::Telegram::shmEcho = sub { shift; \@_ }; 20 | 21 | my $template = $user->srv('template'); 22 | 23 | my $tpl = qq( 24 | <% SWITCH cmd %> 25 | <% CASE '/task' %> 26 | { 27 | "shmEcho": { 28 | "task": "{{ task.foo }}" 29 | } 30 | } 31 | <% CASE '/args' %> 32 | { 33 | "shmEcho": { 34 | "param": "{{ args.1 }}" 35 | } 36 | } 37 | <% END %> 38 | ); 39 | 40 | my $template_bot_id = $template->_add( 41 | id => '_bot', 42 | data => $tpl, 43 | ); 44 | 45 | subtest 'Test telegram.bot() with task' => sub { 46 | my $template_id = $template->_add( 47 | id => '_telegram_bot_task', 48 | data => '{{ toJson(telegram.bot("_bot","/task")) }}', 49 | ); 50 | 51 | my $template_bot_task = $template->id( $template_id ); 52 | 53 | my $ret = $template_bot_task->parse( task => { foo => 2 } ); 54 | 55 | is $ret, '[["task","2"]]'; 56 | }; 57 | 58 | subtest 'Test telegram.bot() with args' => sub { 59 | my $template_id = $template->_add( 60 | id => '_telegram_bot_args', 61 | data => '{{ toJson(telegram.bot("_bot","/args",[5,8])) }}', 62 | ); 63 | 64 | my $template_tg = $template->id( $template_id ); 65 | 66 | my $ret = $template_tg->parse(); 67 | 68 | is $ret, '[["param","8"]]'; 69 | }; 70 | 71 | done_testing(); 72 | -------------------------------------------------------------------------------- /app/t/user/api_safe_args.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | 3 | use Test::More; 4 | use Test::Deep; 5 | use Data::Dumper; 6 | 7 | $ENV{SHM_TEST} = 1; 8 | 9 | use Core::System::ServiceManager qw( get_service ); 10 | use SHM; 11 | my $user = SHM->new( user_id => 40092 ); 12 | 13 | is( $user->get_discount, '0', 'Get user discount' ); 14 | is( $user->get_phone, undef, 'Get user phone' ); 15 | 16 | $user->api('set', 17 | discount => 13, 18 | phone => '+7 123 456-78-90', 19 | admin => 0, 20 | ); 21 | 22 | is( $user->get_discount, '0', 'Get user discount after set' ); 23 | is( $user->get_phone, '+7 123 456-78-90', 'Get user phone' ); 24 | 25 | done_testing(); 26 | -------------------------------------------------------------------------------- /app/t/user/events.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | use warnings; 3 | use utf8; 4 | 5 | use Test::More; 6 | use Data::Dumper; 7 | 8 | $ENV{SHM_TEST} = 1; 9 | 10 | use SHM; 11 | use Core::Const; 12 | use Core::System::ServiceManager qw( get_service ); 13 | 14 | my $user = SHM->new( user_id => 40092 ); 15 | 16 | my $event = get_service('events'); 17 | my $spool = get_service('spool'); 18 | 19 | subtest 'Common payment' => sub { 20 | my @list = $spool->list; 21 | is( scalar @list, 0 ); 22 | 23 | $user->payment( money => 12 ); 24 | 25 | @list = $spool->list; 26 | is( scalar @list, 1 ); 27 | 28 | $spool->_delete; 29 | }; 30 | 31 | subtest 'Add payment event' => sub { 32 | $event->add( 33 | title => 'payment event', 34 | name => 'payment', 35 | server_gid => 1, 36 | settings => { 37 | category => '%', 38 | }, 39 | ); 40 | 41 | my @events = $event->get_events( 42 | name => 'payment', 43 | category => '%', 44 | ); 45 | 46 | is( scalar @events, 1 ); 47 | }; 48 | 49 | subtest 'Payment with extra event' => sub { 50 | my @list = $spool->list; 51 | is( scalar @list, 0 ); 52 | 53 | $user->payment( money => 123 ); 54 | 55 | @list = $spool->list; 56 | is( scalar @list, 2 ); 57 | }; 58 | 59 | done_testing(); 60 | 61 | -------------------------------------------------------------------------------- /app/t/user/functions.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | 3 | use Test::More; 4 | use Test::Deep; 5 | use Data::Dumper; 6 | 7 | $ENV{SHM_TEST} = 1; 8 | 9 | use Core::System::ServiceManager qw( get_service ); 10 | use SHM; 11 | my $user = SHM->new( user_id => 40092 ); 12 | 13 | is( $user->get_user_id, '40092', 'Get user_id' ); 14 | is( $user->get_discount, '0', 'Get user discount' ); 15 | 16 | $user->set( discount => 13 ); 17 | is( $user->get_discount, '13', 'Get user discount after set' ); 18 | 19 | my $who = get_service('user', _id => 108 ); 20 | is ( $who->get_user_id, 108 ); 21 | 22 | my @who_pays = $who->pays->list; 23 | is ( scalar @who_pays, 0, 'Check pays for other user'); 24 | 25 | is( $user->id, 40092 ); 26 | is( $user->pays->user_id, 40092 ); 27 | 28 | my @pays = $user->pays->list; 29 | is ( scalar @pays, 2, 'Check pays for main service'); 30 | 31 | subtest 'Try payment' => sub { 32 | is( $user->get_balance, -21.56, 'Check user balance before payment'); 33 | 34 | $user->payment( money => 1000.03 ); 35 | is( $user->get_balance, 978.47, 'Check user balance after payment'); 36 | 37 | my $spool = get_service('spool'); 38 | my ( $row ) = $spool->list; 39 | 40 | cmp_deeply( $row, superhashof({ 41 | user_id => 40092, 42 | status => 'NEW', 43 | event => { 44 | method => 'activate_services', 45 | kind => 'UserService', 46 | title => 'user payment' 47 | }, 48 | })); 49 | 50 | $spool->_delete(); 51 | }; 52 | 53 | subtest 'Make payment with partner' => sub { 54 | $user->set( partner_id => 40094 ); 55 | my $partner = $user->id( 40094 ); 56 | is( $partner->get_bonus, 0 ); 57 | 58 | $user->payment( money => 100 ); 59 | is( $partner->get_bonus, 20 ); 60 | 61 | my ( $user_spool ) = $user->srv('spool')->list; 62 | is( $user_spool->{user_id}, $user->id ); 63 | is( $user_spool->{event}->{title}, 'user payment' ); 64 | 65 | my ( $partner_spool ) = $partner->srv('spool')->list; 66 | is( $partner_spool->{user_id}, $partner->id ); 67 | is( $partner_spool->{event}->{title}, 'user payment with bonuses' ); 68 | }; 69 | 70 | subtest 'Make payment with partner (personal income percent)' => sub { 71 | $user->set( partner_id => 40094 ); 72 | my $partner = $user->id( 40094 ); 73 | $partner->set_settings( { partner => { income_percent => 50 } } ); 74 | 75 | is( $partner->get_bonus, 20 ); 76 | $user->payment( money => 100 ); 77 | is( $partner->get_bonus, 20 + 100/2 ); 78 | 79 | my ( $user_spool ) = $user->srv('spool')->list; 80 | is( $user_spool->{user_id}, $user->id ); 81 | is( $user_spool->{event}->{title}, 'user payment' ); 82 | 83 | my ( $partner_spool ) = $partner->srv('spool')->list; 84 | is( $partner_spool->{user_id}, $partner->id ); 85 | is( $partner_spool->{event}->{title}, 'user payment with bonuses' ); 86 | }; 87 | 88 | my %profile = $user->profile; 89 | 90 | is $profile{email}, 'email@domain.ru', 'Check user profile'; 91 | is $user->emails, 'email@domain.ru', 'Check user email'; 92 | 93 | subtest 'Check user email by login' => sub { 94 | my $email = 'test@domain.ru'; 95 | my $new_user_id = $user->reg( 96 | login => $email, 97 | password => 'testpassword', 98 | )->{user_id}; 99 | 100 | my $new_user = $user->id( $new_user_id ); 101 | 102 | is( $new_user->login, $email ); 103 | is( $new_user->emails, $email ); 104 | }; 105 | 106 | subtest 'Check refferals count' => sub { 107 | is $user->referrals_count, 0; 108 | 109 | $user->id( 1 )->set( partner_id => 40092 ); 110 | $user->id( 40094 )->set( partner_id => 40092 ); 111 | is $user->referrals_count, 2; 112 | }; 113 | 114 | 115 | done_testing(); 116 | -------------------------------------------------------------------------------- /app/t/user/reg.t: -------------------------------------------------------------------------------- 1 | use v5.14; 2 | 3 | use Test::More; 4 | use Test::Deep; 5 | use Data::Dumper; 6 | 7 | $ENV{SHM_TEST} = 1; 8 | 9 | use Core::System::ServiceManager qw( get_service ); 10 | use SHM; 11 | my $user = SHM->new( user_id => 40092 ); 12 | 13 | my $ret = $user->reg( 14 | login => 'shm@mail.ru', 15 | password => '12345678', 16 | ); 17 | 18 | is( $ret->{login}, 'shm@mail.ru'); 19 | 20 | done_testing(); 21 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | REPO="danuk" 4 | 5 | function build_and_push { 6 | TAGS=() 7 | for TAG in ${LABELS[*]}; do 8 | TAGS+=("$REPO/shm-$1:$TAG") 9 | done 10 | 11 | docker build --platform linux/amd64,linux/arm64 \ 12 | $(printf " -t %s" "${TAGS[@]}") \ 13 | --target $1 . 14 | 15 | for TAG in ${TAGS[*]}; do 16 | docker push $TAG 17 | done 18 | } 19 | 20 | # Set tag from git 21 | GIT_TAG=$(git describe --abbrev=0 --tags) 22 | LABELS=("$GIT_TAG") 23 | 24 | # Add minor tag 25 | VERSION_MINOR=$(echo $GIT_TAG | cut -d '.' -f 1,2) 26 | LABELS+=("$VERSION_MINOR") 27 | 28 | # Add custom tags 29 | LABELS+=("$@") 30 | 31 | REV=$(git rev-parse --short HEAD) 32 | echo "Build version: ${GIT_TAG}-${REV}" 33 | echo "TAGS: ${LABELS[@]}" 34 | 35 | read -p "Press enter to continue..." 36 | echo -n "${GIT_TAG}-${REV}" > app/version 37 | 38 | build_and_push api 39 | build_and_push core 40 | -------------------------------------------------------------------------------- /contributing/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | api: 3 | image: danuk/shm-api:latest 4 | build: 5 | context: shm 6 | dockerfile: ./Dockerfile 7 | target: api 8 | restart: always 9 | links: 10 | - core 11 | core: 12 | image: danuk/shm-core:latest 13 | build: 14 | context: shm 15 | dockerfile: ./Dockerfile 16 | target: core 17 | restart: always 18 | environment: 19 | DEV: 1 20 | DEBUG: DEBUG 21 | TRUNCATE_DB_ON_START: 1 22 | TZ: Europe/Moscow 23 | LANG: C.UTF-8 24 | DB_NAME: shm-db 25 | DB_USER: shm-db-user 26 | DB_PASS: shm-db-user-pass 27 | DB_HOST: mysql 28 | DB_PORT: 3306 29 | links: 30 | - mysql 31 | volumes: 32 | - ./shm/app:/app 33 | depends_on: 34 | mysql: 35 | condition: service_healthy 36 | spool: 37 | image: danuk/shm-core:latest 38 | deploy: 39 | mode: replicated 40 | replicas: 1 41 | build: 42 | context: shm 43 | dockerfile: ./Dockerfile 44 | target: core 45 | restart: always 46 | environment: 47 | DEBUG: ERROR 48 | TZ: Europe/Moscow 49 | LANG: C.UTF-8 50 | SHM_ROLE: spool 51 | DB_NAME: shm-db 52 | DB_USER: shm-db-user 53 | DB_PASS: shm-db-user-pass 54 | DB_HOST: mysql 55 | DB_PORT: 3306 56 | links: 57 | - mysql 58 | volumes: 59 | - ./shm/app:/app 60 | depends_on: 61 | mysql: 62 | condition: service_healthy 63 | admin: 64 | image: danuk/shm-admin:latest 65 | build: 66 | context: shm-admin 67 | dockerfile: ./Dockerfile 68 | target: admin 69 | restart: always 70 | environment: 71 | SHM_HOST: http://api 72 | BASE_PATH: / 73 | volumes: 74 | - ./shm-admin/app:/app 75 | ports: 76 | - "8081:80" 77 | links: 78 | - api 79 | client: 80 | image: danuk/shm-client:latest 81 | build: 82 | context: shm-client 83 | dockerfile: ./Dockerfile 84 | target: client 85 | restart: always 86 | environment: 87 | SHM_HOST: http://api 88 | BASE_PATH: / 89 | volumes: 90 | - ./shm-client/app:/app 91 | ports: 92 | - "8082:80" 93 | links: 94 | - api 95 | mysql: 96 | image: mysql:lts 97 | restart: always 98 | environment: 99 | TZ: Europe/Moscow 100 | LANG: C.UTF-8 101 | MYSQL_ROOT_PASSWORD: shm-db-root-pass 102 | MYSQL_DATABASE: shm-db 103 | MYSQL_USER: shm-db-user 104 | MYSQL_PASSWORD: shm-db-user-pass 105 | volumes: 106 | - mysql-data:/var/lib/mysql 107 | healthcheck: 108 | test: "mysqladmin ping -h 127.0.0.1 -u $$MYSQL_USER --password=$$MYSQL_PASSWORD" 109 | interval: 5s 110 | timeout: 3s 111 | retries: 10 112 | 113 | volumes: 114 | mysql-data: 115 | driver: local 116 | 117 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | api: 3 | image: "danuk/shm-api:${CORE_VERSION}" 4 | pull_policy: always 5 | restart: always 6 | depends_on: 7 | - core 8 | core: 9 | image: "danuk/shm-core:${CORE_VERSION}" 10 | pull_policy: always 11 | restart: always 12 | environment: 13 | #DEBUG: DEBUG 14 | TZ: ${TZ} 15 | LANG: C.UTF-8 16 | DB_NAME: ${MYSQL_DATABASE} 17 | DB_USER: ${MYSQL_USER} 18 | DB_PASS: ${MYSQL_PASS} 19 | DB_HOST: mysql 20 | DB_PORT: 3306 21 | depends_on: 22 | mysql: 23 | condition: service_healthy 24 | spool: 25 | image: "danuk/shm-core:${CORE_VERSION}" 26 | pull_policy: always 27 | deploy: 28 | mode: replicated 29 | replicas: 1 30 | restart: always 31 | environment: 32 | #DEBUG: ERROR 33 | TZ: ${TZ} 34 | LANG: C.UTF-8 35 | SHM_ROLE: "spool" 36 | DB_NAME: ${MYSQL_DATABASE} 37 | DB_USER: ${MYSQL_USER} 38 | DB_PASS: ${MYSQL_PASS} 39 | DB_HOST: mysql 40 | DB_PORT: 3306 41 | depends_on: 42 | mysql: 43 | condition: service_healthy 44 | admin: 45 | image: "danuk/shm-admin:${ADMIN_VERSION}" 46 | pull_policy: always 47 | restart: always 48 | environment: 49 | SHM_HOST: http://api 50 | ports: 51 | - "0.0.0.0:8081:80" 52 | depends_on: 53 | - api 54 | client: 55 | image: "danuk/shm-client:${CLIENT_VERSION}" 56 | pull_policy: always 57 | restart: always 58 | environment: 59 | SHM_HOST: http://api 60 | # volumes: 61 | # - ./styles-alternative.css:/app/assets/css/styles-alternative.css 62 | # - ./head.html:/app/ssi/head.html 63 | # - ./body.html:/app/ssi/body.html 64 | ports: 65 | - "0.0.0.0:8082:80" 66 | depends_on: 67 | - api 68 | mysql: 69 | image: "mysql:lts" 70 | restart: always 71 | environment: 72 | TZ: ${TZ} 73 | LANG: C.UTF-8 74 | MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASS} 75 | MYSQL_DATABASE: ${MYSQL_DATABASE} 76 | MYSQL_USER: ${MYSQL_USER} 77 | MYSQL_PASSWORD: ${MYSQL_PASS} 78 | volumes: 79 | - "mysql-data:/var/lib/mysql" 80 | healthcheck: 81 | test: "mysqladmin ping -h 127.0.0.1 -u $$MYSQL_USER --password=$$MYSQL_PASSWORD" 82 | interval: 5s 83 | timeout: 3s 84 | retries: 10 85 | 86 | volumes: 87 | mysql-data: 88 | driver: local 89 | 90 | -------------------------------------------------------------------------------- /docs/index.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | entries: 3 | shm: 4 | - apiVersion: v2 5 | created: "2024-01-27T23:14:59.309059+03:00" 6 | dependencies: 7 | - name: helm-common 8 | repository: file://charts/helm-common 9 | version: 0.2.12 10 | - name: mysql 11 | repository: https://charts.bitnami.com/bitnami 12 | version: 9.7.2 13 | description: A Helm chart for Kubernetes 14 | digest: 4e0172910bcab01d5aebf178859c018777bc396b24a0cf89ca1ebcf80c9b7fed 15 | home: https://github.com/danuk/shm 16 | maintainers: 17 | - email: danek1982@gmail.com 18 | name: danuk 19 | name: shm 20 | type: application 21 | urls: 22 | - https://danuk.github.io/shm/shm-0.0.1.tgz 23 | version: 0.0.1 24 | generated: "2024-01-27T23:14:59.30598+03:00" 25 | -------------------------------------------------------------------------------- /docs/shm-0.0.1.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danuk/shm/ccf83ecee08b87688e0aefbb3fb8aa119f8bd689/docs/shm-0.0.1.tgz -------------------------------------------------------------------------------- /enterprise/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | api: 3 | image: "cr.yandex/crp53phlntopuut9qqq2/shm-api:${CORE_VERSION}" 4 | pull_policy: always 5 | restart: always 6 | depends_on: 7 | - core 8 | core: 9 | image: "cr.yandex/crp53phlntopuut9qqq2/shm-core:${CORE_VERSION}" 10 | pull_policy: always 11 | restart: always 12 | environment: 13 | #DEBUG: DEBUG 14 | TZ: ${TZ} 15 | LANG: C.UTF-8 16 | DB_NAME: ${MYSQL_DATABASE} 17 | DB_USER: ${MYSQL_USER} 18 | DB_PASS: ${MYSQL_PASS} 19 | DB_HOST: mysql 20 | DB_PORT: 3306 21 | depends_on: 22 | mysql: 23 | condition: service_healthy 24 | spool: 25 | image: "cr.yandex/crp53phlntopuut9qqq2/shm-core:${CORE_VERSION}" 26 | pull_policy: always 27 | deploy: 28 | mode: replicated 29 | replicas: 1 30 | restart: always 31 | environment: 32 | #DEBUG: ERROR 33 | TZ: ${TZ} 34 | LANG: C.UTF-8 35 | SHM_ROLE: "spool" 36 | DB_NAME: ${MYSQL_DATABASE} 37 | DB_USER: ${MYSQL_USER} 38 | DB_PASS: ${MYSQL_PASS} 39 | DB_HOST: mysql 40 | DB_PORT: 3306 41 | depends_on: 42 | mysql: 43 | condition: service_healthy 44 | admin: 45 | image: "danuk/shm-admin:${ADMIN_VERSION}" 46 | pull_policy: always 47 | restart: always 48 | environment: 49 | SHM_HOST: http://api 50 | ports: 51 | - "0.0.0.0:8081:80" 52 | depends_on: 53 | - api 54 | client: 55 | image: "danuk/shm-client:${CLIENT_VERSION}" 56 | pull_policy: always 57 | restart: always 58 | environment: 59 | SHM_HOST: http://api 60 | # volumes: 61 | # - ./styles-alternative.css:/app/assets/css/styles-alternative.css 62 | # - ./head.html:/app/ssi/head.html 63 | # - ./body.html:/app/ssi/body.html 64 | ports: 65 | - "0.0.0.0:8082:80" 66 | depends_on: 67 | - api 68 | mysql: 69 | image: "mysql:lts" 70 | restart: always 71 | environment: 72 | TZ: ${TZ} 73 | LANG: C.UTF-8 74 | MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASS} 75 | MYSQL_DATABASE: ${MYSQL_DATABASE} 76 | MYSQL_USER: ${MYSQL_USER} 77 | MYSQL_PASSWORD: ${MYSQL_PASS} 78 | volumes: 79 | - "mysql-data:/var/lib/mysql" 80 | healthcheck: 81 | test: "mysqladmin ping -h 127.0.0.1 -u $$MYSQL_USER --password=$$MYSQL_PASSWORD" 82 | interval: 5s 83 | timeout: 3s 84 | retries: 10 85 | 86 | volumes: 87 | mysql-data: 88 | driver: local 89 | 90 | -------------------------------------------------------------------------------- /entry-api.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | [ -z "$RESOLVER" ] || sed -i "s|resolver 127.0.0.11|resolver $RESOLVER|" /etc/nginx/conf.d/default.conf 4 | 5 | nginx -g "daemon off;" 6 | 7 | -------------------------------------------------------------------------------- /entry-core.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | cat < /etc/environment 4 | TZ=${TZ} 5 | SHM_ROOT_DIR=${SHM_ROOT_DIR} 6 | SHM_DATA_DIR=${SHM_DATA_DIR} 7 | DB_USER=${DB_USER} 8 | DB_PASS=${DB_PASS} 9 | DB_HOST=${DB_HOST} 10 | DB_PORT=${DB_PORT} 11 | DB_NAME=${DB_NAME} 12 | EOF 13 | 14 | if [ "${SHM_ROLE}" = "spool" ]; then 15 | # Start SHM spool daemon 16 | /app/bin/spool.pl 17 | else 18 | # Create SHM database structure and fill data 19 | /app/bin/init.pl 20 | 21 | uwsgi --ini=/etc/uwsgi/apps-enabled/shm.ini & 22 | test -p /tmp/shm_log || mkfifo /tmp/shm_log 23 | tail -f /tmp/shm_log 24 | fi 25 | 26 | -------------------------------------------------------------------------------- /entry.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | cat < /etc/environment 4 | TZ=${TZ} 5 | SHM_ROOT_DIR=${SHM_ROOT_DIR} 6 | SHM_DATA_DIR=${SHM_DATA_DIR} 7 | DB_USER=${DB_USER} 8 | DB_PASS=${DB_PASS} 9 | DB_HOST=${DB_HOST} 10 | DB_PORT=${DB_PORT} 11 | DB_NAME=${DB_NAME} 12 | EOF 13 | 14 | if [ "${SHM_ROLE}" = "spool" ]; then 15 | # Start SHM spool daemon 16 | /app/bin/spool.pl 17 | else 18 | # Create SHM database structure and fill data 19 | /app/bin/init.pl 20 | 21 | uwsgi --ini=/etc/uwsgi/apps-enabled/shm.ini & 22 | test -p /tmp/shm_log || mkfifo /tmp/shm_log 23 | tail -f /tmp/shm_log 24 | fi 25 | 26 | -------------------------------------------------------------------------------- /helm/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danuk/shm/ccf83ecee08b87688e0aefbb3fb8aa119f8bd689/helm/README.md -------------------------------------------------------------------------------- /helm/k8s-shm/Chart.lock: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: helm-common 3 | repository: file://charts/helm-common 4 | version: 0.2.12 5 | - name: mysql 6 | repository: https://charts.bitnami.com/bitnami 7 | version: 9.7.2 8 | digest: sha256:331956480e6f9f40f6a2de377b77ca78a79ac8c48972a22aee28956c552d8bfa 9 | generated: "2023-04-26T20:14:11.720698+03:00" 10 | -------------------------------------------------------------------------------- /helm/k8s-shm/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: shm 3 | version: 0.0.1 4 | description: A Helm chart for Kubernetes 5 | type: application 6 | home: https://github.com/danuk/shm 7 | maintainers: 8 | - name: danuk 9 | email: danek1982@gmail.com 10 | dependencies: 11 | - name: helm-common 12 | version: 0.2.12 13 | repository: "file://charts/helm-common" 14 | - name: mysql 15 | version: 9.7.2 16 | repository: https://charts.bitnami.com/bitnami 17 | -------------------------------------------------------------------------------- /helm/k8s-shm/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danuk/shm/ccf83ecee08b87688e0aefbb3fb8aa119f8bd689/helm/k8s-shm/README.md -------------------------------------------------------------------------------- /helm/k8s-shm/charts/helm-common-0.2.12.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danuk/shm/ccf83ecee08b87688e0aefbb3fb8aa119f8bd689/helm/k8s-shm/charts/helm-common-0.2.12.tgz -------------------------------------------------------------------------------- /helm/k8s-shm/charts/helm-common/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | appVersion: 1.16.0 3 | description: A base Helm chart for Kubernetes applications 4 | icon: http://storage/devops/default-deploy.png 5 | maintainers: 6 | - email: devops@pimsolutions.ru 7 | name: PimSolutions DevOps Team 8 | name: helm-common 9 | sources: 10 | - https://gitlab.pimpay.ru/infra/helm-common 11 | type: application 12 | version: 0.2.12 13 | -------------------------------------------------------------------------------- /helm/k8s-shm/charts/helm-common/templates/_affinities.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Return a soft podAffinity/podAntiAffinity definition 3 | {{ include "common.affinities.pods.soft" (dict "values" .Values.Labels) -}} 4 | */}} 5 | {{- define "common.affinities.pods.soft" -}} 6 | preferredDuringSchedulingIgnoredDuringExecution: 7 | - weight: 1 8 | podAffinityTerm: 9 | labelSelector: 10 | matchExpressions: 11 | {{- range $key, $value := .labels }} 12 | - key: {{ $key }} 13 | operator: In 14 | values: 15 | - {{ $value }} 16 | {{- end }} 17 | topologyKey: kubernetes.io/hostname 18 | {{- end -}} 19 | 20 | {{/* 21 | Return a hard podAffinity/podAntiAffinity definition 22 | {{ include "common.affinities.pods.hard" (dict "values" .Values.Labels) -}} 23 | */}} 24 | {{- define "common.affinities.pods.hard" -}} 25 | requiredDuringSchedulingIgnoredDuringExecution: 26 | - labelSelector: 27 | matchExpressions: 28 | {{- range $key, $value := .labels }} 29 | - key: {{ $key }} 30 | operator: In 31 | values: 32 | - {{ $value }} 33 | {{- end }} 34 | topologyKey: kubernetes.io/hostname 35 | {{- end -}} 36 | 37 | {{/* 38 | Return a podAffinity/podAntiAffinity definition 39 | {{ include "common.affinities.pods" (dict "antiAffinitySoft" false "values" .Values.labels) -}} 40 | */}} 41 | {{- define "common.affinities.pods" -}} 42 | {{- if .antiAffinitySoft }} 43 | {{- include "common.affinities.pods.soft" . -}} 44 | {{- else }} 45 | {{- include "common.affinities.pods.hard" . -}} 46 | {{- end -}} 47 | {{- end -}} 48 | 49 | -------------------------------------------------------------------------------- /helm/k8s-shm/charts/helm-common/templates/_env.tpl: -------------------------------------------------------------------------------- 1 | 2 | {{- define "env_by_pluck" -}} 3 | {{- $key := index . 0 -}} 4 | {{- $dict := index . 1 -}} 5 | {{- range $name, $value := $dict }} 6 | - name: {{ $name }} 7 | value: 8 | {{- if eq (kindOf $value) "map" -}} 9 | {{- printf " %s" (pluck $key $value | first | default $value._default | toString | quote) }} 10 | {{- else -}} 11 | {{- printf " %s" ($value | toString | quote) }} 12 | {{- end -}} 13 | {{- end }} 14 | {{- end -}} 15 | 16 | {{- define "env_tpl" -}} 17 | {{- $root := index . 0 -}} 18 | {{- $env := index . 1 -}} 19 | {{- range $item := $env -}} 20 | {{- if $item.value }} 21 | {{- $_ := set $item "value" (tpl $item.value $root) }} 22 | {{- end }} 23 | {{- end -}} 24 | {{- $env | toYaml }} 25 | {{- end -}} 26 | 27 | -------------------------------------------------------------------------------- /helm/k8s-shm/charts/helm-common/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "helm.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "helm.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "helm.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "helm.labels" -}} 37 | helm.sh/chart: {{ include "helm.chart" . }} 38 | {{ include "helm.selectorLabels" . | fromJson | toYaml }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "helm.selectorLabels" -}} 49 | {{- dict "app.kubernetes.io/name" (include "helm.name" .) "app.kubernetes.io/instance" .Release.Name | toJson }} 50 | {{- end }} 51 | 52 | {{/* 53 | Create the name of the service account to use 54 | */}} 55 | {{- define "helm.serviceAccountName" -}} 56 | {{- if .Values.serviceAccount.create }} 57 | {{- default (include "helm.fullname" .) .Values.serviceAccount.name }} 58 | {{- else }} 59 | {{- default "default" .Values.serviceAccount.name }} 60 | {{- end }} 61 | {{- end }} 62 | 63 | 64 | {{/* 65 | Usage example: 66 | var_by_tier: {{ include "var_by_pluck" (list $.Values.global.tier .var) | default true }} 67 | var_by_br: {{ include "var_by_pluck" (list $.Values.global.git_branch .var) }} 68 | */}} 69 | {{- define "var_by_pluck" -}} 70 | {{- $key := index . 0 -}} 71 | {{- $dict := index . 1 -}} 72 | {{- if eq (kindOf $dict) "map" -}} 73 | {{- $var := pluck $key $dict | first }} 74 | {{- if eq (kindOf $var) "bool" -}} 75 | {{- $var }} 76 | {{- else -}} 77 | {{- $var := default $dict._default $var }} 78 | {{- if eq (kindOf $var) "map" -}} 79 | {{- $var | toJson }} 80 | {{- else -}} 81 | {{- if eq (kindOf $var) "bool" -}} 82 | {{- $var }} 83 | {{- else -}} 84 | {{- $var | default "" }} 85 | {{- end -}} 86 | {{- end -}} 87 | {{- end -}} 88 | {{- else -}} 89 | {{- if eq (kindOf $dict) "bool" -}} 90 | {{- $dict }} 91 | {{- else -}} 92 | {{- $dict | default "" }} 93 | {{- end -}} 94 | {{- end -}} 95 | {{- end -}} 96 | 97 | {{- define "should_be_deployed" -}} 98 | {{- $ := index . 0 -}} 99 | {{- $item := index . 1 -}} 100 | {{- if or $item.deploy_only_for_tiers $item.deploy_only_for_branches -}} 101 | {{- if has $.Values.global.tier $item.deploy_only_for_tiers -}} 102 | {{- "yes" -}} 103 | {{- else if has $.Values.global.git_branch $item.deploy_only_for_branches -}} 104 | {{- "yes" -}} 105 | {{- end -}} 106 | {{- else -}} 107 | {{- "yes" -}} 108 | {{- end -}} 109 | {{- end -}} 110 | 111 | {{ define "imagePullSecrets" }} 112 | {{- $global_regcred := (index . 0).Values.global.imagePullSecrets | default list }} 113 | {{- $local_regcred := index . 1 | default list }} 114 | {{- if $regcred := concat $global_regcred $local_regcred | compact | uniq }} 115 | {{- with $regcred }} 116 | imagePullSecrets: 117 | {{- toYaml . | replace " - " " " | nindent 2 }} 118 | {{- end }} 119 | {{- end }} 120 | {{ end }} 121 | 122 | {{- define "tolerations" -}} 123 | tolerations: 124 | {{- range ._tolerations }} 125 | - effect: {{ .effect | default "NoSchedule" }} 126 | operator: {{ .operator | default "Equal" }} 127 | {{- if and .key .value }} 128 | key: {{ .key }} 129 | value: {{ .value }} 130 | {{- end }} 131 | {{- end -}} 132 | {{- end -}} 133 | -------------------------------------------------------------------------------- /helm/k8s-shm/charts/helm-common/templates/_resources.tpl: -------------------------------------------------------------------------------- 1 | {{- define "pod_resources" -}} 2 | resources: 3 | {{ pluck ._app .resources | first | default .resources._default | toYaml | indent 2 }} 4 | {{- end }} 5 | 6 | -------------------------------------------------------------------------------- /helm/k8s-shm/charts/helm-common/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- range $name, $root := .Values.ingress }} 2 | {{- if include "should_be_deployed" (list $ .) }} 3 | --- 4 | {{- $is_className := include "var_by_pluck" (list $.Values.global.tier .className) -}} 5 | {{- $is_className_br := include "var_by_pluck" (list $.Values.global.git_branch .classNameForBranches) -}} 6 | {{- $className := or $is_className_br $is_className -}} 7 | {{- $annotations := .annotations | default dict }} 8 | {{- if $annotations_by_tier := include "var_by_pluck" (list $.Values.global.tier .annotations_by_tier) }} 9 | {{- $annotations := merge $annotations ($annotations_by_tier | fromJson) }} 10 | {{- end }} 11 | {{- if and $className (not (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion)) }} 12 | {{- $_ := set $annotations "kubernetes.io/ingress.class" $className}} 13 | {{- end }} 14 | {{- $is_acme := include "var_by_pluck" (list $.Values.global.tier .acme) -}} 15 | {{- $is_acme_br := has $.Values.global.git_branch .acme_for_branches -}} 16 | {{- $acme := or $is_acme $is_acme_br -}} 17 | {{- if $acme }} 18 | {{- $_ := set $annotations "kubernetes.io/tls-acme" "true" }} 19 | {{- end }} 20 | {{- if .no_ssl_redirect }} 21 | {{- $_ := set $annotations "nginx.ingress.kubernetes.io/ssl-redirect" "false" }} 22 | {{- end }} 23 | {{ $ing_name := printf "%s-%s" $name $.Release.Name }} 24 | {{- if and ($.Values.global.git_branch) ( not (contains $.Values.global.git_branch $.Release.Name)) }} 25 | {{ $ing_name = printf "%s-%s-%s" $name $.Release.Name $.Values.global.git_branch }} 26 | {{ end }} 27 | {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion -}} 28 | apiVersion: networking.k8s.io/v1 29 | {{- else if semverCompare ">=1.14-0" $.Capabilities.KubeVersion.GitVersion -}} 30 | apiVersion: networking.k8s.io/v1beta1 31 | {{- else -}} 32 | apiVersion: extensions/v1beta1 33 | {{- end }} 34 | kind: Ingress 35 | metadata: 36 | name: {{ $ing_name }} 37 | labels: 38 | {{- include "helm.labels" $ | nindent 4 }} 39 | {{- with $annotations }} 40 | annotations: 41 | {{- toYaml . | nindent 4 }} 42 | {{- end }} 43 | spec: 44 | {{- if and $className (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} 45 | ingressClassName: {{ $className }} 46 | {{- end }} 47 | rules: 48 | {{- range .rules }} 49 | {{- if $host:= include "var_by_pluck" (list $.Values.global.tier .host) }} 50 | - host: {{ tpl $host $ | quote }} 51 | http: 52 | paths: 53 | {{- range .paths }} 54 | - path: {{ .path }} 55 | {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} 56 | pathType: {{ .pathType }} 57 | {{- end }} 58 | backend: 59 | {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} 60 | service: 61 | name: {{ .service.name }} 62 | port: 63 | number: {{ .service.port }} 64 | {{- else }} 65 | serviceName: {{ .service.name }} 66 | servicePort: {{ .service.port }} 67 | {{- end }} 68 | {{- end }} 69 | {{- end }} 70 | {{- end }} 71 | {{- if $acme }} 72 | tls: 73 | - hosts: 74 | {{- range .rules }} 75 | {{- if $host := include "var_by_pluck" (list $.Values.global.tier .host) }} 76 | - {{ tpl $host $ | quote }} 77 | {{- end }} 78 | {{- end }} 79 | secretName: {{ $name }}-tls 80 | {{- end }} 81 | {{- end }} 82 | {{- end }} 83 | -------------------------------------------------------------------------------- /helm/k8s-shm/charts/helm-common/templates/job.yaml: -------------------------------------------------------------------------------- 1 | {{- range $name, $job := .Values.jobs }} 2 | {{- if include "should_be_deployed" (list $ .) }} 3 | {{- $secrets := include "imagePullSecrets" (list $ .imagePullSecrets ) }} 4 | --- 5 | apiVersion: batch/v1 6 | kind: Job 7 | metadata: 8 | name: {{ $name }} 9 | {{- with .annotations }} 10 | annotations: 11 | {{- toYaml . | nindent 4 }} 12 | {{- end }} 13 | labels: 14 | {{- include "helm.labels" $ | nindent 4 }} 15 | app: {{ $name }} 16 | role: {{ .role | default "job" }} 17 | {{- with .labels }} 18 | {{- toYaml . | nindent 4 }} 19 | {{- end }} 20 | spec: 21 | {{- if .activeDeadlineSeconds }} 22 | activeDeadlineSeconds: {{ .activeDeadlineSeconds }} 23 | {{- end }} 24 | backoffLimit: {{ .backoffLimit | default "0" }} 25 | {{- if .completions }} 26 | completions: {{ .completions }} 27 | {{- end }} 28 | {{- if .parallelism }} 29 | parallelism: {{ .parallelism }} 30 | {{- end }} 31 | template: 32 | metadata: 33 | labels: 34 | {{- include "helm.labels" $ | nindent 8 }} 35 | app: {{ $name }} 36 | role: {{ .role | default "job" }} 37 | {{- with .labels }} 38 | {{- toYaml . | nindent 8 }} 39 | {{- end }} 40 | spec: 41 | containers: 42 | - name: {{ $name }} 43 | imagePullPolicy: {{ .imagePullPolicy | default "Always" }} 44 | image: {{ tpl .image $ }} 45 | {{- with .command }} 46 | command: {{ toJson . }} 47 | {{- end }} 48 | {{- with .args }} 49 | args: {{ toJson . }} 50 | {{- end }} 51 | {{- if or .env .env_by_tier .env_by_br .env_tpl }} 52 | env: 53 | {{- if .env }} 54 | {{- include "env_tpl" (list $ .env) | nindent 12 }} 55 | {{- end }} 56 | {{- include "env_by_pluck" (list $.Values.global.tier .env_by_tier) | nindent 12 }} 57 | {{- include "env_by_pluck" (list $.Values.global.git_branch .env_by_br) | nindent 12 }} 58 | {{- if .env_tpl }} 59 | {{- tpl .env_tpl $ | nindent 12 }} 60 | {{- end }} 61 | {{- end }} 62 | {{- with .volumeMounts }} 63 | volumeMounts: {{ toYaml . | nindent 12 }} 64 | {{- end }} 65 | {{- with .envFrom }} 66 | envFrom: {{ toYaml . | nindent 12 }} 67 | {{- end }} 68 | {{- include "pod_resources" (set $.Values.global "_app" $name) | nindent 10 }} 69 | {{- $secrets | nindent 6}} 70 | restartPolicy: {{ .restartPolicy | default "Never" }} 71 | {{- with .hostAliases }} 72 | hostAliases: {{ toYaml . | nindent 8 }} 73 | {{- end }} 74 | {{- if hasKey . "serviceAccount" }} 75 | {{- if hasKey .serviceAccount "name" }} 76 | serviceAccountName: {{ .serviceAccount.name }} 77 | {{- else }} 78 | serviceAccountName: {{ .name }} 79 | {{- end }} 80 | {{- end }} 81 | {{- if .pool }} 82 | {{- if $pool_name:= pluck $.Values.global.tier .pool | first }} 83 | nodeSelector: 84 | pool: {{ $pool_name }} 85 | tolerations: 86 | - key: "pool" 87 | operator: "Equal" 88 | value: {{ $pool_name }} 89 | effect: "NoSchedule" 90 | {{- end }} 91 | {{- end }} 92 | {{- with .volumes }} 93 | volumes: {{ toYaml . | nindent 8 }} 94 | {{- end }} 95 | {{- end }} 96 | {{- end }} 97 | -------------------------------------------------------------------------------- /helm/k8s-shm/charts/helm-common/templates/pdb.yaml: -------------------------------------------------------------------------------- 1 | {{- range $name, $root := .Values.apps }} 2 | {{- $replicas := include "var_by_pluck" (list $.Values.global.tier .replicas) | int -}} 3 | {{ if ge $replicas 2 }} 4 | --- 5 | {{ if $.Capabilities.KubeVersion.Version | semverCompare ">=1.21.0-0" }} 6 | apiVersion: policy/v1 7 | {{- else }} 8 | apiVersion: policy/v1beta1 9 | {{- end }} 10 | kind: PodDisruptionBudget 11 | metadata: 12 | name: {{ $name }}-pdb 13 | spec: 14 | minAvailable: {{ include "var_by_pluck" (list $.Values.global.tier .podDisruptionBudget) | default 1 }} 15 | selector: 16 | matchLabels: 17 | {{- include "helm.labels" $ | nindent 6 }} 18 | app: {{ $name }} 19 | {{- with .labels }} 20 | {{- toYaml . | nindent 6 }} 21 | {{- end }} 22 | {{- end }} 23 | {{- end }} 24 | -------------------------------------------------------------------------------- /helm/k8s-shm/charts/helm-common/templates/service.yaml: -------------------------------------------------------------------------------- 1 | {{- range $name, $root := .Values.services }} 2 | {{- if include "should_be_deployed" (list $ .) }} 3 | --- 4 | apiVersion: v1 5 | kind: Service 6 | metadata: 7 | name: {{ $name }} 8 | labels: 9 | {{- include "helm.labels" $ | nindent 4 }} 10 | app: {{ .selectorAppName | default $name }} 11 | {{- with .annotations }} 12 | annotations: {{ toYaml . | nindent 4 }} 13 | {{- end }} 14 | spec: 15 | type: {{ .type }} 16 | {{- if .externalName }} 17 | externalName: {{ .externalName }} 18 | {{- end }} 19 | {{- with .ports }} 20 | ports: {{ toYaml . | nindent 4 }} 21 | {{- end }} 22 | {{- if ne .type "ExternalName" }} 23 | selector: 24 | {{- include "helm.selectorLabels" $ | fromJson | toYaml | nindent 4 }} 25 | app: {{ .selectorAppName | default $name }} 26 | {{- end }} 27 | {{- end }} 28 | {{- end }} 29 | -------------------------------------------------------------------------------- /helm/k8s-shm/charts/helm-common/templates/tests/test.yaml: -------------------------------------------------------------------------------- 1 | {{- range $name, $tests := .Values.tests }} 2 | {{- if include "should_be_deployed" (list $ .) }} 3 | {{- $secrets := include "imagePullSecrets" (list $ .imagePullSecrets ) }} 4 | --- 5 | apiVersion: v1 6 | kind: Pod 7 | metadata: 8 | name: {{ $name }} 9 | {{- with .annotations }} 10 | annotations: 11 | {{- toYaml . | nindent 4 }} 12 | {{- end }} 13 | labels: 14 | {{- include "helm.labels" $ | nindent 4 }} 15 | app: {{ $name }} 16 | role: {{ .role | default "tests" }} 17 | {{- with .labels }} 18 | {{- toYaml . | nindent 4 }} 19 | {{- end }} 20 | spec: 21 | containers: 22 | - name: {{ $name }} 23 | imagePullPolicy: {{ .imagePullPolicy | default "Always" }} 24 | image: {{ tpl .image $ }} 25 | {{- with .command }} 26 | command: {{ toJson . }} 27 | {{- end }} 28 | {{- with .args }} 29 | args: {{ toJson . }} 30 | {{- end }} 31 | {{- if or .env .env_by_tier .env_by_br .env_tpl }} 32 | env: 33 | {{- if .env }} 34 | {{- include "env_tpl" (list $ .env) | nindent 10 }} 35 | {{- end }} 36 | {{- include "env_by_pluck" (list $.Values.global.tier .env_by_tier) | nindent 10 }} 37 | {{- include "env_by_pluck" (list $.Values.global.git_branch .env_by_br) | nindent 10 }} 38 | {{- if .env_tpl }} 39 | {{- tpl .env_tpl $ | nindent 10 }} 40 | {{- end }} 41 | {{- end }} 42 | {{- include "pod_resources" (set $.Values.global "_app" $name) | nindent 6 }} 43 | {{- $secrets | nindent 2}} 44 | restartPolicy: {{ .restartPolicy | default "Never" }} 45 | {{- with .hostAliases }} 46 | hostAliases: {{ toYaml . | nindent 2 }} 47 | {{- end }} 48 | {{- if .pool }} 49 | {{- if $pool_name:= pluck $.Values.global.tier .pool | first }} 50 | nodeSelector: 51 | pool: {{ $pool_name }} 52 | tolerations: 53 | - key: "pool" 54 | operator: "Equal" 55 | value: {{ $pool_name }} 56 | effect: "NoSchedule" 57 | {{- end }} 58 | {{- end }} 59 | {{- end }} 60 | {{- end }} 61 | -------------------------------------------------------------------------------- /helm/k8s-shm/charts/helm-common/values.yaml: -------------------------------------------------------------------------------- 1 | nameOverride: "" 2 | global: 3 | resources: 4 | _default: 5 | limits: 6 | cpu: 2 7 | memory: 2Gi 8 | requests: 9 | cpu: 50m 10 | memory: 100Mi 11 | 12 | apps: {} 13 | services: {} 14 | ingress: {} 15 | cronjobs: [] 16 | 17 | imagePullCredentials: 18 | auths: 19 | https://gitlab.pimpay.ru:4567/v2/: 20 | email: devops@pimsolutions.ru 21 | username: regcred 22 | password: 23 | 24 | -------------------------------------------------------------------------------- /helm/k8s-shm/charts/mysql-9.7.2.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danuk/shm/ccf83ecee08b87688e0aefbb3fb8aa119f8bd689/helm/k8s-shm/charts/mysql-9.7.2.tgz -------------------------------------------------------------------------------- /helm/update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | helm package k8s-shm --destination ./docs 4 | helm repo index docs --url https://danuk.github.io/shm 5 | 6 | rm -rf ../docs 7 | mv ./docs ../docs 8 | -------------------------------------------------------------------------------- /nginx/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80 default_server; 3 | listen [::]:80 default_server; 4 | server_name _; 5 | root /app/public_html/; 6 | 7 | charset utf8; 8 | index index.html; 9 | 10 | error_page 403 /403.html; 11 | error_page 404 /404.html; 12 | 13 | location / { 14 | include /etc/nginx/uwsgi_params; 15 | 16 | uwsgi_param PERL5LIB "/app/lib:/app/conf"; 17 | uwsgi_pass_header Authorization; 18 | 19 | resolver 127.0.0.11 valid=3s ipv6=off; 20 | 21 | uwsgi_modifier1 9; 22 | uwsgi_pass core:9082; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /nginx/uwsgi.ini: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | 3 | master = true 4 | workers = 2 5 | no-orphans = true 6 | socket = 0.0.0.0:9082 7 | uid = shm 8 | log-date = true 9 | threads = 20 10 | buffer-size = 32768 11 | 12 | plugins = cgi 13 | cgi = /app/public_html 14 | cgi = /shm/v1=/app/public_html/shm/v1.cgi 15 | cgi = /admin=/home/shm/shm/app/public_html/shm/object.cgi 16 | cgi-index = v1.cgi 17 | 18 | route = ^/shm/healthcheck.cgi donotlog: 19 | 20 | -------------------------------------------------------------------------------- /scripts/mysql/cron/mysql-backup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BACKUP_DIR="/var/shm/backups" 4 | 5 | # Use `docker ps` command to determine container name with mysql 6 | CONTAINER="shm_mysql_1" 7 | 8 | set -e 9 | set -o pipefail 10 | 11 | mkdir -p ${BACKUP_DIR} 12 | cd ${BACKUP_DIR} 13 | 14 | docker exec ${CONTAINER} /bin/bash -c 'MYSQL_PWD=${MYSQL_ROOT_PASSWORD} mysqldump -u root shm' \ 15 | | gzip > shm_$(date +%d%m%Y-%H%M%S).sql.gz 16 | 17 | 18 | -------------------------------------------------------------------------------- /scripts/telegram/setWebhook.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | TOKEN="" 4 | URL="bill.DOMAIN.ru" 5 | SHM_TEMPLATE="telegram_bot" 6 | 7 | curl https://api.telegram.org/bot${TOKEN}/deleteWebhook?drop_pending_updates=True 8 | 9 | cat << EOF | curl -X POST \ 10 | -H 'content-type: application/json' \ 11 | https://api.telegram.org/bot${TOKEN}/setWebhook \ 12 | -d @- 13 | { 14 | "url": "https://${URL}/shm/v1/telegram/bot/${SHM_TEMPLATE}", 15 | "allowed_updates": [ 16 | "message", 17 | "inline_query", 18 | "callback_query", 19 | "my_chat_member", 20 | "pre_checkout_query" 21 | ] 22 | } 23 | EOF 24 | 25 | --------------------------------------------------------------------------------