COMPLETELY REWROTE API

This commit is contained in:
Андреев Григорий 2024-08-27 11:58:52 +03:00
parent 861f889375
commit 2a1d4a57c3

498
api.typ
View File

@ -13,15 +13,23 @@
#let funnyArrow = $~>$
#let bigrect(cnt) = rect(stroke: 1pt, width: 100%, cnt)
#let uritxt(string) = text(fill: rgb(12, 10, 255, 255), raw(string))
#Heading([Руководство], 1)
Обозначается множество запросов,
которые клиент может посыдать серверу для передачи и
получения данных. На один запрос следует один ответ (как минимум что бы сообщить об
успехе запроса).
получения данных. На один запрос следует один ответ.
Обозначаются переменные, которые сервер передаёт nytl шаблонизатору, который в свою очередь
засовывает в html тег ```html <script> ```, что бы при инициализации клиентсайдовой жс-программы,
у неё были эти переменные.
Для каждого запроса есть свой URI адрес на сервере. Для сохранения порядка адреса всех
запросов начинаются в #raw("/internalapi"). Объявление запроса состоит из указания его
запросы начинаются с #uritxt("/api/"). Объявление запроса состоит из указания его
адреса (выступающего чем-то вроде имени), формата отправляемых (на сервер) данных и
формата принимаемых данных. После объявления (в прямоугольнике) идёт описание.
Данные запроса и ответа передаются в формате JSON. Ключевое слово #bold[Sent] обозначает
@ -29,6 +37,8 @@
#Heading([Структура], 1)
Здесь и далее слово "чат" означает то же самое что и комната.
У каждого чата поддерживается текущий HistoryID. Изначально эта переменная (на сервере)
равна нулю, но с удалением, изменением и написанием новых сообщений, с добавлением
и удалением пользователей из группы, изменяется состояние чата. Эти изменения называются
@ -41,165 +51,69 @@
списка событий клиент заменяет свой LocalHistoryID на полученный HistoryID.
И так далее, по циклу. Любой запрос возвращает обновления.
Не только чаты могут получать события. Здесь "сущностями с историей" я называю чаты и
список чатов пользователя.
Не только чаты могут получать события.
Список чатов, в которые входит пользователь, тоже может.
Здесь "сущностями с историей" я называю чаты и список чатов пользователя.
Определим особую структуру hist_entity_request такого формата:
Запросов много, но все они обновляют какую-то из "сущностей с историей".
`hist_entity_request["type"]` = `"chat"` или `"chatlist"`
Например, #uritxt("/api/sendMessage") обновляет историю сообщений чата,
в который новое сообщение было отправлено, а #uritxt("/api/createChat") обновляет
список чатов пользователя, который вызвал этот запрос.
Если запрашивается чат, то нужно указать его ID.
`hist_entity_request["chatId"]` = Число
Значит, для каждого запроса нужен свой способ указать нужную сущность с историей и её
LocalHistoryId, соответствующий сущности, изменяемой запросом, а также нужно уметь
принимать ответ с сервера об изменениях, произошедших с сущностью, что бы уметь
обновлять её состояние.
`hist_entity_request["LocalHistoryId"]` = Число #funnyArrow указывает версию чата в памяти
клиента
#Heading([Чаты], 2) <update_chat>
Пример: ```json {"type": "chat", "chatId": 2, "LocalHistoryId": 12312321}```
```json {"type": "chatlist", "LocalHistoryId": 31231}```
Запросы, изменяющие чат, содержат поле `"chatUpdReq"`, в котором хранится объект с полями
chatId и LocalHistoryId,
а получают ответ с полем `"chatUpdResp`с полями HistoryId (обновлённое), обновлённое lastMsgId,
полем-массивом messages, полем-массивом `members` (о них расскажу позже).
Определим особую структуру hist_entity_response такого формата:
`Sent.chatUpdReq.chatId = Integer` - указывает чат \
`Sent.chatUpdReq.LocalHistoryId = Integer` - через это поле клиент сообщает свой
(возможно устаревший) LocalHistoryId. \
`Recv.chatUpdResp.HistoryId = Integer` - это новое значение HistoryId, прешедшее на замену LocalHistoryId \
`Recv.chatUpdResp.lastMsgId = Integer` - Номер последнего сообщения в чате. -1 если чат пуст. \
`Recv.chatUpdResp.messages = Array` - массив объектов типа `messageSt`. Они выглядят так так: \
`messageSt.id = Integer` - id сообщения (в этом чате). \
`messageSt.isSystem = Boolean` \
`messageSt.exists = Boolean` \
`messageSt.text` \
`messageSt.senderUserId` \
Это всё поля, определяющие внутренности сообщения. Структура messageSt не обязательно означает, что
за время обновления чата появилось такое сообщение, она говорит, что в момент `HistoryId` было такое сообщение
с таким id.\
`Recv.chatUpdResp.members` - массив объектов типа `memberSt`. Они выглядят так: \
`memberSt.userId = Integer` - id пользователя, о членстве которого в этом чате говорит этот объект \
`memberSt.nickname = String` \
`memberSt.name = String` \
`memberSt.roleHere = String` \
`roleHere` это что-то из "admin", "regular", "read-only", "not-a-member".
Самое главное - различать роль "not-a-member" от остальных. Ведь она означает что человек
больше не состоит в чате, и если он отображался в списке, то
его надо удалить. Различие остальных ролей не так принципиально.
`hist_entity_response["type"]` = `"chat"` или `"chatlist"`
#Heading([Список чатов, где есть пользователь], 2) <update_chatList>
Если запрашивается чат, то нужно указать его ID.
`hist_entity_response["chatId"]` = Число
Запросы, изменяющеи список чатов залогиненного пользователя содержат поле `"chatListUpdReq"`,
который содержит объект с полем `LocalHistoryId` (сами понимаете для чего),
а `"chatListUpdResp"`с полем HistoryId (обновлённое), массивом `myChats`
`hist_entity_response["HistoryId"]` = Число. Это новый HistoryId для этой сущности с историей.
`history_entity_response["events"]` = Список новых событий.
Событие - это словарь. Обозначим его ключемым словом #bold[event]:
`event["type"]` = Строка #funnyArrow Это тип события, которые бывают нескольких типов:
- `"newMessage"` - приходит новое сообщение в чат
- `"addedMember"` - в чат добавили нового участника
- `"removedMember"` - из чата выкинули участника
- `"addedChat"` - клиента добавили в чат
- `"removedChat"` - клиента выкинули из чата
#Heading([Событие `newMessage`], 2) <ev_newMessage>
```json
{
"type" : "newMessage",
"previous": 111111 // Id of previous message
"id": 1112 // Id of the message
"content": {
"isSystem": false // Булевое значение, true если это системное сообщение вроде "поприветствуйте нового пользователя". Такие должны выделяться особым цветом.
"text": "<text of the message>" // Text of the message
"sender": 1000 // User ID of sender
},
}
```
#Heading([событие `deletedMessage`], 2)
```json
{
"type": "deletedMessage",
"id": 1112 // If of deleted message
}
```
#Heading([Событие `addedMember`], 2)
```json
{
"type": "addedMember",
"member": 1000 // User ID добавленного человека
"content": {
"name": "<users name>",
"nickname": "<users nickname>"
"role": "regular" // Роль это либо admin, либо regular, либо read-only
}
}
```
#Heading([Событие `removedMember`], 2)
```json
{
"type": "removedMember",
"member": 1000 // User ID удалённого человека (или покинувшего чат человека)
}
```
#Heading([Событие `addedChat`], 2)
```json
{
"type": "addedChat",
"id": 228, // Chat ID
"content": {
"name": "<имя чата>",
"nickname": "<nickname чата>",
"lastMsgId": 1212, // Id последнего сообщения. -1 Если чат пуст
"roleHere": "regular" // Роль текущего пользователя в этом чате
}
}
```
#Heading([Событие `removedChat`], 2)
```json
{
"type": "removedChat",
"id": 228, // Chat ID того чата, из которого клиента удалили / из которого клиент вышел
}
```
Пример объекта hist_entity_response:
```json
{
"type": "chat", "chatId": 2, "HistoryId": 12312325,
"events": [
{
"type": "newMessage",
"previous": 111111,
"id": 111115,
"content": {
"isSystem": false,
"text": "私は頭骨を劇場から盗んだ",
"sender": 666
}
},
{
"type": "newMessage",
"previous": 111115,
"id": 1000000,
"content": {
"isSystem": true,
"text": "Father mushroom left chat",
"dender": 0
}
},
{
"type": "removedMember",
"member": 666
},
{
"type": "addedMember",
"member": 777,
"content": {
"name": "'Definitely not theatre looter'",
"nickname": "father-mushroom-2",
"role": "regular"
}
},
{
"type": "newMessage",
"previous": 1000000,
"id": 1000002,
"content": {
"isSystem": true,
"text": "'Definitely not theatre looter' joined chat",
"sender": 0
}
}
]
}
```
`Recv.chatListUpdResp.myChats` - массив объектов типа `myMembershipSt`.
Каждый такой объект говорит о состоянии "нахождения пользователя (залогиненного) в чате" в момент HistoryId.
Он может говорить как о нахождении пользователя в чате, так и о его уходе оттуда.
Он выглядит так:
`myMembershipSt.chatId = Integer` - id чата \
`myMembershipSt.chatName = String` \
`myMembershipSt.chatNickname = String` \
`myMembershipSt.myRoleHere = String` \
`myRoleHere` это что-то из "admin", "regular", "read-only", "not-a-member".
Самое главное - различать роль "not-a-member" от остальных. Ведь она означает что
чат из списка чатов надо удалить. Различие остальных ролей не так принципиально.
#Heading([Запросы], 1)
@ -211,259 +125,219 @@
Так же, в ответах может присутствовать поле `Recv["error"]`, которое описывает ошибку.
Но оно не обязательно даже в случае когда ошибка есть, когда ошибки нет оно бесполезно.
#let bigrect(cnt) = rect(stroke: 1pt, width: 100%, cnt)
#let uritxt(string) = text(fill: rgb(12, 10, 255, 255), raw(string))
#Heading([Read-only запросы], 2)
#Heading([Получение событий], 3) <api_pollEvents>
#Heading([Получение событий в чате], 3)
#bigrect[
- URI: #uritxt("/internalapi/pollEvents") \
- URI: #uritxt("/api/chatPollEvents") \
- Отправить: \
`Sent["scope"]` = [hist_entity_request, ..., hist_entity_request] #funnyArrow Список
сущностей, по которым клиент хочет получить обновления. \
`Sent.chatUpdReq` - про это поле говорилось в @update_chat \
- Получить: \
`Recv["update"]` = [hist_entity_response, ..., hist_entity_response] #funnyArrow Список
сущностей, для которых пришли изменения.
`Recv.chatUpdResp` - про это поле тоже говорилось выше
]
В списке `Recv["update"]` будут предоставлены обновления только для тех сущностей,
которые были запрошены.
Эта функция может только обновлять чат и только один чат.
#Heading([Получение списка чатов где пользователь состоит], 3)
#Heading([Получение событий в списке чатов вошедшего пользователя], 3)
#bigrect[
- URI: #uritxt("/internalapi/getChatList") \
- URI: #uritxt("/api/chatListPollEvents") \
- Отправить: \
`Sent = {}` #funnyArrow Да, мы просто отправляем пустой словарь \
`Sent.chatListUpdReq` - про это поле говорилось в @update_chatList \
- Получить: \
`Recv["chats"] = Array` \
`Recv["chats"][i]["id"] = Integer` #funnyArrow Chat ID \
`Recv["chats"][i]["content"]["name"] = "<chat name>"` \
`Recv["chats"][i]["content"]["nickname"] = "<chat nickname>"` \
`Recv["chats"][i]["content"]["lastMsgId"]` = Integer #funnyArrow Id последнего сообщения в чате
]
#Heading([Получение информации о чате], 3)
#bigrect[
- URI: #uritxt("/internalapi/getChatInfo") \
- Отправить: \
`Sent["id"] = Integer` Просто отправляем id чата \
- Получить: \
`Recv["name"] = "<chat name>"` \
`Recv["nickname"] = "<chat nickname>"` \
`Recv["lastMsgId"] = Integer` #funnyArrow ID последнего сообщения в чате.
`Recv["roleHere"] = "admin" / "regular" / "read-only"`
]
Если чат пуст, то lastMsgId равен -1.
roleHere указывает роль залогиненного пользователя в этом чате.
#Heading([Получение списка участников чата], 3)
#bigrect[
- URI: #uritxt("/internalapi/getChatMemberList") \
- Отправить: \
`Sent["chatId"] = Integer` Это id чата \
- Получить: \
`Recv["members"] = Array` Получаем мы только список участников чата. \
Вот какие структуры находятся в этом списке: \
`Recv["members"]["id"] = Integer` ID Пользователя \
`Recv["members"]["content"]["name"] = "<users name>"` \
`Recv["members"]["content"]["nickname"] = "<users nickname>"` \
`Recv["members"]["content"]["role"] = "<Роль этого участника чата>"`
]
Роль учатника это либо `"admin"`, либо `"regular"`, либо `"read-only"`. Думаю, семантика их ясна из
названий.
#Heading([Получение информации о пользователе], 3)
#bigrect[
- URI: #uritxt("/internalapi/getUserInfo") \
- Отправить: \
`Sent["id"] = Integer` #funnyArrow Id пользователя о котором хотим получить инфу. \
- Получить: \
`Recv["content"]["name"] = "<user name>"` \
`Recv["content"]["nickname"] = "<user nickname>"` \
]
#Heading([Получение информации о сообщении], 3)
#bigrect[
- URI: #uritxt("/internalapi/getMessageInfo") \
- Отправить: \
`Sent["chatId"] = Integer` \
`Sent["id"] = Integer` \
- Получить: \
`Sent["content"]["text"] = "<text of message>"` \
`Sent["content"]["isSystem"] = Boolean` \
`Sent["content"]["sender"] = 1100` #funnyArrow User ID отправителя \
`Recv.chatListUpdResp` - про это поле тоже говорилось выше
]
#Heading([Получение соседей сообщения], 3)
#bigrect[
- URI: #uritxt("/internalapi/getMessageNeighbours") \
- URI: #uritxt("/api/getMessageNeighbours") \
- Отправить: \
`Sent["chatId"] = Integer` \
`Sent["amount"] = Integer` \
А далее либо \
`Sent["direction"] = "forward"` \
`Sent["id"] = Integer` #funnyArrow id сообщения, от которого начинается отсчет \
Либо \
`Sent["direction"] = "backward"` \
`Sent["previousMsgId"] = Integer`
`Sent.chatUpdReq` - это поле везде значит одно и то же. Отсюда берётся `chatId`,
что бы значть о каком чате идёт речь \
`Sent.msgId` - id сообщения, с которого начинается отсчет. Можно указать -1 при backward-запросе
и отсчет пойдёт "со дна" чата \
`Sent.direction = String` - это либо `"backward"`, либо `"forward"` \
`Sent.amount = Integer` - Либит на пролистывание сообщений \
- Получить: \
`Recv["messages"] = Array` \
Его элементы это ID сообщения и контент сообщения. \
`Recv["messages"][i]["id"]` \
`Recv["messages"][i]["previous"]` \
`Recv["messages"][i]["content"]`
`Sent.chatUpdResp`
]
Структура контента сообщения описана в @ev_newMessage.
Можно получать полную информацию об $n$ сообщения до или после определённого сообщения.
$n = "Sent"."amount"$.
Можно заметить, что в ответе мы не получаем никакого списка сообщений, только класический `chatUpdResp`.
Дело в том, что поля `msgId`, `direction`, `amount` лишь говорят обнови состояние чата, но
ОБ ЭТИХ ВОТ
сообщениях пришли мне всю информацию, что только есть, даже если они не новые и никак не менялись.
Можно узнать соседей сообщения "сверху и снизу". Направление `"backward"` покажет $n$
сообщений до переданного сообщения (они будут расположены в списке в обратном порядке),
направление `"forward"` покажет $n$ сообщений до указанного. Здесь $n$ это выбранное клиентом
количество желаемых сообщений (`Sent["amount"]`).
Если направление forward, то нужно указать id сообщения, чьих соседей мы ищем.
Если направление baackward, то нужно указать id ПРЕДЫДУЩЕГО сообщения, относительно
нашего сообщения. Т.е. в ответ войдёт id сообщения, которое мы указали. Можно указать
-1, тогда точно не вернётся ничего и это не ошибка.
Сервер ОБЯЗАН вернуть ровно $n$ сообщений,
если они есть и ОБЯЗАН вернуть все сообщения до определённого края истории чата, если край достигнут.
Рекомендуется держать сообщения в разреженном массиве.
#Heading([Запросы изменения состояния одного чата], 2)
#Heading([Отправка сообщения], 3)
#bigrect[
- URI: #uritxt("/internalapi/sendMessage") \
- URI: #uritxt("/api/sendMessage") \
- Отправить: \
`Sent["chatId"] = Integer` #funnyArrow В какой чат мы хотим отправить сообщение \
`Sent["LocalHistoryId"] = Integer` #funnyArrow HistoryID для состояния ЭТОГО чата (в памяти клиента) \
`Sent["content"]["text"] = "<text of message>"` \
`Sent.chatUpdReq` - ... \
`Sent.content.text = "<text of message>"` \
- Получить: \
`Recv["update"] = Array`
`Recv.chatUpdResp = Array`
]
Здесь поле `Recv["update"]` означает то же самое, что и в
#uritxt("/internalapi/pollEvents") (см. @api_pollEvents). Там
содержатся только один hist_entity_response - указывающий изменения в этом чате.
Клиент ОБЯЗАН отображать отправленное пользователем сообщение только тогда, когда
он найдёт сообщение с сервера о событии с отправкой этого сообщения.
#Heading([Удаление сообщения], 3)
#bigrect[
- URI: #uritxt("/internalapi/deleteMessage") \
- URI: #uritxt("/api/deleteMessage") \
- Отправить: \
`Sent["chatId"] = Integer` \
`Sent["LocalHistoryId"] = Integer` #funnyArrow HistoryID для состояния ЭТОГО чата \
`Sent["id"] = Integer` \
`Sent.chatUpdReq` \
`Sent.id = Integer` - Id выбранного на расстрел сообщения \
- Получить: \
`Recv["update"] = Array of hist_entity_response` #funnyArrow Всё то же самое (См. @api_pollEvents)
`Recv.chatUpdResp`
]
Recv.update будет содержать только один hist_entity_response (для этого чата)
#Heading([Добавление участника в чат], 3)
#bigrect[
- URI: #uritxt("/internalapi/addMemberToChat") \
- URI: #uritxt("/api/addMemberToChat") \
- Отправить: \
`Sent["chatId"] = Integer` #funnyArrow К какому чату добавляем \
`Sent["LocalHistoryId"] = Integer` #funnyArrow HistoryID для состояния ЭТОГО чата \
`Sent["userId"] = Integer` #funnyArrow ID добавляемого пользователя \
`Sent.chatUpdReq` \
`Sent.nickname = String` - никнейм того участника, которого мы хотим добавить.
- Получить: \
`Recv["update"]`
`Recv.chatUpdResp`
]
Никнейм добавляемого участника должен быть введён безошибочно, иначе вернётся ошибка.
Выполняется только администратором.
#Heading([Удаление участнка из чата], 3)
#bigrect[
- URI: #uritxt("/internalapi/removeMemberFromChat") \
- URI: #uritxt("/api/removeMemberFromChat") \
- Отправить:
`Sent["chatId"] = Integer` #funnyArrow Из какого чата удалить \
`Sent["LocalHistoryId"] = Integer` #funnyArrow HistoryID для состояния ЭТОГО чата \
`Sent["userId"] = Integer` #funnyArrow ID удаляемого пользователя \
`Sent.chatUpdReq` \
`Sent.userId` - id пользователя, которого мы хотим удалить из чата
- Получить: \
`Recv["update"]`
`Recv.chatUpdResp`
]
Как можно заметить, все запросы, изменяющие состояние чата имеют побочную функцию: обновление.
Она обязательна, клиент ОБЯЗАН указывать LocalHistoryID,
в запросе приутствует мнимый hist_entity_requet. Он аддресует тот чат, к котороу применим запрос
Она обязательна, клиент ОБЯЗАН указывать chatUpdReq.
Выполняется только администратором.
#Heading([Запросы изменения состояния списка чатов пользователя], 2)
В запросах этой категории есть ключ `"LocalHistoryId"`, а в ответах на них есть ключ `"update"`,
но они нужны для поллинга событий не какого-то чата, а списка чатов где есть пользователь.
#Heading([Создание чата], 3)
#bigrect[
- URI: #uritxt("/internalapi/createChat")
- URI: #uritxt("/api/createChat")
- Отправить: \
`Sent["LocalHistoryId"] = Integer` #funnyArrow Это ID состояния списка чатов пользователя \
`Sent["content"]["name"] = "<name of chat>"` \
`Sent["content"]["nickname"] = "<nickname of chat>"` \
`Sent.chatListUpdReq` \
`Sent.content.name = String` \
`Sent.content.nickname = String` \
- Получить: \
`Sent["update"]` #funnyArrow События, произошедшие cо списком чатов пользователя.
`Sent["chatId"] = Integer`
`Recv.chatListUpdResp`
]
`Sent["chatId"]` это id созданного по этому запросу чата
`Sent.content.name` и `Sent.content.nickname` это выбранные названия для нашего чата.
Если введены невозможные названия (слишком длинные или занятый никнейм), то вернётся ошибка
Пользователь, создавший чат, мгновенно вступает в него в качестве администратора.
#Heading([Удаление чата], 3)
#Heading([Удаление чата (Уйти из чата)], 3)
#bigrect[
- URI: #uritxt("/internalapi/leaveChat")
- URI: #uritxt("/api/leaveChat")
- Отправить: \
`Sent["LocalHistoryId"] = Integer` #funnyArrow Это ID состояния списка чатов пользователя \
`Sent["id"] = Integer` #funnyArrow Chat Id того чата, который должен быть удалён \
`Sent.chatListUpdReq`
`Sent.chatId` - id чата, который мы покидаем.
- Получить: \
`Sent["update"]` #funnyArrow События, произошедшие cо списком чатов пользователя.
`chatListUpdResp`
]
#Heading([Инициализация списка чатов], 1)
Возникает вопрос "А как программа-клиент узнаёт с какого состояния чата начинать работу,
если мы можем запрашивать только обновление сущностей с историей? Откуда брать 'стартовые данные'?"
Эту информацию сервер динамически вставляет в отправляемую страничку (в тег ```html <script>```) с помощью
New York Transit Line.
Вот какие переменные доступны жс-коду на странице списка чатов:
`pres` - локаль (описана в config.json, поле "presentation". (pres - от слова presentation)) \
`userinfo.id`, `userinfo.nickname`, `userinfo.name` - это всё информация о вошедшем пользователе.
(О себе).
`initial_chatListUpdResp` - этот объект имеет такую же структуру, как и
`Recv.chatListUpdResp` из всех тех запросов к списку чатов, отсюда и название.
В `initial_chatListUpdResp.HistoryId` хранится то значение LocalHistoryId, с которого мы начинаем,
а каждый объект типа `myMembershipSt` в `initial_chatListUpdResp.myChats` говорит об одном чате,
в котором мы членствуем.
Эти данные эквивалентны запросу #uritxt("/api/chatListPollEvents") с LocalHistoryId = 0,
но сейчас вы увидете, что в случае инициализации ЧАТА подобный подход НЕОБХОДИМ.
#Heading([Инициализация чата], 1)
Вот какие переменные доступны жс-коду на странице списка чатов:
`pres` - локаль (описана в config.json, поле "presentation". (pres - от слова presentation)) \
`userinfo.id`, `userinfo.nickname`, `userinfo.name` - это всё информация о вошедшем пользователе.
(О себе). Это всё было и в list-rooms, но теперь добавляются такие переменные:
`chatinfo.id`, `chatinfo.name`, `chatinfo.nickname` - это всё информация о том чате, что мы открыли.
`initial_chatUpdResp` - этот объект имеет такую же структуру, как и `Recv.chatUpdResp`
из всех тех запросов к чату, отсюда и название.
Здесь поля `.HistoryId`, `.members` и `.lastMsgId` имеют то же значение, что и при обычном запросе обновления
(в массиве `members` будет информация о ВСЕХ участниках чата (в начале выполнения жс-кода)),
но массив `.messages` немного хитрее. Если мы зашли по ссылке #uritxt("/chat/<nickanme>"), то
мы просматриваем чат "с конца". И ".messages" будет пустовать. Если же мы зашли по ссылке
#uritxt("/chat/<nickname>/m/<msgId>"), то массив .messages будет содержать ровно один объект типа `messageSt`:
о том самом сообщении, с которого начался просмотр чата. Т.е. отличие от запроса #uritxt("/api/chatPollEvents")
в том, что мы НЕ ХОТИМ ПОЛУЧАТЬ СРАЗУ ВСЕ СООБЩЕНИЯ ИЗ ЧАТА. Мы хотим начать только с одного.
#Heading([Расположение ресурсов веб-чата по URI], 1)
Эта секция не касается internalApi, она о самом веб-чате.
Эта секция не касается API, она о самом веб-чате.
#Heading([Заглавная страница], 2)
По адресу #uritxt("/") показывается список чатов пользователя. Еезалогиненный пользователь,
По адресу #uritxt("/") показывается список чатов пользователя. Незалогиненный пользователь,
зашедший сюда, будет переслан на #uritxt("/login")
#uritxt("/list-rooms") это алиас для #uritxt("/")
#Heading([Логин], 2)
Адрес: #uritxt("/login"). На этой странице нет ничего кроме формы для входа.
Форма отправляет данные на #uritxt("/login") POST запросом.
Успешный вход пересылает на #uritxt("/"), неуспешный показывает ошибку на этой же странице.
#Heading([Создание чата], 2)
Форма создания чата отправляет данные на #uritxt("/createChat") POST запросом.
При успехе пользователь пересылается на страницу чата (#uritxt("/chatRoom/..."))
Где находится форма - сами решайте.
#Heading([Редактирование профиля], 2)
Свой профиль редактируется на странице #uritxt("/mirror"). Туда же отправляются данные формы
Свой профиль редактируется на странице #uritxt("/user/<your nickname>"). Туда же отправляются данные формы
(POST запросом). При успехе пользователь пересылается на главную страницу,
иначе, остаётся на прежней.
иначе, остаётся на прежней и высвечиваются красные коробочки с ошибкой.
#Heading([Просмотр профиля], 2)
Профиль любого человека можно посмотреть на странице #uritxt("/user/<nickname>")
#Heading([Страница чата], 2)
Адрес: #uritxt("/chat/<nickname>"). Об остальных страницах договаривайтесь сами.
Адрес: #uritxt("/chat/<nickname>").
Можно при открытии чата сразу перейти к сообщению с заранее известным id:
#uritxt("/chat/<nickname>/m/<id of message in chat>")
Просмотр списка пользователей на странице #uritxt("/chat-members/<nickname>")
Здесь же администратор может добавлять и удалять участников чата (через api запросы)
#Heading([Замечания], 1)
Эта спецификация не утверждает, что Id для одних и тех же чатов и сообщений
сохраняется междй пользователями. Так же не утверждается, что Id сообщений
сохраняются между чатами. Для именования чатов и пользователей,
пользователи должны использовать никнеймы.
Утверждается, что id сообщений в чате последовательны (от 1 до какого-то натурального числа).
Т.е. все участники чата видят одни и те же номера ссобщений.
Эта спецификация не утверждает, что id для одних и тех же чатов и людей сохраняется между
сессиями. Для именования чатов и пользователей, пользователи должны использовать никнеймы.
Когда чат или пользователь занимают никнейм, а потом заменяют его на новый,
старый не перестаёт им пренадлежать, и не может уже никогда быть занятым другим
чем-либо. Имя чата или пользователя может содержать любые символы. Никнейм