feat(market): add Minha Intenções tab with panel, hook, and badge
- Introduced "Minhas Intenções" tab visible only to logged-in users. - Integrated MyPurchaseIntentionsPanel and useMyPurchaseIntentions hook. - Added tab badge showing count of pending intentions. - Extended session API to include user’s main character for downstream features.
This commit is contained in:
1 parent
f35fee944d
commit
7f42def478
55 files changed
+4906
-95
No files matched your search
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* Model Chat - Representa uma conversa entre comprador e vendedor
|
||||
*/
|
||||
|
||||
import mongoose, { Schema, model, models } from "mongoose";
|
||||
|
||||
export interface IChat {
|
||||
chat_id: string; // ID único: `${listing_id}_${buyer_id}_${seller_id}`
|
||||
listing_id: number; // ID do anúncio (PostgreSQL)
|
||||
buyer_id: number; // ID do comprador (PostgreSQL)
|
||||
seller_id: number; // ID do vendedor (PostgreSQL)
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
last_message_at: Date | null;
|
||||
last_message_preview: string | null;
|
||||
buyer_unread_count: number;
|
||||
seller_unread_count: number;
|
||||
is_active: boolean; // Chat ativo (anúncio ainda disponível)
|
||||
|
||||
// Dados cached do anúncio (para performance)
|
||||
listing_cache: {
|
||||
item_name: string;
|
||||
item_image_url?: string;
|
||||
price_pix?: number;
|
||||
price_tibia_coins?: number;
|
||||
price_gold_coins?: number;
|
||||
server_name: string;
|
||||
listing_status: string;
|
||||
};
|
||||
|
||||
// Dados cached dos usuários
|
||||
buyer_cache: {
|
||||
user_id: number;
|
||||
username: string;
|
||||
character_name?: string;
|
||||
};
|
||||
|
||||
seller_cache: {
|
||||
user_id: number;
|
||||
username: string;
|
||||
character_name?: string;
|
||||
};
|
||||
}
|
||||
|
||||
const ChatSchema = new Schema<IChat>(
|
||||
{
|
||||
chat_id: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true,
|
||||
index: true,
|
||||
},
|
||||
listing_id: {
|
||||
type: Number,
|
||||
required: true,
|
||||
index: true,
|
||||
},
|
||||
buyer_id: {
|
||||
type: Number,
|
||||
required: true,
|
||||
index: true,
|
||||
},
|
||||
seller_id: {
|
||||
type: Number,
|
||||
required: true,
|
||||
index: true,
|
||||
},
|
||||
created_at: {
|
||||
type: Date,
|
||||
default: Date.now,
|
||||
index: true,
|
||||
},
|
||||
updated_at: {
|
||||
type: Date,
|
||||
default: Date.now,
|
||||
},
|
||||
last_message_at: {
|
||||
type: Date,
|
||||
default: null,
|
||||
index: true,
|
||||
},
|
||||
last_message_preview: {
|
||||
type: String,
|
||||
default: null,
|
||||
maxlength: 100,
|
||||
},
|
||||
buyer_unread_count: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
seller_unread_count: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
is_active: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
index: true,
|
||||
},
|
||||
listing_cache: {
|
||||
item_name: { type: String, required: true },
|
||||
item_image_url: { type: String, default: null },
|
||||
price_pix: { type: Number, default: null },
|
||||
price_tibia_coins: { type: Number, default: null },
|
||||
price_gold_coins: { type: Number, default: null },
|
||||
server_name: { type: String, required: true },
|
||||
listing_status: { type: String, required: true },
|
||||
},
|
||||
buyer_cache: {
|
||||
user_id: { type: Number, required: true },
|
||||
username: { type: String, required: true },
|
||||
character_name: { type: String, default: null },
|
||||
},
|
||||
seller_cache: {
|
||||
user_id: { type: Number, required: true },
|
||||
username: { type: String, required: true },
|
||||
character_name: { type: String, default: null },
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: false, // Usamos campos manuais
|
||||
collection: "chats",
|
||||
}
|
||||
);
|
||||
|
||||
// Índices compostos para queries eficientes
|
||||
ChatSchema.index({ buyer_id: 1, updated_at: -1 });
|
||||
ChatSchema.index({ seller_id: 1, updated_at: -1 });
|
||||
ChatSchema.index({ listing_id: 1, buyer_id: 1, seller_id: 1 });
|
||||
|
||||
// Atualizar updated_at antes de salvar
|
||||
ChatSchema.pre("save", function (next) {
|
||||
this.updated_at = new Date();
|
||||
next();
|
||||
});
|
||||
|
||||
export const Chat = models.Chat || model<IChat>("Chat", ChatSchema);
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Model Message - Representa uma mensagem em um chat
|
||||
*/
|
||||
|
||||
import mongoose, { Schema, model, models } from "mongoose";
|
||||
|
||||
export interface IMessage {
|
||||
chat_id: string; // Referência ao chat
|
||||
sender_id: number; // ID do remetente (PostgreSQL)
|
||||
content: string; // Conteúdo da mensagem (sanitizado)
|
||||
created_at: Date;
|
||||
read_at: Date | null; // Quando foi lida
|
||||
edited_at: Date | null; // Se foi editada
|
||||
deleted_at: Date | null; // Soft delete
|
||||
|
||||
// Metadados
|
||||
message_type: "text" | "system"; // System para mensagens automáticas
|
||||
metadata?: {
|
||||
listing_id?: number;
|
||||
action?: "listing_updated" | "listing_sold" | "price_changed";
|
||||
};
|
||||
}
|
||||
|
||||
const MessageSchema = new Schema<IMessage>(
|
||||
{
|
||||
chat_id: {
|
||||
type: String,
|
||||
required: true,
|
||||
index: true,
|
||||
},
|
||||
sender_id: {
|
||||
type: Number,
|
||||
required: true,
|
||||
index: true,
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
required: true,
|
||||
maxlength: 1000,
|
||||
trim: true,
|
||||
},
|
||||
created_at: {
|
||||
type: Date,
|
||||
default: Date.now,
|
||||
index: true,
|
||||
},
|
||||
read_at: {
|
||||
type: Date,
|
||||
default: null,
|
||||
},
|
||||
edited_at: {
|
||||
type: Date,
|
||||
default: null,
|
||||
},
|
||||
deleted_at: {
|
||||
type: Date,
|
||||
default: null,
|
||||
},
|
||||
message_type: {
|
||||
type: String,
|
||||
enum: ["text", "system"],
|
||||
default: "text",
|
||||
},
|
||||
metadata: {
|
||||
type: Schema.Types.Mixed,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: false, // Usamos campos manuais
|
||||
collection: "messages",
|
||||
}
|
||||
);
|
||||
|
||||
// Índices compostos para queries eficientes
|
||||
MessageSchema.index({ chat_id: 1, created_at: -1 }); // Para buscar mensagens de um chat ordenadas
|
||||
MessageSchema.index({ chat_id: 1, sender_id: 1, created_at: -1 });
|
||||
MessageSchema.index({ sender_id: 1, created_at: -1 });
|
||||
|
||||
export const Message = models.Message || model<IMessage>("Message", MessageSchema);
|
||||
|
||||
Reference in new issue
Block a user