Compare commits

...

5 Commits

Author SHA1 Message Date
Keiran
227df20bf2 add cli commands for sending/viewing termails 2025-08-06 06:31:36 +01:00
Keiran
7b63560f74 add cli commands for sending/viewing termails 2025-08-06 06:31:27 +01:00
Keiran
91c39549bd add termail db functions 2025-08-06 06:31:09 +01:00
Keiran
a19c475d2a finish updating schema for termails 2025-08-06 06:30:55 +01:00
Keiran
f99343fea8 update schema for termails 2025-08-06 06:30:39 +01:00
4 changed files with 177 additions and 2 deletions

View File

@ -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 {

View File

@ -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

View File

@ -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);

View File

@ -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