Skip to content

Conversation

@rafaelldi
Copy link
Contributor

Issue #6

Сделал базовый прототип, чтобы дальше детальнее обсуждать (делал пока без тенантов). Подскажите, пожалуйста, по этим вопросам.

Вопросы о том, что откуда брать:

  1. Таймпаду необходимо указать Id и Subdomain организации на таймпаде, откуда лучше мне их получать?
  2. Starts_at и Ends_at - не нашёл временных рамок для всего митапа, есть только для отдельных сессий. Можно выбирать по ним максимальное и минимальное время;
  3. Description_short - тоже не нашёл описаний;
  4. City - у нас это enum, видимо, стоит простой маппер написать на названия городов;
  5. Tickets_limit - тоже нет.

Какие-то из этих параметров можно получать не из объекта Meetup, а из дополнительных параметров метода, какие лучше?

Вопросы, связанные с api:

  1. Правильно я понял, что id события на таймпаде мы хранить не будем, когда нужно будет обновлять, то надо смотреть по совпадению имени?
  2. Секцию партнёры в api я не нашёл, при создании события на самом таймпаде, она там есть? Просто в api есть некое поле custom: Объект с дополнительными полями, специфичными для данной организации, который вроде можно использовать, хотя не понятно, как он вообще выглядит, плюс ещё надо фоточку партнера вставлять;
  3. @AnatolyKulakov не помнишь случайно, как статус для публичного события назывался? Для черновика я по тому powershell репозиторию нашёл draft. Если что буду в сапорт обращаться.

Регистрацию в DI я через IServiceCollection сделал, потому что пока не разбирался, как Autofac добавить. У меня вопрос скорее из любопытства, а почему не стали использовать стандартный DI?

@egorikas
Copy link
Contributor

@AnatolyKulakov тебе тоже интересно будет

@egorikas
Copy link
Contributor

  1. @AnatolyKulakov у каждого города свои же организация и суб-домен?
  2. да, так и нужно
  3. @AnatolyKulakov я думаю это из UI нужно проваливать
  4. Да, придется так сделать, мы сильно завязаны на UI, а там тоже enum
  5. @AnatolyKulakov я так понимаю это из UI тоже

@egorikas
Copy link
Contributor

Регистрацию в DI я через IServiceCollection сделал, потому что пока не разбирался, как Autofac добавить. У меня вопрос скорее из любопытства, а почему не стали использовать стандартный DI?

Потому что Autofac надо выпилить, он остался от прототипа сервиса. C стандартным все ок

@rafaelldi
Copy link
Contributor Author

Добавил работу с различными сообществами:

  1. В папке Templates шаблоны событий в виде Spb-template.cshtml (это если один темплейт для каждого сообщества, или нужно будет ещё что-нибудь придумывать);
  2. API токены ищутся в конфигурации в таком виде:
"TimePad": [
    {"Spb": "token"},
    {"Msk": "token"},
    ...
]
  1. По этим токенам добавляются соответствующие HttpClient;
  2. В методе создания события город беру уже из свойства Venue;
  3. В методе ещё раз проверяю, что токен для этого города был добавлен (может, эта проверка уже избыточна)

@ilabutin
Copy link
Collaborator

А давай наоборот структуру конфигурации:

"Spb" : {
  "TimePad": "token"
}

Мы планируем для каждого сообщества свой файл конфигурации - так будет проще собирать конфигурацию

@rafaelldi
Copy link
Contributor Author

Настройки тогда для сообщества Id и Subdomain на таймпаде можно тоже хранить в конфигурации?

@kulakovt
Copy link
Member

  1. Запроси по API информацию о существующих встречах, там должны быть намёки на то, что это и откуда
  2. Так и надо, время начала митапа - это время начала первой сессии. Время окончания - конец последней
  3. Этого нет в Аудите. Этот параметр нужно запрашивать с UI аргументом
  4. Это должно быть новым полем в Venue, но пока не сделал ибо это ломающее изменение. Пока бери город из Сообщества.
  5. Это должно быть поле в venue. Но пока не нужно было. Надо сделать. Пока принимай из UI

