Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Bootstrap http server for API #18

Merged
merged 5 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions api/api.go

This file was deleted.

6 changes: 0 additions & 6 deletions api/oapi-codegen-server.yaml

This file was deleted.

18 changes: 15 additions & 3 deletions cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"log/slog"
"os"

"github.com/glass-cms/glasscms/ctx"
"github.com/glass-cms/glasscms/server"
"github.com/lmittmann/tint"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -36,7 +38,17 @@ func NewServerCommand() *ServerCommand {
return sc
}

func (c *ServerCommand) Execute(_ *cobra.Command, _ []string) error {
c.logger.Info("Starting server")
return nil
func (c *ServerCommand) Execute(cmd *cobra.Command, _ []string) error {
server, err := server.New(c.logger)
if err != nil {
return err
}

_ = ctx.SigtermCacellationContext(cmd.Context(), func() {
c.logger.Info("shutting down server")
server.Shutdown()
})

c.logger.Info("starting server")
return server.ListenAndServer()
}
5 changes: 5 additions & 0 deletions codegen-server.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package: server
generate:
std-http-server: true
models: true
output: server/server.gen.go
25 changes: 25 additions & 0 deletions ctx/sigterm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package ctx

import (
"context"
"os"
"os/signal"
"syscall"
)

// SigtermCacellationContext returns a new context that is canceled when an
// interrupt signal (SIGTERM or SIGINT) is received.
// Additionaly invokes the provided onCancel function before canceling the context.
func SigtermCacellationContext(ctx context.Context, onCancel func()) context.Context {
ctx, cancel := context.WithCancel(ctx)

interruptCh := make(chan os.Signal, 1)
signal.Notify(interruptCh, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)

go func() {
<-interruptCh
onCancel()
cancel()
}()
return ctx
}
3 changes: 3 additions & 0 deletions generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package main

//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=codegen-server.yaml openapi.yaml
19 changes: 11 additions & 8 deletions item/item.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@ package item
import "time"

const (
// PropertyTitle is the key for the title property.
PropertyTitle = "title"
)

type Item struct {
Name string `json:"name" yaml:"name"`
Path string `json:"path" yaml:"path"`
Content string `json:"content" yaml:"content"`
Hash string `json:"hash" yaml:"hash"`
CreateTime time.Time `json:"createTime" yaml:"createTime"`
UpdateTime time.Time `json:"updateTime" yaml:"updateTime"`
Properties map[string]any `json:"properties" yaml:"properties"`
UID string `json:"uid" yaml:"uid"`
// Name is the full resource name of the item.
// Format: collections/{collection}/items/{item}
Name string `json:"name" yaml:"name"`
DisplayName string `json:"display_name" yaml:"display_name"`
Path string `json:"path" yaml:"path"`
Content string `json:"content" yaml:"content"`
Hash string `json:"hash" yaml:"hash"`
CreateTime time.Time `json:"create_time" yaml:"create_time"`
UpdateTime time.Time `json:"update_time" yaml:"update_time"`
Properties map[string]any `json:"properties" yaml:"properties"`
}

// Title returns the title property of the item if it exists.
Expand Down
14 changes: 8 additions & 6 deletions api/openapi.yaml → openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,28 +54,30 @@ components:
required:
- id
- name
- display_name
- path
- content
- created_time
- create_time
- update_time
- props
- properties
properties:
id:
type: integer
format: int64
type: string
name:
type: string
display_name:
type: string
path:
type: string
content:
type: string
created_time:
create_time:
type: string
format: date-time
update_time:
type: string
format: date-time
props:
properties:
type: object
additionalProperties: {}
description: Item represents an individual content item.
4 changes: 2 additions & 2 deletions scripts/compile-spec.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
# See: https://github.com/microsoft/typespec/issues/3397.

current_dir=$(pwd)
cd api/typespec || exit
tsp compile . --emit @typespec/openapi3 --option "@typespec/openapi3.emitter-output-dir=${current_dir}/api"
cd typespec || exit
tsp compile . --emit @typespec/openapi3 --option "@typespec/openapi3.emitter-output-dir=${current_dir}"
cd "$current_dir" || exit
23 changes: 23 additions & 0 deletions server/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package server

import (
"errors"
"fmt"
)

type Option func(*Server) error

// WithPort is an option that sets the port the server listens on.
func WithPort(port string) func(*Server) error {
return func(s *Server) error {
if port == "" {
return errors.New("port cannot be empty")
}

if s.server != nil {
s.server.Addr = fmt.Sprintf(":%s", port)
}

return nil
}
}
11 changes: 6 additions & 5 deletions api/server.gen.go → server/server.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

81 changes: 81 additions & 0 deletions server/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package server

import (
"context"
"fmt"
"log/slog"
"net/http"
"time"
)

const (
ShutdownGracePeriod = 10 * time.Second
DefaultPort = 8080
DefaultReadTimeout = 5 * time.Second
DefaultWriteTimeout = 10 * time.Second
)

type Server struct {
logger *slog.Logger
server *http.Server
}

var _ ServerInterface = (*Server)(nil)

func New(
logger *slog.Logger,
opts ...Option,
) (*Server, error) {
server := &Server{
logger: logger,
}

server.server = &http.Server{
Handler: HandlerFromMux(server, http.NewServeMux()),
Addr: fmt.Sprintf(":%v", DefaultPort),
ReadTimeout: DefaultReadTimeout,
WriteTimeout: DefaultWriteTimeout,
}

for _, opt := range opts {
if err := opt(server); err != nil {
return nil, err
}
}

return server, nil
}

// ListenAndServe starts the server.
func (s *Server) ListenAndServer() error {
s.logger.Info("server is listening on :8080")
return s.server.ListenAndServe()
}

// Shutdown gracefully shuts down the underlying server without interrupting any active connections.
func (s *Server) Shutdown() {
ctx, cancel := context.WithTimeout(context.Background(), ShutdownGracePeriod)
defer cancel()

if err := s.server.Shutdown(ctx); err != nil {
s.logger.Error("could not gracefully shutdown the server:", "err", err)
return
}

s.logger.Info("server stopped")
}

func (s *Server) ItemsDelete(w http.ResponseWriter, _ *http.Request) {
// TODO.
w.WriteHeader(http.StatusNotImplemented)
}

func (s *Server) ItemsList(w http.ResponseWriter, _ *http.Request) {
// TODO.
w.WriteHeader(http.StatusNotImplemented)
}

func (s *Server) ItemsCreate(w http.ResponseWriter, _ *http.Request) {
// TODO.
w.WriteHeader(http.StatusNotImplemented)
}
File renamed without changes.
7 changes: 4 additions & 3 deletions api/typespec/main.tsp → typespec/main.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@ namespace Items {

@doc("Item represents an individual content item.")
model Item {
id: int64;
id: string;
name: string;
display_name: string;
path: string;
content: string;
created_time: utcDateTime;
create_time: utcDateTime;
update_time: utcDateTime;
props: Record<unknown>;
properties: Record<unknown>;
}
File renamed without changes.
File renamed without changes.
Loading