From 375bc8994524ba67ceda4c1488e88ce75588139c Mon Sep 17 00:00:00 2001 From: ispaneli <43630654+ispaneli@users.noreply.github.com> Date: Mon, 9 Dec 2024 00:47:19 +0300 Subject: [PATCH 1/4] Feature/add module 2 project python (#5) * feat: Add module 2 project sources on python * Update README.md --- module_2/faust_python/project/README.md | 54 ++++++++++++ .../faust_python/project/docker-compose.yaml | 42 +++++++++ .../project/src/message_processor.py | 86 +++++++++++++++++++ .../faust_python/project/src/requirements.txt | 30 +++++++ .../project/src/resources/banned_words.txt | 2 + .../{tmp.txt => src/settings/__init__.py} | 0 .../project/src/settings/logging.py | 22 +++++ 7 files changed, 236 insertions(+) create mode 100644 module_2/faust_python/project/README.md create mode 100644 module_2/faust_python/project/docker-compose.yaml create mode 100644 module_2/faust_python/project/src/message_processor.py create mode 100644 module_2/faust_python/project/src/requirements.txt create mode 100644 module_2/faust_python/project/src/resources/banned_words.txt rename module_2/faust_python/project/{tmp.txt => src/settings/__init__.py} (100%) create mode 100644 module_2/faust_python/project/src/settings/logging.py diff --git a/module_2/faust_python/project/README.md b/module_2/faust_python/project/README.md new file mode 100644 index 0000000..0356530 --- /dev/null +++ b/module_2/faust_python/project/README.md @@ -0,0 +1,54 @@ +# Упрощённый сервис обмена сообщениями + +## Описание +Этот проект реализует потоковую обработку сообщений с функциональностью: +- Блокировка пользователей: Сообщения от заблокированных пользователей фильтруются. +- Цензура сообщений: Сообщения фильтруются на наличие запрещенных слов. + +## Инфраструктура +Для работы используются: +- Kafka: Для хранения и обработки сообщений. +- Faust: Для потоковой обработки данных. + +## Инструкция по запуску +1. Разверните инфраструктуру с помощью Docker Compose: + ```bash + docker-compose up -d + ``` + +2. Создайте необходимые топики: + ```bash + docker exec -it kafka-1 kafka-topics --create --topic messages --bootstrap-server kafka-1:9092 --partitions 1 --replication-factor 1 + docker exec -it kafka-1 kafka-topics --create --topic filtered_messages --bootstrap-server kafka-1:9092 --partitions 1 --replication-factor 1 + docker exec -it kafka-1 kafka-topics --create --topic blocked_users --bootstrap-server kafka-1:9092 --partitions 1 --replication-factor 1 + ``` +## Тестирование +1. Запустите приложение + ```bash + cd src + faust -A message_processor worker -l INFO + ``` + +2. Установите список заблокированных пользователей + ```bash + docker exec -it kafka-1 kafka-console-producer --broker-list kafka-1:9092 --topic blocked_users --property "parse.key=true" --property "key.separator=:" + ``` +Пример данных: +```text +user1: user3 +user2: user1,user3 +``` +3. Отправьте тестовые данные в топик messages + ```bash + docker exec -it kafka-1 kafka-console-producer --broker-list kafka-1:9092 --topic messages --property "parse.key=true" --property "key.separator=:" + ``` +Пример данных: +```text +user1->user2: Hello +user2->user1: 123 bad +``` + +4. Проверьте топик filtered_messages + ```bash + docker exec -it kafka-1 kafka-console-consumer --bootstrap-server kafka-1:9092 --topic filtered_messages --from-beginning + ``` diff --git a/module_2/faust_python/project/docker-compose.yaml b/module_2/faust_python/project/docker-compose.yaml new file mode 100644 index 0000000..6d9070c --- /dev/null +++ b/module_2/faust_python/project/docker-compose.yaml @@ -0,0 +1,42 @@ +version: '3.8' +services: + zookeeper: + ports: + - 22181:2181 + container_name: zookeeper + image: confluentinc/cp-zookeeper:7.4.4 + environment: + ZOOKEEPER_CLIENT_PORT: 2181 + networks: + - kafka-network + kafka-1: + image: confluentinc/cp-kafka:7.4.4 + container_name: kafka-1 + ports: + - 29092:29092 + depends_on: + - zookeeper + environment: + KAFKA_BROKER_ID: 1 + KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka-1:9092,PLAINTEXT_HOST://localhost:29092 + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT + KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + networks: + - kafka-network +# kafka-ui: +# image: provectuslabs/kafka-ui:latest +# container_name: ui +# ports: +# - 8080:8080 +# depends_on: +# - kafka-1 +# environment: +# KAFKA_CLUSTERS_0_NAME: local +# KAFKA_CLUSTERS_0_BOOTSTRAP_SERVERS: kafka:29092 +# networks: +# - kafka-network +networks: + kafka-network: + driver: bridge \ No newline at end of file diff --git a/module_2/faust_python/project/src/message_processor.py b/module_2/faust_python/project/src/message_processor.py new file mode 100644 index 0000000..3c68d9a --- /dev/null +++ b/module_2/faust_python/project/src/message_processor.py @@ -0,0 +1,86 @@ +import logging.config + +import faust + +from settings.logging import LOGGING_CONFIG + +logging.config.dictConfig(LOGGING_CONFIG) +logger = logging.getLogger("message_processor") + +app = faust.App( + "message-processor-app", + broker="localhost:29092", + value_serializer="raw", + store="rocksdb://", +) + + +def load_banned_words(file_path: str) -> list[str]: + """ + Чтение списка запрещенных слов. + + :param file_path: Путь к файлу. + :return: Список запрещенных слов. + """ + result = list() + + try: + with open(file_path, "r", encoding="utf-8") as file: + for line in file.readlines(): + result.append(line.strip()) + + logger.info(f"Loaded banned words: {result}") + except Exception as exp: + logger.error(f"Error loading banned words from file: {file_path}. {exp}") + + return result + + +# Таблица заблокированных пользователей +blocked_users = app.Table( + "blocked_users", + partitions=1, # Количество партиций + default=None, # Функция или тип для пропущенных ключей +) +# Топик входящих сообщений +messages_topic = app.topic("messages", key_type=str, value_type=str) +# Топик отфильтрованных сообщений +filtered_messages_topic = app.topic("filtered_messages", key_type=str, value_type=str) + + +@app.agent(messages_topic) +async def process_messages(stream): + """ + Агент для фильтрации сообщений. + """ + banned_words = load_banned_words("resources/banned_words.txt") + + async for direction, message in stream.items(): + logger.info(f"New message: {direction=}, {message=}") + if not message: + # Исключаем пустые сообщения + continue + + sender, receiver = direction.split("->") + logger.info(f"{sender=}, {receiver=}, {message=}") + + # Проверка списка заблокированных пользователей + blocked_list = blocked_users.get(receiver) + if blocked_list: + logger.info(f"Check that {sender} is not in {blocked_list}") + if sender in blocked_list: + # Если получатель заблокировал отправителя, то игнорируем сообщение + logger.info("This message will be ignored") + continue + + # Замена запрещенных слов на звездочки + for banned_word in banned_words: + message = message.replace(banned_word, "***") + + # Отправка отфильтрованных сообщений в `filtered_messages` + logger.info(f"Filtered key={direction}, {message=}") + await filtered_messages_topic.send(key=direction, value=message) + + +if __name__ == '__main__': + app.main() diff --git a/module_2/faust_python/project/src/requirements.txt b/module_2/faust_python/project/src/requirements.txt new file mode 100644 index 0000000..15977a4 --- /dev/null +++ b/module_2/faust_python/project/src/requirements.txt @@ -0,0 +1,30 @@ +aiohappyeyeballs==2.4.4 +aiohttp==3.11.10 +aiohttp-cors==0.7.0 +aiokafka==0.12.0 +aiosignal==1.3.1 +async-timeout==5.0.1 +attrs==24.2.0 +click==8.1.7 +colorlog==6.9.0 +croniter==2.0.7 +faust-streaming==0.11.3 +faust-streaming-rocksdb==0.9.3 +frozenlist==1.5.0 +idna==3.10 +intervaltree==3.1.0 +mode-streaming==0.4.1 +multidict==6.1.0 +mypy-extensions==1.0.0 +opentracing==2.4.0 +packaging==24.2 +propcache==0.2.1 +python-dateutil==2.9.0.post0 +pytz==2024.2 +rocksdict==0.3.24 +six==1.17.0 +sortedcontainers==2.4.0 +terminaltables==3.1.10 +typing_extensions==4.12.2 +venusian==3.1.0 +yarl==1.18.3 diff --git a/module_2/faust_python/project/src/resources/banned_words.txt b/module_2/faust_python/project/src/resources/banned_words.txt new file mode 100644 index 0000000..08e80de --- /dev/null +++ b/module_2/faust_python/project/src/resources/banned_words.txt @@ -0,0 +1,2 @@ +bad +restrict diff --git a/module_2/faust_python/project/tmp.txt b/module_2/faust_python/project/src/settings/__init__.py similarity index 100% rename from module_2/faust_python/project/tmp.txt rename to module_2/faust_python/project/src/settings/__init__.py diff --git a/module_2/faust_python/project/src/settings/logging.py b/module_2/faust_python/project/src/settings/logging.py new file mode 100644 index 0000000..d9ee3d3 --- /dev/null +++ b/module_2/faust_python/project/src/settings/logging.py @@ -0,0 +1,22 @@ +LOGGING_CONFIG = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "standard": { + "format": "%(asctime)s [%(threadName)s] %(levelname)-5s %(name)s - %(message)s", + "datefmt": "%Y-%m-%d %H:%M:%S", + }, + }, + "handlers": { + "console": { + "class": "logging.StreamHandler", + "formatter": "standard", + "level": "INFO", + "stream": "ext://sys.stdout", + }, + }, + "root": { + "handlers": ["console"], + "level": "INFO", + }, +} \ No newline at end of file From e289fa0cc4e1a8c1c391c1c3c2be388e8455189d Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 9 Dec 2024 18:17:22 +0300 Subject: [PATCH 2/4] add project --- module_2/goka_golang/project/.idea/.gitignore | 8 + .../goka_golang/project/.idea/modules.xml | 8 + .../goka_golang/project/.idea/project.iml | 9 ++ module_2/goka_golang/project/.idea/vcs.xml | 6 + .../goka_golang/project/blocker/blocker.go | 74 +++++++++ .../goka_golang/project/censure/censure.go | 36 +++++ .../project/cmd/block-user/main.go | 33 ++++ .../goka_golang/project/cmd/censore/main.go | 32 ++++ .../goka_golang/project/docs/messages.png | Bin 0 -> 26391 bytes .../goka_golang/project/emitter/emitter.go | 58 +++++++ module_2/goka_golang/project/filter/filter.go | 63 ++++++++ module_2/goka_golang/project/go.mod | 34 +++++ module_2/goka_golang/project/go.sum | 141 ++++++++++++++++++ .../project/infra/docker-compose.yaml | 106 +++++++++++++ module_2/goka_golang/project/main.go | 28 ++++ .../goka_golang/project/message/message.go | 43 ++++++ module_2/goka_golang/project/readme.md | 93 ++++++++++++ module_2/goka_golang/project/tmp.txt | 0 module_2/goka_golang/project/user/user.go | 110 ++++++++++++++ 19 files changed, 882 insertions(+) create mode 100644 module_2/goka_golang/project/.idea/.gitignore create mode 100644 module_2/goka_golang/project/.idea/modules.xml create mode 100644 module_2/goka_golang/project/.idea/project.iml create mode 100644 module_2/goka_golang/project/.idea/vcs.xml create mode 100644 module_2/goka_golang/project/blocker/blocker.go create mode 100644 module_2/goka_golang/project/censure/censure.go create mode 100644 module_2/goka_golang/project/cmd/block-user/main.go create mode 100644 module_2/goka_golang/project/cmd/censore/main.go create mode 100644 module_2/goka_golang/project/docs/messages.png create mode 100644 module_2/goka_golang/project/emitter/emitter.go create mode 100644 module_2/goka_golang/project/filter/filter.go create mode 100644 module_2/goka_golang/project/go.mod create mode 100644 module_2/goka_golang/project/go.sum create mode 100644 module_2/goka_golang/project/infra/docker-compose.yaml create mode 100644 module_2/goka_golang/project/main.go create mode 100644 module_2/goka_golang/project/message/message.go create mode 100644 module_2/goka_golang/project/readme.md delete mode 100644 module_2/goka_golang/project/tmp.txt create mode 100644 module_2/goka_golang/project/user/user.go diff --git a/module_2/goka_golang/project/.idea/.gitignore b/module_2/goka_golang/project/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/module_2/goka_golang/project/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/module_2/goka_golang/project/.idea/modules.xml b/module_2/goka_golang/project/.idea/modules.xml new file mode 100644 index 0000000..a0733a5 --- /dev/null +++ b/module_2/goka_golang/project/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/module_2/goka_golang/project/.idea/project.iml b/module_2/goka_golang/project/.idea/project.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/module_2/goka_golang/project/.idea/project.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/module_2/goka_golang/project/.idea/vcs.xml b/module_2/goka_golang/project/.idea/vcs.xml new file mode 100644 index 0000000..c2365ab --- /dev/null +++ b/module_2/goka_golang/project/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/module_2/goka_golang/project/blocker/blocker.go b/module_2/goka_golang/project/blocker/blocker.go new file mode 100644 index 0000000..3cc0602 --- /dev/null +++ b/module_2/goka_golang/project/blocker/blocker.go @@ -0,0 +1,74 @@ +package blocker + +import ( + "context" + "encoding/json" + "log" + + "github.com/lovoo/goka" +) + +var ( + Group goka.Group = "blocker" +) + +type BlockEvent struct { + Block bool + Name string +} + +type BlockEventCodec struct{} + +func (c *BlockEventCodec) Encode(value interface{}) ([]byte, error) { + return json.Marshal(value) +} + +func (c *BlockEventCodec) Decode(data []byte) (interface{}, error) { + var m BlockEvent + return &m, json.Unmarshal(data, &m) +} + +type BlockValue struct { + Blocked map[string]bool +} +type BlockValueCodec struct{} + +func (c *BlockValueCodec) Encode(value interface{}) ([]byte, error) { + return json.Marshal(value) +} + +func (c *BlockValueCodec) Decode(data []byte) (interface{}, error) { + var m BlockValue + return &m, json.Unmarshal(data, &m) +} + +func block(ctx goka.Context, msg interface{}) { + var s *BlockValue + if v := ctx.Value(); v == nil { + s = &BlockValue{make(map[string]bool)} + } else { + s = v.(*BlockValue) + } + msgBlockEvent, ok := msg.(*BlockEvent) + if !ok { + return + } + s.Blocked[msgBlockEvent.Name] = msgBlockEvent.Block + ctx.SetValue(s) +} + +func RunBlocker(brokers []string, inputTopic goka.Stream) { + g := goka.DefineGroup(Group, + goka.Input(inputTopic, new(BlockEventCodec), block), + goka.Persist(new(BlockValueCodec)), + ) + p, err := goka.NewProcessor(brokers, g) + + if err != nil { + log.Fatal(err) + } + err = p.Run(context.Background()) + if err != nil { + log.Fatal(err) + } +} diff --git a/module_2/goka_golang/project/censure/censure.go b/module_2/goka_golang/project/censure/censure.go new file mode 100644 index 0000000..35c9a09 --- /dev/null +++ b/module_2/goka_golang/project/censure/censure.go @@ -0,0 +1,36 @@ +package censure + +import ( + "context" + "log" + + "github.com/lovoo/goka" + "github.com/lovoo/goka/codec" +) + +var ( + Group goka.Group = "censor" +) + +type ValueCodec struct { + codec.String +} + +func replaceWord(ctx goka.Context, msg interface{}) { + ctx.SetValue(msg.(string)) +} + +func RunCensore(broker []string, inputStream goka.Stream) { + g := goka.DefineGroup(Group, + goka.Input(inputStream, new(ValueCodec), replaceWord), + goka.Persist(new(ValueCodec)), + ) + p, err := goka.NewProcessor(broker, g) + if err != nil { + log.Fatal(err) + } + err = p.Run(context.Background()) + if err != nil { + log.Fatal(err) + } +} diff --git a/module_2/goka_golang/project/cmd/block-user/main.go b/module_2/goka_golang/project/cmd/block-user/main.go new file mode 100644 index 0000000..c0b3e34 --- /dev/null +++ b/module_2/goka_golang/project/cmd/block-user/main.go @@ -0,0 +1,33 @@ +package main + +import ( + "flag" + "log" + + "github.com/lovoo/goka" + "project/blocker" +) + +var ( + user = flag.String("user", "", "user to block") + block = flag.Bool("block", true, "block user") + name = flag.String("name", "", "name of user to block") + broker = flag.String("broker", "localhost:29092", "boostrap Kafka broker") + stream = flag.String("stream", "", "stream name") +) + +func main() { + flag.Parse() + if *user == "" { + log.Fatal("невозможно заблокировать пользователя ''") + } + emitter, err := goka.NewEmitter([]string{*broker}, goka.Stream(*stream), new(blocker.BlockEventCodec)) + if err != nil { + log.Fatal(err) + } + defer emitter.Finish() + err = emitter.EmitSync(*user, &blocker.BlockEvent{Block: *block, Name: *name}) + if err != nil { + log.Fatal(err) + } +} diff --git a/module_2/goka_golang/project/cmd/censore/main.go b/module_2/goka_golang/project/cmd/censore/main.go new file mode 100644 index 0000000..7dd4a9e --- /dev/null +++ b/module_2/goka_golang/project/cmd/censore/main.go @@ -0,0 +1,32 @@ +package main + +import ( + "flag" + "log" + + "github.com/lovoo/goka" + "github.com/lovoo/goka/codec" +) + +var ( + word = flag.String("word", "", "word censored") + with = flag.String("with", "", "new word") + broker = flag.String("broker", "localhost:29092", "boostrap Kafka broker") +) + +func main() { + flag.Parse() + if *word == "" { + log.Fatalln("cannot censor word ''") + } + emitter, err := goka.NewEmitter([]string{*broker}, "censor-stream", new(codec.String)) + if err != nil { + panic(err) + } + defer emitter.Finish() + + err = emitter.EmitSync(*word, *with) + if err != nil { + panic(err) + } +} diff --git a/module_2/goka_golang/project/docs/messages.png b/module_2/goka_golang/project/docs/messages.png new file mode 100644 index 0000000000000000000000000000000000000000..8947d79976b212de3fbcaf01062a1adf58743372 GIT binary patch literal 26391 zcmdpcWmr^S+wUL(N=kPKBGTO{AV^Dhi*$D*NSAcCNSA%M=rCis)AI0_;GA_M|Kk@zU80D&MlLLe}TFW|uuh9;Fv@aMU` zu!PbJ@bGx?ISBlZ@9;s*LDBk)gR{P!5yaTa+R}*L-q6m-$jaWt+TjqkMd0bPrb=oK zB6ddl4yM*tWJ;!%M&KX>GLkXUM8?ETHc~ZGJyK7`!o~aT%)-OUbTZWZ3IZX6 zNQk~ya!K7=bauf~#)Us}c8>YJBqB_Jl!fWW=*tFUN|Q~76B0re!UiYs3j4z+A$hFl zbV}6OWaKZ#+z+!vUVIfIc)2}4bSWN9`MUSuC*8}dhxBgo=%j&#v?lw5H1{tD?+gqK zo*PXhu^PW3AkdiWBqM)%WU~m1N3CJUlEFQlb#9dlvbm>`@cRh8O>SRa8mL zW*{->7juD>Qh-gL(SP0CfBov~Vm2i2>-xd)pCd(=$N!&4z9`PY{cBeK>x0?d2_+@; zN;Jx*xb1XJI%p*-noqOVhnq1$`QN7}FLF`BsE1@K6zG|o|Mwf*I!xIJES4Ja2{|kv z5Qp6<6gmELti8QG$-2+~9`u>+W&Rt7$^xZA40jDx(Q@ZQ9f(koN=fFYcaonQL_cd7 z7?8h}O%1Z&9LjRsox)=`!_v~$mTFMz{P_x8-Pu#LPkSFGt9!v%gbiHL{!#%L(>;WtGeg; z@97+cwl6I;SA~8u+1}o!{FT=7aDxQy+9xQ}{qkUIsmVR+H*{@HfqeZnjVVtW>h4rY zdxc^5D|Ga)`T6gzZf|o8Izvom%3r%(?5WdmI!UK;p}RGaFg6}NQz=%1fg}oe-iR=Y zOVEz~I2g;78qJl$b#Zr3n0PBCYGFagVLA6LLD%a#>(ISFo<5P?90$zM@t73jYc4K4 z$SX`tzw_N`=pTP1JT4ouct+ibjt zna1nPA&^uwYEo`G{`T&AGsDKwvD1E-KQNwNQ@DWc#$uO3EKJA`9%ZD;beykW;PK8L zA{0)*&g8T=LoWEb5iCc(5)I$u-RTDgV|}pJdb7WbT%h5cEBpH>=xx7@`ad-|S+bdo ztYpQm$~%CYzPh>5z}m#b#I)U+pk}jLz(YkvMay#dr%aTl?o?x*2Ksc}8mmP;CcJW6 zsMtU{f5S&;mGsU-&Hij9!s*7Kh^Xkd54)AA0KXmLPJHy zf6B-gsn9Ed*Qd*LyJstnZI4#oKZl1mxH;Q?r`z19+<%e@*4$vO%G6|`mKl|p=bJ6G zOMm{iC0aG|)F*>+#^EGZ1W=#8O-=VAQhjP>w-@_~g5Clc7#K_*S4V-rm)Xsy^e*;h z@%UVrbiA(D`J5^E&Lee-^jEw`+ z`Q5W+QaIGA&9J?$RuPB9pipS|`#Q4C;f(H@Wv|f7CHJzA&P#jqHE3VI ze*M5;sK40kN%*vgJ}|J5&rZL*?$1?`iFqc>Bo=@Cc#|sNNf1uR>GdYBJ0c>Yt2c(~ z_-tHGv&zfPj>T%B_Uq00uI7KVq*4Ddv^y#_HSD+L{4)mSDP-E{3)o-z>*<}Hds+~#nJe`bC@$854NU%5!7^wR1H zcEGZTYiJNGEG~}b%jamxjr{!ib8RF`q}F~D6<~|6ukT2{JjKl19KkfV`vskBI$x6c zGFUhhP>$&=+6_)92L}f>&d$T!X2BOs$>C;Ng z@ZQtYq1|9DpOrl~mw``7P7aBU#dwty^UM(%HmA`+V zi;IiX#(g89s;H<)WHVLBA`v5CHuyH#mosVtI$aI)F9cM^^Y7fe`(gO3HlTASN;Gi| zyTZOkMxqJa?y-guup@%W?*`Zt%AS)?U8GvpH7xiT24Dtyn3|eucQ5EZFh4noZQXbb z?{>ap6!#0#`t2oV=BNpsMhz314>oT>3Tqbc=brCtRzqe1$oS8~#r!6VRAGH4F&k)A ziiOtu;*g}bp0%p#ddBVAUW*_1^1Idkg4Ox->3gTQVu1psIW_`hWpgudzQ&r+d+C=E zQcQhk?Dy|JC7LzlZ{9o~OlB7m7Ji0GDuCRi6It)L`wUX5Rp$%3j*N{BtGT(E3oIDu zg8;xM?W3dU0K7GSTYUBNgPp6f26PzO9*AbRIh3|FQ;}njJ|`l#sWXONlSmW0iZ5AH>8 z9nGgpE#vlaXbV!(M)t?^u);ubg8zVLP3?rpZ$Ku7PbdoI=X?Yh{b4|^*-!@3_(&{MxAdtITUl%=7Fj@ zu5h3{SWE5q=h*4g$|0al10{2X7wa9FO~>9qT4yQ@ZSQX!KY#w*Iy{U93y;jiyWa7{ zf}NqbPfD#s#c(9^eO-hjkRUj8YCe7lXn*vWo`BHhbdYlu3WbGyl#pm^bUn2>+miqB z^XH%Wc|uU`;vYW-*%ETcECKp?nk+D{@__Cpiq*%jTvOOgpZN>JFfuYOG&rL@A%}^{ zI{*?x>HMJphYV-=`1y}-4_lu2+GHdXhMa-|%%3kf2jOzv=no0Zr#ZK^wLLmHdGq}ER31k}POAkW zF)?^>mCL1GJ1NHC#j-QA_naWhk?k;%Y%tQ;BnYG8d+`-`Uiqk;=K?m7#5F4 zHzg7|H){R+$80Qi1!kP?u!j9_+Trhy>{7xLj}P%1b>!AQ)B@8Yznk9n3#m$YM@n`Tlxqf9u53En?6`QrBB;v#Ur7jR=X%gDM zW;O;>Dy$ZH0Oj;^t=j^&5(_7Q6Bq9ejGh2DNmcK#9lfhP3?^~7T6QR-KY=k4w5kE% z8x^S?Gd2Rg;|@$Xfx*dvs*uakHJ*xZbWDtVAQxe->nnfJ8$8rJ3qJVKCYRTm)v~_mEF)%Q2c|Y(0jrZkZm5Cg* zBA6IRB8MfxlKUY)&`em%9;YbC@UUWGcra8{R8Uw|c)zSx+ofom^Bv{JtF_QNFV!3| z6lhHStl;KE=6hH=-R1=|BRdC&jv#ajJ!|X8C;d26u3xuwdRrb>=e%PDZX05Ad7yoF z($9$Vm{uKK?@uiPD)qNIeStZ6@b%K!KHRx_T(2iw9e0zKD3G{$C>1KbR4vn~cDV;| zsqTsR#ayk*1ii7T=?S}_#FhdA0#`@vXtsA(wog3Va(99RUOXNZCta`KrPl-wBmA*c zAs5l17DGlM1)||3YSe& zfck)BPMSqUMZrK^f6f5L69R7QSdAJB@kxcA?@anh=CUx1n^kwU$iKr13J>oDSgjAp zN-~Zn%j^E8yrOQI5AY=g4Gp49G8^*)10NreWbWA2Un^iR%8iFfe*E}>w=92@SzF5` zDkhe#m?zuy*Banl#g{>XrvrBDeJ@Z^12jsyzQQ7`tggaZVH z3Sp0UFI+;y9l@Q9A7wk+#~Rv}_+UR(+wvNtC44UMJMLqk=5n*&aO9iT%2~27?;2v7 z*cHBkCyE0m_NWtASFhCvrpwtG=QCg>wMG}_B^M|PoB34nUqs``6&{}2giKrz(2(j?I-fuPs8|4K zQlsJeD~5%8S<$o)usdSh^Pi8U$?WE90P1{o%7!_kl44}@+JO=&b~^QC_xJaIa@KVM zhD~ngnMyRapnac83cy2ur7Q;+wNZb({&KTtP-GQ0Qwd$iQ!_B2w%(`s+Vx#Gv6+rN-<>WK z|NQw4kMqG@55JM@zd7v(UElzb70>haOreby{X@vD78(`e!M?VypaVbC=mVp4@PQA0 z3tLz7qog96S zdId-nvf^cM34F>|$=tp;;9mftWUG{Dz*&~gEG#SlR9Mt%IXymRPO@(Rya5gstNcub zUQkn0=RWL=vs}t9wzdil0M7}4zx!ylo!R+7tEj26^2cB4Ee&4uMKB{4z^G1-JHw4` zJIqv>Vgjo9TzxVE%>lg(@qtA^{VUnHZVcA%Q-MEWSL@xX<#Uu2us{%k4L1 zkqJ4F<7kwH0l^Aft@sJ=4^K_ufS%)us|ZR=`<)F8$rCPBSk7}4Yu2DXsrW!ruOOh7 zHTc&3g|nWLfKPd{#Gq|+wCe0;fDQ$W2`<77MIqzPOCEaQ=CwuoEHGK3QWTY(j7UgG z_$@FH3S{NAY?KA){qNsjy?Og~?Md>CX8SWI0V|;V{Q2`%Fbv2mV&ZTRqO5|d>iX>T z8(ciwYO((MtwAMb>;7DIzp^aZ<>E)cQ9V69J|L_r)o%EbN|XFgtG|#GR6_pM8NbV! z8Axc6`Ciloi^$J!HOc-;#`&drd_dOdZu=^Mz)RW548Jl@xQSA$$om=$$W^?{XrWSp z@5sm}P%T)spk|&6i-;tRP=-1lE)fIU(*dk@m@5|o0s@^@Ez@xQlWwYaEIP&xnEI3} ziJi*lib2fl)Uh{PSpjO&BSW&fvQi|HgrCWv;{}nR7ZI?J)f!3J+1X)oJ7CNl%lK@h z2%TSbYTG9V9z3oq$cn0Of+xF>+ee^}fdr4i0wNNHQ+gcM`!1H@}FA}b{!oZ z2?2PuzrXw&BS+59zc^#+$rPM+-*NJk^zG~<@3!o{DHF?S15oz~5ObBqECz4^9fK*H z^gKHMwECIuicbKshESy={#1JSpFt>Xg@-D4l_e>nCX~lFkv_D1_8zWrVg`<+S>R){ z9sY5>npS-@JgM$3KeGz}#2C44pvaK)_4QTe1^K-+s?GdCX*_AN(a}-3Qe0fzz|kD> z9I!lJNMF!KP^G&(172DTI0sNg1|IKr-UH{TT&VP3ON;19;Z|ABPnj7J0ICH#<16?m z5LX6&zP(hLPoTWHJd6aHp4yr2wXCQq~ka>9VhN>;1o=5SEL*VndCs zo1r2=3}87qR-loBV&LBN4GsN38?=G+8>9k!J_7~*+8}vYUHuBU9RLCWX7lVgHlt1> zo_4*%)6DHm6u{;x1Ov;N11^%UUilUXHIV-TPzebNdP>4GK{8W!uv7#Z3YCN}2#ABW z>1kZBY@gm?)kXIMnD{6o^Fc`o3xvXmoSZdUhn?Y}q3z&CR<^faqQ7@=V0(&SfFm&! zz5vPu2zI}g7C{IZ1qCb+HTgi2rC+iD+tj+aNUU0_^;M&UL-yJhI80#gFm-fvjxH}b zWsmp3$$s$zp`Yf#CiG6`sxD$&T-*%>`$dSHn-1*oO}sm(_t#M&wsB+Sym*6ByW zZy8EoO|Iu2naZsqj+?5WSB{;;RfN;{UWgES-LC$IpL$oJp{r9{?9vuyx+_zmP$1$y zN=vf|q&|(p<8sj%+^WlNNzE$^3}3)hJl)c4bF#M0D@ORJpik(QB5Z&c+d8f7lRl~50umkWaN&C0!4fNEA=d4 zIO#+t7$6%ufmuYy!upn%?$&2!Bq<})2@r1-$l{{?9HzbnplJ}n$DTsV)%HL`z(Pg` z!Q+6GgvzD`&zv8fz^(uw`==z1!(ztg;r_OLd6^UhPF#*VSRhUQIx(RHuGCiO{B8tl z+VlPO6-X|b4ZDz$@mV^qus(Zu@PSkn3CuM*g;;D)Gw`*xTO+S4cZvC2UM(#xeVFT( zVr=>bf?mVxll4SCR~8^sz5(g81{kb0mRfOTWyOao!vo{V{y)fowqUm3kOqkUO4suo zTl6`|B2AW?`GGxr4w5I=(~Z{v1;K#9T!5(o0TM7V9ZxX|aOR;uy~=B9x{K8-nY>s@)1Q+V{ZZf`IACd%B}c?;PXG5L^SG%ElY__%{A-&SXTMd-iTfdTQHn~0WQw+ z5e24}G}-eV{#z<6o%ADsr9cI~l#`PK6A2H@&j)ZN7zoH^n1L+tH%@nj2We@o(>_5= z{by!XRn>tsUR*HBPVn}l^#omVU@6IcLFNy3AkKED(SSDb4VMZ2@qqXlC=Iak23sRp zi9C)pPkxV1{nv8<4qd8vQvlroZzeM7KYI!QNxiOK54W2}vYIz3s=oG5a-Vn#EdkFa zOl(Lhna1w}XRB6W0LP$J>l2F0NDi_L(ELErAb>%3UVexMn@kE>Q*t0y<6QRe1GljX z{K7Y#vTm?*B;Vxbh)gQLH|#DIx&!(LoXCHcddknw?`bz+qD;3%^g=3^mV*Q5i9_;a z)3{gFIrtzsZKvC&<9DjhqYV$hnwH7KDJtaViiFh7I zZ3qA;e}0%B^veA9qGy2Ha`qQoZ*MPzMHgWvOC&Hb-8>{LtOG<&k-(F%Sg5hoSv(V^-poQiQXqf z*F&E6{s7zL0B>MpXV+Iv2PPH7wt+P@oElXo`o|ne6S==6b4FJp1s__0ka-I4fZ;#@ zJ({mpK~BJ75d@SWe=LPwOif)xxh4G6DGO97qTe_8`{(I> zKEJAv{+*HmNROlfFiuV6tM8x3C{(Iwl0{uoR7u)H;s4rV zMmAJP$VaZISZzQ3haE&kV}lL5L}SAxw6j?{hK5S3LjU7(M$fy$g5Rb5?>*zo?Q=H} z6@eX;D|@V|wfOe;i@4tkiaJF zw=yE%__%xYPj&PHyoHrs1@B@+4aaLy^GWa^j*IeFs*2Hs)2l~U$wwaRa-U=Wev$fy zbK9-rpH|5@pGrFN@h>#RDZ)Xgg%vcay@Xvo}+WcI%u==?GJz~So`psyL+mC-F7wG)S$ zCs2x(AW-!EOSNy*hY$ie$?a4+{M>N_J-!MEw3k9Q&@e`Lb?Wf)Pnu>fo?I2LpisNy z`y;1vZ?@aoT>Q{;+~W_Yb~a}(#n!*Xl$SA0V?!kO-RPj4jVlVcF>)vM%|aiFRV_CA zPzU2*zxdP=n&!{L*nff-9B1;-7Bm-^iF)Ge=P5qWKi0#y=BrUb~r0yV9i~gl12~ z4YyaH(`m^phsrA#={sD+6>nH0pQyZF=X~W&Y5dUl>X>+rmq!0`#7|_KX(7?iskEt_ zjhWcHc`f|QIAdlxspcBT$~-scsQEeew~S=}2&Y*owkv0oS7zaYVP~h~BNLm-#lu## z)d)vk?1W%DV1}obqFk1$@p#fnbiMA2YPP{}@YUKRPr0j&Jab%!F#=@M7^Ic-)>X%1 zuoAhb=(BjgqV1}*uE=Z$U02X9q(8i@K>Pg-(YlJ18?#-bUMOmrqY8{C{8iY@`A2O; zTuoc3%-7@R_%lo+NDw~<-X4f`!iAGN*m0&CL%rngFOEAbNXcLc;cv2! zjrBj6wUDag5I6W@6Gc9E#y$5j6dFQ`A0V6|;`NA3;TCVelY2-h#*ve*wokH{m+h{g zML3d>nkTntT5-wczopb@P;GCRKUmbgJ=-)n&>_rmro@I~@@L|84=q$wgb~ll@`lhN zfJ`Ey-iV<$@`5ShE8 zNc}l)=<9-x`n{oxX4l9+lQng;C@~@jrfKij@lCd`F&?gl7Zzb>*wVuIRsYx?%$Jt4 zwzwrJ6~6qV&=l3F0TtilOmN(|Ewn=mmY{jAjLM+Zt+E|%mX1(vXHQ-)xEWSdr26`& z0yb0N7j6mJ4*m~hG|F_UJuOx3F_rB{mqG|yg5?-7ffu6PJuNnRUo>m+rscCeLLXe3 zK8N1*e1fm(&Qz6bz&ab5RTi(|EJ|>q4iQ@oxoKJ!y&q#hSU-O^O-oZRev^7kI3e> z(8I`)2KiTfsoE>pCvTQ?+zuYnSn7AxF1>IkN>^Z<(HO5(t^J~Q?WDs^?CMDL1zRE_ z9Q{`Jcb=(c(3r5AFhsmrYN?j6H^N>r%?W4-MWFfZ0SnbBjWRE+szRb=-q`-6WMdyt zWG&-BTtkB1H6Q={5_PzZc5w2@>Cz`Jk^m(=DIDtG_;q(_uI5vX6M{6qLMtf&GLz6t zWpt9MPwy;04b9@R^E8KEkJm$o{v=)T^PHYim3d-LPMTdcB>1_f7x)hlHH0l6`AW$& zp0qfR6j`!PAR>z1tKzO2ytcOB)OvjBx%(~qvXK_;e8^tpuiTcRGD>wB(w;CP_q`p? z{^TUy(oYsXqBQi8HSOX-TlbLFAr+6g2AF3HX{&3>_;QUEuc)G*--POB))`yhC10&f zDO4Q9oxDhM8Q9ad#kge@a4qF7ymoEv?1cS#?zZZ^Teg`jChmOZoxguV*L>&5#xq?M z@LkV4e}1@?niwL2>@@5KM^EeeXLc60ZBVD(Ssa5th2VW9nqlE>=5WZ>?e6aI>gn53 zmy;A5JZX=M0ocA^ZaY-3zBk%mG6 z8%JAl|N15-*YUwyM=56lT_@zP_1S2tef(S{MjL5vL&V~1V-DR+wv1D5)U)>0ipS8CTz8Tnoh!Fd1p4r*VxF zkI9N*Zj2LLXe87hXn7fLEsjxcEoMD&Z2vXK7=`clT>i&rdP!b3!uz=iVV^vt)Wy3` zrv=UxdsXOn){GW8va%IwV>`Ng zgq4h{SO2|pV6!)m=cmNt`rf~IvF(>Zfy^`?kYezj?<)t9x!_iahL@F%?fHlBJTZgG+~Q&L za~JZDTx>|TKW`yFO)9)C9G|oAe*5{A$yuJ<=p|=-_#S6nA>!m%#d*4TGxoXJJ=6Ttkc=xEbAOU5&kR zt=#4)5_|!0*45(dq_|uS*EtWTiWL+MuUXs&^*3hJ$A;8f_s%uY=Ln$A+$53>@?a*7V(ogoDk0K8$4cZR@%#0 zcosKlmS1$eMvb{rCc|*= zQ`v6Y22<^E!lH!yj?PK5ouPUz_4RGEbb7!I?8&KrMnLV4tT*Z$)o z7ZE$S&g3NXSs5FY=k0FV>_b$rTP}Z4q=rgB{SDJynj6eApEjIXHGwkEu&~3MprQ&8nHnl5#Ufrzh=?+t;> z)*lfjjvW1p8W;7=-%Nh7Us*BEMtf+>VzZ|7G$ZYij6<+vtMY- zT*3_0tinyxBF(>6^c1PwOodskoNvut{7jd8hfJW1_LYb11xnW;18f;xxm`>c-ncS^W7U zb@f_XU$%q0wA8zT2CnHxHQ8;^F*KQ2H>+x+qcZ*I{Beo9tnIP3n$o*Nn}DF8AJ%T# zcXztqbJ6k1vt!(+FM2N&j}3a}YPQ!kxSP+A)SCshFj$bYgO?~D%{R#7#1C-5_v8*5 z2Tay!lo9UVu5PB~&OA z|5!{VjJ~J6pHv^46A%ip9PGK?@6EDh?|`m5MyKCuvU~20sY;Q$6Bck-RCeHtBHQl% ztrpUCwe*yHEzl5jFqSa3Fr`A^<)-3rLKeP9s7RUd%P6iq(=B1xn6u-aPLB6^Pq`(2 z-S4Oh+wWHaVX^$z#5cZM6zdHa>p!#xU;lc`Cv$cg(VJJI^wP00+;_uL-AyGyd}(!}7~6Jc%t^w#Nwgd;J0xdL zGo}!O_V~)vOGhf7=A%NNb#tuwFr9h!4|6F z$EZ*#9gOO3MV8BCfy)4l_>Xd%Z%?l4QmbWYsRRqDejlX$O*!2rkAA)Rv-;KMhHejB zTF}ROqN~jKS~enDO>1*e+!{Fni&Z*~w!N=D+U@6+)Hc4ybtrS7L%g}T7WoYao#YSR|RkCn)I!2;9dj0Nf zW9_t$hgo-5l;t-{j1aH&HrxI84s+HwcIQP3qtz*Q9#oZBy$!fMVat)dgKtXk#wb4D zk7%q-xD=*N8j;M4Knp{|`(s#G>9j?wFpm3K*KkiFEaZA0q@5H>A6#a=Gtw*L&DaQLwm^B+aITcP!TbVDw@F?` zwpp$jUEs9nTs8L)wix=7%e-RuaSbUKGh-UMjU|i^PfkuqgDOf)P z>JgdqauZr}aFgI;)~vr#`JUhvI;dTP$VJ5pCK5vE?P}}#!{oyo*I~SZb*INvi3KJ# zU%vU?&A@{YS$nAm5*^Jw3&Nh*@!91KksvC#3z#rcS#l?axa#i1jVlX+Sqp98=b=@ih267FDA73m=}kVf~meFq!c;Flkj39hKVP63LrdY*|pHd z&raK#Jf?|m+Nxr#}zwvUikSvea2JJ?-HC1LzYOd3DY%#C#cqX}Fqi2TeW25r=dff-~Z0xABa{RHW z$}A5%1zIC=9Neo*|Deu|n?*RL#i6*bYIG96_V5vG-#z}2o^jHwA~*P0Pp3aUPP(r~ zxBbi6!O_-~Pu8i+fI;yOg2r`^_IoElUXUbLA?a15OGY1Uc3Yf(m< zVZm;3O=%s9C!1@Q7!N{1%pLS`#tVhLBjKaj*nTE!j6E0HxO4=ntlmvuzF5dOs|c9y zF=*q8;h&OpzYPcxkJSAe@t&^Oc_X)Y%W`1;Z)}BS;&Zr-Ey3JJ>N1yrp8O^PsGXuA zSvFdN|Dwm6%x=|RhfV=OuVu)c8RO3NdBDghPhy!1naP4xP8}r(CLC#8STuiJW*?9vuky z-2dw3kqJc%M|#{=RUd6m6bCk8{)_5*G7+Okou7j zsv)*l-!l45*F7}F?c3hio8DwEfh6LyszH`wyYI+q?s8{Q%M$LFB){JMW};HN2%LFS zcB>rgd|-^j8dHxdV>IVSp8@Kn5cL%x8Y^_tNDi+A~nf;drH(zrAm%w@Z{ZPn7 zBB;XXJg?fchWE@-6OMiIID@6=3I%sU*SsR4RS4^TJF;W0ZN$(OkA>`mwOHDM+gDh* zQ4`wVw+HWyj=GSU7m{g-wK8IKV?K;|xy$pVCtJ&JG8oP?y>vLFZ83^$|5+N@boN&ALFUFyDf-gM{FgvX4qLk05Z)#m?^0Aq z`u%#O5<0q{DB}U8j-y~K=G@*e^eu67UZQfvjgvBa8` zyOqY%yfK@x@2&1fB>3td$t!NS)E|W53b?=2d^VBm`+I}^c_qM?K~7pCHn%C&zJs$@ z@(-89G%0DZ{l>hFy>u7my>imzoIJsL?Y21Sad?%Z*_({?Np&muAUWATY0VQP*KdmPv;VuLwQ4NE zAt{}(;N{omoSP zyuTyo(me6tZgXtnUR^z&EMC`MZ9%XvFO8NVMnYRER+esv?|0i}*=uBZma?4S-8zI) zZWazy4AS8QBg+d3!=Pwa;GL();VdC(?x(kY_w3~REBP8E1i?}x=cQnH5m|r1v!-OPH zRM#$BD7+6AM=IkE84bX;(mI9#Ab;YLNz5YJpPimQwh}Xtah~wyqtrB2rGeqg)EIJw zg&$U54c|o50R4l$JUz7;O!c5`b+&%(k`jGTG;@Yk{#!i{pFyb0yPWmjV>-a zK8_DEJIM`0HJ{!e_x!T&@=&ND3%JtFtr3UYX0{L1ieTK+%S_s>zIz~_o=MhW7&M_g zAl~dlX$mqy?L%O147V&bIv;9W17{|IMIY6^6Heykh}pjWVv6K#aiXZFFg-An{4XeG zD9~KN3!>cA`l2xX`g7HuXQJUq?xHovxbuX-&iw|)r=yPMLJhogYpiV4Mzly}#bbN< zpI`mxcbeEls6Z6F zx}^zUr#swCQxej?(@uX>zwJAKU-2LmL*7OJGf>=KBz$d#pmp&euQv zXB;{O6?-}8@H792Kt)^(I#B#+@4*7~0{7I_O+r?fMQdfltarCihO}Ye8Bg0RwYw%w zzLJk@9xd$nH;4~?a@Z|Gw_Iv#Fea6a$ELW6cDA*vYU^VA;?Ap1&-leNn9g zzb{Al`j&lhbQ+D}cr=KeD|;5x(e}6%eSyF6r8vJQ`o*x<8zO$6O7?UiBP>_C%}Yne5EKu6wA#-Skkc!`ZM?rCBk!x!=C0vHNj4yXT@D9 z33YIM(+3^(I!1Ul!LLTNj3OD$X#HMNABJJHSF@NdXX)n}3*;gim~CGzL#~Xhb(CRX zuzv~Wz}PY$m!F>d9Wxbr$hYAJg$Gx<5ODg(upX$IW5@CkJ3MQB6}rXcu9UwLf8Ip9 zqP2uO6xlbwjGeUkPKl#jK3~re_MH;VyK|n4RHw93C;~H$p6 zEesro`uWfqcdpPiPDz%4Bc%KA;E&+ySDU#lBX#=9 zN(!pNl#beAcbu@v$d(6cr8a@}*@Skg&Zm~O+&|Y|BHIQ9D`tB5pL89nZ!5wyhs+S2 z|I8og#3ZBOfTuq2I%5A#%yCq*f+Ayq98G7jg%vSm!{OyflOH>~DNSHtps=dmhC7?0 zdUKU^*Iqb6DP)u*{AJ!6mXHuaLzpl5E}Ln0n;6@q z*fWP-SZfl97c%$}lma40PMvXh?EB@mG|cV$A4N^e^p`4?e>FR^3#Z?}t6GlRGBWS3 zU~MM1k2xKnD=(-a`xEkoQTd}-SRRsUN|kcQmZl%|prRr=ySPDYyX`^f_nDkF7&`(YNmA*LZ2B@?Qi zq{LYYlFFSqs{KGXZ?fvq)%RJakE`J%_q=3w=QOEf+qTCwaWBkgNt^uQ-bLB}dXB{< zMlMn7NQ>W z@85lT<+lFu%91#ANV#c6xtchDo?kae)g-Z2$FHt}os#BqJ1{_8WhL?jEL))J+T}L} zmx3ZHtas8)mFd3SLXh_Tl-iOPV!3+CZQj;vGgyJ!PYSm2^Nw;Ga0VIng_)8}wpYbJ zCvFD6+(d+kH!W*D8}JZ=)7o`>PuODp%1HuYg3e=Ke$s=m$Qj0GrS!N4HUCo^k{2WW zAOAz%zso_?o+EZMoL7c;WChblw+-4ybBQ0eDjd0d<9;R}be6R8GTuM0IecjAZB||_ zW?Shirut;9y-|~WcHv*M*Sc>4yOQ-RQ)D1*FZ3m4;~i~lft$Dm*A4Ryw#~P_B7x9I z9Kq1YOp%3Ee3z`#PtyAn`1ZW9O7NpF$~?|O)yMNTujNPP8n#sHB)U;oQ!QbU3oXr^ zw1b&F_ekK@7ih`Im{_+dBGSvrn&io250;J;L@8+IAWFl0s$&NWV$tG|##qU#-zLGW zN$%x)hfEXrg7~P*79ALQhBlVm&&~ASB9;F0U4Y8GcvNwL!^qL@8-w-(b`#|#)Sj83 zwl=@hx|NcCe8y)KP9Yz(3 z;$HbNZH&hLQXyKAu2D^)wX%^jHu(4>Q?tbx!`i{dM?^PwVf#G}jORznkdfv06U43k z-IZ72;bREvO=Gq@L;Np1GJVJ>-x5$~GaSTACA@gUYOUdL)1S!e^6J5=ir|s%AdCzF z&LObtb=7@tDzER`owwO2Lfd`?$b1bexLfPUc3u4 zH%4}GdZVLTPt32Sx;8YfI6A($YBYh3ntp_a{0$berMRk+%^8d}u)v*LxP&-}1ueHP zAiL|O#&flQ`|z;;R#w$Ayo?SHTjti7Pbbj{0s6@OE$f_5&+j?iHh$1HT6!EJC6=o! zIno>ul^vMxnpU9HK@lMVFiW3cm^wsPBifK0n_^WWsD!u~t+^-{x`Q2Jy~0fhDJj)L6DF+X)CLN34Z7kDBRU-{o#_)^vG&VMHUz%kexBhY^n4D+2ZvKj z^5Sij@1^-h>MwI2DgyD6uU13WN%mrTD?>u2wR7>%fW^Wmn zPzN^kuujj-aBA$&ApND>$j3FW;sT08oDt-mpX;1g-Y$l9ULV$kbr?V-*Zqi-#d&c= zw!=?!2vX%fBK&gux$S&Nu1v&=tI3W-yQ02?(3kj5l+cGJNtf<`@2{Q~P(}Vw2x1(f za1b=@d*EEB1AYWM4=xqju#hd4Ve?I1F?>_#z?i72qTpP zGyI{2v?jd2aPP2$dQ)SPZhIbQa86SdYg?C>(dDO0DTp0cwI=9S8=ua&LymU%wG0ooMh%WwhgWN`~Jadzxa5;WtY#}+=@0$ zEW_|$)4B}2@+y2*Nro7Rg7O07gy++Xjt$TQ)x+uVG!^0b1HI_qu5PmH=R4k`{cwCF zN&9u7ndoL5qLAw=oagt@*3T7(Vu8GUX-zWmhC&_oO08bI1#;Evr4>2?J6Rl)r1TE! zQ_$<-OS5`3y;78#avkhCVw#SbWCi)+)p>H-60gd-<{a%-3J}%{oEq2vf&b0HV}bzs9)=!YWW zyyDGo!;li~NX!p@dwiapT}L3pcKOz9kDfr&0=;2_z_9$1K#C5J9Vf_Ftxat9^g&a8qd5aFvkB90^&sXRuQ_$?v`Ny=tOyhnrb4S_EeF?38(dDE%4!&v zS!wA7*~AwvujS-R3v}O{Cq?1gAH(%YLIX zq>0+i)sj~C=fifhS2ZlO;jY$wigoLesvB*EEy9{HLz)NaCA$1nV1&v%-u5-cr=#e3 z!OQ&YX=5$An|zMbO|B);pLaa*-OmLdpkFsCZ=HE98nro6?PMNYh9};{HM(wI*BRW2 zYsz}UdwK4j)ty!mxwiKw$-8pO>3TQ}x!b0NE&P~oBt6?v9sE7dkjCrntaaexs6w#Z zC`gs&C3QN?uJU&CW_d5g)7#PVL3!lmw5x(EueD5x%q6y}L|La$>*H_5 z;o9A>JEw*Iq|qy}1K#bbg$bjgFs;*^5-$}J!NWwYiv{N2$`;}?LGI0^TYWA{B6JQ+H0?G?fvZM`S!ck_kMsKO-m=Z zUb3f69=Qn~!X`KuytdY6)dpD$-G8ilzp9xgpd&ZB3ihh}#ONsvNXJjI@CdMbj`zr4 zs_h86S`-BgszRlavKD$y)d#WDk5yNj>5x@l7!)#PR&kpZEubSx-(7h@--FHzjS**0 zWrEbdm5-(1cP3^hy}Hd0N9vj>ui71CZTSuL7iCiuSwmguyJKc!;aYhdnCB}2yLQsM z?*8q!*&8U7ZBRvoZ~iwbZVC(uaPNZhyc7A=3h}=g0!)fL{H6At+I-I)n6wJM{)syp}xf_#fyuU|1FvjRqKIX->q^vea z4{KQj`CX@KZd+e0f20AhP(cOM_4-zf)cbdm8ogB)Y;!aee;G{#1cD!?QXfWt?Vn)` zv&~C8P9gnNB~jvwI5?E`DBaUP?psDEdbKZdc~))+q(q9RE?nFDjE6CzfpzZE zg{X_8hVpmnrrM6v+n{Z}!0R0YZ(Uw~Lj-k(4*Lk_+z(8e9$_)*X!lgy=gOC1ZzGbZ zL@450EBlFRJV85W$?K-CAIh9v-=!GPl(5;GafO_D&cVGAopa`6G1C;#o1eF$G*dHv zV&0S)sUOPhoZv?a(+ASf$|djRXADlR?4Vh~+EOkI#BggBYkSLOj*i4U+jly>}i+4efcrFhws?>O2cKMgcBy2;Dqt9ed~Tv!pgIRI`2AhvW-?$30g z8T3S9JJe%*k>yMmlv-x4JhDJs0xDlxw-2-?CY*8I6_jU>gtWw$Art!Do#t_j6Xdr& z$vaCek+IJJJXx=4SPfy>uTG%fuybpmhpoz@!Hmb98+;Frs@c<$ z=sP{B$3137eXG`rSSo|jA;y&X%FJv$6gs#unO-i?ea?HNhPN8ueF>OXjk?Faczc?b zMciVTxi%Izv@<;Z_3JG*9$%aJj=5c?hUq?U>fZ8dvA95#o^(T)+LeY7?Dq*1+29R8wvZ1zs0<-XS?1ccA(ivpi>^AijWYi5F!!#5E;Tb1be_(i%M z4LnSma2oVHP6b0UEB#haX_#>6!f6`4{&R(^qb;%)N4mgbwY>hol|x&}UJgyA>(p zPTS$5H*bhM`!7<|EG-jB2$EEuy#+iLe`>K|`G{hkqS(mcC6#vC4FcmUtNj;}FA1r6 zEE+s4AR(2IMEFiYz7y6!u5jUpABgrC^Zq-v5;_5uP167`!o1u2O z6iRksd6rHKGBhz{<+8t3+RN5KNl7B{K8e^Hk3ZA%dO(T?G2dHAW`wdYb~^b zrP{qHfcOLlo}cI0&^hkbpwCiSFw*H~_@H;4W9T6=WzjDP27{C5vel#7lhn;hPHA_2 zv<_kUNQ;%;cMy}34EXFtnd+eClA|w~Q%KgIsT@&>4l?%xD zF58|V>Y_P_%A)WCsxm9LkJ{vK;JV*7Q?ukN>OJ0(w>egROJ)S@{I{|n2P|LwV> zad{#F*L*atBp;v>fwU9+@%oO;-PYW6!c z37vd$Gy@f8BI9>=lh2#K7}-zjo=;jRI_#2)XxF~DZ6(b*5 zD%e)>+4ox~{YnPG?nt5N8K?{kq+ta`PJ@|-qtg3(H**4)>|`+Sa*Q&_k?6V7xH8>+ zB#sEMH_Df??pY%r9#G7%mUg(EoEfJC#g(^PkS6vU1|j#{`gB-vP3u&vE(~FskOi0Ev4l5lA$_SQYdz-3deuPHk&m81>inO0#fOEB{!9LItgz;)V6;88Tz)#;II z{G*%q#VmcAbk62F05FNvhuG$&!-~VpQwPJl(w=3Ho$pCn=vUqAlcRieUh&YQmui!2H)D zic#a*zc(^{wKYs3X&bQ8>34Q6D&xpz?0&deQ{|h6o@&e?8=QXz;!^x5rj({U;`%C6 zS5Q83r@Q+H3LBO$`-Naptvozu;+)^bWnbSV6I!|6f%%j{k^2rZa$CcWT3SY=jq(o$*6g3HvTpR~~X!tC@Mjk%+-k?4>AvSaX?d;urR#WvO}5M8rUkxb&{ zdNTq>1(f0B24Y{WfQzeBRpdxBnaHvZsCrZ0t%$SUiN7sfEsJx8{NyqcbI}SgBQtSq zYv@&(Ns3?zy0E}k-c~R(RuIzAJ}CQH=)h*2{`Jbh_I=>BR1otMmxc{D-dq77A^S#8 zIhMNV8VmloCUxC9cHb|`_VphYKX17CVDks_r+p{4f3WVJg_b`!WFY4>ggv)F4Hn4s zBkYOH7n^3h*Jf#8MrcY#J>-@T4*4TFu$i)I9-~}n*mfG?*yBw-q?c_NV zVW1D;9=%&-9T9bf;OiKe3pXamu^n9D+w&J|;Z`RVU?= zD%y}|?4!k)y+|OJllsBy?74k-r*tRu4BKTm@%24)g3JNC#y?#%oHw&5BQNeAIE0?F z(8Zo%>=~j*GgrPk=$;SpT$&`FZjiJg*PPpf5S7L^b~J22hN3mf{4u3_FwiN)j@?LY zE``{P*A2GrkTwYDw|Q+GOP-WPFz5XyI#e62+XFaDF#yzdgo#-6cxg_Y_UhI5uWVj6 zCWCsga4U+{_kQkOeBLU5w^JKo5{GAt>}0HKc81NA<)L)5_|@g;A<|)TlaX_oBJi4T zomqqg);(+|2gBh>R!;AgyT0tt0&b>_9T8QeNf4S8CirR&6z5YN4#wv(x3!x83RFtSG%|SD6{QFc?l5` zUsrN&lxKXL{xd3!Efp!RQ|+bAVYdB9UD05*#M`kb_p5tC1S-1^tVtuSK_p}PV!-k{ ziQ|~AI1*(l(M-MIZl7{sitv_xR;F|e{m@#av${s8TvqU=TAQmmP}|j%0@6I~BqnW0 z14vl

@2oXb@|$D4xp+7pRl^g8_D|W)1xoFGo-jVg8|z&Z{ut&SbB`{yVo8r;3$` zL8_l)37NaWbA|gr#1AC0*)?I&8sxgk;P0pLeeO0`k-g$1DApt$Pxl7Gr)JYb=+>AlziVA)YK;MuPr*a^(fkB*# zVJGbIyp0Hke<%`P+mW-enR)_ZWh$ao9T(HyEKj_`Fs&cyHYJ8o6V?{mpcxRgq`E)| z>rLqr)`uf*b+3$F5ai~a%^x|S6g16uC1bQT9U#;hO`6RkY+nFU{1;N6dgfpE7sl4? zKU6zz4j?Xj9P{exJ(OS!GTeFy}dE+#v|bAe=9 zM?&_>>NmIfwTF4tiIK`iDVbjzS%k1NecJB7(q9Y=aYr9-H)RKmx51Sc|Ith&`op;W z?BjdL0q;)@5k8bDrkmJBL~phayYKC%F&W1t;})1Elz?0AJ0qnny^QOk*h}mD{BV1`8a(_0qVr~V z#OLV?L?;MkiKCFj2q93^eS)Vz(lkqUJ(@KIT3&-CcKDY~M-5Ej?6KB2cROH?z8I7cvV3a9otlTrc9H-AE<3@M-=5#sB6k zVKJ;LR7Hg0Z}p%wy*Qb++z)01Y$sq*VnbH5E_Sv5v~mP=O#Yi(>>s!G)8T2C) z-|{5`VeS@HSdlgnt4mpt?Ex8|H7F5r*^tNjse-(YaiN#z^MaZ3laYkAF;Qn%<6pV` zlzeS4LNSyn-yg2H!d&LRiw4|RI;6L#ULOXie{DyZ#7+$;>O!~)EAHpYtEzgyk8gXn z_Z*eJBe;|+k2T!}<`?u|;<=Upg{P|0UhG#@o;S`oh^Vf(c@o4PPj2}!DJi0Z6bb{@w~!DWrq2TYkEL^grSGp? zVKVm52Wk1sI@n7Tf!&CAR&QtyJqPotfMVc38jY0gfQ3AmZphsAyb*bX2>xu6& zVSSRU=z4PZ51})`C3`z7OZ^?1aHdZ2+fl|5Dje0f3I}JNl-8k?oSpeHdM}RGiRp?D zZT?p?zS%E)FB@(q-$;wq?-26`c7uI2MWC!78=Sq1GODU0vA)hZl}l;j#%BLKInaGg z&>+P{s<|}ZmcLXKR6#eme`uO@(28-loDF|2?)dymY45v!*!=eCk?0TB&%a#j{?l>8 z*7OgcUbo49_R|lW|8rvG&p6-Q`FGO&G2r_=eaztpuzZBgA9?tXI>rCTsi1Z1Hv0em zkARO5`p7)LBj+R8{?R}Ge>D84FCWR_BRTvh@Kd;9$mc!c!** Date: Mon, 9 Dec 2024 19:38:13 +0300 Subject: [PATCH 3/4] delete .idea --- module_2/goka_golang/project/.idea/.gitignore | 8 -------- module_2/goka_golang/project/.idea/modules.xml | 8 -------- module_2/goka_golang/project/.idea/project.iml | 9 --------- module_2/goka_golang/project/.idea/vcs.xml | 6 ------ 4 files changed, 31 deletions(-) delete mode 100644 module_2/goka_golang/project/.idea/.gitignore delete mode 100644 module_2/goka_golang/project/.idea/modules.xml delete mode 100644 module_2/goka_golang/project/.idea/project.iml delete mode 100644 module_2/goka_golang/project/.idea/vcs.xml diff --git a/module_2/goka_golang/project/.idea/.gitignore b/module_2/goka_golang/project/.idea/.gitignore deleted file mode 100644 index 13566b8..0000000 --- a/module_2/goka_golang/project/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/module_2/goka_golang/project/.idea/modules.xml b/module_2/goka_golang/project/.idea/modules.xml deleted file mode 100644 index a0733a5..0000000 --- a/module_2/goka_golang/project/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/module_2/goka_golang/project/.idea/project.iml b/module_2/goka_golang/project/.idea/project.iml deleted file mode 100644 index 5e764c4..0000000 --- a/module_2/goka_golang/project/.idea/project.iml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/module_2/goka_golang/project/.idea/vcs.xml b/module_2/goka_golang/project/.idea/vcs.xml deleted file mode 100644 index c2365ab..0000000 --- a/module_2/goka_golang/project/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From aae8c2b47506e76692b0b4efe955dcf02dc19fc8 Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 9 Dec 2024 19:47:38 +0300 Subject: [PATCH 4/4] change readme --- module_2/goka_golang/project/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module_2/goka_golang/project/readme.md b/module_2/goka_golang/project/readme.md index b2fa4b0..c62bc8d 100644 --- a/module_2/goka_golang/project/readme.md +++ b/module_2/goka_golang/project/readme.md @@ -1,4 +1,4 @@ -## Проект Kafka Avro на Go +## Авторское решение на Go Этот проект демонстрирует систему обработки потоков сообщений с функциональностью блокировки пользователей и цензуры сообщений.