дополнительных параметров метода

Это сложный вопрос. По хорошему, все необходимые данные должны быть в моделе и никаких аргументов не нужно. Но так-как ты делаешь первую интеграцию, мы обнаруживаем новые поля, которых в моделе нет. Нам нужно изменять серверную модель. Но не сильно, ибо данная задача не про это совсем. Если как-то возможно сделать быстрый хак и не отвлекаться на модель, то нужно это сделать. @egorikas должен лучше знать как сейчас устроена модель и как можно её подхачить, чтобы не сломать контракт с UI.

Я вижу необходимость в следующих изменениях:

а. Добавить в Meetup обязательное текстовое поле ShortDescription
б. Добавить в Venue обязательное текстовое поле City
в. Добавить в Venue обязательное целочисленное поле Capacity

Сейчас (пока UI не осилили), для TimePad'а нужно сделать следующие defaults:

а. {Meetup.Day} {Meetup.Month} в гостях у компании {Friend.Name} состоится {Meetup.Number}-я встреча {Community.Name}. Например 17 октября в гостях у компании T-Systems состоится 53-я встреча SpbDotNet
б. Из Сообщества
в. 150

Потом редактору эти параметры, при желании, можно будет поменять в конструкторе самого TimePad.

  1. Да, будем искать по названию, это надёжнее
  2. Парьнёры есть. Запроси по API информацию о существующих встречах, там должны быть намёки на то, что это и откуда. custom - это не то, это по кастомные поля для анкеты регистрации (см. PowerShell скрипт)
  3. Не помню. Запроси по API информацию о существующих встречах, там всё будет

у каждого города свои же организация и суб-домен

Да, в #93 это немного обсуждали.

мы сильно завязаны на UI, а там тоже enum

На UI должны уходить DTO для UI. Наши интеграции должны работать с бизнесс моделью. Она должна быть другой, не такой как для UI. Иначе мы никогда не сможем её менять без того чтобы всё не разрушить. Бизнес модель должна мапиться в UI DTO.

В методе создания события город беру уже из свойства Venue

Будь внимателен. Этого поля ещё нет в Аудите. Это проявление недержания UI'я. Поэтому не ясно откуда это поле берётся и как загружается для старых встреч.

А давай наоборот структуру конфигурации

