Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* [Non-Functional Features](#non-functional-features)
* [Quick Start](#quick-start)
* [Prerequisites](#prerequisites)
* [Install awscli and awscli-local](#install-awscli-and-awscli-local)
* [1. Setup Environment](#1-setup-environment)
* [2. Build and Run the Application](#2-build-and-run-the-application)
* [Services](#services)
Expand Down Expand Up @@ -46,7 +47,8 @@ This project is a notification management system that leverages AI for processin
- Docker and Docker Compose (for LocalStack)
- AWS CLI

#### If you don't have AWS CLI installed, you can install it using the following command:
#### Install awscli and awscli-local
If you don't have AWS CLI installed, you can install it using the following command:
```bash
sudo apt-get install python3-pip -y
pip3 install awscli awscli-local
Expand Down Expand Up @@ -117,8 +119,7 @@ Now you can use the APIs in the collection.

## Future Plans

- Authentication in requests
- AI based todo setup
- Report generation based on periodic results
- Additional notification formats (e.g., PDF, Excel)
- Add OPENAI integration for AI processing
- Sandbox for running curl command
- Add Front End
- Generic Pages
- Complex Search screens
9 changes: 6 additions & 3 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ package config
import (
"NotificationManagement/config/helper"
"fmt"
"github.com/spf13/viper"
"os"
"reflect"

"github.com/spf13/viper"
)

type Config struct {
Expand All @@ -22,6 +23,7 @@ type Config struct {
}
type DevelopmentConfig struct {
GeminiKey string `mapstructure:"geminikey"`
OpenAIKey string `mapstructure:"openaikey"`
}
type AppConfig struct {
Name string `mapstructure:"name"`
Expand Down Expand Up @@ -130,7 +132,7 @@ func LoadConfig() {
fmt.Printf("Error unmarshaling config: %v\n", err)
os.Exit(1)
}
printAllVipers()
//printAllVipers()
}

func printAllVipers() {
Expand Down Expand Up @@ -222,7 +224,7 @@ func loadDefaults() *Config {
Format: "",
},
Keycloak: KeycloakConfig{
ServerURL: "http://localhost:8081",
ServerURL: "http://localhost:8081/keycloak/",
Realm: "gocloak",
ClientID: "gocloak",
ClientSecret: "gocloak-secret",
Expand Down Expand Up @@ -338,6 +340,7 @@ func loadFromEnv() *Config {
},
Development: DevelopmentConfig{
GeminiKey: os.Getenv(EnvGeminiKey),
OpenAIKey: os.Getenv(EnvOpenaiKey),
},
}
setViperFields(c, "")
Expand Down
1 change: 1 addition & 0 deletions config/env_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const (
EnvAppDomain = "APP_DOMAIN"

EnvGeminiKey = "GEMINI_KEY"
EnvOpenaiKey = "OPENAI_KEY"

EnvDBHost = "DB_HOST"
EnvDBPort = "DB_PORT"
Expand Down
1 change: 0 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ services:
- --import-realm
environment:
KC_HOSTNAME: $KEYCLOAK_SERVER_URL
KC_HOSTNAME_STRICT: true
KEYCLOAK_USER: admin
KEYCLOAK_PASSWORD: secret
KEYCLOAK_ADMIN: admin
Expand Down
13 changes: 13 additions & 0 deletions domain/openai.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package domain

import (
"NotificationManagement/models"
)

type OpenAIService interface {
AIService[models.OpenAIModel]
}

type OpenAIModelRepository interface {
Repository[models.OpenAIModel, uint]
}
2 changes: 1 addition & 1 deletion env/app-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
}
},
"keycloak": {
"serverUrl": "http://localhost:8081",
"serverUrl": "http://localhost:8081/keycloak/",
"realm": "gocloak",
"clientId": "gocloak",
"clientSecret": "gocloak-secret"
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ require (
github.com/labstack/echo-contrib v0.17.4
github.com/labstack/echo/v4 v4.13.4
github.com/labstack/gommon v0.4.2
github.com/sashabaranov/go-openai v1.41.1
github.com/spf13/cobra v1.8.0
github.com/spf13/viper v1.18.2
go.uber.org/fx v1.24.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,8 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/sashabaranov/go-openai v1.41.1 h1:zf5tM+GuxpyiyD9XZg8nCqu52eYFQg9OOew0gnIuDy4=
github.com/sashabaranov/go-openai v1.41.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=
github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
Expand Down
41 changes: 29 additions & 12 deletions models/ai.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package models

import (
"NotificationManagement/config"

"gorm.io/gorm"
)

Expand All @@ -11,14 +12,21 @@ type AIModelInterface interface {

type AIModel struct {
gorm.Model
Type string `gorm:"size:10;check:type IN ('local','openai','gemini','deepseek')"`
Type string `gorm:"size:10;check:type IN ('local','openai','gemini','deepseek')"`
BaseURL *string `gorm:"size:500" json:"base_url,omitempty"`
}

type OpenAIModel struct {
AIModel `mapper:"inherit"`
Name string `gorm:"size:255;not null" json:"name"`
ModelName string `gorm:"size:255;not null;check:model_name <> '';index:idx_ai_model_model_secret,unique" json:"model"`
APISecret EncryptedString `gorm:"size:500;index:idx_ai_model_model_secret,unique" json:"-"`
}

type DeepseekModel struct {
AIModel `mapper:"inherit"`
Name string `gorm:"size:255;not null" json:"name"`
ModelName string `gorm:"size:255;not null;check:model_name <> '';index:idx_ai_model_model_url,unique" json:"model"`
BaseURL string `gorm:"size:500;index:idx_ai_model_model_url,unique" json:"base_url"`
Size int64 `json:"size"`
}

Expand All @@ -33,26 +41,29 @@ func (d *AIModel) GetType() string {
return d.Type
}

func (d *GeminiModel) GetType() string {
return d.Type
func (*AIModel) TableName() string {
return "ai_models"
}

func (d *GeminiModel) GetAPIKey() string {
if config.IsDevelopment() && config.Development().GeminiKey != "" {
return config.Development().GeminiKey
}
return string(d.APISecret)
}

func (d *DeepseekModel) GetType() string {
return d.Type
}

func (*DeepseekModel) TableName() string {
return "ai_models"
func (d *AIModel) GetBaseURL() string {
if d.BaseURL != nil && *d.BaseURL != "" {
return *d.BaseURL
}
return ""
}

func (*GeminiModel) TableName() string {
return "ai_models"
func (d *OpenAIModel) GetAPIKey() string {
if config.IsDevelopment() && config.Development().OpenAIKey != "" {
return config.Development().OpenAIKey
}
return string(d.APISecret)
}

func (d *DeepseekModel) UpdateFromModel(source ModelInterface) {
Expand All @@ -65,3 +76,9 @@ func (d *GeminiModel) UpdateFromModel(source ModelInterface) {
copyFields(d, src)
}
}

func (d *OpenAIModel) UpdateFromModel(source ModelInterface) {
if src, ok := source.(*OpenAIModel); ok {
copyFields(d, src)
}
}
18 changes: 18 additions & 0 deletions repositories/openai.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package repositories

import (
"NotificationManagement/domain"
"NotificationManagement/models"

"gorm.io/gorm"
)

type OpenAIModelRepositoryImpl struct {
domain.Repository[models.OpenAIModel, uint]
}

func NewOpenAIModelRepository(db *gorm.DB) domain.OpenAIModelRepository {
return &OpenAIModelRepositoryImpl{
Repository: NewSQLRepository[models.OpenAIModel](db),
}
}
8 changes: 4 additions & 4 deletions scripts/public/ai_models.sql
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ CREATE TABLE IF NOT EXISTS public.ai_models
CONSTRAINT chk_ai_models_model_name
CHECK ((model_name)::text <> ''::text),
CONSTRAINT chk_ai_models_type
CHECK ((type)::text = ANY (ARRAY [('local'::character varying)::text, ('openai'::character varying)::text, ('gemini'::character varying)::text, ('deepseek'::character varying)::text]))
CHECK ((type)::text = ANY ((ARRAY ['local'::character varying, 'openai'::character varying, 'gemini'::character varying, 'deepseek'::character varying])::text[]))
);

CREATE UNIQUE INDEX IF NOT EXISTS idx_ai_model_model_secret
ON public.ai_models (model_name, api_secret);

CREATE UNIQUE INDEX IF NOT EXISTS idx_ai_model_model_url
ON public.ai_models (model_name, base_url);

CREATE INDEX IF NOT EXISTS idx_ai_models_deleted_at
ON public.ai_models (deleted_at);

CREATE UNIQUE INDEX IF NOT EXISTS idx_ai_model_model_secret
ON public.ai_models (model_name, api_secret);

5 changes: 4 additions & 1 deletion scripts/public/curl_requests.sql
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ CREATE TABLE IF NOT EXISTS public.curl_requests
body text,
raw_curl text,
response_type varchar(10),
PRIMARY KEY (id)
user_id bigint,
PRIMARY KEY (id),
CONSTRAINT fk_curl_requests_user
FOREIGN KEY (user_id) REFERENCES public.users
);

CREATE INDEX IF NOT EXISTS idx_curl_requests_deleted_at
Expand Down
22 changes: 11 additions & 11 deletions scripts/public/reminders.sql
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,28 @@ CREATE TABLE IF NOT EXISTS public.reminders
updated_at timestamp with time zone,
deleted_at timestamp with time zone,
request_id bigint,
message text NOT NULL,
message text NOT NULL,
triggered_time timestamp with time zone,
next_trigger_time timestamp with time zone,
occurrence bigint DEFAULT 0,
recurrence varchar(50),
recurrence varchar(50) NOT NULL,
after_every bigint NOT NULL,
task_id text,
upto timestamp with time zone,
PRIMARY KEY (id),
CONSTRAINT fk_curl_requests_reminders
FOREIGN KEY (request_id) REFERENCES public.curl_requests,
CONSTRAINT chk_reminders_recurrence
CHECK ((recurrence)::text = ANY
(ARRAY [('once'::character varying)::text, ('minutes'::character varying)::text, ('hour'::character varying)::text, ('daily'::character varying)::text, ('weekly'::character varying)::text]))
FOREIGN KEY (request_id) REFERENCES public.curl_requests
);

CREATE INDEX IF NOT EXISTS idx_reminders_deleted_at
ON public.reminders (deleted_at);
CREATE INDEX IF NOT EXISTS idx_reminders_upto
ON public.reminders (upto);

CREATE INDEX IF NOT EXISTS idx_reminders_next_trigger_time
ON public.reminders (next_trigger_time);

CREATE INDEX IF NOT EXISTS idx_reminders_request_id
ON public.reminders (request_id);

CREATE INDEX IF NOT EXISTS idx_reminders_triggered_time
ON public.reminders (triggered_time);

CREATE INDEX IF NOT EXISTS idx_reminders_deleted_at
ON public.reminders (deleted_at);

6 changes: 3 additions & 3 deletions scripts/public/request_ai_models.sql
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ CREATE TABLE IF NOT EXISTS public.request_ai_models
is_active boolean DEFAULT TRUE,
ai_model_id bigint,
PRIMARY KEY (id),
CONSTRAINT fk_curl_requests_models
FOREIGN KEY (request_id) REFERENCES public.curl_requests,
CONSTRAINT fk_request_ai_models_ai_model
FOREIGN KEY (ai_model_id) REFERENCES public.ai_models
FOREIGN KEY (ai_model_id) REFERENCES public.ai_models,
CONSTRAINT fk_curl_requests_models
FOREIGN KEY (request_id) REFERENCES public.curl_requests
);

CREATE UNIQUE INDEX IF NOT EXISTS idx_request_ai_model
Expand Down
23 changes: 23 additions & 0 deletions scripts/public/telegrams.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
CREATE TABLE IF NOT EXISTS public.telegrams
(
id bigserial,
created_at timestamp with time zone,
updated_at timestamp with time zone,
deleted_at timestamp with time zone,
user_id bigint,
chat_id bigint NOT NULL,
otp varchar(255) NOT NULL,
PRIMARY KEY (id),
CONSTRAINT fk_users_telegram
FOREIGN KEY (user_id) REFERENCES public.users
);

CREATE UNIQUE INDEX IF NOT EXISTS idx_telegrams_chat_id
ON public.telegrams (chat_id);

CREATE INDEX IF NOT EXISTS idx_telegrams_user_id
ON public.telegrams (user_id);

CREATE INDEX IF NOT EXISTS idx_telegrams_deleted_at
ON public.telegrams (deleted_at);

2 changes: 2 additions & 0 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,14 @@ var Module = fx.Options(
repositories.NewReminderRepository,
repositories.NewUserRepository,
repositories.NewTelegramRepository,
repositories.NewOpenAIModelRepository,

services.NewAIModelService,
services.NewAsynqService,
services.NewCurlService,
services.NewDeepseekModelService,
services.NewGeminiService,
services.NewOpenAIService,
services.NewLLMService,
services.NewReminderService,
services.NewUserService,
Expand Down
3 changes: 2 additions & 1 deletion services/ai_dispatcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ type AiDispatcherImpl struct {
aiModel domain.AIModelService
}

func NewAIDispatcher(geminiService domain.GeminiService, deepseekService domain.DeepseekService, ai domain.AIModelService) domain.AiDispatcher {
func NewAIDispatcher(geminiService domain.GeminiService, deepseekService domain.DeepseekService, openaiService domain.OpenAIService, ai domain.AIModelService) domain.AiDispatcher {
return &AiDispatcherImpl{
services: &[]domain.DispatchableAIService{
geminiService,
deepseekService,
openaiService,
},
aiModel: ai,
}
Expand Down
4 changes: 2 additions & 2 deletions services/deepseek.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func (s *DeepseekServiceImpl) PullModel(_ context.Context, model *models.Deepsee
return errutil.NewAppError(errutil.ErrAIMarshalRequestFailed, err)
}

url := fmt.Sprintf("%s/api/pull", model.BaseURL)
url := fmt.Sprintf("%s/api/pull", model.GetBaseURL())
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return errutil.NewAppError(errutil.ErrAICreateRequestFailed, err)
Expand Down Expand Up @@ -166,7 +166,7 @@ func deepseekCall(model *models.DeepseekModel, response *types.CurlResponse, cur
return nil, err
}

url := fmt.Sprintf("%s/api/chat", model.BaseURL)
url := fmt.Sprintf("%s/api/chat", model.GetBaseURL())
client := &http.Client{}
req, err := http.NewRequest("POST", url, strings.NewReader(string(reqBody)))
if err != nil {
Expand Down
Loading
Loading