add service
This commit is contained in:
193
internal/ai/openai.go
Normal file
193
internal/ai/openai.go
Normal file
@@ -0,0 +1,193 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"smart-search-back/pkg/errors"
|
||||
)
|
||||
|
||||
type OpenAIClient struct {
|
||||
apiKey string
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
type openAIRequest struct {
|
||||
Model string `json:"model"`
|
||||
Messages []openAIMessage `json:"messages"`
|
||||
}
|
||||
|
||||
type openAIMessage struct {
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type openAIResponse struct {
|
||||
Choices []struct {
|
||||
Message openAIMessage `json:"message"`
|
||||
} `json:"choices"`
|
||||
Error *struct {
|
||||
Message string `json:"message"`
|
||||
} `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type tzResponse struct {
|
||||
TZResponse string `json:"tz_response"`
|
||||
}
|
||||
|
||||
func NewOpenAIClient(apiKey string) *OpenAIClient {
|
||||
return &OpenAIClient{
|
||||
apiKey: apiKey,
|
||||
client: &http.Client{},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *OpenAIClient) isMockMode() bool {
|
||||
return c.apiKey == ""
|
||||
}
|
||||
|
||||
func (c *OpenAIClient) GenerateTZ(requestTxt string) (string, error) {
|
||||
if c.isMockMode() {
|
||||
return c.generateMockTZ(requestTxt), nil
|
||||
}
|
||||
|
||||
prompt := fmt.Sprintf(`Ты — эксперт по разработке технических заданий.
|
||||
|
||||
ЗАДАЧА:
|
||||
Преобразовать описание заказа в структурированное техническое задание.
|
||||
|
||||
ВХОДНЫЕ ДАННЫЕ:
|
||||
Описание: %s
|
||||
|
||||
СТРУКТУРА ТЗ:
|
||||
1. Наименование и описание
|
||||
2. Количество и единицы измерения
|
||||
3. Технические требования
|
||||
4. Сроки выполнения/поставки
|
||||
6. Требования к качеству (если есть)
|
||||
7. Стоимость/бюджет (если указан)
|
||||
8. Особые условия
|
||||
|
||||
ПРИМЕР:
|
||||
|
||||
Входные данные: "Нужны офисные столы деревянные, 10 шт, белого цвета, на колесиках, 50 тысяч, на ул. Пушкина"
|
||||
|
||||
Ответ:
|
||||
{
|
||||
"tz_response": "ТЕХНИЧЕСКОЕ ЗАДАНИЕ\n\n1. ПРЕДМЕТ\nПоставка офисных столов\n\n2. КОЛИЧЕСТВО\n10 шт.\n\n3. ТРЕБОВАНИЯ\n- Материал: дерево\n- Цвет: белый\n- На колесиках\n\n4. МЕСТО ДОСТАВКИ\nг. Москва, ул. Пушкина\n\n5. БЮДЖЕТ\n50 000 руб.\n\n6. СРОКИ\nОт согласования"
|
||||
}
|
||||
|
||||
ФОРМАТ ОТВЕТА:
|
||||
{
|
||||
"tz_response": "ТЕХНИЧЕСКОЕ ЗАДАНИЕ\n\n1. РАЗДЕЛ\nДетали...\n\n2. РАЗДЕЛ\nДетали..."
|
||||
}
|
||||
|
||||
ПРАВИЛА:
|
||||
- Ответ ТОЛЬКО в формате JSON
|
||||
- Используй \n для переносов строк
|
||||
- Без текста до или после JSON
|
||||
- Ясная нумерация разделов
|
||||
- Все параметры из описания должны быть в ТЗ
|
||||
- Если параметр не указан → укажи "Не указано"`, requestTxt)
|
||||
|
||||
reqBody := openAIRequest{
|
||||
Model: "gpt-4o-mini",
|
||||
Messages: []openAIMessage{
|
||||
{
|
||||
Role: "user",
|
||||
Content: prompt,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(reqBody)
|
||||
if err != nil {
|
||||
return "", errors.NewInternalError(errors.AIAPIError, "failed to marshal request", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", "https://api.openai.com/v1/chat/completions", bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return "", errors.NewInternalError(errors.AIAPIError, "failed to create request", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+c.apiKey)
|
||||
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return "", errors.NewInternalError(errors.AIAPIError, "failed to send request", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", errors.NewInternalError(errors.AIAPIError, "failed to read response", err)
|
||||
}
|
||||
|
||||
var aiResp openAIResponse
|
||||
if err := json.Unmarshal(body, &aiResp); err != nil {
|
||||
return "", errors.NewInternalError(errors.AIAPIError, "failed to unmarshal response", err)
|
||||
}
|
||||
|
||||
if aiResp.Error != nil {
|
||||
return "", errors.NewInternalError(errors.AIAPIError, aiResp.Error.Message, nil)
|
||||
}
|
||||
|
||||
if len(aiResp.Choices) == 0 {
|
||||
return "", errors.NewInternalError(errors.AIAPIError, "no choices in response", nil)
|
||||
}
|
||||
|
||||
content := aiResp.Choices[0].Message.Content
|
||||
|
||||
content = strings.TrimPrefix(content, "```json")
|
||||
content = strings.TrimPrefix(content, "```")
|
||||
content = strings.TrimSuffix(content, "```")
|
||||
content = strings.TrimSpace(content)
|
||||
|
||||
var tzResp tzResponse
|
||||
if err := json.Unmarshal([]byte(content), &tzResp); err != nil {
|
||||
re := regexp.MustCompile(`\{[\s\S]*\}`)
|
||||
match := re.FindString(content)
|
||||
if match != "" {
|
||||
if err := json.Unmarshal([]byte(match), &tzResp); err != nil {
|
||||
return "", errors.NewInternalError(errors.AIAPIError, "failed to parse TZ response", err)
|
||||
}
|
||||
} else {
|
||||
return "", errors.NewInternalError(errors.AIAPIError, "failed to parse TZ response", err)
|
||||
}
|
||||
}
|
||||
|
||||
return tzResp.TZResponse, nil
|
||||
}
|
||||
|
||||
func (c *OpenAIClient) generateMockTZ(requestTxt string) string {
|
||||
return fmt.Sprintf(`ТЕХНИЧЕСКОЕ ЗАДАНИЕ (MOCK)
|
||||
|
||||
1. ПРЕДМЕТ
|
||||
%s
|
||||
|
||||
2. КОЛИЧЕСТВО
|
||||
По запросу
|
||||
|
||||
3. ТРЕБОВАНИЯ
|
||||
- Качественное исполнение
|
||||
- Соответствие стандартам
|
||||
- Своевременная поставка
|
||||
|
||||
4. МЕСТО ДОСТАВКИ
|
||||
г. Москва
|
||||
|
||||
5. БЮДЖЕТ
|
||||
Уточняется
|
||||
|
||||
6. СРОКИ
|
||||
От согласования
|
||||
|
||||
7. ОСОБЫЕ УСЛОВИЯ
|
||||
Mock-данные для тестирования. API ключ OpenAI не настроен.`, requestTxt)
|
||||
}
|
||||
321
internal/ai/perplexity.go
Normal file
321
internal/ai/perplexity.go
Normal file
@@ -0,0 +1,321 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"smart-search-back/internal/model"
|
||||
"smart-search-back/pkg/errors"
|
||||
)
|
||||
|
||||
type PerplexityClient struct {
|
||||
apiKey string
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
type perplexityRequest struct {
|
||||
Model string `json:"model"`
|
||||
Messages []perplexityMessage `json:"messages"`
|
||||
}
|
||||
|
||||
type perplexityMessage struct {
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type perplexityResponse struct {
|
||||
Choices []struct {
|
||||
Message perplexityMessage `json:"message"`
|
||||
} `json:"choices"`
|
||||
Usage *struct {
|
||||
PromptTokens int `json:"prompt_tokens"`
|
||||
CompletionTokens int `json:"completion_tokens"`
|
||||
} `json:"usage,omitempty"`
|
||||
Error *struct {
|
||||
Message string `json:"message"`
|
||||
} `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type supplierData struct {
|
||||
CompanyName string `json:"company_name"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Address string `json:"adress"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
func NewPerplexityClient(apiKey string) *PerplexityClient {
|
||||
return &PerplexityClient{
|
||||
apiKey: apiKey,
|
||||
client: &http.Client{},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *PerplexityClient) isMockMode() bool {
|
||||
return c.apiKey == ""
|
||||
}
|
||||
|
||||
func (c *PerplexityClient) FindSuppliers(tzText string) ([]*model.Supplier, int, int, error) {
|
||||
if c.isMockMode() {
|
||||
return c.generateMockSuppliers(), 1000, 500, nil
|
||||
}
|
||||
|
||||
prompt := fmt.Sprintf(`Ты — эксперт по поиску поставщиков на российском рынке.
|
||||
|
||||
ЗАДАЧА:
|
||||
Найти компании-поставщики/производители для технического задания:
|
||||
%s
|
||||
|
||||
ОПРЕДЕЛЕНИЕ ТИПА ПОИСКА:
|
||||
- Если ТЗ требует производства/изготовления → ищи производителей
|
||||
- Если ТЗ требует готовый товар/услугу → ищи компании, которые это продают
|
||||
|
||||
КРИТЕРИИ:
|
||||
1. Максимальная релевантность
|
||||
2. Действующие компании с полными контактами
|
||||
3. Приоритет: производители > дистрибьюторы
|
||||
|
||||
ПРИМЕРЫ ОТВЕТОВ:
|
||||
|
||||
Пример 1 - ТЗ: "Поставка офисной мебели: столы 20 шт, стулья 50 шт"
|
||||
[
|
||||
{
|
||||
"company_name": "ООО Мебельная фабрика Союз",
|
||||
"email": "zakaz@mebelsoyuz.ru",
|
||||
"phone": "+7 495 123-45-67",
|
||||
"adress": "г. Москва, ул. Промышленная, д. 15",
|
||||
"url": ""
|
||||
}
|
||||
]
|
||||
|
||||
Пример 2 - ТЗ: "Производство кованых изделий: ворота, решётки, перила"
|
||||
[
|
||||
{
|
||||
"company_name": "ООО Кузня Премиум",
|
||||
"email": "info@kuzniya.ru",
|
||||
"phone": "",
|
||||
"adress": "г. Москва, ул. Заводская, д. 42",
|
||||
"url": "www.mebelsoyuz.ru"
|
||||
}
|
||||
]
|
||||
|
||||
ФОРМАТ ОТВЕТА - ТОЛЬКО JSON:
|
||||
[
|
||||
{
|
||||
"company_name": "...",
|
||||
"email": "...",
|
||||
"phone": "...",
|
||||
"adress": "...",
|
||||
"url": "..."
|
||||
}
|
||||
]
|
||||
|
||||
ПРАВИЛА:
|
||||
- Минимум 15 компаний, максимум 100
|
||||
- Только валидный JSON массив, без текста вокруг
|
||||
- email и url всегда заполнены (не null)
|
||||
- Если другие поля пустые, то можно оставить пустую строку -> ""
|
||||
- Сортируй по релевантности`, tzText)
|
||||
|
||||
reqBody := perplexityRequest{
|
||||
Model: "llama-3.1-sonar-large-128k-online",
|
||||
Messages: []perplexityMessage{
|
||||
{
|
||||
Role: "user",
|
||||
Content: prompt,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(reqBody)
|
||||
if err != nil {
|
||||
return nil, 0, 0, errors.NewInternalError(errors.AIAPIError, "failed to marshal request", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", "https://api.perplexity.ai/chat/completions", bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return nil, 0, 0, errors.NewInternalError(errors.AIAPIError, "failed to create request", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+c.apiKey)
|
||||
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, 0, 0, errors.NewInternalError(errors.AIAPIError, "failed to send request", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, 0, 0, errors.NewInternalError(errors.AIAPIError, "failed to read response", err)
|
||||
}
|
||||
|
||||
var aiResp perplexityResponse
|
||||
if err := json.Unmarshal(body, &aiResp); err != nil {
|
||||
return nil, 0, 0, errors.NewInternalError(errors.AIAPIError, "failed to unmarshal response", err)
|
||||
}
|
||||
|
||||
if aiResp.Error != nil {
|
||||
return nil, 0, 0, errors.NewInternalError(errors.AIAPIError, aiResp.Error.Message, nil)
|
||||
}
|
||||
|
||||
if len(aiResp.Choices) == 0 {
|
||||
return nil, 0, 0, errors.NewInternalError(errors.AIAPIError, "no choices in response", nil)
|
||||
}
|
||||
|
||||
content := aiResp.Choices[0].Message.Content
|
||||
|
||||
content = strings.TrimPrefix(content, "```json")
|
||||
content = strings.TrimPrefix(content, "```")
|
||||
content = strings.TrimSuffix(content, "```")
|
||||
content = strings.TrimSpace(content)
|
||||
|
||||
var supplierDataList []supplierData
|
||||
if err := json.Unmarshal([]byte(content), &supplierDataList); err != nil {
|
||||
re := regexp.MustCompile(`\[[\s\S]*\]`)
|
||||
match := re.FindString(content)
|
||||
if match != "" {
|
||||
if err := json.Unmarshal([]byte(match), &supplierDataList); err != nil {
|
||||
return nil, 0, 0, errors.NewInternalError(errors.AIAPIError, "failed to parse suppliers response", err)
|
||||
}
|
||||
} else {
|
||||
return nil, 0, 0, errors.NewInternalError(errors.AIAPIError, "failed to parse suppliers response", err)
|
||||
}
|
||||
}
|
||||
|
||||
suppliers := make([]*model.Supplier, 0, len(supplierDataList))
|
||||
for _, sd := range supplierDataList {
|
||||
suppliers = append(suppliers, &model.Supplier{
|
||||
Name: sd.CompanyName,
|
||||
Email: sd.Email,
|
||||
Phone: sd.Phone,
|
||||
Address: sd.Address,
|
||||
URL: sd.URL,
|
||||
})
|
||||
}
|
||||
|
||||
promptTokens := 0
|
||||
responseTokens := 0
|
||||
if aiResp.Usage != nil {
|
||||
promptTokens = aiResp.Usage.PromptTokens
|
||||
responseTokens = aiResp.Usage.CompletionTokens
|
||||
}
|
||||
|
||||
return suppliers, promptTokens, responseTokens, nil
|
||||
}
|
||||
|
||||
func (c *PerplexityClient) generateMockSuppliers() []*model.Supplier {
|
||||
return []*model.Supplier{
|
||||
{
|
||||
Name: "ООО Поставщик-1 (Mock)",
|
||||
Email: "supplier1@example.com",
|
||||
Phone: "+7 (495) 123-45-67",
|
||||
Address: "г. Москва, ул. Примерная, д. 1",
|
||||
URL: "https://supplier1.example.com",
|
||||
},
|
||||
{
|
||||
Name: "ООО Поставщик-2 (Mock)",
|
||||
Email: "supplier2@example.com",
|
||||
Phone: "+7 (495) 234-56-78",
|
||||
Address: "г. Москва, ул. Примерная, д. 2",
|
||||
URL: "https://supplier2.example.com",
|
||||
},
|
||||
{
|
||||
Name: "ООО Поставщик-3 (Mock)",
|
||||
Email: "supplier3@example.com",
|
||||
Phone: "+7 (495) 345-67-89",
|
||||
Address: "г. Москва, ул. Примерная, д. 3",
|
||||
URL: "https://supplier3.example.com",
|
||||
},
|
||||
{
|
||||
Name: "ООО Производитель-1 (Mock)",
|
||||
Email: "producer1@example.com",
|
||||
Phone: "+7 (495) 456-78-90",
|
||||
Address: "г. Санкт-Петербург, ул. Тестовая, д. 10",
|
||||
URL: "https://producer1.example.com",
|
||||
},
|
||||
{
|
||||
Name: "ООО Производитель-2 (Mock)",
|
||||
Email: "producer2@example.com",
|
||||
Phone: "+7 (495) 567-89-01",
|
||||
Address: "г. Санкт-Петербург, ул. Тестовая, д. 20",
|
||||
URL: "https://producer2.example.com",
|
||||
},
|
||||
{
|
||||
Name: "ООО Дистрибьютор-1 (Mock)",
|
||||
Email: "distributor1@example.com",
|
||||
Phone: "+7 (495) 678-90-12",
|
||||
Address: "г. Казань, ул. Демо, д. 5",
|
||||
URL: "https://distributor1.example.com",
|
||||
},
|
||||
{
|
||||
Name: "ООО Дистрибьютор-2 (Mock)",
|
||||
Email: "distributor2@example.com",
|
||||
Phone: "+7 (495) 789-01-23",
|
||||
Address: "г. Казань, ул. Демо, д. 15",
|
||||
URL: "https://distributor2.example.com",
|
||||
},
|
||||
{
|
||||
Name: "ООО Импортер-1 (Mock)",
|
||||
Email: "importer1@example.com",
|
||||
Phone: "+7 (495) 890-12-34",
|
||||
Address: "г. Новосибирск, ул. Примера, д. 100",
|
||||
URL: "https://importer1.example.com",
|
||||
},
|
||||
{
|
||||
Name: "ООО Импортер-2 (Mock)",
|
||||
Email: "importer2@example.com",
|
||||
Phone: "+7 (495) 901-23-45",
|
||||
Address: "г. Новосибирск, ул. Примера, д. 200",
|
||||
URL: "https://importer2.example.com",
|
||||
},
|
||||
{
|
||||
Name: "ООО Оптовик-1 (Mock)",
|
||||
Email: "wholesale1@example.com",
|
||||
Phone: "+7 (495) 012-34-56",
|
||||
Address: "г. Екатеринбург, ул. Тестовая, д. 50",
|
||||
URL: "https://wholesale1.example.com",
|
||||
},
|
||||
{
|
||||
Name: "ООО Оптовик-2 (Mock)",
|
||||
Email: "wholesale2@example.com",
|
||||
Phone: "+7 (495) 123-45-67",
|
||||
Address: "г. Екатеринбург, ул. Тестовая, д. 60",
|
||||
URL: "https://wholesale2.example.com",
|
||||
},
|
||||
{
|
||||
Name: "ООО Фабрика-1 (Mock)",
|
||||
Email: "factory1@example.com",
|
||||
Phone: "+7 (495) 234-56-78",
|
||||
Address: "г. Нижний Новгород, Промзона, д. 1",
|
||||
URL: "https://factory1.example.com",
|
||||
},
|
||||
{
|
||||
Name: "ООО Фабрика-2 (Mock)",
|
||||
Email: "factory2@example.com",
|
||||
Phone: "+7 (495) 345-67-89",
|
||||
Address: "г. Нижний Новгород, Промзона, д. 2",
|
||||
URL: "https://factory2.example.com",
|
||||
},
|
||||
{
|
||||
Name: "ООО Завод-1 (Mock)",
|
||||
Email: "plant1@example.com",
|
||||
Phone: "+7 (495) 456-78-90",
|
||||
Address: "г. Челябинск, Индустриальная, д. 10",
|
||||
URL: "https://plant1.example.com",
|
||||
},
|
||||
{
|
||||
Name: "ООО Завод-2 (Mock)",
|
||||
Email: "plant2@example.com",
|
||||
Phone: "+7 (495) 567-89-01",
|
||||
Address: "г. Челябинск, Индустриальная, д. 20",
|
||||
URL: "https://plant2.example.com",
|
||||
},
|
||||
}
|
||||
}
|
||||
111
internal/config/config.go
Normal file
111
internal/config/config.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Database DatabaseConfig `yaml:"database"`
|
||||
AI AIConfig `yaml:"ai"`
|
||||
Security SecurityConfig `yaml:"security"`
|
||||
GRPC GRPCConfig `yaml:"grpc"`
|
||||
Logging LoggingConfig `yaml:"logging"`
|
||||
}
|
||||
|
||||
type DatabaseConfig struct {
|
||||
Host string `yaml:"host"`
|
||||
Port int `yaml:"port"`
|
||||
Name string `yaml:"name"`
|
||||
User string `yaml:"user"`
|
||||
Password string `yaml:"password"`
|
||||
SSLMode string `yaml:"ssl_mode"`
|
||||
MaxConns int `yaml:"max_conns"`
|
||||
MinConns int `yaml:"min_conns"`
|
||||
}
|
||||
|
||||
type AIConfig struct {
|
||||
OpenAIKey string `yaml:"openai_key"`
|
||||
PerplexityKey string `yaml:"perplexity_key"`
|
||||
}
|
||||
|
||||
type SecurityConfig struct {
|
||||
JWTSecret string `yaml:"jwt_secret"`
|
||||
CryptoSecret string `yaml:"crypto_secret"`
|
||||
}
|
||||
|
||||
type GRPCConfig struct {
|
||||
Port int `yaml:"port"`
|
||||
MaxConnections int `yaml:"max_connections"`
|
||||
}
|
||||
|
||||
type LoggingConfig struct {
|
||||
Level string `yaml:"level"`
|
||||
}
|
||||
|
||||
func Load(path string) (*Config, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read config file: %w", err)
|
||||
}
|
||||
|
||||
data = []byte(expandEnvWithDefaults(string(data)))
|
||||
|
||||
var cfg Config
|
||||
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal config: %w", err)
|
||||
}
|
||||
|
||||
if err := cfg.validate(); err != nil {
|
||||
return nil, fmt.Errorf("invalid config: %w", err)
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
func expandEnvWithDefaults(s string) string {
|
||||
re := regexp.MustCompile(`\$\{([^:}]+):([^}]*)\}`)
|
||||
result := re.ReplaceAllStringFunc(s, func(match string) string {
|
||||
parts := re.FindStringSubmatch(match)
|
||||
if len(parts) != 3 {
|
||||
return match
|
||||
}
|
||||
envVar := parts[1]
|
||||
defaultVal := parts[2]
|
||||
if val := os.Getenv(envVar); val != "" {
|
||||
return val
|
||||
}
|
||||
return defaultVal
|
||||
})
|
||||
return os.ExpandEnv(result)
|
||||
}
|
||||
|
||||
func (c *Config) validate() error {
|
||||
if c.Database.Host == "" {
|
||||
return fmt.Errorf("database host is required")
|
||||
}
|
||||
if c.Database.Name == "" {
|
||||
return fmt.Errorf("database name is required")
|
||||
}
|
||||
if c.Database.User == "" {
|
||||
return fmt.Errorf("database user is required")
|
||||
}
|
||||
if c.Database.Password == "" {
|
||||
return fmt.Errorf("database password is required")
|
||||
}
|
||||
if c.Security.JWTSecret == "" {
|
||||
return fmt.Errorf("JWT secret is required")
|
||||
}
|
||||
if c.Security.CryptoSecret == "" {
|
||||
return fmt.Errorf("crypto secret is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) DatabaseURL() string {
|
||||
return fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=%s",
|
||||
c.Database.User, c.Database.Password, c.Database.Host, c.Database.Port, c.Database.Name, c.Database.SSLMode)
|
||||
}
|
||||
31
internal/database/migrations.go
Normal file
31
internal/database/migrations.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
_ "github.com/jackc/pgx/v5/stdlib"
|
||||
"github.com/pressly/goose/v3"
|
||||
)
|
||||
|
||||
func RunMigrations(databaseURL string) error {
|
||||
db, err := sql.Open("pgx", databaseURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open database connection for migrations: %w", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
if err := db.Ping(); err != nil {
|
||||
return fmt.Errorf("failed to ping database before migrations: %w", err)
|
||||
}
|
||||
|
||||
if err := goose.SetDialect("postgres"); err != nil {
|
||||
return fmt.Errorf("failed to set goose dialect: %w", err)
|
||||
}
|
||||
|
||||
if err := goose.Up(db, "migrations"); err != nil {
|
||||
return fmt.Errorf("failed to run migrations: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
70
internal/grpc/auth_handler.go
Normal file
70
internal/grpc/auth_handler.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"smart-search-back/pkg/errors"
|
||||
pb "smart-search-back/pkg/pb/api/proto/auth"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (h *AuthHandler) Login(ctx context.Context, req *pb.LoginRequest) (*pb.LoginResponse, error) {
|
||||
accessToken, refreshToken, err := h.authService.Login(
|
||||
ctx,
|
||||
req.Email,
|
||||
req.Password,
|
||||
req.Ip,
|
||||
req.UserAgent,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.ToGRPCError(err, h.logger, "AuthService.Login")
|
||||
}
|
||||
|
||||
return &pb.LoginResponse{
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: refreshToken,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Refresh(ctx context.Context, req *pb.RefreshRequest) (*pb.RefreshResponse, error) {
|
||||
accessToken, err := h.authService.Refresh(ctx, req.RefreshToken)
|
||||
if err != nil {
|
||||
return nil, errors.ToGRPCError(err, h.logger, "AuthService.Refresh")
|
||||
}
|
||||
|
||||
return &pb.RefreshResponse{
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: req.RefreshToken,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Validate(ctx context.Context, req *pb.ValidateRequest) (*pb.ValidateResponse, error) {
|
||||
userID, err := h.authService.Validate(ctx, req.AccessToken)
|
||||
if err != nil {
|
||||
h.logger.Warn("Token validation failed",
|
||||
zap.String("method", "AuthService.Validate"),
|
||||
zap.Error(err),
|
||||
)
|
||||
return &pb.ValidateResponse{
|
||||
Valid: false,
|
||||
UserId: 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &pb.ValidateResponse{
|
||||
Valid: true,
|
||||
UserId: int64(userID),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Logout(ctx context.Context, req *pb.LogoutRequest) (*pb.LogoutResponse, error) {
|
||||
err := h.authService.Logout(ctx, req.AccessToken)
|
||||
if err != nil {
|
||||
return nil, errors.ToGRPCError(err, h.logger, "AuthService.Logout")
|
||||
}
|
||||
|
||||
return &pb.LogoutResponse{
|
||||
Success: true,
|
||||
}, nil
|
||||
}
|
||||
45
internal/grpc/invite_handler.go
Normal file
45
internal/grpc/invite_handler.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
"smart-search-back/pkg/errors"
|
||||
pb "smart-search-back/pkg/pb/api/proto/invite"
|
||||
)
|
||||
|
||||
func (h *InviteHandler) Generate(ctx context.Context, req *pb.GenerateRequest) (*pb.GenerateResponse, error) {
|
||||
invite, err := h.inviteService.Generate(ctx, int(req.UserId), int(req.TtlDays), int(req.MaxUses))
|
||||
if err != nil {
|
||||
return nil, errors.ToGRPCError(err, h.logger, "InviteService.Generate")
|
||||
}
|
||||
|
||||
return &pb.GenerateResponse{
|
||||
Code: strconv.FormatInt(invite.Code, 10),
|
||||
MaxUses: int32(invite.CanBeUsedCount),
|
||||
ExpiresAt: timestamppb.New(invite.ExpiresAt),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *InviteHandler) GetInfo(ctx context.Context, req *pb.GetInfoRequest) (*pb.GetInfoResponse, error) {
|
||||
code, err := strconv.ParseInt(req.Code, 10, 64)
|
||||
if err != nil {
|
||||
return nil, errors.ToGRPCError(err, h.logger, "InviteService.GetInfo")
|
||||
}
|
||||
|
||||
invite, err := h.inviteService.GetInfo(ctx, code)
|
||||
if err != nil {
|
||||
return nil, errors.ToGRPCError(err, h.logger, "InviteService.GetInfo")
|
||||
}
|
||||
|
||||
return &pb.GetInfoResponse{
|
||||
Code: strconv.FormatInt(invite.Code, 10),
|
||||
UserId: int64(invite.UserID),
|
||||
CanBeUsedCount: int32(invite.CanBeUsedCount),
|
||||
UsedCount: int32(invite.UsedCount),
|
||||
ExpiresAt: timestamppb.New(invite.ExpiresAt),
|
||||
IsActive: invite.IsActive,
|
||||
CreatedAt: timestamppb.New(invite.CreatedAt),
|
||||
}, nil
|
||||
}
|
||||
91
internal/grpc/request_handler.go
Normal file
91
internal/grpc/request_handler.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
"smart-search-back/pkg/errors"
|
||||
pb "smart-search-back/pkg/pb/api/proto/request"
|
||||
)
|
||||
|
||||
func (h *RequestHandler) CreateTZ(ctx context.Context, req *pb.CreateTZRequest) (*pb.CreateTZResponse, error) {
|
||||
requestTxt := req.RequestTxt
|
||||
if len(req.FileData) > 0 {
|
||||
requestTxt += "\n[File: " + req.FileName + "]"
|
||||
}
|
||||
|
||||
requestID, tzText, err := h.requestService.CreateTZ(ctx, int(req.UserId), requestTxt)
|
||||
if err != nil {
|
||||
return nil, errors.ToGRPCError(err, h.logger, "RequestService.CreateTZ")
|
||||
}
|
||||
|
||||
return &pb.CreateTZResponse{
|
||||
RequestId: requestID.String(),
|
||||
TzText: tzText,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *RequestHandler) ApproveTZ(ctx context.Context, req *pb.ApproveTZRequest) (*pb.ApproveTZResponse, error) {
|
||||
requestID, err := uuid.Parse(req.RequestId)
|
||||
if err != nil {
|
||||
return nil, errors.ToGRPCError(err, h.logger, "RequestService.ApproveTZ")
|
||||
}
|
||||
|
||||
_, err = h.requestService.ApproveTZ(ctx, requestID, req.FinalTz, int(req.UserId))
|
||||
if err != nil {
|
||||
return nil, errors.ToGRPCError(err, h.logger, "RequestService.ApproveTZ")
|
||||
}
|
||||
|
||||
return &pb.ApproveTZResponse{
|
||||
Success: true,
|
||||
MailingStatus: "sent",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *RequestHandler) GetMailingList(ctx context.Context, req *pb.GetMailingListRequest) (*pb.GetMailingListResponse, error) {
|
||||
requests, err := h.requestService.GetMailingList(ctx, int(req.UserId))
|
||||
if err != nil {
|
||||
return nil, errors.ToGRPCError(err, h.logger, "RequestService.GetMailingList")
|
||||
}
|
||||
|
||||
items := make([]*pb.MailingItem, 0, len(requests))
|
||||
for _, r := range requests {
|
||||
items = append(items, &pb.MailingItem{
|
||||
RequestId: r.ID.String(),
|
||||
RequestTxt: r.RequestTxt,
|
||||
FinalTz: r.FinalTZ,
|
||||
MailingStatus: r.MailingStatus,
|
||||
CreatedAt: timestamppb.New(r.CreatedAt),
|
||||
SuppliersFound: 0,
|
||||
})
|
||||
}
|
||||
|
||||
return &pb.GetMailingListResponse{
|
||||
Items: items,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *RequestHandler) GetMailingListByID(ctx context.Context, req *pb.GetMailingListByIDRequest) (*pb.GetMailingListByIDResponse, error) {
|
||||
requestID, err := uuid.Parse(req.RequestId)
|
||||
if err != nil {
|
||||
return nil, errors.ToGRPCError(err, h.logger, "RequestService.GetMailingListByID")
|
||||
}
|
||||
|
||||
detail, err := h.requestService.GetMailingListByID(ctx, requestID)
|
||||
if err != nil {
|
||||
return nil, errors.ToGRPCError(err, h.logger, "RequestService.GetMailingListByID")
|
||||
}
|
||||
|
||||
return &pb.GetMailingListByIDResponse{
|
||||
Item: &pb.MailingItem{
|
||||
RequestId: detail.RequestID.String(),
|
||||
RequestTxt: detail.Title,
|
||||
FinalTz: detail.MailText,
|
||||
MailingStatus: "sent",
|
||||
CreatedAt: timestamppb.New(time.Now()),
|
||||
SuppliersFound: int32(len(detail.Suppliers)),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
78
internal/grpc/server.go
Normal file
78
internal/grpc/server.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"smart-search-back/internal/ai"
|
||||
"smart-search-back/internal/repository"
|
||||
"smart-search-back/internal/service"
|
||||
authpb "smart-search-back/pkg/pb/api/proto/auth"
|
||||
invitepb "smart-search-back/pkg/pb/api/proto/invite"
|
||||
requestpb "smart-search-back/pkg/pb/api/proto/request"
|
||||
supplierpb "smart-search-back/pkg/pb/api/proto/supplier"
|
||||
userpb "smart-search-back/pkg/pb/api/proto/user"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"go.uber.org/zap"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type AuthHandler struct {
|
||||
authpb.UnimplementedAuthServiceServer
|
||||
authService service.AuthService
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
type UserHandler struct {
|
||||
userpb.UnimplementedUserServiceServer
|
||||
userService service.UserService
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
type InviteHandler struct {
|
||||
invitepb.UnimplementedInviteServiceServer
|
||||
inviteService service.InviteService
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
type RequestHandler struct {
|
||||
requestpb.UnimplementedRequestServiceServer
|
||||
requestService service.RequestService
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
type SupplierHandler struct {
|
||||
supplierpb.UnimplementedSupplierServiceServer
|
||||
supplierService service.SupplierService
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func NewHandlers(pool *pgxpool.Pool, jwtSecret, cryptoSecret, openAIKey, perplexityKey string, logger *zap.Logger) (*AuthHandler, *UserHandler, *InviteHandler, *RequestHandler, *SupplierHandler) {
|
||||
userRepo := repository.NewUserRepository(pool, cryptoSecret)
|
||||
sessionRepo := repository.NewSessionRepository(pool)
|
||||
inviteRepo := repository.NewInviteRepository(pool)
|
||||
requestRepo := repository.NewRequestRepository(pool)
|
||||
supplierRepo := repository.NewSupplierRepository(pool)
|
||||
tokenUsageRepo := repository.NewTokenUsageRepository(pool)
|
||||
|
||||
openAIClient := ai.NewOpenAIClient(openAIKey)
|
||||
perplexityClient := ai.NewPerplexityClient(perplexityKey)
|
||||
|
||||
authService := service.NewAuthService(userRepo, sessionRepo, jwtSecret, cryptoSecret)
|
||||
userService := service.NewUserService(userRepo, requestRepo, cryptoSecret)
|
||||
inviteService := service.NewInviteService(inviteRepo, userRepo)
|
||||
requestService := service.NewRequestService(requestRepo, supplierRepo, tokenUsageRepo, userRepo, openAIClient, perplexityClient)
|
||||
supplierService := service.NewSupplierService(supplierRepo)
|
||||
|
||||
return &AuthHandler{authService: authService, logger: logger},
|
||||
&UserHandler{userService: userService, logger: logger},
|
||||
&InviteHandler{inviteService: inviteService, logger: logger},
|
||||
&RequestHandler{requestService: requestService, logger: logger},
|
||||
&SupplierHandler{supplierService: supplierService, logger: logger}
|
||||
}
|
||||
|
||||
func RegisterServices(s *grpc.Server, authH *AuthHandler, userH *UserHandler, inviteH *InviteHandler, requestH *RequestHandler, supplierH *SupplierHandler) {
|
||||
authpb.RegisterAuthServiceServer(s, authH)
|
||||
userpb.RegisterUserServiceServer(s, userH)
|
||||
invitepb.RegisterInviteServiceServer(s, inviteH)
|
||||
requestpb.RegisterRequestServiceServer(s, requestH)
|
||||
supplierpb.RegisterSupplierServiceServer(s, supplierH)
|
||||
}
|
||||
29
internal/grpc/supplier_handler.go
Normal file
29
internal/grpc/supplier_handler.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"smart-search-back/pkg/errors"
|
||||
pb "smart-search-back/pkg/pb/api/proto/supplier"
|
||||
)
|
||||
|
||||
func (h *SupplierHandler) ExportExcel(ctx context.Context, req *pb.ExportExcelRequest) (*pb.ExportExcelResponse, error) {
|
||||
requestID, err := uuid.Parse(req.RequestId)
|
||||
if err != nil {
|
||||
return nil, errors.ToGRPCError(err, h.logger, "SupplierService.ExportExcel")
|
||||
}
|
||||
|
||||
fileData, err := h.supplierService.ExportExcel(ctx, requestID)
|
||||
if err != nil {
|
||||
return nil, errors.ToGRPCError(err, h.logger, "SupplierService.ExportExcel")
|
||||
}
|
||||
|
||||
fileName := "suppliers_" + requestID.String() + ".xlsx"
|
||||
|
||||
return &pb.ExportExcelResponse{
|
||||
FileData: fileData,
|
||||
FileName: fileName,
|
||||
MimeType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
}, nil
|
||||
}
|
||||
66
internal/grpc/user_handler.go
Normal file
66
internal/grpc/user_handler.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"smart-search-back/pkg/errors"
|
||||
pb "smart-search-back/pkg/pb/api/proto/user"
|
||||
)
|
||||
|
||||
func (h *UserHandler) GetInfo(ctx context.Context, req *pb.GetInfoRequest) (*pb.GetInfoResponse, error) {
|
||||
user, err := h.userService.GetInfo(ctx, int(req.UserId))
|
||||
if err != nil {
|
||||
return nil, errors.ToGRPCError(err, h.logger, "UserService.GetInfo")
|
||||
}
|
||||
|
||||
return &pb.GetInfoResponse{
|
||||
Email: user.Email,
|
||||
Name: user.Name,
|
||||
Phone: user.Phone,
|
||||
CompanyName: user.CompanyName,
|
||||
PaymentStatus: user.PaymentStatus,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *UserHandler) GetBalance(ctx context.Context, req *pb.GetBalanceRequest) (*pb.GetBalanceResponse, error) {
|
||||
balance, err := h.userService.GetBalance(ctx, int(req.UserId))
|
||||
if err != nil {
|
||||
return nil, errors.ToGRPCError(err, h.logger, "UserService.GetBalance")
|
||||
}
|
||||
|
||||
return &pb.GetBalanceResponse{
|
||||
Balance: balance,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *UserHandler) GetStatistics(ctx context.Context, req *pb.GetStatisticsRequest) (*pb.GetStatisticsResponse, error) {
|
||||
stats, err := h.userService.GetStatistics(ctx, int(req.UserId))
|
||||
if err != nil {
|
||||
return nil, errors.ToGRPCError(err, h.logger, "UserService.GetStatistics")
|
||||
}
|
||||
|
||||
return &pb.GetStatisticsResponse{
|
||||
TotalRequests: int32(stats.RequestsCount),
|
||||
SuccessfulRequests: int32(stats.SuppliersCount),
|
||||
FailedRequests: 0,
|
||||
TotalSpent: 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *UserHandler) GetBalanceStatistics(ctx context.Context, req *pb.GetBalanceStatisticsRequest) (*pb.GetBalanceStatisticsResponse, error) {
|
||||
balance, err := h.userService.GetBalance(ctx, int(req.UserId))
|
||||
if err != nil {
|
||||
return nil, errors.ToGRPCError(err, h.logger, "UserService.GetBalanceStatistics")
|
||||
}
|
||||
|
||||
stats, err := h.userService.GetStatistics(ctx, int(req.UserId))
|
||||
if err != nil {
|
||||
return nil, errors.ToGRPCError(err, h.logger, "UserService.GetBalanceStatistics")
|
||||
}
|
||||
|
||||
return &pb.GetBalanceStatisticsResponse{
|
||||
Balance: balance,
|
||||
TotalRequests: int32(stats.RequestsCount),
|
||||
TotalSpent: 0,
|
||||
}, nil
|
||||
}
|
||||
1578
internal/mocks/auth_service_mock.go
Normal file
1578
internal/mocks/auth_service_mock.go
Normal file
File diff suppressed because it is too large
Load Diff
1809
internal/mocks/invite_repository_mock.go
Normal file
1809
internal/mocks/invite_repository_mock.go
Normal file
File diff suppressed because it is too large
Load Diff
836
internal/mocks/invite_service_mock.go
Normal file
836
internal/mocks/invite_service_mock.go
Normal file
@@ -0,0 +1,836 @@
|
||||
// Code generated by http://github.com/gojuno/minimock (v3.4.7). DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
//go:generate minimock -i smart-search-back/internal/service.InviteService -o invite_service_mock.go -n InviteServiceMock -p mocks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"smart-search-back/internal/model"
|
||||
"sync"
|
||||
mm_atomic "sync/atomic"
|
||||
mm_time "time"
|
||||
|
||||
"github.com/gojuno/minimock/v3"
|
||||
)
|
||||
|
||||
// InviteServiceMock implements mm_service.InviteService
|
||||
type InviteServiceMock struct {
|
||||
t minimock.Tester
|
||||
finishOnce sync.Once
|
||||
|
||||
funcGenerate func(ctx context.Context, userID int, maxUses int, ttlDays int) (ip1 *model.InviteCode, err error)
|
||||
funcGenerateOrigin string
|
||||
inspectFuncGenerate func(ctx context.Context, userID int, maxUses int, ttlDays int)
|
||||
afterGenerateCounter uint64
|
||||
beforeGenerateCounter uint64
|
||||
GenerateMock mInviteServiceMockGenerate
|
||||
|
||||
funcGetInfo func(ctx context.Context, code int64) (ip1 *model.InviteCode, err error)
|
||||
funcGetInfoOrigin string
|
||||
inspectFuncGetInfo func(ctx context.Context, code int64)
|
||||
afterGetInfoCounter uint64
|
||||
beforeGetInfoCounter uint64
|
||||
GetInfoMock mInviteServiceMockGetInfo
|
||||
}
|
||||
|
||||
// NewInviteServiceMock returns a mock for mm_service.InviteService
|
||||
func NewInviteServiceMock(t minimock.Tester) *InviteServiceMock {
|
||||
m := &InviteServiceMock{t: t}
|
||||
|
||||
if controller, ok := t.(minimock.MockController); ok {
|
||||
controller.RegisterMocker(m)
|
||||
}
|
||||
|
||||
m.GenerateMock = mInviteServiceMockGenerate{mock: m}
|
||||
m.GenerateMock.callArgs = []*InviteServiceMockGenerateParams{}
|
||||
|
||||
m.GetInfoMock = mInviteServiceMockGetInfo{mock: m}
|
||||
m.GetInfoMock.callArgs = []*InviteServiceMockGetInfoParams{}
|
||||
|
||||
t.Cleanup(m.MinimockFinish)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
type mInviteServiceMockGenerate struct {
|
||||
optional bool
|
||||
mock *InviteServiceMock
|
||||
defaultExpectation *InviteServiceMockGenerateExpectation
|
||||
expectations []*InviteServiceMockGenerateExpectation
|
||||
|
||||
callArgs []*InviteServiceMockGenerateParams
|
||||
mutex sync.RWMutex
|
||||
|
||||
expectedInvocations uint64
|
||||
expectedInvocationsOrigin string
|
||||
}
|
||||
|
||||
// InviteServiceMockGenerateExpectation specifies expectation struct of the InviteService.Generate
|
||||
type InviteServiceMockGenerateExpectation struct {
|
||||
mock *InviteServiceMock
|
||||
params *InviteServiceMockGenerateParams
|
||||
paramPtrs *InviteServiceMockGenerateParamPtrs
|
||||
expectationOrigins InviteServiceMockGenerateExpectationOrigins
|
||||
results *InviteServiceMockGenerateResults
|
||||
returnOrigin string
|
||||
Counter uint64
|
||||
}
|
||||
|
||||
// InviteServiceMockGenerateParams contains parameters of the InviteService.Generate
|
||||
type InviteServiceMockGenerateParams struct {
|
||||
ctx context.Context
|
||||
userID int
|
||||
maxUses int
|
||||
ttlDays int
|
||||
}
|
||||
|
||||
// InviteServiceMockGenerateParamPtrs contains pointers to parameters of the InviteService.Generate
|
||||
type InviteServiceMockGenerateParamPtrs struct {
|
||||
ctx *context.Context
|
||||
userID *int
|
||||
maxUses *int
|
||||
ttlDays *int
|
||||
}
|
||||
|
||||
// InviteServiceMockGenerateResults contains results of the InviteService.Generate
|
||||
type InviteServiceMockGenerateResults struct {
|
||||
ip1 *model.InviteCode
|
||||
err error
|
||||
}
|
||||
|
||||
// InviteServiceMockGenerateOrigins contains origins of expectations of the InviteService.Generate
|
||||
type InviteServiceMockGenerateExpectationOrigins struct {
|
||||
origin string
|
||||
originCtx string
|
||||
originUserID string
|
||||
originMaxUses string
|
||||
originTtlDays string
|
||||
}
|
||||
|
||||
// Marks this method to be optional. The default behavior of any method with Return() is '1 or more', meaning
|
||||
// the test will fail minimock's automatic final call check if the mocked method was not called at least once.
|
||||
// Optional() makes method check to work in '0 or more' mode.
|
||||
// It is NOT RECOMMENDED to use this option unless you really need it, as default behaviour helps to
|
||||
// catch the problems when the expected method call is totally skipped during test run.
|
||||
func (mmGenerate *mInviteServiceMockGenerate) Optional() *mInviteServiceMockGenerate {
|
||||
mmGenerate.optional = true
|
||||
return mmGenerate
|
||||
}
|
||||
|
||||
// Expect sets up expected params for InviteService.Generate
|
||||
func (mmGenerate *mInviteServiceMockGenerate) Expect(ctx context.Context, userID int, maxUses int, ttlDays int) *mInviteServiceMockGenerate {
|
||||
if mmGenerate.mock.funcGenerate != nil {
|
||||
mmGenerate.mock.t.Fatalf("InviteServiceMock.Generate mock is already set by Set")
|
||||
}
|
||||
|
||||
if mmGenerate.defaultExpectation == nil {
|
||||
mmGenerate.defaultExpectation = &InviteServiceMockGenerateExpectation{}
|
||||
}
|
||||
|
||||
if mmGenerate.defaultExpectation.paramPtrs != nil {
|
||||
mmGenerate.mock.t.Fatalf("InviteServiceMock.Generate mock is already set by ExpectParams functions")
|
||||
}
|
||||
|
||||
mmGenerate.defaultExpectation.params = &InviteServiceMockGenerateParams{ctx, userID, maxUses, ttlDays}
|
||||
mmGenerate.defaultExpectation.expectationOrigins.origin = minimock.CallerInfo(1)
|
||||
for _, e := range mmGenerate.expectations {
|
||||
if minimock.Equal(e.params, mmGenerate.defaultExpectation.params) {
|
||||
mmGenerate.mock.t.Fatalf("Expectation set by When has same params: %#v", *mmGenerate.defaultExpectation.params)
|
||||
}
|
||||
}
|
||||
|
||||
return mmGenerate
|
||||
}
|
||||
|
||||
// ExpectCtxParam1 sets up expected param ctx for InviteService.Generate
|
||||
func (mmGenerate *mInviteServiceMockGenerate) ExpectCtxParam1(ctx context.Context) *mInviteServiceMockGenerate {
|
||||
if mmGenerate.mock.funcGenerate != nil {
|
||||
mmGenerate.mock.t.Fatalf("InviteServiceMock.Generate mock is already set by Set")
|
||||
}
|
||||
|
||||
if mmGenerate.defaultExpectation == nil {
|
||||
mmGenerate.defaultExpectation = &InviteServiceMockGenerateExpectation{}
|
||||
}
|
||||
|
||||
if mmGenerate.defaultExpectation.params != nil {
|
||||
mmGenerate.mock.t.Fatalf("InviteServiceMock.Generate mock is already set by Expect")
|
||||
}
|
||||
|
||||
if mmGenerate.defaultExpectation.paramPtrs == nil {
|
||||
mmGenerate.defaultExpectation.paramPtrs = &InviteServiceMockGenerateParamPtrs{}
|
||||
}
|
||||
mmGenerate.defaultExpectation.paramPtrs.ctx = &ctx
|
||||
mmGenerate.defaultExpectation.expectationOrigins.originCtx = minimock.CallerInfo(1)
|
||||
|
||||
return mmGenerate
|
||||
}
|
||||
|
||||
// ExpectUserIDParam2 sets up expected param userID for InviteService.Generate
|
||||
func (mmGenerate *mInviteServiceMockGenerate) ExpectUserIDParam2(userID int) *mInviteServiceMockGenerate {
|
||||
if mmGenerate.mock.funcGenerate != nil {
|
||||
mmGenerate.mock.t.Fatalf("InviteServiceMock.Generate mock is already set by Set")
|
||||
}
|
||||
|
||||
if mmGenerate.defaultExpectation == nil {
|
||||
mmGenerate.defaultExpectation = &InviteServiceMockGenerateExpectation{}
|
||||
}
|
||||
|
||||
if mmGenerate.defaultExpectation.params != nil {
|
||||
mmGenerate.mock.t.Fatalf("InviteServiceMock.Generate mock is already set by Expect")
|
||||
}
|
||||
|
||||
if mmGenerate.defaultExpectation.paramPtrs == nil {
|
||||
mmGenerate.defaultExpectation.paramPtrs = &InviteServiceMockGenerateParamPtrs{}
|
||||
}
|
||||
mmGenerate.defaultExpectation.paramPtrs.userID = &userID
|
||||
mmGenerate.defaultExpectation.expectationOrigins.originUserID = minimock.CallerInfo(1)
|
||||
|
||||
return mmGenerate
|
||||
}
|
||||
|
||||
// ExpectMaxUsesParam3 sets up expected param maxUses for InviteService.Generate
|
||||
func (mmGenerate *mInviteServiceMockGenerate) ExpectMaxUsesParam3(maxUses int) *mInviteServiceMockGenerate {
|
||||
if mmGenerate.mock.funcGenerate != nil {
|
||||
mmGenerate.mock.t.Fatalf("InviteServiceMock.Generate mock is already set by Set")
|
||||
}
|
||||
|
||||
if mmGenerate.defaultExpectation == nil {
|
||||
mmGenerate.defaultExpectation = &InviteServiceMockGenerateExpectation{}
|
||||
}
|
||||
|
||||
if mmGenerate.defaultExpectation.params != nil {
|
||||
mmGenerate.mock.t.Fatalf("InviteServiceMock.Generate mock is already set by Expect")
|
||||
}
|
||||
|
||||
if mmGenerate.defaultExpectation.paramPtrs == nil {
|
||||
mmGenerate.defaultExpectation.paramPtrs = &InviteServiceMockGenerateParamPtrs{}
|
||||
}
|
||||
mmGenerate.defaultExpectation.paramPtrs.maxUses = &maxUses
|
||||
mmGenerate.defaultExpectation.expectationOrigins.originMaxUses = minimock.CallerInfo(1)
|
||||
|
||||
return mmGenerate
|
||||
}
|
||||
|
||||
// ExpectTtlDaysParam4 sets up expected param ttlDays for InviteService.Generate
|
||||
func (mmGenerate *mInviteServiceMockGenerate) ExpectTtlDaysParam4(ttlDays int) *mInviteServiceMockGenerate {
|
||||
if mmGenerate.mock.funcGenerate != nil {
|
||||
mmGenerate.mock.t.Fatalf("InviteServiceMock.Generate mock is already set by Set")
|
||||
}
|
||||
|
||||
if mmGenerate.defaultExpectation == nil {
|
||||
mmGenerate.defaultExpectation = &InviteServiceMockGenerateExpectation{}
|
||||
}
|
||||
|
||||
if mmGenerate.defaultExpectation.params != nil {
|
||||
mmGenerate.mock.t.Fatalf("InviteServiceMock.Generate mock is already set by Expect")
|
||||
}
|
||||
|
||||
if mmGenerate.defaultExpectation.paramPtrs == nil {
|
||||
mmGenerate.defaultExpectation.paramPtrs = &InviteServiceMockGenerateParamPtrs{}
|
||||
}
|
||||
mmGenerate.defaultExpectation.paramPtrs.ttlDays = &ttlDays
|
||||
mmGenerate.defaultExpectation.expectationOrigins.originTtlDays = minimock.CallerInfo(1)
|
||||
|
||||
return mmGenerate
|
||||
}
|
||||
|
||||
// Inspect accepts an inspector function that has same arguments as the InviteService.Generate
|
||||
func (mmGenerate *mInviteServiceMockGenerate) Inspect(f func(ctx context.Context, userID int, maxUses int, ttlDays int)) *mInviteServiceMockGenerate {
|
||||
if mmGenerate.mock.inspectFuncGenerate != nil {
|
||||
mmGenerate.mock.t.Fatalf("Inspect function is already set for InviteServiceMock.Generate")
|
||||
}
|
||||
|
||||
mmGenerate.mock.inspectFuncGenerate = f
|
||||
|
||||
return mmGenerate
|
||||
}
|
||||
|
||||
// Return sets up results that will be returned by InviteService.Generate
|
||||
func (mmGenerate *mInviteServiceMockGenerate) Return(ip1 *model.InviteCode, err error) *InviteServiceMock {
|
||||
if mmGenerate.mock.funcGenerate != nil {
|
||||
mmGenerate.mock.t.Fatalf("InviteServiceMock.Generate mock is already set by Set")
|
||||
}
|
||||
|
||||
if mmGenerate.defaultExpectation == nil {
|
||||
mmGenerate.defaultExpectation = &InviteServiceMockGenerateExpectation{mock: mmGenerate.mock}
|
||||
}
|
||||
mmGenerate.defaultExpectation.results = &InviteServiceMockGenerateResults{ip1, err}
|
||||
mmGenerate.defaultExpectation.returnOrigin = minimock.CallerInfo(1)
|
||||
return mmGenerate.mock
|
||||
}
|
||||
|
||||
// Set uses given function f to mock the InviteService.Generate method
|
||||
func (mmGenerate *mInviteServiceMockGenerate) Set(f func(ctx context.Context, userID int, maxUses int, ttlDays int) (ip1 *model.InviteCode, err error)) *InviteServiceMock {
|
||||
if mmGenerate.defaultExpectation != nil {
|
||||
mmGenerate.mock.t.Fatalf("Default expectation is already set for the InviteService.Generate method")
|
||||
}
|
||||
|
||||
if len(mmGenerate.expectations) > 0 {
|
||||
mmGenerate.mock.t.Fatalf("Some expectations are already set for the InviteService.Generate method")
|
||||
}
|
||||
|
||||
mmGenerate.mock.funcGenerate = f
|
||||
mmGenerate.mock.funcGenerateOrigin = minimock.CallerInfo(1)
|
||||
return mmGenerate.mock
|
||||
}
|
||||
|
||||
// When sets expectation for the InviteService.Generate which will trigger the result defined by the following
|
||||
// Then helper
|
||||
func (mmGenerate *mInviteServiceMockGenerate) When(ctx context.Context, userID int, maxUses int, ttlDays int) *InviteServiceMockGenerateExpectation {
|
||||
if mmGenerate.mock.funcGenerate != nil {
|
||||
mmGenerate.mock.t.Fatalf("InviteServiceMock.Generate mock is already set by Set")
|
||||
}
|
||||
|
||||
expectation := &InviteServiceMockGenerateExpectation{
|
||||
mock: mmGenerate.mock,
|
||||
params: &InviteServiceMockGenerateParams{ctx, userID, maxUses, ttlDays},
|
||||
expectationOrigins: InviteServiceMockGenerateExpectationOrigins{origin: minimock.CallerInfo(1)},
|
||||
}
|
||||
mmGenerate.expectations = append(mmGenerate.expectations, expectation)
|
||||
return expectation
|
||||
}
|
||||
|
||||
// Then sets up InviteService.Generate return parameters for the expectation previously defined by the When method
|
||||
func (e *InviteServiceMockGenerateExpectation) Then(ip1 *model.InviteCode, err error) *InviteServiceMock {
|
||||
e.results = &InviteServiceMockGenerateResults{ip1, err}
|
||||
return e.mock
|
||||
}
|
||||
|
||||
// Times sets number of times InviteService.Generate should be invoked
|
||||
func (mmGenerate *mInviteServiceMockGenerate) Times(n uint64) *mInviteServiceMockGenerate {
|
||||
if n == 0 {
|
||||
mmGenerate.mock.t.Fatalf("Times of InviteServiceMock.Generate mock can not be zero")
|
||||
}
|
||||
mm_atomic.StoreUint64(&mmGenerate.expectedInvocations, n)
|
||||
mmGenerate.expectedInvocationsOrigin = minimock.CallerInfo(1)
|
||||
return mmGenerate
|
||||
}
|
||||
|
||||
func (mmGenerate *mInviteServiceMockGenerate) invocationsDone() bool {
|
||||
if len(mmGenerate.expectations) == 0 && mmGenerate.defaultExpectation == nil && mmGenerate.mock.funcGenerate == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
totalInvocations := mm_atomic.LoadUint64(&mmGenerate.mock.afterGenerateCounter)
|
||||
expectedInvocations := mm_atomic.LoadUint64(&mmGenerate.expectedInvocations)
|
||||
|
||||
return totalInvocations > 0 && (expectedInvocations == 0 || expectedInvocations == totalInvocations)
|
||||
}
|
||||
|
||||
// Generate implements mm_service.InviteService
|
||||
func (mmGenerate *InviteServiceMock) Generate(ctx context.Context, userID int, maxUses int, ttlDays int) (ip1 *model.InviteCode, err error) {
|
||||
mm_atomic.AddUint64(&mmGenerate.beforeGenerateCounter, 1)
|
||||
defer mm_atomic.AddUint64(&mmGenerate.afterGenerateCounter, 1)
|
||||
|
||||
mmGenerate.t.Helper()
|
||||
|
||||
if mmGenerate.inspectFuncGenerate != nil {
|
||||
mmGenerate.inspectFuncGenerate(ctx, userID, maxUses, ttlDays)
|
||||
}
|
||||
|
||||
mm_params := InviteServiceMockGenerateParams{ctx, userID, maxUses, ttlDays}
|
||||
|
||||
// Record call args
|
||||
mmGenerate.GenerateMock.mutex.Lock()
|
||||
mmGenerate.GenerateMock.callArgs = append(mmGenerate.GenerateMock.callArgs, &mm_params)
|
||||
mmGenerate.GenerateMock.mutex.Unlock()
|
||||
|
||||
for _, e := range mmGenerate.GenerateMock.expectations {
|
||||
if minimock.Equal(*e.params, mm_params) {
|
||||
mm_atomic.AddUint64(&e.Counter, 1)
|
||||
return e.results.ip1, e.results.err
|
||||
}
|
||||
}
|
||||
|
||||
if mmGenerate.GenerateMock.defaultExpectation != nil {
|
||||
mm_atomic.AddUint64(&mmGenerate.GenerateMock.defaultExpectation.Counter, 1)
|
||||
mm_want := mmGenerate.GenerateMock.defaultExpectation.params
|
||||
mm_want_ptrs := mmGenerate.GenerateMock.defaultExpectation.paramPtrs
|
||||
|
||||
mm_got := InviteServiceMockGenerateParams{ctx, userID, maxUses, ttlDays}
|
||||
|
||||
if mm_want_ptrs != nil {
|
||||
|
||||
if mm_want_ptrs.ctx != nil && !minimock.Equal(*mm_want_ptrs.ctx, mm_got.ctx) {
|
||||
mmGenerate.t.Errorf("InviteServiceMock.Generate got unexpected parameter ctx, expected at\n%s:\nwant: %#v\n got: %#v%s\n",
|
||||
mmGenerate.GenerateMock.defaultExpectation.expectationOrigins.originCtx, *mm_want_ptrs.ctx, mm_got.ctx, minimock.Diff(*mm_want_ptrs.ctx, mm_got.ctx))
|
||||
}
|
||||
|
||||
if mm_want_ptrs.userID != nil && !minimock.Equal(*mm_want_ptrs.userID, mm_got.userID) {
|
||||
mmGenerate.t.Errorf("InviteServiceMock.Generate got unexpected parameter userID, expected at\n%s:\nwant: %#v\n got: %#v%s\n",
|
||||
mmGenerate.GenerateMock.defaultExpectation.expectationOrigins.originUserID, *mm_want_ptrs.userID, mm_got.userID, minimock.Diff(*mm_want_ptrs.userID, mm_got.userID))
|
||||
}
|
||||
|
||||
if mm_want_ptrs.maxUses != nil && !minimock.Equal(*mm_want_ptrs.maxUses, mm_got.maxUses) {
|
||||
mmGenerate.t.Errorf("InviteServiceMock.Generate got unexpected parameter maxUses, expected at\n%s:\nwant: %#v\n got: %#v%s\n",
|
||||
mmGenerate.GenerateMock.defaultExpectation.expectationOrigins.originMaxUses, *mm_want_ptrs.maxUses, mm_got.maxUses, minimock.Diff(*mm_want_ptrs.maxUses, mm_got.maxUses))
|
||||
}
|
||||
|
||||
if mm_want_ptrs.ttlDays != nil && !minimock.Equal(*mm_want_ptrs.ttlDays, mm_got.ttlDays) {
|
||||
mmGenerate.t.Errorf("InviteServiceMock.Generate got unexpected parameter ttlDays, expected at\n%s:\nwant: %#v\n got: %#v%s\n",
|
||||
mmGenerate.GenerateMock.defaultExpectation.expectationOrigins.originTtlDays, *mm_want_ptrs.ttlDays, mm_got.ttlDays, minimock.Diff(*mm_want_ptrs.ttlDays, mm_got.ttlDays))
|
||||
}
|
||||
|
||||
} else if mm_want != nil && !minimock.Equal(*mm_want, mm_got) {
|
||||
mmGenerate.t.Errorf("InviteServiceMock.Generate got unexpected parameters, expected at\n%s:\nwant: %#v\n got: %#v%s\n",
|
||||
mmGenerate.GenerateMock.defaultExpectation.expectationOrigins.origin, *mm_want, mm_got, minimock.Diff(*mm_want, mm_got))
|
||||
}
|
||||
|
||||
mm_results := mmGenerate.GenerateMock.defaultExpectation.results
|
||||
if mm_results == nil {
|
||||
mmGenerate.t.Fatal("No results are set for the InviteServiceMock.Generate")
|
||||
}
|
||||
return (*mm_results).ip1, (*mm_results).err
|
||||
}
|
||||
if mmGenerate.funcGenerate != nil {
|
||||
return mmGenerate.funcGenerate(ctx, userID, maxUses, ttlDays)
|
||||
}
|
||||
mmGenerate.t.Fatalf("Unexpected call to InviteServiceMock.Generate. %v %v %v %v", ctx, userID, maxUses, ttlDays)
|
||||
return
|
||||
}
|
||||
|
||||
// GenerateAfterCounter returns a count of finished InviteServiceMock.Generate invocations
|
||||
func (mmGenerate *InviteServiceMock) GenerateAfterCounter() uint64 {
|
||||
return mm_atomic.LoadUint64(&mmGenerate.afterGenerateCounter)
|
||||
}
|
||||
|
||||
// GenerateBeforeCounter returns a count of InviteServiceMock.Generate invocations
|
||||
func (mmGenerate *InviteServiceMock) GenerateBeforeCounter() uint64 {
|
||||
return mm_atomic.LoadUint64(&mmGenerate.beforeGenerateCounter)
|
||||
}
|
||||
|
||||
// Calls returns a list of arguments used in each call to InviteServiceMock.Generate.
|
||||
// The list is in the same order as the calls were made (i.e. recent calls have a higher index)
|
||||
func (mmGenerate *mInviteServiceMockGenerate) Calls() []*InviteServiceMockGenerateParams {
|
||||
mmGenerate.mutex.RLock()
|
||||
|
||||
argCopy := make([]*InviteServiceMockGenerateParams, len(mmGenerate.callArgs))
|
||||
copy(argCopy, mmGenerate.callArgs)
|
||||
|
||||
mmGenerate.mutex.RUnlock()
|
||||
|
||||
return argCopy
|
||||
}
|
||||
|
||||
// MinimockGenerateDone returns true if the count of the Generate invocations corresponds
|
||||
// the number of defined expectations
|
||||
func (m *InviteServiceMock) MinimockGenerateDone() bool {
|
||||
if m.GenerateMock.optional {
|
||||
// Optional methods provide '0 or more' call count restriction.
|
||||
return true
|
||||
}
|
||||
|
||||
for _, e := range m.GenerateMock.expectations {
|
||||
if mm_atomic.LoadUint64(&e.Counter) < 1 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return m.GenerateMock.invocationsDone()
|
||||
}
|
||||
|
||||
// MinimockGenerateInspect logs each unmet expectation
|
||||
func (m *InviteServiceMock) MinimockGenerateInspect() {
|
||||
for _, e := range m.GenerateMock.expectations {
|
||||
if mm_atomic.LoadUint64(&e.Counter) < 1 {
|
||||
m.t.Errorf("Expected call to InviteServiceMock.Generate at\n%s with params: %#v", e.expectationOrigins.origin, *e.params)
|
||||
}
|
||||
}
|
||||
|
||||
afterGenerateCounter := mm_atomic.LoadUint64(&m.afterGenerateCounter)
|
||||
// if default expectation was set then invocations count should be greater than zero
|
||||
if m.GenerateMock.defaultExpectation != nil && afterGenerateCounter < 1 {
|
||||
if m.GenerateMock.defaultExpectation.params == nil {
|
||||
m.t.Errorf("Expected call to InviteServiceMock.Generate at\n%s", m.GenerateMock.defaultExpectation.returnOrigin)
|
||||
} else {
|
||||
m.t.Errorf("Expected call to InviteServiceMock.Generate at\n%s with params: %#v", m.GenerateMock.defaultExpectation.expectationOrigins.origin, *m.GenerateMock.defaultExpectation.params)
|
||||
}
|
||||
}
|
||||
// if func was set then invocations count should be greater than zero
|
||||
if m.funcGenerate != nil && afterGenerateCounter < 1 {
|
||||
m.t.Errorf("Expected call to InviteServiceMock.Generate at\n%s", m.funcGenerateOrigin)
|
||||
}
|
||||
|
||||
if !m.GenerateMock.invocationsDone() && afterGenerateCounter > 0 {
|
||||
m.t.Errorf("Expected %d calls to InviteServiceMock.Generate at\n%s but found %d calls",
|
||||
mm_atomic.LoadUint64(&m.GenerateMock.expectedInvocations), m.GenerateMock.expectedInvocationsOrigin, afterGenerateCounter)
|
||||
}
|
||||
}
|
||||
|
||||
type mInviteServiceMockGetInfo struct {
|
||||
optional bool
|
||||
mock *InviteServiceMock
|
||||
defaultExpectation *InviteServiceMockGetInfoExpectation
|
||||
expectations []*InviteServiceMockGetInfoExpectation
|
||||
|
||||
callArgs []*InviteServiceMockGetInfoParams
|
||||
mutex sync.RWMutex
|
||||
|
||||
expectedInvocations uint64
|
||||
expectedInvocationsOrigin string
|
||||
}
|
||||
|
||||
// InviteServiceMockGetInfoExpectation specifies expectation struct of the InviteService.GetInfo
|
||||
type InviteServiceMockGetInfoExpectation struct {
|
||||
mock *InviteServiceMock
|
||||
params *InviteServiceMockGetInfoParams
|
||||
paramPtrs *InviteServiceMockGetInfoParamPtrs
|
||||
expectationOrigins InviteServiceMockGetInfoExpectationOrigins
|
||||
results *InviteServiceMockGetInfoResults
|
||||
returnOrigin string
|
||||
Counter uint64
|
||||
}
|
||||
|
||||
// InviteServiceMockGetInfoParams contains parameters of the InviteService.GetInfo
|
||||
type InviteServiceMockGetInfoParams struct {
|
||||
ctx context.Context
|
||||
code int64
|
||||
}
|
||||
|
||||
// InviteServiceMockGetInfoParamPtrs contains pointers to parameters of the InviteService.GetInfo
|
||||
type InviteServiceMockGetInfoParamPtrs struct {
|
||||
ctx *context.Context
|
||||
code *int64
|
||||
}
|
||||
|
||||
// InviteServiceMockGetInfoResults contains results of the InviteService.GetInfo
|
||||
type InviteServiceMockGetInfoResults struct {
|
||||
ip1 *model.InviteCode
|
||||
err error
|
||||
}
|
||||
|
||||
// InviteServiceMockGetInfoOrigins contains origins of expectations of the InviteService.GetInfo
|
||||
type InviteServiceMockGetInfoExpectationOrigins struct {
|
||||
origin string
|
||||
originCtx string
|
||||
originCode string
|
||||
}
|
||||
|
||||
// Marks this method to be optional. The default behavior of any method with Return() is '1 or more', meaning
|
||||
// the test will fail minimock's automatic final call check if the mocked method was not called at least once.
|
||||
// Optional() makes method check to work in '0 or more' mode.
|
||||
// It is NOT RECOMMENDED to use this option unless you really need it, as default behaviour helps to
|
||||
// catch the problems when the expected method call is totally skipped during test run.
|
||||
func (mmGetInfo *mInviteServiceMockGetInfo) Optional() *mInviteServiceMockGetInfo {
|
||||
mmGetInfo.optional = true
|
||||
return mmGetInfo
|
||||
}
|
||||
|
||||
// Expect sets up expected params for InviteService.GetInfo
|
||||
func (mmGetInfo *mInviteServiceMockGetInfo) Expect(ctx context.Context, code int64) *mInviteServiceMockGetInfo {
|
||||
if mmGetInfo.mock.funcGetInfo != nil {
|
||||
mmGetInfo.mock.t.Fatalf("InviteServiceMock.GetInfo mock is already set by Set")
|
||||
}
|
||||
|
||||
if mmGetInfo.defaultExpectation == nil {
|
||||
mmGetInfo.defaultExpectation = &InviteServiceMockGetInfoExpectation{}
|
||||
}
|
||||
|
||||
if mmGetInfo.defaultExpectation.paramPtrs != nil {
|
||||
mmGetInfo.mock.t.Fatalf("InviteServiceMock.GetInfo mock is already set by ExpectParams functions")
|
||||
}
|
||||
|
||||
mmGetInfo.defaultExpectation.params = &InviteServiceMockGetInfoParams{ctx, code}
|
||||
mmGetInfo.defaultExpectation.expectationOrigins.origin = minimock.CallerInfo(1)
|
||||
for _, e := range mmGetInfo.expectations {
|
||||
if minimock.Equal(e.params, mmGetInfo.defaultExpectation.params) {
|
||||
mmGetInfo.mock.t.Fatalf("Expectation set by When has same params: %#v", *mmGetInfo.defaultExpectation.params)
|
||||
}
|
||||
}
|
||||
|
||||
return mmGetInfo
|
||||
}
|
||||
|
||||
// ExpectCtxParam1 sets up expected param ctx for InviteService.GetInfo
|
||||
func (mmGetInfo *mInviteServiceMockGetInfo) ExpectCtxParam1(ctx context.Context) *mInviteServiceMockGetInfo {
|
||||
if mmGetInfo.mock.funcGetInfo != nil {
|
||||
mmGetInfo.mock.t.Fatalf("InviteServiceMock.GetInfo mock is already set by Set")
|
||||
}
|
||||
|
||||
if mmGetInfo.defaultExpectation == nil {
|
||||
mmGetInfo.defaultExpectation = &InviteServiceMockGetInfoExpectation{}
|
||||
}
|
||||
|
||||
if mmGetInfo.defaultExpectation.params != nil {
|
||||
mmGetInfo.mock.t.Fatalf("InviteServiceMock.GetInfo mock is already set by Expect")
|
||||
}
|
||||
|
||||
if mmGetInfo.defaultExpectation.paramPtrs == nil {
|
||||
mmGetInfo.defaultExpectation.paramPtrs = &InviteServiceMockGetInfoParamPtrs{}
|
||||
}
|
||||
mmGetInfo.defaultExpectation.paramPtrs.ctx = &ctx
|
||||
mmGetInfo.defaultExpectation.expectationOrigins.originCtx = minimock.CallerInfo(1)
|
||||
|
||||
return mmGetInfo
|
||||
}
|
||||
|
||||
// ExpectCodeParam2 sets up expected param code for InviteService.GetInfo
|
||||
func (mmGetInfo *mInviteServiceMockGetInfo) ExpectCodeParam2(code int64) *mInviteServiceMockGetInfo {
|
||||
if mmGetInfo.mock.funcGetInfo != nil {
|
||||
mmGetInfo.mock.t.Fatalf("InviteServiceMock.GetInfo mock is already set by Set")
|
||||
}
|
||||
|
||||
if mmGetInfo.defaultExpectation == nil {
|
||||
mmGetInfo.defaultExpectation = &InviteServiceMockGetInfoExpectation{}
|
||||
}
|
||||
|
||||
if mmGetInfo.defaultExpectation.params != nil {
|
||||
mmGetInfo.mock.t.Fatalf("InviteServiceMock.GetInfo mock is already set by Expect")
|
||||
}
|
||||
|
||||
if mmGetInfo.defaultExpectation.paramPtrs == nil {
|
||||
mmGetInfo.defaultExpectation.paramPtrs = &InviteServiceMockGetInfoParamPtrs{}
|
||||
}
|
||||
mmGetInfo.defaultExpectation.paramPtrs.code = &code
|
||||
mmGetInfo.defaultExpectation.expectationOrigins.originCode = minimock.CallerInfo(1)
|
||||
|
||||
return mmGetInfo
|
||||
}
|
||||
|
||||
// Inspect accepts an inspector function that has same arguments as the InviteService.GetInfo
|
||||
func (mmGetInfo *mInviteServiceMockGetInfo) Inspect(f func(ctx context.Context, code int64)) *mInviteServiceMockGetInfo {
|
||||
if mmGetInfo.mock.inspectFuncGetInfo != nil {
|
||||
mmGetInfo.mock.t.Fatalf("Inspect function is already set for InviteServiceMock.GetInfo")
|
||||
}
|
||||
|
||||
mmGetInfo.mock.inspectFuncGetInfo = f
|
||||
|
||||
return mmGetInfo
|
||||
}
|
||||
|
||||
// Return sets up results that will be returned by InviteService.GetInfo
|
||||
func (mmGetInfo *mInviteServiceMockGetInfo) Return(ip1 *model.InviteCode, err error) *InviteServiceMock {
|
||||
if mmGetInfo.mock.funcGetInfo != nil {
|
||||
mmGetInfo.mock.t.Fatalf("InviteServiceMock.GetInfo mock is already set by Set")
|
||||
}
|
||||
|
||||
if mmGetInfo.defaultExpectation == nil {
|
||||
mmGetInfo.defaultExpectation = &InviteServiceMockGetInfoExpectation{mock: mmGetInfo.mock}
|
||||
}
|
||||
mmGetInfo.defaultExpectation.results = &InviteServiceMockGetInfoResults{ip1, err}
|
||||
mmGetInfo.defaultExpectation.returnOrigin = minimock.CallerInfo(1)
|
||||
return mmGetInfo.mock
|
||||
}
|
||||
|
||||
// Set uses given function f to mock the InviteService.GetInfo method
|
||||
func (mmGetInfo *mInviteServiceMockGetInfo) Set(f func(ctx context.Context, code int64) (ip1 *model.InviteCode, err error)) *InviteServiceMock {
|
||||
if mmGetInfo.defaultExpectation != nil {
|
||||
mmGetInfo.mock.t.Fatalf("Default expectation is already set for the InviteService.GetInfo method")
|
||||
}
|
||||
|
||||
if len(mmGetInfo.expectations) > 0 {
|
||||
mmGetInfo.mock.t.Fatalf("Some expectations are already set for the InviteService.GetInfo method")
|
||||
}
|
||||
|
||||
mmGetInfo.mock.funcGetInfo = f
|
||||
mmGetInfo.mock.funcGetInfoOrigin = minimock.CallerInfo(1)
|
||||
return mmGetInfo.mock
|
||||
}
|
||||
|
||||
// When sets expectation for the InviteService.GetInfo which will trigger the result defined by the following
|
||||
// Then helper
|
||||
func (mmGetInfo *mInviteServiceMockGetInfo) When(ctx context.Context, code int64) *InviteServiceMockGetInfoExpectation {
|
||||
if mmGetInfo.mock.funcGetInfo != nil {
|
||||
mmGetInfo.mock.t.Fatalf("InviteServiceMock.GetInfo mock is already set by Set")
|
||||
}
|
||||
|
||||
expectation := &InviteServiceMockGetInfoExpectation{
|
||||
mock: mmGetInfo.mock,
|
||||
params: &InviteServiceMockGetInfoParams{ctx, code},
|
||||
expectationOrigins: InviteServiceMockGetInfoExpectationOrigins{origin: minimock.CallerInfo(1)},
|
||||
}
|
||||
mmGetInfo.expectations = append(mmGetInfo.expectations, expectation)
|
||||
return expectation
|
||||
}
|
||||
|
||||
// Then sets up InviteService.GetInfo return parameters for the expectation previously defined by the When method
|
||||
func (e *InviteServiceMockGetInfoExpectation) Then(ip1 *model.InviteCode, err error) *InviteServiceMock {
|
||||
e.results = &InviteServiceMockGetInfoResults{ip1, err}
|
||||
return e.mock
|
||||
}
|
||||
|
||||
// Times sets number of times InviteService.GetInfo should be invoked
|
||||
func (mmGetInfo *mInviteServiceMockGetInfo) Times(n uint64) *mInviteServiceMockGetInfo {
|
||||
if n == 0 {
|
||||
mmGetInfo.mock.t.Fatalf("Times of InviteServiceMock.GetInfo mock can not be zero")
|
||||
}
|
||||
mm_atomic.StoreUint64(&mmGetInfo.expectedInvocations, n)
|
||||
mmGetInfo.expectedInvocationsOrigin = minimock.CallerInfo(1)
|
||||
return mmGetInfo
|
||||
}
|
||||
|
||||
func (mmGetInfo *mInviteServiceMockGetInfo) invocationsDone() bool {
|
||||
if len(mmGetInfo.expectations) == 0 && mmGetInfo.defaultExpectation == nil && mmGetInfo.mock.funcGetInfo == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
totalInvocations := mm_atomic.LoadUint64(&mmGetInfo.mock.afterGetInfoCounter)
|
||||
expectedInvocations := mm_atomic.LoadUint64(&mmGetInfo.expectedInvocations)
|
||||
|
||||
return totalInvocations > 0 && (expectedInvocations == 0 || expectedInvocations == totalInvocations)
|
||||
}
|
||||
|
||||
// GetInfo implements mm_service.InviteService
|
||||
func (mmGetInfo *InviteServiceMock) GetInfo(ctx context.Context, code int64) (ip1 *model.InviteCode, err error) {
|
||||
mm_atomic.AddUint64(&mmGetInfo.beforeGetInfoCounter, 1)
|
||||
defer mm_atomic.AddUint64(&mmGetInfo.afterGetInfoCounter, 1)
|
||||
|
||||
mmGetInfo.t.Helper()
|
||||
|
||||
if mmGetInfo.inspectFuncGetInfo != nil {
|
||||
mmGetInfo.inspectFuncGetInfo(ctx, code)
|
||||
}
|
||||
|
||||
mm_params := InviteServiceMockGetInfoParams{ctx, code}
|
||||
|
||||
// Record call args
|
||||
mmGetInfo.GetInfoMock.mutex.Lock()
|
||||
mmGetInfo.GetInfoMock.callArgs = append(mmGetInfo.GetInfoMock.callArgs, &mm_params)
|
||||
mmGetInfo.GetInfoMock.mutex.Unlock()
|
||||
|
||||
for _, e := range mmGetInfo.GetInfoMock.expectations {
|
||||
if minimock.Equal(*e.params, mm_params) {
|
||||
mm_atomic.AddUint64(&e.Counter, 1)
|
||||
return e.results.ip1, e.results.err
|
||||
}
|
||||
}
|
||||
|
||||
if mmGetInfo.GetInfoMock.defaultExpectation != nil {
|
||||
mm_atomic.AddUint64(&mmGetInfo.GetInfoMock.defaultExpectation.Counter, 1)
|
||||
mm_want := mmGetInfo.GetInfoMock.defaultExpectation.params
|
||||
mm_want_ptrs := mmGetInfo.GetInfoMock.defaultExpectation.paramPtrs
|
||||
|
||||
mm_got := InviteServiceMockGetInfoParams{ctx, code}
|
||||
|
||||
if mm_want_ptrs != nil {
|
||||
|
||||
if mm_want_ptrs.ctx != nil && !minimock.Equal(*mm_want_ptrs.ctx, mm_got.ctx) {
|
||||
mmGetInfo.t.Errorf("InviteServiceMock.GetInfo got unexpected parameter ctx, expected at\n%s:\nwant: %#v\n got: %#v%s\n",
|
||||
mmGetInfo.GetInfoMock.defaultExpectation.expectationOrigins.originCtx, *mm_want_ptrs.ctx, mm_got.ctx, minimock.Diff(*mm_want_ptrs.ctx, mm_got.ctx))
|
||||
}
|
||||
|
||||
if mm_want_ptrs.code != nil && !minimock.Equal(*mm_want_ptrs.code, mm_got.code) {
|
||||
mmGetInfo.t.Errorf("InviteServiceMock.GetInfo got unexpected parameter code, expected at\n%s:\nwant: %#v\n got: %#v%s\n",
|
||||
mmGetInfo.GetInfoMock.defaultExpectation.expectationOrigins.originCode, *mm_want_ptrs.code, mm_got.code, minimock.Diff(*mm_want_ptrs.code, mm_got.code))
|
||||
}
|
||||
|
||||
} else if mm_want != nil && !minimock.Equal(*mm_want, mm_got) {
|
||||
mmGetInfo.t.Errorf("InviteServiceMock.GetInfo got unexpected parameters, expected at\n%s:\nwant: %#v\n got: %#v%s\n",
|
||||
mmGetInfo.GetInfoMock.defaultExpectation.expectationOrigins.origin, *mm_want, mm_got, minimock.Diff(*mm_want, mm_got))
|
||||
}
|
||||
|
||||
mm_results := mmGetInfo.GetInfoMock.defaultExpectation.results
|
||||
if mm_results == nil {
|
||||
mmGetInfo.t.Fatal("No results are set for the InviteServiceMock.GetInfo")
|
||||
}
|
||||
return (*mm_results).ip1, (*mm_results).err
|
||||
}
|
||||
if mmGetInfo.funcGetInfo != nil {
|
||||
return mmGetInfo.funcGetInfo(ctx, code)
|
||||
}
|
||||
mmGetInfo.t.Fatalf("Unexpected call to InviteServiceMock.GetInfo. %v %v", ctx, code)
|
||||
return
|
||||
}
|
||||
|
||||
// GetInfoAfterCounter returns a count of finished InviteServiceMock.GetInfo invocations
|
||||
func (mmGetInfo *InviteServiceMock) GetInfoAfterCounter() uint64 {
|
||||
return mm_atomic.LoadUint64(&mmGetInfo.afterGetInfoCounter)
|
||||
}
|
||||
|
||||
// GetInfoBeforeCounter returns a count of InviteServiceMock.GetInfo invocations
|
||||
func (mmGetInfo *InviteServiceMock) GetInfoBeforeCounter() uint64 {
|
||||
return mm_atomic.LoadUint64(&mmGetInfo.beforeGetInfoCounter)
|
||||
}
|
||||
|
||||
// Calls returns a list of arguments used in each call to InviteServiceMock.GetInfo.
|
||||
// The list is in the same order as the calls were made (i.e. recent calls have a higher index)
|
||||
func (mmGetInfo *mInviteServiceMockGetInfo) Calls() []*InviteServiceMockGetInfoParams {
|
||||
mmGetInfo.mutex.RLock()
|
||||
|
||||
argCopy := make([]*InviteServiceMockGetInfoParams, len(mmGetInfo.callArgs))
|
||||
copy(argCopy, mmGetInfo.callArgs)
|
||||
|
||||
mmGetInfo.mutex.RUnlock()
|
||||
|
||||
return argCopy
|
||||
}
|
||||
|
||||
// MinimockGetInfoDone returns true if the count of the GetInfo invocations corresponds
|
||||
// the number of defined expectations
|
||||
func (m *InviteServiceMock) MinimockGetInfoDone() bool {
|
||||
if m.GetInfoMock.optional {
|
||||
// Optional methods provide '0 or more' call count restriction.
|
||||
return true
|
||||
}
|
||||
|
||||
for _, e := range m.GetInfoMock.expectations {
|
||||
if mm_atomic.LoadUint64(&e.Counter) < 1 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return m.GetInfoMock.invocationsDone()
|
||||
}
|
||||
|
||||
// MinimockGetInfoInspect logs each unmet expectation
|
||||
func (m *InviteServiceMock) MinimockGetInfoInspect() {
|
||||
for _, e := range m.GetInfoMock.expectations {
|
||||
if mm_atomic.LoadUint64(&e.Counter) < 1 {
|
||||
m.t.Errorf("Expected call to InviteServiceMock.GetInfo at\n%s with params: %#v", e.expectationOrigins.origin, *e.params)
|
||||
}
|
||||
}
|
||||
|
||||
afterGetInfoCounter := mm_atomic.LoadUint64(&m.afterGetInfoCounter)
|
||||
// if default expectation was set then invocations count should be greater than zero
|
||||
if m.GetInfoMock.defaultExpectation != nil && afterGetInfoCounter < 1 {
|
||||
if m.GetInfoMock.defaultExpectation.params == nil {
|
||||
m.t.Errorf("Expected call to InviteServiceMock.GetInfo at\n%s", m.GetInfoMock.defaultExpectation.returnOrigin)
|
||||
} else {
|
||||
m.t.Errorf("Expected call to InviteServiceMock.GetInfo at\n%s with params: %#v", m.GetInfoMock.defaultExpectation.expectationOrigins.origin, *m.GetInfoMock.defaultExpectation.params)
|
||||
}
|
||||
}
|
||||
// if func was set then invocations count should be greater than zero
|
||||
if m.funcGetInfo != nil && afterGetInfoCounter < 1 {
|
||||
m.t.Errorf("Expected call to InviteServiceMock.GetInfo at\n%s", m.funcGetInfoOrigin)
|
||||
}
|
||||
|
||||
if !m.GetInfoMock.invocationsDone() && afterGetInfoCounter > 0 {
|
||||
m.t.Errorf("Expected %d calls to InviteServiceMock.GetInfo at\n%s but found %d calls",
|
||||
mm_atomic.LoadUint64(&m.GetInfoMock.expectedInvocations), m.GetInfoMock.expectedInvocationsOrigin, afterGetInfoCounter)
|
||||
}
|
||||
}
|
||||
|
||||
// MinimockFinish checks that all mocked methods have been called the expected number of times
|
||||
func (m *InviteServiceMock) MinimockFinish() {
|
||||
m.finishOnce.Do(func() {
|
||||
if !m.minimockDone() {
|
||||
m.MinimockGenerateInspect()
|
||||
|
||||
m.MinimockGetInfoInspect()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// MinimockWait waits for all mocked methods to be called the expected number of times
|
||||
func (m *InviteServiceMock) MinimockWait(timeout mm_time.Duration) {
|
||||
timeoutCh := mm_time.After(timeout)
|
||||
for {
|
||||
if m.minimockDone() {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-timeoutCh:
|
||||
m.MinimockFinish()
|
||||
return
|
||||
case <-mm_time.After(10 * mm_time.Millisecond):
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *InviteServiceMock) minimockDone() bool {
|
||||
done := true
|
||||
return done &&
|
||||
m.MinimockGenerateDone() &&
|
||||
m.MinimockGetInfoDone()
|
||||
}
|
||||
2647
internal/mocks/request_repository_mock.go
Normal file
2647
internal/mocks/request_repository_mock.go
Normal file
File diff suppressed because it is too large
Load Diff
1581
internal/mocks/request_service_mock.go
Normal file
1581
internal/mocks/request_service_mock.go
Normal file
File diff suppressed because it is too large
Load Diff
1839
internal/mocks/session_repository_mock.go
Normal file
1839
internal/mocks/session_repository_mock.go
Normal file
File diff suppressed because it is too large
Load Diff
1160
internal/mocks/supplier_repository_mock.go
Normal file
1160
internal/mocks/supplier_repository_mock.go
Normal file
File diff suppressed because it is too large
Load Diff
418
internal/mocks/supplier_service_mock.go
Normal file
418
internal/mocks/supplier_service_mock.go
Normal file
@@ -0,0 +1,418 @@
|
||||
// Code generated by http://github.com/gojuno/minimock (v3.4.7). DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
//go:generate minimock -i smart-search-back/internal/service.SupplierService -o supplier_service_mock.go -n SupplierServiceMock -p mocks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
mm_atomic "sync/atomic"
|
||||
mm_time "time"
|
||||
|
||||
"github.com/gojuno/minimock/v3"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// SupplierServiceMock implements mm_service.SupplierService
|
||||
type SupplierServiceMock struct {
|
||||
t minimock.Tester
|
||||
finishOnce sync.Once
|
||||
|
||||
funcExportExcel func(ctx context.Context, requestID uuid.UUID) (ba1 []byte, err error)
|
||||
funcExportExcelOrigin string
|
||||
inspectFuncExportExcel func(ctx context.Context, requestID uuid.UUID)
|
||||
afterExportExcelCounter uint64
|
||||
beforeExportExcelCounter uint64
|
||||
ExportExcelMock mSupplierServiceMockExportExcel
|
||||
}
|
||||
|
||||
// NewSupplierServiceMock returns a mock for mm_service.SupplierService
|
||||
func NewSupplierServiceMock(t minimock.Tester) *SupplierServiceMock {
|
||||
m := &SupplierServiceMock{t: t}
|
||||
|
||||
if controller, ok := t.(minimock.MockController); ok {
|
||||
controller.RegisterMocker(m)
|
||||
}
|
||||
|
||||
m.ExportExcelMock = mSupplierServiceMockExportExcel{mock: m}
|
||||
m.ExportExcelMock.callArgs = []*SupplierServiceMockExportExcelParams{}
|
||||
|
||||
t.Cleanup(m.MinimockFinish)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
type mSupplierServiceMockExportExcel struct {
|
||||
optional bool
|
||||
mock *SupplierServiceMock
|
||||
defaultExpectation *SupplierServiceMockExportExcelExpectation
|
||||
expectations []*SupplierServiceMockExportExcelExpectation
|
||||
|
||||
callArgs []*SupplierServiceMockExportExcelParams
|
||||
mutex sync.RWMutex
|
||||
|
||||
expectedInvocations uint64
|
||||
expectedInvocationsOrigin string
|
||||
}
|
||||
|
||||
// SupplierServiceMockExportExcelExpectation specifies expectation struct of the SupplierService.ExportExcel
|
||||
type SupplierServiceMockExportExcelExpectation struct {
|
||||
mock *SupplierServiceMock
|
||||
params *SupplierServiceMockExportExcelParams
|
||||
paramPtrs *SupplierServiceMockExportExcelParamPtrs
|
||||
expectationOrigins SupplierServiceMockExportExcelExpectationOrigins
|
||||
results *SupplierServiceMockExportExcelResults
|
||||
returnOrigin string
|
||||
Counter uint64
|
||||
}
|
||||
|
||||
// SupplierServiceMockExportExcelParams contains parameters of the SupplierService.ExportExcel
|
||||
type SupplierServiceMockExportExcelParams struct {
|
||||
ctx context.Context
|
||||
requestID uuid.UUID
|
||||
}
|
||||
|
||||
// SupplierServiceMockExportExcelParamPtrs contains pointers to parameters of the SupplierService.ExportExcel
|
||||
type SupplierServiceMockExportExcelParamPtrs struct {
|
||||
ctx *context.Context
|
||||
requestID *uuid.UUID
|
||||
}
|
||||
|
||||
// SupplierServiceMockExportExcelResults contains results of the SupplierService.ExportExcel
|
||||
type SupplierServiceMockExportExcelResults struct {
|
||||
ba1 []byte
|
||||
err error
|
||||
}
|
||||
|
||||
// SupplierServiceMockExportExcelOrigins contains origins of expectations of the SupplierService.ExportExcel
|
||||
type SupplierServiceMockExportExcelExpectationOrigins struct {
|
||||
origin string
|
||||
originCtx string
|
||||
originRequestID string
|
||||
}
|
||||
|
||||
// Marks this method to be optional. The default behavior of any method with Return() is '1 or more', meaning
|
||||
// the test will fail minimock's automatic final call check if the mocked method was not called at least once.
|
||||
// Optional() makes method check to work in '0 or more' mode.
|
||||
// It is NOT RECOMMENDED to use this option unless you really need it, as default behaviour helps to
|
||||
// catch the problems when the expected method call is totally skipped during test run.
|
||||
func (mmExportExcel *mSupplierServiceMockExportExcel) Optional() *mSupplierServiceMockExportExcel {
|
||||
mmExportExcel.optional = true
|
||||
return mmExportExcel
|
||||
}
|
||||
|
||||
// Expect sets up expected params for SupplierService.ExportExcel
|
||||
func (mmExportExcel *mSupplierServiceMockExportExcel) Expect(ctx context.Context, requestID uuid.UUID) *mSupplierServiceMockExportExcel {
|
||||
if mmExportExcel.mock.funcExportExcel != nil {
|
||||
mmExportExcel.mock.t.Fatalf("SupplierServiceMock.ExportExcel mock is already set by Set")
|
||||
}
|
||||
|
||||
if mmExportExcel.defaultExpectation == nil {
|
||||
mmExportExcel.defaultExpectation = &SupplierServiceMockExportExcelExpectation{}
|
||||
}
|
||||
|
||||
if mmExportExcel.defaultExpectation.paramPtrs != nil {
|
||||
mmExportExcel.mock.t.Fatalf("SupplierServiceMock.ExportExcel mock is already set by ExpectParams functions")
|
||||
}
|
||||
|
||||
mmExportExcel.defaultExpectation.params = &SupplierServiceMockExportExcelParams{ctx, requestID}
|
||||
mmExportExcel.defaultExpectation.expectationOrigins.origin = minimock.CallerInfo(1)
|
||||
for _, e := range mmExportExcel.expectations {
|
||||
if minimock.Equal(e.params, mmExportExcel.defaultExpectation.params) {
|
||||
mmExportExcel.mock.t.Fatalf("Expectation set by When has same params: %#v", *mmExportExcel.defaultExpectation.params)
|
||||
}
|
||||
}
|
||||
|
||||
return mmExportExcel
|
||||
}
|
||||
|
||||
// ExpectCtxParam1 sets up expected param ctx for SupplierService.ExportExcel
|
||||
func (mmExportExcel *mSupplierServiceMockExportExcel) ExpectCtxParam1(ctx context.Context) *mSupplierServiceMockExportExcel {
|
||||
if mmExportExcel.mock.funcExportExcel != nil {
|
||||
mmExportExcel.mock.t.Fatalf("SupplierServiceMock.ExportExcel mock is already set by Set")
|
||||
}
|
||||
|
||||
if mmExportExcel.defaultExpectation == nil {
|
||||
mmExportExcel.defaultExpectation = &SupplierServiceMockExportExcelExpectation{}
|
||||
}
|
||||
|
||||
if mmExportExcel.defaultExpectation.params != nil {
|
||||
mmExportExcel.mock.t.Fatalf("SupplierServiceMock.ExportExcel mock is already set by Expect")
|
||||
}
|
||||
|
||||
if mmExportExcel.defaultExpectation.paramPtrs == nil {
|
||||
mmExportExcel.defaultExpectation.paramPtrs = &SupplierServiceMockExportExcelParamPtrs{}
|
||||
}
|
||||
mmExportExcel.defaultExpectation.paramPtrs.ctx = &ctx
|
||||
mmExportExcel.defaultExpectation.expectationOrigins.originCtx = minimock.CallerInfo(1)
|
||||
|
||||
return mmExportExcel
|
||||
}
|
||||
|
||||
// ExpectRequestIDParam2 sets up expected param requestID for SupplierService.ExportExcel
|
||||
func (mmExportExcel *mSupplierServiceMockExportExcel) ExpectRequestIDParam2(requestID uuid.UUID) *mSupplierServiceMockExportExcel {
|
||||
if mmExportExcel.mock.funcExportExcel != nil {
|
||||
mmExportExcel.mock.t.Fatalf("SupplierServiceMock.ExportExcel mock is already set by Set")
|
||||
}
|
||||
|
||||
if mmExportExcel.defaultExpectation == nil {
|
||||
mmExportExcel.defaultExpectation = &SupplierServiceMockExportExcelExpectation{}
|
||||
}
|
||||
|
||||
if mmExportExcel.defaultExpectation.params != nil {
|
||||
mmExportExcel.mock.t.Fatalf("SupplierServiceMock.ExportExcel mock is already set by Expect")
|
||||
}
|
||||
|
||||
if mmExportExcel.defaultExpectation.paramPtrs == nil {
|
||||
mmExportExcel.defaultExpectation.paramPtrs = &SupplierServiceMockExportExcelParamPtrs{}
|
||||
}
|
||||
mmExportExcel.defaultExpectation.paramPtrs.requestID = &requestID
|
||||
mmExportExcel.defaultExpectation.expectationOrigins.originRequestID = minimock.CallerInfo(1)
|
||||
|
||||
return mmExportExcel
|
||||
}
|
||||
|
||||
// Inspect accepts an inspector function that has same arguments as the SupplierService.ExportExcel
|
||||
func (mmExportExcel *mSupplierServiceMockExportExcel) Inspect(f func(ctx context.Context, requestID uuid.UUID)) *mSupplierServiceMockExportExcel {
|
||||
if mmExportExcel.mock.inspectFuncExportExcel != nil {
|
||||
mmExportExcel.mock.t.Fatalf("Inspect function is already set for SupplierServiceMock.ExportExcel")
|
||||
}
|
||||
|
||||
mmExportExcel.mock.inspectFuncExportExcel = f
|
||||
|
||||
return mmExportExcel
|
||||
}
|
||||
|
||||
// Return sets up results that will be returned by SupplierService.ExportExcel
|
||||
func (mmExportExcel *mSupplierServiceMockExportExcel) Return(ba1 []byte, err error) *SupplierServiceMock {
|
||||
if mmExportExcel.mock.funcExportExcel != nil {
|
||||
mmExportExcel.mock.t.Fatalf("SupplierServiceMock.ExportExcel mock is already set by Set")
|
||||
}
|
||||
|
||||
if mmExportExcel.defaultExpectation == nil {
|
||||
mmExportExcel.defaultExpectation = &SupplierServiceMockExportExcelExpectation{mock: mmExportExcel.mock}
|
||||
}
|
||||
mmExportExcel.defaultExpectation.results = &SupplierServiceMockExportExcelResults{ba1, err}
|
||||
mmExportExcel.defaultExpectation.returnOrigin = minimock.CallerInfo(1)
|
||||
return mmExportExcel.mock
|
||||
}
|
||||
|
||||
// Set uses given function f to mock the SupplierService.ExportExcel method
|
||||
func (mmExportExcel *mSupplierServiceMockExportExcel) Set(f func(ctx context.Context, requestID uuid.UUID) (ba1 []byte, err error)) *SupplierServiceMock {
|
||||
if mmExportExcel.defaultExpectation != nil {
|
||||
mmExportExcel.mock.t.Fatalf("Default expectation is already set for the SupplierService.ExportExcel method")
|
||||
}
|
||||
|
||||
if len(mmExportExcel.expectations) > 0 {
|
||||
mmExportExcel.mock.t.Fatalf("Some expectations are already set for the SupplierService.ExportExcel method")
|
||||
}
|
||||
|
||||
mmExportExcel.mock.funcExportExcel = f
|
||||
mmExportExcel.mock.funcExportExcelOrigin = minimock.CallerInfo(1)
|
||||
return mmExportExcel.mock
|
||||
}
|
||||
|
||||
// When sets expectation for the SupplierService.ExportExcel which will trigger the result defined by the following
|
||||
// Then helper
|
||||
func (mmExportExcel *mSupplierServiceMockExportExcel) When(ctx context.Context, requestID uuid.UUID) *SupplierServiceMockExportExcelExpectation {
|
||||
if mmExportExcel.mock.funcExportExcel != nil {
|
||||
mmExportExcel.mock.t.Fatalf("SupplierServiceMock.ExportExcel mock is already set by Set")
|
||||
}
|
||||
|
||||
expectation := &SupplierServiceMockExportExcelExpectation{
|
||||
mock: mmExportExcel.mock,
|
||||
params: &SupplierServiceMockExportExcelParams{ctx, requestID},
|
||||
expectationOrigins: SupplierServiceMockExportExcelExpectationOrigins{origin: minimock.CallerInfo(1)},
|
||||
}
|
||||
mmExportExcel.expectations = append(mmExportExcel.expectations, expectation)
|
||||
return expectation
|
||||
}
|
||||
|
||||
// Then sets up SupplierService.ExportExcel return parameters for the expectation previously defined by the When method
|
||||
func (e *SupplierServiceMockExportExcelExpectation) Then(ba1 []byte, err error) *SupplierServiceMock {
|
||||
e.results = &SupplierServiceMockExportExcelResults{ba1, err}
|
||||
return e.mock
|
||||
}
|
||||
|
||||
// Times sets number of times SupplierService.ExportExcel should be invoked
|
||||
func (mmExportExcel *mSupplierServiceMockExportExcel) Times(n uint64) *mSupplierServiceMockExportExcel {
|
||||
if n == 0 {
|
||||
mmExportExcel.mock.t.Fatalf("Times of SupplierServiceMock.ExportExcel mock can not be zero")
|
||||
}
|
||||
mm_atomic.StoreUint64(&mmExportExcel.expectedInvocations, n)
|
||||
mmExportExcel.expectedInvocationsOrigin = minimock.CallerInfo(1)
|
||||
return mmExportExcel
|
||||
}
|
||||
|
||||
func (mmExportExcel *mSupplierServiceMockExportExcel) invocationsDone() bool {
|
||||
if len(mmExportExcel.expectations) == 0 && mmExportExcel.defaultExpectation == nil && mmExportExcel.mock.funcExportExcel == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
totalInvocations := mm_atomic.LoadUint64(&mmExportExcel.mock.afterExportExcelCounter)
|
||||
expectedInvocations := mm_atomic.LoadUint64(&mmExportExcel.expectedInvocations)
|
||||
|
||||
return totalInvocations > 0 && (expectedInvocations == 0 || expectedInvocations == totalInvocations)
|
||||
}
|
||||
|
||||
// ExportExcel implements mm_service.SupplierService
|
||||
func (mmExportExcel *SupplierServiceMock) ExportExcel(ctx context.Context, requestID uuid.UUID) (ba1 []byte, err error) {
|
||||
mm_atomic.AddUint64(&mmExportExcel.beforeExportExcelCounter, 1)
|
||||
defer mm_atomic.AddUint64(&mmExportExcel.afterExportExcelCounter, 1)
|
||||
|
||||
mmExportExcel.t.Helper()
|
||||
|
||||
if mmExportExcel.inspectFuncExportExcel != nil {
|
||||
mmExportExcel.inspectFuncExportExcel(ctx, requestID)
|
||||
}
|
||||
|
||||
mm_params := SupplierServiceMockExportExcelParams{ctx, requestID}
|
||||
|
||||
// Record call args
|
||||
mmExportExcel.ExportExcelMock.mutex.Lock()
|
||||
mmExportExcel.ExportExcelMock.callArgs = append(mmExportExcel.ExportExcelMock.callArgs, &mm_params)
|
||||
mmExportExcel.ExportExcelMock.mutex.Unlock()
|
||||
|
||||
for _, e := range mmExportExcel.ExportExcelMock.expectations {
|
||||
if minimock.Equal(*e.params, mm_params) {
|
||||
mm_atomic.AddUint64(&e.Counter, 1)
|
||||
return e.results.ba1, e.results.err
|
||||
}
|
||||
}
|
||||
|
||||
if mmExportExcel.ExportExcelMock.defaultExpectation != nil {
|
||||
mm_atomic.AddUint64(&mmExportExcel.ExportExcelMock.defaultExpectation.Counter, 1)
|
||||
mm_want := mmExportExcel.ExportExcelMock.defaultExpectation.params
|
||||
mm_want_ptrs := mmExportExcel.ExportExcelMock.defaultExpectation.paramPtrs
|
||||
|
||||
mm_got := SupplierServiceMockExportExcelParams{ctx, requestID}
|
||||
|
||||
if mm_want_ptrs != nil {
|
||||
|
||||
if mm_want_ptrs.ctx != nil && !minimock.Equal(*mm_want_ptrs.ctx, mm_got.ctx) {
|
||||
mmExportExcel.t.Errorf("SupplierServiceMock.ExportExcel got unexpected parameter ctx, expected at\n%s:\nwant: %#v\n got: %#v%s\n",
|
||||
mmExportExcel.ExportExcelMock.defaultExpectation.expectationOrigins.originCtx, *mm_want_ptrs.ctx, mm_got.ctx, minimock.Diff(*mm_want_ptrs.ctx, mm_got.ctx))
|
||||
}
|
||||
|
||||
if mm_want_ptrs.requestID != nil && !minimock.Equal(*mm_want_ptrs.requestID, mm_got.requestID) {
|
||||
mmExportExcel.t.Errorf("SupplierServiceMock.ExportExcel got unexpected parameter requestID, expected at\n%s:\nwant: %#v\n got: %#v%s\n",
|
||||
mmExportExcel.ExportExcelMock.defaultExpectation.expectationOrigins.originRequestID, *mm_want_ptrs.requestID, mm_got.requestID, minimock.Diff(*mm_want_ptrs.requestID, mm_got.requestID))
|
||||
}
|
||||
|
||||
} else if mm_want != nil && !minimock.Equal(*mm_want, mm_got) {
|
||||
mmExportExcel.t.Errorf("SupplierServiceMock.ExportExcel got unexpected parameters, expected at\n%s:\nwant: %#v\n got: %#v%s\n",
|
||||
mmExportExcel.ExportExcelMock.defaultExpectation.expectationOrigins.origin, *mm_want, mm_got, minimock.Diff(*mm_want, mm_got))
|
||||
}
|
||||
|
||||
mm_results := mmExportExcel.ExportExcelMock.defaultExpectation.results
|
||||
if mm_results == nil {
|
||||
mmExportExcel.t.Fatal("No results are set for the SupplierServiceMock.ExportExcel")
|
||||
}
|
||||
return (*mm_results).ba1, (*mm_results).err
|
||||
}
|
||||
if mmExportExcel.funcExportExcel != nil {
|
||||
return mmExportExcel.funcExportExcel(ctx, requestID)
|
||||
}
|
||||
mmExportExcel.t.Fatalf("Unexpected call to SupplierServiceMock.ExportExcel. %v %v", ctx, requestID)
|
||||
return
|
||||
}
|
||||
|
||||
// ExportExcelAfterCounter returns a count of finished SupplierServiceMock.ExportExcel invocations
|
||||
func (mmExportExcel *SupplierServiceMock) ExportExcelAfterCounter() uint64 {
|
||||
return mm_atomic.LoadUint64(&mmExportExcel.afterExportExcelCounter)
|
||||
}
|
||||
|
||||
// ExportExcelBeforeCounter returns a count of SupplierServiceMock.ExportExcel invocations
|
||||
func (mmExportExcel *SupplierServiceMock) ExportExcelBeforeCounter() uint64 {
|
||||
return mm_atomic.LoadUint64(&mmExportExcel.beforeExportExcelCounter)
|
||||
}
|
||||
|
||||
// Calls returns a list of arguments used in each call to SupplierServiceMock.ExportExcel.
|
||||
// The list is in the same order as the calls were made (i.e. recent calls have a higher index)
|
||||
func (mmExportExcel *mSupplierServiceMockExportExcel) Calls() []*SupplierServiceMockExportExcelParams {
|
||||
mmExportExcel.mutex.RLock()
|
||||
|
||||
argCopy := make([]*SupplierServiceMockExportExcelParams, len(mmExportExcel.callArgs))
|
||||
copy(argCopy, mmExportExcel.callArgs)
|
||||
|
||||
mmExportExcel.mutex.RUnlock()
|
||||
|
||||
return argCopy
|
||||
}
|
||||
|
||||
// MinimockExportExcelDone returns true if the count of the ExportExcel invocations corresponds
|
||||
// the number of defined expectations
|
||||
func (m *SupplierServiceMock) MinimockExportExcelDone() bool {
|
||||
if m.ExportExcelMock.optional {
|
||||
// Optional methods provide '0 or more' call count restriction.
|
||||
return true
|
||||
}
|
||||
|
||||
for _, e := range m.ExportExcelMock.expectations {
|
||||
if mm_atomic.LoadUint64(&e.Counter) < 1 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return m.ExportExcelMock.invocationsDone()
|
||||
}
|
||||
|
||||
// MinimockExportExcelInspect logs each unmet expectation
|
||||
func (m *SupplierServiceMock) MinimockExportExcelInspect() {
|
||||
for _, e := range m.ExportExcelMock.expectations {
|
||||
if mm_atomic.LoadUint64(&e.Counter) < 1 {
|
||||
m.t.Errorf("Expected call to SupplierServiceMock.ExportExcel at\n%s with params: %#v", e.expectationOrigins.origin, *e.params)
|
||||
}
|
||||
}
|
||||
|
||||
afterExportExcelCounter := mm_atomic.LoadUint64(&m.afterExportExcelCounter)
|
||||
// if default expectation was set then invocations count should be greater than zero
|
||||
if m.ExportExcelMock.defaultExpectation != nil && afterExportExcelCounter < 1 {
|
||||
if m.ExportExcelMock.defaultExpectation.params == nil {
|
||||
m.t.Errorf("Expected call to SupplierServiceMock.ExportExcel at\n%s", m.ExportExcelMock.defaultExpectation.returnOrigin)
|
||||
} else {
|
||||
m.t.Errorf("Expected call to SupplierServiceMock.ExportExcel at\n%s with params: %#v", m.ExportExcelMock.defaultExpectation.expectationOrigins.origin, *m.ExportExcelMock.defaultExpectation.params)
|
||||
}
|
||||
}
|
||||
// if func was set then invocations count should be greater than zero
|
||||
if m.funcExportExcel != nil && afterExportExcelCounter < 1 {
|
||||
m.t.Errorf("Expected call to SupplierServiceMock.ExportExcel at\n%s", m.funcExportExcelOrigin)
|
||||
}
|
||||
|
||||
if !m.ExportExcelMock.invocationsDone() && afterExportExcelCounter > 0 {
|
||||
m.t.Errorf("Expected %d calls to SupplierServiceMock.ExportExcel at\n%s but found %d calls",
|
||||
mm_atomic.LoadUint64(&m.ExportExcelMock.expectedInvocations), m.ExportExcelMock.expectedInvocationsOrigin, afterExportExcelCounter)
|
||||
}
|
||||
}
|
||||
|
||||
// MinimockFinish checks that all mocked methods have been called the expected number of times
|
||||
func (m *SupplierServiceMock) MinimockFinish() {
|
||||
m.finishOnce.Do(func() {
|
||||
if !m.minimockDone() {
|
||||
m.MinimockExportExcelInspect()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// MinimockWait waits for all mocked methods to be called the expected number of times
|
||||
func (m *SupplierServiceMock) MinimockWait(timeout mm_time.Duration) {
|
||||
timeoutCh := mm_time.After(timeout)
|
||||
for {
|
||||
if m.minimockDone() {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-timeoutCh:
|
||||
m.MinimockFinish()
|
||||
return
|
||||
case <-mm_time.After(10 * mm_time.Millisecond):
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *SupplierServiceMock) minimockDone() bool {
|
||||
done := true
|
||||
return done &&
|
||||
m.MinimockExportExcelDone()
|
||||
}
|
||||
417
internal/mocks/token_usage_repository_mock.go
Normal file
417
internal/mocks/token_usage_repository_mock.go
Normal file
@@ -0,0 +1,417 @@
|
||||
// Code generated by http://github.com/gojuno/minimock (v3.4.7). DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
//go:generate minimock -i smart-search-back/internal/repository.TokenUsageRepository -o token_usage_repository_mock.go -n TokenUsageRepositoryMock -p mocks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"smart-search-back/internal/model"
|
||||
"sync"
|
||||
mm_atomic "sync/atomic"
|
||||
mm_time "time"
|
||||
|
||||
"github.com/gojuno/minimock/v3"
|
||||
)
|
||||
|
||||
// TokenUsageRepositoryMock implements mm_repository.TokenUsageRepository
|
||||
type TokenUsageRepositoryMock struct {
|
||||
t minimock.Tester
|
||||
finishOnce sync.Once
|
||||
|
||||
funcCreate func(ctx context.Context, usage *model.TokenUsage) (err error)
|
||||
funcCreateOrigin string
|
||||
inspectFuncCreate func(ctx context.Context, usage *model.TokenUsage)
|
||||
afterCreateCounter uint64
|
||||
beforeCreateCounter uint64
|
||||
CreateMock mTokenUsageRepositoryMockCreate
|
||||
}
|
||||
|
||||
// NewTokenUsageRepositoryMock returns a mock for mm_repository.TokenUsageRepository
|
||||
func NewTokenUsageRepositoryMock(t minimock.Tester) *TokenUsageRepositoryMock {
|
||||
m := &TokenUsageRepositoryMock{t: t}
|
||||
|
||||
if controller, ok := t.(minimock.MockController); ok {
|
||||
controller.RegisterMocker(m)
|
||||
}
|
||||
|
||||
m.CreateMock = mTokenUsageRepositoryMockCreate{mock: m}
|
||||
m.CreateMock.callArgs = []*TokenUsageRepositoryMockCreateParams{}
|
||||
|
||||
t.Cleanup(m.MinimockFinish)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
type mTokenUsageRepositoryMockCreate struct {
|
||||
optional bool
|
||||
mock *TokenUsageRepositoryMock
|
||||
defaultExpectation *TokenUsageRepositoryMockCreateExpectation
|
||||
expectations []*TokenUsageRepositoryMockCreateExpectation
|
||||
|
||||
callArgs []*TokenUsageRepositoryMockCreateParams
|
||||
mutex sync.RWMutex
|
||||
|
||||
expectedInvocations uint64
|
||||
expectedInvocationsOrigin string
|
||||
}
|
||||
|
||||
// TokenUsageRepositoryMockCreateExpectation specifies expectation struct of the TokenUsageRepository.Create
|
||||
type TokenUsageRepositoryMockCreateExpectation struct {
|
||||
mock *TokenUsageRepositoryMock
|
||||
params *TokenUsageRepositoryMockCreateParams
|
||||
paramPtrs *TokenUsageRepositoryMockCreateParamPtrs
|
||||
expectationOrigins TokenUsageRepositoryMockCreateExpectationOrigins
|
||||
results *TokenUsageRepositoryMockCreateResults
|
||||
returnOrigin string
|
||||
Counter uint64
|
||||
}
|
||||
|
||||
// TokenUsageRepositoryMockCreateParams contains parameters of the TokenUsageRepository.Create
|
||||
type TokenUsageRepositoryMockCreateParams struct {
|
||||
ctx context.Context
|
||||
usage *model.TokenUsage
|
||||
}
|
||||
|
||||
// TokenUsageRepositoryMockCreateParamPtrs contains pointers to parameters of the TokenUsageRepository.Create
|
||||
type TokenUsageRepositoryMockCreateParamPtrs struct {
|
||||
ctx *context.Context
|
||||
usage **model.TokenUsage
|
||||
}
|
||||
|
||||
// TokenUsageRepositoryMockCreateResults contains results of the TokenUsageRepository.Create
|
||||
type TokenUsageRepositoryMockCreateResults struct {
|
||||
err error
|
||||
}
|
||||
|
||||
// TokenUsageRepositoryMockCreateOrigins contains origins of expectations of the TokenUsageRepository.Create
|
||||
type TokenUsageRepositoryMockCreateExpectationOrigins struct {
|
||||
origin string
|
||||
originCtx string
|
||||
originUsage string
|
||||
}
|
||||
|
||||
// Marks this method to be optional. The default behavior of any method with Return() is '1 or more', meaning
|
||||
// the test will fail minimock's automatic final call check if the mocked method was not called at least once.
|
||||
// Optional() makes method check to work in '0 or more' mode.
|
||||
// It is NOT RECOMMENDED to use this option unless you really need it, as default behaviour helps to
|
||||
// catch the problems when the expected method call is totally skipped during test run.
|
||||
func (mmCreate *mTokenUsageRepositoryMockCreate) Optional() *mTokenUsageRepositoryMockCreate {
|
||||
mmCreate.optional = true
|
||||
return mmCreate
|
||||
}
|
||||
|
||||
// Expect sets up expected params for TokenUsageRepository.Create
|
||||
func (mmCreate *mTokenUsageRepositoryMockCreate) Expect(ctx context.Context, usage *model.TokenUsage) *mTokenUsageRepositoryMockCreate {
|
||||
if mmCreate.mock.funcCreate != nil {
|
||||
mmCreate.mock.t.Fatalf("TokenUsageRepositoryMock.Create mock is already set by Set")
|
||||
}
|
||||
|
||||
if mmCreate.defaultExpectation == nil {
|
||||
mmCreate.defaultExpectation = &TokenUsageRepositoryMockCreateExpectation{}
|
||||
}
|
||||
|
||||
if mmCreate.defaultExpectation.paramPtrs != nil {
|
||||
mmCreate.mock.t.Fatalf("TokenUsageRepositoryMock.Create mock is already set by ExpectParams functions")
|
||||
}
|
||||
|
||||
mmCreate.defaultExpectation.params = &TokenUsageRepositoryMockCreateParams{ctx, usage}
|
||||
mmCreate.defaultExpectation.expectationOrigins.origin = minimock.CallerInfo(1)
|
||||
for _, e := range mmCreate.expectations {
|
||||
if minimock.Equal(e.params, mmCreate.defaultExpectation.params) {
|
||||
mmCreate.mock.t.Fatalf("Expectation set by When has same params: %#v", *mmCreate.defaultExpectation.params)
|
||||
}
|
||||
}
|
||||
|
||||
return mmCreate
|
||||
}
|
||||
|
||||
// ExpectCtxParam1 sets up expected param ctx for TokenUsageRepository.Create
|
||||
func (mmCreate *mTokenUsageRepositoryMockCreate) ExpectCtxParam1(ctx context.Context) *mTokenUsageRepositoryMockCreate {
|
||||
if mmCreate.mock.funcCreate != nil {
|
||||
mmCreate.mock.t.Fatalf("TokenUsageRepositoryMock.Create mock is already set by Set")
|
||||
}
|
||||
|
||||
if mmCreate.defaultExpectation == nil {
|
||||
mmCreate.defaultExpectation = &TokenUsageRepositoryMockCreateExpectation{}
|
||||
}
|
||||
|
||||
if mmCreate.defaultExpectation.params != nil {
|
||||
mmCreate.mock.t.Fatalf("TokenUsageRepositoryMock.Create mock is already set by Expect")
|
||||
}
|
||||
|
||||
if mmCreate.defaultExpectation.paramPtrs == nil {
|
||||
mmCreate.defaultExpectation.paramPtrs = &TokenUsageRepositoryMockCreateParamPtrs{}
|
||||
}
|
||||
mmCreate.defaultExpectation.paramPtrs.ctx = &ctx
|
||||
mmCreate.defaultExpectation.expectationOrigins.originCtx = minimock.CallerInfo(1)
|
||||
|
||||
return mmCreate
|
||||
}
|
||||
|
||||
// ExpectUsageParam2 sets up expected param usage for TokenUsageRepository.Create
|
||||
func (mmCreate *mTokenUsageRepositoryMockCreate) ExpectUsageParam2(usage *model.TokenUsage) *mTokenUsageRepositoryMockCreate {
|
||||
if mmCreate.mock.funcCreate != nil {
|
||||
mmCreate.mock.t.Fatalf("TokenUsageRepositoryMock.Create mock is already set by Set")
|
||||
}
|
||||
|
||||
if mmCreate.defaultExpectation == nil {
|
||||
mmCreate.defaultExpectation = &TokenUsageRepositoryMockCreateExpectation{}
|
||||
}
|
||||
|
||||
if mmCreate.defaultExpectation.params != nil {
|
||||
mmCreate.mock.t.Fatalf("TokenUsageRepositoryMock.Create mock is already set by Expect")
|
||||
}
|
||||
|
||||
if mmCreate.defaultExpectation.paramPtrs == nil {
|
||||
mmCreate.defaultExpectation.paramPtrs = &TokenUsageRepositoryMockCreateParamPtrs{}
|
||||
}
|
||||
mmCreate.defaultExpectation.paramPtrs.usage = &usage
|
||||
mmCreate.defaultExpectation.expectationOrigins.originUsage = minimock.CallerInfo(1)
|
||||
|
||||
return mmCreate
|
||||
}
|
||||
|
||||
// Inspect accepts an inspector function that has same arguments as the TokenUsageRepository.Create
|
||||
func (mmCreate *mTokenUsageRepositoryMockCreate) Inspect(f func(ctx context.Context, usage *model.TokenUsage)) *mTokenUsageRepositoryMockCreate {
|
||||
if mmCreate.mock.inspectFuncCreate != nil {
|
||||
mmCreate.mock.t.Fatalf("Inspect function is already set for TokenUsageRepositoryMock.Create")
|
||||
}
|
||||
|
||||
mmCreate.mock.inspectFuncCreate = f
|
||||
|
||||
return mmCreate
|
||||
}
|
||||
|
||||
// Return sets up results that will be returned by TokenUsageRepository.Create
|
||||
func (mmCreate *mTokenUsageRepositoryMockCreate) Return(err error) *TokenUsageRepositoryMock {
|
||||
if mmCreate.mock.funcCreate != nil {
|
||||
mmCreate.mock.t.Fatalf("TokenUsageRepositoryMock.Create mock is already set by Set")
|
||||
}
|
||||
|
||||
if mmCreate.defaultExpectation == nil {
|
||||
mmCreate.defaultExpectation = &TokenUsageRepositoryMockCreateExpectation{mock: mmCreate.mock}
|
||||
}
|
||||
mmCreate.defaultExpectation.results = &TokenUsageRepositoryMockCreateResults{err}
|
||||
mmCreate.defaultExpectation.returnOrigin = minimock.CallerInfo(1)
|
||||
return mmCreate.mock
|
||||
}
|
||||
|
||||
// Set uses given function f to mock the TokenUsageRepository.Create method
|
||||
func (mmCreate *mTokenUsageRepositoryMockCreate) Set(f func(ctx context.Context, usage *model.TokenUsage) (err error)) *TokenUsageRepositoryMock {
|
||||
if mmCreate.defaultExpectation != nil {
|
||||
mmCreate.mock.t.Fatalf("Default expectation is already set for the TokenUsageRepository.Create method")
|
||||
}
|
||||
|
||||
if len(mmCreate.expectations) > 0 {
|
||||
mmCreate.mock.t.Fatalf("Some expectations are already set for the TokenUsageRepository.Create method")
|
||||
}
|
||||
|
||||
mmCreate.mock.funcCreate = f
|
||||
mmCreate.mock.funcCreateOrigin = minimock.CallerInfo(1)
|
||||
return mmCreate.mock
|
||||
}
|
||||
|
||||
// When sets expectation for the TokenUsageRepository.Create which will trigger the result defined by the following
|
||||
// Then helper
|
||||
func (mmCreate *mTokenUsageRepositoryMockCreate) When(ctx context.Context, usage *model.TokenUsage) *TokenUsageRepositoryMockCreateExpectation {
|
||||
if mmCreate.mock.funcCreate != nil {
|
||||
mmCreate.mock.t.Fatalf("TokenUsageRepositoryMock.Create mock is already set by Set")
|
||||
}
|
||||
|
||||
expectation := &TokenUsageRepositoryMockCreateExpectation{
|
||||
mock: mmCreate.mock,
|
||||
params: &TokenUsageRepositoryMockCreateParams{ctx, usage},
|
||||
expectationOrigins: TokenUsageRepositoryMockCreateExpectationOrigins{origin: minimock.CallerInfo(1)},
|
||||
}
|
||||
mmCreate.expectations = append(mmCreate.expectations, expectation)
|
||||
return expectation
|
||||
}
|
||||
|
||||
// Then sets up TokenUsageRepository.Create return parameters for the expectation previously defined by the When method
|
||||
func (e *TokenUsageRepositoryMockCreateExpectation) Then(err error) *TokenUsageRepositoryMock {
|
||||
e.results = &TokenUsageRepositoryMockCreateResults{err}
|
||||
return e.mock
|
||||
}
|
||||
|
||||
// Times sets number of times TokenUsageRepository.Create should be invoked
|
||||
func (mmCreate *mTokenUsageRepositoryMockCreate) Times(n uint64) *mTokenUsageRepositoryMockCreate {
|
||||
if n == 0 {
|
||||
mmCreate.mock.t.Fatalf("Times of TokenUsageRepositoryMock.Create mock can not be zero")
|
||||
}
|
||||
mm_atomic.StoreUint64(&mmCreate.expectedInvocations, n)
|
||||
mmCreate.expectedInvocationsOrigin = minimock.CallerInfo(1)
|
||||
return mmCreate
|
||||
}
|
||||
|
||||
func (mmCreate *mTokenUsageRepositoryMockCreate) invocationsDone() bool {
|
||||
if len(mmCreate.expectations) == 0 && mmCreate.defaultExpectation == nil && mmCreate.mock.funcCreate == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
totalInvocations := mm_atomic.LoadUint64(&mmCreate.mock.afterCreateCounter)
|
||||
expectedInvocations := mm_atomic.LoadUint64(&mmCreate.expectedInvocations)
|
||||
|
||||
return totalInvocations > 0 && (expectedInvocations == 0 || expectedInvocations == totalInvocations)
|
||||
}
|
||||
|
||||
// Create implements mm_repository.TokenUsageRepository
|
||||
func (mmCreate *TokenUsageRepositoryMock) Create(ctx context.Context, usage *model.TokenUsage) (err error) {
|
||||
mm_atomic.AddUint64(&mmCreate.beforeCreateCounter, 1)
|
||||
defer mm_atomic.AddUint64(&mmCreate.afterCreateCounter, 1)
|
||||
|
||||
mmCreate.t.Helper()
|
||||
|
||||
if mmCreate.inspectFuncCreate != nil {
|
||||
mmCreate.inspectFuncCreate(ctx, usage)
|
||||
}
|
||||
|
||||
mm_params := TokenUsageRepositoryMockCreateParams{ctx, usage}
|
||||
|
||||
// Record call args
|
||||
mmCreate.CreateMock.mutex.Lock()
|
||||
mmCreate.CreateMock.callArgs = append(mmCreate.CreateMock.callArgs, &mm_params)
|
||||
mmCreate.CreateMock.mutex.Unlock()
|
||||
|
||||
for _, e := range mmCreate.CreateMock.expectations {
|
||||
if minimock.Equal(*e.params, mm_params) {
|
||||
mm_atomic.AddUint64(&e.Counter, 1)
|
||||
return e.results.err
|
||||
}
|
||||
}
|
||||
|
||||
if mmCreate.CreateMock.defaultExpectation != nil {
|
||||
mm_atomic.AddUint64(&mmCreate.CreateMock.defaultExpectation.Counter, 1)
|
||||
mm_want := mmCreate.CreateMock.defaultExpectation.params
|
||||
mm_want_ptrs := mmCreate.CreateMock.defaultExpectation.paramPtrs
|
||||
|
||||
mm_got := TokenUsageRepositoryMockCreateParams{ctx, usage}
|
||||
|
||||
if mm_want_ptrs != nil {
|
||||
|
||||
if mm_want_ptrs.ctx != nil && !minimock.Equal(*mm_want_ptrs.ctx, mm_got.ctx) {
|
||||
mmCreate.t.Errorf("TokenUsageRepositoryMock.Create got unexpected parameter ctx, expected at\n%s:\nwant: %#v\n got: %#v%s\n",
|
||||
mmCreate.CreateMock.defaultExpectation.expectationOrigins.originCtx, *mm_want_ptrs.ctx, mm_got.ctx, minimock.Diff(*mm_want_ptrs.ctx, mm_got.ctx))
|
||||
}
|
||||
|
||||
if mm_want_ptrs.usage != nil && !minimock.Equal(*mm_want_ptrs.usage, mm_got.usage) {
|
||||
mmCreate.t.Errorf("TokenUsageRepositoryMock.Create got unexpected parameter usage, expected at\n%s:\nwant: %#v\n got: %#v%s\n",
|
||||
mmCreate.CreateMock.defaultExpectation.expectationOrigins.originUsage, *mm_want_ptrs.usage, mm_got.usage, minimock.Diff(*mm_want_ptrs.usage, mm_got.usage))
|
||||
}
|
||||
|
||||
} else if mm_want != nil && !minimock.Equal(*mm_want, mm_got) {
|
||||
mmCreate.t.Errorf("TokenUsageRepositoryMock.Create got unexpected parameters, expected at\n%s:\nwant: %#v\n got: %#v%s\n",
|
||||
mmCreate.CreateMock.defaultExpectation.expectationOrigins.origin, *mm_want, mm_got, minimock.Diff(*mm_want, mm_got))
|
||||
}
|
||||
|
||||
mm_results := mmCreate.CreateMock.defaultExpectation.results
|
||||
if mm_results == nil {
|
||||
mmCreate.t.Fatal("No results are set for the TokenUsageRepositoryMock.Create")
|
||||
}
|
||||
return (*mm_results).err
|
||||
}
|
||||
if mmCreate.funcCreate != nil {
|
||||
return mmCreate.funcCreate(ctx, usage)
|
||||
}
|
||||
mmCreate.t.Fatalf("Unexpected call to TokenUsageRepositoryMock.Create. %v %v", ctx, usage)
|
||||
return
|
||||
}
|
||||
|
||||
// CreateAfterCounter returns a count of finished TokenUsageRepositoryMock.Create invocations
|
||||
func (mmCreate *TokenUsageRepositoryMock) CreateAfterCounter() uint64 {
|
||||
return mm_atomic.LoadUint64(&mmCreate.afterCreateCounter)
|
||||
}
|
||||
|
||||
// CreateBeforeCounter returns a count of TokenUsageRepositoryMock.Create invocations
|
||||
func (mmCreate *TokenUsageRepositoryMock) CreateBeforeCounter() uint64 {
|
||||
return mm_atomic.LoadUint64(&mmCreate.beforeCreateCounter)
|
||||
}
|
||||
|
||||
// Calls returns a list of arguments used in each call to TokenUsageRepositoryMock.Create.
|
||||
// The list is in the same order as the calls were made (i.e. recent calls have a higher index)
|
||||
func (mmCreate *mTokenUsageRepositoryMockCreate) Calls() []*TokenUsageRepositoryMockCreateParams {
|
||||
mmCreate.mutex.RLock()
|
||||
|
||||
argCopy := make([]*TokenUsageRepositoryMockCreateParams, len(mmCreate.callArgs))
|
||||
copy(argCopy, mmCreate.callArgs)
|
||||
|
||||
mmCreate.mutex.RUnlock()
|
||||
|
||||
return argCopy
|
||||
}
|
||||
|
||||
// MinimockCreateDone returns true if the count of the Create invocations corresponds
|
||||
// the number of defined expectations
|
||||
func (m *TokenUsageRepositoryMock) MinimockCreateDone() bool {
|
||||
if m.CreateMock.optional {
|
||||
// Optional methods provide '0 or more' call count restriction.
|
||||
return true
|
||||
}
|
||||
|
||||
for _, e := range m.CreateMock.expectations {
|
||||
if mm_atomic.LoadUint64(&e.Counter) < 1 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return m.CreateMock.invocationsDone()
|
||||
}
|
||||
|
||||
// MinimockCreateInspect logs each unmet expectation
|
||||
func (m *TokenUsageRepositoryMock) MinimockCreateInspect() {
|
||||
for _, e := range m.CreateMock.expectations {
|
||||
if mm_atomic.LoadUint64(&e.Counter) < 1 {
|
||||
m.t.Errorf("Expected call to TokenUsageRepositoryMock.Create at\n%s with params: %#v", e.expectationOrigins.origin, *e.params)
|
||||
}
|
||||
}
|
||||
|
||||
afterCreateCounter := mm_atomic.LoadUint64(&m.afterCreateCounter)
|
||||
// if default expectation was set then invocations count should be greater than zero
|
||||
if m.CreateMock.defaultExpectation != nil && afterCreateCounter < 1 {
|
||||
if m.CreateMock.defaultExpectation.params == nil {
|
||||
m.t.Errorf("Expected call to TokenUsageRepositoryMock.Create at\n%s", m.CreateMock.defaultExpectation.returnOrigin)
|
||||
} else {
|
||||
m.t.Errorf("Expected call to TokenUsageRepositoryMock.Create at\n%s with params: %#v", m.CreateMock.defaultExpectation.expectationOrigins.origin, *m.CreateMock.defaultExpectation.params)
|
||||
}
|
||||
}
|
||||
// if func was set then invocations count should be greater than zero
|
||||
if m.funcCreate != nil && afterCreateCounter < 1 {
|
||||
m.t.Errorf("Expected call to TokenUsageRepositoryMock.Create at\n%s", m.funcCreateOrigin)
|
||||
}
|
||||
|
||||
if !m.CreateMock.invocationsDone() && afterCreateCounter > 0 {
|
||||
m.t.Errorf("Expected %d calls to TokenUsageRepositoryMock.Create at\n%s but found %d calls",
|
||||
mm_atomic.LoadUint64(&m.CreateMock.expectedInvocations), m.CreateMock.expectedInvocationsOrigin, afterCreateCounter)
|
||||
}
|
||||
}
|
||||
|
||||
// MinimockFinish checks that all mocked methods have been called the expected number of times
|
||||
func (m *TokenUsageRepositoryMock) MinimockFinish() {
|
||||
m.finishOnce.Do(func() {
|
||||
if !m.minimockDone() {
|
||||
m.MinimockCreateInspect()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// MinimockWait waits for all mocked methods to be called the expected number of times
|
||||
func (m *TokenUsageRepositoryMock) MinimockWait(timeout mm_time.Duration) {
|
||||
timeoutCh := mm_time.After(timeout)
|
||||
for {
|
||||
if m.minimockDone() {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-timeoutCh:
|
||||
m.MinimockFinish()
|
||||
return
|
||||
case <-mm_time.After(10 * mm_time.Millisecond):
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *TokenUsageRepositoryMock) minimockDone() bool {
|
||||
done := true
|
||||
return done &&
|
||||
m.MinimockCreateDone()
|
||||
}
|
||||
2582
internal/mocks/user_repository_mock.go
Normal file
2582
internal/mocks/user_repository_mock.go
Normal file
File diff suppressed because it is too large
Load Diff
1130
internal/mocks/user_service_mock.go
Normal file
1130
internal/mocks/user_service_mock.go
Normal file
File diff suppressed because it is too large
Load Diff
14
internal/model/invite.go
Normal file
14
internal/model/invite.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type InviteCode struct {
|
||||
ID int
|
||||
UserID int
|
||||
Code int64
|
||||
CanBeUsedCount int
|
||||
UsedCount int
|
||||
IsActive bool
|
||||
CreatedAt time.Time
|
||||
ExpiresAt time.Time
|
||||
}
|
||||
40
internal/model/request.go
Normal file
40
internal/model/request.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Request struct {
|
||||
ID uuid.UUID
|
||||
UserID int
|
||||
RequestTxt string
|
||||
GeneratedTZ bool
|
||||
FinalTZ string
|
||||
GeneratedFinalTZ bool
|
||||
FinalUpdateTZ string
|
||||
MailingStatusID int
|
||||
MailingStatus string
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
type MailingStatus struct {
|
||||
ID int
|
||||
StatusName string
|
||||
}
|
||||
|
||||
type RequestDetail struct {
|
||||
RequestID uuid.UUID
|
||||
Title string
|
||||
MailText string
|
||||
Suppliers []SupplierInfo
|
||||
}
|
||||
|
||||
type SupplierInfo struct {
|
||||
CompanyID int `json:"company_id"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
CompanyName string `json:"company_name"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
15
internal/model/session.go
Normal file
15
internal/model/session.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type Session struct {
|
||||
ID int
|
||||
UserID int
|
||||
AccessToken string
|
||||
RefreshToken string
|
||||
IP string
|
||||
UserAgent string
|
||||
CreatedAt time.Time
|
||||
ExpiresAt time.Time
|
||||
RevokedAt *time.Time
|
||||
}
|
||||
28
internal/model/supplier.go
Normal file
28
internal/model/supplier.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Supplier struct {
|
||||
ID int
|
||||
RequestID uuid.UUID
|
||||
Name string
|
||||
Email string
|
||||
Phone string
|
||||
Address string
|
||||
URL string
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
type TokenUsage struct {
|
||||
ID int
|
||||
RequestID uuid.UUID
|
||||
RequestTokenCount int
|
||||
ResponseTokenCount int
|
||||
TokenCost float64
|
||||
Type string
|
||||
CreatedAt time.Time
|
||||
}
|
||||
18
internal/model/user.go
Normal file
18
internal/model/user.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type User struct {
|
||||
ID int
|
||||
Email string
|
||||
EmailHash string
|
||||
PasswordHash string
|
||||
Phone string
|
||||
UserName string
|
||||
CompanyName string
|
||||
Balance float64
|
||||
PaymentStatus string
|
||||
InvitesIssued int
|
||||
InvitesLimit int
|
||||
CreatedAt time.Time
|
||||
}
|
||||
54
internal/repository/interfaces.go
Normal file
54
internal/repository/interfaces.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"smart-search-back/internal/model"
|
||||
)
|
||||
|
||||
type UserRepository interface {
|
||||
FindByEmailHash(ctx context.Context, emailHash string) (*model.User, error)
|
||||
FindByID(ctx context.Context, userID int) (*model.User, error)
|
||||
Create(ctx context.Context, user *model.User) error
|
||||
UpdateBalance(ctx context.Context, userID int, delta float64) error
|
||||
GetBalance(ctx context.Context, userID int) (float64, error)
|
||||
IncrementInvitesIssued(ctx context.Context, userID int) error
|
||||
CheckInviteLimit(ctx context.Context, userID int) (bool, error)
|
||||
}
|
||||
|
||||
type SessionRepository interface {
|
||||
Create(ctx context.Context, session *model.Session) error
|
||||
FindByRefreshToken(ctx context.Context, token string) (*model.Session, error)
|
||||
UpdateAccessToken(ctx context.Context, refreshToken, newAccessToken string) error
|
||||
Revoke(ctx context.Context, refreshToken string) error
|
||||
DeleteExpired(ctx context.Context) (int, error)
|
||||
}
|
||||
|
||||
type InviteRepository interface {
|
||||
Create(ctx context.Context, invite *model.InviteCode) error
|
||||
FindByCode(ctx context.Context, code int64) (*model.InviteCode, error)
|
||||
IncrementUsedCount(ctx context.Context, code int64) error
|
||||
DeactivateExpired(ctx context.Context) (int, error)
|
||||
GetUserInvites(ctx context.Context, userID int) ([]*model.InviteCode, error)
|
||||
}
|
||||
|
||||
type RequestRepository interface {
|
||||
Create(ctx context.Context, req *model.Request) error
|
||||
UpdateWithTZ(ctx context.Context, id uuid.UUID, tz string, generated bool) error
|
||||
UpdateFinalTZ(ctx context.Context, id uuid.UUID, finalTZ string) error
|
||||
GetByUserID(ctx context.Context, userID int) ([]*model.Request, error)
|
||||
GetByID(ctx context.Context, id uuid.UUID) (*model.Request, error)
|
||||
GetDetailByID(ctx context.Context, id uuid.UUID) (*model.RequestDetail, error)
|
||||
GetUserStatistics(ctx context.Context, userID int) (requestsCount, suppliersCount, createdTZ int, err error)
|
||||
}
|
||||
|
||||
type SupplierRepository interface {
|
||||
BulkInsert(ctx context.Context, requestID uuid.UUID, suppliers []*model.Supplier) error
|
||||
GetByRequestID(ctx context.Context, requestID uuid.UUID) ([]*model.Supplier, error)
|
||||
DeleteByRequestID(ctx context.Context, requestID uuid.UUID) error
|
||||
}
|
||||
|
||||
type TokenUsageRepository interface {
|
||||
Create(ctx context.Context, usage *model.TokenUsage) error
|
||||
}
|
||||
146
internal/repository/invite.go
Normal file
146
internal/repository/invite.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"smart-search-back/internal/model"
|
||||
errs "smart-search-back/pkg/errors"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
type inviteRepository struct {
|
||||
pool *pgxpool.Pool
|
||||
qb sq.StatementBuilderType
|
||||
}
|
||||
|
||||
func NewInviteRepository(pool *pgxpool.Pool) InviteRepository {
|
||||
return &inviteRepository{
|
||||
pool: pool,
|
||||
qb: sq.StatementBuilder.PlaceholderFormat(sq.Dollar),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *inviteRepository) Create(ctx context.Context, invite *model.InviteCode) error {
|
||||
query := r.qb.Insert("invite_codes").Columns(
|
||||
"user_id", "code", "can_be_used_count", "expires_at",
|
||||
).Values(
|
||||
invite.UserID, invite.Code, invite.CanBeUsedCount, invite.ExpiresAt,
|
||||
).Suffix("RETURNING id, created_at")
|
||||
|
||||
sqlQuery, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return errs.NewInternalError(errs.DatabaseError, "failed to build query", err)
|
||||
}
|
||||
|
||||
err = r.pool.QueryRow(ctx, sqlQuery, args...).Scan(&invite.ID, &invite.CreatedAt)
|
||||
if err != nil {
|
||||
return errs.NewInternalError(errs.DatabaseError, "failed to create invite code", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *inviteRepository) FindByCode(ctx context.Context, code int64) (*model.InviteCode, error) {
|
||||
query := r.qb.Select(
|
||||
"id", "user_id", "code", "can_be_used_count", "used_count",
|
||||
"is_active", "created_at", "expires_at",
|
||||
).From("invite_codes").Where(sq.Eq{"code": code})
|
||||
|
||||
sqlQuery, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, errs.NewInternalError(errs.DatabaseError, "failed to build query", err)
|
||||
}
|
||||
|
||||
invite := &model.InviteCode{}
|
||||
err = r.pool.QueryRow(ctx, sqlQuery, args...).Scan(
|
||||
&invite.ID, &invite.UserID, &invite.Code, &invite.CanBeUsedCount,
|
||||
&invite.UsedCount, &invite.IsActive, &invite.CreatedAt, &invite.ExpiresAt,
|
||||
)
|
||||
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, errs.NewBusinessError(errs.UserNotFound, "invite code not found")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errs.NewInternalError(errs.DatabaseError, "failed to find invite code", err)
|
||||
}
|
||||
|
||||
return invite, nil
|
||||
}
|
||||
|
||||
func (r *inviteRepository) IncrementUsedCount(ctx context.Context, code int64) error {
|
||||
query := r.qb.Update("invite_codes").
|
||||
Set("used_count", sq.Expr("used_count + 1")).
|
||||
Where(sq.Eq{"code": code})
|
||||
|
||||
sqlQuery, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return errs.NewInternalError(errs.DatabaseError, "failed to build query", err)
|
||||
}
|
||||
|
||||
_, err = r.pool.Exec(ctx, sqlQuery, args...)
|
||||
if err != nil {
|
||||
return errs.NewInternalError(errs.DatabaseError, "failed to increment used count", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *inviteRepository) DeactivateExpired(ctx context.Context) (int, error) {
|
||||
query := r.qb.Update("invite_codes").
|
||||
Set("is_active", false).
|
||||
Where(sq.And{
|
||||
sq.Expr("expires_at < now()"),
|
||||
sq.Eq{"is_active": true},
|
||||
})
|
||||
|
||||
sqlQuery, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return 0, errs.NewInternalError(errs.DatabaseError, "failed to build query", err)
|
||||
}
|
||||
|
||||
result, err := r.pool.Exec(ctx, sqlQuery, args...)
|
||||
if err != nil {
|
||||
return 0, errs.NewInternalError(errs.DatabaseError, "failed to deactivate expired invites", err)
|
||||
}
|
||||
|
||||
return int(result.RowsAffected()), nil
|
||||
}
|
||||
|
||||
func (r *inviteRepository) GetUserInvites(ctx context.Context, userID int) ([]*model.InviteCode, error) {
|
||||
query := r.qb.Select(
|
||||
"id", "user_id", "code", "can_be_used_count", "used_count",
|
||||
"is_active", "created_at", "expires_at",
|
||||
).From("invite_codes").
|
||||
Where(sq.Eq{"user_id": userID}).
|
||||
OrderBy("created_at DESC")
|
||||
|
||||
sqlQuery, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, errs.NewInternalError(errs.DatabaseError, "failed to build query", err)
|
||||
}
|
||||
|
||||
rows, err := r.pool.Query(ctx, sqlQuery, args...)
|
||||
if err != nil {
|
||||
return nil, errs.NewInternalError(errs.DatabaseError, "failed to get user invites", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var invites []*model.InviteCode
|
||||
for rows.Next() {
|
||||
invite := &model.InviteCode{}
|
||||
err := rows.Scan(
|
||||
&invite.ID, &invite.UserID, &invite.Code, &invite.CanBeUsedCount,
|
||||
&invite.UsedCount, &invite.IsActive, &invite.CreatedAt, &invite.ExpiresAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errs.NewInternalError(errs.DatabaseError, "failed to scan invite", err)
|
||||
}
|
||||
invites = append(invites, invite)
|
||||
}
|
||||
|
||||
return invites, nil
|
||||
}
|
||||
209
internal/repository/request.go
Normal file
209
internal/repository/request.go
Normal file
@@ -0,0 +1,209 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"smart-search-back/internal/model"
|
||||
errs "smart-search-back/pkg/errors"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
type requestRepository struct {
|
||||
pool *pgxpool.Pool
|
||||
qb sq.StatementBuilderType
|
||||
}
|
||||
|
||||
func NewRequestRepository(pool *pgxpool.Pool) RequestRepository {
|
||||
return &requestRepository{
|
||||
pool: pool,
|
||||
qb: sq.StatementBuilder.PlaceholderFormat(sq.Dollar),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *requestRepository) Create(ctx context.Context, req *model.Request) error {
|
||||
query := r.qb.Insert("requests_for_suppliers").Columns(
|
||||
"user_id", "request_txt",
|
||||
).Values(
|
||||
req.UserID, req.RequestTxt,
|
||||
).Suffix("RETURNING id, created_at")
|
||||
|
||||
sqlQuery, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return errs.NewInternalError(errs.DatabaseError, "failed to build query", err)
|
||||
}
|
||||
|
||||
err = r.pool.QueryRow(ctx, sqlQuery, args...).Scan(&req.ID, &req.CreatedAt)
|
||||
if err != nil {
|
||||
return errs.NewInternalError(errs.DatabaseError, "failed to create request", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *requestRepository) UpdateWithTZ(ctx context.Context, id uuid.UUID, tz string, generated bool) error {
|
||||
query := r.qb.Update("requests_for_suppliers").
|
||||
Set("final_tz", tz).
|
||||
Set("generated_tz", generated).
|
||||
Set("generated_final_tz", generated).
|
||||
Where(sq.Eq{"id": id})
|
||||
|
||||
sqlQuery, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return errs.NewInternalError(errs.DatabaseError, "failed to build query", err)
|
||||
}
|
||||
|
||||
_, err = r.pool.Exec(ctx, sqlQuery, args...)
|
||||
if err != nil {
|
||||
return errs.NewInternalError(errs.DatabaseError, "failed to update request", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *requestRepository) UpdateFinalTZ(ctx context.Context, id uuid.UUID, finalTZ string) error {
|
||||
query := r.qb.Update("requests_for_suppliers").
|
||||
Set("final_update_tz", finalTZ).
|
||||
Where(sq.Eq{"id": id})
|
||||
|
||||
sqlQuery, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return errs.NewInternalError(errs.DatabaseError, "failed to build query", err)
|
||||
}
|
||||
|
||||
_, err = r.pool.Exec(ctx, sqlQuery, args...)
|
||||
if err != nil {
|
||||
return errs.NewInternalError(errs.DatabaseError, "failed to update final TZ", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *requestRepository) GetByUserID(ctx context.Context, userID int) ([]*model.Request, error) {
|
||||
query := r.qb.Select(
|
||||
"r.id", "r.request_txt", "ms.status_name as mailing_status",
|
||||
).From("requests_for_suppliers r").
|
||||
Join("mailing_status ms ON r.mailling_status_id = ms.id").
|
||||
Where(sq.Eq{"r.user_id": userID}).
|
||||
OrderBy("r.created_at DESC")
|
||||
|
||||
sqlQuery, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, errs.NewInternalError(errs.DatabaseError, "failed to build query", err)
|
||||
}
|
||||
|
||||
rows, err := r.pool.Query(ctx, sqlQuery, args...)
|
||||
if err != nil {
|
||||
return nil, errs.NewInternalError(errs.DatabaseError, "failed to get requests", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var requests []*model.Request
|
||||
for rows.Next() {
|
||||
req := &model.Request{}
|
||||
var statusName string
|
||||
err := rows.Scan(&req.ID, &req.RequestTxt, &statusName)
|
||||
if err != nil {
|
||||
return nil, errs.NewInternalError(errs.DatabaseError, "failed to scan request", err)
|
||||
}
|
||||
requests = append(requests, req)
|
||||
}
|
||||
|
||||
return requests, nil
|
||||
}
|
||||
|
||||
func (r *requestRepository) GetByID(ctx context.Context, id uuid.UUID) (*model.Request, error) {
|
||||
query := r.qb.Select(
|
||||
"id", "user_id", "request_txt", "generated_tz", "final_tz",
|
||||
"generated_final_tz", "final_update_tz", "mailling_status_id", "created_at",
|
||||
).From("requests_for_suppliers").Where(sq.Eq{"id": id})
|
||||
|
||||
sqlQuery, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, errs.NewInternalError(errs.DatabaseError, "failed to build query", err)
|
||||
}
|
||||
|
||||
req := &model.Request{}
|
||||
err = r.pool.QueryRow(ctx, sqlQuery, args...).Scan(
|
||||
&req.ID, &req.UserID, &req.RequestTxt, &req.GeneratedTZ,
|
||||
&req.FinalTZ, &req.GeneratedFinalTZ, &req.FinalUpdateTZ,
|
||||
&req.MailingStatusID, &req.CreatedAt,
|
||||
)
|
||||
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, errs.NewBusinessError(errs.RequestNotFound, "request not found")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errs.NewInternalError(errs.DatabaseError, "failed to get request", err)
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (r *requestRepository) GetDetailByID(ctx context.Context, id uuid.UUID) (*model.RequestDetail, error) {
|
||||
sqlQuery := `
|
||||
SELECT
|
||||
r.id AS request_id,
|
||||
r.request_txt AS title,
|
||||
r.final_update_tz AS mail_text,
|
||||
COALESCE(json_agg(
|
||||
json_build_object(
|
||||
'email', COALESCE(s.email, ''),
|
||||
'phone', COALESCE(s.phone, ''),
|
||||
'company_name', COALESCE(s.name, ''),
|
||||
'company_id', s.id,
|
||||
'url', COALESCE(s.url, '')
|
||||
)
|
||||
) FILTER (WHERE s.id IS NOT NULL), '[]') AS suppliers
|
||||
FROM requests_for_suppliers r
|
||||
LEFT JOIN suppliers s ON s.request_id = r.id
|
||||
WHERE r.id = $1
|
||||
GROUP BY r.id, r.request_txt, r.final_update_tz
|
||||
`
|
||||
|
||||
detail := &model.RequestDetail{}
|
||||
var suppliersJSON []byte
|
||||
|
||||
err := r.pool.QueryRow(ctx, sqlQuery, id).Scan(
|
||||
&detail.RequestID, &detail.Title, &detail.MailText, &suppliersJSON,
|
||||
)
|
||||
|
||||
if err == pgx.ErrNoRows {
|
||||
return nil, errs.NewBusinessError(errs.RequestNotFound, "request not found")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errs.NewInternalError(errs.DatabaseError, "failed to get request detail", err)
|
||||
}
|
||||
|
||||
if len(suppliersJSON) > 0 && string(suppliersJSON) != "[]" {
|
||||
if err := json.Unmarshal(suppliersJSON, &detail.Suppliers); err != nil {
|
||||
return nil, errs.NewInternalError(errs.DatabaseError, "failed to parse suppliers", err)
|
||||
}
|
||||
}
|
||||
|
||||
return detail, nil
|
||||
}
|
||||
|
||||
func (r *requestRepository) GetUserStatistics(ctx context.Context, userID int) (requestsCount, suppliersCount, createdTZ int, err error) {
|
||||
sqlQuery := `
|
||||
SELECT
|
||||
COUNT(DISTINCT r.id) AS requests_count,
|
||||
COUNT(s.id) AS suppliers_count,
|
||||
COUNT(r.request_txt) AS created_tz
|
||||
FROM requests_for_suppliers r
|
||||
LEFT JOIN suppliers s ON s.request_id = r.id
|
||||
WHERE r.user_id = $1
|
||||
`
|
||||
|
||||
err = r.pool.QueryRow(ctx, sqlQuery, userID).Scan(&requestsCount, &suppliersCount, &createdTZ)
|
||||
if err != nil {
|
||||
return 0, 0, 0, errs.NewInternalError(errs.DatabaseError, "failed to get statistics", err)
|
||||
}
|
||||
|
||||
return requestsCount, suppliersCount, createdTZ, nil
|
||||
}
|
||||
134
internal/repository/session.go
Normal file
134
internal/repository/session.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"smart-search-back/internal/model"
|
||||
errs "smart-search-back/pkg/errors"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
type sessionRepository struct {
|
||||
pool *pgxpool.Pool
|
||||
qb sq.StatementBuilderType
|
||||
}
|
||||
|
||||
func NewSessionRepository(pool *pgxpool.Pool) SessionRepository {
|
||||
return &sessionRepository{
|
||||
pool: pool,
|
||||
qb: sq.StatementBuilder.PlaceholderFormat(sq.Dollar),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *sessionRepository) Create(ctx context.Context, session *model.Session) error {
|
||||
query := r.qb.Insert("sessions").Columns(
|
||||
"user_id", "access_token", "refresh_token", "ip", "user_agent", "expires_at",
|
||||
).Values(
|
||||
session.UserID, session.AccessToken, session.RefreshToken,
|
||||
session.IP, session.UserAgent, session.ExpiresAt,
|
||||
).Suffix("RETURNING id")
|
||||
|
||||
sqlQuery, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return errs.NewInternalError(errs.DatabaseError, "failed to build query", err)
|
||||
}
|
||||
|
||||
err = r.pool.QueryRow(ctx, sqlQuery, args...).Scan(&session.ID)
|
||||
if err != nil {
|
||||
return errs.NewInternalError(errs.DatabaseError, "failed to create session", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *sessionRepository) FindByRefreshToken(ctx context.Context, token string) (*model.Session, error) {
|
||||
query := r.qb.Select(
|
||||
"id", "user_id", "access_token", "refresh_token", "ip",
|
||||
"user_agent", "created_at", "expires_at", "revoked_at",
|
||||
).From("sessions").Where(sq.And{
|
||||
sq.Eq{"refresh_token": token},
|
||||
sq.Expr("revoked_at IS NULL"),
|
||||
sq.Expr("expires_at > now()"),
|
||||
})
|
||||
|
||||
sqlQuery, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, errs.NewInternalError(errs.DatabaseError, "failed to build query", err)
|
||||
}
|
||||
|
||||
session := &model.Session{}
|
||||
err = r.pool.QueryRow(ctx, sqlQuery, args...).Scan(
|
||||
&session.ID, &session.UserID, &session.AccessToken, &session.RefreshToken,
|
||||
&session.IP, &session.UserAgent, &session.CreatedAt, &session.ExpiresAt,
|
||||
&session.RevokedAt,
|
||||
)
|
||||
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, errs.NewBusinessError(errs.RefreshInvalid, "refresh token is invalid or expired")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errs.NewInternalError(errs.DatabaseError, "failed to find session", err)
|
||||
}
|
||||
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func (r *sessionRepository) UpdateAccessToken(ctx context.Context, refreshToken, newAccessToken string) error {
|
||||
query := r.qb.Update("sessions").
|
||||
Set("access_token", newAccessToken).
|
||||
Where(sq.Eq{"refresh_token": refreshToken})
|
||||
|
||||
sqlQuery, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return errs.NewInternalError(errs.DatabaseError, "failed to build query", err)
|
||||
}
|
||||
|
||||
_, err = r.pool.Exec(ctx, sqlQuery, args...)
|
||||
if err != nil {
|
||||
return errs.NewInternalError(errs.DatabaseError, "failed to update access token", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *sessionRepository) Revoke(ctx context.Context, refreshToken string) error {
|
||||
query := r.qb.Update("sessions").
|
||||
Set("revoked_at", time.Now()).
|
||||
Where(sq.Eq{"refresh_token": refreshToken})
|
||||
|
||||
sqlQuery, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return errs.NewInternalError(errs.DatabaseError, "failed to build query", err)
|
||||
}
|
||||
|
||||
_, err = r.pool.Exec(ctx, sqlQuery, args...)
|
||||
if err != nil {
|
||||
return errs.NewInternalError(errs.DatabaseError, "failed to revoke session", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *sessionRepository) DeleteExpired(ctx context.Context) (int, error) {
|
||||
query := r.qb.Delete("sessions").Where(sq.Or{
|
||||
sq.Expr("expires_at < now()"),
|
||||
sq.Expr("(revoked_at IS NOT NULL AND revoked_at < now() - interval '30 days')"),
|
||||
})
|
||||
|
||||
sqlQuery, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return 0, errs.NewInternalError(errs.DatabaseError, "failed to build query", err)
|
||||
}
|
||||
|
||||
result, err := r.pool.Exec(ctx, sqlQuery, args...)
|
||||
if err != nil {
|
||||
return 0, errs.NewInternalError(errs.DatabaseError, "failed to delete expired sessions", err)
|
||||
}
|
||||
|
||||
return int(result.RowsAffected()), nil
|
||||
}
|
||||
97
internal/repository/supplier.go
Normal file
97
internal/repository/supplier.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"smart-search-back/internal/model"
|
||||
errs "smart-search-back/pkg/errors"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
type supplierRepository struct {
|
||||
pool *pgxpool.Pool
|
||||
qb sq.StatementBuilderType
|
||||
}
|
||||
|
||||
func NewSupplierRepository(pool *pgxpool.Pool) SupplierRepository {
|
||||
return &supplierRepository{
|
||||
pool: pool,
|
||||
qb: sq.StatementBuilder.PlaceholderFormat(sq.Dollar),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *supplierRepository) BulkInsert(ctx context.Context, requestID uuid.UUID, suppliers []*model.Supplier) error {
|
||||
if len(suppliers) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
query := r.qb.Insert("suppliers").Columns(
|
||||
"request_id", "name", "email", "phone", "adress", "url",
|
||||
)
|
||||
|
||||
for _, s := range suppliers {
|
||||
query = query.Values(requestID, s.Name, s.Email, s.Phone, s.Address, s.URL)
|
||||
}
|
||||
|
||||
sqlQuery, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return errs.NewInternalError(errs.DatabaseError, "failed to build query", err)
|
||||
}
|
||||
|
||||
_, err = r.pool.Exec(ctx, sqlQuery, args...)
|
||||
if err != nil {
|
||||
return errs.NewInternalError(errs.DatabaseError, "failed to bulk insert suppliers", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *supplierRepository) GetByRequestID(ctx context.Context, requestID uuid.UUID) ([]*model.Supplier, error) {
|
||||
query := r.qb.Select(
|
||||
"id", "request_id", "name", "email", "phone", "adress", "url", "created_at",
|
||||
).From("suppliers").Where(sq.Eq{"request_id": requestID}).OrderBy("id")
|
||||
|
||||
sqlQuery, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, errs.NewInternalError(errs.DatabaseError, "failed to build query", err)
|
||||
}
|
||||
|
||||
rows, err := r.pool.Query(ctx, sqlQuery, args...)
|
||||
if err != nil {
|
||||
return nil, errs.NewInternalError(errs.DatabaseError, "failed to get suppliers", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var suppliers []*model.Supplier
|
||||
for rows.Next() {
|
||||
s := &model.Supplier{}
|
||||
err := rows.Scan(
|
||||
&s.ID, &s.RequestID, &s.Name, &s.Email, &s.Phone, &s.Address, &s.URL, &s.CreatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errs.NewInternalError(errs.DatabaseError, "failed to scan supplier", err)
|
||||
}
|
||||
suppliers = append(suppliers, s)
|
||||
}
|
||||
|
||||
return suppliers, nil
|
||||
}
|
||||
|
||||
func (r *supplierRepository) DeleteByRequestID(ctx context.Context, requestID uuid.UUID) error {
|
||||
query := r.qb.Delete("suppliers").Where(sq.Eq{"request_id": requestID})
|
||||
|
||||
sqlQuery, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return errs.NewInternalError(errs.DatabaseError, "failed to build query", err)
|
||||
}
|
||||
|
||||
_, err = r.pool.Exec(ctx, sqlQuery, args...)
|
||||
if err != nil {
|
||||
return errs.NewInternalError(errs.DatabaseError, "failed to delete suppliers", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
43
internal/repository/token_usage.go
Normal file
43
internal/repository/token_usage.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"smart-search-back/internal/model"
|
||||
errs "smart-search-back/pkg/errors"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
type tokenUsageRepository struct {
|
||||
pool *pgxpool.Pool
|
||||
qb sq.StatementBuilderType
|
||||
}
|
||||
|
||||
func NewTokenUsageRepository(pool *pgxpool.Pool) TokenUsageRepository {
|
||||
return &tokenUsageRepository{
|
||||
pool: pool,
|
||||
qb: sq.StatementBuilder.PlaceholderFormat(sq.Dollar),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *tokenUsageRepository) Create(ctx context.Context, usage *model.TokenUsage) error {
|
||||
query := r.qb.Insert("request_token_usage").Columns(
|
||||
"request_id", "request_token_count", "response_token_count", "token_cost", "type",
|
||||
).Values(
|
||||
usage.RequestID, usage.RequestTokenCount, usage.ResponseTokenCount, usage.TokenCost, usage.Type,
|
||||
).Suffix("RETURNING id, created_at")
|
||||
|
||||
sqlQuery, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return errs.NewInternalError(errs.DatabaseError, "failed to build query", err)
|
||||
}
|
||||
|
||||
err = r.pool.QueryRow(ctx, sqlQuery, args...).Scan(&usage.ID, &usage.CreatedAt)
|
||||
if err != nil {
|
||||
return errs.NewInternalError(errs.DatabaseError, "failed to create token usage", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
199
internal/repository/user.go
Normal file
199
internal/repository/user.go
Normal file
@@ -0,0 +1,199 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"smart-search-back/internal/model"
|
||||
"smart-search-back/pkg/crypto"
|
||||
errs "smart-search-back/pkg/errors"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
type userRepository struct {
|
||||
pool *pgxpool.Pool
|
||||
qb sq.StatementBuilderType
|
||||
cryptoHelper *crypto.Crypto
|
||||
}
|
||||
|
||||
func NewUserRepository(pool *pgxpool.Pool, cryptoSecret string) UserRepository {
|
||||
return &userRepository{
|
||||
pool: pool,
|
||||
qb: sq.StatementBuilder.PlaceholderFormat(sq.Dollar),
|
||||
cryptoHelper: crypto.NewCrypto(cryptoSecret),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *userRepository) FindByEmailHash(ctx context.Context, emailHash string) (*model.User, error) {
|
||||
query := r.qb.Select(
|
||||
"id", "email", "email_hash", "password_hash", "phone",
|
||||
"user_name", "company_name", "balance", "payment_status",
|
||||
"invites_issued", "invites_limit", "created_at",
|
||||
).From("users").Where(sq.Eq{"email_hash": emailHash})
|
||||
|
||||
sqlQuery, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, errs.NewInternalError(errs.DatabaseError, "failed to build query", err)
|
||||
}
|
||||
|
||||
user := &model.User{}
|
||||
err = r.pool.QueryRow(ctx, sqlQuery, args...).Scan(
|
||||
&user.ID, &user.Email, &user.EmailHash, &user.PasswordHash,
|
||||
&user.Phone, &user.UserName, &user.CompanyName, &user.Balance,
|
||||
&user.PaymentStatus, &user.InvitesIssued, &user.InvitesLimit, &user.CreatedAt,
|
||||
)
|
||||
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, errs.NewBusinessError(errs.UserNotFound, "user not found")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errs.NewInternalError(errs.DatabaseError, "failed to find user", err)
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (r *userRepository) FindByID(ctx context.Context, userID int) (*model.User, error) {
|
||||
query := r.qb.Select(
|
||||
"id", "email", "email_hash", "password_hash", "phone",
|
||||
"user_name", "company_name", "balance", "payment_status",
|
||||
"invites_issued", "invites_limit", "created_at",
|
||||
).From("users").Where(sq.Eq{"id": userID})
|
||||
|
||||
sqlQuery, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, errs.NewInternalError(errs.DatabaseError, "failed to build query", err)
|
||||
}
|
||||
|
||||
user := &model.User{}
|
||||
err = r.pool.QueryRow(ctx, sqlQuery, args...).Scan(
|
||||
&user.ID, &user.Email, &user.EmailHash, &user.PasswordHash,
|
||||
&user.Phone, &user.UserName, &user.CompanyName, &user.Balance,
|
||||
&user.PaymentStatus, &user.InvitesIssued, &user.InvitesLimit, &user.CreatedAt,
|
||||
)
|
||||
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, errs.NewBusinessError(errs.UserNotFound, "user not found")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errs.NewInternalError(errs.DatabaseError, "failed to find user", err)
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (r *userRepository) Create(ctx context.Context, user *model.User) error {
|
||||
encryptedEmail, err := r.cryptoHelper.Encrypt(user.Email)
|
||||
if err != nil {
|
||||
return errs.NewInternalError(errs.EncryptionError, "failed to encrypt email", err)
|
||||
}
|
||||
|
||||
encryptedPhone, err := r.cryptoHelper.Encrypt(user.Phone)
|
||||
if err != nil {
|
||||
return errs.NewInternalError(errs.EncryptionError, "failed to encrypt phone", err)
|
||||
}
|
||||
|
||||
encryptedUserName, err := r.cryptoHelper.Encrypt(user.UserName)
|
||||
if err != nil {
|
||||
return errs.NewInternalError(errs.EncryptionError, "failed to encrypt user name", err)
|
||||
}
|
||||
|
||||
query := r.qb.Insert("users").Columns(
|
||||
"email", "email_hash", "password_hash", "phone", "user_name",
|
||||
"company_name", "balance", "payment_status",
|
||||
).Values(
|
||||
encryptedEmail, user.EmailHash, user.PasswordHash, encryptedPhone,
|
||||
encryptedUserName, user.CompanyName, user.Balance, user.PaymentStatus,
|
||||
).Suffix("RETURNING id")
|
||||
|
||||
sqlQuery, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return errs.NewInternalError(errs.DatabaseError, "failed to build query", err)
|
||||
}
|
||||
|
||||
err = r.pool.QueryRow(ctx, sqlQuery, args...).Scan(&user.ID)
|
||||
if err != nil {
|
||||
return errs.NewInternalError(errs.DatabaseError, "failed to create user", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *userRepository) UpdateBalance(ctx context.Context, userID int, delta float64) error {
|
||||
query := r.qb.Update("users").
|
||||
Set("balance", sq.Expr("balance + ?", delta)).
|
||||
Where(sq.Eq{"id": userID})
|
||||
|
||||
sqlQuery, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return errs.NewInternalError(errs.DatabaseError, "failed to build query", err)
|
||||
}
|
||||
|
||||
_, err = r.pool.Exec(ctx, sqlQuery, args...)
|
||||
if err != nil {
|
||||
return errs.NewInternalError(errs.DatabaseError, "failed to update balance", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *userRepository) GetBalance(ctx context.Context, userID int) (float64, error) {
|
||||
query := r.qb.Select("balance").From("users").Where(sq.Eq{"id": userID})
|
||||
|
||||
sqlQuery, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return 0, errs.NewInternalError(errs.DatabaseError, "failed to build query", err)
|
||||
}
|
||||
|
||||
var balance float64
|
||||
err = r.pool.QueryRow(ctx, sqlQuery, args...).Scan(&balance)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return 0, errs.NewBusinessError(errs.UserNotFound, "user not found")
|
||||
}
|
||||
if err != nil {
|
||||
return 0, errs.NewInternalError(errs.DatabaseError, "failed to get balance", err)
|
||||
}
|
||||
|
||||
return balance, nil
|
||||
}
|
||||
|
||||
func (r *userRepository) IncrementInvitesIssued(ctx context.Context, userID int) error {
|
||||
query := r.qb.Update("users").
|
||||
Set("invites_issued", sq.Expr("invites_issued + 1")).
|
||||
Where(sq.Eq{"id": userID})
|
||||
|
||||
sqlQuery, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return errs.NewInternalError(errs.DatabaseError, "failed to build query", err)
|
||||
}
|
||||
|
||||
_, err = r.pool.Exec(ctx, sqlQuery, args...)
|
||||
if err != nil {
|
||||
return errs.NewInternalError(errs.DatabaseError, "failed to increment invites issued", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *userRepository) CheckInviteLimit(ctx context.Context, userID int) (bool, error) {
|
||||
query := r.qb.Select("invites_issued", "invites_limit").
|
||||
From("users").
|
||||
Where(sq.Eq{"id": userID}).
|
||||
Suffix("FOR UPDATE")
|
||||
|
||||
sqlQuery, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return false, errs.NewInternalError(errs.DatabaseError, "failed to build query", err)
|
||||
}
|
||||
|
||||
var issued, limit int
|
||||
err = r.pool.QueryRow(ctx, sqlQuery, args...).Scan(&issued, &limit)
|
||||
if err != nil {
|
||||
return false, errs.NewInternalError(errs.DatabaseError, "failed to check invite limit", err)
|
||||
}
|
||||
|
||||
return issued < limit, nil
|
||||
}
|
||||
107
internal/service/auth.go
Normal file
107
internal/service/auth.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"smart-search-back/internal/model"
|
||||
"smart-search-back/internal/repository"
|
||||
"smart-search-back/pkg/crypto"
|
||||
"smart-search-back/pkg/errors"
|
||||
"smart-search-back/pkg/jwt"
|
||||
)
|
||||
|
||||
type authService struct {
|
||||
userRepo repository.UserRepository
|
||||
sessionRepo repository.SessionRepository
|
||||
jwtSecret string
|
||||
cryptoHelper *crypto.Crypto
|
||||
}
|
||||
|
||||
func NewAuthService(userRepo repository.UserRepository, sessionRepo repository.SessionRepository, jwtSecret, cryptoSecret string) AuthService {
|
||||
return &authService{
|
||||
userRepo: userRepo,
|
||||
sessionRepo: sessionRepo,
|
||||
jwtSecret: jwtSecret,
|
||||
cryptoHelper: crypto.NewCrypto(cryptoSecret),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *authService) Login(ctx context.Context, email, password, ip, userAgent string) (accessToken, refreshToken string, err error) {
|
||||
emailHash := s.cryptoHelper.EmailHash(email)
|
||||
|
||||
user, err := s.userRepo.FindByEmailHash(ctx, emailHash)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
passwordHash := crypto.PasswordHash(password)
|
||||
if user.PasswordHash != passwordHash {
|
||||
return "", "", errors.NewBusinessError(errors.AuthInvalidCredentials, "Invalid email or password")
|
||||
}
|
||||
|
||||
accessToken, err = jwt.GenerateAccessToken(user.ID, s.jwtSecret)
|
||||
if err != nil {
|
||||
return "", "", errors.NewInternalError(errors.InternalError, "failed to generate access token", err)
|
||||
}
|
||||
|
||||
refreshToken, err = jwt.GenerateRefreshToken(user.ID, s.jwtSecret)
|
||||
if err != nil {
|
||||
return "", "", errors.NewInternalError(errors.InternalError, "failed to generate refresh token", err)
|
||||
}
|
||||
|
||||
session := &model.Session{
|
||||
UserID: user.ID,
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: refreshToken,
|
||||
IP: ip,
|
||||
UserAgent: userAgent,
|
||||
ExpiresAt: time.Now().Add(30 * 24 * time.Hour),
|
||||
}
|
||||
|
||||
if err := s.sessionRepo.Create(ctx, session); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return accessToken, refreshToken, nil
|
||||
}
|
||||
|
||||
func (s *authService) Refresh(ctx context.Context, refreshToken string) (string, error) {
|
||||
session, err := s.sessionRepo.FindByRefreshToken(ctx, refreshToken)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
newAccessToken, err := jwt.GenerateAccessToken(session.UserID, s.jwtSecret)
|
||||
if err != nil {
|
||||
return "", errors.NewInternalError(errors.InternalError, "failed to generate access token", err)
|
||||
}
|
||||
|
||||
if err := s.sessionRepo.UpdateAccessToken(ctx, refreshToken, newAccessToken); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return newAccessToken, nil
|
||||
}
|
||||
|
||||
func (s *authService) Validate(ctx context.Context, accessToken string) (int, error) {
|
||||
claims, err := jwt.ValidateToken(accessToken, s.jwtSecret)
|
||||
if err != nil {
|
||||
return 0, errors.NewBusinessError(errors.AuthInvalidToken, "Invalid or expired token")
|
||||
}
|
||||
|
||||
if claims.Type != "access" {
|
||||
return 0, errors.NewBusinessError(errors.AuthInvalidToken, "Token is not an access token")
|
||||
}
|
||||
|
||||
userID, err := jwt.GetUserIDFromToken(accessToken, s.jwtSecret)
|
||||
if err != nil {
|
||||
return 0, errors.NewBusinessError(errors.AuthInvalidToken, "Invalid user ID in token")
|
||||
}
|
||||
|
||||
return userID, nil
|
||||
}
|
||||
|
||||
func (s *authService) Logout(ctx context.Context, refreshToken string) error {
|
||||
return s.sessionRepo.Revoke(ctx, refreshToken)
|
||||
}
|
||||
38
internal/service/interfaces.go
Normal file
38
internal/service/interfaces.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"smart-search-back/internal/model"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type AuthService interface {
|
||||
Login(ctx context.Context, email, password, ip, userAgent string) (accessToken, refreshToken string, err error)
|
||||
Refresh(ctx context.Context, refreshToken string) (string, error)
|
||||
Validate(ctx context.Context, accessToken string) (int, error)
|
||||
Logout(ctx context.Context, refreshToken string) error
|
||||
}
|
||||
|
||||
type UserService interface {
|
||||
GetInfo(ctx context.Context, userID int) (*UserInfo, error)
|
||||
GetBalance(ctx context.Context, userID int) (float64, error)
|
||||
GetStatistics(ctx context.Context, userID int) (*Statistics, error)
|
||||
}
|
||||
|
||||
type InviteService interface {
|
||||
Generate(ctx context.Context, userID, maxUses, ttlDays int) (*model.InviteCode, error)
|
||||
GetInfo(ctx context.Context, code int64) (*model.InviteCode, error)
|
||||
}
|
||||
|
||||
type RequestService interface {
|
||||
CreateTZ(ctx context.Context, userID int, requestTxt string) (uuid.UUID, string, error)
|
||||
ApproveTZ(ctx context.Context, requestID uuid.UUID, tzText string, userID int) ([]*model.Supplier, error)
|
||||
GetMailingList(ctx context.Context, userID int) ([]*model.Request, error)
|
||||
GetMailingListByID(ctx context.Context, requestID uuid.UUID) (*model.RequestDetail, error)
|
||||
}
|
||||
|
||||
type SupplierService interface {
|
||||
ExportExcel(ctx context.Context, requestID uuid.UUID) ([]byte, error)
|
||||
}
|
||||
57
internal/service/invite.go
Normal file
57
internal/service/invite.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"smart-search-back/internal/model"
|
||||
"smart-search-back/internal/repository"
|
||||
"smart-search-back/pkg/errors"
|
||||
)
|
||||
|
||||
type inviteService struct {
|
||||
inviteRepo repository.InviteRepository
|
||||
userRepo repository.UserRepository
|
||||
}
|
||||
|
||||
func NewInviteService(inviteRepo repository.InviteRepository, userRepo repository.UserRepository) InviteService {
|
||||
return &inviteService{
|
||||
inviteRepo: inviteRepo,
|
||||
userRepo: userRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *inviteService) Generate(ctx context.Context, userID, maxUses, ttlDays int) (*model.InviteCode, error) {
|
||||
canIssue, err := s.userRepo.CheckInviteLimit(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !canIssue {
|
||||
return nil, errors.NewBusinessError(errors.InviteLimitReached, "User reached maximum invite codes limit")
|
||||
}
|
||||
|
||||
code := rand.Int63n(90000000) + 10000000
|
||||
|
||||
invite := &model.InviteCode{
|
||||
UserID: userID,
|
||||
Code: code,
|
||||
CanBeUsedCount: maxUses,
|
||||
ExpiresAt: time.Now().Add(time.Duration(ttlDays) * 24 * time.Hour),
|
||||
}
|
||||
|
||||
if err := s.inviteRepo.Create(ctx, invite); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.userRepo.IncrementInvitesIssued(ctx, userID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return invite, nil
|
||||
}
|
||||
|
||||
func (s *inviteService) GetInfo(ctx context.Context, code int64) (*model.InviteCode, error) {
|
||||
return s.inviteRepo.FindByCode(ctx, code)
|
||||
}
|
||||
154
internal/service/request.go
Normal file
154
internal/service/request.go
Normal file
@@ -0,0 +1,154 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"smart-search-back/internal/ai"
|
||||
"smart-search-back/internal/model"
|
||||
"smart-search-back/internal/repository"
|
||||
"smart-search-back/pkg/errors"
|
||||
)
|
||||
|
||||
type requestService struct {
|
||||
requestRepo repository.RequestRepository
|
||||
supplierRepo repository.SupplierRepository
|
||||
tokenUsageRepo repository.TokenUsageRepository
|
||||
userRepo repository.UserRepository
|
||||
openAI *ai.OpenAIClient
|
||||
perplexity *ai.PerplexityClient
|
||||
}
|
||||
|
||||
func NewRequestService(
|
||||
requestRepo repository.RequestRepository,
|
||||
supplierRepo repository.SupplierRepository,
|
||||
tokenUsageRepo repository.TokenUsageRepository,
|
||||
userRepo repository.UserRepository,
|
||||
openAI *ai.OpenAIClient,
|
||||
perplexity *ai.PerplexityClient,
|
||||
) RequestService {
|
||||
return &requestService{
|
||||
requestRepo: requestRepo,
|
||||
supplierRepo: supplierRepo,
|
||||
tokenUsageRepo: tokenUsageRepo,
|
||||
userRepo: userRepo,
|
||||
openAI: openAI,
|
||||
perplexity: perplexity,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *requestService) CreateTZ(ctx context.Context, userID int, requestTxt string) (uuid.UUID, string, error) {
|
||||
req := &model.Request{
|
||||
UserID: userID,
|
||||
RequestTxt: requestTxt,
|
||||
}
|
||||
|
||||
if err := s.requestRepo.Create(ctx, req); err != nil {
|
||||
return uuid.Nil, "", err
|
||||
}
|
||||
|
||||
if requestTxt == "" {
|
||||
return req.ID, "", nil
|
||||
}
|
||||
|
||||
tzText, err := s.openAI.GenerateTZ(requestTxt)
|
||||
if err != nil {
|
||||
if err := s.requestRepo.UpdateWithTZ(ctx, req.ID, "", false); err != nil {
|
||||
return req.ID, "", err
|
||||
}
|
||||
return req.ID, "", err
|
||||
}
|
||||
|
||||
inputLen := len(requestTxt)
|
||||
outputLen := len(tzText)
|
||||
promptTokens := 500
|
||||
|
||||
inputTokens := int(math.Ceil(float64(inputLen) / 2.0))
|
||||
outputTokens := int(math.Ceil(float64(outputLen) / 2.0))
|
||||
|
||||
totalTokens := inputTokens + outputTokens + promptTokens
|
||||
tokenPrice := 25000.0 / 1000000.0
|
||||
cost := float64(totalTokens) * tokenPrice
|
||||
|
||||
tokenUsage := &model.TokenUsage{
|
||||
RequestID: req.ID,
|
||||
RequestTokenCount: inputTokens + promptTokens,
|
||||
ResponseTokenCount: outputTokens,
|
||||
TokenCost: cost,
|
||||
Type: "tz",
|
||||
}
|
||||
|
||||
if err := s.tokenUsageRepo.Create(ctx, tokenUsage); err != nil {
|
||||
return req.ID, "", err
|
||||
}
|
||||
|
||||
if err := s.userRepo.UpdateBalance(ctx, userID, -cost); err != nil {
|
||||
return req.ID, "", err
|
||||
}
|
||||
|
||||
if err := s.requestRepo.UpdateWithTZ(ctx, req.ID, tzText, true); err != nil {
|
||||
return req.ID, "", err
|
||||
}
|
||||
|
||||
return req.ID, tzText, nil
|
||||
}
|
||||
|
||||
func (s *requestService) ApproveTZ(ctx context.Context, requestID uuid.UUID, tzText string, userID int) ([]*model.Supplier, error) {
|
||||
if err := s.requestRepo.UpdateFinalTZ(ctx, requestID, tzText); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var suppliers []*model.Supplier
|
||||
var promptTokens, responseTokens int
|
||||
var err error
|
||||
|
||||
for attempt := 0; attempt < 3; attempt++ {
|
||||
suppliers, promptTokens, responseTokens, err = s.perplexity.FindSuppliers(tzText)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(suppliers) == 0 {
|
||||
return nil, errors.NewInternalError(errors.AIAPIError, "no suppliers found", nil)
|
||||
}
|
||||
|
||||
if err := s.supplierRepo.BulkInsert(ctx, requestID, suppliers); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tokenPrice := 25000.0 / 1000000.0
|
||||
totalTokens := promptTokens + responseTokens
|
||||
cost := float64(totalTokens) * tokenPrice
|
||||
|
||||
tokenUsage := &model.TokenUsage{
|
||||
RequestID: requestID,
|
||||
RequestTokenCount: promptTokens,
|
||||
ResponseTokenCount: responseTokens,
|
||||
TokenCost: cost,
|
||||
Type: "suppliers",
|
||||
}
|
||||
|
||||
if err := s.tokenUsageRepo.Create(ctx, tokenUsage); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.userRepo.UpdateBalance(ctx, userID, -cost); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return suppliers, nil
|
||||
}
|
||||
|
||||
func (s *requestService) GetMailingList(ctx context.Context, userID int) ([]*model.Request, error) {
|
||||
return s.requestRepo.GetByUserID(ctx, userID)
|
||||
}
|
||||
|
||||
func (s *requestService) GetMailingListByID(ctx context.Context, requestID uuid.UUID) (*model.RequestDetail, error) {
|
||||
return s.requestRepo.GetDetailByID(ctx, requestID)
|
||||
}
|
||||
75
internal/service/supplier.go
Normal file
75
internal/service/supplier.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/xuri/excelize/v2"
|
||||
"smart-search-back/internal/repository"
|
||||
)
|
||||
|
||||
type supplierService struct {
|
||||
supplierRepo repository.SupplierRepository
|
||||
}
|
||||
|
||||
func NewSupplierService(supplierRepo repository.SupplierRepository) SupplierService {
|
||||
return &supplierService{
|
||||
supplierRepo: supplierRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *supplierService) ExportExcel(ctx context.Context, requestID uuid.UUID) ([]byte, error) {
|
||||
suppliers, err := s.supplierRepo.GetByRequestID(ctx, requestID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f := excelize.NewFile()
|
||||
defer f.Close()
|
||||
|
||||
sheetName := "Suppliers"
|
||||
index, err := f.NewSheet(sheetName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
headers := []string{"Company ID", "Email", "Phone", "Company Name", "URL"}
|
||||
for i, header := range headers {
|
||||
cell := fmt.Sprintf("%c1", 'A'+i)
|
||||
f.SetCellValue(sheetName, cell, header)
|
||||
}
|
||||
|
||||
style, err := f.NewStyle(&excelize.Style{
|
||||
Font: &excelize.Font{Bold: true},
|
||||
Fill: excelize.Fill{Type: "pattern", Color: []string{"#E0E0E0"}, Pattern: 1},
|
||||
})
|
||||
if err == nil {
|
||||
f.SetCellStyle(sheetName, "A1", fmt.Sprintf("%c1", 'A'+len(headers)-1), style)
|
||||
}
|
||||
|
||||
for i, supplier := range suppliers {
|
||||
row := i + 2
|
||||
f.SetCellValue(sheetName, fmt.Sprintf("A%d", row), supplier.ID)
|
||||
f.SetCellValue(sheetName, fmt.Sprintf("B%d", row), supplier.Email)
|
||||
f.SetCellValue(sheetName, fmt.Sprintf("C%d", row), supplier.Phone)
|
||||
f.SetCellValue(sheetName, fmt.Sprintf("D%d", row), supplier.Name)
|
||||
f.SetCellValue(sheetName, fmt.Sprintf("E%d", row), supplier.URL)
|
||||
}
|
||||
|
||||
f.SetColWidth(sheetName, "A", "A", 12)
|
||||
f.SetColWidth(sheetName, "B", "B", 30)
|
||||
f.SetColWidth(sheetName, "C", "C", 20)
|
||||
f.SetColWidth(sheetName, "D", "D", 40)
|
||||
f.SetColWidth(sheetName, "E", "E", 40)
|
||||
|
||||
f.SetActiveSheet(index)
|
||||
f.DeleteSheet("Sheet1")
|
||||
|
||||
buffer, err := f.WriteToBuffer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
449
internal/service/tests/auth_suite_test.go
Normal file
449
internal/service/tests/auth_suite_test.go
Normal file
@@ -0,0 +1,449 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gojuno/minimock/v3"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"smart-search-back/internal/mocks"
|
||||
"smart-search-back/internal/model"
|
||||
"smart-search-back/internal/service"
|
||||
"smart-search-back/pkg/crypto"
|
||||
apperrors "smart-search-back/pkg/errors"
|
||||
"smart-search-back/pkg/jwt"
|
||||
)
|
||||
|
||||
type Suite struct {
|
||||
suite.Suite
|
||||
|
||||
ctx context.Context
|
||||
authService service.AuthService
|
||||
userRepo *mocks.UserRepositoryMock
|
||||
sessionRepo *mocks.SessionRepositoryMock
|
||||
}
|
||||
|
||||
func newSuite(ctx context.Context) *Suite {
|
||||
return &Suite{ctx: ctx}
|
||||
}
|
||||
|
||||
func TestAuthService(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
suite.Run(t, newSuite(ctx))
|
||||
}
|
||||
|
||||
func (s *Suite) SetupSuite() {
|
||||
s.ctx = context.Background()
|
||||
}
|
||||
|
||||
func (s *Suite) SetupTest() {
|
||||
ctrl := minimock.NewController(s.T())
|
||||
|
||||
s.userRepo = mocks.NewUserRepositoryMock(ctrl)
|
||||
s.sessionRepo = mocks.NewSessionRepositoryMock(ctrl)
|
||||
|
||||
s.authService = service.NewAuthService(s.userRepo, s.sessionRepo)
|
||||
}
|
||||
|
||||
func createTestUser(password string) *model.User {
|
||||
return &model.User{
|
||||
ID: 1,
|
||||
Email: "test@example.com",
|
||||
EmailHash: crypto.EmailHash("test@example.com"),
|
||||
PasswordHash: crypto.PasswordHash(password),
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
func createTestSession(userID int) *model.Session {
|
||||
return &model.Session{
|
||||
ID: 1,
|
||||
UserID: userID,
|
||||
AccessToken: "test-access-token",
|
||||
RefreshToken: "test-refresh-token",
|
||||
IP: "127.0.0.1",
|
||||
UserAgent: "test-agent",
|
||||
CreatedAt: time.Now(),
|
||||
ExpiresAt: time.Now().Add(30 * 24 * time.Hour),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Login_Success() {
|
||||
password := "testpassword"
|
||||
user := createTestUser(password)
|
||||
|
||||
s.userRepo.FindByEmailHashMock.Return(user, nil)
|
||||
s.sessionRepo.CreateMock.Return(nil)
|
||||
|
||||
accessToken, refreshToken, err := s.authService.Login(
|
||||
s.ctx,
|
||||
"test@example.com",
|
||||
password,
|
||||
"127.0.0.1",
|
||||
"test-agent",
|
||||
)
|
||||
|
||||
s.NoError(err)
|
||||
s.NotEmpty(accessToken)
|
||||
s.NotEmpty(refreshToken)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Login_UserNotFound() {
|
||||
err := apperrors.NewBusinessError(apperrors.UserNotFound, "user not found")
|
||||
s.userRepo.FindByEmailHashMock.Return(nil, err)
|
||||
|
||||
accessToken, refreshToken, loginErr := s.authService.Login(
|
||||
s.ctx,
|
||||
"test@example.com",
|
||||
"password",
|
||||
"127.0.0.1",
|
||||
"test-agent",
|
||||
)
|
||||
|
||||
s.Error(loginErr)
|
||||
s.Empty(accessToken)
|
||||
s.Empty(refreshToken)
|
||||
|
||||
var appErr *apperrors.AppError
|
||||
s.True(errors.As(loginErr, &appErr))
|
||||
s.Equal(apperrors.UserNotFound, appErr.Code)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Login_DatabaseError_OnFindUser() {
|
||||
dbErr := apperrors.NewInternalError(apperrors.DatabaseError, "failed to find user", nil)
|
||||
s.userRepo.FindByEmailHashMock.Return(nil, dbErr)
|
||||
|
||||
accessToken, refreshToken, err := s.authService.Login(
|
||||
s.ctx,
|
||||
"test@example.com",
|
||||
"password",
|
||||
"127.0.0.1",
|
||||
"test-agent",
|
||||
)
|
||||
|
||||
s.Error(err)
|
||||
s.Empty(accessToken)
|
||||
s.Empty(refreshToken)
|
||||
|
||||
var appErr *apperrors.AppError
|
||||
s.True(errors.As(err, &appErr))
|
||||
s.Equal(apperrors.InternalErrorType, appErr.Type)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Login_InvalidPassword() {
|
||||
password := "correctpassword"
|
||||
user := createTestUser(password)
|
||||
s.userRepo.FindByEmailHashMock.Return(user, nil)
|
||||
|
||||
accessToken, refreshToken, err := s.authService.Login(
|
||||
s.ctx,
|
||||
"test@example.com",
|
||||
"wrongpassword",
|
||||
"127.0.0.1",
|
||||
"test-agent",
|
||||
)
|
||||
|
||||
s.Error(err)
|
||||
s.Empty(accessToken)
|
||||
s.Empty(refreshToken)
|
||||
|
||||
var appErr *apperrors.AppError
|
||||
s.True(errors.As(err, &appErr))
|
||||
s.Equal(apperrors.AuthInvalidCredentials, appErr.Code)
|
||||
s.Contains(appErr.Message, "Invalid email or password")
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Login_EmptyPassword() {
|
||||
user := createTestUser("")
|
||||
s.userRepo.FindByEmailHashMock.Return(user, nil)
|
||||
s.sessionRepo.CreateMock.Return(nil)
|
||||
|
||||
accessToken, refreshToken, err := s.authService.Login(
|
||||
s.ctx,
|
||||
"test@example.com",
|
||||
"",
|
||||
"127.0.0.1",
|
||||
"test-agent",
|
||||
)
|
||||
|
||||
s.NoError(err)
|
||||
s.NotEmpty(accessToken)
|
||||
s.NotEmpty(refreshToken)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Login_EmailWithSpacesAndCase() {
|
||||
password := "testpassword"
|
||||
normalizedEmail := "test@example.com"
|
||||
user := createTestUser(password)
|
||||
user.EmailHash = crypto.EmailHash(normalizedEmail)
|
||||
s.userRepo.FindByEmailHashMock.Return(user, nil)
|
||||
s.sessionRepo.CreateMock.Return(nil)
|
||||
|
||||
accessToken, refreshToken, err := s.authService.Login(
|
||||
s.ctx,
|
||||
" TEST@EXAMPLE.COM ",
|
||||
password,
|
||||
"127.0.0.1",
|
||||
"test-agent",
|
||||
)
|
||||
|
||||
s.NoError(err)
|
||||
s.NotEmpty(accessToken)
|
||||
s.NotEmpty(refreshToken)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Login_EmptyEmail() {
|
||||
err := apperrors.NewBusinessError(apperrors.UserNotFound, "user not found")
|
||||
s.userRepo.FindByEmailHashMock.Return(nil, err)
|
||||
|
||||
accessToken, refreshToken, loginErr := s.authService.Login(
|
||||
s.ctx,
|
||||
"",
|
||||
"password",
|
||||
"127.0.0.1",
|
||||
"test-agent",
|
||||
)
|
||||
|
||||
s.Error(loginErr)
|
||||
s.Empty(accessToken)
|
||||
s.Empty(refreshToken)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Login_SessionCreateError() {
|
||||
password := "testpassword"
|
||||
user := createTestUser(password)
|
||||
s.userRepo.FindByEmailHashMock.Return(user, nil)
|
||||
|
||||
dbErr := apperrors.NewInternalError(apperrors.DatabaseError, "failed to create session", nil)
|
||||
s.sessionRepo.CreateMock.Return(dbErr)
|
||||
|
||||
accessToken, refreshToken, err := s.authService.Login(
|
||||
s.ctx,
|
||||
"test@example.com",
|
||||
password,
|
||||
"127.0.0.1",
|
||||
"test-agent",
|
||||
)
|
||||
|
||||
s.Error(err)
|
||||
s.Empty(accessToken)
|
||||
s.Empty(refreshToken)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Login_EmptyIPAndUserAgent() {
|
||||
password := "testpassword"
|
||||
user := createTestUser(password)
|
||||
s.userRepo.FindByEmailHashMock.Return(user, nil)
|
||||
s.sessionRepo.CreateMock.Return(nil)
|
||||
|
||||
accessToken, refreshToken, err := s.authService.Login(
|
||||
s.ctx,
|
||||
"test@example.com",
|
||||
password,
|
||||
"",
|
||||
"",
|
||||
)
|
||||
|
||||
s.NoError(err)
|
||||
s.NotEmpty(accessToken)
|
||||
s.NotEmpty(refreshToken)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Refresh_Success() {
|
||||
session := createTestSession(1)
|
||||
s.sessionRepo.FindByRefreshTokenMock.Return(session, nil)
|
||||
s.sessionRepo.UpdateAccessTokenMock.Return(nil)
|
||||
|
||||
accessToken, err := s.authService.Refresh(s.ctx, "test-refresh-token")
|
||||
|
||||
s.NoError(err)
|
||||
s.NotEmpty(accessToken)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Refresh_RefreshInvalid() {
|
||||
err := apperrors.NewBusinessError(apperrors.RefreshInvalid, "refresh token is invalid or expired")
|
||||
s.sessionRepo.FindByRefreshTokenMock.Return(nil, err)
|
||||
|
||||
accessToken, refreshErr := s.authService.Refresh(s.ctx, "invalid-token")
|
||||
|
||||
s.Error(refreshErr)
|
||||
s.Empty(accessToken)
|
||||
|
||||
var appErr *apperrors.AppError
|
||||
s.True(errors.As(refreshErr, &appErr))
|
||||
s.Equal(apperrors.RefreshInvalid, appErr.Code)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Refresh_DatabaseError_OnFindSession() {
|
||||
dbErr := apperrors.NewInternalError(apperrors.DatabaseError, "failed to find session", nil)
|
||||
s.sessionRepo.FindByRefreshTokenMock.Return(nil, dbErr)
|
||||
|
||||
accessToken, err := s.authService.Refresh(s.ctx, "test-refresh-token")
|
||||
|
||||
s.Error(err)
|
||||
s.Empty(accessToken)
|
||||
|
||||
var appErr *apperrors.AppError
|
||||
s.True(errors.As(err, &appErr))
|
||||
s.Equal(apperrors.InternalErrorType, appErr.Type)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Refresh_EmptyToken() {
|
||||
err := apperrors.NewBusinessError(apperrors.RefreshInvalid, "refresh token is invalid or expired")
|
||||
s.sessionRepo.FindByRefreshTokenMock.Return(nil, err)
|
||||
|
||||
accessToken, refreshErr := s.authService.Refresh(s.ctx, "")
|
||||
|
||||
s.Error(refreshErr)
|
||||
s.Empty(accessToken)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Refresh_UpdateAccessTokenError() {
|
||||
session := createTestSession(1)
|
||||
s.sessionRepo.FindByRefreshTokenMock.Return(session, nil)
|
||||
|
||||
dbErr := apperrors.NewInternalError(apperrors.DatabaseError, "failed to update access token", nil)
|
||||
s.sessionRepo.UpdateAccessTokenMock.Return(dbErr)
|
||||
|
||||
accessToken, err := s.authService.Refresh(s.ctx, "test-refresh-token")
|
||||
|
||||
s.Error(err)
|
||||
s.Empty(accessToken)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Refresh_UserIDZero() {
|
||||
session := createTestSession(0)
|
||||
s.sessionRepo.FindByRefreshTokenMock.Return(session, nil)
|
||||
s.sessionRepo.UpdateAccessTokenMock.Return(nil)
|
||||
|
||||
accessToken, err := s.authService.Refresh(s.ctx, "test-refresh-token")
|
||||
|
||||
s.NoError(err)
|
||||
s.NotEmpty(accessToken)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Validate_Success() {
|
||||
s.T().Parallel()
|
||||
|
||||
userID := 1
|
||||
accessToken, err := jwt.GenerateAccessToken(userID)
|
||||
s.NoError(err)
|
||||
|
||||
validatedUserID, validateErr := s.authService.Validate(s.ctx, accessToken)
|
||||
|
||||
s.NoError(validateErr)
|
||||
s.Equal(userID, validatedUserID)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Validate_EmptyToken() {
|
||||
s.T().Parallel()
|
||||
|
||||
userID, err := s.authService.Validate(s.ctx, "")
|
||||
|
||||
s.Error(err)
|
||||
s.Equal(0, userID)
|
||||
|
||||
var appErr *apperrors.AppError
|
||||
s.True(errors.As(err, &appErr))
|
||||
s.Equal(apperrors.AuthInvalidToken, appErr.Code)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Validate_InvalidTokenFormat() {
|
||||
s.T().Parallel()
|
||||
|
||||
userID, err := s.authService.Validate(s.ctx, "invalid.token.format")
|
||||
|
||||
s.Error(err)
|
||||
s.Equal(0, userID)
|
||||
|
||||
var appErr *apperrors.AppError
|
||||
s.True(errors.As(err, &appErr))
|
||||
s.Equal(apperrors.AuthInvalidToken, appErr.Code)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Validate_RefreshTokenInsteadOfAccess() {
|
||||
s.T().Parallel()
|
||||
|
||||
userID := 1
|
||||
refreshToken, err := jwt.GenerateRefreshToken(userID)
|
||||
s.NoError(err)
|
||||
|
||||
validatedUserID, validateErr := s.authService.Validate(s.ctx, refreshToken)
|
||||
|
||||
s.Error(validateErr)
|
||||
s.Equal(0, validatedUserID)
|
||||
|
||||
var appErr *apperrors.AppError
|
||||
s.True(errors.As(validateErr, &appErr))
|
||||
s.Equal(apperrors.AuthInvalidToken, appErr.Code)
|
||||
s.Contains(appErr.Message, "not an access token")
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Validate_UserIDZero() {
|
||||
s.T().Parallel()
|
||||
|
||||
accessToken, err := jwt.GenerateAccessToken(0)
|
||||
s.NoError(err)
|
||||
|
||||
validatedUserID, validateErr := s.authService.Validate(s.ctx, accessToken)
|
||||
|
||||
s.NoError(validateErr)
|
||||
s.Equal(0, validatedUserID)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Validate_InvalidSignature() {
|
||||
s.T().Parallel()
|
||||
|
||||
invalidToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwidHlwZSI6ImFjY2VzcyIsImlhdCI6MTYwOTQ1NjgwMCwiZXhwIjoxNjA5NDY1ODAwfQ.invalid-signature"
|
||||
|
||||
userID, err := s.authService.Validate(s.ctx, invalidToken)
|
||||
|
||||
s.Error(err)
|
||||
s.Equal(0, userID)
|
||||
|
||||
var appErr *apperrors.AppError
|
||||
s.True(errors.As(err, &appErr))
|
||||
s.Equal(apperrors.AuthInvalidToken, appErr.Code)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Logout_Success() {
|
||||
s.sessionRepo.RevokeMock.Return(nil)
|
||||
|
||||
err := s.authService.Logout(s.ctx, "test-refresh-token")
|
||||
|
||||
s.NoError(err)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Logout_DatabaseError() {
|
||||
dbErr := apperrors.NewInternalError(apperrors.DatabaseError, "failed to revoke session", nil)
|
||||
s.sessionRepo.RevokeMock.Return(dbErr)
|
||||
|
||||
err := s.authService.Logout(s.ctx, "test-refresh-token")
|
||||
|
||||
s.Error(err)
|
||||
|
||||
var appErr *apperrors.AppError
|
||||
s.True(errors.As(err, &appErr))
|
||||
s.Equal(apperrors.InternalErrorType, appErr.Type)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Logout_EmptyToken() {
|
||||
s.sessionRepo.RevokeMock.Return(nil)
|
||||
|
||||
err := s.authService.Logout(s.ctx, "")
|
||||
|
||||
s.NoError(err)
|
||||
}
|
||||
|
||||
func (s *Suite) TestAuthService_Logout_NonExistentToken() {
|
||||
s.sessionRepo.RevokeMock.Return(nil)
|
||||
|
||||
err := s.authService.Logout(s.ctx, "non-existent-token")
|
||||
|
||||
s.NoError(err)
|
||||
}
|
||||
83
internal/service/user.go
Normal file
83
internal/service/user.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"smart-search-back/internal/repository"
|
||||
"smart-search-back/pkg/crypto"
|
||||
)
|
||||
|
||||
type userService struct {
|
||||
userRepo repository.UserRepository
|
||||
requestRepo repository.RequestRepository
|
||||
cryptoHelper *crypto.Crypto
|
||||
}
|
||||
|
||||
type UserInfo struct {
|
||||
Email string
|
||||
Name string
|
||||
Phone string
|
||||
CompanyName string
|
||||
PaymentStatus string
|
||||
}
|
||||
|
||||
type Statistics struct {
|
||||
RequestsCount int
|
||||
SuppliersCount int
|
||||
CreatedTZ int
|
||||
}
|
||||
|
||||
func NewUserService(userRepo repository.UserRepository, requestRepo repository.RequestRepository, cryptoSecret string) UserService {
|
||||
return &userService{
|
||||
userRepo: userRepo,
|
||||
requestRepo: requestRepo,
|
||||
cryptoHelper: crypto.NewCrypto(cryptoSecret),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *userService) GetInfo(ctx context.Context, userID int) (*UserInfo, error) {
|
||||
user, err := s.userRepo.FindByID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
email, err := s.cryptoHelper.Decrypt(user.Email)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
phone, err := s.cryptoHelper.Decrypt(user.Phone)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userName, err := s.cryptoHelper.Decrypt(user.UserName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &UserInfo{
|
||||
Email: email,
|
||||
Name: userName,
|
||||
Phone: phone,
|
||||
CompanyName: user.CompanyName,
|
||||
PaymentStatus: user.PaymentStatus,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *userService) GetBalance(ctx context.Context, userID int) (float64, error) {
|
||||
return s.userRepo.GetBalance(ctx, userID)
|
||||
}
|
||||
|
||||
func (s *userService) GetStatistics(ctx context.Context, userID int) (*Statistics, error) {
|
||||
requestsCount, suppliersCount, createdTZ, err := s.requestRepo.GetUserStatistics(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Statistics{
|
||||
RequestsCount: requestsCount,
|
||||
SuppliersCount: suppliersCount,
|
||||
CreatedTZ: createdTZ,
|
||||
}, nil
|
||||
}
|
||||
69
internal/worker/invite_cleaner.go
Normal file
69
internal/worker/invite_cleaner.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package worker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"smart-search-back/internal/repository"
|
||||
)
|
||||
|
||||
type InviteCleaner struct {
|
||||
inviteRepo repository.InviteRepository
|
||||
ctx context.Context
|
||||
ticker *time.Ticker
|
||||
done chan bool
|
||||
}
|
||||
|
||||
func NewInviteCleaner(ctx context.Context, inviteRepo repository.InviteRepository) *InviteCleaner {
|
||||
return &InviteCleaner{
|
||||
inviteRepo: inviteRepo,
|
||||
ctx: ctx,
|
||||
done: make(chan bool),
|
||||
}
|
||||
}
|
||||
|
||||
func (w *InviteCleaner) Start() {
|
||||
w.ticker = time.NewTicker(6 * time.Hour)
|
||||
|
||||
w.deactivateExpiredInvites()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-w.ticker.C:
|
||||
w.deactivateExpiredInvites()
|
||||
case <-w.done:
|
||||
return
|
||||
case <-w.ctx.Done():
|
||||
log.Println("Invite cleaner context cancelled, stopping worker")
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
log.Println("Invite cleaner worker started (runs every 6 hours)")
|
||||
}
|
||||
|
||||
func (w *InviteCleaner) Stop() {
|
||||
if w.ticker != nil {
|
||||
w.ticker.Stop()
|
||||
}
|
||||
select {
|
||||
case w.done <- true:
|
||||
default:
|
||||
}
|
||||
log.Println("Invite cleaner worker stopped")
|
||||
}
|
||||
|
||||
func (w *InviteCleaner) deactivateExpiredInvites() {
|
||||
count, err := w.inviteRepo.DeactivateExpired(w.ctx)
|
||||
if err != nil {
|
||||
log.Printf("Error deactivating expired invites: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
log.Printf("Deactivated %d expired invite codes", count)
|
||||
}
|
||||
}
|
||||
69
internal/worker/session_cleaner.go
Normal file
69
internal/worker/session_cleaner.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package worker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"smart-search-back/internal/repository"
|
||||
)
|
||||
|
||||
type SessionCleaner struct {
|
||||
sessionRepo repository.SessionRepository
|
||||
ctx context.Context
|
||||
ticker *time.Ticker
|
||||
done chan bool
|
||||
}
|
||||
|
||||
func NewSessionCleaner(ctx context.Context, sessionRepo repository.SessionRepository) *SessionCleaner {
|
||||
return &SessionCleaner{
|
||||
sessionRepo: sessionRepo,
|
||||
ctx: ctx,
|
||||
done: make(chan bool),
|
||||
}
|
||||
}
|
||||
|
||||
func (w *SessionCleaner) Start() {
|
||||
w.ticker = time.NewTicker(1 * time.Hour)
|
||||
|
||||
w.cleanExpiredSessions()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-w.ticker.C:
|
||||
w.cleanExpiredSessions()
|
||||
case <-w.done:
|
||||
return
|
||||
case <-w.ctx.Done():
|
||||
log.Println("Session cleaner context cancelled, stopping worker")
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
log.Println("Session cleaner worker started (runs every hour)")
|
||||
}
|
||||
|
||||
func (w *SessionCleaner) Stop() {
|
||||
if w.ticker != nil {
|
||||
w.ticker.Stop()
|
||||
}
|
||||
select {
|
||||
case w.done <- true:
|
||||
default:
|
||||
}
|
||||
log.Println("Session cleaner worker stopped")
|
||||
}
|
||||
|
||||
func (w *SessionCleaner) cleanExpiredSessions() {
|
||||
count, err := w.sessionRepo.DeleteExpired(w.ctx)
|
||||
if err != nil {
|
||||
log.Printf("Error cleaning expired sessions: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
log.Printf("Cleaned %d expired sessions", count)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user