Да, настройки должны храниться не по сервисам, а по сообществам (см. #93). В качестве идентификатора сообщества (для файлов и секций) надо использовать Community.Id. Ибо не понятно, что такое "Spb.cshtml" и как его сматчить с сообществом.

Id и Subdomain на таймпаде можно тоже хранить в конфигурации?

Не надо, это избыточно. Это всё есть в API. Смотри метод Get-TimePadOrganization()

@rafaelldi , не пытайся догадаться как работает API. Заведи тестовую организацию на TimePad и попробуй посоздавать митапы там. Или лучше спроси в поддержке, кажется что у них есть такая тестовая организация, специально для экспериментов с API.

@rafaelldi
Copy link
Contributor Author

Работа с API TimePad

Пообщался с поддержкой TimePad:

  1. По API нет возможности указывать партнёров;
  2. Нельзя получит список черновиков (draft) событий. Теперь при создании черновика встречи я создаю вместо этого private событие, их получать по API возможность есть. На втором этапе перевожу в тип public;
  3. У них swagger документация не очень соответствует реальности, поэтому пришлось править автосгенерированный клиент. Например, по документации поле помечено как Required.Always, но в ответе оно не приходит, из-за этого падает парсинг ответа.

Работа с тенантами

  1. В конфигурации теперь ожидаю такую структуру:
"Community-1": {
  "TimePad": "token"
}

где 1 - это id сообщества.
5. Темплейт ищу в папке темплейтов в виде Template-community-1.cshtml

{Meetup.Day} {Meetup.Month} в гостях у компании {Friend.Name} состоится {Meetup.Number}-я встреча {Community.Name}. Например 17 октября в гостях у компании T-Systems состоится 53-я встреча SpbDotNet

У нас кстати нет номера встречи (Meetup.Number) в модели.

Остальное поправил. Потестировал, события создаются. Осталось доделать нормальный шаблон и можно смотреть.

@kulakovt
Copy link
Member

kulakovt commented Oct 15, 2019

По API нет возможности указывать партнёров;

Ясно. Ну пока руками будем добавлять.

я создаю вместо этого private

А можешь подробнее рассказать что такое private, public, зачем нужны и чем отличаются от draft?

"Community-1": {

Кажется что тебе не нужен префикс "Community". Идентификатор сообщества уникален.

Template-community-1.cshtml

А давай сделаем такой путь:

Templates\{Community.Id}\TimePad.cshtml

У нас кстати нет номера встречи (Meetup.Number) в модели.

Да. Наверное в Аудите его и не надо, ибо он почти всегда парсится. И ещё не все митапы обязаны иметь номер. Надо эту тему получше продумать, если мы начинаем на номер где-то полагаться.

@rafaelldi
Copy link
Contributor Author

private и draft события видны только организаторам, public видны всем.

Черновики позволяют не указывать все обязательные настройки. Если событие опубликовано, все его обязательные параметры должны быть указаны.

Так как мы сразу заполняем все нужные параметры, для нас различий между private и draft не будет.

Я просто использовал префикс Community, чтобы с помощью регулярок искать все секции в конфигурации. Наверное, если других int ключей не будет, то можно убрать.

@kulakovt
Copy link
Member

@rafaelldi А почему ты не использовал стандартный Options pattern?
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-3.0
Зачем регулярки и конфиги?

@rafaelldi
Copy link
Contributor Author

Я в методе расширения для IServiceCollection сразу регистрирую все HttpClient для каждого сообщества с нужными authorization хедерами. Дальше уже в сервисе получаю по id сообщества его HttpClient и использую в методах.
Здесь, наверное, минус в том, что для новых сообществ придётся рестартовать сервис, чтобы зарегестрировать их клиентов. Но зато позволило настроить эти authorization хедеры в одном месте.
Можно переделать через IOptions.

@kulakovt
Copy link
Member

@rafaelldi Рестарт ради нового сообщества это не большая проблема. Нужно только не забыть в документации отметить это требование.

Переделай лучше любую работу с настройками через Options. Это избавит от кучи проблем. В том числе и от регулярок. А мы потом в эти опции соберём правильные значения из файла, из Compose Env, из Azure и прочих разных мест.

@ilabutin
Copy link
Collaborator

Наверное, если других int ключей не будет, то можно убрать.

А почему 'int'? С моей точки зрения, "ID сообщества" это строка "SpbDotNet", "MskDotNet", и т.п.

@rafaelldi
Copy link
Contributor Author

Ну по модельке Id - это int:

    public class Community
    {
        public int Id { get; set; }
        public string ExportId { get; set; }

        public string Name { get; set; }
        public string City { get; set; }
        public string TimeZone { get; set; }

        public List<Meetup> Meetups { get; set; }
    }

@kulakovt
Copy link
Member

Не, не, не. Это UI модель. Она не подходит для бизнес-логики.
@egorikas , что делать?

@rafaelldi
Copy link
Contributor Author

rafaelldi commented Oct 16, 2019

Я пока сделал темплейт похожим на SpbDotNet события и переписал через IOptionsSnapshot получение токена.
Ещё пара моментов:

  1. Постер для события, видимо, нужно будет вручную устанавливать, потому что в модели нет;
  2. Не придумал пока, как добавлять в темплейт аватарки спикеров, у них в существующих событиях внутренняя ссылка таймпада, т.е вроде как их сначала надо загрузить.

В остальном вроде всё готово, если не считать эти модели. Я их брал из DotNetRuServer.Meetups.BL.Entities (есть ещё DotNetRuServer.Meetups.BL.Models, там лежат модельки вида CommunityVm).

@kulakovt
Copy link
Member

@rafaelldi 1. Постер было бы идеально копировать с предыдущего митапа.
2. Когда я смотрел, фотографии нужно было загружать их на специальный сторонний сервис (там кажется есть хороший API) для картинок и вставлять полученные ссылки в TimePad.

@rafaelldi
Copy link
Contributor Author

  1. Добавил постер из последнего public события, если такого нет, то будет дефолтный;
  2. На картинки можно просто давать ссылку, я беру их из репозитория Аудита

@rafaelldi rafaelldi marked this pull request as ready for review October 20, 2019 18:36
@egorikas
Copy link
Contributor

@AnatolyKulakov тебя все устраивает с точки зрения заказчика?

</ItemGroup>

<ItemGroup>
<Content Include="Templates\1\TimePad.cshtml">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Почему "1"? Кажется договаривались всё складывать в подпапку Community.Id. В данном случае можно всё делать для SpbDotNet, на нём будем обкатывать для остальных.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Id сейчас int у модели, можно брать ExportId, там как раз текстовые названия как SpbDotNet лежат

Copy link
Member

@kulakovt kulakovt Oct 25, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Да, когда я упоминал поля типа Community.Id я везде имелл ввиду схему Аудита. Не схему UI. То как это называется в UI здесь не важно.

<br/>
Telegram: <a href="https://t.me/spbdotnet">https://t.me/SpbDotNet</a>
<br/>
Meetup.com: <a href="https://www.meetup.com/SpbDotNet/">https://www.meetup.com/SpbDotNet</a>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ссылки желательно брать из Community

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В этих моделях нет ссылок у сообществ

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@egorikas , а мы можем обновить модель на актуальную схему Аудита?

{
_clientFactory = factory;
_optionsAccessor = options;
var rootDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().CodeBase);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Путь к рабочей директории лучше получать из AppDomain.BaseDirectory. Ибо текущая сборка может лежать где угодно (например в отдельной темповой папке с версией и изоляцией, как делает IIS). А все конфиги всегда будут в BaseDirectory.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Поправил

_clientFactory = factory;
_optionsAccessor = options;
var rootDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().CodeBase);
var appRoot = rootDir.Substring(5);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Не очень понятно что такое "5", откуда взялось, что обозначает и почему это должно работать?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Там путь всегда возвращался в виде: file:/..., эти пять символов я удалял. Но сейчас в нормальном виде путь возвращается, так что убрал это

var shortDescription = $"{dateDescription} {friendsDescription} состоится встреча {meetup.Community.Name}";

var templateModel = CreateTemplateModel(meetup);
var htmlDescription = await _razorEngine.CompileRenderAsync($"Templates/{meetup.CommunityId}/TimePad.cshtml", templateModel);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Лучше использовать абсолютные пути (Path.Combine(BaseDirectory)). Тогда такой код будет более переносим между различными системами и будет работать более предсказуемо и стабильно. Ибо предсказать где в данный момент находится Environment.CurrentDirectory (которая неявно используется в данном случае) абсолютно невозможно.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Не, там как базовый путь используется то, что указали в методе .UseFilesystemProject(), т.е. получается сейчас AppDomain.CurrentDomain.BaseDirectory

{
Name = "Входной билет",
Price = 0,
Send_personal_links = true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Почему true? На что это влияет?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Написано Категория билетов с отправкой персональных ссылок. У текущих событий стоит true, я с них брал

if (friends is null || friends.Count == 0)
return string.Empty;

var description = "в гостях у " + (friends.Count == 1 ? "компании " : "компаний ") +
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"В гостях" всегда только у первой компании (First()), остальные просто спонсоры, их тут указывать не надо.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Поправил

}
}

