Architec.Ton is a ecosystem on the TON chain with non-custodial wallet, swap, apps catalog and launchpad.
Main app: @architec_ton_bot
Our Chat: @architec_ton
EU Channel: @architecton_eu
Twitter: x.com/architec_ton
Support: @architecton_support
Last updated 2 weeks, 4 days ago
Канал для поиска исполнителей для разных задач и организации мини конкурсов
Last updated 1 month ago
PATCH vs PUT#code_tips
Сегодня в очередной раз столкнулся с тем как люди пытаются решить распространённую проблему изобретением велосипеда: как правильно обновлять часть полей объекта через REST API. Рассмотрим ситуацию: у нас есть endpoint PUT /api/documents/{id}, обновляющий документ со множеством полей. И вот в какой-то момент появляется потребность обновлять не весь документ, а только его часть.
Многие решают использовать null в поле как индикатор: обновлять ли поле или оставить его прежним. Итак, код может выглядеть так:
```
var entity = new Document
{
Field1 = apiModel.Field1 ?? entity.Field1,
...
FieldN = apiModel.FieldN ?? entity.FieldN
};
```
Подход имеет свои недостатки (помимо того что выглядит страшно). Иногда null может означать, что поле просто не задано, и API сохранит старое значение, что может вызвать ошибки.
Существует стандартное решение: метод PATCH. Этот HTTP-метод предназначен для частичного обновления объекта. И для него существует особый формат тела запроса, например:
```
[
{ "op": "add", "path": "/field1", "value": ["foo", "bar"] },
{ "op": "replace", "path": "/field2/prop1", "value": 42 },
{ "op": "move", "from": "/field3/c", "path": "/field4/d" }
{ "op": "copy", "from": "/a/b/d", "path": "/a/b/e" }
]
``
В .NET можно использовать
Microsoft.AspNetCore.JsonPatch.JsonPatchDocument` для обработки таких запросов, например так:
```
[HttpPatch("{id}")]
public async Task Patch(string id, [FromBody] JsonPatchDocument apiModel)
{
var entity = await Repository.GetByIdAsync(id);
apiModel.ApplyTo(entity, error => { ModelState.AddModelError(error.Operation.path, error.ErrorMessage); });
if (!ModelState.IsValid)
return new UnprocessableEntityObjectResult(ModelState);
return NoContent();
}
```
Если ваш API-клиент не поддерживает PATCH, то, конечно, можно использовать PUT или POST. Но если возможно, выбирайте PATCH: это будет интуитивно понятно для пользователей вашего API.
IETF Datatracker
RFC 6902: JavaScript Object Notation (JSON) Patch
JSON Patch defines a JSON document structure for expressing a sequence of operations to apply to a JavaScript Object Notation (JSON) document; it is suitable for use with the HTTP PATCH method. The "application/json-patch+json" media type is used to identify…
AiAssistant - Шаг 22. Создание отклика на вакансию#aiassistant
Отпуск — это хорошо, но чат бот сам себя не напишет (пока!) так что погнали дальше. Сегодня добавим функцию отклика на вакансию, доступную в API, на наш сайт. В идеале мы хотим иметь единый интерфейс для создания "conversations", в том числе откликов на вакансии. Но кастомизация шаблонов "prompts" потребует много времени. Поэтому начнем с простой страницы и API метода для рабочего прототипа, а кастомизацией займемся позже.
Добавим кнопку “Respond to vacancy” на страницу со списком "conversations":
```
mdi-plus
Respond to vacancy
```
Создаем новую страницу resume.vue. На нее добавим форму с полями “vacancy” и “resume” и кнопкой Save:
```
v\-model="vacancy"
filled
counter
rows="8"
label="Vacancy"
hint="Paste the vacancy text here"
:readonly="loading"
/>
...
```
Обработчик для кнопки выглядит аналогично обычным conversations:
```
async add() {
const payload = {
resume: this.resume,
vacancy: this.vacancy
}
await this.$axios.post('api/Conversations/optimize-resume', payload)
.then(() => {
this.$router.push('/conversations')
})
}
```
После запуска приложения заполняйте форму своим резюме и вакансией, чтобы увидеть ответ бота. Качество ответа зависит от содержания резюме, описания вакансии и запросов к ChatGPT. У меня получился приличный отклик после 3 попыток. Его придется подкрутить немного перед отправкой, но это явно быстрее чем если бы я сам его придумывал. Если нужно откликнуться на несколько вакансий, то экономия времени будет еще больше. Эх, мне бы этот инструмент когда нужно было отзываться на 10-15 вакансий в день на Upwork.
Сохранение состояния контейнера PostgreSQL в Docker#code_tips #docker
Привет, друзья! Если вы когда-либо сталкивались с ситуацией, когда данные из вашего Docker-контейнера для PostgreSQL исчезали после закрытия Visual Studio или перезагрузки системы, у меня для вас хорошие новости. Есть способ сохранять данные между сессиями. Давайте разберём, как это делать.
Docker Volume
— это механизм для сохранения и восстановления данных, которые генерируются и используются контейнерами Docker. Вместо того чтобы каждый раз начинать с чистого листа, вы можете использовать Volume для сохранения состояния вашей базы данных.
Для наглядности рассмотрим конфигурационный файл нашего приложения Ai Assistant. У нас тут два контейнера: key.aiassistant.api
и database
.
Сначала определим новый Volume под именем postgres-data:
```
volumes:
key-ai-assistant-data:
```
Теперь давайте подключим этот Volume к нашему контейнеру базы данных:
volumes:
```
- key-ai-assistant-data:/var/lib/postgresql/data
```
Таким образом, весь конфигурационный файл будет выглядеть так:
```
version: '3.4'
services:
key.aiassistant.api:
ports:
- "8000:80"
- "8443:443"
image: ${DOCKER_REGISTRY-}keyaiassistantapi
build:
context: .
dockerfile: Key.AiAssistant.API/Dockerfile
depends_on:
- database
database:
image: "postgres"
restart: always
ports:
- "5432:5432"
environment:
- POSTGRES_NAME=keyAiAssistant
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=mysuperpassword
volumes:
- key-ai-assistant-data:/var/lib/postgresql/data
volumes:
key-ai-assistant-data:
```
Добавив эти конфигурации, данные PostgreSQL будут сохранены на Docker-управляемом томе вашей хост-системы и будут сохраняться при перезапусках и выключениях контейнера. Когда вы запустите контейнер с помощью docker-compose up, он будет использовать существующий том, и ваши данные останутся нетронутыми.
Надеюсь, этот совет поможет вам сделать процесс разработки немного проще и комфортнее!
API моделиВсем привет! Рассмотрим один из ключевых моментов при создании RESTful API — API модели.
Когда мы внедряли функцию редактирования Conversations, в контроллере API был использован UpdateConversationDto. Но если попробовать создать Conversation через Swagger, возникают вопросы: какой id использовать? Тот, что указан в body, или тот, что в route? Какой из них применится, если эти значения различаются?
Решение очевидно: чтобы устранить путаницу, структура body и route должны быть ясными и однозначными. В контексте REST API путь к эндпоинту должен включать id в таком формате: /api/v1/conversations/{id}
. Соответственно, логичным будет исключение id из body для предотвращения дублирования.
Поскольку текущий контроллер зависит от UpdateConversationDto для передачи данных обработчику команд, просто исключить id не получится. Создадим UpdateConversationApiModel только с полем Title и применим его в контроллере:
```
public async Task Put([FromBody] UpdateConversationApiModel conversation, int id)
{
var command = new UpdateConversationCommand
{
UpdateConversationDto = new UpdateConversationDto
{
Id = id,
Title = conversation.Title,
}
};
await _mediator.Send(command);
return NoContent();
}
```
Область применения API моделей на этом не ограничивается, еще их можно использовать в следующих случаях:
1️⃣ Адаптация данных: когда требуется возврат или прием данных в формате, отличном от внутреннего представления.
2️⃣ Безопасность: для ограничения доступа к определенным полям.
3️⃣ Дополнительная информация: когда вам нужно предоставить данные, которые не включены в доменную модель.
4️⃣ Специфичная валидация: которая отсутствует в доменной DTO.
API модели предоставляют дополнительный уровень абстракции, улучшая гибкость, безопасность и удобство поддержки. Да, может возникнуть дополнительная работа по маппингу между доменными DTO и API моделями, но инструменты типа AutoMapper, значительно облегчают этот процесс.
Null VS пустые коллекции#code_tips
При работе с коллекциями программисты сталкиваются с ситуацией, когда коллекция не содержит элементов. В этом контексте многие принимают решение вернуть null. Такой подход может возникать при получении пустого результата из БД или ошибке в условиях генерации коллекции.
Однако, возвращение null может усложнить ваш код. Во-первых, это приводит к необходимости дополнительных проверок. Во-вторых, рискуете столкнуться с исключением NullReferenceException.
Пример неправильного подхода:
```
// Bad method
public Item[] GetArrayBad()
{
return null;
}
// Usage
public void Run()
{
var items = GetArrayBad();
if (items == null)
DoSomething();
DoOther(items);
}
```
Лучше возвращать пустую коллекцию. Это упрощает чтение и работу с кодом.
Пример правильного подхода:
```
//Good method
public Item[] GetArrayGood()
{
return Array.Empty();
}
// Usage
public void Run()
{
var items = GetArrayGood();
DoSomething(items);
}
```
Помните: предпочтение следует отдавать возврату пустых коллекций вместо null. Это делает ваш код чище и безопаснее.
AiAssistant - Шаг 21. Вывод сообщений беседы#aiassistant
Всех с пятницей! На сегодняшний день у нас на очереди визуализация сообщений в наших Conversations. Для начала нам надо загрузить сообщения на странице _conversationId.vue:
```
async asyncData (context) {
…
const messages = await context.app.$axios.$get(api/Conversations/${context.params.conversationId}/Messages)
return {
conversation: …,
prompts,
messages
}
},
```
После загрузки мы передаем сообщения как prop в компонент EditConversation. Но перед тем, как их отображать, необходима небольшая предобработка:
```
messages: this.messages?.map((m) => {
return {
from: m.fromAi ? 'Ai Assistant' : 'You',
text: this.splitText(m.text.trim()),
time: new Date(m.createdDate).toLocaleString(),
color: m.fromAi ? 'green' : 'deep-purple lighten-1'
}
})
```
Итак, сообщения обработаны, теперь нужно их правильно отобразить. Я решил использовать компонент timeline из библиотеки Vuetify:
```
v\-for="(message, index) in conversation.messages"
:key="index"
:color="message.color"
small
>
```
Тадам! Теперь у нас полностью рабочий инструмент для создания, просмотра и редактирования ежедневных полезных подсказок от Chat GPT!
AiAssistant - Шаг 20. Cоздание новых Conversations#aiassistant
Мы уже научились редактировать Conversations. Настало время добавить функционал создания новых.
1️⃣ Кнопка для новой Conversation. Сначала давайте добавим кнопку для создания новой Conversation в верхней части списка:
```
mdi-plus
Start new conversation
``
***2️⃣*** **Страница создания Conversation**. Как вы видите, кнопка перенаправляет нас на страницу
/conversations/new. Эта страница будет иметь несколько отличий от страницы редактирования:
• Нет загрузки Conversation по id и нет необходимости передавать эти данные в компонент EditConversation
• Есть загрузка списка Prompts и передача их в компонент EditConversation
Шаблон страницы получается совсем простой:
`3️⃣ Компонент EditConversation. Добавляем в компонент новый контрол со списком Prompts:
```
v\-model="conversation.prompt"
v\-if="!id"
:items="promptItems"
:readonly="loading"
item\-value="id"
item\-text="title"
label="Prompt"
clearable
return\-object
single\-line>
```
А в коде сохранения добавляем метод add(), который отличается от update HTTP методом PUT, отсутствием {id} в URL и моделью:
```
async add() {
var payload = {
title: this.conversation.title,
promptId: this.conversation.prompt?.id
};
await this.$axios.post('api/Conversations', payload)
.then((res) => {
this.$router.push("/conversations");
});
}
```
Теперь у нас есть все необходимое, чтобы создавать новые Conversations! Однако перед тем, как закончить с ежедневными подсказками, у нас есть еще одна задача: отобразить ответ от ChatGPT. Отличная задача для пятницы!
AiAssistant - Шаг 19. Создаем форму для редактирования Conversation#aiassistant
Всем привет! Сегодня мы добавим возможность изменения Conversations через форму редактирования.
1️⃣ Создание компонентаСоздаем новый файл EditConversation.vue в директории /components/conversations и добавляем в шаблон поле ввода для title и кнопку сохранения:
```
:rules="[required]"
:readonly="loading"
label="Title">
```
2️⃣ Настройка данных для компонентаВ
AiAssistant - Шаг 18. Интеграция API с frontend приложением#aiassistant
Привет всем! Сегодня мы будем интегрировать наше API с приложением на VueJs. Мы уже создали компонент для отображения списка Conversations. Теперь наша задача — загрузить данные для этого компонента прямо из API, а не использовать заранее заданные (хардкоднутые) данные.
1️⃣ Настройка компонента списка: Открываем файл conversations.vue
. Удаляем метод data()
из секции скриптов и заменяем его на asyncData()
с таким содержимым:
```
asyncData(context) {
return context.app.$axios.$get('api/Conversations')
.then(response => {
return {
conversations: response.map(c => {
return {
id: c.id,
title: c.title,
updatedDate: new Date(c.updatedDate),
promptName: c.promptName
};
})
}
});
}
```
Для обращения к API мы используем модуль @nuxtjs/axios(выбрали его при установке), форматируем ответ API если надо (дату преобразуем из строки).
2️⃣ Настройка карточки Conversation: В компоненте ConversationListItem.vue
изменяем тип данных для поля updatedDate на Date и указываем, что хотите отображать дату в локальном формате:
```
div class="text-caption">{{updatedDate.toLocaleString()}}
``
***3️⃣*** **Изменение API**: Наш API пока не возвращает полей
promptNameи
updatedDate`. Добавим их в ConversationListDto. В репозитории создаем метод GetAllWithDetail для получения связанной сущности Prompt:
```
public async Task> GetAllWithDetail() {
return await DbContext.Conversations
.Include(t => t.Prompt)
.ToListAsync();
}
``
***4️⃣*** Обновляем профиль AutoMapper для преобразования
Conversation.Prompt.Nameв
ConversationListDto.PromptName` и готово!
```
CreateMap()
.ForMember(dest => dest.PromptName, e => e.MapFrom(s => s.Prompt == null ? null : s.Prompt.Title));
```
Отлично, теперь мы можем видеть все наши Conversations на странице VueJs приложения.
GitHub
Added API to load list of conversations · keymatic/AiAssistant@e3b8327
Contribute to keymatic/AiAssistant development by creating an account on GitHub.
AiAssistant - Шаг 17. Добавляем компонент со списком Conversations#aiassistant
Привет! Начнем создавать интерфейс нашего приложения. Для начала выведем список уже созданных Conversations. Выводить будем в виде карточек с заголовком Conversation, датой и названием Prompt.
Т. к. при создании приложения мы выбрали Vuetify, то идём сюда и смотрим доступные тэги и стили для оформления наших карточек. В папке components/conversations создаем файл ConversationListItem.vue. В тэге
Architec.Ton is a ecosystem on the TON chain with non-custodial wallet, swap, apps catalog and launchpad.
Main app: @architec_ton_bot
Our Chat: @architec_ton
EU Channel: @architecton_eu
Twitter: x.com/architec_ton
Support: @architecton_support
Last updated 2 weeks, 4 days ago
Канал для поиска исполнителей для разных задач и организации мини конкурсов
Last updated 1 month ago