Users and Admin Interface Part 2

In this section, we will create a CRUD interface to manage contacts.

controllers/crud.go

package controllers

import (
    "fmt"
    "queenbee/models"
    "reflect"
    "strconv"
)

type ContactListController struct {
    BaseAdminController
}

type ContactDetailController struct {
    BaseAdminController
}

// READ HELPERS
type Item struct {
    Key   string
    Value any // Using 'any' to allow any type for Value
}

// Function to convert struct to ordered data based on struct field order
func structToOrderedData(v any) []Item {
    var orderedData []Item
    // Use reflection to get the value and type of the struct
    val := reflect.ValueOf(v)
    typ := val.Type()
    // Iterate over the struct fields in their defined order
    for i := 0; i < val.NumField(); i++ {
        field := val.Field(i)
        fieldType := typ.Field(i)
        orderedData = append(orderedData, Item{Key: fieldType.Name, Value: field.Interface()})
    }
    return orderedData
}

func read(c *BaseAdminController, createModel func(id uint64) any) any {
    idStr := c.Ctx.Input.Param(":id")
    id, err := strconv.ParseUint(idStr, 10, 64)
    if err != nil {
        c.Ctx.Output.SetStatus(400) // Bad Request
        c.Data["json"] = map[string]string{"error": "Invalid ID"}
        c.ServeJSON()
        return nil
    }

    ptr := createModel(id)
    err = models.O.Read(ptr)
    if err != nil {
        c.Ctx.Output.SetStatus(404)
        c.Data["json"] = map[string]string{"error": "Not Found"}
        c.ServeJSON()
        return nil
    }

    return ptr
}

// List
func (c *ContactListController) Get() {
    c.RequireAuth()

    c.Data["Title"] = "Contacts"
    c.TplName = "admin/list.tpl"
    q := models.O.QueryTable("contact_model")
    var contacts []*models.ContactModel
    q.All(&contacts)
    var l [][]Item
    for _, contact := range contacts {
        l = append(l, structToOrderedData(*contact))
    }
    c.Data["List"] = l
    c.Data["BaseHref"] = "/admin/contacts"
}

// Create (NOT IMPLEMENTED)
// func (c *ContactListController) Post() {
//  c.RequireAuth()

//  c.Data["Title"] = "Contacts"
//  c.TplName = "admin/create.tpl"
// }

// Read
func (c *ContactDetailController) Get() {
    c.RequireAuth()

    c.Data["Title"] = "Contacts"
    c.TplName = "admin/read.tpl"

    contact := read(&c.BaseAdminController, func(id uint64) any {
        contact := &models.ContactModel{Id: id}
        return (any)(contact)
    })

    if m, ok := contact.(*models.ContactModel); ok {
        val := structToOrderedData(*m)
        c.Data["Title"] = fmt.Sprintf("Contact %d", m.Id)
        c.Data["Item"] = val
        c.Data["Id"] = m.Id
        c.Data["BaseHref"] = "/admin/contacts"
    }
}

// Update (NOT IMPLEMENTED)
// func (c *ContactDetailController) Post() {
//  c.RequireAuth()

//  c.Data["Title"] = "Contacts"
//  c.TplName = "admin/update.tpl"
// }

// Delete
func (c *ContactDetailController) Delete() {
    c.RequireAuth()

    idStr := c.Ctx.Input.Param(":id")
    id, _ := strconv.ParseUint(idStr, 10, 64)
    contact := &models.ContactModel{Id: id}
    _, err := models.O.Delete(contact)
    fmt.Println(err)
    c.Data["Title"] = "Contacts"
}

routers/router.go

Add these two routes.

beego.Router("/admin/contacts/:id", &controllers.ContactDetailController{})
beego.Router("/admin/contacts", &controllers.ContactListController{})

views/admin/read.tpl

{{ template "admin/layout.tpl" . }}
{{ define "content" }}
<h1 class="text-4xl">{{.Title}}</h1>

<button class="btn btn-error" onclick="fetch(window.location.href, {method:'DELETE'}).then(()=>{window.location.href = {{.BaseHref}}})">BALEETED!</button>
<table class="table w-full mt-4">
  <thead>
    <tr>
      <th class="bg-gray-200">Key</th>
      <th class="bg-gray-200">Value</th>
    </tr>
  </thead>
  <tbody>
    {{range .Item}}
    <tr>
      <td class="border px-4 py-2">{{.Key}}</td>
      <td class="border px-4 py-2">{{.Value}}</td>
    </tr>
    {{end}}
  </tbody>
</table>
{{ end }}

views/admin/list.tpl

{{ template "admin/layout.tpl" . }}
{{ define "content" }}
<h1 class="text-4xl">{{.Title}}</h1>
<table class="table w-full">
  {{if eq (len .List) 0}} 
  <tr>
    <td colspan="3">No records</td> 
  </tr>
  {{else}}
  <thead>
    <tr>
    {{range index .List 0}}
      <th class="bg-gray-200">{{.Key}}</th>
    {{end}}
    </tr>
  </thead>
  <tbody>
    {{range .List}}
    <tr>
    {{range .}}
      {{if eq .Key "Id"}}
        <td class="border px-4 py-2">
        <a href="{{$.BaseHref}}/{{.Value}}">
        {{.Value}}
        </a>
        </td>
      </a>
      {{else}}
        <td class="border px-4 py-2">{{.Value}}</td>
      {{end}}
    {{end}}
    </tr>
    </a>
    {{end}}
  </tbody>
  {{end}}
</table>
{{ end }}