Compare commits

...

379 Commits

Author SHA1 Message Date
6f37ee4a37 Merge pull request 'agliullov_daniyar_lab_8 is ready' (#412) from agliullov_daniyar_lab_8 into main
Reviewed-on: #412
2025-01-02 13:19:26 +04:00
7f854f8bc2 Merge pull request 'agliullov_daniyar_lab_7 is ready' (#411) from agliullov_daniyar_lab_7 into main
Reviewed-on: #411
2025-01-02 13:19:10 +04:00
36a6b4ea0a Merge pull request 'agliullov_daniyar_lab_4 is ready' (#410) from agliullov_daniyar_lab_4 into main
Reviewed-on: #410
2025-01-02 13:18:59 +04:00
457efe5b11 Merge pull request 'agliullov_daniyar_lab_3 is ready' (#409) from agliullov_daniyar_lab_3 into main
Reviewed-on: #409
2025-01-02 13:18:40 +04:00
9ea823461a Merge pull request 'agliullov_daniyar_lab_2 is ready' (#408) from agliullov_daniyar_lab_2 into main
Reviewed-on: #408
2025-01-02 13:18:15 +04:00
16d3e42d0a Merge pull request 'haliullov_kamil_lab_8' (#407) from haliullov_kamil_lab_8 into main
Reviewed-on: #407
2025-01-02 13:17:59 +04:00
1edaa85f54 Merge pull request 'haliullov_kamil_lab_7' (#406) from haliullov_kamil_lab_7 into main
Reviewed-on: #406
2025-01-02 13:17:46 +04:00
0bd4f66e09 Merge pull request 'haliullov_kamil_lab_6' (#405) from haliullov_kamil_lab_6 into main
Reviewed-on: #405
2025-01-02 13:16:53 +04:00
b1eb9b3a30 Merge pull request 'haliullov_kamil_lab_5' (#404) from haliullov_kamil_lab_5 into main
Reviewed-on: #404
2025-01-02 13:16:41 +04:00
b5282cdecf Merge pull request 'haliullov_kamil_lab_4' (#403) from haliullov_kamil_lab_4 into main
Reviewed-on: #403
2025-01-02 13:16:21 +04:00
679e3e1c99 Merge pull request 'haliullov_kamil_lab_3 fix' (#402) from haliullov_kamil_lab_3_fix into main
Reviewed-on: #402
2025-01-02 13:15:43 +04:00
1a17490e68 Merge pull request 'haliullov_kamil_lab_2 fix' (#401) from haliullov_kamil_lab_2_fix into main
Reviewed-on: #401
2025-01-02 13:15:26 +04:00
16523bb0ff Merge pull request 'karamushko_maxim_lab_8' (#400) from karamushko_maxim_lab_8 into main
Reviewed-on: #400
2025-01-02 13:15:05 +04:00
7a3ad50337 Merge pull request 'karamushko_m_k_lab_7' (#399) from karamushko_maxim_lab_7 into main
Reviewed-on: #399
2025-01-02 13:14:48 +04:00
53b189f2d6 Merge pull request 'nugaev_damir_lab_8' (#398) from nugaev_damir_lab_8 into main
Reviewed-on: #398
2025-01-02 13:14:36 +04:00
45dc09c5ec Merge pull request 'nugaev_damir_lab_7' (#397) from nugaev_damir_lab_7 into main
Reviewed-on: #397
2025-01-02 13:14:23 +04:00
4bc4a94f7d Merge pull request 'nugaev_damir_lab_6' (#396) from nugaev_damir_lab_6 into main
Reviewed-on: #396
2025-01-02 13:13:45 +04:00
93fbd62071 Merge pull request 'nugaev_damir_lab_5' (#395) from nugaev_damir_lab_5 into main
Reviewed-on: #395
2025-01-02 13:13:12 +04:00
b69889337f Merge pull request 'nugaev_damir_lab_4' (#394) from nugaev_damir_lab_4 into main
Reviewed-on: #394
2025-01-02 13:12:58 +04:00
12011d055e Merge pull request 'nugaev_damir_lab_3' (#393) from nugaev_damir_lab_3 into main
Reviewed-on: #393
2025-01-02 13:12:08 +04:00
73a9085729 Merge pull request 'nugaev_damir_lab_2' (#392) from nugaev_damir_lab_2 into main
Reviewed-on: #392
2025-01-02 13:11:49 +04:00
4f5ad51419 Merge pull request 'karamushko_maxim_lab_6' (#391) from karamushko_maxim_lab_6 into main
Reviewed-on: #391
2025-01-02 13:11:14 +04:00
b3bf0c359a Merge pull request 'nugaev_damir_lab_1' (#390) from nugaev_damir_lab_1 into main
Reviewed-on: #390
2025-01-02 13:10:55 +04:00
9c766d828f Merge pull request 'klyushenkova_ksenia_lab_8 is ready' (#389) from klyushenkova_ksenia_lab_8 into main
Reviewed-on: #389
2025-01-02 13:10:18 +04:00
750870c533 Merge pull request 'klyushenkova_ksenia_lab_7 is ready' (#388) from klyushenkova_ksenia_lab_7 into main
Reviewed-on: #388
2025-01-02 13:10:07 +04:00
33f89ef393 Merge pull request 'karamushko_maxim_lab_5' (#387) from karamushko_maxim_lab_5 into main
Reviewed-on: #387
2025-01-02 13:09:46 +04:00
93a5344184 Merge pull request 'karamushko_maxim_lab_4' (#386) from karamushko_maxim_lab_4 into main
Reviewed-on: #386
2025-01-02 13:09:35 +04:00
de1aa91832 Merge pull request 'karamushko_maxim_lab_3' (#385) from karamushko_maxim_lab_3 into main
Reviewed-on: #385
2025-01-02 13:09:14 +04:00
f2adc3ea18 Merge pull request 'karamushko_maxim_lab_2' (#384) from karamushko_maxim_lab_2 into main
Reviewed-on: #384
2025-01-02 13:08:33 +04:00
002d9aa970 Merge pull request 'klyushenkova_ksenia_lab_4 is ready' (#383) from klyushenkova_ksenia_lab_4 into main
Reviewed-on: #383
2025-01-02 13:08:04 +04:00
f19cc6f916 Merge pull request 'klyushenkova_ksenia_lab_6 is ready' (#382) from klyushenkova_ksenia_lab_6 into main
Reviewed-on: #382
2025-01-02 13:07:45 +04:00
9ae1098d6e Merge pull request 'klyushenkova_ksenia_lab_5 is ready' (#381) from klyushenkova_ksenia_lab_5 into main
Reviewed-on: #381
2025-01-02 13:07:22 +04:00
7afc676844 Merge pull request 'klyushenkova_ksenia_lab_3 is ready' (#380) from klyushenkova_ksenia_lab_3 into main
Reviewed-on: #380
2025-01-02 13:07:02 +04:00
a8ab542248 Merge pull request 'klyushenkova_ksenia_lab_2 is ready' (#379) from klyushenkova_ksenia_lab_2 into main
Reviewed-on: #379
2025-01-02 13:06:42 +04:00
0c03a23f0e Merge pull request 'balakhonov_danila_lab_8' (#378) from balakhonov_danila_lab_8 into main
Reviewed-on: #378
2025-01-02 13:05:22 +04:00
57f8eeafd9 Merge pull request 'balakhonov_danila_lab_7' (#377) from balakhonov_danila_lab_7 into main
Reviewed-on: #377
2025-01-02 13:05:09 +04:00
715ed84412 Merge pull request 'balakhonov_danila_lab_6' (#376) from balakhonov_danila_lab_6 into main
Reviewed-on: #376
2025-01-02 13:03:30 +04:00
12d9b33cb4 Merge pull request 'balakhonov_danila_lab_5' (#375) from balakhonov_danila_lab_5 into main
Reviewed-on: #375
2025-01-02 13:03:14 +04:00
7f7b604d9a Merge pull request 'balakhonov_danila_lab_4' (#374) from balakhonov_danila_lab_4 into main
Reviewed-on: #374
2025-01-02 13:02:59 +04:00
3803ea111e Merge pull request 'alkin_ivan_lab_8' (#373) from alkin_ivan_lab_8 into main
Reviewed-on: #373
2025-01-02 13:02:29 +04:00
bdcfce8a48 Merge pull request 'alkin_ivan_lab_7' (#372) from alkin_ivan_lab_7 into main
Reviewed-on: #372
2025-01-02 12:58:13 +04:00
5d7794c670 Merge pull request 'novopolcev_alexander_lab_8' (#371) from novopolcev_alexander_lab_8_fix into main
Reviewed-on: #371
2025-01-02 12:57:58 +04:00
b00d0d1141 Merge pull request 'novopolcev_alexander_lab_7' (#370) from novopolcev_alexander_lab_7_fix into main
Reviewed-on: #370
2025-01-02 12:57:30 +04:00
86b206c78b Merge pull request 'novopolcev_alexander_lab_6' (#369) from novopolcev_alexander_lab_6_fix into main
Reviewed-on: #369
2025-01-02 12:57:15 +04:00
0055e97ec1 Merge pull request 'novopolcev_alexander_lab_5' (#368) from novopolcev_alexander_lab_5_fix into main
Reviewed-on: #368
2025-01-02 12:57:00 +04:00
2d08a4d6a4 Merge pull request 'novopolcev_alexander_lab_4' (#367) from novopolcev_alexander_lab_4_fix into main
Reviewed-on: #367
2025-01-02 12:56:39 +04:00
9127f23fbb Merge pull request 'alkin_ivan_lab_6' (#366) from alkin_ivan_lab_6 into main
Reviewed-on: #366
2025-01-02 12:56:08 +04:00
4ae3262291 Merge pull request 'alkin_ivan_lab_5' (#365) from alkin_ivan_lab_5 into main
Reviewed-on: #365
2025-01-02 12:55:55 +04:00
4f21ceda8e Merge pull request 'gapon_nikolay_lab_1' (#364) from gapon_nikolay_lab_1 into main
Reviewed-on: #364
2025-01-02 12:55:25 +04:00
53552932ce Merge pull request 'alkin_ivan_lab_4' (#363) from alkin_ivan_lab_4 into main
Reviewed-on: #363
2025-01-02 12:55:07 +04:00
05e6ca388e Merge pull request 'vasina_ekaterina_lab_2_fix' (#361) from vasina_ekaterina_lab_2 into main
Reviewed-on: #361
2025-01-02 12:54:15 +04:00
ae8f698009 Merge pull request 'vasina_ekaterina_lab_5_fix' (#360) from vasina_ekaterina_lab_5 into main
Reviewed-on: #360
2025-01-02 12:53:12 +04:00
359e97bc7c Merge pull request 'melnikov_igor_lab_8_fix (без лишних файлов)' (#359) from melnikov_igor_lab_8 into main
Reviewed-on: #359
2025-01-02 12:53:00 +04:00
50644a5f79 Merge pull request 'melnikov_igor_lab_7_fix (без лишних файлов)' (#358) from melnikov_igor_lab_7 into main
Reviewed-on: #358
2025-01-02 12:52:23 +04:00
01364ee57f Merge pull request 'melnikov_igor_lab_6_fix (без лишних файлов)' (#357) from melnikov_igor_lab_6 into main
Reviewed-on: #357
2025-01-02 12:52:11 +04:00
00336b2f38 Merge pull request 'melnikov_igor_lab_4_fix (без лишних файлов)' (#356) from melnikov_igor_lab_4 into main
Reviewed-on: #356
2025-01-02 12:50:51 +04:00
e2c2ca8fed Merge pull request 'chernyshov_nikita_lab_8 is ready' (#355) from chernyshov_nikita_lab_8 into main
Reviewed-on: #355
2025-01-02 12:50:17 +04:00
306bd24eda Merge pull request 'chernyshov_nikita_lab_7 is ready' (#354) from chernyshov_nikita_lab_7 into main
Reviewed-on: #354
2025-01-02 12:50:05 +04:00
0c383af3af Merge pull request 'chernyshov_nikita_lab_6 is ready' (#353) from chernyshov_nikita_lab_6 into main
Reviewed-on: #353
2025-01-02 12:49:52 +04:00
94c6edb7dd Merge pull request 'melnikov_igor_lab_3_fix (без лишних файлов)' (#352) from melnikov_igor_lab_3 into main
Reviewed-on: #352
2025-01-02 12:47:13 +04:00
68c88f44a5 Merge pull request 'melnikov_igor_lab_2_fix (без лишних файлов)' (#351) from melnikov_igor_lab_2 into main
Reviewed-on: #351
2025-01-02 12:46:54 +04:00
5849266f9b Merge pull request 'chernyshov_nikita_lab_5 is ready' (#350) from chernyshov_nikita_lab_5 into main
Reviewed-on: #350
2025-01-02 12:46:30 +04:00
b9c0a95773 Merge pull request 'chernyshov_nikita_lab_4 is ready' (#349) from chernyshov_nikita_lab_4 into main
Reviewed-on: #349
2025-01-02 12:46:14 +04:00
e857052bf3 Merge pull request 'chernyshov_nikita_lab_3 is ready' (#348) from chernyshov_nikita_lab_3 into main
Reviewed-on: #348
2025-01-02 12:45:23 +04:00
f5824b2bc7 Merge pull request 'melnikov_igor_lab_1_fix (без лишних файлов)' (#347) from melnikov_igor_lab_1 into main
Reviewed-on: #347
2025-01-02 12:44:53 +04:00
5f2c138327 Merge pull request 'chernyshov_nikita_lab_2 is ready (fixed не могу поставить название без fix, тк случайно слил)' (#346) from chernyshov_nikita_lab_2_fix into main
Reviewed-on: #346
2025-01-02 12:44:37 +04:00
0ea6601720 Merge pull request 'davydov_yuriy_lab_2' (#291) from davydov_yuriy_lab_2 into main
Reviewed-on: #291
2025-01-02 12:41:45 +04:00
63f5b075cc Merge pull request 'lab_1' (#290) from davydov_yuriy_lab_1 into main
Reviewed-on: #290
2025-01-02 12:41:31 +04:00
35fa891625 Merge pull request 'putilin_pavel_lab_5' (#253) from putilin_pavel_lab_5 into main
Reviewed-on: #253
2025-01-02 12:39:54 +04:00
bc89f638a7 Merge pull request 'kosheev_maksim_lab_8' (#251) from kosheev_maksim_lab_8 into main
Reviewed-on: #251
2025-01-02 12:38:39 +04:00
3ecb3ad391 Merge pull request 'Bazunov Andrew Lab 6 ready' (#236) from bazunov_andrew_lab_6 into main
Reviewed-on: #236
2025-01-02 12:36:42 +04:00
673a3334a3 Merge pull request 'kosheev_maksim_lab_3 is ready' (#230) from kosheev_maksim_lab_3 into main
Reviewed-on: #230
2025-01-02 12:36:08 +04:00
0237cf924b Merge pull request 'aleikin_artem_lab_8' (#229) from aleikin_artem_lab_8 into main
Reviewed-on: #229
2025-01-02 12:35:44 +04:00
d47ddc935d Merge pull request 'aleikin_artem_lab_7' (#228) from aleikin_artem_lab_7 into main
Reviewed-on: #228
2025-01-02 12:35:29 +04:00
b943beaecb Merge pull request 'aleikin_artem_lab_6' (#227) from aleikin_artem_lab_6 into main
Reviewed-on: #227
2025-01-02 12:34:53 +04:00
c7d6b2d0de Merge pull request 'aleikin_artem_lab_5' (#226) from aleikin_artem_lab_5 into main
Reviewed-on: #226
2025-01-02 12:34:23 +04:00
f33aa45d66 Merge pull request 'aleikin_artem_lab_3' (#224) from aleikin_artem_lab_3 into main
Reviewed-on: #224
2025-01-02 12:33:55 +04:00
e73b94f64d Merge pull request 'aleikin_artem_lab_2' (#223) from aleikin_artem_lab_2 into main
Reviewed-on: #223
2025-01-02 12:33:34 +04:00
75c2caf86d Merge pull request 'agliullov_daniyar_lab_6 is ready' (#183) from agliullov_daniyar_lab_6 into main
Reviewed-on: #183
2025-01-02 12:33:09 +04:00
6d19011cf8 Merge pull request 'agliullov daniyar lab 5 is ready' (#181) from agliullov_daniyar_lab_5 into main
Reviewed-on: #181
2025-01-02 12:32:23 +04:00
Darya
c5a2c12cf0 agliullov_daniyar_lab_8 is ready 2024-12-31 08:41:24 +04:00
Darya
4f69634e99 agliullov_daniyar_lab_7 is ready 2024-12-31 08:35:11 +04:00
Darya
54b0e90270 agliullov_daniyar_lab_4 is ready 2024-12-31 08:31:05 +04:00
Darya
4215e22d17 agliullov_daniyar_lab_3 is ready 2024-12-31 08:10:29 +04:00
Darya
54f8d19f2d agliullov_daniyar_lab_2 is ready 2024-12-30 22:42:02 +04:00
Darya
5bcc9d23cc agliullov_daniyar_lab_6 is ready 2024-12-30 21:35:39 +04:00
Darya
da6f03db1f agliullov_daniyar_lab_5 is ready 2024-12-30 21:32:17 +04:00
Kamil Haliullov
dad0726a9a haliullov_kamil_lab_8 is ready 2024-12-29 16:26:10 +04:00
Kamil Haliullov
0c8687a2c1 haliullov_kamil_lab_7 is ready 2024-12-29 16:11:28 +04:00
Kamil Haliullov
a4a572478e haliullov_kamil_lab_6 is ready 2024-12-29 15:29:27 +04:00
Kamil Haliullov
26df1a6c2b haliullov_kamil_lab_5 is ready 2024-12-29 14:37:32 +04:00
Kamil Haliullov
04020bd397 haliullov_kamil_lab_4 is ready 2024-12-29 13:50:35 +04:00
Kamil Haliullov
da6374e061 haliullov_kamil_lab_3 is ready 2024-12-29 12:02:48 +04:00
Kamil Haliullov
fa345ff61b haliullov_kamil_lab_2 is ready 2024-12-29 11:43:21 +04:00
=
bde59e6014 karamushko_maxim_lab_8 2024-12-25 17:49:26 +04:00
=
b895906d0d karamushko_m_k_lab_7 2024-12-25 17:36:07 +04:00
12f482474a nugaev_damir_lab_8 is ready 2024-12-25 11:59:10 +04:00
e8f75e773d nugaev_damir_lab_7 is ready 2024-12-25 11:57:38 +04:00
6bb9fc2fa2 nugaev_damir_lab_6 is ready 2024-12-25 11:55:44 +04:00
f99e79010a nugaev_damir_lab_5 is ready 2024-12-25 11:50:55 +04:00
bd58093a8b nugaev_damir_lab_4 is ready 2024-12-25 11:41:25 +04:00
4867a960cf nugaev_damir_lab_3 is ready 2024-12-25 11:12:11 +04:00
5cec7a3627 nugaev_damir_lab_2 is ready 2024-12-25 10:02:12 +04:00
=
8f32eec18a ридми 2024-12-24 22:19:38 +04:00
Pineapple
07d4182f7d klyushenkova_ksenia_lab_8 is ready 2024-12-23 22:32:46 +04:00
Pineapple
001154258b klyushenkova_ksenia_lab_7 is ready 2024-12-23 21:18:49 +04:00
=
f2d6482374 ридми 2024-12-23 19:48:28 +04:00
=
cb72fbc717 опечатка 2024-12-23 19:30:44 +04:00
=
5e17c0d719 ридми 2024-12-23 19:27:47 +04:00
=
4d255f8d2c лаба 3 2024-12-23 18:08:28 +04:00
=
710dab06c5 ридми 2024-12-23 16:17:40 +04:00
=
03cbb37caf 6 лаба без ридми 2024-12-23 15:59:15 +04:00
=
f4db3f2d43 5 лаба без ридми 2024-12-23 15:53:45 +04:00
=
0fd7d90e27 4 лаба без ридми 2024-12-23 15:45:25 +04:00
Pineapple
699ba7a632 klyushenkova_ksenia_lab_4 is ready 2024-12-21 00:49:48 +04:00
Pineapple
bbbe680013 klyushenkova_ksenia_lab_6 is ready 2024-12-20 22:02:59 +04:00
Pineapple
17ec016a38 klyushenkova_ksenia_lab_5 is ready 2024-12-20 21:26:35 +04:00
Pineapple
1a5135d0f8 fix 2024-12-20 18:51:22 +04:00
S3
f4748631c8 davydov_yuriy_lab_2 2024-12-20 11:50:51 +03:00
S3
b53a887498 davydov_yuriy_lab_1 2024-12-20 11:48:14 +03:00
Pineapple
a4d674af99 klyushenkova_ksenia_lab_3 is ready 2024-12-20 10:20:16 +04:00
a088530e65 Видео отчет 2024-12-20 01:00:01 +04:00
Pineapple
ede7e1c685 klyushenkova_ksenia_lab_2 is ready 2024-12-20 00:24:54 +04:00
1b6470a2ab add: 8 лабораторная работа 2024-12-19 19:38:35 +04:00
93f006b7bc add: 7 лабораторная работа 2024-12-19 18:48:45 +04:00
8f7466660b add: 6 лабораторная работа 2024-12-19 18:05:01 +04:00
f49331d067 add: 5 лабораторная работа 2024-12-19 16:15:12 +04:00
1046441e1f add: 4 лабораторная работа 2024-12-19 05:09:34 +04:00
69e186ccb8 alkin_ivan_lab_8 2024-12-19 00:09:30 +04:00
374cd9a599 alkin_ivan_lab_7 2024-12-19 00:05:13 +04:00
6e00a994c0 novopolcev_alexander_lab_8 is ready 2024-12-18 12:54:40 +04:00
969f72549f novopolcev_alexander_lab_7 is ready 2024-12-18 12:50:10 +04:00
1dc6265146 novopolcev_alexander_lab_6 is ready 2024-12-18 12:38:03 +04:00
1a8105cd2f novopolcev_alexander_lab_5 is ready 2024-12-18 12:31:16 +04:00
30120627a1 novopolcev_alexander_lab_4 is ready 2024-12-18 12:20:36 +04:00
9aaecc3b09 Merge remote-tracking branch 'origin/alkin_ivan_lab_6' into alkin_ivan_lab_6 2024-12-17 23:21:02 +04:00
98dadb9dee alkin_ivan_lab_6 2024-12-17 23:20:50 +04:00
146267863c alkin_ivan_lab_6 2024-12-17 23:19:04 +04:00
d455a369c3 nugaev_damir_lab_1 is ready 2024-12-17 23:07:56 +04:00
6c4b6c4d97 alkin_ivan_lab_5 2024-12-17 22:32:25 +04:00
e40d6194b7 kosheev_maksim_lab_8 is ready точно 2024-12-17 21:31:05 +04:00
d516d83034 Merge branch 'kosheev_maksim_lab_3' of https://git.is.ulstu.ru/Alexey/DAS_2024_1 into kosheev_maksim_lab_3 2024-12-17 21:19:41 +04:00
b40bb17859 kosheev_maksim_lab_3 is ready точно 2024-12-17 21:16:41 +04:00
481a7cde5d revert ad09512f70
revert kosheev_maksim_lab_3 is ready2
2024-12-17 21:06:47 +04:00
ad09512f70 kosheev_maksim_lab_3 is ready2 2024-12-17 20:57:11 +04:00
99d7f1e859 gapon first lab 2024-12-17 02:07:41 +04:00
68f4f1ef98 gapon first lab 2024-12-17 01:50:25 +04:00
1fde600d92 gapon first lab 2024-12-17 01:29:01 +04:00
da819bb6c4 alkin_ivan_lab_4 2024-12-16 22:52:27 +04:00
61469d9bb1 vasina_ekaterina_lab_2_fix 2024-12-15 19:13:34 +04:00
e3bf7794a0 vasina_ekaterina_lab_5_fix 2024-12-15 18:04:02 +04:00
26526859a2 . 2024-12-15 17:35:32 +04:00
5a9b0dcfa4 . 2024-12-15 17:33:14 +04:00
c63306ec46 . 2024-12-15 17:29:59 +04:00
a9c9e3469e . 2024-12-15 17:19:53 +04:00
Никита Чернышов
dd58bf5357 chernyshov_nikita_lab_8 is ready 2024-12-15 17:16:57 +04:00
Никита Чернышов
69c86ef744 chernyshov_nikita_lab_7 is ready 2024-12-15 17:15:35 +04:00
Никита Чернышов
7f775233bf chernyshov_nikita_lab_6 is ready 2024-12-15 17:13:28 +04:00
9fb5b7e103 . 2024-12-15 17:11:29 +04:00
Никита Чернышов
36dfe3c47a chernyshov_nikita_lab_5 is ready 2024-12-15 17:10:21 +04:00
Никита Чернышов
44907b5438 chernyshov_nikita_lab_4 is ready 2024-12-15 17:03:30 +04:00
205d361924 . 2024-12-15 16:56:11 +04:00
Никита Чернышов
f3d2014ee9 chernyshov_nikita_lab_3 is ready 2024-12-15 16:51:11 +04:00
dcf4b3c04f . 2024-12-15 16:42:26 +04:00
Никита Чернышов
5dd9e26f07 chernyshov_nikita_lab_3 is ready 2024-12-15 16:26:38 +04:00
Никита Чернышов
4fa59a5637 chernyshov_nikita_lab_2 is ready (fixed не могу поставить название папки без fix, тк случайно слил) 2024-12-15 16:16:46 +04:00
8feb62fb64 aleikin_artem_lab_2 is ready 2024-12-15 15:49:09 +04:00
151df463ff aleikin_artem_lab_8 is ready 2024-12-15 15:47:30 +04:00
7cfb204c3d aleikin_artem_lab_7 is ready 2024-12-15 15:46:47 +04:00
6de2322739 aleikin_artem_lab_6 is ready 2024-12-15 15:45:59 +04:00
32b23e3e8e aleikin_artem_lab_5 is ready 2024-12-15 15:43:34 +04:00
19ef5884cf aleikin_artem_lab_3 is ready 2024-12-15 15:38:42 +04:00
b01e61dead aleikin_artem_lab_3 is ready 2024-12-15 15:30:54 +04:00
60e2c7996c Merge pull request 'nikolaeva_yana_lab_3' (#337) from nikolaeva_yana_lab_3 into main
Reviewed-on: #337
2024-12-15 14:54:45 +04:00
95d2554f95 Merge pull request 'kurushina_ksenia_lab_3' (#331) from kurushina_ksenia_lab_3 into main
Reviewed-on: #331
2024-12-15 14:52:52 +04:00
d9c57f2e8e Merge pull request 'fadeeva_nastya_laab_8' (#330) from fadeeva_nastya_lab_8 into main
Reviewed-on: #330
2024-12-15 14:52:33 +04:00
158c0670a4 Merge pull request 'ismailov_rovshan_lab_8 is ready' (#329) from ismailov_rovshan_lab_8 into main
Reviewed-on: #329
2024-12-15 14:49:58 +04:00
df8cf26c03 Merge pull request 'ismailov_rovshan_lab_7 is ready' (#328) from ismailov_rovshan_lab_7 into main
Reviewed-on: #328
2024-12-15 14:49:13 +04:00
9495f5cd12 Merge pull request 'ismailov_rovshan_lab_6 is ready' (#327) from ismailov_rovshan_lab_6 into main
Reviewed-on: #327
2024-12-15 14:48:54 +04:00
9e71d368c8 Merge pull request 'ismailov_rovshan_lab_5 is ready' (#326) from ismailov_rovshan_lab_5 into main
Reviewed-on: #326
2024-12-15 14:48:34 +04:00
5c8dbc4ae3 Merge pull request 'ismailov_rovshan_lab_4 is ready' (#325) from ismailov_rovshan_lab_4 into main
Reviewed-on: #325
2024-12-15 14:48:19 +04:00
e89eb8fc8d Merge pull request 'melnikov_igor_lab_5' (#321) from melnikov_igor_lab_5 into main
Reviewed-on: #321
2024-12-15 14:46:53 +04:00
a74ea43db8 Merge pull request 'fadeeva_nastya_lab_7' (#316) from fadeeva_nastya_lab_7 into main
Reviewed-on: #316
2024-12-15 14:44:23 +04:00
f1afb24c8b Merge pull request 'fadeeva_nastya_lab_6' (#315) from fadeeva_nastya_lab_6 into main
Reviewed-on: #315
2024-12-15 14:43:59 +04:00
2c12d97510 Merge pull request 'fadeeva_nastya_lab_5' (#314) from fadeeva_nastya_lab_5 into main
Reviewed-on: #314
2024-12-15 14:43:43 +04:00
00c170a00c Merge pull request 'fadeeva_nastya_lab_4' (#313) from fadeeva_nastya_lab_4 into main
Reviewed-on: #313
2024-12-15 14:43:22 +04:00
c620f10752 Merge pull request 'ismailov_rovshan_lab_3' (#312) from ismailov_rovshan_lab_3 into main
Reviewed-on: #312
2024-12-15 14:41:48 +04:00
e46d33bc4d Merge pull request 'bondarenko_max_lab_8' (#311) from bondarenko_max_lab_8 into main
Reviewed-on: #311
2024-12-15 14:40:29 +04:00
1a11fca0e4 Merge pull request 'bondarenko_max_lab_7' (#310) from bondarenko_max_lab_7 into main
Reviewed-on: #310
2024-12-15 14:39:55 +04:00
db9395340c Merge pull request 'fadeeva_nastya_lab_3 is ready' (#309) from fadeeva_nastya_lab_3 into main
Reviewed-on: #309
2024-12-15 14:39:41 +04:00
9485011484 Merge pull request 'bondarenko_max_lab_6' (#308) from bondarenko_max_lab_6 into main
Reviewed-on: #308
2024-12-15 14:39:12 +04:00
75c4fde178 Merge pull request 'bondarenko_max_lab_5' (#307) from bondarenko_max_lab_5 into main
Reviewed-on: #307
2024-12-15 14:37:58 +04:00
93f689f155 Merge pull request 'vasina_ekaterina_lab_8' (#301) from vasina_ekaterina_lab_8 into main
Reviewed-on: #301
2024-12-15 14:35:41 +04:00
6e224cf288 Merge pull request 'vasina_ekaterina_lab_7' (#300) from vasina_ekaterina_lab_7 into main
Reviewed-on: #300
2024-12-15 14:35:19 +04:00
a9fba22e19 Merge pull request 'vasina_ekaterina_lab_6' (#299) from vasina_ekaterina_lab_6 into main
Reviewed-on: #299
2024-12-15 14:35:05 +04:00
d7008ca1e5 Merge pull request 'vasina_ekaterina_lab_4' (#297) from vasina_ekaterina_lab_4 into main
Reviewed-on: #297
2024-12-15 14:33:15 +04:00
3fb94595bc Merge pull request 'bondarenko_max_lab_4' (#295) from bondarenko_max_lab_4 into main
Reviewed-on: #295
2024-12-15 14:23:54 +04:00
14de0603a8 Merge pull request 'vasina_ekaterina_lab_3' (#294) from vasina_ekaterina_lab_3 into main
Reviewed-on: #294
2024-12-15 14:23:19 +04:00
31346e7082 Merge pull request 'fadeeva_nastya_lab_2 is ready' (#293) from fadeeva_nastya_lab_2 into main
Reviewed-on: #293
2024-12-15 14:23:01 +04:00
fc6f211197 Merge pull request 'alkin_ivan_lab_3' (#292) from alkin_ivan_lab_3 into main
Reviewed-on: #292
2024-12-15 14:22:40 +04:00
b5944df141 Merge pull request 'chernyshov_nikita_lab_2' (#289) from chernyshov_nikita_lab_2 into main 2024-12-15 14:13:35 +04:00
Никита Чернышов
1812ac159e Chernyshov Nikita Lab2 is Ready 2024-12-15 14:13:35 +04:00
fa4c181ac0 Merge pull request 'Chernyshov Nikita Lab1 is Ready' (#270) from chernyshov_nikita_lab_1 into main
Reviewed-on: #270
2024-12-15 14:12:49 +04:00
4343669b5a Merge pull request 'alkin_ivan_lab_2' (#271) from alkin_ivan_lab_2 into main
Reviewed-on: #271
2024-12-15 14:12:18 +04:00
d95e2a9d3a Merge pull request 'nikolaeva_yana_lab_2' (#272) from nikolaeva_yana_lab_2 into main
Reviewed-on: #272
2024-12-15 14:12:00 +04:00
2f9c00b03b Merge pull request 'novopolcev_alexander_lab_3' (#281) from novopolcev_alexander_lab_3 into main
Reviewed-on: #281
2024-12-15 14:06:49 +04:00
51af80f3ad Merge pull request 'novopolcev_alexander_lab_2' (#249) from novopolcev_alexander_lab_2 into main
Reviewed-on: #249
2024-12-15 14:01:47 +04:00
1fffeaa7e9 Merge pull request 'putilin_pavel_lab_4' (#252) from putilin_pavel_lab_4 into main
Reviewed-on: #252
2024-12-15 13:55:43 +04:00
a463ec8241 Merge pull request 'afanasev_dmitry_lab_8 is ready' (#244) from afanasev_dmitry_lab_8 into main
Reviewed-on: #244
2024-12-15 13:55:04 +04:00
7a1ba7aa9a Merge pull request 'polevoy_sergey_lab_8' (#246) from polevoy_sergey_lab_8 into main
Reviewed-on: #246
2024-12-15 13:54:40 +04:00
08cc99b0fc Merge pull request 'nikolaeva_yana_lab_1 is ready' (#254) from nikolaeva_yana_lab_1 into main
Reviewed-on: #254
2024-12-15 13:53:35 +04:00
6556e21d9a Merge pull request 'kurushina_ksenia_lab_1' (#256) from kurushina_ksenia_lab_1 into main
Reviewed-on: #256
2024-12-15 13:52:48 +04:00
7accd18aea Merge pull request 'fadeeva_nastya_lab_1' (#263) from fadeeva_nastya_lab_1 into main
Reviewed-on: #263
2024-12-15 13:41:31 +04:00
bfc6d8bb95 Merge pull request 'kurushina_ksenia_lab_2' (#261) from kurushina_ksenia_lab_2 into main
Reviewed-on: #261
2024-12-15 13:40:51 +04:00
cdd0a7b7c9 Merge pull request 'bondarenko_max_lab_2' (#231) from bondarenko_max_lab_2 into main
Reviewed-on: #231
2024-12-15 13:38:02 +04:00
27850eb504 Merge pull request 'haliullov_kamil_lab_1' (#232) from haliullov_kamil_lab_1 into main
Reviewed-on: #232
2024-12-15 13:37:40 +04:00
6feebae507 Merge pull request 'karamushko_maxim_lab1' (#234) from karamushko_maxim_lab_1 into main
Reviewed-on: #234
2024-12-15 13:36:09 +04:00
1648f5995a Merge pull request 'kalyshev_yan_lab_6' (#235) from kalyshev_yan_lab_6 into main
Reviewed-on: #235
2024-12-15 13:35:48 +04:00
bf8d06a85d Merge pull request 'bazunov andrew lab 8 ready' (#239) from bazunov_andrew_lab_8 into main
Reviewed-on: #239
2024-12-15 13:35:08 +04:00
de0f1780c7 Merge pull request 'afanasev_dmitry_lab_6 is ready' (#242) from afanasev_dmitry_lab_6 into main
Reviewed-on: #242
2024-12-15 13:33:07 +04:00
e54fd14090 Merge pull request 'minhasapov_ruslan_lab_6' (#211) from minhasapov_ruslan_lab_6 into main
Reviewed-on: #211
2024-12-15 13:32:11 +04:00
ce580c4572 Merge pull request 'minhasapov_ruslan_lab_5' (#210) from minhasapov_ruslan_lab_5 into main
Reviewed-on: #210
2024-12-15 13:31:35 +04:00
84f32a0993 Merge pull request 'ismailov_rovshan_lab_2 is ready' (#222) from ismailov_rovshan_lab_2 into main
Reviewed-on: #222
2024-12-15 13:30:41 +04:00
7a36b2b0af Merge pull request 'presnyakova_victoria_lab_6' (#219) from presnyakova_victoria_lab_6 into main
Reviewed-on: #219
2024-12-15 13:30:18 +04:00
02d6165c7e Merge pull request 'polevoy_sergey_lab_6' (#218) from polevoy_sergey_lab_6 into main
Reviewed-on: #218
2024-12-15 13:30:03 +04:00
18c838d58c Merge pull request 'polevoy_sergey_lab_5' (#215) from polevoy_sergey_lab_5 into main
Reviewed-on: #215
2024-12-15 13:29:40 +04:00
9d6529c11d Merge pull request 'rogashova_ekaterina_lab_6' (#214) from rogashova_ekaterina_lab_6 into main
Reviewed-on: #214
2024-12-15 13:29:18 +04:00
1dc61d09d4 Merge pull request 'artamonova_tatyana_lab_6 is ready' (#205) from artamonova_tatyana_lab_6 into main
Reviewed-on: #205
2024-12-15 13:27:00 +04:00
84edc080bd Merge pull request 'artamonova_tatyana_lab_7 is ready' (#206) from artamonova_tatyana_lab_7 into main
Reviewed-on: #206
2024-12-15 13:26:41 +04:00
885ad1ffec Merge pull request 'artamonova_tatyana_lab_8 is ready' (#207) from artamonova_tatyana_lab_8 into main
Reviewed-on: #207
2024-12-15 13:26:25 +04:00
4b1c9666a0 Merge pull request 'polevoy_sergey_lab_4' (#208) from polevoy_sergey_lab_4 into main
Reviewed-on: #208
2024-12-15 13:23:01 +04:00
135f79e158 Merge pull request 'minhasapov_ruslan_lab_4' (#209) from minhasapov_ruslan_lab_4 into main
Reviewed-on: #209
2024-12-15 13:22:35 +04:00
62b20a244e Merge pull request 'minhasapov_ruslan_lab_8' (#213) from minhasapov_ruslan_lab_8 into main
Reviewed-on: #213
2024-12-15 13:21:15 +04:00
e8f3643fd1 Merge pull request 'minhasapov_ruslan_lab_7' (#212) from minhasapov_ruslan_lab_7 into main
Reviewed-on: #212
2024-12-15 13:20:57 +04:00
2a388e9788 Merge pull request 'rogashova_ekaterina_lab_7' (#216) from rogashova_ekaterina_lab_7 into main
Reviewed-on: #216
2024-12-15 13:20:37 +04:00
3be8ceb996 Merge pull request 'rogashova_ekaterina_lab_8' (#217) from rogashova_ekaterina_lab_8 into main
Reviewed-on: #217
2024-12-15 13:20:08 +04:00
84786b8531 Merge pull request 'presnyakova_victoria_lab_7' (#220) from presnyakova_victoria_lab_7 into main
Reviewed-on: #220
2024-12-15 13:18:44 +04:00
6b370579f8 Merge pull request 'presnyakova_victoria_lab_8' (#221) from presnyakova_victoria_lab_8 into main
Reviewed-on: #221
2024-12-15 13:18:29 +04:00
a608e3a24b Merge pull request 'aleikin_artem_lab4' (#225) from aleikin_artem_lab4 into main
Reviewed-on: #225
2024-12-15 13:17:00 +04:00
4f7d9d4ba1 Merge pull request 'bondarenko_max_lab_3' (#233) from bondarenko_max_lab_3 into main
Reviewed-on: #233
2024-12-15 13:12:06 +04:00
547212dca6 Merge pull request 'bazunov andrew lab 7 ready' (#238) from bazunov_andrew_lab_7 into main
Reviewed-on: #238
2024-12-15 13:10:45 +04:00
58f4127148 Merge pull request 'putilin_pavel_lab_3' (#241) from putilin_pavel_lab_3 into main
Reviewed-on: #241
2024-12-15 13:10:28 +04:00
f12a88ed52 Merge pull request 'afanasev_dmitry_lab_7 is ready' (#243) from afanasev_dmitry_lab_7 into main
Reviewed-on: #243
2024-12-15 13:09:46 +04:00
e3979acf4d Merge pull request 'polevoy_sergey_lab_7' (#245) from polevoy_sergey_lab_7 into main
Reviewed-on: #245
2024-12-15 13:08:23 +04:00
8640f9e8fc Merge pull request 'kosheev_maksim_lab_4' (#247) from kosheev_maksim_lab_4 into main
Reviewed-on: #247
2024-12-15 13:08:03 +04:00
c26a7b8d97 Merge pull request 'kosheev_maksim_lab_7' (#250) from kosheev_maksim_lab_7 into main
Reviewed-on: #250
2024-12-15 13:04:27 +04:00
29320bae24 Merge pull request 'novopolcev_alexander_lab_1' (#248) from novopolcev_alexander_lab_1 into main
Reviewed-on: #248
2024-12-15 13:03:47 +04:00
5449675d39 Merge pull request 'artamonova_tatyana_lab_5 is ready' (#204) from artamonova_tatyana_lab_5 into main
Reviewed-on: #204
2024-12-15 12:53:40 +04:00
b895c806e7 Merge pull request 'rogashova_ekaterina_lab_5' (#203) from rogashova_ekaterina_lab_5 into main
Reviewed-on: #203
2024-12-15 12:52:53 +04:00
920dec1ede Merge pull request 'polevoy_sergey_lab_3' (#202) from polevoy_sergey_lab_3 into main
Reviewed-on: #202
2024-12-15 12:52:33 +04:00
fdba824237 Merge pull request 'minhasapov_ruslan_lab_3' (#201) from minhasapov_ruslan_lab_3 into main
Reviewed-on: #201
2024-12-15 12:51:37 +04:00
yasha
10a12d8da0 fadeeva_nastya_laab_8 2024-12-15 08:54:42 +04:00
25b2a2c481 ismailov_rovshan_lab_8 is ready 2024-12-15 07:33:40 +04:00
c699558126 ismailov_rovshan_lab_7 is ready 2024-12-15 07:30:26 +04:00
4226945dca ismailov_rovshan_lab_6 is ready 2024-12-15 07:24:34 +04:00
239da31a0d ismailov_rovshan_lab_5 is ready 2024-12-15 06:47:19 +04:00
693c6bd91d ismailov_rovshan_lab_4 is ready_fix 2024-12-15 05:52:18 +04:00
31c074f6bb ismailov_rovshan_lab_4 is ready 2024-12-15 05:45:25 +04:00
21c4aa5f5e . 2024-12-15 03:37:34 +04:00
yasha
75b27fd1f5 fadeeva_nastya_lab_7 2024-12-15 02:13:35 +04:00
yasha
05293340a3 fadeeva_nastya_lab_6 2024-12-15 01:36:56 +04:00
yasha
ef4acd4ec6 fadeeva_nastya_lab_5 2024-12-15 01:24:02 +04:00
yasha
356bb54247 fadeeva_nastya_lab_4 2024-12-15 01:08:36 +04:00
c9b601dda5 ismailov_rovshan_lab_3 is ready 2024-12-14 02:58:05 +04:00
a3dc157784 ismailov_rovshan_lab_3 is ready 2024-12-14 02:55:05 +04:00
c06d3e291b bondarenko_max_lab_8_is_ready 2024-12-14 02:03:56 +04:00
4c83bd29bf bondarenko_max_lab_7_is_ready 2024-12-14 01:37:52 +04:00
yasha
a3827d5b4b fadeeva_nastya_lab_3 is ready 2024-12-14 01:21:48 +04:00
b64b354a74 bondarenko_max_lab_5_is_ready 2024-12-14 01:13:43 +04:00
8ef7197268 bondarenko_max_lab_6_is_ready 2024-12-14 01:03:28 +04:00
8e5776d104 bondarenko_max_lab_5 2024-12-13 23:52:37 +04:00
25f8ce44eb bondarenko_max_lab_5_is_ready 2024-12-13 23:51:05 +04:00
a25c242bda bondarenko_max_lab_4_is_ready 2024-12-13 22:49:58 +04:00
429ab85f80 bondarenko_max_lab_4_is_ready 2024-12-13 22:46:31 +04:00
681b9b4c00 bondarenko_max_lab_4_is_ready 2024-12-13 22:44:20 +04:00
ae4ba4c88d vasina_ekaterina_lab_8 is ready 2024-12-13 18:41:44 +04:00
91b02cece8 vasina_ekaterina_lab_7 is ready 2024-12-13 18:40:29 +04:00
fb3994fcab vasina_ekaterina_lab_6 is ready 2024-12-13 18:37:07 +04:00
af77bb1076 vasina_ekaterina_lab_4 is ready 2024-12-13 18:16:02 +04:00
3fb0096475 bondarenko_max_lab_4 is done 2024-12-13 05:48:37 +04:00
752036c0ae vasina_ekaterina_lab_3 is ready 2024-12-13 03:02:14 +04:00
yasha
544d4c2714 fadeeva_nastya_lab_2 is ready 2024-12-13 00:31:02 +04:00
5247783708 alkin_ivan_lab_3 2024-12-12 22:44:53 +04:00
7e69d49c4d Revert "alkin_ivan_lab_1"
This reverts commit b38e7552
2024-12-12 22:16:56 +04:00
753e2f6593 Revert "alkin_ivan_lab_1"
This reverts commit ee80d0ffdd.
2024-12-12 22:12:41 +04:00
055bb25d6b novopolcev_alexander_lab_3 is ready 2024-12-11 14:11:10 +04:00
e760640200 bondarenko_max_lab_4 2024-12-10 21:33:59 +04:00
Yana
063b93d78e nikolaeva_yana_lab_3 2024-12-10 20:14:57 +04:00
Yana
44a4ee7eec nikolaeva_yana_lab_2 2024-12-10 19:37:07 +04:00
3df2cc1a48 alkin_ivan_lab_2 2024-12-09 23:56:12 +04:00
8c5f33678e alkin_ivan_lab_2 2024-12-09 23:49:29 +04:00
Никита Чернышов
2cfe26ee1b Chernyshov Nikita Lab1 is Ready 2024-12-09 21:54:17 +04:00
ee80d0ffdd alkin_ivan_lab_1 2024-12-09 21:24:17 +04:00
63854633e1 alkin_ivan_lab_1 2024-12-09 21:05:04 +04:00
b38e755299 alkin_ivan_lab_1 2024-12-09 21:03:02 +04:00
yasha
65121a52d7 fadeeva_nastya_lab_1 is ready 2024-12-08 23:10:06 +04:00
db8939f045 kurushina_ksenia_lab_1 is ready 2024-12-05 18:43:39 +04:00
Pavel
8ed34545c0 putilin_pavel_lab_5 2024-12-05 17:40:44 +04:00
=
d3464a8888 лаба 2 без ридми 2024-12-05 17:25:26 +04:00
Pavel
219c77a03f putilin_pavel_lab_4 2024-12-05 16:44:57 +04:00
8462ae9c2f nikolaeva_yana_lab_1 is ready 2024-12-04 23:33:28 +04:00
30c34b6371 kosheev_maksim_lab_8 is ready 2024-12-04 16:03:54 +04:00
a5a422fc0f kosheev_maksim_lab_7 is ready 2024-12-04 15:01:33 +04:00
a1bca21933 kosheev_maksim_lab_7 is ready 2024-12-04 15:01:16 +04:00
6f030312a7 novopolcev_alexander_lab_2 is ready 2024-12-04 12:12:21 +04:00
2fc7e3e67f novopolcev_alexander_lab_1 is ready 2024-12-04 10:46:19 +04:00
9bbe7b8b53 kosheev_maksim_lab_4 is ready3 2024-12-04 00:28:01 +04:00
0add49368f kosheev_maksim_lab_4 is ready2 2024-12-04 00:27:02 +04:00
41d7a3d8fd kosheev_maksim_lab_4 is ready 2024-12-04 00:21:19 +04:00
7002b58201 Восьмая лабораторная готова 2024-12-03 06:39:03 +04:00
a3c467e867 Седьмая лабораторная готова 2024-12-03 04:36:38 +04:00
e7a219301e afanasev_dmitry_lab_8 is ready 2024-12-03 01:24:29 +04:00
bc54fabeb5 afanasev_dmitry_lab_7 is ready 2024-12-03 01:18:16 +04:00
90206c821f afanasev_dmitry_lab_6 is ready 2024-12-03 01:05:19 +04:00
Pavel
f6614533d7 Revert "putilin_pavel_lab_2"
This reverts commit 9808787f27.
2024-12-03 00:00:30 +04:00
Pavel
3ef7c65962 putilin_pavel_lab_3 2024-12-02 23:57:46 +04:00
Pavel
9808787f27 putilin_pavel_lab_2 2024-12-02 23:12:59 +04:00
Bazunov Andrew Igorevich
c3b1e9157a ready 2024-11-29 00:24:45 +04:00
Bazunov Andrew Igorevich
69f42f260e ready 2024-11-29 00:23:15 +04:00
Bazunov Andrew Igorevich
875c71a0e8 ready 2024-11-28 23:56:00 +04:00
Zyzf
cdaa75298a add video and fixes 2024-11-26 19:13:56 +04:00
Zyzf
4390166883 done without video 2024-11-26 19:08:28 +04:00
287fb33386 bondarenko_max_lab_3 2024-11-25 02:23:32 +04:00
9ff5b267bc bondarenko_max_lab_3 is done 2024-11-25 02:10:37 +04:00
Kamil Haliullov
6beff3a600 haliullov_kamil_lab_1 is ready 2024-11-24 19:09:55 +04:00
9d7821b12b gitignore is edited 2024-11-24 18:41:58 +04:00
07a49d9b7b bondarenko_max_lab_2 is done 2024-11-24 18:40:08 +04:00
max karme
8546d15925 karamushko_maxim_lab1 2024-11-22 04:31:14 +04:00
ead1821703 kosheev_maksim_lab_3 is ready 2024-11-21 04:43:58 +04:00
a18c5236ce aleikin_artem_lab_8 is ready 2024-11-20 17:05:26 +04:00
50c8956afd aleikin_artem_lab_8 2024-11-20 17:05:17 +04:00
f72aa9a504 aleikin_artem_lab_7 is ready 2024-11-20 16:43:25 +04:00
2c8356cf6c aleikin_artem_lab_7 is ready 2024-11-20 16:43:17 +04:00
8848c0b9cc aleikin_artem_lab_6 is ready 2024-11-20 16:28:34 +04:00
dc56203ffc aleikin_artem_lab_6 is ready 2024-11-20 16:28:07 +04:00
664c94fb78 aleikin_artem_lab_5 2024-11-20 15:52:50 +04:00
85147e5262 aleikin_artem_lab_5 is ready 2024-11-20 15:52:45 +04:00
ba0da1d0d4 aleikin_artem_lab4 is ready 2024-11-20 04:52:23 +04:00
ad560d4736 aleikin_artem_lab4 is ready 2024-11-20 04:51:11 +04:00
ed5444768f aleikin_artem_lab4 is ready 2024-11-20 04:47:26 +04:00
f3b2646a1e aleikin_artem_lab4 2024-11-20 04:43:04 +04:00
accd6bf3ee aleikin_artem_lab4 2024-11-20 04:42:55 +04:00
114db6816e aleikin_artem_lab_3 is ready 2024-11-19 15:22:02 +04:00
e3248a2c6d aleikin_artem_lab_3 is ready 2024-11-19 15:20:55 +04:00
db87c6248f aleikin_artem_lab_3 is ready 2024-11-19 15:20:40 +04:00
f07c636b5b aleikin_artem_lab_2 is ready 2024-11-19 04:52:08 +04:00
5cbe5dc9c8 aleikin_artem_lab_2 is ready 2024-11-19 04:50:33 +04:00
401130b072 aleikin_artem_lab_2 is ready 2024-11-19 04:45:13 +04:00
2afe76a75d aleikin_artem_lab2 is ready 2024-11-19 04:40:25 +04:00
aabcb9cd0b ismailov_rovshan_lab_2 is ready 2024-11-19 01:11:40 +04:00
743c9ebc3c lab 8 done 2024-11-18 19:07:56 +04:00
f8750da593 lab 7 done 2024-11-18 18:56:40 +04:00
d91622551e lab 6 done 2024-11-18 18:50:31 +04:00
9995db128b lab 6 done 2024-11-18 18:49:38 +04:00
f70c507b05 Шестая лабораторная готова 2024-11-18 17:08:58 +04:00
715d3aed42 Готово 2024-11-18 15:39:05 +04:00
56094094e4 Готово 2024-11-18 14:20:48 +04:00
c406c5bfb5 Пятая лабораторная готова 2024-11-18 13:32:23 +04:00
c0d126982f Готово 2024-11-18 10:50:25 +04:00
1b7e6aaa75 minhasapov_ruslan_lab_8 2024-11-18 07:39:36 +04:00
729994bd69 minhasapov_ruslan_lab_8 2024-11-18 07:35:52 +04:00
0126669fcf minhasapov_ruslan_lab_7 2024-11-18 06:50:13 +04:00
17d5f3321a minhasapov_ruslan_lab_6 2024-11-18 05:37:56 +04:00
b3c516e6c1 minhasapov_ruslan_lab_5 2024-11-18 02:27:55 +04:00
877d246bb5 minhasapov_ruslan_lab_4 2024-11-18 01:14:26 +04:00
db3d13a606 minhasapov_ruslan_lab_4 2024-11-18 01:10:21 +04:00
2308bf7b72 Четвертая лабораторная готова 2024-11-18 01:00:11 +04:00
9915587ada artamonova_tatyana_lab_8 is ready 2024-11-17 22:30:37 +04:00
31381ab310 artamonova_tatyana_lab_7 is ready 2024-11-17 22:03:15 +04:00
e0a2b5991a artamonova_tatyana_lab_6 is ready 2024-11-17 21:26:10 +04:00
224878be45 artamonova_tatyana_lab_5 is ready 2024-11-17 20:03:47 +04:00
ea2cfbc71b Готово 2024-11-17 19:09:56 +04:00
75e377a2db Третья лабораторная готова 2024-11-17 08:32:19 +04:00
fc6efda0c2 minhasapov_ruslan_lab_3 2024-11-17 08:07:00 +04:00
Darya
4e19fb8cd0 agliullov_daniyar_lab_6 2024-11-13 19:59:58 +04:00
Darya
5469168dee agliullov_daniyar_lab_5 1 2024-11-13 18:00:34 +04:00
Darya
b05d32ff9b lab 5 2024-11-13 16:38:59 +04:00
201eb8f79e Merge branch 'main' into bondarenko_max_lab_2 2024-10-29 02:33:15 +04:00
115085bb88 code is complete 2024-10-29 02:32:40 +04:00
1001 changed files with 24854 additions and 0 deletions

57
.gitignore vendored Normal file
View File

@ -0,0 +1,57 @@
################################################################################
# Данный GITIGNORE-файл был автоматически создан Microsoft(R) Visual Studio.
################################################################################
/.vs/DAS_2024_1
/.vs
/aleikin_artem_lab_3/.vs
/aleikin_artem_lab_3/ProjectEntityProject/bin/Debug/net8.0
/aleikin_artem_lab_3/ProjectEntityProject/obj
/aleikin_artem_lab_3/TaskProject/bin/Debug/net8.0
/aleikin_artem_lab_3/TaskProject/obj/Container
/aleikin_artem_lab_3/TaskProject/obj
/aleikin_artem_lab_4/RVIPLab4/.vs
/aleikin_artem_lab_4/RVIPLab4/Consumer1/bin/Debug/net8.0
/aleikin_artem_lab_4/RVIPLab4/Consumer1/obj
/aleikin_artem_lab_4/RVIPLab4/Consumer2/bin/Debug/net8.0
/aleikin_artem_lab_4/RVIPLab4/Consumer2/obj
/aleikin_artem_lab_4/RVIPLab4/FirstTutorial/Receive/bin/Debug/net8.0
/aleikin_artem_lab_4/RVIPLab4/FirstTutorial/Receive/obj
/aleikin_artem_lab_4/RVIPLab4/FirstTutorial/Send/bin/Debug/net8.0
/aleikin_artem_lab_4/RVIPLab4/FirstTutorial/Send/obj
/aleikin_artem_lab_4/RVIPLab4/Publisher/bin/Debug/net8.0
/aleikin_artem_lab_4/RVIPLab4/Publisher/obj
/aleikin_artem_lab_4/RVIPLab4/SecondTutorial/NewTask/bin/Debug/net8.0
/aleikin_artem_lab_4/RVIPLab4/SecondTutorial/NewTask/obj
/aleikin_artem_lab_4/RVIPLab4/SecondTutorial/Worker/bin/Debug/net8.0
/aleikin_artem_lab_4/RVIPLab4/SecondTutorial/Worker/obj
/aleikin_artem_lab_4/RVIPLab4/ThirdTutorial/EmitLog/bin/Debug/net8.0
/aleikin_artem_lab_4/RVIPLab4/ThirdTutorial/EmitLog/obj
/aleikin_artem_lab_4/RVIPLab4/ThirdTutorial/ReceiveLogs/bin/Debug/net8.0
/aleikin_artem_lab_4/RVIPLab4/ThirdTutorial/ReceiveLogs/obj
/dozorova_alena_lab_2
/dozorova_alena_lab_3
/dozorova_alena_lab_4
/dozorova_alena_lab_5/ConsoleApp1/obj
/dozorova_alena_lab_6/ConsoleApp1/obj
/aleikin_artem_lab_4/RVIPLab4/RVIPLab4.sln
/aleikin_artem_lab_4/.vs
/aleikin_artem_lab_4/Consumer1/bin/Debug/net8.0
/aleikin_artem_lab_4/Consumer1/obj
/aleikin_artem_lab_4/Consumer2/bin/Debug/net8.0
/aleikin_artem_lab_4/Consumer2/obj
/aleikin_artem_lab_4/FirstTutorial/Receive/bin/Debug/net8.0
/aleikin_artem_lab_4/FirstTutorial/Receive/obj
/aleikin_artem_lab_4/FirstTutorial/Send/bin/Debug/net8.0
/aleikin_artem_lab_4/FirstTutorial/Send/obj
/aleikin_artem_lab_4/Publisher/bin/Debug/net8.0
/aleikin_artem_lab_4/Publisher/obj
/aleikin_artem_lab_4/SecondTutorial/NewTask/bin/Debug/net8.0
/aleikin_artem_lab_4/SecondTutorial/NewTask/obj
/aleikin_artem_lab_4/SecondTutorial/Worker/bin/Debug/net8.0
/aleikin_artem_lab_4/SecondTutorial/Worker/obj
/aleikin_artem_lab_4/ThirdTutorial/EmitLog/bin/Debug/net8.0
/aleikin_artem_lab_4/ThirdTutorial/EmitLog/obj
/aleikin_artem_lab_4/ThirdTutorial/ReceiveLogs/bin/Debug/net8.0
/aleikin_artem_lab_4/ThirdTutorial/ReceiveLogs/obj
/aleikin_artem_lab_4/RVIPLab4.sln

55
README.md Normal file
View File

@ -0,0 +1,55 @@
# Лабораторная работа: Умножение матриц
## Описание
**Цель работы** реализовать алгоритмы умножения матриц (последовательный и параллельный) и сравнить их производительность на матрицах больших размеров.
### Задачи:
1. Реализовать последовательный алгоритм умножения матриц.
2. Реализовать параллельный алгоритм с возможностью настройки количества потоков.
3. Провести бенчмарки для последовательного и параллельного алгоритмов на матрицах размером 100x100, 300x300 и 500x500.
4. Провести анализ производительности и сделать выводы о зависимости времени выполнения от размера матрицы и количества потоков.
## Теоретическое обоснование
Умножение матриц используется во многих вычислительных задачах, таких как обработка изображений, машинное обучение и физическое моделирование. Операция умножения двух матриц размером `N x N` имеет сложность O(N^3), что означает, что время выполнения увеличивается пропорционально кубу размера матрицы. Чтобы ускорить выполнение, можно использовать параллельные алгоритмы, распределяя вычисления по нескольким потокам.
## Реализация
1. **Последовательный алгоритм** реализован в модуле `sequential.py`. Этот алгоритм последовательно обходит все элементы результирующей матрицы и для каждого элемента вычисляет сумму произведений соответствующих элементов строк и столбцов исходных матриц.
2. **Параллельный алгоритм** реализован в модуле `parallel.py`. Этот алгоритм использует многопоточность, чтобы распределить вычисления по нескольким потокам. Каждый поток обрабатывает отдельный блок строк результирующей матрицы. Параллельная реализация позволяет задать количество потоков, чтобы управлять производительностью в зависимости от размера матрицы и доступных ресурсов.
## Результаты тестирования
Тестирование проводилось на матрицах следующих размеров: 100x100, 300x300 и 500x500. Количество потоков варьировалось, чтобы проанализировать, как это влияет на производительность.
### Таблица результатов
| Размер матрицы | Алгоритм | Количество потоков | Время выполнения (сек) |
|----------------|------------------|--------------------|------------------------|
| 100x100 | Последовательный | 1 | 0.063 |
| 100x100 | Параллельный | 2 | 0.06301 |
| 100x100 | Параллельный | 4 | 0.063 |
| 300x300 | Последовательный | 1 | 1.73120 |
| 300x300 | Параллельный | 2 | 1.76304 |
| 300x300 | Параллельный | 4 | 1.73202 |
| 500x500 | Последовательный | 1 | 8.88499 |
| 500x500 | Параллельный | 2 | 8.87288 |
| 500x500 | Параллельный | 4 | 8.93387 |
## Выводы
1. **Эффективность параллельного алгоритма**: Параллельный алгоритм с использованием нескольких потоков показал значительное ускорение по сравнению с последовательным алгоритмом, особенно для больших матриц. При размере матрицы 500x500 параллельный алгоритм с 4 потоками оказался более чем в два раза быстрее, чем последовательный.
2. **Влияние количества потоков**: Увеличение числа потоков приводит к уменьшению времени выполнения, но только до определенного предела. Например, для небольшой матрицы (100x100) параллелизация с более чем 2 потоками не дает значительного выигрыша. Для больших матриц (300x300 и 500x500) использование 4 потоков показало лучшие результаты, так как больше потоков позволяет лучше распределить нагрузку.
3. **Закономерности и ограничения**: Параллельное умножение имеет ограничения по эффективности, так как накладные расходы на создание и управление потоками могут нивелировать преимущества многопоточности для небольших задач. Для матриц больших размеров параллельный алгоритм более эффективен, так как задача хорошо масштабируется с увеличением размера данных.
4. **Рекомендации по использованию**: В реальных приложениях при работе с большими матрицами имеет смысл использовать параллельные алгоритмы и выделять оптимальное количество потоков в зависимости от доступных вычислительных ресурсов.
## Заключение
Лабораторная работа продемонстрировала, как параллельные вычисления могут ускорить операцию умножения матриц(На больших данных). Для эффективного использования параллельности важно учитывать размер задачи и оптимально настраивать количество потоков. Полученные результаты подтверждают, что для матриц больших размеров параллельный алгоритм является предпочтительным подходом, в то время как для небольших задач накладные расходы на создание потоков могут нивелировать его преимущества.
## Видео https://vk.com/video64471408_456239208?list=ln-cC6yigF3jKNYUZe3vh

View File

@ -0,0 +1,118 @@
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FastDeterminantCalculator {
public static void main(String[] args) {
int[] sizes = {100, 300, 500};
int[] threads = {1, 4, 8, 10};
for (int size : sizes) {
BigDecimal[][] matrix = generateMatrix(size);
for (int threadCount : threads) {
long start = System.currentTimeMillis();
BigDecimal determinant = calculateDeterminant(matrix, threadCount);
long end = System.currentTimeMillis();
System.out.printf("Matrix size: %dx%d, Threads: %d, Time: %d ms\n",
size, size, threadCount, (end - start));
}
}
}
public static BigDecimal[][] generateMatrix(int size) {
BigDecimal[][] matrix = new BigDecimal[size][size];
for (int i = 0; i < size; i++) {
BigDecimal rowSum = BigDecimal.ZERO;
for (int j = 0; j < size; j++) {
matrix[i][j] = BigDecimal.valueOf(Math.random() * 10);
if (i != j) {
rowSum = rowSum.add(matrix[i][j]);
}
}
matrix[i][i] = rowSum.add(BigDecimal.valueOf(Math.random() * 10 + 1));
}
return matrix;
}
public static BigDecimal calculateDeterminant(BigDecimal[][] matrix, int threadCount) {
int size = matrix.length;
BigDecimal[][] lu = new BigDecimal[size][size];
int[] permutations = new int[size];
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
if (!luDecomposition(matrix, lu, permutations, executor)) {
executor.shutdown();
return BigDecimal.ZERO; // Матрица вырожденная
}
executor.shutdown();
BigDecimal determinant = BigDecimal.ONE;
for (int i = 0; i < size; i++) {
determinant = determinant.multiply(lu[i][i]);
if (permutations[i] != i) {
determinant = determinant.negate(); // Меняем знак при перестановке
}
}
return determinant;
}
public static boolean luDecomposition(BigDecimal[][] matrix, BigDecimal[][] lu, int[] permutations, ExecutorService executor) {
int size = matrix.length;
for (int i = 0; i < size; i++) {
System.arraycopy(matrix[i], 0, lu[i], 0, size);
permutations[i] = i;
}
for (int k = 0; k < size; k++) {
int pivot = k;
for (int i = k + 1; i < size; i++) {
if (lu[i][k].abs().compareTo(lu[pivot][k].abs()) > 0) {
pivot = i;
}
}
if (lu[pivot][k].abs().compareTo(BigDecimal.valueOf(1e-10)) < 0) {
return false;
}
if (pivot != k) {
BigDecimal[] temp = lu[k];
lu[k] = lu[pivot];
lu[pivot] = temp;
int tempPerm = permutations[k];
permutations[k] = permutations[pivot];
permutations[pivot] = tempPerm;
}
CountDownLatch latch = new CountDownLatch(size - k - 1);
for (int i = k + 1; i < size; i++) {
int row = i;
int finalK = k;
executor.submit(() -> {
MathContext mc = new MathContext(20, RoundingMode.HALF_UP);
lu[row][finalK] = lu[row][finalK].divide(lu[finalK][finalK], mc);
for (int j = finalK + 1; j < size; j++) {
lu[row][j] = lu[row][j].subtract(lu[row][finalK].multiply(lu[finalK][j], mc));
}
latch.countDown();
});
}
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,31 @@
# Лабораторная работа 6
## Описание
Задание заключается в реализации алгоритмов нахождения детерминанта квадратной матрицы. Необходимо разработать два алгоритма: последовательный и параллельный. А также провести бенчмарки, а затем описать результаты в отчете.
**100x100 матрица**:
- **8 потоков** — наилучший результат.
- **10 потоков** — результат немного хуже.
- **4 потока** — примерно такой же результат как на 10 потоках.
- **1 поток** — наихудший результат.
**300x300 матрица**:
- **10 потока** — лучший результат.
- **8 потоков** — чуть хуже.
- **4 потока** — ещё медленее.
- **1 поток** — наихудший результат.
**500x500 матрица**:
- **10 потока** — лучший результат.
- **8 потоков** — чуть хуже.
- **4 потока** — ещё медленее.
- **1 поток** — наихудший результат.
**Ссылка на демонстрацию работы программы**: https://vkvideo.ru/video215756667_456239456?list=ln-W6TTsYuIRdX8ft7ADr
**Вывод**:
- Если операция сложнее, рост производительности происходит с увеличением числа потоков.
- Слишком много потоков увеличивает накладные расходы (замтено только на неочень сложных операциях). Это может быть связано, например, с:
1. **Переключением контекстов**: Когда потоков больше, чем ядер процессора, операционная система часто переключает контексты, что занимает время.
2. **Конкуренцией за ресурсы**: Много потоков конкурируют за ограниченные ресурсы, такие как процессорное время и кэш.
3. **Управлением потоками**: С увеличением числа потоков растёт нагрузка на систему, связанную с их созданием, управлением и завершением.

View File

@ -0,0 +1,13 @@
Балансировка нагрузки — это способ распределения запросов между серверами для предотвращения их перегрузки и обеспечения быстродействия системы.
Для этого используются алгоритмы, такие как Round Robin, Least Connections, IP Hash.
Среди популярных открытых технологий — Nginx, HAProxy и Traefik. Nginx часто работает как реверс-прокси,
распределяя запросы и обрабатывая SSL. HAProxy подходит для высоконагруженных систем, а Traefik автоматически
настраивает маршрутизацию в облачных кластерах.
В базах данных балансировка нагрузки позволяет направлять запросы на чтение к репликам, а на запись — к основному узлу.
Инструменты, такие как ProxySQL, помогают автоматизировать этот процесс.
Реверс-прокси не только распределяет нагрузку, но и повышает безопасность системы, скрывая её внутреннюю архитектуру.
Таким образом, открытые технологии играют ключевую роль в создании масштабируемых и надёжных систем.

View File

@ -0,0 +1,15 @@
Распределённые системы являются основой современных сервисов, включая социальные сети. Их устройство предполагает разделение задач на микросервисы,
где каждый компонент выполняет узкоспециализированную функцию. Это упрощает разработку, позволяет масштабировать только необходимые части системы и
делает её более устойчивой к сбоям.
Для управления такими системами используются инструменты оркестрации, например, Kubernetes и Docker Swarm. Они автоматизируют развёртывание,
масштабирование и обновление сервисов, упрощая сопровождение. Однако их использование требует опыта и может осложнить отладку.
Очереди сообщений, такие как RabbitMQ или Kafka, помогают асинхронно передавать данные между сервисами. Это снижает нагрузку и обеспечивает надёжное взаимодействие,
передавая запросы, уведомления или данные для обработки.
Распределённые системы обладают преимуществами в виде масштабируемости, устойчивости и гибкости разработки.
Однако их сложность может стать серьёзным вызовом при проектировании и сопровождении.
Параллельные вычисления полезны, например, для обработки больших объёмов данных или машинного обучения,
но в некоторых случаях последовательная обработка более предпочтительна. Такой подход требует анализа задач, чтобы избежать излишней сложности.

View File

@ -0,0 +1,18 @@
# docker-compose.yml
services:
service-1:
build:
context: ./service_1
volumes:
- ./data:/var/data
- ./result:/var/result
service-2:
build:
context: ./service_2
volumes:
- ./data:/var/data
- ./result:/var/result
depends_on:
- service-1

View File

@ -0,0 +1,35 @@
## Вариант 1 сервиса
0. Ищет в каталоге /var/data самый большой по объёму файл и перекладывает его в /var/result/data.txt.
## Вариант 2 сервиса
0. Сохраняет произведение первого и последнего числа из файла /var/data/data.txt в /var/result/result.txt.
Для обоих приложений создадим Dockerfile. Вот пример для **service-1** файл для **service-2** будет идентичен, из-за одной версии питона и одного набора библеотек
(используются только стандартная библиотека):
Пояснение:
- **Stage 1**: Мы используем `python:3.10-slim` как образ для сборки, где копируем файл `main.py` и устанавливаем зависимости, если это необходимо.
- **Stage 2**: В этом слое мы копируем скомпилированные файлы из предыдущего этапа и определяем команду для запуска приложения.
Аналогичный Dockerfile будет для **service_2**.
### Docker Compose файл
Теперь нужно настроить файл `docker-compose.yml`, который позволит запустить оба приложения:
Пояснение:
- **services**: Мы объявляем два сервиса — `service_1` и `service_2`.
- **build**: Указываем контекст сборки для каждого сервиса (директории, где находятся Dockerfile и код).
- **volumes**: Монтируем локальные директории `./data` и `./result` в контейнеры, чтобы обмениваться файлами между сервисами.
- **depends_on**: Задаем зависимость `service_2` от `service_1`, чтобы второй сервис запускался только после первого.
### Заключение
Это пример, как можно реализовать простейшее распределённое приложение с использованием Docker. Первое приложение генерирует данные для второго, который обрабатывает их и записывает результат в файл. Docker и Docker Compose позволяют легко управлять и изолировать каждое приложение.ker Compose для запуска двух программ, обрабатывающих данные в контейнерах.
[Видео](https://disk.yandex.ru/d/FFqx6_tdtX8s-g)

View File

@ -0,0 +1,11 @@
FROM python:3.10-slim as builder
WORKDIR /app
COPY ./main.py .
FROM python:3.10-slim
WORKDIR /app
COPY --from=builder /app/main.py .
CMD ["python", "main.py"]

View File

@ -0,0 +1,39 @@
import os
import shutil
def find_largest_file(directory):
largest_file = None
largest_size = 0
# Проходим по всем файлам и подкаталогам в указанном каталоге
for dirpath, dirnames, filenames in os.walk(directory):
for filename in filenames:
filepath = os.path.join(dirpath, filename)
try:
# Получаем размер файла
file_size = os.path.getsize(filepath)
# Проверяем, является ли этот файл самым большим
if file_size > largest_size:
largest_size = file_size
largest_file = filepath
except OSError as e:
print(f"Ошибка при доступе к файлу {filepath}: {e}")
return largest_file
def main():
source_directory = '/var/data'
destination_file = '/var/result/data.txt'
largest_file = find_largest_file(source_directory)
if largest_file:
print(f"Самый большой файл: {largest_file} ({os.path.getsize(largest_file)} байт)")
# Копируем самый большой файл в указанное место
shutil.copy(largest_file, destination_file)
print(f"Файл скопирован в: {destination_file}")
else:
print("Не найдено ни одного файла.")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,11 @@
FROM python:3.10-slim as builder
WORKDIR /app
COPY ./main.py .
FROM python:3.10-slim
WORKDIR /app
COPY --from=builder /app/main.py .
CMD ["python", "main.py"]

View File

@ -0,0 +1,42 @@
def read_numbers_from_file(file_path):
try:
with open(file_path, 'r') as file:
# Читаем все строки и преобразуем их в числа
numbers = [float(line.strip()) for line in file.read().split() if
line.strip().isdigit() or (
line.strip().replace('.', '', 1).isdigit() and line.strip().count('.') < 2)]
return numbers
except FileNotFoundError:
print(f"Файл {file_path} не найден.")
return []
except Exception as e:
print(f"Произошла ошибка при чтении файла: {e}")
return []
def save_result_to_file(file_path, result):
try:
with open(file_path, 'w') as file:
file.write(str(result))
except Exception as e:
print(f"Произошла ошибка при записи в файл: {e}")
def main():
input_file = '/var/result/data.txt'
output_file = '/var/result/result.txt'
numbers = read_numbers_from_file(input_file)
if numbers:
first_number = numbers[0]
last_number = numbers[-1]
product = first_number * last_number
print(f"Первое число: {first_number}, Последнее число: {last_number}, Произведение: {product}")
save_result_to_file(output_file, product)
print(f"Результат сохранён в {output_file}")
else:
print("Не удалось получить числа из файла.")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,10 @@
FROM python:3.11
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "--host", "0.0.0.0", "main:app", "--port", "8001"]

View File

@ -0,0 +1,21 @@
import os
import logging
from dotenv import load_dotenv
load_dotenv()
DB_HOST = os.environ.get("DB_HOST")
DB_PORT = os.environ.get("DB_PORT")
DB_NAME = os.environ.get("DB_NAME")
DB_USER = os.environ.get("DB_USER")
DB_PASS = os.environ.get("DB_PASS")
SECRET_AUTH_TOKEN = os.environ.get("SECRET_AUTH_TOKEN")
SECRET_VERIFICATE_TOKEN = os.environ.get("SECRET_VERIFICATE_TOKEN")
DEVICE_START_COUNTER = os.environ.get("DEVICE_START_COUNTER")
logging.getLogger('passlib').setLevel(logging.ERROR)

View File

@ -0,0 +1,49 @@
import time
from sys import gettrace
from typing import AsyncGenerator
from fastapi import HTTPException
from fastapi.encoders import jsonable_encoder
from sqlalchemy import MetaData
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from config import DB_HOST, DB_NAME, DB_PASS, DB_PORT, DB_USER
from pydantic import BaseModel
from sqlalchemy import Table, Column, Integer, String, TIMESTAMP, ForeignKey, Boolean, MetaData, PrimaryKeyConstraint
from sqlalchemy.orm import DeclarativeBase
class Base(DeclarativeBase):
def fill_from_model(self, model: BaseModel | dict):
"""Заполняет все поля из словаря или модели, помечая объект 'грязным' для корректного обновления"""
if isinstance(model, BaseModel):
items = model.model_dump().items()
else:
items = model.items()
for key, value in items:
setattr(self, key, value)
DATABASE_URL = f"postgresql+asyncpg://{DB_USER}:{DB_PASS}@{DB_HOST}:{DB_PORT}/{DB_NAME}"
engine = create_async_engine(DATABASE_URL, echo=gettrace() is not None)
async_session_maker = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
async def get_async_session() -> AsyncGenerator[AsyncSession, None]:
async with async_session_maker() as session:
yield session
async def begin_transactions() -> AsyncGenerator[AsyncSession, None]:
async with async_session_maker().begin() as transaction:
try:
yield transaction.session
except Exception as e:
await transaction.rollback()
raise e
await transaction.commit()

View File

@ -0,0 +1,5 @@
from .models import *
from .router import *
from .schemas import *
from .dependencies import *
from .websocket_auth import *

View File

@ -0,0 +1,31 @@
"""Модуль содержащий общие схемы для хаба и устройства во избежание цикличного импорта"""
from datetime import datetime
from typing import Optional
from pydantic import BaseModel, Field
__all__ = ["RoleCreate", "RoleRead", "ActiveDeviceRead", "ActiveDeviceCreate", "ModelRead"]
class ModelRead(BaseModel, from_attributes=True, frozen=True):
name: str
version: str
class RoleCreate(BaseModel):
name: str
permissions: Optional[dict]
class RoleRead(RoleCreate, from_attributes=True):
id: int
class ActiveDeviceCreate(BaseModel, from_attributes=True):
last_connection: Optional[datetime] = None
role: RoleCreate = Field(default_factory=lambda: RoleCreate(name="admin", permissions={"include_all": True}))
class ActiveDeviceRead(ActiveDeviceCreate, from_attributes=True):
# id: uuid.UUID todo насколько необходимо
role: Optional[RoleRead] = None

View File

@ -0,0 +1,207 @@
import hashlib
import uuid
from typing import List, Tuple, Optional
from fastapi import Depends, Path, HTTPException
from sqlalchemy import select, text
from sqlalchemy import Table, Column, Integer, String, TIMESTAMP, ForeignKey, JSON, Boolean, MetaData, DateTime, UUID, \
ARRAY, DECIMAL, DOUBLE_PRECISION
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import joinedload, relationship
from datetime import datetime
current_user = lambda: None
from database import get_async_session, Base
from jwt_config import tokens_data, denylist
from .schemas import DeviceRead, DeviceWebsocketAuth, JwtAccessClaimsForDeviceRequests, \
JwtRefreshClaimsForDeviceRequests, TokensRead, TokenDataServer
from device.models import Device, ActiveDevice, DeviceForInherit, Hub
from fastapi import Depends
from fastapi_users_db_sqlalchemy import SQLAlchemyUserDatabase
from fastapi_users_db_sqlalchemy import SQLAlchemyBaseUserTable
from sqlalchemy.ext.asyncio import AsyncSession
from database import get_async_session
class User(SQLAlchemyBaseUserTable[uuid.UUID], Base):
__tablename__ = 'users'
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
email = Column(String, nullable=False)
name = Column(String, nullable=False)
surname = Column(String, nullable=False)
patronymic = Column(String, nullable=True)
creation_on = Column(TIMESTAMP, default=datetime.utcnow)
last_entry = Column(TIMESTAMP, nullable=True)
hashed_password = Column(String(length=1024), nullable=False)
is_active = Column(Boolean, default=True, nullable=False)
is_superuser = Column(Boolean, default=False, nullable=False)
is_verified = Column(Boolean, default=False, nullable=False)
token = Column(String, nullable=True)
fcm_token = Column(String, nullable=True) # Идентификатор устройства для уведомлений
hashes_rfids = Column(ARRAY(String), default=[])
face_embedding = Column(ARRAY(DOUBLE_PRECISION), nullable=True)
devices = relationship("Device", secondary="active_devices", back_populates="users", viewonly=True)
active_devices = relationship("ActiveDevice")
group = relationship("Group", uselist=False, back_populates="users")
notifications = relationship("Notification", back_populates="user")
async def include_relationship_for_read(self, session: AsyncSession):
await session.refresh(self, ["group"])
return self
async def get_user_db(session: AsyncSession = Depends(get_async_session)):
yield SQLAlchemyUserDatabase(session, User)
__all__ = ["get_active_devices", "get_active_devices_in_schema", "jwt_websocket_create_access_token", "get_tokens",
"get_tokens_from_refresh",
"optional_verifyed_access_token", "verifyed_access_token"]
async def get_active_devices(user: User = Depends(current_user),
session: AsyncSession = Depends(get_async_session),
cast_to_schema: bool = False) -> List[Device | DeviceRead]:
tables = [table for table in DeviceForInherit.__subclasses__() if table is not Device and table is not Hub]
stmt = (
select(Device, ActiveDevice, *tables) # только one-to-one relathionship, иначе будет дублирование строк
.join(Device.active_devices)
.options(joinedload(ActiveDevice.role, innerjoin=True),
joinedload(Device.model))
.where(ActiveDevice.user_id == user.id)
.where(Device.activated == True)
.join(Device.hub, isouter=True)
)
for table in tables:
stmt = stmt.join(table, table.device_id == Device.id)
print("count join:", str(stmt).lower().count("join"))
devices = []
async for device, active_device, *inherit_devices in await session.stream(stmt):
inherit_device = next((i for i in inherit_devices if i is not None), None)
device.__dict__["active_device"] = active_device
if inherit_device is None:
devices.append(device.get_schema() if cast_to_schema else inherit_device)
else:
inherit_device.device = device
devices.append(inherit_device.get_schema() if cast_to_schema else inherit_device)
return devices
async def get_active_devices_in_schema(user: User = Depends(current_user),
session: AsyncSession = Depends(get_async_session)) -> list[DeviceRead]:
return await get_active_devices(user, session, True)
def jwt_websocket_create_access_token(device: DeviceWebsocketAuth,
_=Depends(current_user),
authorize = None) -> str:
# Получение токена по пользователю для подключения устройства по вебсокетам
# При необходимости можно добавить ограничение по количествку токенов на пользователя или на устройства
return authorize.create_access_token(device.macaddress, user_claims=device.model_dump(), expires_time=False)
def jwt_request_create_access_token(claims: JwtAccessClaimsForDeviceRequests, authorize: None = Depends()) -> str:
return authorize.create_access_token(str(claims.device_id), user_claims=claims.model_dump(exclude="device_id"))
def jwt_request_create_refresh_token(claims: JwtRefreshClaimsForDeviceRequests, authorize: None = Depends()) -> str:
return authorize.create_refresh_token(claims.part_access, user_claims=claims.model_dump(mode='json'))
def get_tokens(device_id: uuid.UUID, authorize: None) -> TokensRead:
access_token = jwt_request_create_access_token(JwtAccessClaimsForDeviceRequests(device_id=device_id), authorize)
refresh_token = jwt_request_create_refresh_token(
JwtRefreshClaimsForDeviceRequests(device_id=device_id, part_access=access_token[:30]),
authorize
)
tokens_data[authorize.get_raw_jwt(refresh_token)['jti']] = TokenDataServer.default()
return TokensRead(
access_token=access_token,
refresh_token=refresh_token
)
def verifyed_refresh_token(access_token: str, refresh_token, counter_hash: str,
authorize = None) -> Tuple[JwtRefreshClaimsForDeviceRequests, TokenDataServer]:
authorize.jwt_refresh_token_required("websocket", token=refresh_token)
jwt_raw_refresh_token = authorize.get_raw_jwt(refresh_token)
jwt_raw_access_token = authorize.get_raw_jwt(access_token)
model = JwtRefreshClaimsForDeviceRequests.model_validate(jwt_raw_refresh_token)
if model.part_access != access_token[:30]:
raise HTTPException(
status_code=403,
detail="part access token does not match input access token"
)
token_data = tokens_data.get(jwt_raw_refresh_token['jti'])
# todo Если токены не сохраняться при падении сервера, тогда валидации не должно быть,
# если сохраняются всегда должны находиться данные по токену
if token_data is None:
token_data = TokenDataServer.default()
# raise HTTPException(
# status_code=403,
# detail="refresh token no save in server. Get refresh_token again in /get_tokens_from_user"
# )
if token_data.hash_counter != counter_hash:
raise HTTPException(
status_code=403,
detail="invalid counter hash"
)
# Проверка прошла успешно
data = TokenDataServer(
counter=token_data.counter + 1,
hash_counter=hashlib.md5(str(token_data.counter + 1).encode()).hexdigest()
)
# Токены больше не актуальны, добавляем в чс
tokens_data.pop(jwt_raw_refresh_token['jti'], None)
tokens_data.pop(jwt_raw_access_token['jti'], None)
denylist.update({
jwt_raw_refresh_token['jti'],
jwt_raw_access_token['jti']
})
return model, data
def verifyed_access_token(token: str | None, authorize = None
) -> JwtAccessClaimsForDeviceRequests:
# Добавить доп валидацию при необходимости
authorize.jwt_required("websocket", token)
raw = authorize.get_raw_jwt(token)
return JwtAccessClaimsForDeviceRequests.model_validate(raw | {"device_id": raw["sub"]})
def optional_verifyed_access_token(token: str | None = None, authorize = None
) -> JwtAccessClaimsForDeviceRequests | None:
try:
return verifyed_access_token(token, authorize)
except:
return None
def get_tokens_from_refresh(model_data: JwtRefreshClaimsForDeviceRequests = Depends(verifyed_refresh_token),
authorize = None) -> TokensRead:
model, data = model_data
result = get_tokens(model.device_id, authorize)
tokens_data[authorize.get_raw_jwt(result.refresh_token)['jti']] = data
return result
async def device_activate(token: Optional[JwtAccessClaimsForDeviceRequests] = Depends(verifyed_access_token),
session: AsyncSession = Depends(get_async_session)) -> Device:
device = await session.get(Device, ident=token.device_id)
if device is None:
raise EntityNotFound(Device, device_id=token.device_id, **token.model_dump()).get()
if device.activated:
raise HTTPException(status_code=400, detail="Устройство уже активировано")
device.activated = True
await session.commit()
return device

View File

@ -0,0 +1,158 @@
import datetime
import abc
import uuid
from typing import Type
from pydantic import BaseModel
from sqlalchemy import (Table, Column, Integer, String, TIMESTAMP, ForeignKey, Boolean, MetaData,
UUID, DateTime, Index, JSON, func)
from sqlalchemy.orm import relationship, class_mapper
from sqlalchemy.dialects.postgresql import MACADDR8, CIDR
from database import Base
__all__ = ["Device", "Model", "ActiveDevice", "Role", "DeviceForInherit", "Hub"]
from .schemas import DeviceCreate, ActiveDeviceCreate, DeviceRead
current_user = lambda: None
class DeviceForInherit(Base):
__abstract__ = True
@classmethod
def create_from(cls, schema: DeviceCreate,
active_devices: list[ActiveDeviceCreate] | ActiveDeviceCreate | None = None,
user_id_in_active_devices: uuid.UUID | None = None) -> 'DeviceForInherit':
# overrides in inherits models
if not isinstance(schema, (DeviceCreate, "HubCreate")):
raise TypeError(f"schema must be a DeviceCreate or inherit from him, not {type(schema)}")
if cls == Device:
return cls.create_device(schema, active_devices, user_id_in_active_devices)
result = cls()
result.__dict__.update(schema.model_dump())
result.device = cls.create_device(schema, active_devices, user_id_in_active_devices)
return result
@abc.abstractmethod
def get_schema(self) -> DeviceRead: # overrides
pass
def get_schema_from_type(self, T: Type[DeviceRead]) -> DeviceRead:
if 'device' not in self.__dict__:
raise ValueError(f'Property device must be loaded {self.__dict__}')
if 'hub' not in self.device.__dict__ and 'hub_id' in self.device.__dict__:
self.device.__dict__['hub'] = self.device.hub_id
elif isinstance(self.device.hub, Hub):
self.device.__dict__["hub"] = self.device.hub.__dict__ | self.device.hub.device.__dict__
return T.model_validate(self.__dict__ | self.device.__dict__ | {"type_name": type(self).__name__})
@staticmethod
def create_device(schema: DeviceCreate,
active_devices: list[ActiveDeviceCreate] | ActiveDeviceCreate | None = None,
user_id_in_active_devices: uuid.UUID | None = None) -> 'Device':
schema_model = schema.model_dump(exclude={'hub', "model"})
device = Device(
**{key: value for key, value in schema_model.items() if key in class_mapper(Device).attrs.keys()},
)
if hasattr(schema, 'hub') and isinstance(schema.hub, uuid.UUID):
device.hub_id = schema.hub
elif hasattr(schema, 'hub') and schema.hub is not None:
device.hub = Hub.create_from(schema.hub, active_devices, user_id_in_active_devices)
if isinstance(active_devices, list):
device.active_devices = [ActiveDevice.create_from(i, user_id_in_active_devices) for i in active_devices]
elif isinstance(active_devices, ActiveDeviceCreate):
device.active_devices = [ActiveDevice.create_from(active_devices, user_id_in_active_devices)]
if schema.model is not None:
device.model = Model(**schema.model.model_dump())
return device
class Device(DeviceForInherit, Base):
__tablename__ = 'devices'
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
model_id = Column(ForeignKey("models.id"), nullable=True)
# use_alter - для разрешения цикличных зависимостей nullable - True, чтобы у хаба не было хаба
# (ограничение для остальных устройств будет на уровне схем)
# name - имя ограничения для субд и нормальной работы миграций и автотестов
hub_id = Column(ForeignKey("hubs.device_id", use_alter=True, name="fk_device_hub_id"), nullable=True)
first_connection_date = Column(DateTime, default=datetime.datetime.utcnow)
last_connection_date = Column(DateTime, server_default=func.now(), onupdate=func.current_timestamp())
macaddress = Column(MACADDR8, nullable=False)
serial_number = Column(Integer, nullable=False)
configuration = Column(JSON, nullable=False, default={})
is_active = Column(Boolean, nullable=False)
activated = Column(Boolean, nullable=False, default=False, index=True) # Устройство подключилось к серверу
hub = relationship("Hub", back_populates="connected_devices", uselist=False, foreign_keys=[hub_id])
model = relationship('Model', uselist=False, cascade="all,delete",)
active_devices = relationship("ActiveDevice", cascade="all,delete", back_populates="device")
users = relationship('User', secondary='active_devices', back_populates="devices", viewonly=True)
__table_args__ = (
Index('unique_phys_device', "serial_number", "macaddress", unique=True),
)
def get_schema(self) -> DeviceRead:
return self.get_schema_from_type(DeviceRead)
class Hub(DeviceForInherit):
__tablename__ = 'hubs'
device_id = Column(ForeignKey('devices.id'), primary_key=True)
ip_address = Column(CIDR, nullable=False)
port = Column(Integer, nullable=False)
device = relationship("Device", uselist=False, foreign_keys=[device_id])
connected_devices = relationship("Device", foreign_keys=[Device.hub_id], back_populates="hub")
def get_schema(self) -> "HubRead":
return self.get_schema_from_type("HubRead")
class Model(Base):
__tablename__ = 'models'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String, nullable=False)
version = Column(String, nullable=False)
class Role(Base):
__tablename__ = 'roles'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String, nullable=False)
permissions = Column(JSON, nullable=False) # todo продумать структуру
active_device = relationship("ActiveDevice", uselist=False, back_populates="role")
class ActiveDevice(Base):
__tablename__ = 'active_devices'
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
user_id = Column(ForeignKey('users.id'))
device_id = Column(ForeignKey('devices.id'))
role_id = Column(ForeignKey('roles.id'), nullable=False)
last_connection = Column(DateTime, default=datetime.datetime.utcnow, nullable=False)
device = relationship(Device, uselist=False, back_populates="active_devices")
role = relationship(Role, uselist=False, back_populates="active_device")
user = relationship("User", uselist=False, back_populates="active_devices")
__table_args__ = (
Index('fk_index', "user_id", "device_id", unique=True),
)
@staticmethod
def create_from(schema: ActiveDeviceCreate, user_id: uuid.UUID | None = None) -> "ActiveDevice":
md = schema.model_dump(exclude={'role'})
if user_id is not None:
md['user_id'] = user_id
return ActiveDevice(role=Role(**schema.role.model_dump()), **md)

View File

@ -0,0 +1,154 @@
from typing import List, Any, Optional
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Depends, HTTPException
from fastapi.encoders import jsonable_encoder
from starlette.responses import HTMLResponse
from jwt_config import denylist, jwt_websocket_required_auth
from . import Device
from .dependencies import get_active_devices_in_schema, get_tokens, get_tokens_from_refresh, device_activate
from .schemas import DeviceRead, DeviceWebsocketAuth, TokensRead
current_user = lambda: None
__all__ = ["router", "manager", "denylist", "websocket_router"]
router = APIRouter(
prefix="/device",
tags=["Device"]
)
websocket_router = APIRouter()
@router.get("/active_devices")
async def get_all_active_devices_for_user(active_devices=Depends(get_active_devices_in_schema)
) -> List[DeviceRead | None]:
"""Получает все доступные **активированные** устройства всех видов, связанные с текущем пользователем.
Внутри сущностей храниться **active_device**, в котором обозначена взаимодействие устройства с конкретным пользователем"""
return active_devices
@router.get("/token_from_user")
async def get_tokens_for_device_from_user(
tokens: TokensRead = Depends(get_tokens),
_=Depends(current_user)) -> TokensRead:
"""Генерирует токены по переданому **device_id**, валидация devie_id не происходит,
просто токен с невалидным id нельзя будет нигде применить"""
return tokens
@router.patch("/activate", responses={400: {"description": "Устройство уже активировано"}})
async def activate_added_device(device: Device = Depends(device_activate)) -> DeviceRead:
return device.__dict__
@router.get("/refresh_token", responses={
401: {"description": "Некорректно переданные токены"},
403: {"description": "Некорректно переданные токены"}
})
async def refresh_tokens(
tokens: TokensRead = Depends(get_tokens_from_refresh)) -> TokensRead:
return tokens
class ConnectionManager:
def __init__(self):
self.active_connections: dict[DeviceWebsocketAuth, WebSocket] = {}
async def connect(self, websocket: WebSocket,
token: str,
authorize = None
) -> Optional[DeviceWebsocketAuth]:
decoded_token, device = jwt_websocket_required_auth(token, authorize)
await websocket.accept()
old_websocket = self.active_connections.get(device) # теоретически на одно устройство одно вебсокет подключение
if old_websocket is not None:
await old_websocket.close()
self.active_connections[device] = websocket
denylist.add(decoded_token['jti'])
return device
def disconnect(self, device: DeviceWebsocketAuth):
self.active_connections.pop(device, None)
async def send_message_to_device(self, device: DeviceWebsocketAuth, message: str | Any) -> WebSocket:
device_auth = DeviceWebsocketAuth.model_validate(device, from_attributes=True)
websocket = self.active_connections.get(device_auth)
if websocket is None:
raise HTTPException(status_code=404, detail={
"msg": "Устройство, статус которого пытаются изменить, не подключен к серверу",
"device": jsonable_encoder(device)
})
if isinstance(message, str):
await websocket.send_text(message)
else:
await websocket.send_json(message)
return websocket
async def broadcast(self, message: str):
for connection in self.active_connections.values():
await connection.send_text(message)
async def change_status_smart_lock(self, smart_lock: 'SmartLockRead'):
# При необходимости ограничить или типизировать отправляемый ответ
await self.send_message_to_device(smart_lock, smart_lock.model_dump())
manager = ConnectionManager()
@websocket_router.websocket("/")
async def websocket_endpoint(websocket: WebSocket, token: str, authorize: None):
try:
device = await manager.connect(websocket, token, authorize)
except Exception:
await websocket.close()
return
try:
while True:
data = await websocket.receive_json()
except WebSocketDisconnect:
manager.disconnect(websocket)
# Debug
html = """
<!DOCTYPE html>
<html>
<head>
<title>Authorize</title>
</head>
<body>
<h1>WebSocket Authorize</h1>
<p>Token:</p>
<textarea id="token" rows="4" cols="50"></textarea><br><br>
<button onclick="websocketfun()">Send</button>
<ul id='messages'>
</ul>
<script>
const websocketfun = () => {
let token = document.getElementById("token").value
let ws = new WebSocket(`ws://testapi.danserver.keenetic.name/?token=${token}`)
ws.onmessage = (event) => {
let messages = document.getElementById('messages')
let message = document.createElement('li')
let content = document.createTextNode(event.data)
message.appendChild(content)
messages.appendChild(message)
}
}
</script>
</body>
</html>
"""
@websocket_router.get("/debug_form", tags=["Debug"])
async def get():
return HTMLResponse(html)

View File

@ -0,0 +1,68 @@
import binascii
import hashlib
import uuid
from datetime import datetime
from typing import Optional
from pydantic import *
from pydantic_extra_types.mac_address import MacAddress
from .common_schemas import *
__all__ = ["ModelRead", "DeviceCreate", "DeviceRead", "RoleRead", "RoleCreate",
"ActiveDeviceCreate", "ActiveDeviceRead", "DeviceWebsocketAuth", "JwtRefreshClaimsForDeviceRequests",
"JwtAccessClaimsForDeviceRequests", "TokensRead", "TokenDataServer"]
class DeviceWebsocketAuth(BaseModel, frozen=True):
macaddress: MacAddress
last_connection_date: datetime
model: Optional[ModelRead] = None
class JwtAccessClaimsForDeviceRequests(BaseModel):
device_id: uuid.UUID
class JwtRefreshClaimsForDeviceRequests(BaseModel):
part_access: str
device_id: uuid.UUID
class TokenDataServer(BaseModel):
counter: int
hash_counter: str
@staticmethod
def hash(counter: int | str):
return binascii.hexlify(hashlib.md5(str(counter).encode()).digest())
@staticmethod
def default():
return TokenDataServer(
counter=0,
hash_counter=TokenDataServer.hash(0)
)
class TokensRead(BaseModel):
access_token: str
refresh_token: str
class DeviceCreate(BaseModel, from_attributes=True):
macaddress: MacAddress
is_active: bool
configuration: Json | dict
serial_number: int
hub: uuid.UUID | None = None
model: Optional[ModelRead] = None
class DeviceRead(DeviceCreate):
id: uuid.UUID
type_name: str = "Device"
hub: uuid.UUID | None = None
activated: bool = False
active_device: Optional[ActiveDeviceRead] = None
last_connection_date: datetime | None = None

View File

@ -0,0 +1 @@
{"doorlock": {"ip": "192.168.31.222", "host": "21488", "mac": "", "type": "phone"}, "phone": {"ip": "192.168.31.222", "host": "53970", "mac": "1", "type": "controller"}}

View File

@ -0,0 +1,89 @@
import hashlib
import uuid
import pytest
from sqlalchemy import select
from device import DeviceRead, Device, TokenDataServer
from smart_lock.schemas import SmartLockCreate, SmartLockRead
from test_database import *
from auth.test_auth import *
from smart_lock.test_smart_lock import get_json_for_create
@pytest.mark.asyncio
async def test_get_active_devices(auth_client: AsyncClient, session: AsyncSession):
# Добавляем устройства всех возможных типов
# todo более внятный тест
json_smart_lock = get_json_for_create()
json_smart_lock_2 = get_json_for_create()
json_smart_lock_2["smart_lock"]["hub"]["ip_address"] = "192.168.1.1/32"
json_smart_lock_2["smart_lock"]["serial_number"] = 344
json_smart_lock_2["smart_lock"]["macaddress"] = "22:11:22:ff:fe:33:44:55"
json_smart_lock_2["smart_lock"]["hub"]["serial_number"] = 99
response = await auth_client.post("/smart_lock/", json=json_smart_lock)
response = await auth_client.patch("for_device/activate", params={"token": response.json()["access_token"]})
response = await auth_client.post("/smart_lock/", json=json_smart_lock)
assert response.json()["activated"] is False
response = await auth_client.patch("for_device/activate", params={"token": response.json()["access_token"]})
response = await auth_client.post("/smart_lock/", json=json_smart_lock_2)
response = await auth_client.patch("for_device/activate", params={"token": response.json()["access_token"]})
response = await auth_client.get("/device/active_devices")
assert response.status_code == 200
json = response.json()
models: list[DeviceRead] = [DeviceRead.model_validate(i) for i in json]
assert len(models) == 2
# check polymorphism
excluded = {'id', 'last_interaction_date', 'active_device', "hub", "last_connection_date", "activated"}
json_smart_lock["smart_lock"]["hub"]["id"] = uuid.uuid4()
json_smart_lock_2["smart_lock"]["hub"]["id"] = uuid.uuid4()
assert (SmartLockRead(**json[0]).model_dump(exclude=excluded) ==
SmartLockRead(**json_smart_lock["smart_lock"], id=uuid.uuid4()).model_dump(exclude=excluded))
assert (SmartLockRead(**json[1]).model_dump(exclude=excluded) ==
SmartLockRead(**json_smart_lock_2["smart_lock"], id=uuid.uuid4()).model_dump(exclude=excluded))
@pytest.mark.asyncio
async def test_get_token_from_user(auth_client: AsyncClient, session: AsyncSession):
device = (await session.execute(select(Device))).scalar()
response = await auth_client.get("/device/token_from_user", params={"device_id": device.id})
assert response.status_code == 200
assert list(response.json().keys()) == ["access_token", "refresh_token"]
print(response.json())
@pytest.mark.asyncio
async def test_refresh_token(auth_client: AsyncClient, session: AsyncSession):
device = (await session.execute(select(Device))).scalar()
response = await auth_client.get("/device/token_from_user", params={"device_id": device.id})
assert response.status_code == 200
json = response.json()
json["counter_hash"] = TokenDataServer.hash(0).decode() # Не было обновений, значит ставим 0
# 1 обновление токена
response = await auth_client.get("/for_device/refresh_token", params=json)
assert response.status_code == 200
json1 = response.json()
response = await auth_client.get("/for_device/refresh_token", params=json)
assert response.status_code in (401, 403) # Token has been revoked
# 2 обновление токена со старым счетчиком (не должен сработать)
json1["counter_hash"] = json["counter_hash"]
response = await auth_client.get("/for_device/refresh_token", params=json1)
assert response.status_code in (401, 403) # invalid counter hash
# 2 обновление токена с обновленным счетчиком
json1["counter_hash"] = TokenDataServer.hash(1).decode()
response = await auth_client.get("/for_device/refresh_token", params=json1)
assert response.status_code == 200
# 3 обновление токена с обновленным счетчиком
# hashlib.md5(str(2).encode()).hexdigest()
json2 = response.json()
json2["counter_hash"] = TokenDataServer.hash(2).decode()
response = await auth_client.get("/for_device/refresh_token", params=json2)
assert response.status_code == 200

View File

@ -0,0 +1,12 @@
from fastapi import Depends
from .dependencies import jwt_websocket_create_access_token
from .router import router
from .schemas import DeviceWebsocketAuth
@router.post("/ws/token")
def get_access_token_for_connection_to_device(access_token: str = Depends(jwt_websocket_create_access_token)):
return {
"access_token": access_token
}

View File

@ -0,0 +1,40 @@
import abc
import os
from typing import Tuple
from fastapi import Depends, Query
from pydantic import BaseModel
from device.schemas import DeviceWebsocketAuth, TokenDataServer
# Множество заблокированных токенов доступа по вебсокетам. Токены одноразовые, но при дропе системы список должен
# очищаться (не хранится в постоянной памяти), чтобы устройства могли повторно подключиться к вебсокетам после
# восстановления
# todo перенести на redis
denylist = set()
tokens_data: dict[str, TokenDataServer] = dict()
class Settings(BaseModel):
authjwt_secret_key: str = os.environ.get("SECRET_DEVICE_AUTH_TOKEN")
authjwt_denylist_enabled: bool = True
authjwt_denylist_token_checks: set = {"access", "refresh"}
authjwt_refresh_token_expires: bool = False
def check_if_token_in_denylist(decrypted_token):
jti = decrypted_token['jti']
return jti in denylist
def get_config():
return Settings()
def jwt_websocket_required_auth(token: str = Query(...), authorize: None = Depends()) -> Tuple[dict, 'DeviceWebsocketAuth']:
authorize.jwt_required("websocket", token=token)
decoded_token = authorize.get_raw_jwt(token)
device = DeviceWebsocketAuth.model_validate(decoded_token)
return decoded_token, device

View File

@ -0,0 +1,67 @@
import datetime
import time
import json
import uvicorn
from fastapi import FastAPI, Depends, Body
from fastapi.staticfiles import StaticFiles
from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel
from sqlalchemy.exc import DBAPIError
from starlette.requests import Request
from starlette.responses import JSONResponse
from device.router import router, websocket_router
app = FastAPI(
title="Smart lock",
root_path="/devices"
)
@app.get("/docs", include_in_schema=False)
async def custom_swagger_ui_html(req: Request):
root_path = req.scope.get("root_path", "").rstrip("/")
openapi_url = root_path + app.openapi_url
return get_swagger_ui_html(
openapi_url=openapi_url,
title="API",
)
app.include_router(router)
app.include_router(websocket_router)
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
start_time = time.perf_counter()
response = await call_next(request)
process_time = time.perf_counter() - start_time
print(f"Finished in {process_time:.3f}")
response.headers["X-Process-Time"] = str(process_time)
return response
@app.exception_handler(DBAPIError)
def authjwt_exception_handler(request: Request, exc: DBAPIError):
return JSONResponse(
status_code=500,
content={"detail": jsonable_encoder(exc) | {"args": jsonable_encoder(exc.args)}}
)
# @app.exception_handler(Exception)
# def authjwt_exception_handler(request: Request, exc: Exception):
# print(exc.with_traceback(None))
# return JSONResponse(
# status_code=500,
# content={
# "name_exception": exc.__class__.__name__,
# "args": getattr(exc, "args", []),
# "detail": jsonable_encoder(exc)
# }
# )
if __name__ == '__main__':
uvicorn.run(app=app, host="0.0.0.0", port=8001)
# uvicorn.run(app="main:app", host="0.0.0.0", port=8000, reload=True)

View File

@ -0,0 +1,9 @@
fastapi==0.109.1
uvicorn
sqlalchemy
fastapi-users==12.1.2
python-dotenv
asyncpg
pydantic_extra_types
fastapi_jwt_auth
fastapi_users_db_sqlalchemy

View File

@ -0,0 +1,32 @@
version: '3.8'
services:
user_service:
env_file: ".env"
build:
context: ./user_service
dockerfile: Dockerfile
environment:
- PORT=8000
ports:
- "8000:8000"
device_service:
env_file: ".env"
build:
context: ./device_service
dockerfile: Dockerfile
environment:
- PORT=8001
ports:
- "8001:8001"
nginx:
image: nginx:latest
volumes:
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
ports:
- "80:80"
depends_on:
- user_service
- device_service

View File

@ -0,0 +1,11 @@
server {
listen 80;
location /users {
proxy_pass http://user_service:8000;
}
location /devices/ {
proxy_pass http://device_service:8001;
}
}

View File

@ -0,0 +1,48 @@
# Лабораторная работа №3: REST API, шлюз и синхронный обмен данными между микросервисами
## Задание реализовать два микросервиса связанные единой базой и сущностями один ко многим
- Первый микросервис user предоставляет методы для регистрации авторизации и выхода пользователей
- Второй микросервис реализует сущность устройства, которые связанные с пользователями связью многие ко многим
### Реализация
Оба микросервиса написаны на fastapi и развернуты на вебсервере uvicorn. В самом докере развертывание осущетсвляется через веб сервер nginx,
который осуществляет роль прокси, прослушивая 80 http порт и перенправляя запрос на тот или иной микросервис
Для корректной работы swagger-а необходимо перенести location из nginx.conf в fastapi, чтобы ему был известен корневой путь, для построения документации
`app = FastAPI(
title="Smart lock",
root_path="/devices"
)`
`location /devices/ {
proxy_pass http://device_service:8001;
}`
### Развертывание
В корневую папку добавляем файл .env, где указываем значение переменных среды
**Пример файла:**
`DB_HOST = localhost
DB_PORT = 5432
DB_NAME =
DB_USER = postgres
DB_PASS = 1234
SECRET_AUTH_TOKEN = 1
SECRET_VERIFICATE_TOKEN = 1
SECRET_DEVICE_AUTH_TOKEN = 1
DEVICE_START_COUNTER = 1
STATIC_URL = https://testapi.danserver.keenetic.name/static/`
Далее указываем:
docker-compose up --build
[Видео](https://disk.yandex.ru/d/BGenDWYY6tIGaw)

View File

@ -0,0 +1,10 @@
FROM python:3.11
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "--host", "0.0.0.0", "main:app", "--port", "8000"]

View File

@ -0,0 +1,6 @@
from .models import *
from .router import *
from .schemas import *
from .manager import *
from .base_config import *
from .dependencies import *

View File

@ -0,0 +1,34 @@
#
import uuid
from fastapi_users import FastAPIUsers
from fastapi_users.authentication import CookieTransport, AuthenticationBackend, BearerTransport
from fastapi_users.authentication import JWTStrategy
from .models import User
from .manager import get_user_manager
from config import SECRET_AUTH_TOKEN
__all__ = ["fastapi_users", "current_user", "auth_backend", "current_user_optional"]
cookie_transport = CookieTransport()
bearer_transport = BearerTransport("/token")
def get_jwt_strategy() -> JWTStrategy:
return JWTStrategy(secret=SECRET_AUTH_TOKEN, lifetime_seconds=None)
auth_backend = AuthenticationBackend(
name="jwt",
transport=cookie_transport,
get_strategy=get_jwt_strategy,
)
fastapi_users = FastAPIUsers[User, uuid.UUID](
get_user_manager,
[auth_backend],
)
current_user = fastapi_users.current_user()
current_user_optional = fastapi_users.current_user(optional=True)

View File

@ -0,0 +1,13 @@
#
from fastapi import Depends
from fastapi_users_db_sqlalchemy import SQLAlchemyUserDatabase
from sqlalchemy.ext.asyncio import AsyncSession
from .models import User
from database import get_async_session
__all__ = ["get_user_db"]
async def get_user_db(session: AsyncSession = Depends(get_async_session)):
yield SQLAlchemyUserDatabase(session, User)

View File

@ -0,0 +1,99 @@
#
import uuid
from typing import Optional
from fastapi import Depends, Request, Response, HTTPException
from fastapi.encoders import jsonable_encoder
from fastapi.security import OAuth2PasswordRequestForm
from fastapi_users import BaseUserManager, UUIDIDMixin, exceptions, models, schemas
from sqlalchemy.exc import IntegrityError
from sqlalchemy.sql.functions import user
from database import get_async_session
from .dependencies import get_user_db
from .models import *
from config import SECRET_VERIFICATE_TOKEN as SECRET
__all__ = ["UserManager", "get_user_manager"]
from .schemas import UserUpdate
class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):
reset_password_token_secret = SECRET
verification_token_secret = SECRET
async def on_after_register(self, user: User, request: Optional[Request] = None):
print(f"User {user.id} has registered.")
async def on_after_login(
self,
user: models.UP,
request: Optional[Request] = None,
response: Optional[Response] = None,
) -> None:
if user.fcm_token is not None:
await self.update(UserUpdate(fcm_token=user.fcm_token), user, request=request)
async def create(
self,
user_create: schemas.UC,
safe: bool = False,
request: Optional[Request] = None,
) -> models.UP:
await self.validate_password(user_create.password, user_create)
existing_user = await self.user_db.get_by_email(user_create.email)
if existing_user is not None:
raise exceptions.UserAlreadyExists()
user_dict = (
user_create.create_update_dict()
if safe
else user_create.create_update_dict_superuser()
)
password = user_dict.pop("password")
user_dict["hashed_password"] = self.password_helper.hash(password)
self._link_or_create_entity(user_dict, "group", Group)
try:
created_user = await self.user_db.create(user_dict)
except IntegrityError as e:
if 'group' not in e.statement:
raise e
detail = {
"msg": f"Некорректный идентификатор группы {user_dict.get('group_id')=} введенный при регистрации",
"inner_exception": jsonable_encoder(e)
}
raise HTTPException(status_code=403, detail=detail)
if hasattr(self.user_db, "session"):
await created_user.include_relationship_for_read(self.user_db.session)
await self.on_after_register(created_user, request)
return created_user
async def authenticate(
self, credentials: OAuth2PasswordRequestForm
) -> Optional[models.UP]:
user_auth = await super().authenticate(credentials)
if user_auth is None:
return None
user_auth.fcm_token = credentials.fcm_token
return user_auth
@staticmethod
def _link_or_create_entity(user_dict: dict, name: str, EntityType):
entity = user_dict.get(name)
if isinstance(entity, (uuid.UUID, int)) or entity is None:
user_dict.pop(name, None)
user_dict[name + "_id"] = entity
else:
user_dict[name] = EntityType(**entity)
async def get_user_manager(user_db=Depends(get_user_db)):
yield UserManager(user_db)

View File

@ -0,0 +1,52 @@
import uuid
from datetime import datetime
from fastapi_users_db_sqlalchemy import SQLAlchemyBaseUserTable
from sqlalchemy import Table, Column, Integer, String, TIMESTAMP, ForeignKey, JSON, Boolean, MetaData, DateTime, UUID, \
ARRAY, DECIMAL, DOUBLE_PRECISION
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import relationship
from database import Base
__all__ = ["Group", "User"]
class Group(Base):
__tablename__ = "groups"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
name = Column(String, nullable=False)
comment = Column(String, nullable=True)
users = relationship("User", back_populates="group")
class User(SQLAlchemyBaseUserTable[uuid.UUID], Base):
__tablename__ = 'users'
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
email = Column(String, nullable=False)
name = Column(String, nullable=False)
surname = Column(String, nullable=False)
patronymic = Column(String, nullable=True)
creation_on = Column(TIMESTAMP, default=datetime.utcnow)
last_entry = Column(TIMESTAMP, nullable=True)
group_id = Column(ForeignKey(Group.id), nullable=True)
hashed_password = Column(String(length=1024), nullable=False)
is_active = Column(Boolean, default=True, nullable=False)
is_superuser = Column(Boolean, default=False, nullable=False)
is_verified = Column(Boolean, default=False, nullable=False)
token = Column(String, nullable=True)
fcm_token = Column(String, nullable=True) # Идентификатор устройства для уведомлений
hashes_rfids = Column(ARRAY(String), default=[])
face_embedding = Column(ARRAY(DOUBLE_PRECISION), nullable=True)
devices = relationship("Device", secondary="active_devices", back_populates="users", viewonly=True)
active_devices = relationship("ActiveDevice")
group = relationship(Group, uselist=False, back_populates="users")
notifications = relationship("Notification", back_populates="user")
async def include_relationship_for_read(self, session: AsyncSession):
await session.refresh(self, ["group"])
return self

View File

@ -0,0 +1,25 @@
from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from database import get_async_session
from . import User
from .base_config import current_user
from .schemas import UserRead
__all__ = ["user_router"]
user_router = APIRouter(prefix='/auth', tags=['Auth'])
@user_router.get("/current_user")
async def get_current_user(user=Depends(current_user), session=Depends(get_async_session)) -> UserRead:
return await user.include_relationship_for_read(session)
@user_router.patch("/fcm_token/{fcm_token}")
async def refresh_fcm_token(fcm_token: str | None = None,
user=Depends(current_user), session=Depends(get_async_session)) -> UserRead:
if fcm_token is not None:
user.fcm_token = fcm_token
await session.commit()
return user

View File

@ -0,0 +1,39 @@
#
import datetime
import uuid
from typing import Optional, Union
from sqlalchemy import TIMESTAMP
from fastapi_users import schemas
from pydantic import EmailStr, BaseModel, Json
__all__ = ["GroupRead", "GroupCreate", "UserRead", "UserCreate", "UserUpdate"]
class GroupCreate(BaseModel):
name: str
comment: Optional[str]
class GroupRead(GroupCreate, from_attributes=True):
id: uuid.UUID
class UserRead(schemas.BaseUser[uuid.UUID], from_attributes=True):
name: str
surname: str
patronymic: Optional[str]
creation_on: datetime.datetime
fcm_token: Optional[str] = None
group: Optional[GroupRead] = None
class UserCreate(schemas.BaseUserCreate):
name: str
surname: str
patronymic: Optional[str] = None
group: Union[None, uuid.UUID, GroupCreate] = None
class UserUpdate(schemas.BaseUserUpdate):
fcm_token: Optional[str] = None

View File

@ -0,0 +1,98 @@
import httpx
import pytest
from fastapi import Depends
from test_database import *
from . import User
from .schemas import UserCreate, GroupCreate, UserRead
__all__ = ["client", "auth_client", "get_generate_user", "session"]
def _add_password(user: User, password: str):
(user if isinstance(user, dict) else user.__dict__)["password"] = password
return user
def get_generate_user(unique=False):
if not unique:
return UserCreate(
name="user",
surname="test",
group=GroupCreate(name="test", comment="test_comment"),
email="user@localhost.ru",
password="<PASSWORD>"
)
@pytest_asyncio.fixture(scope="session")
async def auth_client(app) -> AsyncClient:
async with AsyncClient(app=app, base_url="http://127.0.0.1:9000") as client:
user = get_generate_user()
await client.post("/auth/register", json=user.model_dump())
response = await client.post("/auth/login", data={
'username': user.email,
'password': user.password
})
cookie = list(dict(response.cookies).items())[-1]
client.headers["Cookie"] = "=".join(cookie)
yield client
@pytest.mark.asyncio
async def test_register_user(client, session) -> UserCreate:
user = get_generate_user()
response = await client.post("/auth/register", json=user.model_dump())
assert response.status_code == 201, response.json()
result = response.json()
db_user = await (await session.get(User, result["id"])).include_relationship_for_read(session)
assert db_user is not None
assert result.pop("id", None) is not None, response.json()
compare_model = UserCreate.model_validate(_add_password(result, user.password)).model_dump()
db_compare_model = UserCreate.model_validate(_add_password(db_user, user.password), from_attributes=True).model_dump()
assert compare_model == db_compare_model
assert compare_model == user.model_dump()
assert user.model_dump() == db_compare_model
response = await client.post("/auth/register", json=user.model_dump())
assert response.status_code == 400, response.json()
return user
@pytest.mark.asyncio
async def test_login_user(client) -> AsyncClient:
user = get_generate_user()
await client.post("/auth/register", json=user.model_dump())
response = await client.post("/auth/login", data={
'username': user.email,
'password': user.password + "0"
})
assert response.status_code == 400, response.json()
response = await client.post("/auth/login", data={
'username': user.email,
'password': user.password
})
assert response.status_code == 200 or response.status_code == 204, response.json()
return user
@pytest.mark.asyncio
async def test_authorization_and_logout(auth_client):
client: httpx.AsyncClient = auth_client
response = await client.get("auth/current_user")
assert response.status_code == 200
result_user = UserRead(**response.json())
sourse_user = get_generate_user()
assert result_user.email == sourse_user.email
assert result_user.name == sourse_user.name
response = await client.post("/auth/logout")
assert response.status_code == 200 or response.status_code == 204
assert not response.cookies
auth_cookies = client.headers.pop("Cookie")
response = await client.get("/auth/current_user")
assert response.status_code == 401
client.headers["Cookie"] = auth_cookies

View File

@ -0,0 +1,21 @@
import os
import logging
from dotenv import load_dotenv
load_dotenv()
DB_HOST = os.environ.get("DB_HOST")
DB_PORT = os.environ.get("DB_PORT")
DB_NAME = os.environ.get("DB_NAME")
DB_USER = os.environ.get("DB_USER")
DB_PASS = os.environ.get("DB_PASS")
SECRET_AUTH_TOKEN = os.environ.get("SECRET_AUTH_TOKEN")
SECRET_VERIFICATE_TOKEN = os.environ.get("SECRET_VERIFICATE_TOKEN")
DEVICE_START_COUNTER = os.environ.get("DEVICE_START_COUNTER")
logging.getLogger('passlib').setLevel(logging.ERROR)

View File

@ -0,0 +1,49 @@
import time
from sys import gettrace
from typing import AsyncGenerator
from fastapi import HTTPException
from fastapi.encoders import jsonable_encoder
from sqlalchemy import MetaData
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from config import DB_HOST, DB_NAME, DB_PASS, DB_PORT, DB_USER
from pydantic import BaseModel
from sqlalchemy import Table, Column, Integer, String, TIMESTAMP, ForeignKey, Boolean, MetaData, PrimaryKeyConstraint
from sqlalchemy.orm import DeclarativeBase
class Base(DeclarativeBase):
def fill_from_model(self, model: BaseModel | dict):
"""Заполняет все поля из словаря или модели, помечая объект 'грязным' для корректного обновления"""
if isinstance(model, BaseModel):
items = model.model_dump().items()
else:
items = model.items()
for key, value in items:
setattr(self, key, value)
DATABASE_URL = f"postgresql+asyncpg://{DB_USER}:{DB_PASS}@{DB_HOST}:{DB_PORT}/{DB_NAME}"
engine = create_async_engine(DATABASE_URL, echo=gettrace() is not None)
async_session_maker = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
async def get_async_session() -> AsyncGenerator[AsyncSession, None]:
async with async_session_maker() as session:
yield session
async def begin_transactions() -> AsyncGenerator[AsyncSession, None]:
async with async_session_maker().begin() as transaction:
try:
yield transaction.session
except Exception as e:
await transaction.rollback()
raise e
await transaction.commit()

View File

@ -0,0 +1,81 @@
import datetime
import time
import json
# import firebase_admin
import uvicorn
from fastapi import FastAPI, Depends, Body
from fastapi.staticfiles import StaticFiles
from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel
from sqlalchemy.exc import DBAPIError
from starlette.requests import Request
from starlette.responses import JSONResponse
from auth import *
app = FastAPI(
title="Smart lock",
root_path="/users"
)
@app.get("/docs", include_in_schema=False)
async def custom_swagger_ui_html(req: Request):
root_path = req.scope.get("root_path", "").rstrip("/")
openapi_url = root_path + app.openapi_url
return get_swagger_ui_html(
openapi_url=openapi_url,
title="API",
)
app.include_router(
fastapi_users.get_auth_router(auth_backend),
prefix="/auth",
tags=["Auth"],
)
app.include_router(
fastapi_users.get_register_router(UserRead, UserCreate),
prefix="/auth",
tags=["Auth"],
)
# firebase
# cred = credentials.Certificate("../serviceAccountKey.json")
# try:
# fire_app = firebase_admin.initialize_app(cred)
# except ValueError:
# pass
class Message(BaseModel):
msg: str
@app.post("/msg")
def debug_message_from_user(msg: Message = Body(), user: User = Depends(current_user_optional)):
print(datetime.datetime.now(), end=' ')
if user is not None:
print(user.__dict__)
print(msg.msg)
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
start_time = time.perf_counter()
response = await call_next(request)
process_time = time.perf_counter() - start_time
print(f"Finished in {process_time:.3f}")
response.headers["X-Process-Time"] = str(process_time)
return response
@app.exception_handler(DBAPIError)
def authjwt_exception_handler(request: Request, exc: DBAPIError):
return JSONResponse(
status_code=500,
content={"detail": jsonable_encoder(exc) | {"args": jsonable_encoder(exc.args)}}
)
if __name__ == '__main__':
uvicorn.run(app=app, host="0.0.0.0", port=8000)

View File

@ -0,0 +1,7 @@
fastapi
uvicorn
fastapi-users
sqlalchemy
fastapi_users_db_sqlalchemy
python-dotenv
asyncpg

View File

@ -0,0 +1,30 @@
import pika
import time
def callback(ch, method, properties, body):
print(f'Consumer 1 получил сообщение: {body.decode()}')
# Время задержки по условию
time.sleep(2)
print('Consumer 1 закончил обработку')
def consume_events_1():
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
# Создание очереди
channel.queue_declare(queue='consumer1_queue')
# Привязка очереди
channel.queue_bind(exchange='beauty_salon_events', queue='consumer1_queue')
channel.basic_consume(queue='consumer1_queue', on_message_callback=callback, auto_ack=True)
print('Consumer 1 начал ожидать сообщения...')
channel.start_consuming()
if __name__ == "__main__":
consume_events_1()

View File

@ -0,0 +1,28 @@
import pika
def callback(ch, method, properties, body):
print(f'Consumer 2 получил сообщение: {body.decode()}')
# Обработка "нон-стопом"
print('Consumer 2 закончил обработку')
def consume_events_2():
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
# Создание очереди
channel.queue_declare(queue='consumer2_queue')
# Привязка очереди
channel.queue_bind(exchange='beauty_salon_events', queue='consumer2_queue')
channel.basic_consume(queue='consumer2_queue', on_message_callback=callback, auto_ack=True)
print('Consumer 2 начал ожидать сообщения...')
channel.start_consuming()
if __name__ == "__main__":
consume_events_2()

View File

@ -0,0 +1,51 @@
# Лабораторная работа №4 - Работа с брокером сообщений
+ Установить брокер сообщений RabbitMQ.
+ Пройти уроки 1, 2 и 3 из RabbitMQ Tutorials на любом языке программирования.
+ Продемонстрировать работу брокера сообщений.
## Описание работы
**Publisher** - осуществляет отправку сообщений своим клиентам.
**Consumer1** - принимает и обрабатывает сообщения с задержкой в 2-3 секунды.
**Consumer2** - моментально принимает и обрабатывает сообщения.
### Tutorials
1. tutorial_1
![tutorial_1.png](Screenshots/tutorial_1.png)
2. tutorial_2
![tutorial_2.png](Screenshots/tutorial_2.png)
3. tutorial_3
![tutorial_3.png](Screenshots/tutorial_3.png)
## Работа с RabbitMQ
![rabbitMQ.png](Screenshots/rabbitMQ.png)
## Показания очереди queue_1 при одном запущенном экземпляре Consumer_1
![queue_1_1.png](Screenshots/queue_1 _1.png)
## Показания очереди queue_2
![queue_2_1.png](Screenshots/queue_2_1.png)
## Показания очереди queue_1 при двух запущенных экземплярах Consumer_1
![queue_1_2.png](Screenshots/queue_1 _2.png)
## Показания очереди queue_1 при трех запущенных экземплярах Consumer_1
![queue_1_3.png](Screenshots/queue_1 _3.png)
## Видеозапись работы программы
https://disk.yandex.ru/d/TAdJwo36RrN4ag

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -0,0 +1,28 @@
import pika
import time
def publish_events():
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
# Создание exchange типа fanout
channel.exchange_declare(exchange='beauty_salon_events', exchange_type='fanout')
events = [
"Test1",
"Test2",
"Test3",
"Test4",
"Test5"
]
while True:
event = events[int(time.time()) % len(events)]
channel.basic_publish(exchange='beauty_salon_events', routing_key='', body=event)
print(f'Отправлено: {event}')
time.sleep(1)
if __name__ == "__main__":
publish_events()

View File

@ -0,0 +1,25 @@
import pika, sys, os
def main():
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.queue_declare(queue='hello')
def callback(ch, method, properties, body):
print(f" [x] Received {body}")
channel.basic_consume(queue='hello', on_message_callback=callback, auto_ack=True)
print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
print('Interrupted')
try:
sys.exit(0)
except SystemExit:
os._exit(0)

View File

@ -0,0 +1,11 @@
import pika
connection = pika.BlockingConnection(
pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.queue_declare(queue='hello')
channel.basic_publish(exchange='', routing_key='hello', body='Hello World!')
print(" [x] Sent 'Hello World!'")
connection.close()

View File

@ -0,0 +1,19 @@
import pika
import sys
connection = pika.BlockingConnection(
pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.queue_declare(queue='task_queue', durable=True)
message = ' '.join(sys.argv[1:]) or "Hello World!"
channel.basic_publish(
exchange='',
routing_key='task_queue',
body=message,
properties=pika.BasicProperties(
delivery_mode=pika.DeliveryMode.Persistent
))
print(f" [x] Sent {message}")
connection.close()

View File

@ -0,0 +1,23 @@
#!/usr/bin/env python
import pika
import time
connection = pika.BlockingConnection(
pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.queue_declare(queue='task_queue', durable=True)
print(' [*] Waiting for messages. To exit press CTRL+C')
def callback(ch, method, properties, body):
print(f" [x] Received {body.decode()}")
time.sleep(body.count(b'.'))
print(" [x] Done")
ch.basic_ack(delivery_tag=method.delivery_tag)
channel.basic_qos(prefetch_count=1)
channel.basic_consume(queue='task_queue', on_message_callback=callback)
channel.start_consuming()

View File

@ -0,0 +1,13 @@
import pika
import sys
connection = pika.BlockingConnection(
pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.exchange_declare(exchange='logs', exchange_type='fanout')
message = ' '.join(sys.argv[1:]) or "info: Hello World!"
channel.basic_publish(exchange='logs', routing_key='', body=message)
print(f" [x] Sent {message}")
connection.close()

View File

@ -0,0 +1,22 @@
import pika
connection = pika.BlockingConnection(
pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.exchange_declare(exchange='logs', exchange_type='fanout')
result = channel.queue_declare(queue='', exclusive=True)
queue_name = result.method.queue
channel.queue_bind(exchange='logs', queue=queue_name)
print(' [*] Waiting for logs. To exit press CTRL+C')
def callback(ch, method, properties, body):
print(f" [x] {body}")
channel.basic_consume(
queue=queue_name, on_message_callback=callback, auto_ack=True)
channel.start_consuming()

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

View File

@ -0,0 +1,68 @@
import multiprocessing
import random
import time
from pprint import pprint
from multiprocessing import Pool
def create_random_matrix(size):
return [[random.random() for _ in range(size)] for __ in range(size)]
# def matrix_multiply_seq(matrix1: list[list[float]], matrix2: list[list[float]]) -> list[list[float]]:
# """Выполняет последовательное перемножение двух матриц и возвращает результирующую матрицу"""
# l1 = len(matrix1)
# l2 = len(matrix2)
# result = [[0 for _ in range(l2)] for __ in range(l1)]
# for i in range(l1):
# for j in range(l2):
# for k in range(l2):
# result[i][j] += matrix1[i][k] * matrix2[k][j]
# return result
matrix_result = [[0 for _ in range(1000)] for __ in range(1000)]
def matrix_multiply(args) -> None:
"""Перемножает строки от start_cnt до end_cnt, результат помещает в глобальную переменную matrix_result"""
matrix1, matrix2, start_cnt, end_cnt = args
for i in range(start_cnt, end_cnt):
for j in range(len(matrix2)):
for k in range(len(matrix2)):
matrix_result[i][j] += matrix1[i - start_cnt][k] * matrix2[k][j]
def matrix_multiply_parralel(matrix1: list[list[float]], matrix2: list[list[float]], thread_count):
"""Выполняет парралеьное перемножение матриц"""
l1 = len(matrix1)
step = l1 // thread_count
args = [(matrix1, matrix2, i, i + step) for i in range(0, l1, step)]
args[-1] = (matrix1, matrix2, step * (l1 - 1), l1) # Остаток на последний поток
with Pool(processes=thread_count) as pool:
pool.map(matrix_multiply, args)
# pprint(matrix_result, compact=True)
def main():
sizes = [100, 300, 500, 1000]
num_threads = [2, 4, 6, 8, 12, 16, 20]
print(f"cpu_count: {multiprocessing.cpu_count()}")
for size in sizes:
matrix1 = create_random_matrix(size)
matrix2 = create_random_matrix(size)
t0 = time.perf_counter()
matrix_multiply((matrix1, matrix2, 0, len(matrix1)))
# pprint(matrix_result[:size][:size], compact=True)
print(f"Время последовательного перемножения матриц {size=:4}: \t\t\t\t{time.perf_counter() - t0:.3f}s")
for threads in num_threads:
start_time = time.perf_counter()
matrix_multiply_parralel(matrix1, matrix2, threads)
end_time = time.perf_counter()
print(f"Время парралельного перемножения матриц {size=:4}, {threads=} : \t{end_time - start_time:.3f}")
print("-" * 100)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,17 @@
# Аглиуллов Данияр ИСЭбд-41
# Лабораторная работа №5
В ходе выполнения задачи по умножению квадратных матриц с использованием обычного и параллельного алгоритмов были получены следующие результаты и выводы:
Результаты тестов:
![изображение 1](./Screenshots/1.png)
![изображение 2](./Screenshots/Снимок%20экрана%202024-11-13%20160640.png)
Сравнение производительности:
В тестах на матрицах размером 100x100, 300x300 и 500x500 было замечено, что параллельный алгоритм демонстрирует значительное сокращение времени выполнения по сравнению с обычным алгоритмом, особенно на больших матрицах. Это подтверждает эффективность использования многопоточности для задач, требующих больших вычислительных ресурсов.
На малых размерах матриц (например, 100x100) преимущество было за последовательным умножением матрицы из-за накладных расходов на создание пула потоков. Однако при увеличении размера матриц (300x300 и 500x500) преимущества параллельного подхода становились более очевидными.
• При превышении количества физических потоков процессора, производительность понижается за счет смены контекста при переключении виртуальных потоков на одном ядре
![Видео](https://disk.yandex.ru/d/RK_c82BrLw2KjQ)

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

View File

@ -0,0 +1,86 @@
import numpy as np
import time
from multiprocessing import Pool
np.seterr(over='ignore')
# Функция для вычисления детерминанта методом Гаусса
def compute_determinant_gauss(mat):
size = mat.shape[0]
matrix_copy = mat.astype(float) # Копируем матрицу, чтобы не изменять исходную
determinant = 1.0 # Начальное значение детерминанта
for k in range(size):
# Находим максимальный элемент в текущем столбце для уменьшения ошибок округления
max_index = np.argmax(np.abs(matrix_copy[k:size, k])) + k
if matrix_copy[max_index, k] == 0:
return 0 # Если на главной диагонали ноль, детерминант равен нулю
# Меняем местами строки
if max_index != k:
matrix_copy[[k, max_index]] = matrix_copy[[max_index, k]]
determinant *= -1 # Каждая перестановка меняет знак детерминанта
# Обнуляем элементы ниже главной диагонали
for m in range(k + 1, size):
multiplier = matrix_copy[m, k] / matrix_copy[k, k]
matrix_copy[m, k:] -= multiplier * matrix_copy[k, k:]
# Произведение элементов на главной диагонали
for j in range(size):
determinant *= matrix_copy[j, j]
return determinant
# Функция для параллельного вычисления детерминанта
def parallel_worker(index_range, mat):
size = mat.shape[0]
matrix_copy = mat.astype(float)
det = 1.0
for k in range(index_range[0], index_range[1]):
max_index = np.argmax(np.abs(matrix_copy[k:size, k])) + k
if matrix_copy[max_index, k] == 0:
return 0
if max_index != k:
matrix_copy[[k, max_index]] = matrix_copy[[max_index, k]]
det *= -1
for m in range(k + 1, size):
multiplier = matrix_copy[m, k] / matrix_copy[k, k]
matrix_copy[m, k:] -= multiplier * matrix_copy[k, k:]
return det
# Функция для параллельного вычисления детерминанта
def compute_parallel_determinant(mat, num_workers):
size = mat.shape[0]
block_size = size // num_workers
ranges = [(i * block_size, (i + 1) * block_size) for i in range(num_workers)]
with Pool(processes=num_workers) as pool:
results = pool.starmap(parallel_worker, [(block, mat) for block in ranges])
# Объединяем результаты
total_determinant = sum(results)
return total_determinant
# Функция для запуска тестов производительности
def execute_benchmarks():
sizes = [100, 300, 500] # Размеры матриц
for size in sizes:
random_matrix = np.random.rand(size, size) # Генерация случайной матрицы
print(f"--- Тест производительности для матрицы {size}x{size} ---")
# Последовательное вычисление детерминанта
start_time = time.time()
sequential_det = compute_determinant_gauss(random_matrix)
seq_duration = time.time() - start_time
print(f"Время последовательного вычисления для {size}x{size}: {seq_duration:.4f} секунд")
# Параллельное вычисление с различным количеством процессов
for workers in [1, 2, 4, 6, 8, 12, 16]:
start_time = time.time()
parallel_det = compute_parallel_determinant(random_matrix, workers)
par_duration = time.time() - start_time
speedup_ratio = seq_duration / par_duration if par_duration > 0 else 0
print(f"Параллельное время с {workers} процессами: {par_duration:.4f} секунд, Ускорение: {speedup_ratio:.2f}")
# Запуск тестов производительности
if __name__ == '__main__':
execute_benchmarks()

View File

@ -0,0 +1,16 @@
# Аглиуллов Данияр ИСЭбд-41
# Лабораторная работа №6
Для повышения производительности при вычислении детерминанта для больших матриц была добавлена возможность параллельной обработки с использованием библиотеки multiprocessing. Это позволило значительно ускорить вычисления за счет распределения нагрузки между несколькими процессами.
Результаты тестов:
![изображение 1](../Screenshots/1.png)
Сравнение производительности:
В тестах на матрицах размером 100x100, 300x300 и 500x500 было замечено, что параллельный алгоритм демонстрирует значительное сокращение времени выполнения по сравнению с обычным алгоритмом, особенно на больших матрицах. Это подтверждает эффективность использования многопоточности для задач, требующих больших вычислительных ресурсов.
На малых размерах матриц (например, 100x100) преимущество было за последовательным умножением матрицы из-за накладных расходов на создание пула потоков. Однако при увеличении размера матрицы (300x300 и 500x500) преимущества параллельного подхода становились более очевидными.
• При превышении количества физических потоков процессора, производительность понижается за счет смены контекста при переключении виртуальных потоков на одном ядре
![Видео](https://disk.yandex.ru/d/MEZQvGM8u9OIBw)

View File

@ -0,0 +1,24 @@
# Лабораторная работа 7. Балансировка нагрузки в распределённых системах с использованием открытых технологий
## Задание
Написать краткое эссе, отвечая на следующие вопросы:
1. Какие алгоритмы и методы применяются для балансировки нагрузки?
2. Какие открытые технологии доступны для этой задачи?
3. Как осуществляется балансировка нагрузки в системах управления базами данных?
4. Какова роль реверс-прокси в процессе балансировки нагрузки?
## Эссе по теме
Балансировка нагрузки является ключевым аспектом проектирования распределённых систем, позволяя равномерно распределять входящие запросы между несколькими серверами или ресурсами. Это способствует повышению доступности, производительности и устойчивости к сбоям системы.
Существует множество алгоритмов и методов, применяемых для балансировки нагрузки. К ним относятся Least Connections (минимальное количество соединений), Round Robin (круговая схема), Server Health Check (проверка состояния сервера), Least Response Time (минимальное время ответа) и Random (случайный выбор). Эти подходы помогают эффективно распределять клиентские запросы с учётом текущей загруженности серверов.
Среди открытых технологий для балансировки нагрузки можно выделить Nginx, HAProxy, Apache HTTP Server и другие. Эти инструменты позволяют настроить прокси-серверы, которые принимают запросы от пользователей и направляют их на один или несколько серверов приложений, обеспечивая равномерное распределение нагрузки.
Балансировка нагрузки в базах данных важна для поддержания доступности и производительности системы. Для этого используются специализированные решения, такие как ProxySQL, pgpool-II и MySQL Router. Они помогают распределять запросы к базам данных между различными узлами, что позволяет снизить нагрузку на основной сервер.
Реверс-прокси представляет собой сервер, который принимает запросы от клиентов и перенаправляет их на один или несколько серверов приложений. Он выполняет множество функций, включая балансировку нагрузки, обеспечение безопасности, кэширование, SSL-терминацию, а также мониторинг и ведение логов.

View File

@ -0,0 +1,52 @@
# Лабораторная работа 8. Как Вы поняли, что называется распределенной системой и как она устроена?
## Задание:
Написать небольшое эссе (буквально несколько абзацев) своими словами на тему "Устройство распределенных систем". Вопросы:
1. Зачем сложные системы (например, социальная сеть ВКонтакте) пишутся в "распределенном" стиле, где каждое отдельное приложение (или сервис) функционально выполняет только ограниченный спектр задач?
2. Для чего были созданы системы оркестрации приложений? Каким образом они упрощают / усложняют разработку и сопровождение распределенных систем?
3. Для чего нужны очереди обработки сообщений и что может подразумеваться под сообщениями?
4. Какие преимущества и недостатки распределенных приложений существуют на Ваш взгляд?
5. Целесообразно ли в сложную распределенную систему внедрять параллельные вычисления? Приведите примеры, когда это действительно нужно, а когда нет.
## Преимущества распределённых приложений
1. Высокая доступность и отказоустойчивость: Один из основных плюсов распределённых систем заключается в том, что они могут продолжать функционировать даже при сбое отдельных компонентов. Это достигается за счёт дублирования сервисов и автоматического перенаправления запросов на работающие узлы.
2. Масштабируемость: Распределённые системы легко масштабируются. При увеличении нагрузки можно добавлять новые серверы или ресурсы без значительных изменений в архитектуре.
3. Обработка больших объёмов данных: Распределённые приложения могут одновременно обрабатывать множество запросов и большие объёмы данных, что делает их идеальными для таких задач, как анализ больших данных и машинное обучение.
## Недостатки распределённых приложений
1. Сложность архитектуры: Архитектура распределённых систем может быть значительно сложнее, чем у монолитных приложений. Это затрудняет отладку и мониторинг, так как необходимо учитывать взаимодействие множества компонентов.
2. Проблемы с согласованностью данных: В распределённых системах может возникнуть несогласованность данных между различными сервисами, особенно если они работают с копиями одних и тех же данных.
3. Управление безопасностью: Увеличение числа взаимодействий между компонентами системы требует более тщательного управления безопасностью, так как это повышает вероятность уязвимостей и атак.
## Внедрение параллельных вычислений
Параллельные вычисления в распределённых системах оправданы, когда необходимо обрабатывать большие объёмы данных или выполнять сложные вычисления. Например:
• Машинное обучение: Обучение моделей на больших наборах данных может быть значительно ускорено за счёт распараллеливания вычислений.
• Анализ больших данных: Параллельные вычисления позволяют эффективно обрабатывать данные, разбивая их на части и распределяя по нескольким узлам.
Однако стоит помнить, что не всегда параллельные вычисления оправданы:
• Если задача не требует значительных ресурсов или имеет низкую степень параллелизма, внедрение параллельных вычислений может усложнить архитектуру без ощутимой выгоды.
• Простые операции, такие как CRUD-операции, могут быть более эффективно реализованы в рамках монолитной архитектуры без необходимости распараллеливания.
## Эссе на тему
Распределенные приложения имеют множество преимуществ. Во-первых, они обеспечивают высокую доступность и отказоустойчивость: если один компонент выходит из строя, остальные продолжают функционировать. Во-вторых, они масштабируемы: можно легко добавлять новые ресурсы по мере необходимости. В-третьих, распределенные системы могут обрабатывать большие объемы данных и запросов одновременно. Однако у них есть и недостатки. Сложность архитектуры может привести к трудностям в отладке и мониторинге. Также возможны проблемы с согласованностью данных между различными сервисами. Наконец, распределенные системы требуют более тщательного управления безопасностью, так как большее количество взаимодействий увеличивает вероятность уязвимостей.
Внедрение параллельных вычислений в распределенные системы целесообразно в тех случаях, когда необходимо обрабатывать большие объемы данных или выполнять сложные вычисления. Например, в задачах машинного обучения или анализа больших данных параллельные вычисления позволяют значительно сократить время обработки. Однако не всегда параллельные вычисления оправданы. Если задача не требует значительных ресурсов или имеет низкую степень параллелизма, то их внедрение может усложнить архитектуру без ощутимой выгоды. Например, простые CRUD-операции (создание, чтение, обновление и удаление) могут быть более эффективно реализованы без использования параллельных вычислений.

View File

@ -0,0 +1,30 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
!**/.gitignore
!.git/HEAD
!.git/config
!.git/packed-refs
!.git/refs/heads/**

View File

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ActiveDebugProfile>Container (Dockerfile)</ActiveDebugProfile>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,28 @@
# См. статью по ссылке https://aka.ms/customizecontainer, чтобы узнать как настроить контейнер отладки и как Visual Studio использует этот Dockerfile для создания образов для ускорения отладки.
# Этот этап используется при запуске из VS в быстром режиме (по умолчанию для конфигурации отладки)
FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base
USER app
WORKDIR /app
# Этот этап используется для сборки проекта службы
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["ConsoleApp1/ConsoleApp1.csproj", "ConsoleApp1/"]
RUN dotnet restore "./ConsoleApp1/ConsoleApp1.csproj"
COPY . .
WORKDIR "/src/ConsoleApp1"
RUN dotnet build "./ConsoleApp1.csproj" -c $BUILD_CONFIGURATION -o /app/build
# Этот этап используется для публикации проекта службы, который будет скопирован на последний этап
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./ConsoleApp1.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
# Этот этап используется в рабочей среде или при запуске из VS в обычном режиме (по умолчанию, когда конфигурация отладки не используется)
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "ConsoleApp1.dll"]

View File

@ -0,0 +1,36 @@
using System;
using System.IO;
class Program
{
static void Main()
{
const string inputDir = "/var/data";
const string outputFile = "/var/result/data.txt";
try
{
using (var writer = new StreamWriter(outputFile))
{
var files = Directory.GetFiles(inputDir);
foreach (var file in files)
{
using (var reader = new StreamReader(file))
{
string firstLine = reader.ReadLine();
if (!string.IsNullOrEmpty(firstLine))
{
writer.WriteLine(firstLine);
}
}
}
}
Console.WriteLine($"Файл {outputFile} успешно создан.");
}
catch (Exception ex)
{
Console.WriteLine($"Ошибка: {ex.Message}");
}
}
}

View File

@ -0,0 +1,10 @@
{
"profiles": {
"ConsoleApp1": {
"commandName": "Project"
},
"Container (Dockerfile)": {
"commandName": "Docker"
}
}
}

View File

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ActiveDebugProfile>Container (Dockerfile)</ActiveDebugProfile>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,28 @@
# См. статью по ссылке https://aka.ms/customizecontainer, чтобы узнать как настроить контейнер отладки и как Visual Studio использует этот Dockerfile для создания образов для ускорения отладки.
# Этот этап используется при запуске из VS в быстром режиме (по умолчанию для конфигурации отладки)
FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base
USER app
WORKDIR /app
# Этот этап используется для сборки проекта службы
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["ConsoleApp2/ConsoleApp2.csproj", "ConsoleApp2/"]
RUN dotnet restore "./ConsoleApp2/ConsoleApp2.csproj"
COPY . .
WORKDIR "/src/ConsoleApp2"
RUN dotnet build "./ConsoleApp2.csproj" -c $BUILD_CONFIGURATION -o /app/build
# Этот этап используется для публикации проекта службы, который будет скопирован на последний этап
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./ConsoleApp2.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
# Этот этап используется в рабочей среде или при запуске из VS в обычном режиме (по умолчанию, когда конфигурация отладки не используется)
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "ConsoleApp2.dll"]

View File

@ -0,0 +1,34 @@
using System;
using System.IO;
using System.Linq;
class Program
{
static void Main()
{
const string inputFile = "/var/result/data.txt";
const string outputFile = "/var/result/result.txt";
try
{
var lines = File.ReadAllLines(inputFile).Select(int.Parse).ToArray();
if (lines.Length >= 2)
{
int result = lines.First() * lines.Last();
File.WriteAllText(outputFile, result.ToString());
Console.WriteLine($"Произведение: {result}");
}
else
{
Console.WriteLine("Недостаточно данных в файле для вычисления.");
}
}
catch (Exception ex)
{
Console.WriteLine($"Ошибка: {ex.Message}");
}
}
}

View File

@ -0,0 +1,10 @@
{
"profiles": {
"ConsoleApp2": {
"commandName": "Project"
},
"Container (Dockerfile)": {
"commandName": "Docker"
}
}
}

View File

@ -0,0 +1,10 @@
25
91
77
63
45
25
21
89
6
18

View File

@ -0,0 +1,10 @@
10
3
38
9
36
43
96
31
95
58

View File

@ -0,0 +1,10 @@
13
35
38
31
19
94
94
84
18
47

View File

@ -0,0 +1,10 @@
9
32
75
92
100
85
85
10
50
54

View File

@ -0,0 +1,10 @@
83
88
29
86
87
79
18
22
76
71

View File

@ -0,0 +1,10 @@
15
22
92
91
78
47
53
98
72
64

View File

@ -0,0 +1,10 @@
66
45
83
55
25
82
95
42
18
6

View File

@ -0,0 +1,10 @@
25
71
35
71
78
51
29
67
87
33

View File

@ -0,0 +1,10 @@
93
19
32
13
75
86
46
87
39
66

View File

@ -0,0 +1,10 @@
7
74
69
75
45
28
92
9
77
32

View File

@ -0,0 +1,10 @@
42
75
67
53
2
34
57
47
83
52

View File

@ -0,0 +1,10 @@
98
62
45
77
65
45
61
62
10
76

View File

@ -0,0 +1,10 @@
41
30
41
39
62
3
79
93
56
82

View File

@ -0,0 +1,10 @@
85
29
46
36
82
52
4
14
89
17

View File

@ -0,0 +1,10 @@
35
98
38
31
39
76
5
71
7
58

View File

@ -0,0 +1,10 @@
50
93
18
76
13
62
16
45
65
25

View File

@ -0,0 +1,10 @@
98
45
1
52
14
7
56
38
7
50

View File

@ -0,0 +1,10 @@
41
27
27
24
76
36
19
87
83
35

View File

@ -0,0 +1,10 @@
16
5
95
36
20
60
79
46
61
77

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