Skip to content

Commit 8c4f7f9

Browse files
authored
Merge pull request #8 from chadleeshaw/feature/architecture-refactor
Feature/architecture refactor
2 parents 5abcf0c + ab242f2 commit 8c4f7f9

58 files changed

Lines changed: 5682 additions & 2162 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
.DS_Store
33
.idea
44
.codegpt
5+
.claude
6+
.grok
57
ignite.db
8+
ignite
69
bin
710
public/node_modules
811
public/http/css/tailwind.css

app/application.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package app
2+
3+
import (
4+
"context"
5+
"embed"
6+
"fmt"
7+
"log"
8+
"net/http"
9+
"os"
10+
"os/signal"
11+
"syscall"
12+
"time"
13+
14+
"ignite/handlers"
15+
"ignite/routes"
16+
"ignite/tftp"
17+
)
18+
19+
// Application represents the main application with embedded static files
20+
type Application struct {
21+
container *Container
22+
httpServer *http.Server
23+
tftpServer *tftp.Server
24+
staticFS embed.FS
25+
staticHandler *handlers.StaticHandlers
26+
}
27+
28+
// NewApplicationWithStatic creates a new application instance with embedded static files
29+
func NewApplicationWithStatic(staticFS embed.FS) (*Application, error) {
30+
container, err := NewContainer()
31+
if err != nil {
32+
return nil, fmt.Errorf("failed to create container: %w", err)
33+
}
34+
35+
// Create static file handler
36+
staticHandler := handlers.NewStaticHandlers(staticFS, container.Config.HTTP.Dir)
37+
38+
return &Application{
39+
container: container,
40+
staticFS: staticFS,
41+
staticHandler: staticHandler,
42+
}, nil
43+
}
44+
45+
// Start starts all application services including static file serving
46+
func (a *Application) Start() error {
47+
// Start TFTP server
48+
a.tftpServer = tftp.NewServer(a.container.Config.TFTP.Dir)
49+
if err := a.tftpServer.Start(); err != nil {
50+
return fmt.Errorf("failed to start TFTP server: %w", err)
51+
}
52+
log.Printf("TFTP server started on port 69, serving from %s", a.container.Config.TFTP.Dir)
53+
54+
// Setup HTTP handlers with dependency injection
55+
handlerContainer := &handlers.Container{
56+
ServerService: a.container.ServerService,
57+
LeaseService: a.container.LeaseService,
58+
Config: a.container.Config,
59+
}
60+
61+
// Create HTTP router with injected dependencies and static file handling
62+
router := routes.SetupWithContainerAndStatic(handlerContainer, a.staticHandler)
63+
log.Printf("Embedded HTTP server configured")
64+
65+
// Create HTTP server
66+
a.httpServer = &http.Server{
67+
Addr: ":" + a.container.Config.HTTP.Port,
68+
Handler: router,
69+
}
70+
71+
// Start HTTP server
72+
go func() {
73+
log.Printf("HTTP API server started on port %s", a.container.Config.HTTP.Port)
74+
if err := a.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
75+
log.Fatalf("HTTP server failed: %v", err)
76+
}
77+
}()
78+
79+
return nil
80+
}
81+
82+
// Rest of the Application methods remain the same...
83+
func (a *Application) Stop() error {
84+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
85+
defer cancel()
86+
87+
if a.httpServer != nil {
88+
if err := a.httpServer.Shutdown(ctx); err != nil {
89+
log.Printf("Error shutting down HTTP server: %v", err)
90+
}
91+
}
92+
93+
if a.tftpServer != nil {
94+
a.tftpServer.Stop()
95+
}
96+
97+
if err := a.container.Close(); err != nil {
98+
log.Printf("Error closing container: %v", err)
99+
}
100+
101+
return nil
102+
}
103+
104+
func (a *Application) Run() error {
105+
if err := a.Start(); err != nil {
106+
return fmt.Errorf("failed to start application: %w", err)
107+
}
108+
109+
quit := make(chan os.Signal, 1)
110+
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
111+
112+
log.Println("Application started. Press Ctrl+C to stop.")
113+
<-quit
114+
log.Println("Shutting down application...")
115+
116+
if err := a.Stop(); err != nil {
117+
return fmt.Errorf("failed to stop application: %w", err)
118+
}
119+
120+
log.Println("Application stopped")
121+
return nil
122+
}
123+
124+
// GetContainer returns the application's container for access to services
125+
func (a *Application) GetContainer() *handlers.Container {
126+
return &handlers.Container{
127+
ServerService: a.container.ServerService,
128+
LeaseService: a.container.LeaseService,
129+
Config: a.container.Config,
130+
}
131+
}

