Basic server app with WS connection.
Some checks failed
Linting and tests / Linting (push) Failing after 37s
Some checks failed
Linting and tests / Linting (push) Failing after 37s
This commit is contained in:
parent
466b58b41d
commit
2c13e3f380
2
.vscode/tasks.json
vendored
2
.vscode/tasks.json
vendored
@ -4,7 +4,7 @@
|
|||||||
{
|
{
|
||||||
"label": "Run bunkerd debug build",
|
"label": "Run bunkerd debug build",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "task server:localdev:bunkerd:build-debug && task server:localdev:bunkerd:restart-debug",
|
"command": "task server:localdev:bunkerd:stop; task server:localdev:bunkerd:build-debug && task server:localdev:bunkerd:restart-debug",
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "test",
|
"kind": "test",
|
||||||
"isDefault": true
|
"isDefault": true
|
||||||
|
@ -34,7 +34,7 @@ func Initialize(app *application.Application) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := app.RegisterService(db); err != nil {
|
if err := app.RegisterService(db); err != nil {
|
||||||
return fmt.Errorf("%w: %w", core.ErrMainWindow, err)
|
return fmt.Errorf("%w: %w", core.ErrDatabase, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -71,7 +71,7 @@ func (d *database) ConnectDependencies() error {
|
|||||||
|
|
||||||
mainWindow, valid := mainWindowRaw.(core.MainWindow)
|
mainWindow, valid := mainWindowRaw.(core.MainWindow)
|
||||||
if !valid {
|
if !valid {
|
||||||
return fmt.Errorf("connect dependencies: type assert main window: %w", core.ErrMainWindowIsInvalid)
|
return fmt.Errorf("connect dependencies: type assert main window: %w", core.ErrDatabaseIsInvalid)
|
||||||
}
|
}
|
||||||
|
|
||||||
d.mainWindow = mainWindow
|
d.mainWindow = mainWindow
|
||||||
@ -106,7 +106,6 @@ func (d *database) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *database) LaunchStartupTasks() error {
|
func (d *database) LaunchStartupTasks() error {
|
||||||
// Запускаем миграции.
|
|
||||||
if err := d.applyMigrations(); err != nil {
|
if err := d.applyMigrations(); err != nil {
|
||||||
return fmt.Errorf("launch startup tasks: %w", err)
|
return fmt.Errorf("launch startup tasks: %w", err)
|
||||||
}
|
}
|
||||||
|
7
commons/ids.go
Normal file
7
commons/ids.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// File "ids.go" stores all static identificators that is used across Bunker server.
|
||||||
|
package commons
|
||||||
|
|
||||||
|
const (
|
||||||
|
// SystemUserID is an UUID for "System" user. With this ID server's settings are stored in database.
|
||||||
|
SystemUserID = "00000001-0000-0000-0000-000000000000"
|
||||||
|
)
|
14
go.mod
14
go.mod
@ -4,6 +4,7 @@ go 1.24.0
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
fyne.io/fyne/v2 v2.6.3
|
fyne.io/fyne/v2 v2.6.3
|
||||||
|
github.com/jackc/pgx/v5 v5.7.6
|
||||||
github.com/jmoiron/sqlx v1.4.0
|
github.com/jmoiron/sqlx v1.4.0
|
||||||
github.com/pressly/goose/v3 v3.25.0
|
github.com/pressly/goose/v3 v3.25.0
|
||||||
github.com/shirou/gopsutil/v3 v3.24.5
|
github.com/shirou/gopsutil/v3 v3.24.5
|
||||||
@ -13,6 +14,7 @@ require (
|
|||||||
require (
|
require (
|
||||||
fyne.io/systray v1.11.0 // indirect
|
fyne.io/systray v1.11.0 // indirect
|
||||||
github.com/BurntSushi/toml v1.4.0 // indirect
|
github.com/BurntSushi/toml v1.4.0 // indirect
|
||||||
|
github.com/coder/websocket v1.8.14 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/fredbi/uri v1.1.0 // indirect
|
github.com/fredbi/uri v1.1.0 // indirect
|
||||||
@ -30,6 +32,9 @@ require (
|
|||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/hack-pad/go-indexeddb v0.3.2 // indirect
|
github.com/hack-pad/go-indexeddb v0.3.2 // indirect
|
||||||
github.com/hack-pad/safejs v0.1.0 // indirect
|
github.com/hack-pad/safejs v0.1.0 // indirect
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade // indirect
|
github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade // indirect
|
||||||
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect
|
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
@ -53,12 +58,13 @@ require (
|
|||||||
github.com/yuin/goldmark v1.7.8 // indirect
|
github.com/yuin/goldmark v1.7.8 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
|
golang.org/x/crypto v0.42.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
|
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
|
||||||
golang.org/x/image v0.24.0 // indirect
|
golang.org/x/image v0.24.0 // indirect
|
||||||
golang.org/x/net v0.42.0 // indirect
|
golang.org/x/net v0.43.0 // indirect
|
||||||
golang.org/x/sync v0.16.0 // indirect
|
golang.org/x/sync v0.17.0 // indirect
|
||||||
golang.org/x/sys v0.34.0 // indirect
|
golang.org/x/sys v0.36.0 // indirect
|
||||||
golang.org/x/text v0.27.0 // indirect
|
golang.org/x/text v0.29.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
modernc.org/libc v1.66.3 // indirect
|
modernc.org/libc v1.66.3 // indirect
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
|
51
go.sum
51
go.sum
@ -6,7 +6,10 @@ fyne.io/systray v1.11.0 h1:D9HISlxSkx+jHSniMBR6fCFOUjk1x/OOOJLa9lJYAKg=
|
|||||||
fyne.io/systray v1.11.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
|
fyne.io/systray v1.11.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
|
||||||
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
||||||
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
|
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
|
||||||
|
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
@ -53,12 +56,22 @@ github.com/hack-pad/go-indexeddb v0.3.2 h1:DTqeJJYc1usa45Q5r52t01KhvlSN02+Oq+tQb
|
|||||||
github.com/hack-pad/go-indexeddb v0.3.2/go.mod h1:QvfTevpDVlkfomY498LhstjwbPW6QC4VC/lxYb0Kom0=
|
github.com/hack-pad/go-indexeddb v0.3.2/go.mod h1:QvfTevpDVlkfomY498LhstjwbPW6QC4VC/lxYb0Kom0=
|
||||||
github.com/hack-pad/safejs v0.1.0 h1:qPS6vjreAqh2amUqj4WNG1zIw7qlRQJ9K10eDKMCnE8=
|
github.com/hack-pad/safejs v0.1.0 h1:qPS6vjreAqh2amUqj4WNG1zIw7qlRQJ9K10eDKMCnE8=
|
||||||
github.com/hack-pad/safejs v0.1.0/go.mod h1:HdS+bKF1NrE72VoXZeWzxFOVQVUSqZJAG0xNCnb+Tio=
|
github.com/hack-pad/safejs v0.1.0/go.mod h1:HdS+bKF1NrE72VoXZeWzxFOVQVUSqZJAG0xNCnb+Tio=
|
||||||
|
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/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade h1:FmusiCI1wHw+XQbvL9M+1r/C3SPqKrmBaIOYwVfQoDE=
|
github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade h1:FmusiCI1wHw+XQbvL9M+1r/C3SPqKrmBaIOYwVfQoDE=
|
||||||
github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o=
|
github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o=
|
||||||
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
||||||
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
||||||
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M=
|
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M=
|
||||||
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
|
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
|
||||||
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
@ -77,8 +90,6 @@ github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6
|
|||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.5.1 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk=
|
github.com/nicksnyder/go-i18n/v2 v2.5.1 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.5.1/go.mod h1:DrhgsSDZxoAfvVrBVLXoxZn/pN5TXqaDbq7ju94viiQ=
|
github.com/nicksnyder/go-i18n/v2 v2.5.1/go.mod h1:DrhgsSDZxoAfvVrBVLXoxZn/pN5TXqaDbq7ju94viiQ=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
|
||||||
github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
|
github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
|
||||||
github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
|
github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
@ -89,6 +100,8 @@ github.com/pressly/goose/v3 v3.25.0 h1:6WeYhMWGRCzpyd89SpODFnCBCKz41KrVbRT58nVjG
|
|||||||
github.com/pressly/goose/v3 v3.25.0/go.mod h1:4hC1KrritdCxtuFsqgs1R4AU5bWtTAf+cnWvfhf2DNY=
|
github.com/pressly/goose/v3 v3.25.0/go.mod h1:4hC1KrritdCxtuFsqgs1R4AU5bWtTAf+cnWvfhf2DNY=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
github.com/rymdport/portal v0.4.1 h1:2dnZhjf5uEaeDjeF/yBIeeRo6pNI2QAKm7kq1w/kbnA=
|
github.com/rymdport/portal v0.4.1 h1:2dnZhjf5uEaeDjeF/yBIeeRo6pNI2QAKm7kq1w/kbnA=
|
||||||
github.com/rymdport/portal v0.4.1/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4=
|
github.com/rymdport/portal v0.4.1/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4=
|
||||||
github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE=
|
github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE=
|
||||||
@ -103,6 +116,9 @@ github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiY
|
|||||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q=
|
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q=
|
||||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ=
|
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ=
|
||||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE=
|
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE=
|
||||||
|
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=
|
||||||
github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8=
|
github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8=
|
||||||
github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||||
@ -115,31 +131,34 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo
|
|||||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
|
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||||
|
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
|
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
|
||||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
|
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
|
||||||
golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
|
golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
|
||||||
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
|
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
|
||||||
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||||
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||||
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
modernc.org/cc/v4 v4.26.2 h1:991HMkLjJzYBIfha6ECZdjrIYz2/1ayr+FL8GN+CNzM=
|
modernc.org/cc/v4 v4.26.2 h1:991HMkLjJzYBIfha6ECZdjrIYz2/1ayr+FL8GN+CNzM=
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"bunker/commons"
|
"bunker/commons"
|
||||||
"bunker/server/internal/application"
|
"bunker/server/internal/application"
|
||||||
|
"bunker/server/internal/services/core/database"
|
||||||
|
"bunker/server/internal/services/core/httpserver"
|
||||||
|
"bunker/server/internal/services/core/options"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -26,6 +30,16 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkError(lgr, database.Initialize(app))
|
||||||
|
checkError(lgr, options.Initialize(app))
|
||||||
|
checkError(lgr, httpserver.Initialize(app))
|
||||||
|
|
||||||
|
if err := app.Start(); err != nil {
|
||||||
|
lgr.Error("Failed to start bunkerd", "error", err.Error())
|
||||||
|
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
lgr.Info("bunkerd started.")
|
lgr.Info("bunkerd started.")
|
||||||
|
|
||||||
<-app.ShutdownChan()
|
<-app.ShutdownChan()
|
||||||
@ -37,3 +51,13 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkError(logger *slog.Logger, err error) {
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Error("Failed to initialize bunkerd.", "error", err.Error())
|
||||||
|
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
@ -3,12 +3,37 @@ package application
|
|||||||
import (
|
import (
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultLogLevel slog.Level = slog.LevelInfo
|
||||||
|
|
||||||
|
logLevelEnvVar = "BUNKERD_LOG_LEVEL"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *Application) initializeLogger() {
|
func (a *Application) initializeLogger() {
|
||||||
|
logLevel := defaultLogLevel
|
||||||
|
|
||||||
|
logLevelAsString, found := os.LookupEnv(logLevelEnvVar)
|
||||||
|
if found {
|
||||||
|
switch strings.ToLower(logLevelAsString) {
|
||||||
|
case "debug":
|
||||||
|
logLevel = slog.LevelDebug
|
||||||
|
case "info":
|
||||||
|
logLevel = slog.LevelInfo
|
||||||
|
case "warn":
|
||||||
|
logLevel = slog.LevelWarn
|
||||||
|
case "error":
|
||||||
|
logLevel = slog.LevelError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Warn("Setting log level.", "level", logLevel.String())
|
||||||
|
|
||||||
a.baseLogger = slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
|
a.baseLogger = slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
|
||||||
AddSource: true,
|
AddSource: true,
|
||||||
Level: slog.LevelDebug,
|
Level: logLevel,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
a.appLogger = a.baseLogger.With("module", "application")
|
a.appLogger = a.baseLogger.With("module", "application")
|
||||||
|
44
server/internal/services/core/database.go
Normal file
44
server/internal/services/core/database.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"io/fs"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ServiceNameDatabase is a name for database service.
|
||||||
|
const ServiceNameDatabase = "core/database"
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrDatabase indicates that error appeared somewhere in database service.
|
||||||
|
ErrDatabase = errors.New("database service")
|
||||||
|
// ErrDatabaseIsInvalid indicates that database service implementation is invalid.
|
||||||
|
ErrDatabaseIsInvalid = errors.New("database service implementation is invalid")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Database is an interface for database service.
|
||||||
|
type Database interface {
|
||||||
|
// Exec is a proxy for ExecContext from sqlx.
|
||||||
|
Exec(ctx context.Context, query string, params ...interface{}) error
|
||||||
|
// Get is a proxy for GetContext from sqlx.
|
||||||
|
Get(ctx context.Context, target interface{}, query string, params ...interface{}) error
|
||||||
|
// NamedExec is a proxy for NamedExecContext from sqlx.
|
||||||
|
NamedExec(ctx context.Context, query string, param interface{}) error
|
||||||
|
// RegisterMigrations registers migrations for applying from other services. Migrations should reside
|
||||||
|
// in "migrations" directory in passed filesystem.
|
||||||
|
RegisterMigrations(moduleName string, fs fs.FS) error
|
||||||
|
// Select is a proxy for SelectContext from sqlx.
|
||||||
|
Select(ctx context.Context, target interface{}, query string, params ...interface{}) error
|
||||||
|
// Transaction is a wrapper for transactions processing which wraps sqlx's transactions.
|
||||||
|
Transaction(ctx context.Context) (DatabaseTransaction, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DatabaseTransaction is an interface for database transactions controllers implementations.
|
||||||
|
type DatabaseTransaction interface {
|
||||||
|
Apply(steps ...TransactionFunc) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransactionFunc is a function that is used in transactions to mangle with data.
|
||||||
|
type TransactionFunc func(*sqlx.Tx) error
|
83
server/internal/services/core/database/connection.go
Normal file
83
server/internal/services/core/database/connection.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
// postgresql driver.
|
||||||
|
_ "github.com/jackc/pgx/v5/stdlib"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
databaseDSNEnvVar = "BUNKERD_DATABASE_DSN"
|
||||||
|
databaseMaxIdleConnsEnvVar = "BUNKERD_DATABASE_MAX_IDLE_CONNS"
|
||||||
|
databaseMaxOpenedConnsEnvVar = "BUNKERD_DATABASE_MAX_OPENED_CONNS"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errDSNInvalid = errors.New("BUNKERD_DATABASE_DSN environment variable is empty or invalid")
|
||||||
|
errNoMaxIdleConns = errors.New("no BUNKERD_DATABASE_MAX_IDLE_CONNS defined")
|
||||||
|
errNoMaxOpenedConns = errors.New("no BUNKERD_DATABASE_MAX_OPENED_CONNS defined")
|
||||||
|
errPostgresOnlySupported = errors.New("only PostgreSQL database is currently supported")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (d *database) initializeConnection() error {
|
||||||
|
// Getting database DSN from environment as well as other required settings.
|
||||||
|
dsn, found := os.LookupEnv(databaseDSNEnvVar)
|
||||||
|
if !found {
|
||||||
|
return fmt.Errorf("initialize connection: getting database DSN: %w", errDSNInvalid)
|
||||||
|
}
|
||||||
|
|
||||||
|
maxOpenedConnsRaw, found := os.LookupEnv(databaseMaxOpenedConnsEnvVar)
|
||||||
|
if !found {
|
||||||
|
return fmt.Errorf("initialize connection: getting maximum number of opened conections: %w", errNoMaxOpenedConns)
|
||||||
|
}
|
||||||
|
|
||||||
|
maxOpenedConns, err := strconv.ParseInt(maxOpenedConnsRaw, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("initialize connection: parsing maximum number of opened conections: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
maxIdleConnsRaw, found := os.LookupEnv(databaseMaxIdleConnsEnvVar)
|
||||||
|
if !found {
|
||||||
|
return fmt.Errorf("initialize connection: getting maximum number of idle conections: %w", errNoMaxIdleConns)
|
||||||
|
}
|
||||||
|
|
||||||
|
maxIdleConns, err := strconv.ParseInt(maxIdleConnsRaw, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("initialize connection: parsing maximum number of opened conections: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// While database/sql (and sqlx) supports all possible DSN formats, we will force user to use DSN in form
|
||||||
|
// "proto://user:passowrd@host:port/dbname" as it is easier to parse.
|
||||||
|
if _, err := url.Parse(dsn); err != nil {
|
||||||
|
return fmt.Errorf("initialize connection: validate DSN: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Currently we're support only postgresql, but this may change in future.
|
||||||
|
if !strings.HasPrefix(dsn, "postgres://") {
|
||||||
|
return fmt.Errorf("initialize connection: validate DSN: %w", errPostgresOnlySupported)
|
||||||
|
}
|
||||||
|
|
||||||
|
proto := strings.Split(dsn, ":")[0]
|
||||||
|
if proto == "postgres" {
|
||||||
|
proto = "pgx"
|
||||||
|
}
|
||||||
|
|
||||||
|
db, err := sqlx.Open(proto, dsn)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("initialize connection: open database: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.db = db
|
||||||
|
|
||||||
|
d.db.SetMaxOpenConns(int(maxOpenedConns))
|
||||||
|
d.db.SetMaxIdleConns(int(maxIdleConns))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
73
server/internal/services/core/database/database.go
Normal file
73
server/internal/services/core/database/database.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"bunker/server/internal/application"
|
||||||
|
"bunker/server/internal/services/core"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = core.Database(&database{})
|
||||||
|
|
||||||
|
type database struct {
|
||||||
|
app *application.Application
|
||||||
|
db *sqlx.DB
|
||||||
|
logger *slog.Logger
|
||||||
|
migrations map[string]fs.FS
|
||||||
|
version int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize initializes service.
|
||||||
|
func Initialize(app *application.Application) error {
|
||||||
|
db := &database{
|
||||||
|
app: app,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := app.RegisterService(db); err != nil {
|
||||||
|
return fmt.Errorf("%w: %w", core.ErrDatabase, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *database) Configure() error {
|
||||||
|
if err := d.initializeConnection(); err != nil {
|
||||||
|
return fmt.Errorf("configure: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *database) ConnectDependencies() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *database) Initialize() error {
|
||||||
|
d.logger = d.app.NewLogger("service", core.ServiceNameDatabase)
|
||||||
|
|
||||||
|
d.logger.Info("Initializing...")
|
||||||
|
|
||||||
|
d.migrations = make(map[string]fs.FS, 0)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *database) Name() string {
|
||||||
|
return core.ServiceNameDatabase
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *database) LaunchStartupTasks() error {
|
||||||
|
if err := d.applyMigrations(); err != nil {
|
||||||
|
return fmt.Errorf("launch startup tasks: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *database) Shutdown() error {
|
||||||
|
return nil
|
||||||
|
}
|
82
server/internal/services/core/database/migrations.go
Normal file
82
server/internal/services/core/database/migrations.go
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"log/slog"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"bunker/commons"
|
||||||
|
"bunker/server/internal/services/core"
|
||||||
|
|
||||||
|
"github.com/pressly/goose/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errMigrationsAlreadyRegistered = errors.New("migrations already registered")
|
||||||
|
|
||||||
|
func (d *database) applyMigrations() error {
|
||||||
|
d.logger.Info("Migrating database...")
|
||||||
|
|
||||||
|
modules := make([]string, 0)
|
||||||
|
|
||||||
|
for module := range d.migrations {
|
||||||
|
modules = append(modules, module)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(modules)
|
||||||
|
|
||||||
|
_ = goose.SetDialect(string(goose.DialectPostgres))
|
||||||
|
|
||||||
|
gooseLogger := commons.NewGooseLogger(d.logger)
|
||||||
|
goose.SetLogger(gooseLogger)
|
||||||
|
|
||||||
|
for _, module := range modules {
|
||||||
|
d.logger.Info("Migrating database for module...", "module", module)
|
||||||
|
|
||||||
|
goose.SetBaseFS(d.migrations[module])
|
||||||
|
goose.SetTableName(strings.ReplaceAll(module, "/", "_") + "_migrations")
|
||||||
|
|
||||||
|
if err := goose.Up(d.db.DB, "migrations"); err != nil {
|
||||||
|
return fmt.Errorf("%w: applying migrations for module '%s': %w", core.ErrDatabase, module, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleDBVersion, err := goose.GetDBVersion(d.db.DB)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: get database version for module '%s': %w", core.ErrDatabase, module, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.version += moduleDBVersion
|
||||||
|
|
||||||
|
d.logger.Info(
|
||||||
|
"Database for module migrated to latest version",
|
||||||
|
"module", module,
|
||||||
|
"module_db_version", moduleDBVersion,
|
||||||
|
"db_version", d.version,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Info("Database migrated.", "version", d.version)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *database) RegisterMigrations(moduleName string, fSys fs.FS) error {
|
||||||
|
slog.Debug("Registering migrations for service.", "service", moduleName)
|
||||||
|
|
||||||
|
if _, found := d.migrations[moduleName]; found {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"%w: RegisterMigrations: module '%s': %w",
|
||||||
|
core.ErrDatabase,
|
||||||
|
moduleName,
|
||||||
|
errMigrationsAlreadyRegistered,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.migrations[moduleName] = fSys
|
||||||
|
|
||||||
|
slog.Debug("Migrations for service successfully registered.", "service", moduleName)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
69
server/internal/services/core/database/queries.go
Normal file
69
server/internal/services/core/database/queries.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"bunker/server/internal/services/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Exec is a proxy for ExecContext from sqlx.
|
||||||
|
func (d *database) Exec(ctx context.Context, query string, params ...interface{}) error {
|
||||||
|
if strings.Contains(query, "?") {
|
||||||
|
query = d.db.Rebind(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Debug("Executing query.", "query", query, "params", fmt.Sprintf("%+v", params))
|
||||||
|
|
||||||
|
if _, err := d.db.ExecContext(ctx, query, params...); err != nil {
|
||||||
|
return fmt.Errorf("%w: failed to Exec(): %w", core.ErrDatabase, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get is a proxy for GetContext from sqlx.
|
||||||
|
func (d *database) Get(ctx context.Context, target interface{}, query string, params ...interface{}) error {
|
||||||
|
if strings.Contains(query, "?") {
|
||||||
|
query = d.db.Rebind(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Debug("Getting single data from database with query.", "query", query, "params", fmt.Sprintf("%+v", params))
|
||||||
|
|
||||||
|
if err := d.db.GetContext(ctx, target, query, params...); err != nil {
|
||||||
|
return fmt.Errorf("%w: failed to Get(): %w", core.ErrDatabase, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NamedExec is a proxy for NamedExecContext from sqlx.
|
||||||
|
func (d *database) NamedExec(ctx context.Context, query string, param interface{}) error {
|
||||||
|
if strings.Contains(query, "?") {
|
||||||
|
query = d.db.Rebind(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Debug("Executing named query.", "query", query, "params", fmt.Sprintf("%+v", param))
|
||||||
|
|
||||||
|
if _, err := d.db.NamedExecContext(ctx, query, param); err != nil {
|
||||||
|
return fmt.Errorf("%w: failed to NamedExec(): %w", core.ErrDatabase, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select is a proxy for SelectContext from sqlx.
|
||||||
|
func (d *database) Select(ctx context.Context, target interface{}, query string, params ...interface{}) error {
|
||||||
|
if strings.Contains(query, "?") {
|
||||||
|
query = d.db.Rebind(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logger.Debug("Selecting from database with query.", "query", query, "params", fmt.Sprintf("%+v", params))
|
||||||
|
|
||||||
|
if err := d.db.SelectContext(ctx, target, query, params...); err != nil {
|
||||||
|
return fmt.Errorf("%w: failed to Select(): %w", core.ErrDatabase, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
81
server/internal/services/core/database/transaction.go
Normal file
81
server/internal/services/core/database/transaction.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"bunker/server/internal/services/core"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type transaction struct {
|
||||||
|
transaction *sqlx.Tx
|
||||||
|
logger *slog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *database) Transaction(ctx context.Context) (core.DatabaseTransaction, error) {
|
||||||
|
txn, err := d.db.BeginTxx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%w: starting transaction: %w", core.ErrDatabase, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
txHandler := &transaction{
|
||||||
|
transaction: txn,
|
||||||
|
logger: d.logger.With("module", "transactioner"),
|
||||||
|
}
|
||||||
|
|
||||||
|
return txHandler, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *transaction) Apply(steps ...core.TransactionFunc) error {
|
||||||
|
for stepNumber, stepFunc := range steps {
|
||||||
|
if err := stepFunc(t.transaction); err != nil {
|
||||||
|
t.logger.Error(
|
||||||
|
"Error occurred.",
|
||||||
|
"step", stepNumber,
|
||||||
|
"error", err.Error(),
|
||||||
|
"module", "core/database",
|
||||||
|
"subsystem", "transaction",
|
||||||
|
)
|
||||||
|
|
||||||
|
if rollbackErr := t.transaction.Rollback(); rollbackErr != nil {
|
||||||
|
t.logger.Error(
|
||||||
|
"Transaction rollback failed.",
|
||||||
|
"error", err.Error(),
|
||||||
|
"module", "core/database",
|
||||||
|
"subsystem", "transaction",
|
||||||
|
)
|
||||||
|
|
||||||
|
return fmt.Errorf("%w: transaction rollback: %w", core.ErrDatabase, rollbackErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := t.transaction.Commit(); err != nil {
|
||||||
|
t.logger.Error(
|
||||||
|
"Transaction commit failed.",
|
||||||
|
"error", err.Error(),
|
||||||
|
"module", "core/database",
|
||||||
|
"subsystem", "transaction",
|
||||||
|
)
|
||||||
|
|
||||||
|
if rollbackErr := t.transaction.Rollback(); rollbackErr != nil {
|
||||||
|
t.logger.Error(
|
||||||
|
"Transaction rollback failed.",
|
||||||
|
"error", err.Error(),
|
||||||
|
"module", "core/database",
|
||||||
|
"subsystem", "transaction",
|
||||||
|
)
|
||||||
|
|
||||||
|
return fmt.Errorf("%w: transaction rollback: %w", core.ErrDatabase, rollbackErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("%w: transaction commit: %w", core.ErrDatabase, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
14
server/internal/services/core/httpserver.go
Normal file
14
server/internal/services/core/httpserver.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ServiceNameHTTPServer is a name for HTTP server service.
|
||||||
|
const ServiceNameHTTPServer = "core/http_server"
|
||||||
|
|
||||||
|
// ErrHTTPServerIsInvalid appears when HTTP server service implementation is invalid.
|
||||||
|
var ErrHTTPServerIsInvalid = errors.New("HTTP server service implementation is invalid")
|
||||||
|
|
||||||
|
// HTTPServer is an interface for HTTP server service.
|
||||||
|
type HTTPServer interface{}
|
91
server/internal/services/core/httpserver/httpserver.go
Normal file
91
server/internal/services/core/httpserver/httpserver.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package httpserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"bunker/server/internal/application"
|
||||||
|
"bunker/server/internal/services/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ = core.HTTPServer(&httpServer{})
|
||||||
|
|
||||||
|
errHTTPServer = errors.New("HTTP server core service")
|
||||||
|
)
|
||||||
|
|
||||||
|
type httpServer struct {
|
||||||
|
app *application.Application
|
||||||
|
logger *slog.Logger
|
||||||
|
db core.Database
|
||||||
|
httpSrv *http.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize initializes service.
|
||||||
|
func Initialize(app *application.Application) error {
|
||||||
|
httpSrv := &httpServer{
|
||||||
|
app: app,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := app.RegisterService(httpSrv); err != nil {
|
||||||
|
return fmt.Errorf("%w: %w", errHTTPServer, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *httpServer) Configure() error {
|
||||||
|
h.logger.Debug("Configuring service...")
|
||||||
|
|
||||||
|
if err := h.configureHTTPServer(); err != nil {
|
||||||
|
return fmt.Errorf("configure: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *httpServer) ConnectDependencies() error {
|
||||||
|
databaseRaw := h.app.Service(core.ServiceNameDatabase)
|
||||||
|
if databaseRaw == nil {
|
||||||
|
return fmt.Errorf("connect dependencies: get database service: %w", application.ErrServiceNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
database, valid := databaseRaw.(core.Database)
|
||||||
|
if !valid {
|
||||||
|
return fmt.Errorf("connect dependencies: type assert database service: %w", core.ErrDatabaseIsInvalid)
|
||||||
|
}
|
||||||
|
|
||||||
|
h.db = database
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *httpServer) Initialize() error {
|
||||||
|
h.logger = h.app.NewLogger("service", core.ServiceNameHTTPServer)
|
||||||
|
|
||||||
|
h.logger.Info("Initializing...")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *httpServer) Name() string {
|
||||||
|
return core.ServiceNameHTTPServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *httpServer) LaunchStartupTasks() error {
|
||||||
|
h.logger.Debug("Launching startup tasks...")
|
||||||
|
|
||||||
|
go h.startHTTPServer()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *httpServer) Shutdown() error {
|
||||||
|
if err := h.stopHTTPServer(); err != nil {
|
||||||
|
return fmt.Errorf("%w: Shutdown: %w", errHTTPServer, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
85
server/internal/services/core/httpserver/server.go
Normal file
85
server/internal/services/core/httpserver/server.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package httpserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/coder/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
const httpServerAddrEnvVar = "BUNKERD_HTTP_ADDRESS"
|
||||||
|
|
||||||
|
var (
|
||||||
|
errHTTPServerAddrInvalid = errors.New("BUNKERD_HTTP_ADDRESS environment variable contains invalid address to " +
|
||||||
|
"listen, should be 'host:port'")
|
||||||
|
errHTTPServerAddrNotFound = errors.New("BUNKERD_HTTP_ADDRESS environment variable empty")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *httpServer) configureHTTPServer() error {
|
||||||
|
httpSrvAddr, found := os.LookupEnv(httpServerAddrEnvVar)
|
||||||
|
if !found {
|
||||||
|
return fmt.Errorf("configure HTTP server: get address from environment variable: %w", errHTTPServerAddrNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
host, port, err := net.SplitHostPort(httpSrvAddr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("configure HTTP server: validate HTTP server address: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if httpSrvAddr != host+":"+port {
|
||||||
|
return fmt.Errorf("configure HTTP server: validate HTTP server address: %w", errHTTPServerAddrInvalid)
|
||||||
|
}
|
||||||
|
|
||||||
|
mux := new(http.ServeMux)
|
||||||
|
mux.HandleFunc("GET /api/v1/socket", h.handleWebsocketRequest)
|
||||||
|
|
||||||
|
h.httpSrv = &http.Server{
|
||||||
|
Addr: httpSrvAddr,
|
||||||
|
Handler: mux,
|
||||||
|
ReadHeaderTimeout: time.Second * 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *httpServer) handleWebsocketRequest(w http.ResponseWriter, r *http.Request) {
|
||||||
|
wsConn, err := websocket.Accept(w, r, &websocket.AcceptOptions{
|
||||||
|
OnPingReceived: func(_ context.Context, _ []byte) bool {
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("Failed to accept WS connection!", "error", err.Error())
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := wsConn.CloseNow(); err != nil {
|
||||||
|
h.logger.Warn("Failed to close WS connection in defer!", "error", err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *httpServer) startHTTPServer() {
|
||||||
|
h.logger.Info("Starting listening for HTTP requests.", "address", h.httpSrv.Addr)
|
||||||
|
|
||||||
|
if err := h.httpSrv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
|
h.logger.Warn("Error when listening to ", "error", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *httpServer) stopHTTPServer() error {
|
||||||
|
h.logger.Info("Stopping HTTP server...")
|
||||||
|
|
||||||
|
if err := h.httpSrv.Shutdown(h.app.ContextWithTimeout(time.Second * 3)); err != nil {
|
||||||
|
return fmt.Errorf("stopping HTTP server: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
14
server/internal/services/core/options.go
Normal file
14
server/internal/services/core/options.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ServiceNameOptions is a name for options service which controls options storage.
|
||||||
|
const ServiceNameOptions = "core/options"
|
||||||
|
|
||||||
|
// ErrOptionsIsInvalid appears when options service implementation is invalid.
|
||||||
|
var ErrOptionsIsInvalid = errors.New("options service implementation is invalid")
|
||||||
|
|
||||||
|
// Options is an interface for options service.
|
||||||
|
type Options interface{}
|
17
server/internal/services/core/options/migrations.go
Normal file
17
server/internal/services/core/options/migrations.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package options
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed migrations
|
||||||
|
var migrations embed.FS
|
||||||
|
|
||||||
|
func (o *options) registerMigrations() error {
|
||||||
|
if err := o.db.RegisterMigrations("core/options", migrations); err != nil {
|
||||||
|
return fmt.Errorf("register migrations: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
-- +goose Up
|
||||||
|
CREATE TABLE IF NOT EXISTS options (
|
||||||
|
id UUID NOT NULL PRIMARY KEY,
|
||||||
|
user_id UUID NOT NULL,
|
||||||
|
key VARCHAR(1024) NOT NULL,
|
||||||
|
value VARCHAR(8192)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS options_user_id_key_idx ON options(user_id, key);
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
DROP TABLE IF EXISTS options;
|
79
server/internal/services/core/options/options.go
Normal file
79
server/internal/services/core/options/options.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package options
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"bunker/server/internal/application"
|
||||||
|
"bunker/server/internal/services/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ = core.Options(&options{})
|
||||||
|
|
||||||
|
errOptions = errors.New("options core service")
|
||||||
|
)
|
||||||
|
|
||||||
|
type options struct {
|
||||||
|
app *application.Application
|
||||||
|
logger *slog.Logger
|
||||||
|
db core.Database
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize initializes service.
|
||||||
|
func Initialize(app *application.Application) error {
|
||||||
|
opts := &options{
|
||||||
|
app: app,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := app.RegisterService(opts); err != nil {
|
||||||
|
return fmt.Errorf("%w: %w", errOptions, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *options) Configure() error {
|
||||||
|
if err := o.registerMigrations(); err != nil {
|
||||||
|
return fmt.Errorf("configure: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *options) ConnectDependencies() error {
|
||||||
|
databaseRaw := o.app.Service(core.ServiceNameDatabase)
|
||||||
|
if databaseRaw == nil {
|
||||||
|
return fmt.Errorf("connect dependencies: get database service: %w", application.ErrServiceNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
database, valid := databaseRaw.(core.Database)
|
||||||
|
if !valid {
|
||||||
|
return fmt.Errorf("connect dependencies: type assert database service: %w", core.ErrDatabaseIsInvalid)
|
||||||
|
}
|
||||||
|
|
||||||
|
o.db = database
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *options) Initialize() error {
|
||||||
|
o.logger = o.app.NewLogger("service", core.ServiceNameOptions)
|
||||||
|
|
||||||
|
o.logger.Info("Initializing...")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *options) Name() string {
|
||||||
|
return core.ServiceNameOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *options) LaunchStartupTasks() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *options) Shutdown() error {
|
||||||
|
return nil
|
||||||
|
}
|
@ -4,16 +4,20 @@ version: "3"
|
|||||||
includes:
|
includes:
|
||||||
bunkerd: ./bunkerd
|
bunkerd: ./bunkerd
|
||||||
common: ./common
|
common: ./common
|
||||||
|
postgresql: ./postgresql
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
down:
|
down:
|
||||||
desc: "Removes development environment."
|
desc: "Removes development environment."
|
||||||
cmds:
|
cmds:
|
||||||
- task: bunkerd:down
|
- task: bunkerd:down
|
||||||
|
- task: bunkerd:down-debug
|
||||||
|
- task: postgresql:down
|
||||||
- task: common:network-down
|
- task: common:network-down
|
||||||
|
|
||||||
up:
|
up:
|
||||||
desc: "Creates development environment."
|
desc: "Creates development environment."
|
||||||
cmds:
|
cmds:
|
||||||
- task: common:network-up
|
- task: common:network-up
|
||||||
|
- task: postgresql:up
|
||||||
- task: bunkerd:up
|
- task: bunkerd:up
|
||||||
|
@ -14,7 +14,11 @@ services:
|
|||||||
bunkerd:
|
bunkerd:
|
||||||
ipv4_address: 247.247.0.3
|
ipv4_address: 247.247.0.3
|
||||||
environment:
|
environment:
|
||||||
BUNKERD_DATABASE_DSN: " "
|
BUNKERD_DATABASE_MAX_IDLE_CONNS: 5
|
||||||
|
BUNKERD_DATABASE_MAX_OPENED_CONNS: 20
|
||||||
|
BUNKERD_DATABASE_DSN: "postgres://postgres:postgres@247.247.0.4:5432/bunkerd?sslmode=disable"
|
||||||
|
BUNKERD_HTTP_ADDRESS: "0.0.0.0:53400"
|
||||||
|
BUNKERD_LOG_LEVEL: "debug"
|
||||||
cap_add:
|
cap_add:
|
||||||
- SYS_PTRACE
|
- SYS_PTRACE
|
||||||
|
|
||||||
|
@ -13,7 +13,11 @@ services:
|
|||||||
bunkerd:
|
bunkerd:
|
||||||
ipv4_address: 247.247.0.2
|
ipv4_address: 247.247.0.2
|
||||||
environment:
|
environment:
|
||||||
BUNKERD_DATABASE_DSN: " "
|
BUNKERD_DATABASE_MAX_IDLE_CONNS: 5
|
||||||
|
BUNKERD_DATABASE_MAX_OPENED_CONNS: 20
|
||||||
|
BUNKERD_DATABASE_DSN: "postgres://postgres:postgres@247.247.0.4:5432/bunkerd?sslmode=disable"
|
||||||
|
BUNKERD_HTTP_ADDRESS: "0.0.0.0:53400"
|
||||||
|
BUNKERD_LOG_LEVEL: "debug"
|
||||||
cap_add:
|
cap_add:
|
||||||
- SYS_PTRACE
|
- SYS_PTRACE
|
||||||
|
|
||||||
|
40
server/localdevzone/postgresql/Taskfile.yml
Normal file
40
server/localdevzone/postgresql/Taskfile.yml
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
version: "3"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
down:
|
||||||
|
desc: "Deletes PostgreSQL and it's data from Docker (down)."
|
||||||
|
dir: "./server/localdevzone/postgresql"
|
||||||
|
cmds:
|
||||||
|
- docker compose -p bunkerd-postgres -f docker-compose.yaml down --volumes
|
||||||
|
|
||||||
|
logs:
|
||||||
|
desc: "Show PostgreSQL logs."
|
||||||
|
dir: "./server/localdevzone/postgresql"
|
||||||
|
cmds:
|
||||||
|
- docker compose -p bunkerd-postgres -f docker-compose.yaml logs -f
|
||||||
|
|
||||||
|
restart:
|
||||||
|
desc: "Restarts PostgreSQL."
|
||||||
|
dir: "./server/localdevzone/postgresql"
|
||||||
|
cmds:
|
||||||
|
- docker compose -p bunkerd-postgres -f docker-compose.yaml restart
|
||||||
|
|
||||||
|
start:
|
||||||
|
desc: "Starts PostgreSQL in Docker."
|
||||||
|
dir: "./server/localdevzone/postgresql"
|
||||||
|
cmds:
|
||||||
|
- docker compose -p bunkerd-postgres -f docker-compose.yaml start
|
||||||
|
|
||||||
|
stop:
|
||||||
|
desc: "Stops PostgreSQL in Docker."
|
||||||
|
dir: "./server/localdevzone/postgresql"
|
||||||
|
cmds:
|
||||||
|
- docker compose -p bunkerd-postgres -f docker-compose.yaml stop
|
||||||
|
|
||||||
|
up:
|
||||||
|
desc: "Starts PostgreSQL in Docker (up -d)."
|
||||||
|
dir: "./server/localdevzone/postgresql"
|
||||||
|
cmds:
|
||||||
|
- task: :common:network-up
|
||||||
|
- docker compose -p bunkerd-postgres -f docker-compose.yaml up -d
|
25
server/localdevzone/postgresql/docker-compose.yaml
Normal file
25
server/localdevzone/postgresql/docker-compose.yaml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
services:
|
||||||
|
postgresql:
|
||||||
|
container_name: bunkerd-postgresql
|
||||||
|
image: postgres:17.6-alpine
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=postgres
|
||||||
|
- POSTGRES_PASSWORD=postgres
|
||||||
|
- POSTGRES_DB=bunkerd
|
||||||
|
- PGDATA=/var/lib/postgresql/data
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:53500:5432"
|
||||||
|
volumes:
|
||||||
|
- "bunkerd_localdevzone_postgresql:/var/lib/postgresql/data"
|
||||||
|
networks:
|
||||||
|
bunkerd:
|
||||||
|
ipv4_address: 247.247.0.4
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
bunkerd_localdevzone_postgresql:
|
||||||
|
external: false
|
||||||
|
|
||||||
|
networks:
|
||||||
|
bunkerd:
|
||||||
|
external: true
|
Loading…
x
Reference in New Issue
Block a user