public async Task PublishEventAsync(Meetup meetup, CancellationToken ct)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Надеюсь сейчас этот метод ниоткуда не вызывается? Первое время, пока не отладимся, анонсы будут публиковаться только вручную с сайта TimePad. У нас пока слишком много необходимых ручных правок нужно сделать перед автоматической публикацией

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Оба метода пока ниоткуда не вызываются

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ты же добавил контроллер. Там они используются. Вот я бы Publish от греха подальше убрал бы из контроллера, чтобы случайно его не вызвать


var communityOrganization = introspect.Organizations?.SingleOrDefault(o => o.Name.Contains(community.Name));
if (communityOrganization is null)
throw new Exception("Can't find community TimePad organization");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А у нас нормально исключения на UI отображаются? Я, как пользователь портала, увижу причину ошибки?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Стандартного отлова исключений через UseExceptionHandler я вроде не нашёл, а в остальном не знаю

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AnatolyKulakov сейчас универсально вроде не ловится нигде. Я по крайней мере не писал общего Filter для перехвата

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

«У нас не ловится и стандартный обработчик покажет вменяемое сообщение об ошибке» или «у нас не ловится и пользователь ничего полезного не увидит»? Ну т.е. нужно увидеть текст исключения (хотя бы пока). Иначе мы никогда не узнаем об этой ошибке и не поймём почему же всё не работает. Логов ведь тоже нет похоже.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

