Π Π°Π·Π²ΠΈΡΠΈΠ΅ Π½Π° ΡΠ΅ΠΌΠ°ΡΠ° ΠΈ Π·Π° ΡΠ°Π·ΡΠΈΡΠ΅Π½ ΠΎΡΠ³ΠΎΠ²ΠΎΡ Π½Π°
ΠΠ·ΠΏΠΎΠ»Π·Π²Π°Π½Π°ΡΠ° ΡΡΡΠ°ΡΠ΅Π³ΠΈΡ ΠΏΡΠ΅Π΄Π²ΠΈΠΆΠ΄Π° ΠΊΠΎΠ½ΡΠ΅ΠΏΡΠΈΡΡΠ° βΠΠΈΠ·Π½Π΅Ρ Π»ΠΎΠ³ΠΈΠΊΠ° Π² ΠΠβ, ΠΊΠΎΠ΅ΡΠΎ Π±Π΅ΡΠ΅ ΠΎΠΏΠΈΡΠ°Π½ΠΎ ΠΏΠΎ-ΠΏΠΎΠ΄ΡΠΎΠ±Π½ΠΎ ΡΡΠΊ β
Π’Π΅ΠΎΡΠ΅ΡΠΈΡΠ½Π°ΡΠ° ΡΠ°ΡΡ Π΅ ΠΎΡΠ»ΠΈΡΠ½ΠΎ ΠΎΠΏΠΈΡΠ°Π½Π° Π² Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ°ΡΠΈΡΡΠ° β . ΠΠΎ-Π΄ΠΎΠ»Ρ Π΅ ΡΠ°Π·Π³Π»Π΅Π΄Π°Π½Π° ΠΏΡΠ°ΠΊΡΠΈΡΠ΅ΡΠΊΠ°ΡΠ° ΡΠ΅Π°Π»ΠΈΠ·Π°ΡΠΈΡ Π½Π° ΠΊΠΎΠ½ΠΊΡΠ΅ΡΠ½Π° Π±ΠΈΠ·Π½Π΅Ρ Π·Π°Π΄Π°ΡΠ° β ΡΠΎΠ»Π΅Π²Π°ΡΠ° ΠΌΠΎΠ΄Π΅Π» Π½Π° Π΄ΠΎΡΡΡΠΏ Π΄ΠΎ Π΄Π°Π½Π½ΠΈ.

