Files
Gogs/docs/dev/flamego_migration_examples.md
2026-01-25 04:26:38 +00:00

1197 lines
27 KiB
Markdown

# Flamego Migration: Code Examples
This document provides practical, side-by-side code examples showing how to migrate from Macaron to Flamego in the Gogs codebase.
## Table of Contents
1. [Basic Application Setup](#basic-application-setup)
2. [Middleware Configuration](#middleware-configuration)
3. [Route Definitions](#route-definitions)
4. [Handler Functions](#handler-functions)
5. [Context Usage](#context-usage)
6. [Form Binding](#form-binding)
7. [Template Rendering](#template-rendering)
8. [Custom Middleware](#custom-middleware)
9. [Complete Example](#complete-example)
## Basic Application Setup
### Macaron (Current)
```go
// internal/cmd/web.go
package cmd
import (
"gopkg.in/macaron.v1"
"github.com/go-macaron/session"
"github.com/go-macaron/csrf"
)
func newMacaron() *macaron.Macaron {
m := macaron.New()
// Basic middleware
if !conf.Server.DisableRouterLog {
m.Use(macaron.Logger())
}
m.Use(macaron.Recovery())
// Optional gzip
if conf.Server.EnableGzip {
m.Use(gzip.Gziper())
}
return m
}
func runWeb(c *cli.Context) error {
m := newMacaron()
// Configure routes
m.Get("/", route.Home)
// Start server
return http.ListenAndServe(":3000", m)
}
```
### Flamego (Target)
```go
// internal/cmd/web.go
package cmd
import (
"github.com/flamego/flamego"
"github.com/flamego/session"
"github.com/flamego/csrf"
)
func newFlamego() *flamego.Flame {
f := flamego.New()
// Basic middleware
if !conf.Server.DisableRouterLog {
f.Use(flamego.Logger())
}
f.Use(flamego.Recovery())
// Optional gzip
if conf.Server.EnableGzip {
f.Use(gzip.Gziper())
}
return f
}
func runWeb(c *cli.Context) error {
f := newFlamego()
// Configure routes
f.Get("/", route.Home)
// Start server
return f.Run(":3000")
}
```
## Middleware Configuration
### Session Middleware
#### Macaron (Current)
```go
import "github.com/go-macaron/session"
m.Use(session.Sessioner(session.Options{
Provider: conf.Session.Provider,
ProviderConfig: conf.Session.ProviderConfig,
CookieName: conf.Session.CookieName,
CookiePath: conf.Server.Subpath,
Gclifetime: conf.Session.GCInterval,
Maxlifetime: conf.Session.MaxLifeTime,
Secure: conf.Session.CookieSecure,
}))
// Handler usage
func handler(sess session.Store) {
sess.Set("user_id", 123)
userID := sess.Get("user_id")
}
```
#### Flamego (Target)
```go
import "github.com/flamego/session"
f.Use(session.Sessioner(session.Options{
// Config depends on provider type
Config: session.RedisConfig{
Options: &redis.Options{
Addr: conf.Session.ProviderConfig,
},
},
Cookie: session.CookieOptions{
Name: conf.Session.CookieName,
Path: conf.Server.Subpath,
MaxAge: conf.Session.MaxLifeTime,
Secure: conf.Session.CookieSecure,
},
// For memory provider:
// Config: session.MemoryConfig{
// GCInterval: conf.Session.GCInterval,
// },
}))
// Handler usage - interface name changed
func handler(sess session.Session) {
sess.Set("user_id", 123)
userID := sess.Get("user_id")
}
```
### CSRF Middleware
#### Macaron (Current)
```go
import "github.com/go-macaron/csrf"
m.Use(csrf.Csrfer(csrf.Options{
Secret: conf.Security.SecretKey,
Header: "X-CSRF-Token",
Cookie: conf.Session.CSRFCookieName,
CookieDomain: conf.Server.URL.Hostname(),
CookiePath: conf.Server.Subpath,
CookieHttpOnly: true,
SetCookie: true,
Secure: conf.Server.URL.Scheme == "https",
}))
// Handler usage
func handler(x csrf.CSRF) {
token := x.GetToken()
}
```
#### Flamego (Target)
```go
import "github.com/flamego/csrf"
f.Use(csrf.Csrfer(csrf.Options{
Secret: conf.Security.SecretKey,
Header: "X-CSRF-Token",
Cookie: conf.Session.CSRFCookieName,
CookiePath: conf.Server.Subpath,
Secure: conf.Server.URL.Scheme == "https",
}))
// Handler usage - method name changed
func handler(x csrf.CSRF) {
token := x.Token() // Changed from GetToken()
}
```
### Template Middleware
#### Macaron (Current)
```go
m.Use(macaron.Renderer(macaron.RenderOptions{
Directory: filepath.Join(conf.WorkDir(), "templates"),
AppendDirectories: []string{customDir},
Funcs: template.FuncMap(),
IndentJSON: macaron.Env != macaron.PROD,
}))
// Handler usage
func handler(c *macaron.Context) {
c.Data["Title"] = "Home"
c.HTML(200, "home")
}
```
#### Flamego (Target)
```go
import "github.com/flamego/template"
f.Use(template.Templater(template.Options{
Directory: filepath.Join(conf.WorkDir(), "templates"),
AppendDirectories: []string{customDir},
FuncMaps: []template.FuncMap{template.FuncMap()},
}))
// Handler usage - separate template and data injection
func handler(t template.Template, data template.Data) {
data["Title"] = "Home"
t.HTML(200, "home")
}
```
### Cache Middleware
#### Macaron (Current)
```go
import "github.com/go-macaron/cache"
m.Use(cache.Cacher(cache.Options{
Adapter: conf.Cache.Adapter,
AdapterConfig: conf.Cache.Host,
Interval: conf.Cache.Interval,
}))
// Handler usage
func handler(cache cache.Cache) {
cache.Put("key", "value", 60)
value := cache.Get("key")
cache.Delete("key")
}
```
#### Flamego (Target)
```go
import "github.com/flamego/cache"
var cacheConfig cache.Config
switch conf.Cache.Adapter {
case "memory":
cacheConfig = cache.MemoryConfig{
GCInterval: conf.Cache.Interval,
}
case "redis":
cacheConfig = cache.RedisConfig{
Options: &redis.Options{
Addr: conf.Cache.Host,
},
}
}
f.Use(cache.Cacher(cache.Options{
Config: cacheConfig,
}))
// Handler usage - method names changed
func handler(c cache.Cache) {
c.Set("key", "value", 60) // Changed from Put
value := c.Get("key")
c.Delete("key")
}
```
### i18n Middleware
#### Macaron (Current)
```go
import "github.com/go-macaron/i18n"
m.Use(i18n.I18n(i18n.Options{
SubURL: conf.Server.Subpath,
Files: localeFiles,
CustomDirectory: filepath.Join(conf.CustomDir(), "conf", "locale"),
Langs: conf.I18n.Langs,
Names: conf.I18n.Names,
DefaultLang: "en-US",
Redirect: true,
}))
// Handler usage
func handler(l i18n.Locale) {
text := l.Tr("user.login")
}
```
#### Flamego (Target)
```go
import "github.com/flamego/i18n"
f.Use(i18n.I18n(i18n.Options{
URLPrefix: conf.Server.Subpath,
Files: localeFiles,
CustomDirectory: filepath.Join(conf.CustomDir(), "conf", "locale"),
Languages: conf.I18n.Langs, // Changed from Langs
Names: conf.I18n.Names,
DefaultLanguage: "en-US", // Changed from DefaultLang
Redirect: true,
}))
// Handler usage - same interface
func handler(l i18n.Locale) {
text := l.Tr("user.login")
}
```
## Route Definitions
### Basic Routes
#### Macaron (Current)
```go
m.Get("/", ignSignIn, route.Home)
m.Post("/login", bindIgnErr(form.SignIn{}), user.LoginPost)
m.Get("/:username", user.Profile)
m.Get("/:username/:reponame", context.RepoAssignment(), repo.Home)
```
#### Flamego (Target)
```go
f.Get("/", ignSignIn, route.Home)
f.Post("/login", binding.Form(form.SignIn{}), user.LoginPost)
f.Get("/<username>", user.Profile)
f.Get("/<username>/<reponame>", context.RepoAssignment(), repo.Home)
```
**Key Changes:**
- `:param` becomes `<param>`
- `bindIgnErr(form)` becomes `binding.Form(form)`
### Route Groups
#### Macaron (Current)
```go
m.Group("/user", func() {
m.Group("/login", func() {
m.Combo("").Get(user.Login).
Post(bindIgnErr(form.SignIn{}), user.LoginPost)
m.Combo("/two_factor").Get(user.LoginTwoFactor).
Post(user.LoginTwoFactorPost)
})
m.Get("/sign_up", user.SignUp)
m.Post("/sign_up", bindIgnErr(form.Register{}), user.SignUpPost)
}, reqSignOut)
```
#### Flamego (Target)
```go
f.Group("/user", func() {
f.Group("/login", func() {
f.Combo("").Get(user.Login).
Post(binding.Form(form.SignIn{}), user.LoginPost)
f.Combo("/two_factor").Get(user.LoginTwoFactor).
Post(user.LoginTwoFactorPost)
})
f.Get("/sign_up", user.SignUp)
f.Post("/sign_up", binding.Form(form.Register{}), user.SignUpPost)
}, reqSignOut)
```
**Key Changes:**
- `m.Group` becomes `f.Group`
- `bindIgnErr` becomes `binding.Form`
### Regex Routes
#### Macaron (Current)
```go
m.Get("/^:type(issues|pulls)$", reqSignIn, user.Issues)
```
#### Flamego (Target)
```go
f.Get("/<type:issues|pulls>", reqSignIn, user.Issues)
```
### Route with Optional Segments
#### Macaron (Current)
```go
// Not well supported - need multiple routes
m.Get("/wiki", repo.Wiki)
m.Get("/wiki/:page", repo.Wiki)
```
#### Flamego (Target)
```go
// Better support for optional segments
f.Get("/wiki/?<page>", repo.Wiki)
```
## Handler Functions
### Basic Handler
#### Macaron (Current)
```go
func Home(c *context.Context) {
c.Data["Title"] = "Home"
c.HTML(http.StatusOK, "home")
}
```
#### Flamego (Target)
```go
func Home(c *context.Context, t template.Template, data template.Data) {
data["Title"] = "Home"
t.HTML(http.StatusOK, "home")
}
// Note: context.Context needs to be updated to wrap flamego.Context
```
### Handler with Parameters
#### Macaron (Current)
```go
func UserProfile(c *context.Context) {
username := c.Params(":username")
user, err := database.GetUserByName(username)
if err != nil {
c.NotFoundOrError(err, "get user")
return
}
c.Data["User"] = user
c.HTML(http.StatusOK, "user/profile")
}
```
#### Flamego (Target)
```go
func UserProfile(c *context.Context, t template.Template, data template.Data) {
username := c.Param("username") // No colon prefix
user, err := database.GetUserByName(username)
if err != nil {
c.NotFoundOrError(err, "get user")
return
}
data["User"] = user
t.HTML(http.StatusOK, "user/profile")
}
```
### Handler with Form Binding
#### Macaron (Current)
```go
type LoginForm struct {
Username string `form:"username" binding:"Required"`
Password string `form:"password" binding:"Required"`
}
func LoginPost(c *context.Context, form LoginForm) {
if !database.ValidateUser(form.Username, form.Password) {
c.RenderWithErr("Invalid credentials", "user/login", &form)
return
}
c.Session.Set("user_id", user.ID)
c.Redirect("/")
}
```
#### Flamego (Target)
```go
type LoginForm struct {
Username string `form:"username" validate:"required"`
Password string `form:"password" validate:"required"`
}
func LoginPost(c *context.Context, form LoginForm, t template.Template, data template.Data) {
if !database.ValidateUser(form.Username, form.Password) {
c.RenderWithErr("Invalid credentials", "user/login", &form, t, data)
return
}
c.Session().Set("user_id", user.ID)
c.Redirect("/")
}
```
### Handler with Session
#### Macaron (Current)
```go
func RequireLogin(c *context.Context, sess session.Store) {
userID := sess.Get("user_id")
if userID == nil {
c.Redirect("/login")
return
}
user, err := database.GetUserByID(userID.(int64))
if err != nil {
c.Error(err, "get user")
return
}
c.User = user
}
```
#### Flamego (Target)
```go
func RequireLogin(c *context.Context, sess session.Session) {
userID := sess.Get("user_id")
if userID == nil {
c.Redirect("/login")
return
}
user, err := database.GetUserByID(userID.(int64))
if err != nil {
c.Error(err, "get user")
return
}
c.User = user
}
```
### JSON API Handler
#### Macaron (Current)
```go
func APIUserInfo(c *context.APIContext) {
user := c.User
c.JSON(http.StatusOK, &api.User{
ID: user.ID,
Username: user.Name,
Email: user.Email,
})
}
```
#### Flamego (Target)
```go
import "encoding/json"
func APIUserInfo(c *context.APIContext) {
user := c.User
resp := &api.User{
ID: user.ID,
Username: user.Name,
Email: user.Email,
}
c.ResponseWriter().Header().Set("Content-Type", "application/json")
c.ResponseWriter().WriteHeader(http.StatusOK)
json.NewEncoder(c.ResponseWriter()).Encode(resp)
}
// Or create a helper method on context.APIContext
func (c *APIContext) JSON(status int, v any) {
c.ResponseWriter().Header().Set("Content-Type", "application/json")
c.ResponseWriter().WriteHeader(status)
json.NewEncoder(c.ResponseWriter()).Encode(v)
}
```
## Context Usage
### Context Wrapper Update
#### Macaron (Current)
```go
// internal/context/context.go
package context
import (
"github.com/go-macaron/cache"
"github.com/go-macaron/csrf"
"github.com/go-macaron/session"
"gopkg.in/macaron.v1"
)
type Context struct {
*macaron.Context
Cache cache.Cache
csrf csrf.CSRF
Flash *session.Flash
Session session.Store
Link string
User *database.User
IsLogged bool
Repo *Repository
Org *Organization
}
// Contexter middleware
func Contexter(store Store) macaron.Handler {
return func(
ctx *macaron.Context,
l i18n.Locale,
cache cache.Cache,
sess session.Store,
f *session.Flash,
x csrf.CSRF,
) {
c := &Context{
Context: ctx,
Cache: cache,
csrf: x,
Flash: f,
Session: sess,
}
// Authentication logic...
c.User, c.IsBasicAuth, c.IsTokenAuth = authenticatedUser(store, c.Context, c.Session)
ctx.Map(c)
}
}
```
#### Flamego (Target)
```go
// internal/context/context.go
package context
import (
"github.com/flamego/flamego"
"github.com/flamego/cache"
"github.com/flamego/csrf"
"github.com/flamego/session"
)
type Context struct {
flamego.Context // Embedded instead of pointer
cache cache.Cache
csrf csrf.CSRF
flash *session.Flash
session session.Session
Link string
User *database.User
IsLogged bool
Repo *Repository
Org *Organization
}
// Accessor methods
func (c *Context) Cache() cache.Cache { return c.cache }
func (c *Context) CSRF() csrf.CSRF { return c.csrf }
func (c *Context) Flash() *session.Flash { return c.flash }
func (c *Context) Session() session.Session { return c.session }
// Contexter middleware
func Contexter(store Store) flamego.Handler {
return func(
ctx flamego.Context,
l i18n.Locale,
cache cache.Cache,
sess session.Session,
f *session.Flash,
x csrf.CSRF,
) {
c := &Context{
Context: ctx,
cache: cache,
csrf: x,
flash: f,
session: sess,
}
// Authentication logic - note Session is now a method
c.User, c.IsBasicAuth, c.IsTokenAuth = authenticatedUser(store, c, c.session)
ctx.MapTo(c, (*Context)(nil))
}
}
```
### Response Methods
#### Macaron (Current)
```go
func (c *Context) HTML(status int, name string) {
log.Trace("Template: %s", name)
c.Context.HTML(status, name)
}
func (c *Context) JSON(status int, data any) {
c.Context.JSON(status, data)
}
```
#### Flamego (Target)
```go
// These methods need to be updated to work with injected services
// Option 1: Require template.Template injection
func (c *Context) HTML(status int, name string, t template.Template, data template.Data) {
log.Trace("Template: %s", name)
// Copy c.Data to template.Data if needed
for k, v := range c.Data {
data[k] = v
}
t.HTML(status, name)
}
// Option 2: Store template reference in context during initialization
func (c *Context) HTML(status int, name string) {
if c.template == nil {
panic("template not initialized")
}
log.Trace("Template: %s", name)
c.template.HTML(status, name)
}
func (c *Context) JSON(status int, data any) {
c.ResponseWriter().Header().Set("Content-Type", "application/json")
c.ResponseWriter().WriteHeader(status)
json.NewEncoder(c.ResponseWriter()).Encode(data)
}
```
## Form Binding
### Form Struct Tags
#### Macaron (Current)
```go
type CreateRepoForm struct {
UserID int64 `form:"user_id" binding:"Required"`
RepoName string `form:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"`
Private bool `form:"private"`
Description string `form:"description" binding:"MaxSize(255)"`
AutoInit bool `form:"auto_init"`
Gitignores string `form:"gitignores"`
License string `form:"license"`
Readme string `form:"readme"`
}
```
#### Flamego (Target)
```go
type CreateRepoForm struct {
UserID int64 `form:"user_id" validate:"required"`
RepoName string `form:"repo_name" validate:"required,alphaDashDot,max=100"`
Private bool `form:"private"`
Description string `form:"description" validate:"max=255"`
AutoInit bool `form:"auto_init"`
Gitignores string `form:"gitignores"`
License string `form:"license"`
Readme string `form:"readme"`
}
// Note: Custom validators like AlphaDashDot need to be registered with Flamego's validator
```
### Custom Validators
#### Macaron (Current)
```go
import "github.com/go-macaron/binding"
const (
AlphaDashDotSlash binding.Rule = "AlphaDashDotSlash"
)
func init() {
binding.SetNameMapper(com.ToSnakeCase)
binding.AddRule(&binding.Rule{
IsMatch: func(rule string) bool {
return rule == "AlphaDashDotSlash"
},
IsValid: func(errs binding.Errors, name string, v interface{}) (bool, binding.Errors) {
str := v.(string)
if !alphaDashDotSlashPattern.MatchString(str) {
errs = append(errs, binding.Error{
FieldNames: []string{name},
Message: name + " must be valid alpha, dash, dot or slash",
})
return false, errs
}
return true, errs
},
})
}
```
#### Flamego (Target)
```go
import (
"github.com/flamego/binding"
"github.com/go-playground/validator/v10"
)
func init() {
// Register custom validator
binding.RegisterValidation("alphaDashDotSlash", func(fl validator.FieldLevel) bool {
str := fl.Field().String()
return alphaDashDotSlashPattern.MatchString(str)
})
}
// Usage in struct
type Form struct {
Path string `form:"path" validate:"required,alphaDashDotSlash"`
}
```
### Multipart Form
#### Macaron (Current)
```go
import "github.com/go-macaron/binding"
type AvatarForm struct {
Avatar *multipart.FileHeader `form:"avatar"`
}
m.Post("/avatar", binding.MultipartForm(AvatarForm{}), handler)
```
#### Flamego (Target)
```go
import "github.com/flamego/binding"
type AvatarForm struct {
Avatar *multipart.FileHeader `form:"avatar"`
}
f.Post("/avatar", binding.MultipartForm(AvatarForm{}), handler)
```
## Template Rendering
### Render with Data
#### Macaron (Current)
```go
func ShowRepo(c *context.Context) {
c.Data["Title"] = c.Repo.Repository.Name
c.Data["Owner"] = c.Repo.Owner
c.Data["Repository"] = c.Repo.Repository
c.Data["IsRepositoryAdmin"] = c.Repo.IsAdmin()
c.HTML(http.StatusOK, "repo/home")
}
```
#### Flamego (Target)
```go
func ShowRepo(c *context.Context, t template.Template, data template.Data) {
data["Title"] = c.Repo.Repository.Name
data["Owner"] = c.Repo.Owner
data["Repository"] = c.Repo.Repository
data["IsRepositoryAdmin"] = c.Repo.IsAdmin()
t.HTML(http.StatusOK, "repo/home")
}
// Or if context has template reference:
func ShowRepo(c *context.Context) {
c.Data["Title"] = c.Repo.Repository.Name
c.Data["Owner"] = c.Repo.Owner
c.Data["Repository"] = c.Repo.Repository
c.Data["IsRepositoryAdmin"] = c.Repo.IsAdmin()
c.HTML(http.StatusOK, "repo/home")
}
```
### Render with Error
#### Macaron (Current)
```go
func (c *Context) RenderWithErr(msg, tpl string, f any) {
if f != nil {
form.Assign(f, c.Data)
}
c.Flash.ErrorMsg = msg
c.Data["Flash"] = c.Flash
c.HTML(http.StatusOK, tpl)
}
```
#### Flamego (Target)
```go
func (c *Context) RenderWithErr(msg, tpl string, f any, t template.Template, data template.Data) {
if f != nil {
form.Assign(f, c.Data)
// Also need to assign to data
for k, v := range c.Data {
data[k] = v
}
}
c.Flash().ErrorMsg = msg
data["Flash"] = c.Flash()
t.HTML(http.StatusOK, tpl)
}
```
## Custom Middleware
### Authentication Middleware
#### Macaron (Current)
```go
func Toggle(options *ToggleOptions) macaron.Handler {
return func(c *Context) {
// Check authentication
if options.SignInRequired {
if !c.IsLogged {
c.SetCookie("redirect_to", c.Req.RequestURI, 0, conf.Server.Subpath)
c.Redirect(conf.Server.Subpath + "/user/login")
return
}
}
// Check admin
if options.AdminRequired {
if !c.User.IsAdmin {
c.Error(nil, http.StatusForbidden)
return
}
}
}
}
```
#### Flamego (Target)
```go
func Toggle(options *ToggleOptions) flamego.Handler {
return func(c *Context) {
// Check authentication
if options.SignInRequired {
if !c.IsLogged {
c.SetCookie(http.Cookie{
Name: "redirect_to",
Value: c.Request().RequestURI,
Path: conf.Server.Subpath,
})
c.Redirect(conf.Server.Subpath + "/user/login")
return
}
}
// Check admin
if options.AdminRequired {
if !c.User.IsAdmin {
c.Error(nil, http.StatusForbidden)
return
}
}
}
}
```
### Repository Context Middleware
#### Macaron (Current)
```go
func RepoAssignment() macaron.Handler {
return func(c *Context) {
userName := c.Params(":username")
repoName := c.Params(":reponame")
owner, err := database.GetUserByName(userName)
if err != nil {
c.NotFoundOrError(err, "get user")
return
}
c.Repo.Owner = owner
repo, err := database.GetRepositoryByName(owner.ID, repoName)
if err != nil {
c.NotFoundOrError(err, "get repository")
return
}
c.Repo.Repository = repo
}
}
```
#### Flamego (Target)
```go
func RepoAssignment() flamego.Handler {
return func(c *Context) {
userName := c.Param("username") // No colon prefix
repoName := c.Param("reponame")
owner, err := database.GetUserByName(userName)
if err != nil {
c.NotFoundOrError(err, "get user")
return
}
c.Repo.Owner = owner
repo, err := database.GetRepositoryByName(owner.ID, repoName)
if err != nil {
c.NotFoundOrError(err, "get repository")
return
}
c.Repo.Repository = repo
}
}
```
## Complete Example
### Full Route Handler Chain
#### Macaron (Current)
```go
// Setup
m := macaron.New()
m.Use(macaron.Logger())
m.Use(macaron.Recovery())
m.Use(session.Sessioner())
m.Use(csrf.Csrfer())
m.Use(context.Contexter(store))
// Middleware
reqSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: true})
// Routes
m.Group("/:username/:reponame", func() {
m.Get("/issues", repo.Issues)
m.Combo("/issues/new").
Get(repo.NewIssue).
Post(bindIgnErr(form.NewIssue{}), repo.NewIssuePost)
}, reqSignIn, context.RepoAssignment())
// Handler
func NewIssuePost(c *context.Context, form form.NewIssue) {
if c.HasError() {
c.RenderWithErr(c.GetErrMsg(), "repo/issue/new", &form)
return
}
issue, err := database.NewIssue(&database.Issue{
RepoID: c.Repo.Repository.ID,
Index: c.Repo.Repository.NextIssueIndex(),
Title: form.Title,
Content: form.Content,
})
if err != nil {
c.Error(err, "create issue")
return
}
c.Redirect(fmt.Sprintf("/%s/%s/issues/%d",
c.Repo.Owner.Name, c.Repo.Repository.Name, issue.Index))
}
```
#### Flamego (Target)
```go
// Setup
f := flamego.New()
f.Use(flamego.Logger())
f.Use(flamego.Recovery())
f.Use(session.Sessioner())
f.Use(csrf.Csrfer())
f.Use(template.Templater())
f.Use(context.Contexter(store))
// Middleware
reqSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: true})
// Routes - note parameter syntax change
f.Group("/<username>/<reponame>", func() {
f.Get("/issues", repo.Issues)
f.Combo("/issues/new").
Get(repo.NewIssue).
Post(binding.Form(form.NewIssue{}), repo.NewIssuePost)
}, reqSignIn, context.RepoAssignment())
// Handler - note template injection
func NewIssuePost(
c *context.Context,
form form.NewIssue,
t template.Template,
data template.Data,
) {
if c.HasError() {
c.RenderWithErr(c.GetErrMsg(), "repo/issue/new", &form, t, data)
return
}
issue, err := database.NewIssue(&database.Issue{
RepoID: c.Repo.Repository.ID,
Index: c.Repo.Repository.NextIssueIndex(),
Title: form.Title,
Content: form.Content,
})
if err != nil {
c.Error(err, "create issue")
return
}
c.Redirect(fmt.Sprintf("/%s/%s/issues/%d",
c.Repo.Owner.Name, c.Repo.Repository.Name, issue.Index))
}
```
## Key Takeaways
1. **Parameter Names**: Remove `:` prefix when getting params (`c.Param("name")` vs `c.Params(":name")`)
2. **Route Syntax**: Use `<param>` instead of `:param`
3. **Interface Names**: `session.Store``session.Session`
4. **Method Names**: `GetToken()``Token()`, `Put()``Set()`
5. **Template Injection**: Need to inject `template.Template` and `template.Data` parameters
6. **Response Access**: `c.Resp``c.ResponseWriter()`
7. **Request Access**: `c.Req``c.Request()`
8. **Context Embedding**: Use `flamego.Context` interface instead of `*macaron.Context` pointer
## Summary
The migration from Macaron to Flamego is largely mechanical with clear patterns:
- Most middleware has direct equivalents
- Handler signatures gain template parameters
- Route parameter syntax changes
- Context access changes from fields to methods
- Overall structure and patterns remain similar
The main work is updating ~150+ files to follow these new patterns consistently.