init
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.idea
|
||||||
55
app/app.go
Normal file
55
app/app.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
type App struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *App {
|
||||||
|
return &App{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) Run() error {
|
||||||
|
// Context pour gérer l'arrêt gracieux
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// Channel pour capturer les signaux d'arrêt (Ctrl+c, etc...)
|
||||||
|
sigChan := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
|
||||||
|
|
||||||
|
// Channel pour les erreurs
|
||||||
|
errChan := make(chan error, 1)
|
||||||
|
|
||||||
|
// Lancer les app ici
|
||||||
|
go app.runWorker(ctx, errChan)
|
||||||
|
|
||||||
|
fmt.Println("App started, press CTRL+C to stop...")
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-errChan:
|
||||||
|
return fmt.Errorf("error in the app: %w", err)
|
||||||
|
case sig := <-sigChan:
|
||||||
|
fmt.Printf("\nSIGTERM received: %v, stopping app...\n", sig)
|
||||||
|
cancel() // Annule le context pour arrêter les goroutines
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) runWorker(ctx context.Context, err_chan chan<- error) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
fmt.Println("Worker stopped")
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
// Logique métier ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
65
database/database.go
Normal file
65
database/database.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"gorm.io/driver/mysql"
|
||||||
|
"gorm.io/driver/postgres"
|
||||||
|
"gorm.io/driver/sqlite"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var DB *gorm.DB
|
||||||
|
|
||||||
|
type DBConfig struct {
|
||||||
|
Driver string // "sqlite", "postgres", "mysql"
|
||||||
|
DSN string // Data Source Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize initialise la connexion à la base de données
|
||||||
|
func Initialize(config DBConfig) error {
|
||||||
|
var err error
|
||||||
|
var dialector gorm.Dialector
|
||||||
|
|
||||||
|
// Sélection du driver approprié
|
||||||
|
switch config.Driver {
|
||||||
|
case "sqlite":
|
||||||
|
dialector = sqlite.Open(config.DSN)
|
||||||
|
case "postgres":
|
||||||
|
dialector = postgres.Open(config.DSN)
|
||||||
|
case "mysql":
|
||||||
|
dialector = mysql.Open(config.DSN)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("driver non supporté: %s", config.Driver)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuration de GORM
|
||||||
|
gormConfig := &gorm.Config{
|
||||||
|
Logger: logger.Default.LogMode(logger.Info),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connexion à la base de données
|
||||||
|
DB, err = gorm.Open(dialector, gormConfig)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("erreur lors de la connexion à la base de données: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Connexion à la base de données (%s) établie avec succès", config.Driver)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AutoMigrate effectue les migrations automatiques pour les modèles
|
||||||
|
func AutoMigrate(models ...interface{}) error {
|
||||||
|
return DB.AutoMigrate(models...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close ferme la connexion à la base de données
|
||||||
|
func Close() error {
|
||||||
|
sqlDB, err := DB.DB()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return sqlDB.Close()
|
||||||
|
}
|
||||||
24
go.mod
Normal file
24
go.mod
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
module go_oxspeak_server
|
||||||
|
|
||||||
|
go 1.25.3
|
||||||
|
|
||||||
|
require (
|
||||||
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
|
github.com/deckarep/golang-set/v2 v2.8.0 // indirect
|
||||||
|
github.com/go-sql-driver/mysql v1.9.3 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
|
github.com/jackc/pgx/v5 v5.7.6 // indirect
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.32 // indirect
|
||||||
|
golang.org/x/crypto v0.43.0 // indirect
|
||||||
|
golang.org/x/sync v0.17.0 // indirect
|
||||||
|
golang.org/x/text v0.30.0 // indirect
|
||||||
|
gorm.io/driver/mysql v1.6.0 // indirect
|
||||||
|
gorm.io/driver/postgres v1.6.0 // indirect
|
||||||
|
gorm.io/driver/sqlite v1.6.0 // indirect
|
||||||
|
gorm.io/gorm v1.31.0 // indirect
|
||||||
|
)
|
||||||
43
go.sum
Normal file
43
go.sum
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/deckarep/golang-set/v2 v2.8.0 h1:swm0rlPCmdWn9mESxKOjWk8hXSqoxOp+ZlfuyaAdFlQ=
|
||||||
|
github.com/deckarep/golang-set/v2 v2.8.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
|
||||||
|
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||||
|
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
|
github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
|
||||||
|
github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||||
|
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||||
|
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||||
|
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
|
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||||
|
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
|
||||||
|
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
|
||||||
|
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
|
||||||
|
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
||||||
|
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
|
||||||
|
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
|
||||||
|
gorm.io/gorm v1.31.0 h1:0VlycGreVhK7RF/Bwt51Fk8v0xLiiiFdbGDPIZQ7mJY=
|
||||||
|
gorm.io/gorm v1.31.0/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||||
15
main.go
Normal file
15
main.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import app "go_oxspeak_server/app"
|
||||||
|
|
||||||
|
//TIP <p>To run your code, right-click the code and select <b>Run</b>.</p> <p>Alternatively, click
|
||||||
|
// the <icon src="AllIcons.Actions.Execute"/> icon in the gutter and select the <b>Run</b> menu item from here.</p>
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
process := app.App{}
|
||||||
|
err := process.Run()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
19
models/attachment.go
Normal file
19
models/attachment.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Attachment struct {
|
||||||
|
ID uuid.UUID `gorm:"primaryKey" json:"id"`
|
||||||
|
MessageID uuid.UUID `gorm:"index;not null" json:"message_id"`
|
||||||
|
Filename string `gorm:"not null" json:"filename"`
|
||||||
|
FileSize int64 `gorm:"not null" json:"file_size"`
|
||||||
|
MimeType string `gorm:"not null" json:"mime_type"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
|
|
||||||
|
// Relation optionnelle vers le message
|
||||||
|
Message *Message `gorm:"foreignKey:MessageID" json:"message,omitempty"`
|
||||||
|
}
|
||||||
18
models/category.go
Normal file
18
models/category.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Category struct {
|
||||||
|
ID uuid.UUID `gorm:"primaryKey" json:"id"`
|
||||||
|
ServerID uuid.UUID `gorm:"index;not null" json:"server_id"`
|
||||||
|
Name string `gorm:"not null" json:"name"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||||
|
|
||||||
|
// Relation optionnelle vers le serveur
|
||||||
|
Server *Server `gorm:"foreignKey:ServerID" json:"server,omitempty"`
|
||||||
|
}
|
||||||
50
models/channel.go
Normal file
50
models/channel.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// #[derive(Debug, Clone, sqlx::FromRow, Serialize, Deserialize)]
|
||||||
|
// pub struct Channel {
|
||||||
|
// pub id: Uuid,
|
||||||
|
// #[sqlx(default)]
|
||||||
|
// pub server_id: Option<Uuid>, // Option = nullable
|
||||||
|
// #[sqlx(default)]
|
||||||
|
// pub category_id: Option<Uuid>, // Option = nullable
|
||||||
|
// #[sqlx(default)]
|
||||||
|
// pub position: i32,
|
||||||
|
// #[sqlx(rename = "type")]
|
||||||
|
// pub channel_type: ChannelType,
|
||||||
|
// pub name: Option<String>, // Option = nullable
|
||||||
|
// pub created_at: DateTime<Utc>,
|
||||||
|
// pub updated_at: DateTime<Utc>,
|
||||||
|
// }
|
||||||
|
|
||||||
|
type ChannelType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ChannelTypeText ChannelType = "text"
|
||||||
|
ChannelTypeVoice ChannelType = "voice"
|
||||||
|
ChannelTypeDM ChannelType = "dm"
|
||||||
|
// Ajoutez vos autres types ici
|
||||||
|
)
|
||||||
|
|
||||||
|
type Channel struct {
|
||||||
|
ID uuid.UUID `gorm:"primaryKey" json:"id"`
|
||||||
|
ServerID *uuid.UUID `gorm:"index;constraint:OnDelete:CASCADE;" json:"server_id,omitempty"` // Pointeur = nullable
|
||||||
|
CategoryID *uuid.UUID `gorm:"index;constraint:OnDelete:SET NULL;" json:"category_id,omitempty"` // Pointeur = nullable
|
||||||
|
Position int32 `gorm:"default:0" json:"position"`
|
||||||
|
Type ChannelType `gorm:"not null;column:type" json:"type"`
|
||||||
|
Name *string `gorm:"" json:"name,omitempty"` // Pointeur = nullable
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||||
|
|
||||||
|
Users []*User `gorm:"many2many:channel_users" json:"users,omitempty"` // accès direct aux users
|
||||||
|
UserLinks []ChannelUser `gorm:"foreignKey:ChannelID" json:"user_links,omitempty"` // accès aux lignes de jointure/métadonnées
|
||||||
|
|
||||||
|
// Relations (optionnelles)
|
||||||
|
Server *Server `gorm:"foreignKey:ServerID" json:"server,omitempty"`
|
||||||
|
Category *Category `gorm:"foreignKey:CategoryID" json:"category,omitempty"`
|
||||||
|
}
|
||||||
19
models/channel_user.go
Normal file
19
models/channel_user.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChannelUser struct {
|
||||||
|
ChannelID uuid.UUID `gorm:"primaryKey" json:"channel_id"`
|
||||||
|
UserID uuid.UUID `gorm:"primaryKey" json:"user_id"`
|
||||||
|
|
||||||
|
Role string `gorm:"default:'member'" json:"role"`
|
||||||
|
JoinedAt time.Time `gorm:"autoCreateTime" json:"joined_at"`
|
||||||
|
|
||||||
|
// Relations pour navigation (optionnelles)
|
||||||
|
Channel *Channel `gorm:"foreignKey:ChannelID" json:"channel,omitempty"`
|
||||||
|
User *User `gorm:"foreignKey:UserID" json:"user,omitempty"`
|
||||||
|
}
|
||||||
28
models/message.go
Normal file
28
models/message.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// #[derive(Debug, Clone, FromRow, Serialize, Deserialize)]
|
||||||
|
//pub struct Message {
|
||||||
|
// pub id: Uuid,
|
||||||
|
// pub channel_id: Uuid,
|
||||||
|
// pub user_id: Uuid,
|
||||||
|
// pub content: String,
|
||||||
|
// pub created_at: DateTime<Utc>,
|
||||||
|
// pub edited_at: DateTime<Utc>,
|
||||||
|
// pub reply_to_id: Option<Uuid>,
|
||||||
|
//}
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
ID uuid.UUID `gorm:"primaryKey" json:"id"`
|
||||||
|
ChannelID uuid.UUID `gorm:"index" json:"channel_id"`
|
||||||
|
UserID uuid.UUID `gorm:"index" json:"user_id"`
|
||||||
|
Content string `gorm:"not null" json:"content"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
|
EditedAt *time.Time `gorm:"default:null" json:"edited_at,omitempty"`
|
||||||
|
ReplyToID *uuid.UUID `gorm:"index" json:"reply_to_id,omitempty"`
|
||||||
|
}
|
||||||
14
models/server.go
Normal file
14
models/server.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
ID uuid.UUID `gorm:"primaryKey" json:"id"`
|
||||||
|
Password string `gorm:"not null" json:"-"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||||
|
}
|
||||||
20
models/server_user.go
Normal file
20
models/server_user.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServerUser struct {
|
||||||
|
ID uuid.UUID `gorm:"primaryKey" json:"id"`
|
||||||
|
ServerID uuid.UUID `gorm:"index;not null" json:"server_id"`
|
||||||
|
UserID uuid.UUID `gorm:"index;not null" json:"user_id"`
|
||||||
|
Username *string `json:"username,omitempty"` // Option<String> = pointeur nullable
|
||||||
|
JoinedAt time.Time `gorm:"autoCreateTime" json:"joined_at"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||||
|
|
||||||
|
// Relations pour navigation (optionnelles)
|
||||||
|
Server *Server `gorm:"foreignKey:ServerID" json:"server,omitempty"`
|
||||||
|
User *User `gorm:"foreignKey:UserID" json:"user,omitempty"`
|
||||||
|
}
|
||||||
18
models/user.go
Normal file
18
models/user.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
ID uuid.UUID `gorm:"primaryKey" json:"id"`
|
||||||
|
Username string `gorm:"not null" json:"username"`
|
||||||
|
PubKey string `gorm:"type:text;uniqueIndex;not null" json:"pub_key"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||||
|
|
||||||
|
Channels []*Channel `gorm:"many2many:channel_users" json:"channels,omitempty"`
|
||||||
|
ChannelLinks []ChannelUser `gorm:"foreignKey:UserID" json:"channel_links,omitempty"`
|
||||||
|
}
|
||||||
39
network/udp/enums.go
Normal file
39
network/udp/enums.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package udp
|
||||||
|
|
||||||
|
import (
|
||||||
|
//"encoding/binary"
|
||||||
|
//"errors"
|
||||||
|
//"fmt"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PacketType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
PacketTypePing PacketType = 0
|
||||||
|
PacketTypeConnect PacketType = 1
|
||||||
|
PacketTypeDisconnect PacketType = 2
|
||||||
|
PacketTypeVoiceData PacketType = 9
|
||||||
|
)
|
||||||
|
|
||||||
|
type Message interface {
|
||||||
|
Type() PacketType
|
||||||
|
MarshalBinary() ([]byte, error)
|
||||||
|
Size() int
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessagePing ClientPing représente un message ping client
|
||||||
|
type MessagePing struct {
|
||||||
|
MessageID uuid.UUID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MessagePing) Type() PacketType { return PacketTypePing }
|
||||||
|
func (m *MessagePing) Size() int { return 17 } // 1 + 16
|
||||||
|
|
||||||
|
func (m *MessagePing) MarshalBinary() ([]byte, error) {
|
||||||
|
buf := make([]byte, m.Size())
|
||||||
|
buf[0] = byte(PacketTypePing)
|
||||||
|
copy(buf[1:], m.MessageID[:])
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
55
network/udp/routing_table.go
Normal file
55
network/udp/routing_table.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package udp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
mapset "github.com/deckarep/golang-set/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RoutingTable struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
routes map[string]mapset.Set[*net.UDPAddr]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRoutingTable() *RoutingTable {
|
||||||
|
return &RoutingTable{
|
||||||
|
routes: make(map[string]mapset.Set[*net.UDPAddr]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *RoutingTable) AddClient(channelID string, addr *net.UDPAddr) {
|
||||||
|
rt.mu.Lock()
|
||||||
|
defer rt.mu.Unlock()
|
||||||
|
|
||||||
|
if rt.routes[channelID] == nil {
|
||||||
|
rt.routes[channelID] = mapset.NewSet[*net.UDPAddr]()
|
||||||
|
}
|
||||||
|
rt.routes[channelID].Add(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *RoutingTable) RemoveClient(channelID string, addr *net.UDPAddr) {
|
||||||
|
rt.mu.Lock()
|
||||||
|
defer rt.mu.Unlock()
|
||||||
|
|
||||||
|
if clients, exists := rt.routes[channelID]; exists {
|
||||||
|
clients.Remove(addr)
|
||||||
|
if clients.Cardinality() == 0 {
|
||||||
|
delete(rt.routes, channelID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetClients returns the clients connected to the given channelID
|
||||||
|
// don't modify the returned set!
|
||||||
|
func (rt *RoutingTable) GetClients(channelID string) mapset.Set[*net.UDPAddr] {
|
||||||
|
rt.mu.RLock()
|
||||||
|
defer rt.mu.RUnlock()
|
||||||
|
|
||||||
|
clients, exists := rt.routes[channelID]
|
||||||
|
if !exists {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return clients
|
||||||
|
}
|
||||||
115
network/udp/server.go
Normal file
115
network/udp/server.go
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
package udp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
bindAddr string
|
||||||
|
routingTable *RoutingTable
|
||||||
|
|
||||||
|
conn *net.UDPConn
|
||||||
|
wg sync.WaitGroup
|
||||||
|
ctx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServer(bindAddr string) (*Server, error) {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
return &Server{
|
||||||
|
bindAddr: bindAddr,
|
||||||
|
ctx: ctx,
|
||||||
|
cancel: cancel,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) run() error {
|
||||||
|
add, err := net.ResolveUDPAddr("udp", s.bindAddr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot resolve address: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.conn, err = net.ListenUDP("udp", add)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot listen on address: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.conn.SetReadBuffer(8 * 1024 * 1024)
|
||||||
|
s.conn.SetWriteBuffer(8 * 1024 * 1024)
|
||||||
|
fmt.Println("Listening on", s.bindAddr)
|
||||||
|
|
||||||
|
for i := 0; i < runtime.NumCPU(); i++ {
|
||||||
|
s.wg.Add(1)
|
||||||
|
// todo : add so_reuseport option when on unix like system
|
||||||
|
go s.workerLoop(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) sendTo(data []byte, addr *net.UDPAddr) error {
|
||||||
|
if s.conn == nil {
|
||||||
|
return fmt.Errorf("server not started")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := s.conn.WriteToUDP(data, addr)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) workerLoop(id int) {
|
||||||
|
defer s.wg.Done()
|
||||||
|
|
||||||
|
buffer := make([]byte, 1500)
|
||||||
|
fmt.Println("Worker", id, "started")
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-s.ctx.Done():
|
||||||
|
fmt.Println("Worker", id, "stopped")
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
size, addr, err := s.conn.ReadFromUDP(buffer)
|
||||||
|
if err != nil {
|
||||||
|
if s.ctx.Err() != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if opErr, ok := err.(*net.OpError); ok && opErr.Temporary() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Printf("Error reading from UDP: %v\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
s.handlePacket(buffer[:size], addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) handlePacket(data []byte, addr *net.UDPAddr) {
|
||||||
|
pt := PacketType(data[0])
|
||||||
|
switch pt {
|
||||||
|
case PacketTypePing:
|
||||||
|
err := s.sendTo([]byte{byte(PacketTypePing)}, addr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case PacketTypeConnect:
|
||||||
|
return
|
||||||
|
case PacketTypeDisconnect:
|
||||||
|
return
|
||||||
|
case PacketTypeVoiceData:
|
||||||
|
// todo : déterminer le format du packet
|
||||||
|
//channelID := string(data[1:5])
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user