Π ΡΡΠ°ΡΠΈΡΡΠ° Π½ΡΠΌΠ° Π½ΠΈΡΠΎ Π½ΠΎΠ²ΠΎ, Π½ΡΠΌΠ° ΡΠΊΡΠΈΡΠΎ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅ ΠΈ ΡΠ°ΠΉΠ½ΠΈ Π·Π½Π°Π½ΠΈΡ. ΠΡΠΎΡΡΠΎ ΡΠΊΠΈΡΠ° Π½Π° ΠΏΡΠ°ΠΊΡΠΈΡΠ΅ΡΠΊΠ°ΡΠ° ΡΠ΅Π°Π»ΠΈΠ·Π°ΡΠΈΡ Π½Π° ΡΠ΅ΠΎΡΠ΅ΡΠΈΡΠ½Π° ΠΈΠ΄Π΅Ρ. ΠΠΊΠΎ Π½ΡΠΊΠΎΠΉ ΡΠ΅ ΠΈΠ½ΡΠ΅ΡΠ΅ΡΡΠ²Π° β ΡΠ΅ΡΠ΅. ΠΠΊΠΎ Π½Π΅ ΡΠ΅ ΠΈΠ½ΡΠ΅ΡΠ΅ΡΡΠ²Π° β Π½Π΅ Π³ΡΠ±Π΅ΡΠ΅ Π²ΡΠ΅ΠΌΠ΅ΡΠΎ ΡΠΈ Π½Π°ΠΏΡΠ°Π·Π½ΠΎ.
Π€ΠΎΡΠΌΡΠ»ΠΈΡΠ°Π½Π΅ Π½Π° Π·Π°Π΄Π°ΡΠ°ΡΠ°
ΠΠ΅ΠΎΠ±Ρ ΠΎΠ΄ΠΈΠΌΠΎ Π΅ Π΄Π° ΡΠ΅ ΡΠ°Π·Π³ΡΠ°Π½ΠΈΡΠ°Ρ ΠΏΡΠ°Π²Π°ΡΠ° Π·Π° ΠΏΡΠ΅Π³Π»Π΅Π΄/Π²ΠΌΡΠΊΠ²Π°Π½Π΅/ΠΏΡΠΎΠΌΡΠ½Π°/ΠΈΠ·ΡΡΠΈΠ²Π°Π½Π΅ Π½Π° Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ° Π² ΡΡΠΎΡΠ²Π΅ΡΡΡΠ²ΠΈΠ΅ Ρ ΡΠΎΠ»ΡΡΠ° Π½Π° ΠΏΠΎΡΡΠ΅Π±ΠΈΡΠ΅Π»Ρ Π½Π° ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ΡΠΎ. ΠΠΎΠ΄ ΡΠΎΠ»ΡΡΠ° ΡΠ΅ ΡΠ°Π·Π±ΠΈΡΠ° Π·Π°ΠΏΠΈΡΠ° Π² ΡΠ°Π±Π»ΠΈΡΠ°ΡΠ° roles ΡΠ²ΡΡΠ·Π°Π½Π° Ρ ΠΎΡΠ½ΠΎΡΠ΅Π½ΠΈΠ΅ ΠΌΠ½ΠΎΠ³ΠΎ-ΠΊΡΠΌ-ΠΌΠ½ΠΎΠ³ΠΎ Ρ ΡΠ°Π±Π»ΠΈΡΠ°ΡΠ° users. ΠΠ΅ΡΠ°ΠΉΠ»ΠΈΡΠ΅ ΠΎΡ ΡΠ΅Π°Π»ΠΈΠ·Π°ΡΠΈΡΡΠ° Π½Π° ΡΠ°Π±Π»ΠΈΡΠΈΡΠ΅, ΠΏΠΎΡΠ°Π΄ΠΈ ΡΡΠΈΠ²ΠΈΠ°Π»Π½ΠΎΡΡΡΠ° ΠΈΠΌ, ΡΠ° ΠΎΠΏΡΡΠ½Π°ΡΠΈ. Π‘ΡΡΠΎ ΡΠ°ΠΊΠ° ΡΠ° ΠΎΠΏΡΡΠ½Π°ΡΠΈ ΠΊΠΎΠ½ΠΊΡΠ΅ΡΠ½ΠΈ Π΄Π΅ΡΠ°ΠΉΠ»ΠΈ ΠΎΡ ΡΠ΅Π°Π»ΠΈΠ·Π°ΡΠΈΡΡΠ°, ΡΠ²ΡΡΠ·Π°Π½ΠΈ Ρ ΠΏΡΠ΅Π΄ΠΌΠ΅ΡΠ½Π°ΡΠ° ΠΎΠ±Π»Π°ΡΡ.
Π Π΅Π°Π»ΠΈΠ·Π°ΡΠΈΡ
Π‘ΡΠ·Π΄Π°Π²Π°ΠΌΠ΅ ΡΠΎΠ»ΠΈ, ΡΡ Π΅ΠΌΠΈ, ΡΠ°Π±Π»ΠΈΡΠ°
Π‘ΡΠ·Π΄Π°Π²Π°Π½Π΅ Π½Π° ΠΎΠ±Π΅ΠΊΡΠΈ Π½Π° ΠΠ
CREATE ROLE store;
CREATE SCHEMA store AUTHORIZATION store;
CREATE TABLE store.docs
(
id integer , --id Π½Π° Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ°
man_id integer , --id Π½Π° ΠΌΠ΅Π½ΠΈΠ΄ΠΆΡΡΠ° Π½Π° Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ°
stat_id integer , --id Π½Π° ΡΡΠ°ΡΡΡΠ° Π½Π° Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ°
...
is_del BOOLEAN DEFAULT FALSE
);
ALTER TABLE store.docs ADD CONSTRAINT doc_pk PRIMARY KEY (id);
ALTER TABLE store.docs OWNER TO store ;
Π‘ΡΠ·Π΄Π°Π²Π°ΠΌΠ΅ ΡΡΠ½ΠΊΡΠΈΠΈ Π·Π° ΡΠ΅Π°Π»ΠΈΠ·Π°ΡΠΈΡ Π½Π° RLS
ΠΡΠΎΠ²Π΅ΡΠΊΠ° Π½Π° Π²ΡΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡΡΠ° Π·Π° ΠΈΠ·ΠΏΡΠ»Π½Π΅Π½ΠΈΠ΅ Π½Π° SELECT ΡΠ΅Π΄
check_select
CREATE OR REPLACE FUNCTION store.check_select ( current_id store.docs.id%TYPE ) RETURNS boolean AS $$
DECLARE
result boolean ;
curr_pid integer ;
curr_stat_id integer ;
doc_man_id integer ;
BEGIN
-- DBA ΠΈΠΌΠ° Π΄ΠΎΡΡΡΠΏ Π΄ΠΎ Π²ΡΠΈΡΠΊΠΈ Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠΈ
IF SESSION_USER = 'curr_dba'
THEN
RETURN TRUE ;
END IF ;
--------------------------------
--ΠΠΊΠΎ Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΡΡ ΠΈΠΌΠ° ΠΌΠ°ΡΠΊΠ° 'ΠΈΠ·ΡΡΠΈΡ' - Π½Π΅ ΠΏΠΎΠΊΠ°Π·Π²Π°ΠΉΡΠ΅ Π² ΠΈΠ·Π±ΠΎΡΠ°
SELECT
is_del
INTO
result
FROM
store.docs
WHERE
id = current_id ;
IF result = TRUE
THEN
RETURN FALSE ;
END IF ;
--------------------------------
--ΠΠΎΠ»ΡΡΠ΅ΡΠ΅ id Π½Π° ΡΠ΅ΠΊΡΡΠΈΡ ΠΏΠΎΡΡΠ΅Π±ΠΈΡΠ΅Π»
SELECT
service_function.get_curr_pid ()
INTO
curr_pid ;
--------------------------------
--ΠΠΎΠ»ΡΡΠ΅ΡΠ΅ id Π½Π° ΠΌΠ΅Π½ΠΈΠ΄ΠΆΡΡΠ° Π½Π° Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ°
SELECT
man_id
INTO
doc_man_id
FROM
store.docs
WHERE
id = current_id ;
--------------------------------
--ΠΠΊΠΎ ΠΌΠ΅Π½ΠΈΠ΄ΠΆΡΡΡΡ Π½Π° Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ° Π½Π΅ Π΅ ΡΠ΅ΠΊΡΡΠΈΡΡ ΠΏΠΎΡΡΠ΅Π±ΠΈΡΠ΅Π» ΠΈΠ»ΠΈ ΠΌΠ΅Π½ΠΈΠ΄ΠΆΡΡΡΡ Π½Π΅ Π΅ Π½Π°Π·Π½Π°ΡΠ΅Π½
--Π΄ΠΎΠ±Π°Π²Π΅ΡΠ΅ Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ° Π² ΠΈΠ·Π±ΠΎΡΠ°
IF doc_man_id != curr_pid OR doc_man_id IS NULL
THEN
RETURN TRUE ;
ELSE
--ΠΠΎΠ»ΡΡΠ΅ΡΠ΅ ΡΠ΅ΠΊΡΡΠΈΡ ΡΡΠ°ΡΡΡ Π½Π° Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ°
SELECT
stat_id
INTO
curr_statid
FROM
store.docs
WHERE
id = current_id ;
--ΠΠΊΠΎ ΡΡΠ°ΡΡΡΡΡ ΠΏΠΎΠ·Π²ΠΎΠ»ΡΠ²Π° Π²ΠΈΠ΄ΠΈΠΌΠΎΡΡ Π½Π° Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ° - Π΄ΠΎΠ±Π°Π²Π΅ΡΠ΅ Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ° Π² ΠΈΠ·Π±ΠΎΡΠ°
IF curr_statid = 4 OR curr_statid = 9
THEN
RETURN TRUE ;
ELSE
--ΠΠ½Π°ΡΠ΅ - ΠΈΠ·ΠΊΠ»ΡΡΠ΅ΡΠ΅ Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ° ΠΎΡ ΠΈΠ·Π±ΠΎΡΠ°
RETURN FALSE ;
END IF ;
END IF ;
--------------------------------
RETURN FALSE ;
END
$$ LANGUAGE plpgsql SECURITY DEFINER;
ALTER FUNCTION store.check_select( store.docs.id%TYPE ) OWNER TO store ;
REVOKE EXECUTE ON FUNCTION store.check_select( store.docs.id%TYPE ) FROM public;
GRANT EXECUTE ON FUNCTION store.check_select( store.docs.id%TYPE ) TO service_functions;
ΠΡΠΎΠ²Π΅ΡΠΊΠ° Π½Π° Π²ΡΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡΡΠ° Π·Π° ΠΈΠ·Π²ΡΡΡΠ²Π°Π½Π΅ Π½Π° INSERT Π½Π° ΡΠ΅Π΄
check_insert
CREATE OR REPLACE FUNCTION store.check_insert ( current_id store.docs.id%TYPE ) RETURNS boolean AS $$
DECLARE
curr_role_id integer ;
BEGIN
--DBA ΠΌΠΎΠΆΠ΅ Π΄Π° Π΄ΠΎΠ±Π°Π²Ρ ΡΠ΅Π΄ Π²ΡΠ² Π²ΡΠ΅ΠΊΠΈ ΡΠ»ΡΡΠ°ΠΉ
IF SESSION_USER = 'curr_dba'
THEN
RETURN TRUE ;
END IF ;
--------------------------------
--ΠΠΎΠ»ΡΡΠ΅ΡΠ΅ id Π½Π° ΡΠΎΠ»ΡΡΠ° Π½Π° ΡΠ΅ΠΊΡΡΠΈΡ ΠΏΠΎΡΡΠ΅Π±ΠΈΡΠ΅Π»
SELECT
service_functions.current_rid()
INTO
curr_role_id ;
--------------------------------
--ΠΠΊΠΎ ΡΠΎΠ»ΡΡΠ° Π΄ΠΎΠΏΡΡΠΊΠ° Π²ΡΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡΡΠ° Π·Π° ΡΡΠ·Π΄Π°Π²Π°Π½Π΅ Π½Π° Π½ΠΎΠ² Π΄ΠΎΠΊΡΠΌΠ΅Π½Ρ
--ΡΠ°Π·ΡΠ΅ΡΠ΅ΡΠ΅
IF curr_role_id = 3 OR curr_role_id = 5
THEN
RETURN TRUE ;
END IF ;
--------------------------------
RETURN FALSE ;
END
$$ LANGUAGE plpgsql SECURITY DEFINER;
ALTER FUNCTION store.check_insert( store.docs.id%TYPE ) OWNER TO store ;
REVOKE EXECUTE ON FUNCTION store.check_insert( store.docs.id%TYPE ) FROM public;
GRANT EXECUTE ON FUNCTION store.check_insert( store.docs.id%TYPE ) TO service_functions;
ΠΡΠΎΠ²Π΅ΡΠΊΠ° Π½Π° Π²ΡΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡΡΠ° Π·Π° ΠΈΠ·Π²ΡΡΡΠ²Π°Π½Π΅ Π½Π° DELETE Π½Π° ΡΠ΅Π΄
check_delete
CREATE OR REPLACE FUNCTION store.check_delete ( current_id store.docs.id%TYPE )
RETURNS boolean AS $$
BEGIN
--Π‘Π°ΠΌΠΎ DBA ΠΌΠΎΠΆΠ΅ Π΄Π° ΠΈΠ·ΡΡΠΈΠ²Π° ΡΠ΅Π΄
IF SESSION_USER = 'curr_dba'
THEN
RETURN TRUE ;
END IF ;
--------------------------------
RETURN FALSE ;
END
$$ LANGUAGE plpgsql
SECURITY DEFINER;
ALTER FUNCTION store.check_delete( store.docs.id%TYPE ) OWNER TO store ;
REVOKE EXECUTE ON FUNCTION store.check_delete( store.docs.id%TYPE ) FROM public;ΠΡΠΎΠ²Π΅ΡΠΊΠ° Π½Π° Π²ΡΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡΡΠ° Π·Π° ΠΈΠ·Π²ΡΡΡΠ²Π°Π½Π΅ Π½Π° UPDATE Π½Π° ΡΠ΅Π΄.
update_using
CREATE OR REPLACE FUNCTION store.update_using ( current_id store.docs.id%TYPE , is_del boolean )
RETURNS boolean AS $$
BEGIN
--ΠΠΎΠΊΡΠΌΠ΅Π½ΡΠΈΡΠ΅ ΡΡΡ ΡΡΠ°ΡΡΡ 'ΠΈΠ·ΡΡΠΈΡ' Π½Π΅ ΠΌΠΎΠ³Π°Ρ Π΄Π° Π±ΡΠ΄Π°Ρ ΡΠ΅Π΄Π°ΠΊΡΠΈΡΠ°Π½ΠΈ
IF is_del
THEN
RETURN FALSE ;
ELSE
RETURN TRUE ;
END IF ;
END
$$ LANGUAGE plpgsql SECURITY DEFINER;
ALTER FUNCTION store.update_using( store.docs.id%TYPE , boolean ) OWNER TO store ;
REVOKE EXECUTE ON FUNCTION store.update_using( store.docs.id%TYPE , boolean ) FROM public;
GRANT EXECUTE ON FUNCTION store.update_using( store.docs.id%TYPE ) TO service_functions;update_check
CREATE OR REPLACE FUNCTION store.update_with_check ( current_id store.docs.id%TYPE , is_del boolean )
RETURNS boolean AS $$
DECLARE
current_rid integer ;
current_statid integer ;
BEGIN
--DBA ΠΌΠΎΠΆΠ΅ Π΄Π° ΠΏΡΠ΅Π³Π»Π΅ΠΆΠ΄Π° ΡΠ΅Π΄Π°
IF SESSION_USER = 'curr_dba'
THEN
RETURN TRUE ;
END IF ;
--------------------------------
--ΠΠΎΠ»ΡΡΠ°Π²Π°Π½Π΅ Π½Π° id Π½Π° ΡΠΎΠ»ΡΡΠ° Π½Π° ΡΠ΅ΠΊΡΡΠΈΡ ΠΏΠΎΡΡΠ΅Π±ΠΈΡΠ΅Π»
SELECT
service_functions.current_rid()
INTO
curr_role_id ;
--------------------------------
--ΠΠ·ΡΡΠΈΠ²Π°Π½Π΅ Π½Π° Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ° - ΠΏΡΠΎΠΌΡΠ½Π° Π½Π° ΠΏΡΠΈΠ·Π½Π°ΠΊΠ°
IF is_deleted
THEN
--ΠΠΊΠΎ ΡΠΎΠ»ΡΡΠ° Π½Π° ΠΏΠΎΡΡΠ΅Π±ΠΈΡΠ΅Π»Ρ ***
IF current_role_id = 3
THEN
SELECT
stat_id
INTO
curr_statid
FROM
store.docs
WHERE
id = current_id ;
--ΠΠΎΠΊΡΠΌΠ΅Π½Ρ Π² ΡΡΠ°ΡΡΡ *** Π½Π΅ ΠΌΠΎΠΆΠ΅ Π΄Π° Π±ΡΠ΄Π΅ ΠΈΠ·ΡΡΠΈΡ
IF current_status_id = 11
THEN
RETURN FALSE ;
ELSE
--ΠΠΎΠΆΠ΅ Π΄Π° ΡΠ΅ ΠΈΠ·ΡΡΠΈΠ΅ Π΄ΠΎΠΊΡΠΌΠ΅Π½Ρ Π² Π΄ΡΡΠ³ΠΈ ΡΡΠ°ΡΡΡΠΈ
RETURN TRUE ;
END IF ;
--Π ΠΏΡΠΎΡΠΈΠ²Π΅Π½ ΡΠ»ΡΡΠ°ΠΉ, Π°ΠΊΠΎ ΡΠΎΠ»ΡΡΠ° Π½Π° ΠΏΠΎΡΡΠ΅Π±ΠΈΡΠ΅Π»Ρ ***
ELSIF current_role_id = 5
THEN
--ΠΡΠΈΡΠΊΠΈ ΡΡΠ°ΡΡΡΠΈ Π½Π° Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ°
RETURN TRUE ;
ELSE
--ΠΡΡΠ³ΠΈ ΠΏΠΎΡΡΠ΅Π±ΠΈΡΠ΅Π»ΠΈ Π½Π΅ ΠΌΠΎΠ³Π°Ρ Π΄Π° ΠΈΠ·ΡΡΠΈΠ²Π°Ρ Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠΈ
RETURN FALSE ;
END IF ;
ELSE
--ΠΠ±Π½ΠΎΠ²ΡΠ²Π°Π½Π΅ΡΠΎ Π½Π° Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ° Π΅ ΡΠ°Π·ΡΠ΅ΡΠ΅Π½ΠΎ
RETURN TRUE ;
END IF ;
RETURN FALSE ;
END
$$ LANGUAGE plpgsql SECURITY DEFINER;
ALTER FUNCTION store.update_with_check( store.docs.id%TYPE , boolean ) OWNER TO store ;
REVOKE EXECUTE ON FUNCTION store.update_with_check( store.docs.id%TYPE , boolean ) FROM public;
GRANT EXECUTE ON FUNCTION store.update_with_check( store.docs.id%TYPE ) TO service_functions;ΠΠΊΠ»ΡΡΠ²Π°Π½Π΅ Π½Π° ΠΏΠΎΠ»ΠΈΡΠΈΠΊΠ°ΡΠ° Row Level Security Π·Π° ΡΠ°Π±Π»ΠΈΡΠ°ΡΠ°.
ENABLE ROW LEVEL SECURITY
ALTER TABLE store.docs ENABLE ROW LEVEL SECURITY ;
CREATE POLICY doc_select ON store.docs FOR SELECT TO service_functions USING ( (SELECT store.check_select(id)) );
CREATE POLICY doc_insert ON store.docs FOR INSERT TO service_functions WITH CHECK ( (SELECT store.check_insert(id)) );
CREATE POLICY docs_delete ON store.docs FOR DELETE TO service_functions USING ( (SELECT store.check_delete(id)) );
CREATE POLICY doc_update_using ON store.docs FOR UPDATE TO service_functions USING ( (SELECT store.update_using(id , is_del )) );
CREATE POLICY doc_update_check ON store.docs FOR UPDATE TO service_functions WITH CHECK ( (SELECT store.update_with_check(id , is_del )) );Π Π΅Π·ΡΠΌΠ΅
Π’ΠΎΠ²Π° ΡΠ°Π±ΠΎΡΠΈ.
ΠΡΠ΅Π΄Π»ΠΎΠΆΠ΅Π½Π°ΡΠ° ΡΡΡΠ°ΡΠ΅Π³ΠΈΡ ΠΏΠΎΠ·Π²ΠΎΠ»ΠΈ ΠΏΡΠ΅ΠΌΠ΅ΡΡΠ²Π°Π½Π΅ΡΠΎ Π½Π° ΡΠ΅Π°Π»ΠΈΠ·Π°ΡΠΈΡΡΠ° Π½Π° ΠΌΠΎΠ΄Π΅Π» Π½Π° ΡΠΎΠ»Ρ ΠΎΡ Π½ΠΈΠ²ΠΎΡΠΎ Π½Π° Π±ΠΈΠ·Π½Π΅Ρ ΡΡΠ½ΠΊΡΠΈΠΈ Π΄ΠΎ Π½ΠΈΠ²ΠΎΡΠΎ Π½Π° ΡΡΡ ΡΠ°Π½Π΅Π½ΠΈΠ΅ Π½Π° Π΄Π°Π½Π½ΠΈ.
Π€ΡΠ½ΠΊΡΠΈΠΈΡΠ΅ ΠΌΠΎΠ³Π°Ρ Π΄Π° Π±ΡΠ΄Π°Ρ ΠΈΠ·ΠΏΠΎΠ»Π·Π²Π°Π½ΠΈ ΠΊΠ°ΡΠΎ ΡΠ°Π±Π»ΠΎΠ½ Π·Π° ΡΠ΅Π°Π»ΠΈΠ·ΠΈΡΠ°Π½Π΅ Π½Π° ΠΏΠΎ-ΡΠ»ΠΎΠΆΠ½ΠΈ ΠΌΠΎΠ΄Π΅Π»ΠΈ Π½Π° ΡΠΊΡΠΈΠ²Π°Π½Π΅ Π½Π° Π΄Π°Π½Π½ΠΈ, Π°ΠΊΠΎ Π±ΠΈΠ·Π½Π΅Ρ ΠΈΠ·ΠΈΡΠΊΠ²Π°Π½ΠΈΡΡΠ° Π³ΠΎ ΠΈΠ·ΠΈΡΠΊΠ²Π°Ρ.
ΠΠ·ΡΠΎΡΠ½ΠΈΠΊ: habr.com
