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:
d1nch8g
2026-04-18 23:20:48 +03:00
parent 03abd9fd89
commit f7dc36465f
11 changed files with 119 additions and 62 deletions
+2 -2
View File
@@ -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).
+3 -3
View File
@@ -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 {
+1 -1
View File
@@ -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")
}
+5 -5
View File
@@ -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 (
+9 -9
View File
@@ -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)
+19 -26
View File
@@ -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);
+8 -8
View File
@@ -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
}
+2
View File
@@ -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)
+67
View File
@@ -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"`
}
-5
View File
@@ -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) {