Compare commits
5 Commits
b2c7dcae83
...
227df20bf2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
227df20bf2 | ||
|
|
7b63560f74 | ||
|
|
91c39549bd | ||
|
|
a19c475d2a | ||
|
|
f99343fea8 |
@ -8,6 +8,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
@ -54,11 +55,52 @@ var logoutCmd = &cobra.Command{
|
||||
Run: runLogout,
|
||||
}
|
||||
|
||||
var inboxCmd = &cobra.Command{
|
||||
Use: "inbox",
|
||||
Short: "View your termail inbox",
|
||||
Long: "View, search, and manage your termail inbox.",
|
||||
Run: runInbox,
|
||||
}
|
||||
|
||||
var inboxReadCmd = &cobra.Command{
|
||||
Use: "read [termail_id]",
|
||||
Short: "Mark termail as read",
|
||||
Long: "Mark a specific termail as read by ID.",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: runInboxRead,
|
||||
}
|
||||
|
||||
var inboxDeleteCmd = &cobra.Command{
|
||||
Use: "delete [termail_id]",
|
||||
Short: "Delete termail",
|
||||
Long: "Delete a specific termail by ID.",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: runInboxDelete,
|
||||
}
|
||||
|
||||
var sendCmd = &cobra.Command{
|
||||
Use: "send [username]",
|
||||
Short: "Send termail to a user",
|
||||
Long: "Send termail to a specific user by username.",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: runSend,
|
||||
}
|
||||
|
||||
func init() {
|
||||
inboxCmd.Flags().StringP("search", "s", "", "Search termails by content, subject, or sender")
|
||||
inboxCmd.Flags().IntP("limit", "l", 10, "Number of termails to show")
|
||||
inboxCmd.Flags().IntP("offset", "o", 0, "Number of termails to skip")
|
||||
inboxCmd.Flags().Bool("unread", false, "Show only unread termails")
|
||||
|
||||
inboxCmd.AddCommand(inboxReadCmd)
|
||||
inboxCmd.AddCommand(inboxDeleteCmd)
|
||||
|
||||
authCmd.AddCommand(registerCmd)
|
||||
authCmd.AddCommand(loginCmd)
|
||||
authCmd.AddCommand(logoutCmd)
|
||||
rootCmd.AddCommand(authCmd)
|
||||
rootCmd.AddCommand(inboxCmd)
|
||||
rootCmd.AddCommand(sendCmd)
|
||||
}
|
||||
|
||||
func Execute() error {
|
||||
|
||||
@ -72,13 +72,19 @@ func CreateTables() error {
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS messages (
|
||||
CREATE TABLE IF NOT EXISTS termails (
|
||||
id SERIAL PRIMARY KEY,
|
||||
sender_id INT REFERENCES users(id),
|
||||
receiver_id INT REFERENCES users(id),
|
||||
subject VARCHAR(255) NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
is_read BOOLEAN DEFAULT FALSE,
|
||||
sent_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_termails_receiver_id ON termails(receiver_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_termails_sender_id ON termails(sender_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_termails_sent_at ON termails(sent_at);
|
||||
`)
|
||||
|
||||
return err
|
||||
|
||||
@ -16,10 +16,16 @@ CREATE TABLE verification_codes (
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE messages (
|
||||
CREATE TABLE termails (
|
||||
id SERIAL PRIMARY KEY,
|
||||
sender_id INT REFERENCES users(id),
|
||||
receiver_id INT REFERENCES users(id),
|
||||
subject VARCHAR(255) NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
is_read BOOLEAN DEFAULT FALSE,
|
||||
sent_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_termails_receiver_id ON termails(receiver_id);
|
||||
CREATE INDEX idx_termails_sender_id ON termails(sender_id);
|
||||
CREATE INDEX idx_termails_sent_at ON termails(sent_at);
|
||||
|
||||
@ -173,6 +173,127 @@ func GetUserByEmail(ctx context.Context, email string) (*User, error) {
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
type Termail struct {
|
||||
ID int `json:"id"`
|
||||
SenderID int `json:"sender_id"`
|
||||
ReceiverID int `json:"receiver_id"`
|
||||
Subject string `json:"subject"`
|
||||
Content string `json:"content"`
|
||||
IsRead bool `json:"is_read"`
|
||||
SentAt time.Time `json:"sent_at"`
|
||||
Sender string `json:"sender,omitempty"`
|
||||
Receiver string `json:"receiver,omitempty"`
|
||||
}
|
||||
|
||||
type SendTermailRequest struct {
|
||||
ReceiverUsername string `json:"receiver_username"`
|
||||
Subject string `json:"subject"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
func SendTermail(ctx context.Context, senderID int, req SendTermailRequest) (*Termail, error) {
|
||||
receiver, err := GetUserByUsername(ctx, req.ReceiverUsername)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("user not found: %s", req.ReceiverUsername)
|
||||
}
|
||||
|
||||
var termail Termail
|
||||
err = Pool.QueryRow(ctx,
|
||||
"INSERT INTO termails (sender_id, receiver_id, subject, content) VALUES ($1, $2, $3, $4) RETURNING id, sender_id, receiver_id, subject, content, is_read, sent_at",
|
||||
senderID, receiver.ID, req.Subject, req.Content,
|
||||
).Scan(&termail.ID, &termail.SenderID, &termail.ReceiverID, &termail.Subject, &termail.Content, &termail.IsRead, &termail.SentAt)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &termail, nil
|
||||
}
|
||||
|
||||
func GetInbox(ctx context.Context, userID int, limit, offset int) ([]Termail, error) {
|
||||
query := `
|
||||
SELECT t.id, t.sender_id, t.receiver_id, t.subject, t.content, t.is_read, t.sent_at, u.username as sender
|
||||
FROM termails t
|
||||
JOIN users u ON t.sender_id = u.id
|
||||
WHERE t.receiver_id = $1
|
||||
ORDER BY t.sent_at DESC
|
||||
LIMIT $2 OFFSET $3
|
||||
`
|
||||
|
||||
rows, err := Pool.Query(ctx, query, userID, limit, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var termails []Termail
|
||||
for rows.Next() {
|
||||
var t Termail
|
||||
err := rows.Scan(&t.ID, &t.SenderID, &t.ReceiverID, &t.Subject, &t.Content, &t.IsRead, &t.SentAt, &t.Sender)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
termails = append(termails, t)
|
||||
}
|
||||
|
||||
return termails, nil
|
||||
}
|
||||
|
||||
func MarkTermailAsRead(ctx context.Context, termailID, userID int) error {
|
||||
_, err := Pool.Exec(ctx,
|
||||
"UPDATE termails SET is_read = true WHERE id = $1 AND receiver_id = $2",
|
||||
termailID, userID,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func DeleteTermail(ctx context.Context, termailID, userID int) error {
|
||||
result, err := Pool.Exec(ctx,
|
||||
"DELETE FROM termails WHERE id = $1 AND receiver_id = $2",
|
||||
termailID, userID,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rowsAffected := result.RowsAffected()
|
||||
if rowsAffected == 0 {
|
||||
return fmt.Errorf("termail not found or access denied")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func SearchTermails(ctx context.Context, userID int, query string, limit, offset int) ([]Termail, error) {
|
||||
sqlQuery := `
|
||||
SELECT t.id, t.sender_id, t.receiver_id, t.subject, t.content, t.is_read, t.sent_at, u.username as sender
|
||||
FROM termails t
|
||||
JOIN users u ON t.sender_id = u.id
|
||||
WHERE t.receiver_id = $1 AND (t.subject ILIKE $2 OR t.content ILIKE $2 OR u.username ILIKE $2)
|
||||
ORDER BY t.sent_at DESC
|
||||
LIMIT $3 OFFSET $4
|
||||
`
|
||||
|
||||
searchPattern := "%" + query + "%"
|
||||
rows, err := Pool.Query(ctx, sqlQuery, userID, searchPattern, limit, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var termails []Termail
|
||||
for rows.Next() {
|
||||
var t Termail
|
||||
err := rows.Scan(&t.ID, &t.SenderID, &t.ReceiverID, &t.Subject, &t.Content, &t.IsRead, &t.SentAt, &t.Sender)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
termails = append(termails, t)
|
||||
}
|
||||
|
||||
return termails, nil
|
||||
}
|
||||
|
||||
func CleanupUnverifiedUsers(ctx context.Context) error {
|
||||
_, err := Pool.Exec(ctx, `
|
||||
DELETE FROM users
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user