У нас не ловится и ты все увидишь. Логи тоже надо добавить #101

SpeakersDescriptions = s.Talk.Speakers.Select(sp => new TemplateModel.TemplateSpeaker
{
Description = sp.Speaker.Description,
AvatarUrl = $"https://raw.githubusercontent.com/DotNetRu/Audit/master/db/speakers/{sp.Speaker.ExportId}/avatar.small.jpg"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Думаю надо всё-таки создать issue по загрузки фотографий на их стандартное хранилище. Очень не хочется привязывать структуру хранения в Аудите к TimePad'у. Поменяем место хранения фотографий и старые анонсы превратятся в тыкву. А для кого-то они могут быть важны.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Создал ишью #100

@kulakovt
Copy link
Member

@egorikas , да отличная работа. А как это сейчас будет работать? Похоже что никак? Мы можем прикрутить какую-нибудь кнопочку чтобы можно было тестировать? Можно даже через свагер: чтобы вбить Meetup.Id и у тебя создался приватный анонс в TimePad.

@rafaelldi
Copy link
Contributor Author

Ещё надо не забыть регистрировать конфигурацию с токеном, потому что сервис ожидает IOptionsSnapshot<TimePadConfiguration> options

@egorikas
Copy link
Contributor

@AnatolyKulakov мы можем метод вывалить в сваггер и им можно будет пользоваться. нужный умел фронтендер, чтобы поддержать на UI

@egorikas
Copy link
Contributor

@rafaelldi @AnatolyKulakov давайте реально сделаем контроллер, чтобы можно было кнопочку в сваггере потыкать. мне нравится идея, для тестов, то что нужно

@rafaelldi
Copy link
Contributor Author

Добавил контроллер

@egorikas
Copy link
Contributor

@AnatolyKulakov вродь как MVP все ок

@kulakovt
Copy link
Member

@egorikas , да шикарно, давай вливать и дальше допиливать

@egorikas egorikas merged commit 2bb90e2 into DotNetRu:master Oct 26, 2019
@rafaelldi rafaelldi deleted the Issue-6-TimepadIntegration branch October 26, 2019 09:29
var events = await GetLastPrivateEvents(communityOrganization.Id, timePadClient, ct);
var meetupEvent = events.FirstOrDefault(e => e.Name == meetup.Name);
if (meetupEvent is null)
throw new Exception("Can't find meetup event");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

По хорошему, надо завести специальные еxception, чтобы потом не было проблем с их определением.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants