added temprorary uuid to user in database - for interactions, removed initiator from actions, changed owner_id to user_id in actions table, added initial set of action types
This commit is contained in:
+2
-2
@@ -3,14 +3,14 @@ package chat
|
||||
import "context"
|
||||
|
||||
const (
|
||||
SourceTelegram = "telegram"
|
||||
Telegram = "telegram"
|
||||
)
|
||||
|
||||
// Message represents an incoming message from a user.
|
||||
type Message struct {
|
||||
// Always should be identifier of the source
|
||||
// defined as const in this file.
|
||||
Source string
|
||||
Chat string
|
||||
|
||||
// ID is a universal user identifier
|
||||
// (Telegram chat ID, email, phone, etc).
|
||||
|
||||
@@ -53,9 +53,9 @@ func (b *Bot) Receive(ctx context.Context) <-chan chat.Message {
|
||||
case update := <-updates:
|
||||
if update.Message != nil && update.Message.Chat != nil {
|
||||
msg := chat.Message{
|
||||
Source: chat.SourceTelegram,
|
||||
ID: strconv.FormatInt(update.Message.Chat.ID, 10),
|
||||
Text: update.Message.Text,
|
||||
Chat: chat.Telegram,
|
||||
ID: strconv.FormatInt(update.Message.Chat.ID, 10),
|
||||
Text: update.Message.Text,
|
||||
}
|
||||
|
||||
select {
|
||||
|
||||
@@ -149,7 +149,7 @@ func TestReceive_MockAPI(t *testing.T) {
|
||||
case msg := <-msgs:
|
||||
assert.Equal(t, "123456789", msg.ID)
|
||||
assert.Equal(t, "hello world", msg.Text)
|
||||
assert.Equal(t, "telegram", msg.Source)
|
||||
assert.Equal(t, "telegram", msg.Chat)
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("timeout waiting for message")
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ type Notifications interface {
|
||||
|
||||
type Actions interface {
|
||||
Log(ctx context.Context, a *Action) error
|
||||
Recent(ctx context.Context, ownerID uuid.UUID, limit int) ([]Action, error)
|
||||
Recent(ctx context.Context, userID uuid.UUID, limit int) ([]Action, error)
|
||||
}
|
||||
|
||||
// User represents a Jules user.
|
||||
@@ -68,6 +68,7 @@ type User struct {
|
||||
SearchCount int
|
||||
NotificationCount int
|
||||
CountUpdatedAt time.Time
|
||||
TemporaryCode uuid.UUID
|
||||
Role string
|
||||
}
|
||||
|
||||
@@ -102,10 +103,9 @@ type Notification struct {
|
||||
|
||||
// Action records an interaction between Jules and a user.
|
||||
type Action struct {
|
||||
OwnerID uuid.UUID
|
||||
InitiatorID uuid.UUID
|
||||
ExecutedAt time.Time
|
||||
Payload json.RawMessage
|
||||
UserID uuid.UUID
|
||||
ExecutedAt time.Time
|
||||
Payload json.RawMessage
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -19,24 +19,24 @@ func (a *Actions) Log(ctx context.Context, action *database.Action) error {
|
||||
}
|
||||
|
||||
_, err := a.conn.ExecContext(ctx, `
|
||||
INSERT INTO actions (owner_id, initiator_id, executed_at, payload)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
`, action.OwnerID, action.InitiatorID, action.ExecutedAt, action.Payload)
|
||||
INSERT INTO actions (user_id, executed_at, payload)
|
||||
VALUES ($1, $2, $3)
|
||||
`, action.UserID, action.ExecutedAt, action.Payload)
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *Actions) Recent(ctx context.Context, ownerID uuid.UUID, limit int) ([]database.Action, error) {
|
||||
func (a *Actions) Recent(ctx context.Context, userID uuid.UUID, limit int) ([]database.Action, error) {
|
||||
rows, err := a.conn.QueryContext(ctx, `
|
||||
WITH deleted AS (
|
||||
DELETE FROM actions
|
||||
WHERE owner_id = $1 AND executed_at < NOW() - INTERVAL '3 days'
|
||||
WHERE user_id = $1 AND executed_at < NOW() - INTERVAL '3 days'
|
||||
)
|
||||
SELECT owner_id, initiator_id, executed_at, payload
|
||||
SELECT user_id, executed_at, payload
|
||||
FROM actions
|
||||
WHERE owner_id = $1
|
||||
WHERE user_id = $1
|
||||
ORDER BY executed_at DESC
|
||||
LIMIT $2
|
||||
`, ownerID, limit)
|
||||
`, userID, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -45,7 +45,7 @@ func (a *Actions) Recent(ctx context.Context, ownerID uuid.UUID, limit int) ([]d
|
||||
var actions []database.Action
|
||||
for rows.Next() {
|
||||
var action database.Action
|
||||
if err := rows.Scan(&action.OwnerID, &action.InitiatorID, &action.ExecutedAt, &action.Payload); err != nil {
|
||||
if err := rows.Scan(&action.UserID, &action.ExecutedAt, &action.Payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
actions = append(actions, action)
|
||||
|
||||
@@ -20,10 +20,9 @@ func TestActions_Log(t *testing.T) {
|
||||
payload, _ := json.Marshal(map[string]any{"type": "user_msg", "text": "hello"})
|
||||
|
||||
action := &database.Action{
|
||||
OwnerID: user.ID,
|
||||
InitiatorID: user.ID,
|
||||
ExecutedAt: time.Now().UTC(),
|
||||
Payload: payload,
|
||||
UserID: user.ID,
|
||||
ExecutedAt: time.Now().UTC(),
|
||||
Payload: payload,
|
||||
}
|
||||
|
||||
err := db.actions.Log(t.Context(), action)
|
||||
@@ -32,8 +31,7 @@ func TestActions_Log(t *testing.T) {
|
||||
actions, err := db.actions.Recent(t.Context(), user.ID, 10)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, actions, 1)
|
||||
assert.Equal(t, user.ID, actions[0].OwnerID)
|
||||
assert.Equal(t, user.ID, actions[0].InitiatorID)
|
||||
assert.Equal(t, user.ID, actions[0].UserID)
|
||||
assert.JSONEq(t, string(payload), string(actions[0].Payload))
|
||||
}
|
||||
|
||||
@@ -42,10 +40,9 @@ func TestActions_Log_NilPayload(t *testing.T) {
|
||||
user := setupTestUser(t, db)
|
||||
|
||||
err := db.actions.Log(t.Context(), &database.Action{
|
||||
OwnerID: user.ID,
|
||||
InitiatorID: user.ID,
|
||||
ExecutedAt: time.Now().UTC(),
|
||||
Payload: nil,
|
||||
UserID: user.ID,
|
||||
ExecutedAt: time.Now().UTC(),
|
||||
Payload: nil,
|
||||
})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "payload is required")
|
||||
@@ -56,10 +53,9 @@ func TestActions_Log_InvalidJSON(t *testing.T) {
|
||||
user := setupTestUser(t, db)
|
||||
|
||||
err := db.actions.Log(t.Context(), &database.Action{
|
||||
OwnerID: user.ID,
|
||||
InitiatorID: user.ID,
|
||||
ExecutedAt: time.Now().UTC(),
|
||||
Payload: json.RawMessage(`{invalid}`),
|
||||
UserID: user.ID,
|
||||
ExecutedAt: time.Now().UTC(),
|
||||
Payload: json.RawMessage(`{invalid}`),
|
||||
})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
@@ -72,16 +68,14 @@ func TestActions_Recent(t *testing.T) {
|
||||
payload2, _ := json.Marshal(map[string]any{"type": "user_msg", "text": "second"})
|
||||
|
||||
db.actions.Log(t.Context(), &database.Action{
|
||||
OwnerID: user.ID,
|
||||
InitiatorID: user.ID,
|
||||
ExecutedAt: time.Now().UTC().Add(-time.Hour),
|
||||
Payload: payload1,
|
||||
UserID: user.ID,
|
||||
ExecutedAt: time.Now().UTC().Add(-time.Hour),
|
||||
Payload: payload1,
|
||||
})
|
||||
db.actions.Log(t.Context(), &database.Action{
|
||||
OwnerID: user.ID,
|
||||
InitiatorID: user.ID,
|
||||
ExecutedAt: time.Now().UTC(),
|
||||
Payload: payload2,
|
||||
UserID: user.ID,
|
||||
ExecutedAt: time.Now().UTC(),
|
||||
Payload: payload2,
|
||||
})
|
||||
|
||||
actions, err := db.actions.Recent(t.Context(), user.ID, 10)
|
||||
@@ -130,10 +124,9 @@ func TestActions_Log_DatabaseError(t *testing.T) {
|
||||
|
||||
payload, _ := json.Marshal(map[string]any{"type": "user_msg"})
|
||||
err := db.actions.Log(t.Context(), &database.Action{
|
||||
OwnerID: user.ID,
|
||||
InitiatorID: user.ID,
|
||||
ExecutedAt: time.Now().UTC(),
|
||||
Payload: payload,
|
||||
UserID: user.ID,
|
||||
ExecutedAt: time.Now().UTC(),
|
||||
Payload: payload,
|
||||
})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ CREATE TABLE users (
|
||||
search_count INTEGER NOT NULL,
|
||||
notification_count INTEGER NOT NULL,
|
||||
count_updated_at DATE NOT NULL,
|
||||
temporary_code UUID NOT NULL,
|
||||
role TEXT NOT NULL
|
||||
);
|
||||
|
||||
@@ -45,10 +46,9 @@ CREATE INDEX idx_notifications_initiator_id ON notifications(initiator_id);
|
||||
CREATE INDEX idx_notifications_scheduled_at ON notifications(scheduled_at);
|
||||
|
||||
CREATE TABLE actions (
|
||||
owner_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
initiator_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
executed_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
payload JSONB NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX idx_actions_owner_executed ON actions(owner_id, executed_at DESC);
|
||||
CREATE INDEX idx_actions_owner_executed ON actions(user_id, executed_at DESC);
|
||||
@@ -15,24 +15,24 @@ type Users struct {
|
||||
|
||||
func (u *Users) Create(ctx context.Context, user *database.User) error {
|
||||
_, err := u.conn.ExecContext(ctx, `
|
||||
INSERT INTO users (id, preferred_chat, language, timezone, llm_count, search_count, notification_count, count_updated_at, role)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||
INSERT INTO users (id, preferred_chat, language, timezone, llm_count, search_count, notification_count, count_updated_at, temporary_code, role)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||
`, user.ID, user.PreferredChat, user.Language, user.Timezone,
|
||||
user.LLMCount, user.SearchCount, user.NotificationCount,
|
||||
user.CountUpdatedAt, user.Role)
|
||||
user.CountUpdatedAt, user.TemporaryCode, user.Role)
|
||||
return err
|
||||
}
|
||||
|
||||
func (u *Users) Get(ctx context.Context, id uuid.UUID) (*database.User, error) {
|
||||
var user database.User
|
||||
err := u.conn.QueryRowContext(ctx, `
|
||||
SELECT id, preferred_chat, language, timezone, llm_count, search_count, notification_count, count_updated_at, role
|
||||
SELECT id, preferred_chat, language, timezone, llm_count, search_count, notification_count, count_updated_at, temporary_code, role
|
||||
FROM users
|
||||
WHERE id = $1
|
||||
`, id).Scan(
|
||||
&user.ID, &user.PreferredChat, &user.Language, &user.Timezone,
|
||||
&user.LLMCount, &user.SearchCount, &user.NotificationCount,
|
||||
&user.CountUpdatedAt, &user.Role,
|
||||
&user.CountUpdatedAt, &user.TemporaryCode, &user.Role,
|
||||
)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, database.ErrNotFound
|
||||
@@ -48,11 +48,11 @@ func (u *Users) Update(ctx context.Context, user *database.User) error {
|
||||
UPDATE users
|
||||
SET preferred_chat = $1, language = $2, timezone = $3,
|
||||
llm_count = $4, search_count = $5, notification_count = $6,
|
||||
count_updated_at = $7, role = $8
|
||||
WHERE id = $9
|
||||
count_updated_at = $7, temporary_code = $8, role = $9
|
||||
WHERE id = $10
|
||||
`, user.PreferredChat, user.Language, user.Timezone,
|
||||
user.LLMCount, user.SearchCount, user.NotificationCount,
|
||||
user.CountUpdatedAt, user.Role, user.ID)
|
||||
user.CountUpdatedAt, user.TemporaryCode, user.Role, user.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ func setupTestUser(t *testing.T, db *DB) *database.User {
|
||||
SearchCount: 0,
|
||||
NotificationCount: 0,
|
||||
CountUpdatedAt: time.Now(),
|
||||
TemporaryCode: uuid.New(),
|
||||
Role: "free",
|
||||
}
|
||||
err := db.users.Create(t.Context(), u)
|
||||
@@ -33,6 +34,7 @@ func TestUsers_Create(t *testing.T) {
|
||||
user := setupTestUser(t, db)
|
||||
|
||||
assert.NotEqual(t, uuid.Nil, user.ID)
|
||||
assert.NotEqual(t, uuid.Nil, user.TemporaryCode)
|
||||
assert.Equal(t, "telegram", user.PreferredChat)
|
||||
assert.Equal(t, "en", user.Language)
|
||||
assert.Equal(t, "UTC", user.Timezone)
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package engine
|
||||
|
||||
const (
|
||||
ActionBindChat = "bind_chat"
|
||||
ActionWait = "wait"
|
||||
ActionUpdateLang = "update_lang"
|
||||
ActionUpdateTZ = "update_tz"
|
||||
ActionSetChat = "set_chat"
|
||||
ActionAddFact = "add_fact"
|
||||
ActionRemoveFact = "remove_fact"
|
||||
ActionAddContact = "add_contact"
|
||||
ActionAddNotification = "add_notification"
|
||||
ActionSearch = "search"
|
||||
)
|
||||
|
||||
type BindChat struct {
|
||||
Type string `json:"type"`
|
||||
TargetUUID string `json:"target_uuid"`
|
||||
}
|
||||
|
||||
type Wait struct {
|
||||
Type string `json:"type"`
|
||||
Ms int `json:"ms"`
|
||||
}
|
||||
|
||||
type UpdateLang struct {
|
||||
Type string `json:"type"`
|
||||
Lang string `json:"lang"`
|
||||
}
|
||||
|
||||
type UpdateTZ struct {
|
||||
Type string `json:"type"`
|
||||
TZ string `json:"tz"`
|
||||
}
|
||||
|
||||
type SetChat struct {
|
||||
Type string `json:"type"`
|
||||
Chat string `json:"chat"`
|
||||
}
|
||||
|
||||
type AddFact struct {
|
||||
Type string `json:"type"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type RemoveFact struct {
|
||||
Type string `json:"type"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type AddContact struct {
|
||||
Type string `json:"type"`
|
||||
UUID string `json:"uuid"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type AddNotification struct {
|
||||
Type string `json:"type"`
|
||||
Target string `json:"target"`
|
||||
Time string `json:"time"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type Search struct {
|
||||
Type string `json:"type"`
|
||||
Query string `json:"query"`
|
||||
}
|
||||
@@ -8,12 +8,7 @@ import (
|
||||
)
|
||||
|
||||
func (e *Engine) defaultProcessMessage(ctx context.Context, msg chat.Message) {
|
||||
// uuid, err := e.Database.Chats().GetUserID(ctx, msg.Source, msg.ID)
|
||||
// if err != nil {
|
||||
// if errors.Is(err, database.ErrNotFound) {
|
||||
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
func (e *Engine) defaultProcessNotification(ctx context.Context, notif database.Notification) {
|
||||
|
||||
Reference in New Issue
Block a user