@@ -15,160 +15,258 @@ import (
1515 "encoding/json"
1616)
1717
18+ type CustomError struct {
19+ Err error
20+ }
21+
22+ func (e * CustomError ) Error () string {
23+ return e .Err .Error ()
24+ }
25+
1826//go:embed templates/*
1927var templatesFS embed.FS
2028
21- type PortSequence struct {
22- LastPort int `json:"last_port"`
29+ // PortInfo represents a single entry in the used ports file.
30+ type PortInfo struct {
31+ Port int `json:"port"`
32+ Service string `json:"service"`
2333}
2434
35+ // UsedPorts represents the entire used ports file content.
2536type UsedPorts struct {
26- Ports []int `json:"used_ports"`
37+ Ports []PortInfo `json:"used_ports"`
2738}
2839
40+ // serviceExists checks if a directory for the service already exists.
41+ func serviceExists (serviceName string ) bool {
42+ // os.Stat gets file info. If the file doesn't exist, it returns a specific error.
43+ _ , err := os .Stat (serviceName )
44+ return ! os .IsNotExist (err )
45+ }
46+
47+ // readUsedPorts reads the used ports from a JSON file.
2948func readUsedPorts (filename string ) (* UsedPorts , error ) {
30- data := & UsedPorts {}
49+ data := & UsedPorts {}
3150
32- content , err := ioutil .ReadFile (filename )
33- if err != nil {
34- if os .IsNotExist (err ) {
35- // File doesn't exist, return empty UsedPorts
36- return data , nil
37- }
38- return nil , err
39- }
40-
41- err = json .Unmarshal (content , data )
42- if err != nil {
43- return nil , err
44- }
45- return data , nil
51+ content , err := ioutil .ReadFile (filename )
52+ if err != nil {
53+ if os .IsNotExist (err ) {
54+ // File doesn't exist, return empty UsedPorts
55+ return data , nil
56+ }
57+ return nil , err
58+ }
59+
60+ err = json .Unmarshal (content , data )
61+ if err != nil {
62+ return nil , err
63+ }
64+ return data , nil
4665}
4766
67+ // writeUsedPorts writes the used ports data to a JSON file.
4868func writeUsedPorts (filename string , data * UsedPorts ) error {
49- bytes , err := json .MarshalIndent (data , "" , " " )
50- if err != nil {
51- return err
52- }
53- return ioutil .WriteFile (filename , bytes , 0644 )
69+ bytes , err := json .MarshalIndent (data , "" , " " )
70+ if err != nil {
71+ return err
72+ }
73+ return ioutil .WriteFile (filename , bytes , 0644 )
5474}
5575
56- // Check if port is in UsedPorts.Ports slice
76+ // isPortUsed checks if a port is in the UsedPorts slice.
5777func isPortUsed (used * UsedPorts , port int ) bool {
58- for _ , p := range used .Ports {
59- if p == port {
60- return true
61- }
62- }
63- return false
78+ for _ , p := range used .Ports {
79+ if p . Port == port {
80+ return true
81+ }
82+ }
83+ return false
6484}
6585
66- // Find next available port, skipping used ports and occupied OS ports
86+ // getNextAvailablePort finds the next available port, skipping used and occupied ports.
6787func getNextAvailablePort (start int , used * UsedPorts ) (int , error ) {
68- for port := start ; port <= 65535 ; port ++ {
69- if isPortUsed (used , port ) {
70- continue
71- }
88+ for port := start ; port <= 65535 ; port ++ {
89+ if isPortUsed (used , port ) {
90+ continue
91+ }
7292
73- addr := fmt .Sprintf (":%d" , port )
74- l , err := net .Listen ("tcp" , addr )
75- if err != nil {
76- // Port in use at OS level, skip
77- continue
78- }
79- l .Close ()
80- return port , nil
81- }
82- return 0 , fmt .Errorf ("no available port found starting at %d" , start )
93+ addr := fmt .Sprintf (":%d" , port )
94+ l , err := net .Listen ("tcp" , addr )
95+ if err != nil {
96+ // Port in use at OS level, skip
97+ continue
98+ }
99+ l .Close ()
100+ return port , nil
101+ }
102+ return 0 , fmt .Errorf ("no available port found starting at %d" , start )
83103}
84104
85- func readAndIncrementPortWithUsed (start int , nextPortFile , usedPortsFile string ) (int , error ) {
86- // Read next port number
87- nextPort := start
88- portBytes , err := ioutil .ReadFile (nextPortFile )
89- if err == nil {
90- p , err := strconv .Atoi (string (portBytes ))
91- if err == nil && p >= start {
92- nextPort = p
93- }
94- }
95-
96- // Read used ports slice
97- usedPorts , err := readUsedPorts (usedPortsFile )
98- if err != nil {
99- return 0 , err
100- }
101-
102- // Get next available port skipping used
103- port , err := getNextAvailablePort (nextPort , usedPorts )
104- if err != nil {
105- return 0 , err
106- }
107-
108- // Add new port to used ports slice
109- usedPorts .Ports = append (usedPorts .Ports , port )
110-
111- // Write back updated used ports
112- err = writeUsedPorts (usedPortsFile , usedPorts )
113- if err != nil {
114- return 0 , err
115- }
116-
117- // Update next port file to port+1
118- err = ioutil .WriteFile (nextPortFile , []byte (strconv .Itoa (port + 1 )), 0644 )
119- if err != nil {
120- return 0 , err
121- }
122-
123- return port , nil
105+ // readAndIncrementPortWithUsed finds the next available port and stores it.
106+ func readAndIncrementPortWithUsed (start int , serviceName , nextPortFile , usedPortsFile string ) (int , error ) {
107+ // Read next port number
108+ nextPort := start
109+ portBytes , err := ioutil .ReadFile (nextPortFile )
110+ if err == nil {
111+ p , err := strconv .Atoi (string (portBytes ))
112+ if err == nil && p >= start {
113+ nextPort = p
114+ }
115+ }
116+
117+ // Read used ports slice
118+ usedPorts , err := readUsedPorts (usedPortsFile )
119+ if err != nil {
120+ return 0 , err
121+ }
122+
123+ // Get next available port skipping used
124+ port , err := getNextAvailablePort (nextPort , usedPorts )
125+ if err != nil {
126+ return 0 , err
127+ }
128+
129+ // Add new port to used ports slice with the service name
130+ usedPorts .Ports = append (usedPorts .Ports , PortInfo {Port : port , Service : serviceName })
131+
132+ // Write back updated used ports
133+ err = writeUsedPorts (usedPortsFile , usedPorts )
134+ if err != nil {
135+ return 0 , err
136+ }
137+
138+ // Update next port file to port+1
139+ err = ioutil .WriteFile (nextPortFile , []byte (strconv .Itoa (port + 1 )), 0644 )
140+ if err != nil {
141+ return 0 , err
142+ }
143+
144+ return port , nil
145+ }
146+
147+ // writeUsedPortForService adds a user-provided port and service to the used ports file.
148+ func writeUsedPortForService (port int , serviceName , filename string ) error {
149+ usedPorts , err := readUsedPorts (filename )
150+ if err != nil {
151+ return err
152+ }
153+
154+ // Check if the port is already in the list
155+ if ! isPortUsed (usedPorts , port ) {
156+ usedPorts .Ports = append (usedPorts .Ports , PortInfo {Port : port , Service : serviceName })
157+ return writeUsedPorts (filename , usedPorts )
158+ }
159+
160+ // Port is already in the list, no need to write.
161+ return nil
124162}
125163
126164var generateCmd = & cobra.Command {
127165 Use : "generate [service-name] [port]" ,
128166 Short : "Generate code resources" ,
129167 Long : "Generate microservice boilerplate code including router, controller, service, entity, go.mod, Dockerfile, and go.sum." ,
130- Args : func (cmd * cobra.Command , args []string ) error {
131- if len (args ) < 1 {
132- return fmt .Errorf ("requires service name argument" )
133- }
134- if len (args ) > 1 {
135- port := args [1 ]
136- if len (port ) == 0 {
137- return fmt .Errorf ("port cannot be empty string" )
138- }
139- p , err := strconv .Atoi (port )
140- if err != nil {
141- return fmt .Errorf ("port must be a valid number" )
142- }
143- if p < 1024 || p > 65535 {
144- return fmt .Errorf ("port must be between 1024 and 65535" )
168+ Args : func (cmd * cobra.Command , args []string ) error {
169+ if len (args ) < 1 {
170+ return fmt .Errorf ("requires service name argument" )
171+ }
172+ if len (args ) > 1 {
173+ port := args [1 ]
174+ if len (port ) == 0 {
175+ return fmt .Errorf ("port cannot be empty string" )
176+ }
177+ p , err := strconv .Atoi (port )
178+ if err != nil {
179+ return fmt .Errorf ("port must be a valid number" )
180+ }
181+ if p < 1024 || p > 65535 {
182+ return fmt .Errorf ("port must be between 1024 and 65535" )
183+ }
184+ }
185+ return nil
186+ },
187+ RunE : func (cmd * cobra.Command , args []string ) error {
188+ serviceName := args [0 ]
189+ usedPortsFile := "used_ports.json"
190+
191+ // 1. Check if the service name already exists.
192+ // Assuming you have a function to check for existing services.
193+ if serviceExists (serviceName ) {
194+ return fmt .Errorf ("a service with the name '%s' already exists" , serviceName )
195+ }
196+
197+ var port int
198+ if len (args ) > 1 && args [1 ] != "" {
199+ var err error
200+ port , err = strconv .Atoi (args [1 ])
201+ if err != nil {
202+ return fmt .Errorf ("port must be a valid number: %w" , err )
203+ }
204+
205+ // 2. Check if the user-provided port is already used.
206+ usedPorts , err := readUsedPorts (usedPortsFile )
207+ if err != nil {
208+ return fmt .Errorf ("failed to read used ports file: %w" , err )
209+ }
210+ if isPortUsed (usedPorts , port ) {
211+ return fmt .Errorf ("the port '%d' is already in use" , port )
212+ }
213+
214+ // Store the user-provided port with the service name.
215+ err = writeUsedPortForService (port , serviceName , usedPortsFile )
216+ if err != nil {
217+ return fmt .Errorf ("failed to store user-provided port: %w" , err )
218+ }
219+
220+ } else {
221+ // Logic for automatically finding a port remains the same.
222+ portFile := "port.json"
223+ p , err := readAndIncrementPortWithUsed (8080 , serviceName , portFile , usedPortsFile )
224+ if err != nil {
225+ return fmt .Errorf ("failed to get next available port: %w" , err )
226+ }
227+ port = p
228+ }
229+
230+ return generateService (serviceName , strconv .Itoa (port ))
231+ },
232+ }
233+
234+ // listServicesCmd is a new Cobra command to list all services and their ports.
235+ var listServicesCmd = & cobra.Command {
236+ Use : "list-services" ,
237+ Short : "List all generated services and their ports" ,
238+ Long : "Displays a list of all microservices that have been generated, along with the ports they are using." ,
239+ Run : func (cmd * cobra.Command , args []string ) {
240+ usedPortsFile := "used_ports.json"
241+
242+ usedPorts , err := readUsedPorts (usedPortsFile )
243+ if err != nil {
244+ // Check if the error is due to the file not existing.
245+ if os .IsNotExist (err ) {
246+ fmt .Println ("No services have been generated yet" )
247+ return
145248 }
249+ // For other file-related errors, print a more detailed message.
250+ fmt .Fprintf (os .Stderr , "Error reading used ports file: %v\n " , err )
251+ return
146252 }
147- return nil
148- },
149- RunE : func (cmd * cobra.Command , args []string ) error {
150- serviceName := args [0 ]
151253
152- var port string
153- if len (args ) > 1 && args [1 ] != "" {
154- port = args [1 ]
254+ if len (usedPorts .Ports ) == 0 {
255+ fmt .Println ("No services have been generated yet." )
155256 } else {
156- portFile := "port.json"
157- usedPortsFile := "used_ports.json"
158-
159- p , err := readAndIncrementPortWithUsed (8080 , portFile , usedPortsFile )
160- if err != nil {
161- return fmt .Errorf ("failed to get next available port: %w" , err )
257+ fmt .Println ("--- Generated Services ---" )
258+ for _ , pInfo := range usedPorts .Ports {
259+ fmt .Printf ("Service: %-25s Port: %d\n " , pInfo .Service , pInfo .Port )
162260 }
163- port = strconv . Itoa ( p )
261+ fmt . Println ( "--------------------------" )
164262 }
165- return generateService (serviceName , port ) // Your Restful generator
166-
167263 },
168264}
169265
170266func init () {
171267 rootCmd .AddCommand (generateCmd )
268+
269+ rootCmd .AddCommand (listServicesCmd )
172270}
173271
174272func generateService (name , port string ) error {
@@ -330,6 +428,7 @@ func createMicroservice(name, port string) error {
330428 "service.tmpl" ,
331429 "go.mod.tmpl" ,
332430 "Dockerfile.tmpl" ,
431+ "entity_pkg.tmpl" , // New template for the entity
333432 }
334433
335434 outputFiles := []string {
@@ -339,6 +438,7 @@ func createMicroservice(name, port string) error {
339438 filepath .Join (basePath , "internal" , "service.go" ),
340439 filepath .Join (basePath , "go.mod" ),
341440 filepath .Join (basePath , "Dockerfile" ),
441+ filepath .Join ("pkg" , "entities" , fmt .Sprintf ("%s.entity.go" , name )),
342442 }
343443
344444 funcMap := template.FuncMap {
0 commit comments