From de467e5d69b0aadd5df3550c2829e37d3502de94 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 25 Jan 2026 11:58:06 +0000
Subject: [PATCH] WIP: Migrate from Macaron to Flamego web framework
- Update imports in web.go, context.go, auth.go, api.go
- Replace Macaron with Flamego initialization
- Update middleware configuration
- Convert route syntax from :param to
- Update session, CSRF, cache middleware to Flamego versions
- Add custom health check endpoint
- Update Context struct to work with Flamego
- Update Contexter middleware
- Add helper methods for Status, JSON, Header, Written
Co-authored-by: unknwon <2946214+unknwon@users.noreply.github.com>
---
docs/dev/FLAMEGO_MIGRATION_SUMMARY.md | 300 ------
docs/dev/README_FLAMEGO_MIGRATION.md | 232 -----
docs/dev/flamego_migration_checklist.md | 658 ------------
docs/dev/flamego_migration_examples.md | 1196 ----------------------
docs/dev/flamego_quick_reference.md | 595 -----------
docs/dev/macaron_to_flamego_migration.md | 715 -------------
go.mod | 33 +-
go.sum | 81 ++
internal/cmd/web.go | 1058 ++++++++++---------
internal/context/api.go | 14 +-
internal/context/auth.go | 52 +-
internal/context/context.go | 130 ++-
12 files changed, 762 insertions(+), 4302 deletions(-)
delete mode 100644 docs/dev/FLAMEGO_MIGRATION_SUMMARY.md
delete mode 100644 docs/dev/README_FLAMEGO_MIGRATION.md
delete mode 100644 docs/dev/flamego_migration_checklist.md
delete mode 100644 docs/dev/flamego_migration_examples.md
delete mode 100644 docs/dev/flamego_quick_reference.md
delete mode 100644 docs/dev/macaron_to_flamego_migration.md
diff --git a/docs/dev/FLAMEGO_MIGRATION_SUMMARY.md b/docs/dev/FLAMEGO_MIGRATION_SUMMARY.md
deleted file mode 100644
index 7a4212152..000000000
--- a/docs/dev/FLAMEGO_MIGRATION_SUMMARY.md
+++ /dev/null
@@ -1,300 +0,0 @@
-# Macaron to Flamego Migration: Executive Summary
-
-## Question Answered
-
-**"If you were to fully replace macaron with flamego, how would you do it? Anything available in macaron and its middleware not available in flamego and its middleware?"**
-
-## Short Answer
-
-**Yes, Flamego has full feature parity with Macaron.** All middleware that Gogs currently uses has direct equivalents in Flamego, with only one minor exception (toolbox) that's easy to replace. The migration is feasible and recommended.
-
-## Feature Parity Analysis
-
-### ✅ Available in Both Frameworks
-
-| Feature | Macaron | Flamego | Migration Effort |
-|---------|---------|---------|------------------|
-| **Core Framework** | gopkg.in/macaron.v1 | github.com/flamego/flamego | Low - similar API |
-| **Form Binding** | go-macaron/binding | flamego/binding | Low - tag syntax change |
-| **Cache** | go-macaron/cache | flamego/cache | Low - method name changes |
-| **Captcha** | go-macaron/captcha | flamego/captcha | Low - compatible |
-| **CSRF Protection** | go-macaron/csrf | flamego/csrf | Low - minor API changes |
-| **Gzip Compression** | go-macaron/gzip | flamego/gzip | Low - compatible |
-| **Internationalization** | go-macaron/i18n | flamego/i18n | Low - option name changes |
-| **Session Management** | go-macaron/session | flamego/session | Medium - config struct changes |
-| **Template Rendering** | Built-in Renderer | flamego/template | Medium - injection pattern change |
-| **Static Files** | Built-in Static | Built-in Static | Low - similar API |
-| **Logger** | Built-in Logger | Built-in Logger | Low - same pattern |
-| **Recovery** | Built-in Recovery | Built-in Recovery | Low - same pattern |
-
-### ⚠️ Needs Replacement
-
-| Feature | Macaron | Flamego | Solution |
-|---------|---------|---------|----------|
-| **Toolbox** (health checks, profiling) | go-macaron/toolbox | ❌ Not available | ✅ Easy to implement custom health check endpoint (~20 lines) |
-
-**Verdict:** Only 1 middleware (toolbox) needs custom implementation, and it's straightforward.
-
-## Migration Approach
-
-### High-Level Strategy
-
-The migration would be performed in **8 phases over 20-25 days**:
-
-1. **Dependencies** (1 day) - Add Flamego packages
-2. **Core Framework** (2-3 days) - Main app and middleware setup
-3. **Context System** (2-3 days) - Update context wrapper and helpers
-4. **Form Binding** (2 days) - Update form structs and validators
-5. **Route Handlers** (7 days) - Update ~150+ handler functions
-6. **Testing** (4 days) - Fix tests and perform comprehensive testing
-7. **Cleanup** (2 days) - Remove old code, polish, document
-8. **Deployment** (2 days) - Deploy and monitor
-
-### Key Technical Changes
-
-#### 1. Route Syntax
-```go
-// Before (Macaron)
-m.Get("/:username/:repo", handler)
-
-// After (Flamego)
-f.Get("//", handler)
-```
-
-#### 2. Handler Signatures
-```go
-// Before (Macaron)
-func Handler(c *context.Context) { }
-
-// After (Flamego)
-func Handler(c *context.Context, t template.Template, data template.Data) { }
-```
-
-#### 3. Parameter Access
-```go
-// Before (Macaron)
-username := c.Params(":username")
-
-// After (Flamego)
-username := c.Param("username") // No colon
-```
-
-#### 4. Session Interface
-```go
-// Before (Macaron)
-func Handler(sess session.Store) { }
-
-// After (Flamego)
-func Handler(sess session.Session) { }
-```
-
-#### 5. Context Embedding
-```go
-// Before (Macaron)
-type Context struct {
- *macaron.Context // Embedded pointer
-}
-
-// After (Flamego)
-type Context struct {
- flamego.Context // Embedded interface
-}
-```
-
-### Files Requiring Changes
-
-Approximately **150-200 files** need modification:
-
-- **Critical (10 files):** Core setup, context, forms
-- **High (50 files):** Route handlers in user, repo, admin modules
-- **Medium (50 files):** API, LFS, organization routes
-- **Low (40-90 files):** Tests, utilities, documentation
-
-## Why Migrate?
-
-### Benefits
-
-1. **Official Successor** - Created by Macaron's author as its replacement
-2. **Active Development** - Regular updates (Macaron is maintenance-only)
-3. **Better Performance** - Improved routing engine with O(1) static routes
-4. **Modern Go** - Uses Go 1.19+ features and best practices
-5. **Enhanced Routing** - Most powerful routing in Go ecosystem (regex, optional segments)
-6. **Same Philosophy** - Maintains dependency injection pattern
-7. **Future-Proof** - Long-term support and evolution
-
-### Risks
-
-1. **Large Scope** - ~150-200 files need changes
-2. **Testing Burden** - Comprehensive testing required for web functionality
-3. **Learning Curve** - Team needs to learn new APIs
-4. **Migration Time** - 3-4 weeks of focused development
-5. **Potential Bugs** - Risk of introducing regressions
-
-## Recommendation
-
-### ✅ **Proceed with Migration**
-
-The migration is **technically feasible and strategically sound** because:
-
-1. **Complete Feature Parity** - All required middleware available
-2. **Clear Path** - Well-documented migration pattern
-3. **Low Risk** - Easy rollback if issues arise
-4. **Long-term Benefits** - Future-proofs the codebase
-5. **Similar API** - Not a complete rewrite, mostly mechanical changes
-
-### Migration Approach Options
-
-#### Option A: Full Migration (Recommended)
-- Create feature branch
-- Migrate everything at once
-- Comprehensive testing
-- Deploy as single update
-- **Timeline:** 20-25 days
-
-#### Option B: Incremental Migration
-- Use feature flags
-- Migrate module by module
-- Gradual rollout
-- **Timeline:** 30-40 days (slower but safer)
-
-#### Option C: Hybrid Approach
-- Migrate non-critical modules first
-- Test in production with subset of users
-- Migrate critical modules last
-- **Timeline:** 25-35 days
-
-## Implementation Resources
-
-Three comprehensive documents have been created to guide the migration:
-
-1. **[Migration Guide](./macaron_to_flamego_migration.md)** (19KB)
- - Detailed framework comparison
- - Middleware mapping
- - Migration strategy
- - Potential issues and solutions
-
-2. **[Code Examples](./flamego_migration_examples.md)** (27KB)
- - Side-by-side code comparisons
- - Complete working examples
- - Pattern transformations
- - Real-world scenarios from Gogs
-
-3. **[Migration Checklist](./flamego_migration_checklist.md)** (17KB)
- - Step-by-step execution plan
- - 8 phases with daily tasks
- - Testing procedures
- - Rollback procedures
-
-## Missing Middleware Deep Dive
-
-### Toolbox Replacement
-
-**Current Usage:**
-```go
-m.Use(toolbox.Toolboxer(m, toolbox.Options{
- HealthCheckFuncs: []*toolbox.HealthCheckFuncDesc{
- {
- Desc: "Database connection",
- Func: database.Ping,
- },
- },
-}))
-```
-
-**Flamego Replacement:**
-```go
-// Simple health check endpoint
-f.Get("/-/health", func(c flamego.Context) {
- if err := database.Ping(); err != nil {
- c.ResponseWriter().WriteHeader(http.StatusInternalServerError)
- c.ResponseWriter().Write([]byte("Database connection failed"))
- return
- }
- c.ResponseWriter().WriteHeader(http.StatusOK)
- c.ResponseWriter().Write([]byte("OK"))
-})
-
-// Add more health checks as needed
-f.Get("/-/readiness", func(c flamego.Context) {
- // Check all dependencies
- checks := map[string]error{
- "database": database.Ping(),
- "cache": cache.Ping(),
- // Add more...
- }
-
- allHealthy := true
- for _, err := range checks {
- if err != nil {
- allHealthy = false
- break
- }
- }
-
- if allHealthy {
- c.ResponseWriter().WriteHeader(http.StatusOK)
- } else {
- c.ResponseWriter().WriteHeader(http.StatusServiceUnavailable)
- }
-
- json.NewEncoder(c.ResponseWriter()).Encode(checks)
-})
-```
-
-**Conclusion:** Toolbox functionality is easily replaced with ~50 lines of custom code.
-
-## Success Metrics
-
-The migration will be considered successful when:
-
-- [ ] All tests pass (unit + integration)
-- [ ] All manual test cases pass
-- [ ] Performance is equal or better than Macaron
-- [ ] No security vulnerabilities introduced
-- [ ] No functionality lost
-- [ ] Code quality maintained or improved
-- [ ] Documentation updated
-- [ ] Zero critical bugs in first 2 weeks post-deployment
-
-## Conclusion
-
-**To directly answer the original question:**
-
-1. **How would you do it?**
- - Follow the 8-phase approach over 20-25 days
- - Start with dependencies, then core, context, forms, handlers, tests, cleanup, deploy
- - Use the comprehensive checklist and examples provided
- - Test extensively at each phase
-
-2. **Anything missing in Flamego?**
- - **No** - All essential middleware is available
- - Only toolbox (health checks) needs custom implementation
- - Custom implementation is trivial (~50 lines)
- - All other features have direct equivalents
-
-**Final Recommendation:** ✅ **Proceed with migration using the documented approach.**
-
-## Next Steps
-
-If proceeding with migration:
-
-1. **Week 1:** Get team buy-in and schedule migration
-2. **Week 2:** Review documentation and prepare environment
-3. **Weeks 3-5:** Execute migration following checklist
-4. **Week 6:** Testing and deployment
-
-## Additional Resources
-
-- [Flamego Official Documentation](https://flamego.dev/)
-- [Flamego GitHub Repository](https://github.com/flamego/flamego)
-- [Flamego Middleware](https://github.com/flamego)
-- [Macaron to Flamego FAQ](https://flamego.dev/faqs.html#how-is-flamego-different-from-macaron)
-
----
-
-**Document Created:** 2026-01-25
-**Author:** GitHub Copilot
-**Status:** Ready for Review
-**Confidence Level:** High (95%)
-**Risk Assessment:** Medium-Low
-**Recommendation:** Proceed ✅
diff --git a/docs/dev/README_FLAMEGO_MIGRATION.md b/docs/dev/README_FLAMEGO_MIGRATION.md
deleted file mode 100644
index d870986d7..000000000
--- a/docs/dev/README_FLAMEGO_MIGRATION.md
+++ /dev/null
@@ -1,232 +0,0 @@
-# Flamego Migration Documentation
-
-This directory contains comprehensive documentation for migrating Gogs from Macaron to Flamego.
-
-## Quick Navigation
-
-### 📋 Start Here
-- **[FLAMEGO_MIGRATION_SUMMARY.md](./FLAMEGO_MIGRATION_SUMMARY.md)** - Read this first! Answers the core question and provides executive summary
-
-### 📚 Detailed Guides
-- **[macaron_to_flamego_migration.md](./macaron_to_flamego_migration.md)** - Complete migration guide with strategy, timeline, and solutions
-- **[flamego_migration_examples.md](./flamego_migration_examples.md)** - Side-by-side code examples showing before/after patterns
-- **[flamego_migration_checklist.md](./flamego_migration_checklist.md)** - Step-by-step execution checklist with daily tasks
-- **[flamego_quick_reference.md](./flamego_quick_reference.md)** - Quick lookup tables for common patterns and APIs
-
-## Document Purposes
-
-| Document | Purpose | Best For |
-|----------|---------|----------|
-| **SUMMARY** | Decision making | Management, stakeholders |
-| **Migration Guide** | Understanding approach | Tech leads, architects |
-| **Code Examples** | Implementation reference | Developers during coding |
-| **Checklist** | Execution tracking | Project managers, developers |
-| **Quick Reference** | Quick lookups | All developers during migration |
-
-## Reading Order
-
-### For Decision Makers
-1. Read: FLAMEGO_MIGRATION_SUMMARY.md
-2. Scan: macaron_to_flamego_migration.md (focus on risks/benefits)
-3. Review: flamego_migration_checklist.md (focus on timeline)
-
-### For Project Managers
-1. Read: FLAMEGO_MIGRATION_SUMMARY.md
-2. Read: flamego_migration_checklist.md (execution plan)
-3. Reference: macaron_to_flamego_migration.md (technical details)
-
-### For Developers
-1. Read: FLAMEGO_MIGRATION_SUMMARY.md (overview)
-2. Study: flamego_migration_examples.md (learn patterns)
-3. Reference: flamego_quick_reference.md (during coding)
-4. Follow: flamego_migration_checklist.md (track progress)
-
-### For Reviewers
-1. Read: FLAMEGO_MIGRATION_SUMMARY.md
-2. Reference: flamego_quick_reference.md
-3. Check: flamego_migration_examples.md (verify patterns used)
-
-## Key Questions Answered
-
-### "Should we migrate?"
-✅ Yes - see [FLAMEGO_MIGRATION_SUMMARY.md](./FLAMEGO_MIGRATION_SUMMARY.md)
-- Complete feature parity
-- Better performance
-- Active development
-- Official successor
-
-### "What's involved?"
-📋 See [macaron_to_flamego_migration.md](./macaron_to_flamego_migration.md)
-- 8 phases over 20-25 days
-- ~150-200 files to modify
-- Comprehensive testing required
-
-### "How do I do X in Flamego?"
-🔍 See [flamego_quick_reference.md](./flamego_quick_reference.md)
-- Quick lookup tables
-- Common patterns
-- Method mappings
-
-### "What does the code look like?"
-💻 See [flamego_migration_examples.md](./flamego_migration_examples.md)
-- Side-by-side comparisons
-- Complete working examples
-- Real-world scenarios
-
-### "What's the step-by-step process?"
-✅ See [flamego_migration_checklist.md](./flamego_migration_checklist.md)
-- Day-by-day tasks
-- Testing procedures
-- Rollback procedures
-
-## Migration at a Glance
-
-### Timeline
-```
-Phase 1: Dependencies [1 day] ████
-Phase 2: Core Framework [2-3 days] ████████
-Phase 3: Context System [2-3 days] ████████
-Phase 4: Form Binding [2 days] ████
-Phase 5: Route Handlers [7 days] ████████████████████
-Phase 6: Testing [4 days] ████████████
-Phase 7: Cleanup [2 days] ████
-Phase 8: Deployment [2 days] ████
- ─────────
- Total: 20-25 days
-```
-
-### Feature Parity
-
-| Feature | Macaron | Flamego | Status |
-|---------|---------|---------|--------|
-| Core framework | ✅ | ✅ | Full parity |
-| Routing | ✅ | ✅ | Enhanced in Flamego |
-| Middleware | ✅ | ✅ | All available |
-| Session | ✅ | ✅ | Full parity |
-| CSRF | ✅ | ✅ | Full parity |
-| Cache | ✅ | ✅ | Full parity |
-| i18n | ✅ | ✅ | Full parity |
-| Forms | ✅ | ✅ | Full parity |
-| Templates | ✅ | ✅ | Full parity |
-| Toolbox | ✅ | ⚠️ | Easy to replace |
-
-**Overall: ✅ 99% feature parity** (only toolbox needs custom code)
-
-### Files to Modify
-
-```
-Core setup: 10 files
-Route handlers: 100+ files
-Forms: 6 files
-Tests: 50+ files
-Documentation: 10+ files
- ─────────
-Total: ~180-200 files
-```
-
-### Risk Assessment
-
-| Risk Level | Description | Mitigation |
-|------------|-------------|------------|
-| 🟢 Low | Technical feasibility | Clear migration path documented |
-| 🟡 Medium | Time commitment | 3-4 weeks allocated |
-| 🟡 Medium | Testing burden | Comprehensive test plan included |
-| 🟢 Low | Rollback difficulty | Easy git revert, backup plan ready |
-| 🟢 Low | Missing features | All features available |
-
-### Success Criteria
-
-✅ All tests pass
-✅ Performance equal or better
-✅ No security regressions
-✅ No functionality lost
-✅ Zero critical bugs (first 2 weeks)
-
-## External Resources
-
-- [Flamego Official Docs](https://flamego.dev/)
-- [Flamego GitHub](https://github.com/flamego/flamego)
-- [Flamego Middleware](https://github.com/flamego)
-- [Flamego Examples](https://github.com/flamego/flamego/tree/main/_examples)
-- [Macaron to Flamego FAQ](https://flamego.dev/faqs.html#how-is-flamego-different-from-macaron)
-
-## Quick Comparisons
-
-### Import Changes
-```go
-// Before
-import "gopkg.in/macaron.v1"
-
-// After
-import "github.com/flamego/flamego"
-```
-
-### Route Syntax
-```go
-// Before
-m.Get("/:username/:repo", handler)
-
-// After
-f.Get("//", handler)
-```
-
-### Handler Signature
-```go
-// Before
-func Handler(c *macaron.Context) { }
-
-// After
-func Handler(c flamego.Context) { }
-```
-
-### Parameter Access
-```go
-// Before
-username := c.Params(":username")
-
-// After
-username := c.Param("username")
-```
-
-## Support
-
-### Questions?
-- Read the documentation in order listed above
-- Check the quick reference for specific patterns
-- Review code examples for implementation details
-
-### Found an Issue?
-- Document in the checklist notes section
-- Update examples if solution found
-- Share with team
-
-### Need Help?
-- Flamego community: https://github.com/flamego/flamego/discussions
-- Flamego issues: https://github.com/flamego/flamego/issues
-
-## Document Metadata
-
-| Document | Size | Last Updated | Status |
-|----------|------|--------------|--------|
-| FLAMEGO_MIGRATION_SUMMARY.md | 10 KB | 2026-01-25 | ✅ Complete |
-| macaron_to_flamego_migration.md | 19 KB | 2026-01-25 | ✅ Complete |
-| flamego_migration_examples.md | 27 KB | 2026-01-25 | ✅ Complete |
-| flamego_migration_checklist.md | 17 KB | 2026-01-25 | ✅ Complete |
-| flamego_quick_reference.md | 15 KB | 2026-01-25 | ✅ Complete |
-| **Total** | **88 KB** | | **Ready for use** |
-
-## License
-
-These documents are part of the Gogs project and follow the same license.
-
-## Contributing
-
-If you find errors or have improvements:
-1. Make corrections
-2. Update relevant documents
-3. Ensure consistency across all docs
-4. Submit PR
-
----
-
-**Ready to start?** → Begin with [FLAMEGO_MIGRATION_SUMMARY.md](./FLAMEGO_MIGRATION_SUMMARY.md)
diff --git a/docs/dev/flamego_migration_checklist.md b/docs/dev/flamego_migration_checklist.md
deleted file mode 100644
index fa8f33fd8..000000000
--- a/docs/dev/flamego_migration_checklist.md
+++ /dev/null
@@ -1,658 +0,0 @@
-# Flamego Migration Checklist
-
-This checklist provides a step-by-step guide for executing the migration from Macaron to Flamego.
-
-## Pre-Migration
-
-### 1. Team Preparation
-- [ ] Review migration guide with entire team
-- [ ] Ensure all developers understand Flamego basics
-- [ ] Allocate 3-4 weeks for migration effort
-- [ ] Schedule regular sync meetings during migration
-- [ ] Identify rollback champion
-
-### 2. Documentation Review
-- [ ] Read [Flamego documentation](https://flamego.dev/)
-- [ ] Review [migration examples](./flamego_migration_examples.md)
-- [ ] Review [middleware documentation](https://flamego.dev/middleware/)
-- [ ] Understand Flamego's dependency injection
-
-### 3. Environment Setup
-- [ ] Create feature branch: `feature/flamego-migration`
-- [ ] Set up local development environment
-- [ ] Verify current tests pass: `go test ./...`
-- [ ] Document current test coverage: `go test -cover ./...`
-- [ ] Benchmark current performance (optional)
-
-### 4. Backup and Safety
-- [ ] Tag current stable version: `git tag v0.14.0-pre-flamego`
-- [ ] Create backup branch: `git branch backup/before-flamego`
-- [ ] Document current behavior (screenshots, videos)
-- [ ] Ensure CI/CD can roll back quickly
-
-## Phase 1: Dependencies (Day 1)
-
-### 1.1 Update go.mod
-
-- [ ] Add Flamego core
- ```bash
- go get github.com/flamego/flamego@latest
- ```
-
-- [ ] Add Flamego middleware
- ```bash
- go get github.com/flamego/binding@latest
- go get github.com/flamego/cache@latest
- go get github.com/flamego/captcha@latest
- go get github.com/flamego/csrf@latest
- go get github.com/flamego/gzip@latest
- go get github.com/flamego/i18n@latest
- go get github.com/flamego/session@latest
- go get github.com/flamego/template@latest
- ```
-
-- [ ] Run `go mod tidy`
-- [ ] Verify no conflicts
-- [ ] Commit: `git commit -m "Add Flamego dependencies"`
-
-### 1.2 Import Updates
-
-- [ ] Create find-and-replace script
-- [ ] Test script on sample file
-- [ ] Document import mapping
-
-## Phase 2: Core Framework (Days 2-3)
-
-### 2.1 Main Application Setup
-
-File: `internal/cmd/web.go`
-
-- [ ] Update imports
- ```go
- // Remove
- "gopkg.in/macaron.v1"
- "github.com/go-macaron/*"
-
- // Add
- "github.com/flamego/flamego"
- "github.com/flamego/session"
- "github.com/flamego/csrf"
- // etc...
- ```
-
-- [ ] Rename function: `newMacaron()` → `newFlamego()`
-- [ ] Update initialization: `macaron.New()` → `flamego.New()`
-- [ ] Update logger: `macaron.Logger()` → `flamego.Logger()`
-- [ ] Update recovery: `macaron.Recovery()` → `flamego.Recovery()`
-- [ ] Update gzip: Keep similar pattern
-- [ ] Update static file serving
- ```go
- // Change from macaron.Static() to flamego.Static()
- f.Use(flamego.Static(...))
- ```
-
-- [ ] Update renderer setup
- ```go
- // Change from macaron.Renderer() to template.Templater()
- f.Use(template.Templater(template.Options{...}))
- ```
-
-- [ ] Test compilation: `go build`
-- [ ] Fix any compilation errors
-- [ ] Commit: `git commit -m "Migrate main app setup to Flamego"`
-
-### 2.2 Middleware Configuration
-
-Still in `internal/cmd/web.go`:
-
-- [ ] Update i18n middleware
- - Change `Langs` → `Languages`
- - Change `DefaultLang` → `DefaultLanguage`
- - Change `SubURL` → `URLPrefix`
-
-- [ ] Update cache middleware
- - Change from string adapter to config struct
- - Update method calls: `Put()` → `Set()`
-
-- [ ] Update captcha middleware
- - Verify options compatibility
- - Test captcha generation
-
-- [ ] Update toolbox functionality
- - Remove toolbox middleware
- - Create custom health check endpoint
- ```go
- f.Get("/-/health", func(c flamego.Context) {
- if err := database.Ping(); err != nil {
- c.ResponseWriter().WriteHeader(500)
- return
- }
- c.ResponseWriter().WriteHeader(200)
- })
- ```
-
-- [ ] Update session middleware
- - Change `Provider` → Use config structs
- - Change interface: `session.Store` → `session.Session`
- - Test session persistence
-
-- [ ] Update CSRF middleware
- - Verify options compatibility
- - Test token generation
- - Update method calls: `GetToken()` → `Token()`
-
-- [ ] Test server starts: `go run gogs.go web`
-- [ ] Verify middleware loads in correct order
-- [ ] Commit: `git commit -m "Migrate middleware to Flamego"`
-
-### 2.3 Route Definitions
-
-Still in `internal/cmd/web.go`:
-
-- [ ] Update basic routes: `:param` → ``
-- [ ] Update regex routes: `^:name(a|b)$` → ``
-- [ ] Test route compilation
-- [ ] Verify route pattern matching
-
-Routes to update:
-- [ ] Home route: `/`
-- [ ] Explore routes: `/explore/*`
-- [ ] Install routes: `/install`
-- [ ] User routes: `/user/*`
-- [ ] Admin routes: `/admin/*`
-- [ ] Org routes: `/org/*`
-- [ ] Repo routes: `/:username/:reponame/*`
-- [ ] API routes: `/api/*`
-
-- [ ] Commit: `git commit -m "Update route syntax to Flamego"`
-
-## Phase 3: Context System (Days 4-5)
-
-### 3.1 Context Wrapper
-
-File: `internal/context/context.go`
-
-- [ ] Update imports
- ```go
- "github.com/flamego/flamego"
- "github.com/flamego/cache"
- "github.com/flamego/csrf"
- "github.com/flamego/session"
- ```
-
-- [ ] Update Context struct
- ```go
- type Context struct {
- flamego.Context // Embedded interface
- cache cache.Cache
- csrf csrf.CSRF
- flash *session.Flash
- session session.Session
- // ... other fields
- }
- ```
-
-- [ ] Add accessor methods
- ```go
- func (c *Context) Cache() cache.Cache { return c.cache }
- func (c *Context) CSRF() csrf.CSRF { return c.csrf }
- func (c *Context) Session() session.Session { return c.session }
- ```
-
-- [ ] Update Contexter middleware signature
- ```go
- func Contexter(store Store) flamego.Handler {
- return func(
- ctx flamego.Context,
- cache cache.Cache,
- sess session.Session,
- // ... other injectables
- ) {
- // ...
- }
- }
- ```
-
-- [ ] Update response methods
- - `c.HTML()` - needs template parameter or stored reference
- - `c.JSON()` - use ResponseWriter directly
- - `c.Redirect()` - should work as-is
- - `c.PlainText()` - use ResponseWriter
- - `c.ServeContent()` - use ResponseWriter
-
-- [ ] Update parameter access
- - All `c.Params(":name")` → `c.Param("name")`
-
-- [ ] Test compilation: `go build`
-- [ ] Commit: `git commit -m "Migrate Context wrapper to Flamego"`
-
-### 3.2 Other Context Files
-
-Files in `internal/context/`:
-
-- [ ] `auth.go` - Update handler signatures
-- [ ] `api.go` - Update APIContext
-- [ ] `user.go` - Update user context helpers
-- [ ] `repo.go` - Update repository context
- - Fix all `c.Params()` calls
- - Update middleware signatures
-- [ ] `org.go` - Update organization context
-- [ ] `go_get.go` - Update go-get handler
-
-For each file:
-- [ ] Update imports
-- [ ] Change `macaron.Handler` → `flamego.Handler`
-- [ ] Change `*macaron.Context` → `flamego.Context` or `*Context`
-- [ ] Update `c.Params(":name")` → `c.Param("name")`
-- [ ] Fix compilation errors
-
-- [ ] Commit after each file or group
-- [ ] Final commit: `git commit -m "Complete context system migration"`
-
-## Phase 4: Form Binding (Days 6-7)
-
-### 4.1 Form Package
-
-File: `internal/form/form.go`
-
-- [ ] Update imports
- ```go
- "github.com/flamego/binding"
- ```
-
-- [ ] Update custom validators
- ```go
- // Register custom validators with go-playground/validator
- binding.RegisterValidation("alphaDashDot", validatorFunc)
- ```
-
-- [ ] Update `SetNameMapper` if used
-- [ ] Test validator registration
-
-### 4.2 Form Structs
-
-Files: `internal/form/*.go`
-
-For each file:
-- [ ] `auth.go` - Update auth forms
-- [ ] `admin.go` - Update admin forms
-- [ ] `user.go` - Update user forms
-- [ ] `repo.go` - Update repo forms
-- [ ] `org.go` - Update org forms
-
-For each form:
-- [ ] Change `binding:"Required"` → `validate:"required"`
-- [ ] Change `binding:"MaxSize(100)"` → `validate:"max=100"`
-- [ ] Change `binding:"MinSize(5)"` → `validate:"min=5"`
-- [ ] Update custom validators
-- [ ] Test form validation
-
-Pattern replacements:
-- `binding:"Required"` → `validate:"required"`
-- `binding:"AlphaDashDot"` → `validate:"alphaDashDot"`
-- `binding:"MaxSize(N)"` → `validate:"max=N"`
-- `binding:"MinSize(N)"` → `validate:"min=N"`
-- `binding:"Email"` → `validate:"email"`
-- `binding:"Url"` → `validate:"url"`
-
-- [ ] Commit: `git commit -m "Migrate form binding to Flamego"`
-
-## Phase 5: Route Handlers (Days 8-14)
-
-### 5.1 User Routes
-
-Files: `internal/route/user/*.go`
-
-- [ ] `user.go` - Basic user handlers
- - Update handler signatures
- - Add template parameters
- - Fix parameter access
-
-- [ ] `auth.go` - Login/logout handlers
- - Update session access: `sess.Get()` etc.
- - Fix CSRF token access
-
-- [ ] `setting.go` - User settings
- - Update form binding usage
- - Fix template rendering
-
-- [ ] Test user flows:
- - [ ] User registration
- - [ ] User login
- - [ ] User logout
- - [ ] Profile view
- - [ ] Settings update
-
-- [ ] Commit: `git commit -m "Migrate user routes to Flamego"`
-
-### 5.2 Repository Routes
-
-Files: `internal/route/repo/*.go`
-
-Priority files:
-- [ ] `repo.go` - Main repo handler
-- [ ] `home.go` - Repository home
-- [ ] `issue.go` - Issue management
-- [ ] `pull.go` - Pull requests
-- [ ] `release.go` - Releases
-- [ ] `webhook.go` - Webhooks
-- [ ] `setting.go` - Repo settings
-- [ ] `http.go` - HTTP Git operations
-
-For each file:
-- [ ] Update imports
-- [ ] Update handler signatures
-- [ ] Add template parameters where needed
-- [ ] Fix `c.Params()` calls
-- [ ] Update form binding calls
-
-- [ ] Test repository flows:
- - [ ] Create repository
- - [ ] View repository
- - [ ] Create issue
- - [ ] Create pull request
- - [ ] Push via HTTP
-
-- [ ] Commit: `git commit -m "Migrate repository routes to Flamego"`
-
-### 5.3 Admin Routes
-
-Files: `internal/route/admin/*.go`
-
-- [ ] `admin.go` - Admin dashboard
-- [ ] `user.go` - User management
-- [ ] `org.go` - Organization management
-- [ ] `repo.go` - Repository management
-- [ ] `auth.go` - Auth source management
-- [ ] `notice.go` - System notices
-
-- [ ] Test admin flows:
- - [ ] Admin dashboard
- - [ ] Create user
- - [ ] Delete user
- - [ ] Manage auth sources
-
-- [ ] Commit: `git commit -m "Migrate admin routes to Flamego"`
-
-### 5.4 Organization Routes
-
-Files: `internal/route/org/*.go`
-
-- [ ] `org.go` - Organization handlers
-- [ ] `team.go` - Team management
-- [ ] `setting.go` - Org settings
-
-- [ ] Test organization flows:
- - [ ] Create organization
- - [ ] Manage teams
- - [ ] Org settings
-
-- [ ] Commit: `git commit -m "Migrate organization routes to Flamego"`
-
-### 5.5 API Routes
-
-Files: `internal/route/api/v1/*.go`
-
-- [ ] `api.go` - API router setup
-- [ ] `user/*.go` - User API endpoints
-- [ ] `repo/*.go` - Repository API endpoints
-- [ ] `org/*.go` - Organization API endpoints
-- [ ] `admin/*.go` - Admin API endpoints
-
-For API handlers:
-- [ ] Update JSON response methods
-- [ ] Ensure authentication works
-- [ ] Test error responses
-
-- [ ] Test API endpoints:
- - [ ] GET /api/v1/user
- - [ ] GET /api/v1/users/:username
- - [ ] GET /api/v1/repos/:owner/:repo
- - [ ] Create/Update operations
-
-- [ ] Commit: `git commit -m "Migrate API routes to Flamego"`
-
-### 5.6 LFS Routes
-
-Files: `internal/route/lfs/*.go`
-
-- [ ] `route.go` - LFS router
-- [ ] `basic.go` - Basic auth
-- [ ] `batch.go` - Batch API
-- [ ] Update tests in `*_test.go`
-
-- [ ] Test LFS operations
-- [ ] Commit: `git commit -m "Migrate LFS routes to Flamego"`
-
-### 5.7 Other Routes
-
-Files: `internal/route/*.go`
-
-- [ ] `home.go` - Home page
-- [ ] `install.go` - Installation
-- [ ] `dev/*.go` - Development tools
-
-- [ ] Commit: `git commit -m "Migrate remaining routes to Flamego"`
-
-## Phase 6: Testing (Days 15-18)
-
-### 6.1 Unit Tests
-
-- [ ] Update test helpers
- - Create mock flamego.Context
- - Update test fixtures
-
-- [ ] Run unit tests: `go test ./internal/context/...`
-- [ ] Run unit tests: `go test ./internal/form/...`
-- [ ] Run unit tests: `go test ./internal/route/...`
-
-- [ ] Fix failing tests one by one
-- [ ] Document test changes
-- [ ] Commit: `git commit -m "Fix unit tests for Flamego"`
-
-### 6.2 Integration Tests
-
-- [ ] Update integration test setup
-- [ ] Test complete user flows
- - [ ] Registration → Login → Create Repo → Push → Pull
-
-- [ ] Test admin flows
- - [ ] Admin login → User management
-
-- [ ] Test API flows
- - [ ] Token auth → API calls
-
-- [ ] Commit: `git commit -m "Fix integration tests for Flamego"`
-
-### 6.3 Manual Testing
-
-Create test plan document covering:
-
-Web UI:
-- [ ] Homepage loads
-- [ ] User registration works
-- [ ] User login works
-- [ ] User logout works
-- [ ] Profile viewing works
-- [ ] Repository creation works
-- [ ] Repository viewing works
-- [ ] Issue creation works
-- [ ] Issue commenting works
-- [ ] Pull request creation works
-- [ ] Pull request merging works
-- [ ] Webhooks work
-- [ ] LFS operations work
-- [ ] File uploads work
-- [ ] Avatar uploads work
-- [ ] Admin panel works
-- [ ] Organization creation works
-- [ ] Team management works
-
-Git Operations:
-- [ ] HTTP clone works
-- [ ] HTTP push works
-- [ ] HTTP pull works
-- [ ] SSH clone works
-- [ ] SSH push works
-- [ ] SSH pull works
-
-API:
-- [ ] Authentication works
-- [ ] All v1 endpoints work
-- [ ] Error responses correct
-
-Security:
-- [ ] CSRF protection works
-- [ ] Session security works
-- [ ] Auth required endpoints protected
-
-Localization:
-- [ ] Language switching works
-- [ ] Translations load correctly
-
-- [ ] Document any issues found
-- [ ] Create issues for bugs
-- [ ] Commit fixes as they're made
-
-### 6.4 Performance Testing
-
-- [ ] Benchmark homepage
-- [ ] Benchmark repository view
-- [ ] Benchmark API endpoints
-- [ ] Compare with pre-migration benchmarks
-- [ ] Document performance differences
-- [ ] Optimize if needed
-
-## Phase 7: Cleanup (Days 19-20)
-
-### 7.1 Remove Old Code
-
-- [ ] Remove all Macaron imports
- ```bash
- grep -r "gopkg.in/macaron.v1" .
- grep -r "github.com/go-macaron/" .
- ```
-
-- [ ] Remove from go.mod
- ```bash
- go mod edit -droprequire gopkg.in/macaron.v1
- go mod edit -droprequire github.com/go-macaron/binding
- # etc...
- ```
-
-- [ ] Run `go mod tidy`
-- [ ] Verify unused dependencies removed
-- [ ] Commit: `git commit -m "Remove Macaron dependencies"`
-
-### 7.2 Code Quality
-
-- [ ] Run linter: `golangci-lint run`
-- [ ] Fix linter issues
-- [ ] Run `go fmt ./...`
-- [ ] Run `go vet ./...`
-- [ ] Check for TODO/FIXME comments
-- [ ] Commit: `git commit -m "Code quality improvements"`
-
-### 7.3 Documentation
-
-- [ ] Update README.md if needed
-- [ ] Update CONTRIBUTING.md if needed
-- [ ] Update development documentation
-- [ ] Document migration in CHANGELOG.md
-- [ ] Create migration announcement
-- [ ] Commit: `git commit -m "Update documentation for Flamego"`
-
-### 7.4 Final Review
-
-- [ ] Review all changes
-- [ ] Ensure no debug code left
-- [ ] Verify test coverage maintained
-- [ ] Check for security issues
-- [ ] Run final test suite: `go test ./...`
-- [ ] Run final manual tests
-
-## Phase 8: Deployment (Days 21-22)
-
-### 8.1 Pre-Deployment
-
-- [ ] Create release candidate tag
-- [ ] Deploy to staging environment
-- [ ] Run smoke tests on staging
-- [ ] Performance test on staging
-- [ ] Security scan
-- [ ] Get team approval
-
-### 8.2 Deployment
-
-- [ ] Schedule deployment window
-- [ ] Notify users of maintenance
-- [ ] Take backup of production
-- [ ] Deploy new version
-- [ ] Monitor logs
-- [ ] Run smoke tests on production
-- [ ] Monitor performance metrics
-
-### 8.3 Post-Deployment
-
-- [ ] Monitor for issues (24-48 hours)
-- [ ] Check error rates
-- [ ] Verify all features working
-- [ ] Collect user feedback
-- [ ] Address any urgent issues
-
-## Rollback Procedure
-
-If critical issues occur:
-
-### Quick Rollback
-1. [ ] Stop application
-2. [ ] Restore from backup
-3. [ ] Start application
-4. [ ] Verify functionality
-5. [ ] Notify users
-
-### Git Rollback
-1. [ ] Identify last good commit
-2. [ ] `git revert `
-3. [ ] `git push`
-4. [ ] Deploy reverted version
-
-### Issues to Watch For
-- [ ] Session persistence issues
-- [ ] CSRF token validation failures
-- [ ] Form validation errors
-- [ ] Template rendering errors
-- [ ] Performance degradation
-- [ ] Memory leaks
-- [ ] Authentication bypass
-
-## Success Criteria
-
-Migration is successful when:
-
-- [ ] All tests pass
-- [ ] All manual test cases pass
-- [ ] Performance is equal or better
-- [ ] No security regressions
-- [ ] No functionality lost
-- [ ] Code quality maintained
-- [ ] Documentation updated
-- [ ] Team trained on new code
-
-## Notes Section
-
-Use this section to track:
-- Issues encountered
-- Solutions found
-- Time spent on each phase
-- Lessons learned
-- Tips for future migrations
-
----
-
-**Migration Started:** _____________
-**Migration Completed:** _____________
-**Total Time:** _____________
-**Team Members:** _____________
-**Issues Created:** _____________
-**Issues Resolved:** _____________
diff --git a/docs/dev/flamego_migration_examples.md b/docs/dev/flamego_migration_examples.md
deleted file mode 100644
index 96f628675..000000000
--- a/docs/dev/flamego_migration_examples.md
+++ /dev/null
@@ -1,1196 +0,0 @@
-# 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("/", user.Profile)
-f.Get("//", context.RepoAssignment(), repo.Home)
-```
-
-**Key Changes:**
-- `:param` becomes ``
-- `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("/", 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/?", 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("//", 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 `` 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.
diff --git a/docs/dev/flamego_quick_reference.md b/docs/dev/flamego_quick_reference.md
deleted file mode 100644
index 9fcd7b642..000000000
--- a/docs/dev/flamego_quick_reference.md
+++ /dev/null
@@ -1,595 +0,0 @@
-# Macaron vs Flamego: Quick Reference
-
-This document provides quick lookup tables for common migration patterns.
-
-## At a Glance
-
-| Aspect | Macaron | Flamego | Status |
-|--------|---------|---------|--------|
-| **Creator** | Unknwon | Unknwon | ✅ Same author |
-| **Status** | Maintenance only | Active development | ⚠️ Macaron deprecated |
-| **Go Version** | 1.11+ | 1.19+ | 📈 Modern |
-| **Philosophy** | Dependency injection | Dependency injection | ✅ Same |
-| **Performance** | Good | Better | 📈 Improved |
-| **Routing** | Basic | Advanced | 📈 Enhanced |
-
-## Import Mapping
-
-| Macaron Package | Flamego Package | Notes |
-|----------------|-----------------|-------|
-| `gopkg.in/macaron.v1` | `github.com/flamego/flamego` | Core framework |
-| `github.com/go-macaron/binding` | `github.com/flamego/binding` | Form binding |
-| `github.com/go-macaron/cache` | `github.com/flamego/cache` | Caching |
-| `github.com/go-macaron/captcha` | `github.com/flamego/captcha` | Captcha |
-| `github.com/go-macaron/csrf` | `github.com/flamego/csrf` | CSRF protection |
-| `github.com/go-macaron/gzip` | `github.com/flamego/gzip` | Gzip compression |
-| `github.com/go-macaron/i18n` | `github.com/flamego/i18n` | Internationalization |
-| `github.com/go-macaron/session` | `github.com/flamego/session` | Session management |
-| Built-in | `github.com/flamego/template` | Template rendering |
-| `github.com/go-macaron/toolbox` | ❌ Custom implementation | Health checks |
-
-## Type Mapping
-
-| Macaron Type | Flamego Type | Change |
-|--------------|--------------|--------|
-| `*macaron.Macaron` | `*flamego.Flame` | Main app type |
-| `macaron.Context` | `flamego.Context` | Interface vs pointer |
-| `macaron.Handler` | `flamego.Handler` | Same concept |
-| `session.Store` | `session.Session` | Interface name |
-| `csrf.CSRF` | `csrf.CSRF` | Same |
-| `cache.Cache` | `cache.Cache` | Same |
-
-## Method Mapping
-
-### Core Methods
-
-| Operation | Macaron | Flamego |
-|-----------|---------|---------|
-| Create app | `macaron.New()` | `flamego.New()` |
-| Classic setup | `macaron.Classic()` | `flamego.Classic()` |
-| Add middleware | `m.Use(handler)` | `f.Use(handler)` |
-| GET route | `m.Get(path, h)` | `f.Get(path, h)` |
-| POST route | `m.Post(path, h)` | `f.Post(path, h)` |
-| Route group | `m.Group(path, fn)` | `f.Group(path, fn)` |
-| Combo route | `m.Combo(path)` | `f.Combo(path)` |
-| Start server | `http.ListenAndServe(addr, m)` | `f.Run(addr)` |
-
-### Context Methods
-
-| Operation | Macaron | Flamego |
-|-----------|---------|---------|
-| Get param | `c.Params(":name")` | `c.Param("name")` |
-| Get query | `c.Query("key")` | `c.Query("key")` |
-| Get request | `c.Req` | `c.Request()` |
-| Get response | `c.Resp` | `c.ResponseWriter()` |
-| Redirect | `c.Redirect(url)` | `c.Redirect(url)` |
-| Set cookie | `c.SetCookie(...)` | `c.SetCookie(...)` |
-| Get cookie | `c.GetCookie(name)` | `c.Cookie(name)` |
-
-### Session Methods
-
-| Operation | Macaron | Flamego |
-|-----------|---------|---------|
-| Set value | `sess.Set(k, v)` | `sess.Set(k, v)` |
-| Get value | `sess.Get(k)` | `sess.Get(k)` |
-| Delete | `sess.Delete(k)` | `sess.Delete(k)` |
-| ID | `sess.ID()` | `sess.ID()` |
-| Flush | `sess.Flush()` | `sess.Flush()` |
-
-### CSRF Methods
-
-| Operation | Macaron | Flamego |
-|-----------|---------|---------|
-| Get token | `x.GetToken()` | `x.Token()` |
-| Validate | Automatic | Automatic |
-
-### Cache Methods
-
-| Operation | Macaron | Flamego |
-|-----------|---------|---------|
-| Set value | `c.Put(k, v, timeout)` | `c.Set(k, v, timeout)` |
-| Get value | `c.Get(k)` | `c.Get(k)` |
-| Delete | `c.Delete(k)` | `c.Delete(k)` |
-| Flush | `c.Flush()` | `c.Flush()` |
-
-## Route Syntax
-
-| Feature | Macaron | Flamego | Example |
-|---------|---------|---------|---------|
-| Basic param | `:param` | `` | `/:id` → `/` |
-| Regex param | `^:name(a\|b)$` | `` | Pattern matching |
-| Optional param | Multiple routes | `?` | `/wiki/?` |
-| Wildcard | `:path(*)` | `<**path>` | Glob pattern |
-
-### Before (Macaron)
-```go
-m.Get("/", handler) // Root
-m.Get("/:username", handler) // Basic param
-m.Get("/:username/:repo", handler) // Multiple params
-m.Get("/^:type(issues|pulls)$", handler) // Regex
-```
-
-### After (Flamego)
-```go
-f.Get("/", handler) // Root
-f.Get("/", handler) // Basic param
-f.Get("//", handler) // Multiple params
-f.Get("/", handler) // Regex
-```
-
-## Handler Signatures
-
-### Basic Handler
-
-**Macaron:**
-```go
-func Handler(c *macaron.Context) {
- c.JSON(200, map[string]string{"msg": "hello"})
-}
-```
-
-**Flamego:**
-```go
-func Handler(c flamego.Context) {
- c.ResponseWriter().Header().Set("Content-Type", "application/json")
- c.ResponseWriter().WriteHeader(200)
- json.NewEncoder(c.ResponseWriter()).Encode(map[string]string{"msg": "hello"})
-}
-```
-
-### With Custom Context
-
-**Macaron:**
-```go
-func Handler(c *context.Context) {
- c.Data["Title"] = "Page"
- c.HTML(200, "template")
-}
-```
-
-**Flamego:**
-```go
-func Handler(c *context.Context, t template.Template, data template.Data) {
- data["Title"] = "Page"
- t.HTML(200, "template")
-}
-```
-
-### With Session
-
-**Macaron:**
-```go
-func Handler(c *context.Context, sess session.Store) {
- sess.Set("key", "value")
-}
-```
-
-**Flamego:**
-```go
-func Handler(c *context.Context, sess session.Session) {
- sess.Set("key", "value")
-}
-```
-
-### With Form Binding
-
-**Macaron:**
-```go
-func Handler(c *context.Context, form Form) {
- // Use form
-}
-
-// Route
-m.Post("/", binding.Bind(Form{}), Handler)
-```
-
-**Flamego:**
-```go
-func Handler(c *context.Context, form Form) {
- // Use form
-}
-
-// Route
-f.Post("/", binding.Form(Form{}), Handler)
-```
-
-## Form Tags
-
-| Validation | Macaron | Flamego |
-|------------|---------|---------|
-| Required | `binding:"Required"` | `validate:"required"` |
-| Email | `binding:"Email"` | `validate:"email"` |
-| URL | `binding:"Url"` | `validate:"url"` |
-| Min length | `binding:"MinSize(5)"` | `validate:"min=5"` |
-| Max length | `binding:"MaxSize(100)"` | `validate:"max=100"` |
-| Range | `binding:"Range(1,10)"` | `validate:"min=1,max=10"` |
-| Alpha | `binding:"Alpha"` | `validate:"alpha"` |
-| AlphaDash | `binding:"AlphaDash"` | `validate:"alphanum"` |
-
-### Before (Macaron)
-```go
-type LoginForm struct {
- Username string `form:"username" binding:"Required;AlphaDash"`
- Password string `form:"password" binding:"Required;MinSize(6)"`
- Email string `form:"email" binding:"Email"`
-}
-```
-
-### After (Flamego)
-```go
-type LoginForm struct {
- Username string `form:"username" validate:"required,alphanum"`
- Password string `form:"password" validate:"required,min=6"`
- Email string `form:"email" validate:"email"`
-}
-```
-
-## Middleware Configuration
-
-### Session
-
-**Macaron:**
-```go
-m.Use(session.Sessioner(session.Options{
- Provider: "memory",
- ProviderConfig: "",
- CookieName: "session_id",
- CookiePath: "/",
- Gclifetime: 3600,
- Maxlifetime: 3600,
-}))
-```
-
-**Flamego:**
-```go
-f.Use(session.Sessioner(session.Options{
- Config: session.MemoryConfig{
- GCInterval: 3600,
- },
- Cookie: session.CookieOptions{
- Name: "session_id",
- Path: "/",
- MaxAge: 3600,
- },
-}))
-```
-
-### CSRF
-
-**Macaron:**
-```go
-m.Use(csrf.Csrfer(csrf.Options{
- Secret: "secret-key",
- Cookie: "_csrf",
- CookiePath: "/",
- SetCookie: true,
- Secure: false,
-}))
-```
-
-**Flamego:**
-```go
-f.Use(csrf.Csrfer(csrf.Options{
- Secret: "secret-key",
- Cookie: "_csrf",
- CookiePath: "/",
- Secure: false,
-}))
-```
-
-### Cache
-
-**Macaron:**
-```go
-m.Use(cache.Cacher(cache.Options{
- Adapter: "memory",
- AdapterConfig: "",
- Interval: 60,
-}))
-```
-
-**Flamego:**
-```go
-f.Use(cache.Cacher(cache.Options{
- Config: cache.MemoryConfig{
- GCInterval: 60,
- },
-}))
-```
-
-### Template
-
-**Macaron:**
-```go
-m.Use(macaron.Renderer(macaron.RenderOptions{
- Directory: "templates",
- Funcs: template.FuncMap(),
-}))
-```
-
-**Flamego:**
-```go
-f.Use(template.Templater(template.Options{
- Directory: "templates",
- FuncMaps: []template.FuncMap{template.FuncMap()},
-}))
-```
-
-### i18n
-
-**Macaron:**
-```go
-m.Use(i18n.I18n(i18n.Options{
- SubURL: "/",
- Langs: []string{"en-US", "zh-CN"},
- Names: []string{"English", "简体中文"},
- DefaultLang: "en-US",
-}))
-```
-
-**Flamego:**
-```go
-f.Use(i18n.I18n(i18n.Options{
- URLPrefix: "/",
- Languages: []string{"en-US", "zh-CN"},
- Names: []string{"English", "简体中文"},
- DefaultLanguage: "en-US",
-}))
-```
-
-## Common Patterns
-
-### Pattern: Get User by Username
-
-**Macaron:**
-```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(200, "user/profile")
-}
-```
-
-**Flamego:**
-```go
-func UserProfile(c *context.Context, t template.Template, data template.Data) {
- username := c.Param("username")
- user, err := database.GetUserByName(username)
- if err != nil {
- c.NotFoundOrError(err, "get user")
- return
- }
- data["User"] = user
- t.HTML(200, "user/profile")
-}
-```
-
-### Pattern: Form Submission
-
-**Macaron:**
-```go
-// Form struct
-type CreateRepoForm struct {
- Name string `form:"name" binding:"Required;AlphaDashDot"`
-}
-
-// Route
-m.Post("/repo/create", binding.Bind(CreateRepoForm{}), CreateRepoPost)
-
-// Handler
-func CreateRepoPost(c *context.Context, form CreateRepoForm) {
- if c.HasError() {
- c.RenderWithErr(c.GetErrMsg(), "repo/create", &form)
- return
- }
- // Create repo...
- c.Redirect("/")
-}
-```
-
-**Flamego:**
-```go
-// Form struct
-type CreateRepoForm struct {
- Name string `form:"name" validate:"required,alphaDashDot"`
-}
-
-// Route
-f.Post("/repo/create", binding.Form(CreateRepoForm{}), CreateRepoPost)
-
-// Handler
-func CreateRepoPost(c *context.Context, form CreateRepoForm, t template.Template, data template.Data) {
- if c.HasError() {
- c.RenderWithErr(c.GetErrMsg(), "repo/create", &form, t, data)
- return
- }
- // Create repo...
- c.Redirect("/")
-}
-```
-
-### Pattern: JSON API
-
-**Macaron:**
-```go
-func APIHandler(c *context.APIContext) {
- data := map[string]any{
- "id": 123,
- "name": "example",
- }
- c.JSON(200, data)
-}
-```
-
-**Flamego:**
-```go
-func APIHandler(c *context.APIContext) {
- data := map[string]any{
- "id": 123,
- "name": "example",
- }
-
- c.ResponseWriter().Header().Set("Content-Type", "application/json")
- c.ResponseWriter().WriteHeader(200)
- json.NewEncoder(c.ResponseWriter()).Encode(data)
-}
-
-// Or create helper method on APIContext
-func (c *APIContext) JSON(status int, v any) error {
- c.ResponseWriter().Header().Set("Content-Type", "application/json")
- c.ResponseWriter().WriteHeader(status)
- return json.NewEncoder(c.ResponseWriter()).Encode(v)
-}
-```
-
-### Pattern: Middleware Chain
-
-**Macaron:**
-```go
-m.Group("/repo", func() {
- m.Get("/create", repo.Create)
- m.Post("/create", binding.Bind(form.CreateRepo{}), repo.CreatePost)
-}, reqSignIn, context.RepoAssignment())
-```
-
-**Flamego:**
-```go
-f.Group("/repo", func() {
- f.Get("/create", repo.Create)
- f.Post("/create", binding.Form(form.CreateRepo{}), repo.CreatePost)
-}, reqSignIn, context.RepoAssignment())
-```
-
-## Error Handling
-
-### Not Found
-
-**Macaron:**
-```go
-func Handler(c *context.Context) {
- user, err := getUser()
- if err != nil {
- if isNotFound(err) {
- c.NotFound()
- return
- }
- c.Error(err, "get user")
- return
- }
-}
-```
-
-**Flamego:**
-```go
-func Handler(c *context.Context) {
- user, err := getUser()
- if err != nil {
- if isNotFound(err) {
- c.NotFound()
- return
- }
- c.Error(err, "get user")
- return
- }
-}
-```
-
-## Testing
-
-### Mock Context
-
-**Macaron:**
-```go
-import "gopkg.in/macaron.v1"
-
-func TestHandler(t *testing.T) {
- m := macaron.New()
- req, _ := http.NewRequest("GET", "/", nil)
- resp := httptest.NewRecorder()
- m.ServeHTTP(resp, req)
-}
-```
-
-**Flamego:**
-```go
-import "github.com/flamego/flamego"
-
-func TestHandler(t *testing.T) {
- f := flamego.New()
- req, _ := http.NewRequest("GET", "/", nil)
- resp := httptest.NewRecorder()
- f.ServeHTTP(resp, req)
-}
-```
-
-## Migration Checklist (Quick)
-
-- [ ] Update imports
-- [ ] Change `:param` → `` in routes
-- [ ] Change `macaron.Handler` → `flamego.Handler`
-- [ ] Change `*macaron.Context` → `flamego.Context`
-- [ ] Change `c.Params(":name")` → `c.Param("name")`
-- [ ] Change `c.Resp` → `c.ResponseWriter()`
-- [ ] Change `c.Req` → `c.Request()`
-- [ ] Change `session.Store` → `session.Session`
-- [ ] Change `x.GetToken()` → `x.Token()`
-- [ ] Change `cache.Put()` → `cache.Set()`
-- [ ] Add template parameters to handlers
-- [ ] Update form validation tags
-- [ ] Test everything!
-
-## Common Pitfalls
-
-| Issue | Solution |
-|-------|----------|
-| Forgot to remove `:` from param name | Use `c.Param("name")` not `c.Param(":name")` |
-| Template not rendering | Add `template.Template` and `template.Data` to handler |
-| Session not working | Changed interface from `Store` to `Session` |
-| CSRF validation fails | Use `Token()` not `GetToken()` |
-| Cache not working | Use `Set()` not `Put()` |
-| Form validation errors | Update tags: `binding` → `validate` |
-| Context methods fail | Use methods not fields: `c.ResponseWriter()` not `c.Resp` |
-
-## Performance Notes
-
-### Flamego Advantages
-
-1. **O(1) static route lookup** - Faster than Macaron's tree
-2. **Better regex handling** - Compiled patterns cached
-3. **Reduced allocations** - More efficient memory usage
-4. **Faster middleware chain** - Optimized injection
-
-### Expected Improvements
-
-- 10-30% faster route matching for static routes
-- 5-15% faster overall request handling
-- Slightly lower memory usage
-- Better scalability under load
-
-## Support and Resources
-
-| Need Help? | Resource |
-|------------|----------|
-| Official Docs | https://flamego.dev/ |
-| API Reference | https://pkg.go.dev/github.com/flamego/flamego |
-| GitHub | https://github.com/flamego/flamego |
-| Middleware | https://github.com/flamego (multiple repos) |
-| FAQ | https://flamego.dev/faqs.html |
-| Examples | https://github.com/flamego/flamego/tree/main/_examples |
-
-## Version Information
-
-| Framework | Current Version | Release Date | Status |
-|-----------|----------------|--------------|--------|
-| Macaron | v1.5.0 | 2021 | Maintenance |
-| Flamego | v1.9.0+ | 2024 | Active |
-
----
-
-**Last Updated:** 2026-01-25
-**Applies to:** Gogs migration from Macaron to Flamego
diff --git a/docs/dev/macaron_to_flamego_migration.md b/docs/dev/macaron_to_flamego_migration.md
deleted file mode 100644
index 376a9c822..000000000
--- a/docs/dev/macaron_to_flamego_migration.md
+++ /dev/null
@@ -1,715 +0,0 @@
-# Macaron to Flamego Migration Guide
-
-## Executive Summary
-
-This document provides a comprehensive guide for migrating Gogs from the Macaron web framework to Flamego. Flamego is the official successor to Macaron, created by the same author, offering improved performance, better routing capabilities, and modern Go practices.
-
-## Table of Contents
-
-1. [Why Migrate to Flamego?](#why-migrate-to-flamego)
-2. [Framework Comparison](#framework-comparison)
-3. [Middleware Availability](#middleware-availability)
-4. [Migration Strategy](#migration-strategy)
-5. [Code Changes Required](#code-changes-required)
-6. [Potential Issues](#potential-issues)
-7. [Testing Strategy](#testing-strategy)
-8. [Rollback Plan](#rollback-plan)
-
-## Why Migrate to Flamego?
-
-### Benefits of Flamego
-
-1. **Official Successor**: Flamego is created by the same author as Macaron and is designed as its replacement
-2. **Better Performance**: Improved routing engine with O(1) lookup for static routes
-3. **Modern Go**: Requires Go 1.19+, uses modern Go features and best practices
-4. **Enhanced Routing**: Most powerful routing syntax in the Go ecosystem
-5. **Active Development**: Regular updates and maintenance (Macaron is in maintenance mode)
-6. **Better Testing**: Designed with testability in mind
-7. **Same Philosophy**: Maintains the dependency injection pattern that makes Macaron great
-
-### Risks and Considerations
-
-1. **Breaking Changes**: Handler signatures and some middleware APIs differ
-2. **Migration Scope**: ~150+ files need modification
-3. **Testing Burden**: Comprehensive testing required for web functionality
-4. **Learning Curve**: Team needs to understand new APIs and patterns
-5. **Third-party Dependencies**: Some custom Macaron middleware may need replacement
-
-## Framework Comparison
-
-### Core Framework
-
-| Feature | Macaron | Flamego | Notes |
-|---------|---------|---------|-------|
-| **Initialization** | `macaron.New()` | `flamego.New()` | Similar API |
-| **Classic Setup** | `macaron.Classic()` | `flamego.Classic()` | Both include logger, recovery, static |
-| **Handler Signature** | `func(*macaron.Context)` | `func(flamego.Context)` | Flamego uses interface |
-| **Dependency Injection** | Function parameters | Function parameters | Same pattern |
-| **Routing** | Basic | Advanced (regex, optional segments) | Flamego more powerful |
-| **Route Groups** | `m.Group()` | `f.Group()` | Same concept, similar API |
-| **Middleware** | `m.Use()` | `f.Use()` | Same pattern |
-
-### Context API Comparison
-
-| Operation | Macaron | Flamego |
-|-----------|---------|---------|
-| **Get Request** | `c.Req.Request` | `c.Request().Request` |
-| **Get Response** | `c.Resp` | `c.ResponseWriter()` |
-| **URL Params** | `c.Params(":name")` | `c.Param("name")` (no colon) |
-| **Query Params** | `c.Query("key")` | `c.Query("key")` |
-| **Redirect** | `c.Redirect(url)` | `c.Redirect(url)` |
-| **Set Header** | `c.Resp.Header().Set()` | `c.ResponseWriter().Header().Set()` |
-| **JSON Response** | `c.JSON(200, data)` | Use render middleware |
-| **HTML Response** | `c.HTML(200, tpl)` | Use template middleware |
-
-### Example Code Comparison
-
-**Macaron:**
-```go
-m := macaron.New()
-m.Use(macaron.Logger())
-m.Use(macaron.Recovery())
-
-m.Get("/:username/:repo", func(c *macaron.Context) {
- username := c.Params(":username")
- repo := c.Params(":repo")
- c.JSON(200, map[string]string{
- "username": username,
- "repo": repo,
- })
-})
-```
-
-**Flamego:**
-```go
-f := flamego.New()
-f.Use(flamego.Logger())
-f.Use(flamego.Recovery())
-
-f.Get("//", func(c flamego.Context) {
- username := c.Param("username")
- repo := c.Param("repo")
- // Use render middleware for JSON
- c.ResponseWriter().Header().Set("Content-Type", "application/json")
- json.NewEncoder(c.ResponseWriter()).Encode(map[string]string{
- "username": username,
- "repo": repo,
- })
-})
-```
-
-## Middleware Availability
-
-### Middleware Mapping
-
-| Function | Macaron Package | Flamego Package | Status |
-|----------|----------------|-----------------|--------|
-| **Core** | `gopkg.in/macaron.v1` | `github.com/flamego/flamego` | ✅ Available |
-| **Binding** | `github.com/go-macaron/binding` | `github.com/flamego/binding` | ✅ Available |
-| **Cache** | `github.com/go-macaron/cache` | `github.com/flamego/cache` | ✅ Available |
-| **Captcha** | `github.com/go-macaron/captcha` | `github.com/flamego/captcha` | ✅ Available |
-| **CSRF** | `github.com/go-macaron/csrf` | `github.com/flamego/csrf` | ✅ Available |
-| **Gzip** | `github.com/go-macaron/gzip` | `github.com/flamego/gzip` | ✅ Available |
-| **i18n** | `github.com/go-macaron/i18n` | `github.com/flamego/i18n` | ✅ Available |
-| **Session** | `github.com/go-macaron/session` | `github.com/flamego/session` | ✅ Available |
-| **Template** | Built-in `macaron.Renderer()` | `github.com/flamego/template` | ✅ Available |
-| **Toolbox** | `github.com/go-macaron/toolbox` | N/A | ⚠️ Need custom implementation |
-
-### Middleware API Changes
-
-#### Session
-
-**Macaron:**
-```go
-m.Use(session.Sessioner(session.Options{
- Provider: "memory",
- ProviderConfig: "",
-}))
-
-// In handler
-func(sess session.Store) {
- sess.Set("key", "value")
- value := sess.Get("key")
-}
-```
-
-**Flamego:**
-```go
-f.Use(session.Sessioner(session.Options{
- Config: session.MemoryConfig{},
-}))
-
-// In handler
-func(s session.Session) {
- s.Set("key", "value")
- value := s.Get("key")
-}
-```
-
-#### CSRF
-
-**Macaron:**
-```go
-m.Use(csrf.Csrfer(csrf.Options{
- Secret: "secret-key",
-}))
-
-// In handler
-func(x csrf.CSRF) {
- token := x.GetToken()
-}
-```
-
-**Flamego:**
-```go
-f.Use(csrf.Csrfer(csrf.Options{
- Secret: "secret-key",
-}))
-
-// In handler - similar API
-func(x csrf.CSRF) {
- token := x.Token()
-}
-```
-
-#### Binding
-
-**Macaron:**
-```go
-type Form struct {
- Username string `form:"username" binding:"Required"`
-}
-
-m.Post("/signup", binding.Bind(Form{}), func(form Form) {
- // Use form
-})
-```
-
-**Flamego:**
-```go
-type Form struct {
- Username string `form:"username" validate:"required"`
-}
-
-f.Post("/signup", binding.Form(Form{}), func(form Form) {
- // Use form
-})
-```
-
-#### Template/Renderer
-
-**Macaron:**
-```go
-m.Use(macaron.Renderer(macaron.RenderOptions{
- Directory: "templates",
-}))
-
-func(c *macaron.Context) {
- c.HTML(200, "index")
-}
-```
-
-**Flamego:**
-```go
-import "github.com/flamego/template"
-
-f.Use(template.Templater(template.Options{
- Directory: "templates",
-}))
-
-func(t template.Template, data template.Data) {
- data["Title"] = "Home"
- t.HTML(200, "index")
-}
-```
-
-#### Cache
-
-**Macaron:**
-```go
-m.Use(cache.Cacher(cache.Options{
- Adapter: "memory",
-}))
-
-func(cache cache.Cache) {
- cache.Put("key", "value", 60)
- value := cache.Get("key")
-}
-```
-
-**Flamego:**
-```go
-import "github.com/flamego/cache"
-
-f.Use(cache.Cacher(cache.Options{
- Config: cache.MemoryConfig{},
-}))
-
-func(c cache.Cache) {
- c.Set("key", "value", 60)
- value := c.Get("key")
-}
-```
-
-## Migration Strategy
-
-### Phase 1: Preparation (1-2 days)
-
-1. **Create feature branch**: `feature/flamego-migration`
-2. **Update go.mod**: Add Flamego dependencies
-3. **Study Flamego docs**: Ensure team understanding
-4. **Identify custom middleware**: Document any custom Macaron extensions
-5. **Setup test environment**: Ensure comprehensive test coverage
-
-### Phase 2: Core Migration (3-5 days)
-
-1. **Update main application setup** (`internal/cmd/web.go`)
- - Replace `macaron.New()` with `flamego.New()`
- - Convert middleware stack to Flamego
- - Update static file serving
-
-2. **Update Context wrapper** (`internal/context/context.go`)
- - Change from `*macaron.Context` to `flamego.Context`
- - Update all Context methods to use Flamego APIs
- - Ensure backward compatibility where possible
-
-3. **Migrate middleware configuration**
- - Session → Flamego session
- - CSRF → Flamego csrf
- - Cache → Flamego cache
- - i18n → Flamego i18n
- - Template rendering → Flamego template
- - Gzip → Flamego gzip
- - Captcha → Flamego captcha
-
-### Phase 3: Route Handlers (5-7 days)
-
-1. **Update route definitions**
- - Change route parameter syntax (`:param` → ``)
- - Update regex patterns if used
- - Test all route patterns
-
-2. **Update handler functions** (organized by module)
- - User routes (`internal/route/user/*.go`)
- - Repo routes (`internal/route/repo/*.go`)
- - Admin routes (`internal/route/admin/*.go`)
- - Org routes (`internal/route/org/*.go`)
- - API routes (`internal/route/api/v1/*.go`)
- - LFS routes (`internal/route/lfs/*.go`)
-
-3. **Update context usage in handlers**
- - Replace `c.Params(":name")` with `c.Param("name")`
- - Update response methods
- - Update redirect calls
-
-### Phase 4: Forms and Binding (2-3 days)
-
-1. **Update form structs** (`internal/form/*.go`)
- - Change binding tags to Flamego format
- - Update validation rules
- - Test form binding with all HTTP methods
-
-2. **Update custom validators**
- - Adapt to Flamego's validation system
- - Ensure all custom rules work
-
-### Phase 5: Testing (3-5 days)
-
-1. **Unit tests**
- - Update test helpers
- - Fix broken tests
- - Add new tests for changed functionality
-
-2. **Integration tests**
- - Test all major user flows
- - Test API endpoints
- - Test authentication/authorization
-
-3. **Manual testing**
- - Test UI flows
- - Test file uploads
- - Test webhooks
- - Test LFS
-
-### Phase 6: Performance and Polish (2-3 days)
-
-1. **Performance testing**
- - Benchmark critical paths
- - Compare with Macaron version
- - Optimize if needed
-
-2. **Code cleanup**
- - Remove old Macaron imports
- - Update comments and documentation
- - Remove unused code
-
-3. **Documentation updates**
- - Update README if needed
- - Update developer documentation
- - Document new patterns
-
-### Total Estimated Timeline: 16-25 days
-
-## Code Changes Required
-
-### File Categories
-
-1. **Core Web Setup** (2 files)
- - `internal/cmd/web.go` - Main application setup
- - `internal/app/api.go` - API setup
-
-2. **Context System** (10 files in `internal/context/`)
- - `context.go` - Main context wrapper
- - `auth.go` - Authentication context
- - `api.go` - API context
- - `user.go` - User context
- - `repo.go` - Repository context
- - `org.go` - Organization context
- - And others...
-
-3. **Form Definitions** (6 files in `internal/form/`)
- - All form binding structs need tag updates
-
-4. **Route Handlers** (100+ files)
- - All files in `internal/route/` and subdirectories
- - Update handler signatures
- - Update context usage
-
-5. **Tests** (50+ files)
- - Update test helpers
- - Fix integration tests
- - Update mocks
-
-### Critical Files to Update
-
-```
-/internal/cmd/web.go # Main app setup - HIGH PRIORITY
-/internal/context/context.go # Context wrapper - HIGH PRIORITY
-/internal/form/form.go # Form binding - HIGH PRIORITY
-/internal/route/install.go # Install flow - CRITICAL
-/internal/route/home.go # Home page - CRITICAL
-/internal/route/user/*.go # User management
-/internal/route/repo/*.go # Repository operations
-/internal/route/admin/*.go # Admin panel
-/internal/route/api/v1/*.go # API endpoints
-/internal/route/lfs/*.go # LFS operations
-/templates/embed.go # Template system
-/go.mod # Dependencies
-```
-
-## Potential Issues
-
-### 1. Toolbox Middleware
-
-**Issue**: Macaron's toolbox middleware (health checks, profiling) has no direct Flamego equivalent.
-
-**Solution**: Implement custom health check endpoint:
-```go
-f.Get("/-/health", func(c flamego.Context) {
- if err := database.Ping(); err != nil {
- c.ResponseWriter().WriteHeader(500)
- return
- }
- c.ResponseWriter().WriteHeader(200)
- c.ResponseWriter().Write([]byte("OK"))
-})
-```
-
-### 2. Context Embedding
-
-**Issue**: Current Context embeds `*macaron.Context`, which is tightly coupled.
-
-**Solution**: Refactor to use composition instead:
-```go
-type Context struct {
- ctx flamego.Context
- // Other fields...
-}
-
-func (c *Context) Context() flamego.Context {
- return c.ctx
-}
-```
-
-### 3. Response Methods
-
-**Issue**: Gogs has many custom response methods on Context (HTML, JSON, etc.).
-
-**Solution**: Update methods to use Flamego's middleware:
-```go
-// Before (Macaron)
-func (c *Context) JSON(status int, data any) {
- c.Context.JSON(status, data)
-}
-
-// After (Flamego) - inject template.Template
-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)
-}
-```
-
-### 4. Route Parameter Syntax
-
-**Issue**: Macaron uses `:param`, Flamego uses ``.
-
-**Solution**: Find and replace all route definitions:
-```bash
-# Find all route definitions
-grep -r 'm\.Get\|m\.Post\|m\.Put\|m\.Delete\|m\.Patch' internal/cmd/web.go
-
-# Update syntax
-:param →
-```
-
-### 5. Regex Routes
-
-**Issue**: Macaron uses `^pattern$` for regex, Flamego has different syntax.
-
-**Solution**: Update regex patterns to Flamego format:
-```go
-// Macaron
-m.Get("/^:type(issues|pulls)$", handler)
-
-// Flamego
-f.Get("/", handler)
-```
-
-### 6. Dependency Injection Order
-
-**Issue**: Handler function parameter order matters in both frameworks.
-
-**Solution**: Ensure correct parameter order in handlers:
-```go
-// Flamego injects in order: Context, custom services, form bindings
-func handler(
- c flamego.Context,
- sess session.Session,
- form UserForm,
-) { }
-```
-
-### 7. Flash Messages
-
-**Issue**: Flash messages API may differ.
-
-**Solution**: Test and update flash message handling:
-```go
-// Verify API compatibility
-sess.SetFlash("message")
-flash := sess.GetFlash()
-```
-
-### 8. Custom Middleware
-
-**Issue**: Any custom Macaron middleware needs porting.
-
-**Solution**: Audit and rewrite custom middleware:
-```go
-// Macaron middleware
-func MyMiddleware() macaron.Handler {
- return func(c *macaron.Context) { }
-}
-
-// Flamego middleware
-func MyMiddleware() flamego.Handler {
- return func(c flamego.Context) { }
-}
-```
-
-## Testing Strategy
-
-### 1. Test Environment Setup
-
-```bash
-# Keep both versions temporarily
-go mod tidy
-
-# Run tests with Flamego
-go test ./...
-
-# Compare behavior
-```
-
-### 2. Critical Test Cases
-
-- [ ] User registration and login
-- [ ] Repository creation and deletion
-- [ ] Push and pull operations (HTTP)
-- [ ] Issue creation and comments
-- [ ] Pull request flow
-- [ ] Webhooks
-- [ ] API endpoints (all v1 routes)
-- [ ] LFS operations
-- [ ] Admin panel functionality
-- [ ] File uploads
-- [ ] Session management
-- [ ] CSRF protection
-- [ ] i18n/localization
-
-### 3. Integration Tests
-
-Create integration test suite that covers:
-- HTTP request/response cycle
-- Middleware execution order
-- Session persistence
-- CSRF token validation
-- Form binding and validation
-- Template rendering
-- Static file serving
-
-### 4. Performance Testing
-
-```bash
-# Benchmark before migration
-ab -n 1000 -c 10 http://localhost:3000/
-
-# Benchmark after migration
-ab -n 1000 -c 10 http://localhost:3000/
-
-# Compare results
-```
-
-### 5. Security Testing
-
-- [ ] CSRF protection works
-- [ ] Session security maintained
-- [ ] Authentication bypass tests
-- [ ] XSS prevention
-- [ ] SQL injection prevention (should be unchanged)
-
-## Rollback Plan
-
-### Quick Rollback
-
-If critical issues are discovered:
-
-1. **Git Revert**
- ```bash
- git revert
- git push
- ```
-
-2. **go.mod Rollback**
- ```bash
- git checkout main -- go.mod go.sum
- go mod tidy
- ```
-
-3. **Deploy Previous Version**
- - Use tagged release
- - Roll back to last stable commit
-
-### Gradual Migration (Alternative Approach)
-
-If full migration is too risky:
-
-1. **Feature Flag**: Use build tags or environment variables
-2. **Parallel Handlers**: Support both frameworks temporarily
-3. **Incremental Migration**: Migrate module by module
-4. **A/B Testing**: Route subset of traffic to new version
-
-## Conclusion
-
-### Summary
-
-Migrating from Macaron to Flamego is a **significant but manageable** undertaking. Flamego provides excellent feature parity with Macaron, including all the middleware that Gogs currently uses (except toolbox, which is easy to replace).
-
-### Key Advantages of Migration
-
-✅ **Complete Feature Parity**: All required middleware is available
-✅ **Same Philosophy**: Dependency injection pattern maintained
-✅ **Better Performance**: Improved routing engine
-✅ **Active Development**: Regular updates and improvements
-✅ **Official Successor**: Created by Macaron's author
-✅ **Better Routing**: More powerful routing capabilities
-
-### Remaining Concerns
-
-⚠️ **Large Scope**: ~150+ files need modification
-⚠️ **Testing Burden**: Comprehensive testing required
-⚠️ **Learning Curve**: Team needs to learn new APIs
-⚠️ **Toolbox Replacement**: Need custom health check implementation
-
-### Recommendation
-
-**Proceed with migration** using the phased approach outlined above. The migration is worthwhile because:
-
-1. Flamego is the official successor to Macaron
-2. All necessary middleware is available
-3. The migration path is clear and well-documented
-4. Long-term benefits outweigh short-term costs
-5. Macaron is in maintenance mode only
-
-### Next Steps
-
-1. **Get team buy-in** on migration decision
-2. **Allocate resources** (3-4 weeks of developer time)
-3. **Create feature branch** and begin Phase 1
-4. **Set up comprehensive test coverage** before starting
-5. **Document progress** and issues encountered
-6. **Plan staged rollout** to production
-
-## References
-
-- [Flamego Official Documentation](https://flamego.dev/)
-- [Flamego GitHub](https://github.com/flamego/flamego)
-- [Flamego vs Macaron Comparison](https://flamego.dev/faqs.html#how-is-flamego-different-from-macaron)
-- [Macaron GitHub](https://github.com/go-macaron/macaron)
-
-## Appendix: Quick Reference
-
-### Import Changes
-
-```go
-// Old imports
-import (
- "gopkg.in/macaron.v1"
- "github.com/go-macaron/binding"
- "github.com/go-macaron/cache"
- "github.com/go-macaron/captcha"
- "github.com/go-macaron/csrf"
- "github.com/go-macaron/gzip"
- "github.com/go-macaron/i18n"
- "github.com/go-macaron/session"
-)
-
-// New imports
-import (
- "github.com/flamego/flamego"
- "github.com/flamego/binding"
- "github.com/flamego/cache"
- "github.com/flamego/captcha"
- "github.com/flamego/csrf"
- "github.com/flamego/gzip"
- "github.com/flamego/i18n"
- "github.com/flamego/session"
- "github.com/flamego/template"
-)
-```
-
-### Common Pattern Changes
-
-```go
-// Route parameters
-m.Get("/:username/:repo") → f.Get("//")
-
-// Handler signature
-func(c *macaron.Context) → func(c flamego.Context)
-
-// Get parameter
-c.Params(":username") → c.Param("username")
-
-// Response writer
-c.Resp → c.ResponseWriter()
-
-// Request
-c.Req.Request → c.Request().Request
-
-// Session interface
-func(sess session.Store) → func(sess session.Session)
-
-// Template rendering
-c.HTML(200, "tpl") → t.HTML(200, "tpl")
-```
diff --git a/go.mod b/go.mod
index 348897065..c2cd73992 100644
--- a/go.mod
+++ b/go.mod
@@ -64,12 +64,21 @@ require (
require (
bitbucket.org/creachadair/shell v0.0.7 // indirect
+ filippo.io/edwards25519 v1.1.0 // indirect
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
+ github.com/alecthomas/participle/v2 v2.1.4 // indirect
+ github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
+ github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
+ github.com/charmbracelet/lipgloss v1.1.0 // indirect
+ github.com/charmbracelet/log v0.4.2 // indirect
+ github.com/charmbracelet/x/ansi v0.8.0 // indirect
+ github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
+ github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
github.com/cockroachdb/redact v1.1.5 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
@@ -80,16 +89,28 @@ require (
github.com/djherbis/nio/v3 v3.0.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fatih/color v1.13.0 // indirect
+ github.com/flamego/binding v1.3.0 // indirect
+ github.com/flamego/cache v1.5.1 // indirect
+ github.com/flamego/captcha v1.3.0 // indirect
+ github.com/flamego/csrf v1.3.0 // indirect
+ github.com/flamego/flamego v1.9.7 // indirect
+ github.com/flamego/gzip v1.2.0 // indirect
+ github.com/flamego/i18n v1.2.0 // indirect
+ github.com/flamego/session v1.6.5 // indirect
+ github.com/flamego/template v1.2.2 // indirect
+ github.com/flamego/validator v1.0.0 // indirect
github.com/getsentry/sentry-go v0.27.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
+ github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-macaron/inject v0.0.0-20200308113650-138e5925c53b // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect
- github.com/go-sql-driver/mysql v1.7.0 // indirect
+ github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
+ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/google/go-querystring v1.0.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/css v1.0.1 // indirect
@@ -104,15 +125,18 @@ require (
github.com/klauspost/compress v1.18.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
+ github.com/leodido/go-urn v1.2.1 // indirect
github.com/lib/pq v1.10.2 // indirect
+ github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
- github.com/mattn/go-runewidth v0.0.14 // indirect
+ github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mattn/go-sqlite3 v1.14.24 // indirect
github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2 // indirect
github.com/microsoft/go-mssqldb v0.17.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/muesli/termenv v0.16.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/pkg/errors v0.9.1 // indirect
@@ -121,15 +145,17 @@ require (
github.com/prometheus/common v0.65.0 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
- github.com/rivo/uniseg v0.2.0 // indirect
+ github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca // indirect
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
+ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.bobheadxi.dev/streamline v1.2.1 // indirect
go.opentelemetry.io/otel v1.11.0 // indirect
go.opentelemetry.io/otel/trace v1.11.0 // indirect
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
+ golang.org/x/image v0.6.0 // indirect
golang.org/x/mod v0.29.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.38.0 // indirect
@@ -141,6 +167,7 @@ require (
modernc.org/libc v1.66.3 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
+ unknwon.dev/i18n v1.0.1 // indirect
)
// +heroku goVersion go1.25
diff --git a/go.sum b/go.sum
index 0138770eb..5dd528e46 100644
--- a/go.sum
+++ b/go.sum
@@ -3,6 +3,8 @@ bitbucket.org/creachadair/shell v0.0.7/go.mod h1:oqtXSSvSYr4624lnnabXHaBsYW6RD80
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
+filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
+filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
gitea.com/lunny/log v0.0.0-20190322053110-01b5df579c4e/go.mod h1:uJEsN4LQpeGYRCjuPXPZBClU7N5pWzGuyF4uqLpE/e0=
gitea.com/lunny/nodb v0.0.0-20200923032308-3238c4655727/go.mod h1:h0OwsgcpJLSYtHcM5+Xciw9OEeuxi6ty4HDiO8C7aIY=
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw=
@@ -20,11 +22,15 @@ github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
+github.com/alecthomas/participle/v2 v2.1.4 h1:W/H79S8Sat/krZ3el6sQMvMaahJ+XcM9WSI2naI7w2U=
+github.com/alecthomas/participle/v2 v2.1.4/go.mod h1:8tqVbpTX20Ru4NfYQgZf4mP18eXPTBViyMWiArNEgGI=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
+github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
+github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@@ -37,6 +43,18 @@ github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQ
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
+github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
+github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
+github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
+github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig=
+github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw=
+github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
+github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
+github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
+github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
+github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
+github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -85,6 +103,26 @@ github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaB
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
+github.com/flamego/binding v1.3.0 h1:CPbnSuP0SxT50JR7lK2khTjcQi1oOECqRK7kbOYw91U=
+github.com/flamego/binding v1.3.0/go.mod h1:xgm6FEpEKKkF8CQilK2X3MJ5kTjOTnYdz/ooFctDTdc=
+github.com/flamego/cache v1.5.1 h1:2B4QhLFV7je0oUMCVKsAGAT+OyDHlXhozOoUffm+O3s=
+github.com/flamego/cache v1.5.1/go.mod h1:cTWYm/Ls35KKHo8vwcKgTlJUNXswEhzFWqVCTFzj24s=
+github.com/flamego/captcha v1.3.0 h1:CyQivqkiO4zT0nJY2vO0ySdOi85Z7EyESGMXvNQmi5U=
+github.com/flamego/captcha v1.3.0/go.mod h1:fCjE5o1cJXQkVJ2aYk7ISIBohfbNy1WxI2A3Ervzyp8=
+github.com/flamego/csrf v1.3.0 h1:rbbn9Iippu0iZdBudt6diMtzD8T69s+TZQmsZzCOfdc=
+github.com/flamego/csrf v1.3.0/go.mod h1:lB4vmeiEE7TJsw02EbjLP6QxY/iPX+2wabmel3/ODYg=
+github.com/flamego/flamego v1.9.7 h1:x3gkGOALg+HkpqFngkxQ3ZMC2vIa3Kze/WIpYTU2L0k=
+github.com/flamego/flamego v1.9.7/go.mod h1:m9Uc8FaCRVTpK/HuoK3quBhlHX0cE/DNY5LPXkRok9s=
+github.com/flamego/gzip v1.2.0 h1:LRNHcLCFZnRTKLpDXUm3nfCjVk4+Qi5nWaXC6JdSXTA=
+github.com/flamego/gzip v1.2.0/go.mod h1:y0XniLyIOf0/z5WTmPgyWw1SUYMqypqYxdKk5j7KDDE=
+github.com/flamego/i18n v1.2.0 h1:wRbrI0BD5mX/hs04c5EITzn7uCWZW1/K4m9ALrk+cOo=
+github.com/flamego/i18n v1.2.0/go.mod h1:AbmBNH8ljRzx7kepSOZzUhjNvLJ3CclIAnbLJrN5cNk=
+github.com/flamego/session v1.6.5 h1:YlQfMGjV84JcGihg5OjufKP5qOh/05iOfHYrf5qta5I=
+github.com/flamego/session v1.6.5/go.mod h1:EhBKxrWSmkqa2XwQSC6WbwXn7pLzyFY0BREtTwJBpQU=
+github.com/flamego/template v1.2.2 h1:aMpt8RzXBb2ZGuABf9p/q8oBBpXrurUV8rgBbz7mj2o=
+github.com/flamego/template v1.2.2/go.mod h1:xTAmwCCPaOuxN5t4CpzOP7WZN5WkLRiJfJCpsiB0aUg=
+github.com/flamego/validator v1.0.0 h1:ixuWHVgiVGp4pVGtUn/0d6HBjZJbbXfJHDNkxW+rZoY=
+github.com/flamego/validator v1.0.0/go.mod h1:POYn0/5iW4sdamdPAYPrzqN6DFC4YaczY0gYY+Pyx5E=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@@ -100,6 +138,8 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
github.com/go-ldap/ldap/v3 v3.4.11 h1:4k0Yxweg+a3OyBLjdYn5OKglv18JNvfDykSoI8bW0gU=
github.com/go-ldap/ldap/v3 v3.4.11/go.mod h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
+github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -125,11 +165,14 @@ github.com/go-macaron/session v1.0.3 h1:YnSfcm24a4HHRnZzBU30FGvoo4kR6vYbTeyTlA1d
github.com/go-macaron/session v1.0.3/go.mod h1:NKoSrKpBFGEgeDtdLr/mnGaxa2LZVOg8/LwZKwPgQr0=
github.com/go-macaron/toolbox v0.0.0-20190813233741-94defb8383c6 h1:x/v1iUWlqXTKVg17ulB0qCgcM2s+eysAbr/dseKLLss=
github.com/go-macaron/toolbox v0.0.0-20190813233741-94defb8383c6/go.mod h1:YFNJ/JT4yLnpuIXTFef30SZkxGHUczjGZGFaZpPcdn0=
+github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
+github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
+github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y=
@@ -159,6 +202,8 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2V
github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188/go.mod h1:vXjM/+wXQnTPR4KqTKDgJukSZ6amVRtWMPEjE6sQoK8=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
@@ -275,10 +320,14 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
+github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
+github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
+github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de/go.mod h1:3q8WtuPQsoRbatJuy3nvq/hRSvuBJrHHr+ybPPiNvHQ=
github.com/lunny/nodb v0.0.0-20160621015157-fc1ef06ad4af/go.mod h1:Cqz6pqow14VObJ7peltM+2n3PWOz7yTrfUuGbVFkzN0=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
@@ -295,6 +344,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
+github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
+github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
@@ -317,6 +368,8 @@ github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3P
github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/msteinert/pam v1.2.0 h1:mYfjlvN2KYs2Pb9G6nb/1f/nPfAttT/Jee5Sq9r3bGE=
github.com/msteinert/pam v1.2.0/go.mod h1:d2n0DCUK8rGecChV3JzvmsDjOY4R7AYbsNxAT+ftQl0=
+github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
+github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
@@ -387,6 +440,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
+github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
@@ -446,8 +501,11 @@ github.com/unknwon/paginater v0.0.0-20170405233947-45e5d631308e h1:Qf3QQl/zmEbWD
github.com/unknwon/paginater v0.0.0-20170405233947-45e5d631308e/go.mod h1:TBwoao3Q4Eb/cp+dHbXDfRTrZSsj/k7kLr2j1oWRWC0=
github.com/urfave/cli v1.22.17 h1:SYzXoiPfQjHBbkYxbew5prZHS1TOLT3ierW8SYLqtVQ=
github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo=
+github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
+github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
go.bobheadxi.dev/streamline v1.2.1 h1:IqKSA1TbeuDqCzYNAwtlh8sqf3tsQus8XgJdkCWFT8c=
@@ -470,6 +528,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
+golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
@@ -477,11 +537,15 @@ golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
+golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4=
+golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -500,10 +564,13 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -515,6 +582,8 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
@@ -545,15 +614,20 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -562,6 +636,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -577,6 +653,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -621,6 +699,7 @@ gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AW
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/ini.v1 v1.46.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/ini.v1 v1.64.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/macaron.v1 v1.3.4/go.mod h1:/RoHTdC8ALpyJ3+QR36mKjwnT1F1dyYtsGM9Ate6ZFI=
@@ -689,6 +768,8 @@ mvdan.cc/gofumpt v0.4.0 h1:JVf4NN1mIpHogBj7ABpgOyZc65/UUOkKQFkoURsz4MM=
mvdan.cc/gofumpt v0.4.0/go.mod h1:PljLOHDeZqgS8opHRKLzp2It2VBuSdteAgqUfzMTxlQ=
unknwon.dev/clog/v2 v2.2.0 h1:jkPdsxux0MC04BT/9NHbT75z4prK92SH10VBNmIpVCc=
unknwon.dev/clog/v2 v2.2.0/go.mod h1:zvUlyibDHI4mykYdWyWje2G9nF/nBzfDOqRo2my4mWc=
+unknwon.dev/i18n v1.0.1 h1:u3lR67ur4bsM5lucFO5LTHCwAUqGbQ4Gk+1Oe3J8U1M=
+unknwon.dev/i18n v1.0.1/go.mod h1:3dj1tQFJQE+HA5/iwBXVkZbWgMwdoRQZ9X2O90ZixBc=
xorm.io/builder v0.3.6 h1:ha28mQ2M+TFx96Hxo+iq6tQgnkC9IZkM6D8w9sKHHF8=
xorm.io/builder v0.3.6/go.mod h1:LEFAPISnRzG+zxaxj2vPicRwz67BdhFreKg8yv8/TgU=
xorm.io/core v0.7.2 h1:mEO22A2Z7a3fPaZMk6gKL/jMD80iiyNwRrX5HOv3XLw=
diff --git a/internal/cmd/web.go b/internal/cmd/web.go
index caf616be6..a081be6b6 100644
--- a/internal/cmd/web.go
+++ b/internal/cmd/web.go
@@ -11,18 +11,18 @@ import (
"path/filepath"
"strings"
- "github.com/go-macaron/binding"
- "github.com/go-macaron/cache"
- "github.com/go-macaron/captcha"
- "github.com/go-macaron/csrf"
- "github.com/go-macaron/gzip"
- "github.com/go-macaron/i18n"
- "github.com/go-macaron/session"
- "github.com/go-macaron/toolbox"
+ "github.com/flamego/binding"
+ "github.com/flamego/cache"
+ "github.com/flamego/captcha"
+ "github.com/flamego/csrf"
+ "github.com/flamego/flamego"
+ "github.com/flamego/gzip"
+ "github.com/flamego/i18n"
+ "github.com/flamego/session"
+ "github.com/flamego/template"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/unknwon/com"
"github.com/urfave/cli"
- "gopkg.in/macaron.v1"
log "unknwon.dev/clog/v2"
embedConf "gogs.io/gogs/conf"
@@ -40,7 +40,7 @@ import (
"gogs.io/gogs/internal/route/org"
"gogs.io/gogs/internal/route/repo"
"gogs.io/gogs/internal/route/user"
- "gogs.io/gogs/internal/template"
+ gogstemplate "gogs.io/gogs/internal/template"
"gogs.io/gogs/public"
"gogs.io/gogs/templates"
)
@@ -57,24 +57,24 @@ and it takes care of all the other things for you`,
},
}
-// newMacaron initializes Macaron instance.
-func newMacaron() *macaron.Macaron {
- m := macaron.New()
+// newFlamego initializes Flamego instance.
+func newFlamego() *flamego.Flame {
+ f := flamego.New()
if !conf.Server.DisableRouterLog {
- m.Use(macaron.Logger())
+ f.Use(flamego.Logger())
}
- m.Use(macaron.Recovery())
+ f.Use(flamego.Recovery())
if conf.Server.EnableGzip {
- m.Use(gzip.Gziper())
+ f.Use(gzip.Gzip())
}
if conf.Server.Protocol == "fcgi" {
- m.SetURLPrefix(conf.Server.Subpath)
+ f.SetURLPrefix(conf.Server.Subpath)
}
// Register custom middleware first to make it possible to override files under "public".
- m.Use(macaron.Static(
- filepath.Join(conf.CustomDir(), "public"),
- macaron.StaticOptions{
+ f.Use(flamego.Static(
+ flamego.StaticOptions{
+ Directory: filepath.Join(conf.CustomDir(), "public"),
SkipLogging: conf.Server.DisableRouterLog,
},
))
@@ -82,26 +82,26 @@ func newMacaron() *macaron.Macaron {
if !conf.Server.LoadAssetsFromDisk {
publicFs = http.FS(public.Files)
}
- m.Use(macaron.Static(
- filepath.Join(conf.WorkDir(), "public"),
- macaron.StaticOptions{
+ f.Use(flamego.Static(
+ flamego.StaticOptions{
+ Directory: filepath.Join(conf.WorkDir(), "public"),
ETag: true,
SkipLogging: conf.Server.DisableRouterLog,
FileSystem: publicFs,
},
))
- m.Use(macaron.Static(
- conf.Picture.AvatarUploadPath,
- macaron.StaticOptions{
+ f.Use(flamego.Static(
+ flamego.StaticOptions{
+ Directory: conf.Picture.AvatarUploadPath,
ETag: true,
Prefix: conf.UsersAvatarPathPrefix,
SkipLogging: conf.Server.DisableRouterLog,
},
))
- m.Use(macaron.Static(
- conf.Picture.RepositoryAvatarUploadPath,
- macaron.StaticOptions{
+ f.Use(flamego.Static(
+ flamego.StaticOptions{
+ Directory: conf.Picture.RepositoryAvatarUploadPath,
ETag: true,
Prefix: database.RepoAvatarURLPrefix,
SkipLogging: conf.Server.DisableRouterLog,
@@ -109,16 +109,16 @@ func newMacaron() *macaron.Macaron {
))
customDir := filepath.Join(conf.CustomDir(), "templates")
- renderOpt := macaron.RenderOptions{
+ renderOpt := template.Options{
Directory: filepath.Join(conf.WorkDir(), "templates"),
AppendDirectories: []string{customDir},
- Funcs: template.FuncMap(),
- IndentJSON: macaron.Env != macaron.PROD,
+ Funcs: gogstemplate.FuncMap(),
+ FileSystem: nil,
}
if !conf.Server.LoadAssetsFromDisk {
- renderOpt.TemplateFileSystem = templates.NewTemplateFileSystem("", customDir)
+ renderOpt.FileSystem = templates.NewTemplateFileSystem("", customDir)
}
- m.Use(macaron.Renderer(renderOpt))
+ f.Use(template.Templater(renderOpt))
localeNames, err := embedConf.FileNames("locale")
if err != nil {
@@ -131,32 +131,35 @@ func newMacaron() *macaron.Macaron {
log.Fatal("Failed to read locale file %q: %v", name, err)
}
}
- m.Use(i18n.I18n(i18n.Options{
- SubURL: conf.Server.Subpath,
+ f.Use(i18n.I18n(i18n.Options{
+ Directory: filepath.Join(conf.CustomDir(), "conf", "locale"),
Files: localeFiles,
- CustomDirectory: filepath.Join(conf.CustomDir(), "conf", "locale"),
- Langs: conf.I18n.Langs,
+ Languages: conf.I18n.Langs,
Names: conf.I18n.Names,
- DefaultLang: "en-US",
+ DefaultLanguage: "en-US",
Redirect: true,
}))
- m.Use(cache.Cacher(cache.Options{
- Adapter: conf.Cache.Adapter,
- AdapterConfig: conf.Cache.Host,
- Interval: conf.Cache.Interval,
+ f.Use(cache.Cacher(cache.Options{
+ Adapter: conf.Cache.Adapter,
+ Config: conf.Cache.Host,
+ Interval: conf.Cache.Interval,
}))
- m.Use(captcha.Captchaer(captcha.Options{
- SubURL: conf.Server.Subpath,
+ f.Use(captcha.Captchaer(captcha.Options{
+ URLPrefix: conf.Server.Subpath,
}))
- m.Use(toolbox.Toolboxer(m, toolbox.Options{
- HealthCheckFuncs: []*toolbox.HealthCheckFuncDesc{
- {
- Desc: "Database connection",
- Func: database.Ping,
- },
- },
- }))
- return m
+
+ // Custom health check endpoint (replaces toolbox)
+ f.Get("/-/healthz", func(w http.ResponseWriter) {
+ if err := database.Ping(); err != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ fmt.Fprintf(w, "database connection failed: %v", err)
+ return
+ }
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprint(w, "ok")
+ })
+
+ return f
}
func runWeb(c *cli.Context) error {
@@ -165,518 +168,513 @@ func runWeb(c *cli.Context) error {
log.Fatal("Failed to initialize application: %v", err)
}
- m := newMacaron()
+ f := newFlamego()
+
+ // Apply global middleware
+ f.Use(session.Sessioner(session.Options{
+ Provider: conf.Session.Provider,
+ Config: conf.Session.ProviderConfig,
+ CookieName: conf.Session.CookieName,
+ CookiePath: conf.Server.Subpath,
+ Gclifetime: conf.Session.GCInterval,
+ Maxlifetime: conf.Session.MaxLifeTime,
+ Secure: conf.Session.CookieSecure,
+ }))
+ f.Use(csrf.Csrfer(csrf.Options{
+ Secret: conf.Security.SecretKey,
+ Header: "X-CSRF-Token",
+ Cookie: conf.Session.CSRFCookieName,
+ Domain: conf.Server.URL.Hostname(),
+ Path: conf.Server.Subpath,
+ HTTPOnly: true,
+ SetCookie: true,
+ Secure: conf.Server.URL.Scheme == "https",
+ }))
+ f.Use(context.Contexter(context.NewStore()))
reqSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: true})
ignSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: conf.Auth.RequireSigninView})
reqSignOut := context.Toggle(&context.ToggleOptions{SignOutRequired: true})
- bindIgnErr := binding.BindIgnErr
+ f.Get("/", ignSignIn, route.Home)
+ f.Group("/explore", func() {
+ f.Get("", func(c *context.Context) {
+ c.Redirect(conf.Server.Subpath + "/explore/repos")
+ })
+ f.Get("/repos", route.ExploreRepos)
+ f.Get("/users", route.ExploreUsers)
+ f.Get("/organizations", route.ExploreOrganizations)
+ }, ignSignIn)
+ f.Combo("/install", route.InstallInit).Get(route.Install).
+ Post(binding.Form(form.Install{}), route.InstallPost)
+ f.Get("/", reqSignIn, user.Issues)
- m.SetAutoHead(true)
-
- m.Group("", func() {
- m.Get("/", ignSignIn, route.Home)
- m.Group("/explore", func() {
- m.Get("", func(c *context.Context) {
- c.Redirect(conf.Server.Subpath + "/explore/repos")
- })
- m.Get("/repos", route.ExploreRepos)
- m.Get("/users", route.ExploreUsers)
- m.Get("/organizations", route.ExploreOrganizations)
- }, ignSignIn)
- m.Combo("/install", route.InstallInit).Get(route.Install).
- Post(bindIgnErr(form.Install{}), route.InstallPost)
- m.Get("/^:type(issues|pulls)$", reqSignIn, user.Issues)
-
- // ***** START: User *****
- 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.Combo("/two_factor_recovery_code").Get(user.LoginTwoFactorRecoveryCode).Post(user.LoginTwoFactorRecoveryCodePost)
- })
-
- m.Get("/sign_up", user.SignUp)
- m.Post("/sign_up", bindIgnErr(form.Register{}), user.SignUpPost)
- m.Get("/reset_password", user.ResetPasswd)
- m.Post("/reset_password", user.ResetPasswdPost)
- }, reqSignOut)
-
- m.Group("/user/settings", func() {
- m.Get("", user.Settings)
- m.Post("", bindIgnErr(form.UpdateProfile{}), user.SettingsPost)
- m.Combo("/avatar").Get(user.SettingsAvatar).
- Post(binding.MultipartForm(form.Avatar{}), user.SettingsAvatarPost)
- m.Post("/avatar/delete", user.SettingsDeleteAvatar)
- m.Combo("/email").Get(user.SettingsEmails).
- Post(bindIgnErr(form.AddEmail{}), user.SettingsEmailPost)
- m.Post("/email/delete", user.DeleteEmail)
- m.Get("/password", user.SettingsPassword)
- m.Post("/password", bindIgnErr(form.ChangePassword{}), user.SettingsPasswordPost)
- m.Combo("/ssh").Get(user.SettingsSSHKeys).
- Post(bindIgnErr(form.AddSSHKey{}), user.SettingsSSHKeysPost)
- m.Post("/ssh/delete", user.DeleteSSHKey)
- m.Group("/security", func() {
- m.Get("", user.SettingsSecurity)
- m.Combo("/two_factor_enable").Get(user.SettingsTwoFactorEnable).
- Post(user.SettingsTwoFactorEnablePost)
- m.Combo("/two_factor_recovery_codes").Get(user.SettingsTwoFactorRecoveryCodes).
- Post(user.SettingsTwoFactorRecoveryCodesPost)
- m.Post("/two_factor_disable", user.SettingsTwoFactorDisable)
- })
- m.Group("/repositories", func() {
- m.Get("", user.SettingsRepos)
- m.Post("/leave", user.SettingsLeaveRepo)
- })
- m.Group("/organizations", func() {
- m.Get("", user.SettingsOrganizations)
- m.Post("/leave", user.SettingsLeaveOrganization)
- })
-
- settingsHandler := user.NewSettingsHandler(user.NewSettingsStore())
- m.Combo("/applications").Get(settingsHandler.Applications()).
- Post(bindIgnErr(form.NewAccessToken{}), settingsHandler.ApplicationsPost())
- m.Post("/applications/delete", settingsHandler.DeleteApplication())
- m.Route("/delete", "GET,POST", user.SettingsDelete)
- }, reqSignIn, func(c *context.Context) {
- c.Data["PageIsUserSettings"] = true
+ // ***** START: User *****
+ 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.Combo("/two_factor_recovery_code").Get(user.LoginTwoFactorRecoveryCode).Post(user.LoginTwoFactorRecoveryCodePost)
})
- m.Group("/user", func() {
- m.Any("/activate", user.Activate)
- m.Any("/activate_email", user.ActivateEmail)
- m.Get("/email2user", user.Email2User)
- m.Get("/forget_password", user.ForgotPasswd)
- m.Post("/forget_password", user.ForgotPasswdPost)
- m.Post("/logout", user.SignOut)
+ f.Get("/sign_up", user.SignUp)
+ f.Post("/sign_up", binding.Form(form.Register{}), user.SignUpPost)
+ f.Get("/reset_password", user.ResetPasswd)
+ f.Post("/reset_password", user.ResetPasswdPost)
+ }, reqSignOut)
+
+ f.Group("/user/settings", func() {
+ f.Get("", user.Settings)
+ f.Post("", binding.Form(form.UpdateProfile{}), user.SettingsPost)
+ f.Combo("/avatar").Get(user.SettingsAvatar).
+ Post(binding.Form(form.Avatar{}), user.SettingsAvatarPost)
+ f.Post("/avatar/delete", user.SettingsDeleteAvatar)
+ f.Combo("/email").Get(user.SettingsEmails).
+ Post(binding.Form(form.AddEmail{}), user.SettingsEmailPost)
+ f.Post("/email/delete", user.DeleteEmail)
+ f.Get("/password", user.SettingsPassword)
+ f.Post("/password", binding.Form(form.ChangePassword{}), user.SettingsPasswordPost)
+ f.Combo("/ssh").Get(user.SettingsSSHKeys).
+ Post(binding.Form(form.AddSSHKey{}), user.SettingsSSHKeysPost)
+ f.Post("/ssh/delete", user.DeleteSSHKey)
+ f.Group("/security", func() {
+ f.Get("", user.SettingsSecurity)
+ f.Combo("/two_factor_enable").Get(user.SettingsTwoFactorEnable).
+ Post(user.SettingsTwoFactorEnablePost)
+ f.Combo("/two_factor_recovery_codes").Get(user.SettingsTwoFactorRecoveryCodes).
+ Post(user.SettingsTwoFactorRecoveryCodesPost)
+ f.Post("/two_factor_disable", user.SettingsTwoFactorDisable)
+ })
+ f.Group("/repositories", func() {
+ f.Get("", user.SettingsRepos)
+ f.Post("/leave", user.SettingsLeaveRepo)
+ })
+ f.Group("/organizations", func() {
+ f.Get("", user.SettingsOrganizations)
+ f.Post("/leave", user.SettingsLeaveOrganization)
})
- // ***** END: User *****
- reqAdmin := context.Toggle(&context.ToggleOptions{SignInRequired: true, AdminRequired: true})
+ settingsHandler := user.NewSettingsHandler(user.NewSettingsStore())
+ f.Combo("/applications").Get(settingsHandler.Applications()).
+ Post(binding.Form(form.NewAccessToken{}), settingsHandler.ApplicationsPost())
+ f.Post("/applications/delete", settingsHandler.DeleteApplication())
+ f.Route("/delete", "GET,POST", user.SettingsDelete)
+ }, reqSignIn, func(c *context.Context) {
+ c.Data["PageIsUserSettings"] = true
+ })
- // ***** START: Admin *****
- m.Group("/admin", func() {
- m.Combo("").Get(admin.Dashboard).Post(admin.Operation) // "/admin"
- m.Get("/config", admin.Config)
- m.Post("/config/test_mail", admin.SendTestMail)
- m.Get("/monitor", admin.Monitor)
+ f.Group("/user", func() {
+ f.Any("/activate", user.Activate)
+ f.Any("/activate_email", user.ActivateEmail)
+ f.Get("/email2user", user.Email2User)
+ f.Get("/forget_password", user.ForgotPasswd)
+ f.Post("/forget_password", user.ForgotPasswdPost)
+ f.Post("/logout", user.SignOut)
+ })
+ // ***** END: User *****
- m.Group("/users", func() {
- m.Get("", admin.Users)
- m.Combo("/new").Get(admin.NewUser).Post(bindIgnErr(form.AdminCrateUser{}), admin.NewUserPost)
- m.Combo("/:userid").Get(admin.EditUser).Post(bindIgnErr(form.AdminEditUser{}), admin.EditUserPost)
- m.Post("/:userid/delete", admin.DeleteUser)
+ reqAdmin := context.Toggle(&context.ToggleOptions{SignInRequired: true, AdminRequired: true})
+
+ // ***** START: Admin *****
+ f.Group("/admin", func() {
+ f.Combo("").Get(admin.Dashboard).Post(admin.Operation) // "/admin"
+ f.Get("/config", admin.Config)
+ f.Post("/config/test_mail", admin.SendTestMail)
+ f.Get("/monitor", admin.Monitor)
+
+ f.Group("/users", func() {
+ f.Get("", admin.Users)
+ f.Combo("/new").Get(admin.NewUser).Post(binding.Form(form.AdminCrateUser{}), admin.NewUserPost)
+ f.Combo("/").Get(admin.EditUser).Post(binding.Form(form.AdminEditUser{}), admin.EditUserPost)
+ f.Post("//delete", admin.DeleteUser)
+ })
+
+ f.Group("/orgs", func() {
+ f.Get("", admin.Organizations)
+ })
+
+ f.Group("/repos", func() {
+ f.Get("", admin.Repos)
+ f.Post("/delete", admin.DeleteRepo)
+ })
+
+ f.Group("/auths", func() {
+ f.Get("", admin.Authentications)
+ f.Combo("/new").Get(admin.NewAuthSource).Post(binding.Form(form.Authentication{}), admin.NewAuthSourcePost)
+ f.Combo("/").Get(admin.EditAuthSource).
+ Post(binding.Form(form.Authentication{}), admin.EditAuthSourcePost)
+ f.Post("//delete", admin.DeleteAuthSource)
+ })
+
+ f.Group("/notices", func() {
+ f.Get("", admin.Notices)
+ f.Post("/delete", admin.DeleteNotices)
+ f.Get("/empty", admin.EmptyNotices)
+ })
+ }, reqAdmin)
+ // ***** END: Admin *****
+
+ f.Group("", func() {
+ f.Group("/", func() {
+ f.Get("", user.Profile)
+ f.Get("/followers", user.Followers)
+ f.Get("/following", user.Following)
+ f.Get("/stars", user.Stars)
+ }, context.InjectParamsUser())
+
+ f.Get("/attachments/", func(c *context.Context) {
+ attach, err := database.GetAttachmentByUUID(c.Params(""))
+ if err != nil {
+ c.NotFoundOrError(err, "get attachment by UUID")
+ return
+ } else if !com.IsFile(attach.LocalPath()) {
+ c.NotFound()
+ return
+ }
+
+ fr, err := os.Open(attach.LocalPath())
+ if err != nil {
+ c.Error(err, "open attachment file")
+ return
+ }
+ defer fr.Close()
+
+ c.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox")
+ c.Header().Set("Cache-Control", "public,max-age=86400")
+ c.Header().Set("Content-Disposition", fmt.Sprintf(`inline; filename="%s"`, attach.Name))
+
+ if _, err = io.Copy(c.Resp, fr); err != nil {
+ c.Error(err, "copy from file to response")
+ return
+ }
+ })
+ f.Post("/issues/attachments", repo.UploadIssueAttachment)
+ f.Post("/releases/attachments", repo.UploadReleaseAttachment)
+ }, ignSignIn)
+
+ f.Group("/", func() {
+ f.Post("/action/", user.Action)
+ }, reqSignIn, context.InjectParamsUser())
+
+ if macaron.Env == macaron.DEV {
+ f.Get("/template/*", dev.TemplatePreview)
+ }
+
+ reqRepoAdmin := context.RequireRepoAdmin()
+ reqRepoWriter := context.RequireRepoWriter()
+
+ webhookRoutes := func() {
+ f.Group("", func() {
+ f.Get("", repo.Webhooks)
+ f.Post("/delete", repo.DeleteWebhook)
+ f.Get("//new", repo.WebhooksNew)
+ f.Post("/gogs/new", binding.Form(form.NewWebhook{}), repo.WebhooksNewPost)
+ f.Post("/slack/new", binding.Form(form.NewSlackHook{}), repo.WebhooksSlackNewPost)
+ f.Post("/discord/new", binding.Form(form.NewDiscordHook{}), repo.WebhooksDiscordNewPost)
+ f.Post("/dingtalk/new", binding.Form(form.NewDingtalkHook{}), repo.WebhooksDingtalkNewPost)
+ f.Get("/", repo.WebhooksEdit)
+ f.Post("/gogs/", binding.Form(form.NewWebhook{}), repo.WebhooksEditPost)
+ f.Post("/slack/", binding.Form(form.NewSlackHook{}), repo.WebhooksSlackEditPost)
+ f.Post("/discord/", binding.Form(form.NewDiscordHook{}), repo.WebhooksDiscordEditPost)
+ f.Post("/dingtalk/", binding.Form(form.NewDingtalkHook{}), repo.WebhooksDingtalkEditPost)
+ }, repo.InjectOrgRepoContext())
+ }
+
+ // ***** START: Organization *****
+ f.Group("/org", func() {
+ f.Group("", func() {
+ f.Get("/create", org.Create)
+ f.Post("/create", binding.Form(form.CreateOrg{}), org.CreatePost)
+ }, func(c *context.Context) {
+ if !c.User.CanCreateOrganization() {
+ c.NotFound()
+ }
+ })
+
+ f.Group("/", func() {
+ f.Get("/dashboard", user.Dashboard)
+ f.Get("/", user.Issues)
+ f.Get("/members", org.Members)
+ f.Get("/members/action/", org.MembersAction)
+
+ f.Get("/teams", org.Teams)
+ }, context.OrgAssignment(true))
+
+ f.Group("/", func() {
+ f.Get("/teams/", org.TeamMembers)
+ f.Get("/teams//repositories", org.TeamRepositories)
+ f.Route("/teams//action/", "GET,POST", org.TeamsAction)
+ f.Route("/teams//action/repo/", "GET,POST", org.TeamsRepoAction)
+ }, context.OrgAssignment(true, false, true))
+
+ f.Group("/", func() {
+ f.Get("/teams/new", org.NewTeam)
+ f.Post("/teams/new", binding.Form(form.CreateTeam{}), org.NewTeamPost)
+ f.Get("/teams//edit", org.EditTeam)
+ f.Post("/teams//edit", binding.Form(form.CreateTeam{}), org.EditTeamPost)
+ f.Post("/teams//delete", org.DeleteTeam)
+
+ f.Group("/settings", func() {
+ f.Combo("").Get(org.Settings).
+ Post(binding.Form(form.UpdateOrgSetting{}), org.SettingsPost)
+ f.Post("/avatar", binding.Form(form.Avatar{}), org.SettingsAvatar)
+ f.Post("/avatar/delete", org.SettingsDeleteAvatar)
+ f.Group("/hooks", webhookRoutes)
+ f.Route("/delete", "GET,POST", org.SettingsDelete)
})
- m.Group("/orgs", func() {
- m.Get("", admin.Organizations)
+ f.Route("/invitations/new", "GET,POST", org.Invitation)
+ }, context.OrgAssignment(true, true))
+ }, reqSignIn)
+ // ***** END: Organization *****
+
+ // ***** START: Repository *****
+ f.Group("/repo", func() {
+ f.Get("/create", repo.Create)
+ f.Post("/create", binding.Form(form.CreateRepo{}), repo.CreatePost)
+ f.Get("/migrate", repo.Migrate)
+ f.Post("/migrate", binding.Form(form.MigrateRepo{}), repo.MigratePost)
+ f.Combo("/fork/").Get(repo.Fork).
+ Post(binding.Form(form.CreateRepo{}), repo.ForkPost)
+ }, reqSignIn)
+
+ f.Group("//", func() {
+ f.Group("/settings", func() {
+ f.Combo("").Get(repo.Settings).
+ Post(binding.Form(form.RepoSetting{}), repo.SettingsPost)
+ f.Combo("/avatar").Get(repo.SettingsAvatar).
+ Post(binding.Form(form.Avatar{}), repo.SettingsAvatarPost)
+ f.Post("/avatar/delete", repo.SettingsDeleteAvatar)
+ f.Group("/collaboration", func() {
+ f.Combo("").Get(repo.SettingsCollaboration).Post(repo.SettingsCollaborationPost)
+ f.Post("/access_mode", repo.ChangeCollaborationAccessMode)
+ f.Post("/delete", repo.DeleteCollaboration)
})
-
- m.Group("/repos", func() {
- m.Get("", admin.Repos)
- m.Post("/delete", admin.DeleteRepo)
- })
-
- m.Group("/auths", func() {
- m.Get("", admin.Authentications)
- m.Combo("/new").Get(admin.NewAuthSource).Post(bindIgnErr(form.Authentication{}), admin.NewAuthSourcePost)
- m.Combo("/:authid").Get(admin.EditAuthSource).
- Post(bindIgnErr(form.Authentication{}), admin.EditAuthSourcePost)
- m.Post("/:authid/delete", admin.DeleteAuthSource)
- })
-
- m.Group("/notices", func() {
- m.Get("", admin.Notices)
- m.Post("/delete", admin.DeleteNotices)
- m.Get("/empty", admin.EmptyNotices)
- })
- }, reqAdmin)
- // ***** END: Admin *****
-
- m.Group("", func() {
- m.Group("/:username", func() {
- m.Get("", user.Profile)
- m.Get("/followers", user.Followers)
- m.Get("/following", user.Following)
- m.Get("/stars", user.Stars)
- }, context.InjectParamsUser())
-
- m.Get("/attachments/:uuid", func(c *context.Context) {
- attach, err := database.GetAttachmentByUUID(c.Params(":uuid"))
- if err != nil {
- c.NotFoundOrError(err, "get attachment by UUID")
- return
- } else if !com.IsFile(attach.LocalPath()) {
- c.NotFound()
- return
- }
-
- fr, err := os.Open(attach.LocalPath())
- if err != nil {
- c.Error(err, "open attachment file")
- return
- }
- defer fr.Close()
-
- c.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox")
- c.Header().Set("Cache-Control", "public,max-age=86400")
- c.Header().Set("Content-Disposition", fmt.Sprintf(`inline; filename="%s"`, attach.Name))
-
- if _, err = io.Copy(c.Resp, fr); err != nil {
- c.Error(err, "copy from file to response")
- return
- }
- })
- m.Post("/issues/attachments", repo.UploadIssueAttachment)
- m.Post("/releases/attachments", repo.UploadReleaseAttachment)
- }, ignSignIn)
-
- m.Group("/:username", func() {
- m.Post("/action/:action", user.Action)
- }, reqSignIn, context.InjectParamsUser())
-
- if macaron.Env == macaron.DEV {
- m.Get("/template/*", dev.TemplatePreview)
- }
-
- reqRepoAdmin := context.RequireRepoAdmin()
- reqRepoWriter := context.RequireRepoWriter()
-
- webhookRoutes := func() {
- m.Group("", func() {
- m.Get("", repo.Webhooks)
- m.Post("/delete", repo.DeleteWebhook)
- m.Get("/:type/new", repo.WebhooksNew)
- m.Post("/gogs/new", bindIgnErr(form.NewWebhook{}), repo.WebhooksNewPost)
- m.Post("/slack/new", bindIgnErr(form.NewSlackHook{}), repo.WebhooksSlackNewPost)
- m.Post("/discord/new", bindIgnErr(form.NewDiscordHook{}), repo.WebhooksDiscordNewPost)
- m.Post("/dingtalk/new", bindIgnErr(form.NewDingtalkHook{}), repo.WebhooksDingtalkNewPost)
- m.Get("/:id", repo.WebhooksEdit)
- m.Post("/gogs/:id", bindIgnErr(form.NewWebhook{}), repo.WebhooksEditPost)
- m.Post("/slack/:id", bindIgnErr(form.NewSlackHook{}), repo.WebhooksSlackEditPost)
- m.Post("/discord/:id", bindIgnErr(form.NewDiscordHook{}), repo.WebhooksDiscordEditPost)
- m.Post("/dingtalk/:id", bindIgnErr(form.NewDingtalkHook{}), repo.WebhooksDingtalkEditPost)
- }, repo.InjectOrgRepoContext())
- }
-
- // ***** START: Organization *****
- m.Group("/org", func() {
- m.Group("", func() {
- m.Get("/create", org.Create)
- m.Post("/create", bindIgnErr(form.CreateOrg{}), org.CreatePost)
+ f.Group("/branches", func() {
+ f.Get("", repo.SettingsBranches)
+ f.Post("/default_branch", repo.UpdateDefaultBranch)
+ f.Combo("/*").Get(repo.SettingsProtectedBranch).
+ Post(binding.Form(form.ProtectBranch{}), repo.SettingsProtectedBranchPost)
}, func(c *context.Context) {
- if !c.User.CanCreateOrganization() {
- c.NotFound()
- }
- })
-
- m.Group("/:org", func() {
- m.Get("/dashboard", user.Dashboard)
- m.Get("/^:type(issues|pulls)$", user.Issues)
- m.Get("/members", org.Members)
- m.Get("/members/action/:action", org.MembersAction)
-
- m.Get("/teams", org.Teams)
- }, context.OrgAssignment(true))
-
- m.Group("/:org", func() {
- m.Get("/teams/:team", org.TeamMembers)
- m.Get("/teams/:team/repositories", org.TeamRepositories)
- m.Route("/teams/:team/action/:action", "GET,POST", org.TeamsAction)
- m.Route("/teams/:team/action/repo/:action", "GET,POST", org.TeamsRepoAction)
- }, context.OrgAssignment(true, false, true))
-
- m.Group("/:org", func() {
- m.Get("/teams/new", org.NewTeam)
- m.Post("/teams/new", bindIgnErr(form.CreateTeam{}), org.NewTeamPost)
- m.Get("/teams/:team/edit", org.EditTeam)
- m.Post("/teams/:team/edit", bindIgnErr(form.CreateTeam{}), org.EditTeamPost)
- m.Post("/teams/:team/delete", org.DeleteTeam)
-
- m.Group("/settings", func() {
- m.Combo("").Get(org.Settings).
- Post(bindIgnErr(form.UpdateOrgSetting{}), org.SettingsPost)
- m.Post("/avatar", binding.MultipartForm(form.Avatar{}), org.SettingsAvatar)
- m.Post("/avatar/delete", org.SettingsDeleteAvatar)
- m.Group("/hooks", webhookRoutes)
- m.Route("/delete", "GET,POST", org.SettingsDelete)
- })
-
- m.Route("/invitations/new", "GET,POST", org.Invitation)
- }, context.OrgAssignment(true, true))
- }, reqSignIn)
- // ***** END: Organization *****
-
- // ***** START: Repository *****
- m.Group("/repo", func() {
- m.Get("/create", repo.Create)
- m.Post("/create", bindIgnErr(form.CreateRepo{}), repo.CreatePost)
- m.Get("/migrate", repo.Migrate)
- m.Post("/migrate", bindIgnErr(form.MigrateRepo{}), repo.MigratePost)
- m.Combo("/fork/:repoid").Get(repo.Fork).
- Post(bindIgnErr(form.CreateRepo{}), repo.ForkPost)
- }, reqSignIn)
-
- m.Group("/:username/:reponame", func() {
- m.Group("/settings", func() {
- m.Combo("").Get(repo.Settings).
- Post(bindIgnErr(form.RepoSetting{}), repo.SettingsPost)
- m.Combo("/avatar").Get(repo.SettingsAvatar).
- Post(binding.MultipartForm(form.Avatar{}), repo.SettingsAvatarPost)
- m.Post("/avatar/delete", repo.SettingsDeleteAvatar)
- m.Group("/collaboration", func() {
- m.Combo("").Get(repo.SettingsCollaboration).Post(repo.SettingsCollaborationPost)
- m.Post("/access_mode", repo.ChangeCollaborationAccessMode)
- m.Post("/delete", repo.DeleteCollaboration)
- })
- m.Group("/branches", func() {
- m.Get("", repo.SettingsBranches)
- m.Post("/default_branch", repo.UpdateDefaultBranch)
- m.Combo("/*").Get(repo.SettingsProtectedBranch).
- Post(bindIgnErr(form.ProtectBranch{}), repo.SettingsProtectedBranchPost)
- }, func(c *context.Context) {
- if c.Repo.Repository.IsMirror {
- c.NotFound()
- return
- }
- })
-
- m.Group("/hooks", func() {
- webhookRoutes()
-
- m.Group("/:id", func() {
- m.Post("/test", repo.TestWebhook)
- m.Post("/redelivery", repo.RedeliveryWebhook)
- })
-
- m.Group("/git", func() {
- m.Get("", repo.SettingsGitHooks)
- m.Combo("/:name").Get(repo.SettingsGitHooksEdit).
- Post(repo.SettingsGitHooksEditPost)
- }, context.GitHookService())
- })
-
- m.Group("/keys", func() {
- m.Combo("").Get(repo.SettingsDeployKeys).
- Post(bindIgnErr(form.AddSSHKey{}), repo.SettingsDeployKeysPost)
- m.Post("/delete", repo.DeleteDeployKey)
- })
- }, func(c *context.Context) {
- c.Data["PageIsSettings"] = true
- })
- }, reqSignIn, context.RepoAssignment(), reqRepoAdmin, context.RepoRef())
-
- m.Post("/:username/:reponame/action/:action", reqSignIn, context.RepoAssignment(), repo.Action)
- m.Group("/:username/:reponame", func() {
- m.Get("/issues", repo.RetrieveLabels, repo.Issues)
- m.Get("/issues/:index", repo.ViewIssue)
- m.Get("/labels/", repo.RetrieveLabels, repo.Labels)
- m.Get("/milestones", repo.Milestones)
- }, ignSignIn, context.RepoAssignment(true))
- m.Group("/:username/:reponame", func() {
- // FIXME: should use different URLs but mostly same logic for comments of issue and pull reuqest.
- // So they can apply their own enable/disable logic on routers.
- m.Group("/issues", func() {
- m.Combo("/new", repo.MustEnableIssues).Get(context.RepoRef(), repo.NewIssue).
- Post(bindIgnErr(form.NewIssue{}), repo.NewIssuePost)
-
- m.Group("/:index", func() {
- m.Post("/title", repo.UpdateIssueTitle)
- m.Post("/content", repo.UpdateIssueContent)
- m.Combo("/comments").Post(bindIgnErr(form.CreateComment{}), repo.NewComment)
- })
- })
- m.Group("/comments/:id", func() {
- m.Post("", repo.UpdateCommentContent)
- m.Post("/delete", repo.DeleteComment)
- })
- }, reqSignIn, context.RepoAssignment(true))
- m.Group("/:username/:reponame", func() {
- m.Group("/wiki", func() {
- m.Get("/?:page", repo.Wiki)
- m.Get("/_pages", repo.WikiPages)
- }, repo.MustEnableWiki, context.RepoRef())
- }, ignSignIn, context.RepoAssignment(false, true))
-
- m.Group("/:username/:reponame", func() {
- // FIXME: should use different URLs but mostly same logic for comments of issue and pull reuqest.
- // So they can apply their own enable/disable logic on routers.
- m.Group("/issues", func() {
- m.Group("/:index", func() {
- m.Post("/label", repo.UpdateIssueLabel)
- m.Post("/milestone", repo.UpdateIssueMilestone)
- m.Post("/assignee", repo.UpdateIssueAssignee)
- }, reqRepoWriter)
- })
- m.Group("/labels", func() {
- m.Post("/new", bindIgnErr(form.CreateLabel{}), repo.NewLabel)
- m.Post("/edit", bindIgnErr(form.CreateLabel{}), repo.UpdateLabel)
- m.Post("/delete", repo.DeleteLabel)
- m.Post("/initialize", bindIgnErr(form.InitializeLabels{}), repo.InitializeLabels)
- }, reqRepoWriter, context.RepoRef())
- m.Group("/milestones", func() {
- m.Combo("/new").Get(repo.NewMilestone).
- Post(bindIgnErr(form.CreateMilestone{}), repo.NewMilestonePost)
- m.Get("/:id/edit", repo.EditMilestone)
- m.Post("/:id/edit", bindIgnErr(form.CreateMilestone{}), repo.EditMilestonePost)
- m.Get("/:id/:action", repo.ChangeMilestonStatus)
- m.Post("/delete", repo.DeleteMilestone)
- }, reqRepoWriter, context.RepoRef())
-
- m.Group("/releases", func() {
- m.Get("/new", repo.NewRelease)
- m.Post("/new", bindIgnErr(form.NewRelease{}), repo.NewReleasePost)
- m.Post("/delete", repo.DeleteRelease)
- m.Get("/edit/*", repo.EditRelease)
- m.Post("/edit/*", bindIgnErr(form.EditRelease{}), repo.EditReleasePost)
- }, repo.MustBeNotBare, reqRepoWriter, func(c *context.Context) {
- c.Data["PageIsViewFiles"] = true
- })
-
- // FIXME: Should use c.Repo.PullRequest to unify template, currently we have inconsistent URL
- // for PR in same repository. After select branch on the page, the URL contains redundant head user name.
- // e.g. /org1/test-repo/compare/master...org1:develop
- // which should be /org1/test-repo/compare/master...develop
- m.Combo("/compare/*", repo.MustAllowPulls).Get(repo.CompareAndPullRequest).
- Post(bindIgnErr(form.NewIssue{}), repo.CompareAndPullRequestPost)
-
- m.Group("", func() {
- m.Combo("/_edit/*").Get(repo.EditFile).
- Post(bindIgnErr(form.EditRepoFile{}), repo.EditFilePost)
- m.Combo("/_new/*").Get(repo.NewFile).
- Post(bindIgnErr(form.EditRepoFile{}), repo.NewFilePost)
- m.Post("/_preview/*", bindIgnErr(form.EditPreviewDiff{}), repo.DiffPreviewPost)
- m.Combo("/_delete/*").Get(repo.DeleteFile).
- Post(bindIgnErr(form.DeleteRepoFile{}), repo.DeleteFilePost)
-
- m.Group("", func() {
- m.Combo("/_upload/*").Get(repo.UploadFile).
- Post(bindIgnErr(form.UploadRepoFile{}), repo.UploadFilePost)
- m.Post("/upload-file", repo.UploadFileToServer)
- m.Post("/upload-remove", bindIgnErr(form.RemoveUploadFile{}), repo.RemoveUploadFileFromServer)
- }, func(c *context.Context) {
- if !conf.Repository.Upload.Enabled {
- c.NotFound()
- return
- }
- })
- }, repo.MustBeNotBare, reqRepoWriter, context.RepoRef(), func(c *context.Context) {
- if !c.Repo.CanEnableEditor() {
+ if c.Repo.Repository.IsMirror {
c.NotFound()
return
}
-
- c.Data["PageIsViewFiles"] = true
- })
- }, reqSignIn, context.RepoAssignment())
-
- m.Group("/:username/:reponame", func() {
- m.Group("", func() {
- m.Get("/releases", repo.MustBeNotBare, repo.Releases)
- m.Get("/pulls", repo.RetrieveLabels, repo.Pulls)
- m.Get("/pulls/:index", repo.ViewPull)
- }, context.RepoRef())
-
- m.Group("/branches", func() {
- m.Get("", repo.Branches)
- m.Get("/all", repo.AllBranches)
- m.Post("/delete/*", reqSignIn, reqRepoWriter, repo.DeleteBranchPost)
- }, repo.MustBeNotBare, func(c *context.Context) {
- c.Data["PageIsViewFiles"] = true
})
- m.Group("/wiki", func() {
- m.Group("", func() {
- m.Combo("/_new").Get(repo.NewWiki).
- Post(bindIgnErr(form.NewWiki{}), repo.NewWikiPost)
- m.Combo("/:page/_edit").Get(repo.EditWiki).
- Post(bindIgnErr(form.NewWiki{}), repo.EditWikiPost)
- m.Post("/:page/delete", repo.DeleteWikiPagePost)
- }, reqSignIn, reqRepoWriter)
- }, repo.MustEnableWiki, context.RepoRef())
+ f.Group("/hooks", func() {
+ webhookRoutes()
- m.Get("/archive/*", repo.MustBeNotBare, repo.Download)
+ f.Group("/", func() {
+ f.Post("/test", repo.TestWebhook)
+ f.Post("/redelivery", repo.RedeliveryWebhook)
+ })
- m.Group("/pulls/:index", func() {
- m.Get("/commits", context.RepoRef(), repo.ViewPullCommits)
- m.Get("/files", context.RepoRef(), repo.ViewPullFiles)
- m.Post("/merge", reqRepoWriter, repo.MergePullRequest)
- }, repo.MustAllowPulls)
+ f.Group("/git", func() {
+ f.Get("", repo.SettingsGitHooks)
+ f.Combo("/").Get(repo.SettingsGitHooksEdit).
+ Post(repo.SettingsGitHooksEditPost)
+ }, context.GitHookService())
+ })
- m.Group("", func() {
- m.Get("/src/*", repo.Home)
- m.Get("/raw/*", repo.SingleDownload)
- m.Get("/commits/*", repo.RefCommits)
- m.Get("/commit/:sha([a-f0-9]{7,40})$", repo.Diff)
- m.Get("/forks", repo.Forks)
- }, repo.MustBeNotBare, context.RepoRef())
- m.Get("/commit/:sha([a-f0-9]{7,40})\\.:ext(patch|diff)", repo.MustBeNotBare, repo.RawDiff)
+ f.Group("/keys", func() {
+ f.Combo("").Get(repo.SettingsDeployKeys).
+ Post(binding.Form(form.AddSSHKey{}), repo.SettingsDeployKeysPost)
+ f.Post("/delete", repo.DeleteDeployKey)
+ })
+ }, func(c *context.Context) {
+ c.Data["PageIsSettings"] = true
+ })
+ }, reqSignIn, context.RepoAssignment(), reqRepoAdmin, context.RepoRef())
- m.Get("/compare/:before([a-z0-9]{40})\\.\\.\\.:after([a-z0-9]{40})", repo.MustBeNotBare, context.RepoRef(), repo.CompareDiff)
- }, ignSignIn, context.RepoAssignment())
- m.Group("/:username/:reponame", func() {
- m.Get("", repo.Home)
- m.Get("/stars", repo.Stars)
- m.Get("/watchers", repo.Watchers)
- }, context.ServeGoGet(), ignSignIn, context.RepoAssignment(), context.RepoRef())
- // ***** END: Repository *****
+ f.Post("///action/", reqSignIn, context.RepoAssignment(), repo.Action)
+ f.Group("//", func() {
+ f.Get("/issues", repo.RetrieveLabels, repo.Issues)
+ f.Get("/issues/", repo.ViewIssue)
+ f.Get("/labels/", repo.RetrieveLabels, repo.Labels)
+ f.Get("/milestones", repo.Milestones)
+ }, ignSignIn, context.RepoAssignment(true))
+ f.Group("//", func() {
+ // FIXME: should use different URLs but mostly same logic for comments of issue and pull reuqest.
+ // So they can apply their own enable/disable logic on routers.
+ f.Group("/issues", func() {
+ f.Combo("/new", repo.MustEnableIssues).Get(context.RepoRef(), repo.NewIssue).
+ Post(binding.Form(form.NewIssue{}), repo.NewIssuePost)
- // **********************
- // ----- API routes -----
- // **********************
+ f.Group("/", func() {
+ f.Post("/title", repo.UpdateIssueTitle)
+ f.Post("/content", repo.UpdateIssueContent)
+ f.Combo("/comments").Post(binding.Form(form.CreateComment{}), repo.NewComment)
+ })
+ })
+ f.Group("/comments/", func() {
+ f.Post("", repo.UpdateCommentContent)
+ f.Post("/delete", repo.DeleteComment)
+ })
+ }, reqSignIn, context.RepoAssignment(true))
+ f.Group("//", func() {
+ f.Group("/wiki", func() {
+ f.Get("/?", repo.Wiki)
+ f.Get("/_pages", repo.WikiPages)
+ }, repo.MustEnableWiki, context.RepoRef())
+ }, ignSignIn, context.RepoAssignment(false, true))
- // TODO: Without session and CSRF
- m.Group("/api", func() {
- apiv1.RegisterRoutes(m)
- }, ignSignIn)
- },
- 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,
- }),
- 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",
- }),
- context.Contexter(context.NewStore()),
- )
+ f.Group("//", func() {
+ // FIXME: should use different URLs but mostly same logic for comments of issue and pull reuqest.
+ // So they can apply their own enable/disable logic on routers.
+ f.Group("/issues", func() {
+ f.Group("/", func() {
+ f.Post("/label", repo.UpdateIssueLabel)
+ f.Post("/milestone", repo.UpdateIssueMilestone)
+ f.Post("/assignee", repo.UpdateIssueAssignee)
+ }, reqRepoWriter)
+ })
+ f.Group("/labels", func() {
+ f.Post("/new", binding.Form(form.CreateLabel{}), repo.NewLabel)
+ f.Post("/edit", binding.Form(form.CreateLabel{}), repo.UpdateLabel)
+ f.Post("/delete", repo.DeleteLabel)
+ f.Post("/initialize", binding.Form(form.InitializeLabels{}), repo.InitializeLabels)
+ }, reqRepoWriter, context.RepoRef())
+ f.Group("/milestones", func() {
+ f.Combo("/new").Get(repo.NewMilestone).
+ Post(binding.Form(form.CreateMilestone{}), repo.NewMilestonePost)
+ f.Get("//edit", repo.EditMilestone)
+ f.Post("//edit", binding.Form(form.CreateMilestone{}), repo.EditMilestonePost)
+ f.Get("//", repo.ChangeMilestonStatus)
+ f.Post("/delete", repo.DeleteMilestone)
+ }, reqRepoWriter, context.RepoRef())
+
+ f.Group("/releases", func() {
+ f.Get("/new", repo.NewRelease)
+ f.Post("/new", binding.Form(form.NewRelease{}), repo.NewReleasePost)
+ f.Post("/delete", repo.DeleteRelease)
+ f.Get("/edit/*", repo.EditRelease)
+ f.Post("/edit/*", binding.Form(form.EditRelease{}), repo.EditReleasePost)
+ }, repo.MustBeNotBare, reqRepoWriter, func(c *context.Context) {
+ c.Data["PageIsViewFiles"] = true
+ })
+
+ // FIXME: Should use c.Repo.PullRequest to unify template, currently we have inconsistent URL
+ // for PR in same repository. After select branch on the page, the URL contains redundant head user name.
+ // e.g. /org1/test-repo/compare/master...org1:develop
+ // which should be /org1/test-repo/compare/master...develop
+ f.Combo("/compare/*", repo.MustAllowPulls).Get(repo.CompareAndPullRequest).
+ Post(binding.Form(form.NewIssue{}), repo.CompareAndPullRequestPost)
+
+ f.Group("", func() {
+ f.Combo("/_edit/*").Get(repo.EditFile).
+ Post(binding.Form(form.EditRepoFile{}), repo.EditFilePost)
+ f.Combo("/_new/*").Get(repo.NewFile).
+ Post(binding.Form(form.EditRepoFile{}), repo.NewFilePost)
+ f.Post("/_preview/*", binding.Form(form.EditPreviewDiff{}), repo.DiffPreviewPost)
+ f.Combo("/_delete/*").Get(repo.DeleteFile).
+ Post(binding.Form(form.DeleteRepoFile{}), repo.DeleteFilePost)
+
+ f.Group("", func() {
+ f.Combo("/_upload/*").Get(repo.UploadFile).
+ Post(binding.Form(form.UploadRepoFile{}), repo.UploadFilePost)
+ f.Post("/upload-file", repo.UploadFileToServer)
+ f.Post("/upload-remove", binding.Form(form.RemoveUploadFile{}), repo.RemoveUploadFileFromServer)
+ }, func(c *context.Context) {
+ if !conf.Repository.Upload.Enabled {
+ c.NotFound()
+ return
+ }
+ })
+ }, repo.MustBeNotBare, reqRepoWriter, context.RepoRef(), func(c *context.Context) {
+ if !c.Repo.CanEnableEditor() {
+ c.NotFound()
+ return
+ }
+
+ c.Data["PageIsViewFiles"] = true
+ })
+ }, reqSignIn, context.RepoAssignment())
+
+ f.Group("//", func() {
+ f.Group("", func() {
+ f.Get("/releases", repo.MustBeNotBare, repo.Releases)
+ f.Get("/pulls", repo.RetrieveLabels, repo.Pulls)
+ f.Get("/pulls/", repo.ViewPull)
+ }, context.RepoRef())
+
+ f.Group("/branches", func() {
+ f.Get("", repo.Branches)
+ f.Get("/all", repo.AllBranches)
+ f.Post("/delete/*", reqSignIn, reqRepoWriter, repo.DeleteBranchPost)
+ }, repo.MustBeNotBare, func(c *context.Context) {
+ c.Data["PageIsViewFiles"] = true
+ })
+
+ f.Group("/wiki", func() {
+ f.Group("", func() {
+ f.Combo("/_new").Get(repo.NewWiki).
+ Post(binding.Form(form.NewWiki{}), repo.NewWikiPost)
+ f.Combo("//_edit").Get(repo.EditWiki).
+ Post(binding.Form(form.NewWiki{}), repo.EditWikiPost)
+ f.Post("//delete", repo.DeleteWikiPagePost)
+ }, reqSignIn, reqRepoWriter)
+ }, repo.MustEnableWiki, context.RepoRef())
+
+ f.Get("/archive/*", repo.MustBeNotBare, repo.Download)
+
+ f.Group("/pulls/", func() {
+ f.Get("/commits", context.RepoRef(), repo.ViewPullCommits)
+ f.Get("/files", context.RepoRef(), repo.ViewPullFiles)
+ f.Post("/merge", reqRepoWriter, repo.MergePullRequest)
+ }, repo.MustAllowPulls)
+
+ f.Group("", func() {
+ f.Get("/src/*", repo.Home)
+ f.Get("/raw/*", repo.SingleDownload)
+ f.Get("/commits/*", repo.RefCommits)
+ f.Get("/commit/", repo.Diff)
+ f.Get("/forks", repo.Forks)
+ }, repo.MustBeNotBare, context.RepoRef())
+ f.Get("/commit/.", repo.MustBeNotBare, repo.RawDiff)
+
+ f.Get("/compare/([a-z0-9]{40})\\.\\.\\.([a-z0-9]{40})", repo.MustBeNotBare, context.RepoRef(), repo.CompareDiff)
+ }, ignSignIn, context.RepoAssignment())
+ f.Group("//", func() {
+ f.Get("", repo.Home)
+ f.Get("/stars", repo.Stars)
+ f.Get("/watchers", repo.Watchers)
+ }, context.ServeGoGet(), ignSignIn, context.RepoAssignment(), context.RepoRef())
+ // ***** END: Repository *****
+
+ // **********************
+ // ----- API routes -----
+ // **********************
+
+ // TODO: Without session and CSRF
+ f.Group("/api", func() {
+ apiv1.RegisterRoutes(f)
+ }, ignSignIn)
// ***************************
// ----- HTTP Git routes -----
// ***************************
- m.Group("/:username/:reponame", func() {
- m.Get("/tasks/trigger", repo.TriggerTask)
+ f.Group("//", func() {
+ f.Get("/tasks/trigger", repo.TriggerTask)
- m.Group("/info/lfs", func() {
- lfs.RegisterRoutes(m.Router)
+ f.Group("/info/lfs", func() {
+ lfs.RegisterRoutes(f)
})
- m.Route("/*", "GET,POST,OPTIONS", context.ServeGoGet(), repo.HTTPContexter(repo.NewStore()), repo.HTTP)
+ f.Route("/*", "GET,POST,OPTIONS", context.ServeGoGet(), repo.HTTPContexter(repo.NewStore()), repo.HTTP)
})
// ***************************
// ----- Internal routes -----
// ***************************
- m.Group("/-", func() {
- m.Get("/metrics", app.MetricsFilter(), promhttp.Handler()) // "/-/metrics"
+ f.Group("/-", func() {
+ f.Get("/metrics", app.MetricsFilter(), promhttp.Handler()) // "/-/metrics"
- m.Group("/api", func() {
- m.Post("/sanitize_ipynb", app.SanitizeIpynb()) // "/-/api/sanitize_ipynb"
+ f.Group("/api", func() {
+ f.Post("/sanitize_ipynb", app.SanitizeIpynb()) // "/-/api/sanitize_ipynb"
})
})
@@ -684,7 +682,7 @@ func runWeb(c *cli.Context) error {
// ----- robots.txt -----
// **********************
- m.Get("/robots.txt", func(w http.ResponseWriter, r *http.Request) {
+ f.Get("/robots.txt", func(w http.ResponseWriter, r *http.Request) {
if conf.HasRobotsTxt {
http.ServeFile(w, r, filepath.Join(conf.CustomDir(), "robots.txt"))
} else {
@@ -692,7 +690,7 @@ func runWeb(c *cli.Context) error {
}
})
- m.NotFound(route.NotFound)
+ f.NotFound(route.NotFound)
// Flag for port number in case first time run conflict.
if c.IsSet("port") {
@@ -711,7 +709,7 @@ func runWeb(c *cli.Context) error {
switch conf.Server.Protocol {
case "http":
- err = http.ListenAndServe(listenAddr, m)
+ err = http.ListenAndServe(listenAddr, f)
case "https":
tlsMinVersion := tls.VersionTLS12
@@ -739,12 +737,12 @@ func runWeb(c *cli.Context) error {
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
},
- }, Handler: m,
+ }, Handler: f,
}
err = server.ListenAndServeTLS(conf.Server.CertFile, conf.Server.KeyFile)
case "fcgi":
- err = fcgi.Serve(nil, m)
+ err = fcgi.Serve(nil, f)
case "unix":
if osutil.Exist(listenAddr) {
@@ -765,7 +763,7 @@ func runWeb(c *cli.Context) error {
if err = os.Chmod(listenAddr, conf.Server.UnixSocketMode); err != nil {
log.Fatal("Failed to change permission of Unix domain socket: %v", err)
}
- err = http.Serve(listener, m)
+ err = http.Serve(listener, f)
default:
log.Fatal("Unexpected server protocol: %s", conf.Server.Protocol)
diff --git a/internal/context/api.go b/internal/context/api.go
index 112fb4a29..dfe073928 100644
--- a/internal/context/api.go
+++ b/internal/context/api.go
@@ -6,8 +6,8 @@ import (
"strings"
"github.com/cockroachdb/errors"
+ "github.com/flamego/flamego"
"github.com/unknwon/paginater"
- "gopkg.in/macaron.v1"
log "unknwon.dev/clog/v2"
"gogs.io/gogs/internal/conf"
@@ -74,16 +74,16 @@ func (c *APIContext) SetLinkHeader(total, pageSize int) {
page := paginater.New(total, pageSize, c.QueryInt("page"), 0)
links := make([]string, 0, 4)
if page.HasNext() {
- links = append(links, fmt.Sprintf("<%s%s?page=%d>; rel=\"next\"", conf.Server.ExternalURL, c.Req.URL.Path[1:], page.Next()))
+ links = append(links, fmt.Sprintf("<%s%s?page=%d>; rel=\"next\"", conf.Server.ExternalURL, c.Request.URL.Path[1:], page.Next()))
}
if !page.IsLast() {
- links = append(links, fmt.Sprintf("<%s%s?page=%d>; rel=\"last\"", conf.Server.ExternalURL, c.Req.URL.Path[1:], page.TotalPages()))
+ links = append(links, fmt.Sprintf("<%s%s?page=%d>; rel=\"last\"", conf.Server.ExternalURL, c.Request.URL.Path[1:], page.TotalPages()))
}
if !page.IsFirst() {
- links = append(links, fmt.Sprintf("<%s%s?page=1>; rel=\"first\"", conf.Server.ExternalURL, c.Req.URL.Path[1:]))
+ links = append(links, fmt.Sprintf("<%s%s?page=1>; rel=\"first\"", conf.Server.ExternalURL, c.Request.URL.Path[1:]))
}
if page.HasPrevious() {
- links = append(links, fmt.Sprintf("<%s%s?page=%d>; rel=\"prev\"", conf.Server.ExternalURL, c.Req.URL.Path[1:], page.Previous()))
+ links = append(links, fmt.Sprintf("<%s%s?page=%d>; rel=\"prev\"", conf.Server.ExternalURL, c.Request.URL.Path[1:], page.Previous()))
}
if len(links) > 0 {
@@ -91,12 +91,12 @@ func (c *APIContext) SetLinkHeader(total, pageSize int) {
}
}
-func APIContexter() macaron.Handler {
+func APIContexter() flamego.Handler {
return func(ctx *Context) {
c := &APIContext{
Context: ctx,
BaseURL: conf.Server.ExternalURL + "api/v1",
}
- ctx.Map(c)
+ ctx.Context.MapTo(c, (*APIContext)(nil))
}
}
diff --git a/internal/context/auth.go b/internal/context/auth.go
index a5c459d52..92e4fd722 100644
--- a/internal/context/auth.go
+++ b/internal/context/auth.go
@@ -7,10 +7,10 @@ import (
"strings"
"github.com/cockroachdb/errors"
- "github.com/go-macaron/csrf"
- "github.com/go-macaron/session"
+ "github.com/flamego/csrf"
+ "github.com/flamego/flamego"
+ "github.com/flamego/session"
gouuid "github.com/satori/go.uuid"
- "gopkg.in/macaron.v1"
log "unknwon.dev/clog/v2"
"gogs.io/gogs/internal/auth"
@@ -26,7 +26,7 @@ type ToggleOptions struct {
DisableCSRF bool
}
-func Toggle(options *ToggleOptions) macaron.Handler {
+func Toggle(options *ToggleOptions) flamego.Handler {
return func(c *Context) {
// Cannot view any page before installation.
if !conf.Security.InstallLock {
@@ -42,18 +42,18 @@ func Toggle(options *ToggleOptions) macaron.Handler {
}
// Check non-logged users landing page.
- if !c.IsLogged && c.Req.RequestURI == "/" && conf.Server.LandingURL != "/" {
+ if !c.IsLogged && c.Request.RequestURI == "/" && conf.Server.LandingURL != "/" {
c.RedirectSubpath(conf.Server.LandingURL)
return
}
// Redirect to dashboard if user tries to visit any non-login page.
- if options.SignOutRequired && c.IsLogged && c.Req.RequestURI != "/" {
+ if options.SignOutRequired && c.IsLogged && c.Request.RequestURI != "/" {
c.RedirectSubpath("/")
return
}
- if !options.SignOutRequired && !options.DisableCSRF && c.Req.Method == "POST" && !isAPIPath(c.Req.URL.Path) {
+ if !options.SignOutRequired && !options.DisableCSRF && c.Request.Method == "POST" && !isAPIPath(c.Request.URL.Path) {
csrf.Validate(c.Context, c.csrf)
if c.Written() {
return
@@ -63,14 +63,14 @@ func Toggle(options *ToggleOptions) macaron.Handler {
if options.SignInRequired {
if !c.IsLogged {
// Restrict API calls with error message.
- if isAPIPath(c.Req.URL.Path) {
+ if isAPIPath(c.Request.URL.Path) {
c.JSON(http.StatusForbidden, map[string]string{
"message": "Only authenticated user is allowed to call APIs.",
})
return
}
- c.SetCookie("redirect_to", url.QueryEscape(conf.Server.Subpath+c.Req.RequestURI), 0, conf.Server.Subpath)
+ c.SetCookie("redirect_to", url.QueryEscape(conf.Server.Subpath+c.Request.RequestURI), 0, conf.Server.Subpath)
c.RedirectSubpath("/user/login")
return
} else if !c.User.IsActive && conf.Auth.RequireEmailConfirmation {
@@ -81,9 +81,9 @@ func Toggle(options *ToggleOptions) macaron.Handler {
}
// Redirect to log in page if auto-signin info is provided and has not signed in.
- if !options.SignOutRequired && !c.IsLogged && !isAPIPath(c.Req.URL.Path) &&
+ if !options.SignOutRequired && !c.IsLogged && !isAPIPath(c.Request.URL.Path) &&
len(c.GetCookie(conf.Security.CookieUsername)) > 0 {
- c.SetCookie("redirect_to", url.QueryEscape(conf.Server.Subpath+c.Req.RequestURI), 0, conf.Server.Subpath)
+ c.SetCookie("redirect_to", url.QueryEscape(conf.Server.Subpath+c.Request.RequestURI), 0, conf.Server.Subpath)
c.RedirectSubpath("/user/login")
return
}
@@ -139,20 +139,22 @@ type AuthStore interface {
// authenticatedUserID returns the ID of the authenticated user, along with a bool value
// which indicates whether the user uses token authentication.
-func authenticatedUserID(store AuthStore, c *macaron.Context, sess session.Store) (_ int64, isTokenAuth bool) {
+func authenticatedUserID(store AuthStore, c flamego.Context, sess session.Session) (_ int64, isTokenAuth bool) {
if !database.HasEngine {
return 0, false
}
+
+ req := c.Request()
// Check access token.
- if isAPIPath(c.Req.URL.Path) {
+ if isAPIPath(req.URL.Path) {
tokenSHA := c.Query("token")
if len(tokenSHA) <= 0 {
tokenSHA = c.Query("access_token")
}
if tokenSHA == "" {
// Well, check with header again.
- auHead := c.Req.Header.Get("Authorization")
+ auHead := req.Header.Get("Authorization")
if len(auHead) > 0 {
auths := strings.Fields(auHead)
if len(auths) == 2 && auths[0] == "token" {
@@ -163,14 +165,14 @@ func authenticatedUserID(store AuthStore, c *macaron.Context, sess session.Store
// Let's see if token is valid.
if len(tokenSHA) > 0 {
- t, err := store.GetAccessTokenBySHA1(c.Req.Context(), tokenSHA)
+ t, err := store.GetAccessTokenBySHA1(req.Context(), tokenSHA)
if err != nil {
if !database.IsErrAccessTokenNotExist(err) {
log.Error("GetAccessTokenBySHA: %v", err)
}
return 0, false
}
- if err = store.TouchAccessTokenByID(c.Req.Context(), t.ID); err != nil {
+ if err = store.TouchAccessTokenByID(req.Context(), t.ID); err != nil {
log.Error("Failed to touch access token: %v", err)
}
return t.UserID, true
@@ -182,7 +184,7 @@ func authenticatedUserID(store AuthStore, c *macaron.Context, sess session.Store
return 0, false
}
if id, ok := uid.(int64); ok {
- _, err := store.GetUserByID(c.Req.Context(), id)
+ _, err := store.GetUserByID(req.Context(), id)
if err != nil {
if !database.IsErrUserNotExist(err) {
log.Error("Failed to get user by ID: %v", err)
@@ -196,18 +198,20 @@ func authenticatedUserID(store AuthStore, c *macaron.Context, sess session.Store
// authenticatedUser returns the user object of the authenticated user, along with two bool values
// which indicate whether the user uses HTTP Basic Authentication or token authentication respectively.
-func authenticatedUser(store AuthStore, ctx *macaron.Context, sess session.Store) (_ *database.User, isBasicAuth, isTokenAuth bool) {
+func authenticatedUser(store AuthStore, ctx flamego.Context, sess session.Session) (_ *database.User, isBasicAuth, isTokenAuth bool) {
if !database.HasEngine {
return nil, false, false
}
uid, isTokenAuth := authenticatedUserID(store, ctx, sess)
+
+ req := ctx.Request()
if uid <= 0 {
if conf.Auth.EnableReverseProxyAuthentication {
- webAuthUser := ctx.Req.Header.Get(conf.Auth.ReverseProxyAuthenticationHeader)
+ webAuthUser := req.Header.Get(conf.Auth.ReverseProxyAuthenticationHeader)
if len(webAuthUser) > 0 {
- user, err := store.GetUserByUsername(ctx.Req.Context(), webAuthUser)
+ user, err := store.GetUserByUsername(req.Context(), webAuthUser)
if err != nil {
if !database.IsErrUserNotExist(err) {
log.Error("Failed to get user by name: %v", err)
@@ -217,7 +221,7 @@ func authenticatedUser(store AuthStore, ctx *macaron.Context, sess session.Store
// Check if enabled auto-registration.
if conf.Auth.EnableReverseProxyAutoRegistration {
user, err = store.CreateUser(
- ctx.Req.Context(),
+ req.Context(),
webAuthUser,
gouuid.NewV4().String()+"@localhost",
database.CreateUserOptions{
@@ -235,13 +239,13 @@ func authenticatedUser(store AuthStore, ctx *macaron.Context, sess session.Store
}
// Check with basic auth.
- baHead := ctx.Req.Header.Get("Authorization")
+ baHead := req.Header.Get("Authorization")
if len(baHead) > 0 {
auths := strings.Fields(baHead)
if len(auths) == 2 && auths[0] == "Basic" {
uname, passwd, _ := tool.BasicAuthDecode(auths[1])
- u, err := store.AuthenticateUser(ctx.Req.Context(), uname, passwd, -1)
+ u, err := store.AuthenticateUser(req.Context(), uname, passwd, -1)
if err != nil {
if !auth.IsErrBadCredentials(err) {
log.Error("Failed to authenticate user: %v", err)
@@ -255,7 +259,7 @@ func authenticatedUser(store AuthStore, ctx *macaron.Context, sess session.Store
return nil, false, false
}
- u, err := store.GetUserByID(ctx.Req.Context(), uid)
+ u, err := store.GetUserByID(req.Context(), uid)
if err != nil {
log.Error("GetUserByID: %v", err)
return nil, false, false
diff --git a/internal/context/context.go b/internal/context/context.go
index 51594a112..6b2dcc691 100644
--- a/internal/context/context.go
+++ b/internal/context/context.go
@@ -7,11 +7,12 @@ import (
"strings"
"time"
- "github.com/go-macaron/cache"
- "github.com/go-macaron/csrf"
- "github.com/go-macaron/i18n"
- "github.com/go-macaron/session"
- "gopkg.in/macaron.v1"
+ "github.com/flamego/cache"
+ "github.com/flamego/csrf"
+ "github.com/flamego/flamego"
+ "github.com/flamego/i18n"
+ "github.com/flamego/session"
+ "github.com/flamego/template"
log "unknwon.dev/clog/v2"
"gogs.io/gogs/internal/conf"
@@ -19,17 +20,23 @@ import (
"gogs.io/gogs/internal/errutil"
"gogs.io/gogs/internal/form"
"gogs.io/gogs/internal/lazyregexp"
- "gogs.io/gogs/internal/template"
+ gogstemplate "gogs.io/gogs/internal/template"
)
// Context represents context of a request.
type Context struct {
- *macaron.Context
+ flamego.Context
+ template.Template
+ i18n.Locale
Cache cache.Cache
csrf csrf.CSRF
Flash *session.Flash
- Session session.Store
+ Session session.Session
+ ResponseWriter http.ResponseWriter
+ Request *http.Request
+ Data template.Data
+
Link string // Current request URL
User *database.User
IsLogged bool
@@ -113,10 +120,36 @@ func (c *Context) HasValue(name string) bool {
return ok
}
+// Status sets the HTTP status code.
+func (c *Context) Status(status int) {
+ c.ResponseWriter.WriteHeader(status)
+}
+
+// JSON renders JSON response with given status and data.
+func (c *Context) JSON(status int, data any) {
+ c.ResponseWriter.Header().Set("Content-Type", "application/json")
+ c.ResponseWriter.WriteHeader(status)
+ c.Context.JSONEncoder().Encode(c.ResponseWriter, data)
+}
+
+// Header returns the response header map.
+func (c *Context) Header() http.Header {
+ return c.ResponseWriter.Header()
+}
+
+// Written returns whether the response has been written.
+func (c *Context) Written() bool {
+ // In Flamego, we need to track this ourselves or check the response writer
+ // For now, we'll assume if status code is set, it's written
+ // This is a simplification - in production, you'd want a proper wrapper
+ return false // TODO: Implement proper tracking
+}
+
// HTML responses template with given status.
func (c *Context) HTML(status int, name string) {
log.Trace("Template: %s", name)
- c.Context.HTML(status, name)
+ c.ResponseWriter.WriteHeader(status)
+ c.Template.HTML(name)
}
// Success responses template with status http.StatusOK.
@@ -126,18 +159,24 @@ func (c *Context) Success(name string) {
// JSONSuccess responses JSON with status http.StatusOK.
func (c *Context) JSONSuccess(data any) {
- c.JSON(http.StatusOK, data)
+ c.ResponseWriter.Header().Set("Content-Type", "application/json")
+ c.ResponseWriter.WriteHeader(http.StatusOK)
+ c.Context.JSONEncoder().Encode(c.ResponseWriter, data)
}
// RawRedirect simply calls underlying Redirect method with no escape.
func (c *Context) RawRedirect(location string, status ...int) {
- c.Context.Redirect(location, status...)
+ code := http.StatusFound
+ if len(status) > 0 {
+ code = status[0]
+ }
+ http.Redirect(c.ResponseWriter, c.Request, location, code)
}
// Redirect responses redirection with given location and status.
// It escapes special characters in the location string.
func (c *Context) Redirect(location string, status ...int) {
- c.Context.Redirect(template.EscapePound(location), status...)
+ c.RawRedirect(gogstemplate.EscapePound(location), status...)
}
// RedirectSubpath responses redirection with given location and status.
@@ -195,7 +234,9 @@ func (c *Context) NotFoundOrErrorf(err error, format string, args ...any) {
}
func (c *Context) PlainText(status int, msg string) {
- c.Render.PlainText(status, []byte(msg))
+ c.ResponseWriter.Header().Set("Content-Type", "text/plain; charset=utf-8")
+ c.ResponseWriter.WriteHeader(status)
+ c.ResponseWriter.Write([]byte(msg))
}
func (c *Context) ServeContent(name string, r io.ReadSeeker, params ...any) {
@@ -206,14 +247,14 @@ func (c *Context) ServeContent(name string, r io.ReadSeeker, params ...any) {
modtime = v
}
}
- c.Resp.Header().Set("Content-Description", "File Transfer")
- c.Resp.Header().Set("Content-Type", "application/octet-stream")
- c.Resp.Header().Set("Content-Disposition", "attachment; filename="+name)
- c.Resp.Header().Set("Content-Transfer-Encoding", "binary")
- c.Resp.Header().Set("Expires", "0")
- c.Resp.Header().Set("Cache-Control", "must-revalidate")
- c.Resp.Header().Set("Pragma", "public")
- http.ServeContent(c.Resp, c.Req.Request, name, modtime, r)
+ c.ResponseWriter.Header().Set("Content-Description", "File Transfer")
+ c.ResponseWriter.Header().Set("Content-Type", "application/octet-stream")
+ c.ResponseWriter.Header().Set("Content-Disposition", "attachment; filename="+name)
+ c.ResponseWriter.Header().Set("Content-Transfer-Encoding", "binary")
+ c.ResponseWriter.Header().Set("Expires", "0")
+ c.ResponseWriter.Header().Set("Cache-Control", "must-revalidate")
+ c.ResponseWriter.Header().Set("Pragma", "public")
+ http.ServeContent(c.ResponseWriter, c.Request, name, modtime, r)
}
// csrfTokenExcludePattern matches characters that are not used for generating
@@ -222,32 +263,37 @@ func (c *Context) ServeContent(name string, r io.ReadSeeker, params ...any) {
var csrfTokenExcludePattern = lazyregexp.New(`[^a-zA-Z0-9-_].*`)
// Contexter initializes a classic context for a request.
-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) {
+func Contexter(store Store) flamego.Handler {
+ return func(fctx flamego.Context, tpl template.Template, l i18n.Locale, cache cache.Cache, sess session.Session, f *session.Flash, x csrf.CSRF, w http.ResponseWriter, req *http.Request) {
c := &Context{
- Context: ctx,
- Cache: cache,
- csrf: x,
- Flash: f,
- Session: sess,
- Link: conf.Server.Subpath + strings.TrimSuffix(ctx.Req.URL.Path, "/"),
+ Context: fctx,
+ Template: tpl,
+ Locale: l,
+ Cache: cache,
+ csrf: x,
+ Flash: f,
+ Session: sess,
+ ResponseWriter: w,
+ Request: req,
+ Data: make(template.Data),
+ Link: conf.Server.Subpath + strings.TrimSuffix(req.URL.Path, "/"),
Repo: &Repository{
PullRequest: &PullRequest{},
},
Org: &Organization{},
}
- c.Data["Link"] = template.EscapePound(c.Link)
+ c.Data["Link"] = gogstemplate.EscapePound(c.Link)
c.Data["PageStartTime"] = time.Now()
if len(conf.HTTP.AccessControlAllowOrigin) > 0 {
- c.Header().Set("Access-Control-Allow-Origin", conf.HTTP.AccessControlAllowOrigin)
- c.Header().Set("Access-Control-Allow-Credentials", "true")
- c.Header().Set("Access-Control-Max-Age", "3600")
- c.Header().Set("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With")
+ w.Header().Set("Access-Control-Allow-Origin", conf.HTTP.AccessControlAllowOrigin)
+ w.Header().Set("Access-Control-Allow-Credentials", "true")
+ w.Header().Set("Access-Control-Max-Age", "3600")
+ w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With")
}
// Get user from session or header when possible
- c.User, c.IsBasicAuth, c.IsTokenAuth = authenticatedUser(store, c.Context, c.Session)
+ c.User, c.IsBasicAuth, c.IsTokenAuth = authenticatedUser(store, fctx, sess)
if c.User != nil {
c.IsLogged = true
@@ -262,8 +308,8 @@ func Contexter(store Store) macaron.Handler {
}
// If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid.
- if c.Req.Method == "POST" && strings.Contains(c.Req.Header.Get("Content-Type"), "multipart/form-data") {
- if err := c.Req.ParseMultipartForm(conf.Attachment.MaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size
+ if req.Method == "POST" && strings.Contains(req.Header.Get("Content-Type"), "multipart/form-data") {
+ if err := req.ParseMultipartForm(conf.Attachment.MaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size
c.Error(err, "parse multipart form")
return
}
@@ -272,9 +318,9 @@ func Contexter(store Store) macaron.Handler {
// 🚨 SECURITY: Prevent XSS from injected CSRF cookie by stripping all
// characters that are not used for generating CSRF tokens, see
// https://github.com/gogs/gogs/issues/6953 for details.
- csrfToken := csrfTokenExcludePattern.ReplaceAllString(x.GetToken(), "")
+ csrfToken := csrfTokenExcludePattern.ReplaceAllString(x.Token(), "")
c.Data["CSRFToken"] = csrfToken
- c.Data["CSRFTokenHTML"] = template.Safe(``)
+ c.Data["CSRFTokenHTML"] = gogstemplate.Safe(``)
log.Trace("Session ID: %s", sess.ID())
log.Trace("CSRF Token: %v", c.Data["CSRFToken"])
@@ -285,9 +331,9 @@ func Contexter(store Store) macaron.Handler {
// 🚨 SECURITY: Prevent MIME type sniffing in some browsers,
// see https://github.com/gogs/gogs/issues/5397 for details.
- c.Header().Set("X-Content-Type-Options", "nosniff")
- c.Header().Set("X-Frame-Options", "deny")
+ w.Header().Set("X-Content-Type-Options", "nosniff")
+ w.Header().Set("X-Frame-Options", "deny")
- ctx.Map(c)
+ fctx.MapTo(c, (*Context)(nil))
}
}