app/application_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package app
2+
3+
import (
4+
"embed"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestNewApplicationWithStatic(t *testing.T) {
11+
// Create a simple empty embedded filesystem for testing
12+
testFS := embed.FS{}
13+
app, err := NewApplicationWithStatic(testFS)
14+
assert.NoError(t, err)
15+
assert.NotNil(t, app)
16+
assert.NotNil(t, app.GetContainer())
17+
assert.NotNil(t, app.GetContainer().ServerService)
18+
assert.NotNil(t, app.GetContainer().LeaseService)
19+
assert.NotNil(t, app.GetContainer().Config)
20+
}

app/container.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package app
2+
3+
import (
4+
"fmt"
5+
"ignite/config"
6+
"ignite/db"
7+
"ignite/dhcp"
8+
)
9+
10+
// Container holds all application dependencies
11+
type Container struct {
12+
Config *config.Config
13+
Database db.Database
14+
ServerRepo dhcp.ServerRepository
15+
LeaseRepo dhcp.LeaseRepository
16+
ServerService dhcp.ServerService
17+
LeaseService dhcp.LeaseService
18+
}
19+
20+
// NewContainer creates and wires up all dependencies
21+
func NewContainer() (*Container, error) {
22+
// Load configuration
23+
cfg, err := config.LoadDefault()
24+
if err != nil {
25+
return nil, fmt.Errorf("failed to load configuration: %w", err)
26+
}
27+
28+
// Initialize database
29+
database, err := db.NewBoltDB(cfg)
30+
if err != nil {
31+
return nil, fmt.Errorf("failed to initialize database: %w", err)
32+
}
33+
34+
// Create repositories
35+
serverRepo := dhcp.NewBoltServerRepository(database, cfg.DB.Bucket+"_servers")
36+
leaseRepo := dhcp.NewBoltLeaseRepository(database, cfg.DB.Bucket+"_leases")
37+
38+
// Create services
39+
serverService := dhcp.NewDHCPServerService(serverRepo, leaseRepo)
40+
leaseService := dhcp.NewDHCPLeaseService(leaseRepo, serverRepo)
41+
42+
return &Container{
43+
Config: cfg,
44+
Database: database,
45+
ServerRepo: serverRepo,
46+
LeaseRepo: leaseRepo,
47+
ServerService: serverService,
48+
LeaseService: leaseService,
49+
}, nil
50+
}
51+
52+
// Close closes all resources held by the container
53+
func (c *Container) Close() error {
54+
if c.Database != nil {
55+
return c.Database.Close()
56+
}
57+
return nil
58+
}

app/container_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package app
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestNewContainer(t *testing.T) {
10+
container, err := NewContainer()
11+
assert.NoError(t, err)
12+
assert.NotNil(t, container)
13+
assert.NotNil(t, container.Database)
14+
assert.NotNil(t, container.ServerService)
15+
assert.NotNil(t, container.LeaseService)
16+
assert.NotNil(t, container.Config)
17+
}

app/static.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package app
2+
3+
import (
4+
"embed"
5+
"io/fs"
6+
"net/http"
7+
)
8+
9+
// StaticFileSystem wraps embed.FS for serving static files
10+
type StaticFileSystem struct {
11+
embedFS embed.FS
12+
prefix string
13+
}
14+
15+
// NewStaticFileSystem creates a new static file system
16+
func NewStaticFileSystem(embedFS embed.FS, prefix string) *StaticFileSystem {
17+
return &StaticFileSystem{
18+
embedFS: embedFS,
19+
prefix: prefix,
20+
}
21+
}
22+
23+
// Open implements fs.FS interface
24+
func (sfs *StaticFileSystem) Open(name string) (fs.File, error) {
25+
return sfs.embedFS.Open(sfs.prefix + "/" + name)
26+
}
27+
28+
// HTTPHandler returns an http.Handler for serving static files
29+
func (sfs *StaticFileSystem) HTTPHandler() http.Handler {
30+
return http.FileServer(http.FS(sfs))
31+
}
32+
33+
// StripPrefix returns a handler that strips the prefix from the URL path
34+
func (sfs *StaticFileSystem) StripPrefixHandler(prefix string) http.Handler {
35+
return http.StripPrefix(prefix, sfs.HTTPHandler())
36+
}

0 commit comments

Comments
 (0)