feat: integrate user profile pictures with circular avatar display and upload in settings
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 57s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 57s
This commit is contained in:
1 parent
8b043c4809
commit
444442fdf0
13 files changed
+367
-65
No files matched your search
+27
-19
@@ -81,6 +81,7 @@ func (s *Store) migrate() error {
|
||||
discord_webhook_url TEXT NOT NULL DEFAULT '',
|
||||
custom_webhook_url TEXT NOT NULL DEFAULT '',
|
||||
notify_before_minutes INTEGER NOT NULL DEFAULT 30,
|
||||
avatar_url TEXT NOT NULL DEFAULT '',
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
@@ -313,6 +314,7 @@ func (s *Store) migrate() error {
|
||||
"ALTER TABLE users ADD COLUMN discord_webhook_url TEXT NOT NULL DEFAULT '';",
|
||||
"ALTER TABLE users ADD COLUMN custom_webhook_url TEXT NOT NULL DEFAULT '';",
|
||||
"ALTER TABLE users ADD COLUMN notify_before_minutes INTEGER NOT NULL DEFAULT 30;",
|
||||
"ALTER TABLE users ADD COLUMN avatar_url TEXT NOT NULL DEFAULT '';",
|
||||
|
||||
"ALTER TABLE thoughts ADD COLUMN task_list_id TEXT NOT NULL DEFAULT '';",
|
||||
"ALTER TABLE thoughts ADD COLUMN kanban_status TEXT NOT NULL DEFAULT 'todo';",
|
||||
@@ -486,7 +488,7 @@ func (s *Store) EnsureDefaultUser() (*domain.User, error) {
|
||||
|
||||
// --- USERS ---
|
||||
|
||||
const userSelectCols = `id, email, username, password_hash, display_name, role, is_active, alarm_channels, alarm_email, discord_webhook_url, custom_webhook_url, notify_before_minutes, created_at`
|
||||
const userSelectCols = `id, email, username, password_hash, display_name, role, is_active, alarm_channels, alarm_email, discord_webhook_url, custom_webhook_url, notify_before_minutes, avatar_url, created_at`
|
||||
|
||||
func parseAlarmChannels(raw string) []string {
|
||||
parts := strings.Split(raw, ",")
|
||||
@@ -509,7 +511,7 @@ func scanUser(row interface{ Scan(dest ...any) error }) (*domain.User, error) {
|
||||
err := row.Scan(
|
||||
&u.ID, &u.Email, &u.Username, &u.PasswordHash, &u.DisplayName,
|
||||
&u.Role, &isActiveInt, &channelsRaw, &u.AlarmEmail,
|
||||
&u.DiscordWebhookURL, &u.CustomWebhookURL, &u.NotifyBeforeMinutes, &u.CreatedAt,
|
||||
&u.DiscordWebhookURL, &u.CustomWebhookURL, &u.NotifyBeforeMinutes, &u.AvatarURL, &u.CreatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -559,11 +561,12 @@ func (s *Store) CreateUserWithUsername(email, username, passwordHash, displayNam
|
||||
DiscordWebhookURL: "",
|
||||
CustomWebhookURL: "",
|
||||
NotifyBeforeMinutes: 30,
|
||||
AvatarURL: "",
|
||||
CreatedAt: domain.NowISO(),
|
||||
}
|
||||
_, err := s.db.Exec(
|
||||
`INSERT INTO users (id, email, username, password_hash, display_name, role, is_active, alarm_channels, alarm_email, discord_webhook_url, custom_webhook_url, notify_before_minutes, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, 1, '', ?, '', '', 30, ?)`,
|
||||
`INSERT INTO users (id, email, username, password_hash, display_name, role, is_active, alarm_channels, alarm_email, discord_webhook_url, custom_webhook_url, notify_before_minutes, avatar_url, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, 1, '', ?, '', '', 30, '', ?)`,
|
||||
u.ID, u.Email, u.Username, u.PasswordHash, u.DisplayName, u.Role, u.AlarmEmail, u.CreatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -714,7 +717,7 @@ func (s *Store) UpdateUser(
|
||||
discordWebhookURL *string,
|
||||
notifyBeforeMinutes *int,
|
||||
) (*domain.User, error) {
|
||||
return s.UpdateUserFull(userID, displayName, email, role, isActive, nil, nil, discordWebhookURL, nil, notifyBeforeMinutes)
|
||||
return s.UpdateUserFull(userID, displayName, email, role, isActive, nil, nil, discordWebhookURL, nil, notifyBeforeMinutes, nil)
|
||||
}
|
||||
|
||||
func (s *Store) UpdateUserFull(
|
||||
@@ -724,6 +727,7 @@ func (s *Store) UpdateUserFull(
|
||||
alarmChannels *[]string,
|
||||
alarmEmail, discordWebhookURL, customWebhookURL *string,
|
||||
notifyBeforeMinutes *int,
|
||||
avatarURL *string,
|
||||
) (*domain.User, error) {
|
||||
u, err := s.GetUserByID(userID)
|
||||
if err != nil {
|
||||
@@ -756,14 +760,17 @@ func (s *Store) UpdateUserFull(
|
||||
if notifyBeforeMinutes != nil {
|
||||
u.NotifyBeforeMinutes = *notifyBeforeMinutes
|
||||
}
|
||||
if avatarURL != nil {
|
||||
u.AvatarURL = strings.TrimSpace(*avatarURL)
|
||||
}
|
||||
activeInt := 0
|
||||
if u.IsActive {
|
||||
activeInt = 1
|
||||
}
|
||||
channelsStr := strings.Join(u.AlarmChannels, ",")
|
||||
_, err = s.db.Exec(
|
||||
`UPDATE users SET display_name = ?, email = ?, role = ?, is_active = ?, alarm_channels = ?, alarm_email = ?, discord_webhook_url = ?, custom_webhook_url = ?, notify_before_minutes = ? WHERE id = ?`,
|
||||
u.DisplayName, u.Email, u.Role, activeInt, channelsStr, u.AlarmEmail, u.DiscordWebhookURL, u.CustomWebhookURL, u.NotifyBeforeMinutes, userID,
|
||||
`UPDATE users SET display_name = ?, email = ?, role = ?, is_active = ?, alarm_channels = ?, alarm_email = ?, discord_webhook_url = ?, custom_webhook_url = ?, notify_before_minutes = ?, avatar_url = ? WHERE id = ?`,
|
||||
u.DisplayName, u.Email, u.Role, activeInt, channelsStr, u.AlarmEmail, u.DiscordWebhookURL, u.CustomWebhookURL, u.NotifyBeforeMinutes, u.AvatarURL, userID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -2196,7 +2203,7 @@ func (s *Store) DeleteDocShare(currentUserID, docID, targetID string) error {
|
||||
|
||||
func (s *Store) GetCollaborators(currentUserID string) ([]domain.User, error) {
|
||||
rows, err := s.db.Query(
|
||||
`SELECT id, email, display_name, role, is_active, created_at FROM users WHERE id != ? AND is_active = 1 ORDER BY display_name ASC`,
|
||||
`SELECT id, email, display_name, role, is_active, avatar_url, created_at FROM users WHERE id != ? AND is_active = 1 ORDER BY display_name ASC`,
|
||||
currentUserID,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -2208,7 +2215,7 @@ func (s *Store) GetCollaborators(currentUserID string) ([]domain.User, error) {
|
||||
for rows.Next() {
|
||||
var u domain.User
|
||||
var activeInt int
|
||||
if err := rows.Scan(&u.ID, &u.Email, &u.DisplayName, &u.Role, &activeInt, &u.CreatedAt); err == nil {
|
||||
if err := rows.Scan(&u.ID, &u.Email, &u.DisplayName, &u.Role, &activeInt, &u.AvatarURL, &u.CreatedAt); err == nil {
|
||||
u.IsActive = activeInt == 1
|
||||
users = append(users, u)
|
||||
}
|
||||
@@ -2936,7 +2943,7 @@ func (s *Store) GetTaskListShares(taskListID string) ([]domain.TaskListShare, er
|
||||
if shares[i].TargetType == "team" {
|
||||
_ = s.db.QueryRow(`SELECT name FROM teams WHERE id = ?`, shares[i].TargetID).Scan(&shares[i].TargetName)
|
||||
} else {
|
||||
_ = s.db.QueryRow(`SELECT display_name, email FROM users WHERE id = ?`, shares[i].TargetID).Scan(&shares[i].TargetName, &shares[i].Email)
|
||||
_ = s.db.QueryRow(`SELECT display_name, email, avatar_url FROM users WHERE id = ?`, shares[i].TargetID).Scan(&shares[i].TargetName, &shares[i].Email, &shares[i].AvatarURL)
|
||||
}
|
||||
}
|
||||
return shares, nil
|
||||
@@ -2946,25 +2953,26 @@ func (s *Store) getMembersForTaskList(tl *domain.TaskList) []domain.TaskListMemb
|
||||
memberMap := make(map[string]domain.TaskListMember)
|
||||
|
||||
// 1. Owner
|
||||
var ownerName, ownerEmail string
|
||||
_ = s.db.QueryRow(`SELECT display_name, email FROM users WHERE id = ?`, tl.UserID).Scan(&ownerName, &ownerEmail)
|
||||
var ownerName, ownerEmail, ownerAvatar string
|
||||
_ = s.db.QueryRow(`SELECT display_name, email, avatar_url FROM users WHERE id = ?`, tl.UserID).Scan(&ownerName, &ownerEmail, &ownerAvatar)
|
||||
memberMap[tl.UserID] = domain.TaskListMember{
|
||||
UserID: tl.UserID,
|
||||
DisplayName: ownerName,
|
||||
Email: ownerEmail,
|
||||
AvatarURL: ownerAvatar,
|
||||
Role: "owner",
|
||||
}
|
||||
|
||||
// 2. Team members if tl.TeamID != ""
|
||||
if tl.TeamID != "" {
|
||||
rows, err := s.db.Query(
|
||||
`SELECT u.id, u.display_name, u.email FROM team_members tm JOIN users u ON u.id = tm.user_id WHERE tm.team_id = ?`,
|
||||
`SELECT u.id, u.display_name, u.email, u.avatar_url FROM team_members tm JOIN users u ON u.id = tm.user_id WHERE tm.team_id = ?`,
|
||||
tl.TeamID,
|
||||
)
|
||||
if err == nil {
|
||||
for rows.Next() {
|
||||
var m domain.TaskListMember
|
||||
if err := rows.Scan(&m.UserID, &m.DisplayName, &m.Email); err == nil {
|
||||
if err := rows.Scan(&m.UserID, &m.DisplayName, &m.Email, &m.AvatarURL); err == nil {
|
||||
m.Role = "member"
|
||||
memberMap[m.UserID] = m
|
||||
}
|
||||
@@ -2977,13 +2985,13 @@ func (s *Store) getMembersForTaskList(tl *domain.TaskList) []domain.TaskListMemb
|
||||
for _, sh := range tl.Shares {
|
||||
if sh.TargetType == "team" {
|
||||
rows, err := s.db.Query(
|
||||
`SELECT u.id, u.display_name, u.email FROM team_members tm JOIN users u ON u.id = tm.user_id WHERE tm.team_id = ?`,
|
||||
`SELECT u.id, u.display_name, u.email, u.avatar_url FROM team_members tm JOIN users u ON u.id = tm.user_id WHERE tm.team_id = ?`,
|
||||
sh.TargetID,
|
||||
)
|
||||
if err == nil {
|
||||
for rows.Next() {
|
||||
var m domain.TaskListMember
|
||||
if err := rows.Scan(&m.UserID, &m.DisplayName, &m.Email); err == nil {
|
||||
if err := rows.Scan(&m.UserID, &m.DisplayName, &m.Email, &m.AvatarURL); err == nil {
|
||||
m.Role = "member"
|
||||
memberMap[m.UserID] = m
|
||||
}
|
||||
@@ -2992,7 +3000,7 @@ func (s *Store) getMembersForTaskList(tl *domain.TaskList) []domain.TaskListMemb
|
||||
}
|
||||
} else if sh.TargetType == "user" {
|
||||
var m domain.TaskListMember
|
||||
if err := s.db.QueryRow(`SELECT id, display_name, email FROM users WHERE id = ?`, sh.TargetID).Scan(&m.UserID, &m.DisplayName, &m.Email); err == nil {
|
||||
if err := s.db.QueryRow(`SELECT id, display_name, email, avatar_url FROM users WHERE id = ?`, sh.TargetID).Scan(&m.UserID, &m.DisplayName, &m.Email, &m.AvatarURL); err == nil {
|
||||
m.Role = "member"
|
||||
memberMap[m.UserID] = m
|
||||
}
|
||||
@@ -3466,7 +3474,7 @@ func (s *Store) RemoveTeamMember(teamID, userID string) error {
|
||||
|
||||
func (s *Store) GetTeamMembers(teamID string) ([]domain.TeamMember, error) {
|
||||
rows, err := s.db.Query(
|
||||
`SELECT tm.team_id, tm.user_id, u.display_name, u.email, tm.role, tm.joined_at
|
||||
`SELECT tm.team_id, tm.user_id, u.display_name, u.email, u.avatar_url, tm.role, tm.joined_at
|
||||
FROM team_members tm
|
||||
JOIN users u ON u.id = tm.user_id
|
||||
WHERE tm.team_id = ?
|
||||
@@ -3481,7 +3489,7 @@ func (s *Store) GetTeamMembers(teamID string) ([]domain.TeamMember, error) {
|
||||
members := make([]domain.TeamMember, 0)
|
||||
for rows.Next() {
|
||||
var m domain.TeamMember
|
||||
if err := rows.Scan(&m.TeamID, &m.UserID, &m.DisplayName, &m.Email, &m.Role, &m.JoinedAt); err == nil {
|
||||
if err := rows.Scan(&m.TeamID, &m.UserID, &m.DisplayName, &m.Email, &m.AvatarURL, &m.Role, &m.JoinedAt); err == nil {
|
||||
members = append(members, m)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user