Skip to content

Commit

Permalink
feat: Add simple versioning support (#22)
Browse files Browse the repository at this point in the history
  • Loading branch information
MaikelVeen authored Aug 18, 2024
1 parent 3763605 commit 1e20100
Show file tree
Hide file tree
Showing 18 changed files with 136 additions and 50 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ lint: ## Run all available linters
lint-fix: ## Run all available linters and fix the issues
golangci-lint run --timeout=5m --fix

.PHONY: spec
.PHONY: typespec
## Type spec:
spec: ### Run compilation of OpenAPI specification
typespec: ### Run compilation of OpenAPI specification
@echo "Compiling OpenAPI spec"
sh ./scripts/compile-spec.sh

Expand Down
4 changes: 2 additions & 2 deletions api/api.gen.go → api/v1/api.gen.go

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

2 changes: 1 addition & 1 deletion api/item.go → api/v1/item.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package api
package v1

import (
"github.com/glass-cms/glasscms/item"
Expand Down
6 changes: 3 additions & 3 deletions api/item_test.go → api/v1/item_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package api_test
package v1_test

import (
"testing"
"time"

"github.com/glass-cms/glasscms/api"
v1 "github.com/glass-cms/glasscms/api/v1"
"github.com/glass-cms/glasscms/item"
"github.com/glass-cms/glasscms/parser"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -104,7 +104,7 @@ func TestItem_MapToDomain(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

apiItem := &api.Item{
apiItem := &v1.Item{
Content: tt.fields.Content,
CreateTime: tt.fields.CreateTime,
DisplayName: tt.fields.DisplayName,
Expand Down
6 changes: 5 additions & 1 deletion cmd/server/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"github.com/glass-cms/glasscms/item"
ctx "github.com/glass-cms/glasscms/lib/context"
"github.com/glass-cms/glasscms/server"
"github.com/glass-cms/glasscms/server/handler"
v1 "github.com/glass-cms/glasscms/server/handler/v1"
"github.com/lmittmann/tint"
"github.com/spf13/cobra"
"github.com/spf13/viper"
Expand Down Expand Up @@ -91,7 +93,9 @@ func (c *StartCommand) Execute(cmd *cobra.Command, _ []string) error {
return err
}

server, err := server.New(c.logger, item.NewRepository(db))
v1Handler := v1.NewAPIHandler(c.logger, item.NewRepository(db))

server, err := server.New(c.logger, []handler.VersionedHandler{v1Handler})
if err != nil {
return err
}
Expand Down
21 changes: 21 additions & 0 deletions docs/development/code-gen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Code Generation

This project uses [TypeSpec](https://typespec.io/) for describing the API of the application. The TypeSpec is compiled in an OpenAPI 3.0 specification file, which is then used to generate the client and server code.

## Generating the OpenAPI specification

The TypeSpec files are located in the `typespec` directory. To compile the OpenAPI specification, run the following command:

```bash
make typespec
```

The OpenAPI specification will be generated in the `openapi` directory.

## Generating the client and server code

The client and server code is generated using the [oapi-codegen](https://github.com/oapi-codegen/oapi-codegen) module. To generate the code, run the following command:

```bash
go generate ./...
```
2 changes: 1 addition & 1 deletion generate.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package main

//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=codegen-server.yaml openapi.yaml
//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=openapi/codegen.cfg.v1.yaml openapi/openapi.v1.yaml
4 changes: 2 additions & 2 deletions codegen-server.yaml → openapi/codegen.cfg.v1.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package: api
package: v1
generate:
std-http-server: true
models: true
output: api/api.gen.go
output: api/v1/api.gen.go
6 changes: 5 additions & 1 deletion openapi.yaml → openapi/openapi.v1.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
openapi: 3.0.0
info:
title: GlassCMS API
version: 0.0.0
version: v1
tags: []
paths:
/items:
Expand Down Expand Up @@ -81,3 +81,7 @@ components:
type: object
additionalProperties: {}
description: Item represents an individual content item.
Versions:
type: string
enum:
- v1
2 changes: 1 addition & 1 deletion scripts/compile-spec.sh
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@

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

import "net/http"

// VersionedHandler is an interface that defines an HTTP handler.
type VersionedHandler interface {
// HttpHandler returns an http.Handler that implements the API for a specific version.
Handler(baseRouter *http.ServeMux, middlewares []func(http.Handler) http.Handler) http.Handler
}
17 changes: 17 additions & 0 deletions server/handler/v1/item.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package v1

import "net/http"

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

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

func (s *APIHandler) ItemsCreate(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusNotImplemented)
}
47 changes: 47 additions & 0 deletions server/handler/v1/v1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Package v1 implements the API handlers for the v1 version of the Glass CMS API.
package v1

import (
"log/slog"
"net/http"

v1 "github.com/glass-cms/glasscms/api/v1"
"github.com/glass-cms/glasscms/item"
"github.com/glass-cms/glasscms/server/handler"
)

type APIHandler struct {
logger *slog.Logger
repository *item.Repository
}

// NewAPIHandler returns a new instance of ApiHandler.
func NewAPIHandler(
logger *slog.Logger,
repo *item.Repository,
) *APIHandler {
return &APIHandler{
logger: logger,
repository: repo,
}
}

// Handler returns an http.Handler that implements the API.
func (s *APIHandler) Handler(
baseRouter *http.ServeMux,
middlewares []func(http.Handler) http.Handler,
) http.Handler {
convertedMiddlewares := make([]v1.MiddlewareFunc, len(middlewares))
for i, mw := range middlewares {
convertedMiddlewares[i] = v1.MiddlewareFunc(mw)
}

return v1.HandlerWithOptions(s, v1.StdHTTPServerOptions{
BaseURL: "/v1",
BaseRouter: baseRouter,
Middlewares: convertedMiddlewares,
})
}

var _ v1.ServerInterface = (*APIHandler)(nil)
var _ handler.VersionedHandler = (*APIHandler)(nil)
17 changes: 0 additions & 17 deletions server/item.go

This file was deleted.

24 changes: 9 additions & 15 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ import (
"net/http"
"time"

"github.com/glass-cms/glasscms/api"
"github.com/glass-cms/glasscms/item"
"github.com/glass-cms/glasscms/lib/middleware"
"github.com/glass-cms/glasscms/server/handler"
)

const (
Expand All @@ -22,32 +21,27 @@ const (
type Server struct {
logger *slog.Logger
server *http.Server

repository *item.Repository
}

var _ api.ServerInterface = (*Server)(nil)

func New(
logger *slog.Logger,
repo *item.Repository,
handlers []handler.VersionedHandler,
opts ...Option,
) (*Server, error) {
server := &Server{
logger: logger,
repository: repo,
logger: logger,
}

serverOpts := api.StdHTTPServerOptions{
Middlewares: []api.MiddlewareFunc{
serveMux := http.NewServeMux()

for _, h := range handlers {
_ = h.Handler(serveMux, []func(http.Handler) http.Handler{
middleware.MediaType("application/json"),
},
})
}

handler := api.HandlerWithOptions(server, serverOpts)

server.server = &http.Server{
Handler: handler,
Handler: serveMux,
Addr: fmt.Sprintf(":%v", DefaultPort),
ReadTimeout: DefaultReadTimeout,
WriteTimeout: DefaultWriteTimeout,
Expand Down
8 changes: 7 additions & 1 deletion typespec/main.tsp
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import "@typespec/http";
import "@typespec/rest";
import "@typespec/openapi3";
import "@typespec/versioning";

using TypeSpec.Http;
using TypeSpec.Rest;

@service({
title: "GlassCMS API",
})
namespace GlassCMS;
@TypeSpec.Versioning.versioned(Versions)
namespace GlassCMSCore;

enum Versions {
v1,
}

@route("/items")
namespace Items {
Expand Down
4 changes: 2 additions & 2 deletions typespec/package-lock.json

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

3 changes: 2 additions & 1 deletion typespec/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"@typespec/compiler": "latest",
"@typespec/http": "latest",
"@typespec/rest": "latest",
"@typespec/openapi3": "latest"
"@typespec/openapi3": "latest",
"@typespec/versioning": "latest"
},
"private": true
}

0 comments on commit 1e20100

Please sign in to comment.