Skip to content

Commit

Permalink
feat: add contacts handler
Browse files Browse the repository at this point in the history
This endpoint aims to provide the foundation for phone books which relay
information about existing endpoints in Asterisk. It may be used
directly or through something like an LDAP adaptor to provide phonebook
services to hardphones.
  • Loading branch information
crazybolillo committed Oct 5, 2024
1 parent 4cbad26 commit f75224d
Show file tree
Hide file tree
Showing 7 changed files with 242 additions and 2 deletions.
6 changes: 4 additions & 2 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,12 @@ func serve(ctx context.Context) error {
endpoint := handler.Endpoint{Service: &service.EndpointService{Cursor: pool}}
r.Mount("/endpoints", endpoint.Router())

checker := &service.Bouncer{Cursor: pool}
authorization := handler.Authorization{Bouncer: checker}
authorization := handler.Authorization{Bouncer: &service.Bouncer{Cursor: pool}}
r.Mount("/bouncer", authorization.Router())

phonebook := handler.Contact{Service: &service.Contact{Cursor: pool}}
r.Mount("/contacts", phonebook.Router())

listen := os.Getenv("LISTEN_ADDR")
if listen == "" {
listen = ":8080"
Expand Down
41 changes: 41 additions & 0 deletions docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,24 @@ definitions:
destination:
type: string
type: object
model.Contact:
properties:
name:
type: string
phone:
type: string
type: object
model.ContactPage:
properties:
contacts:
items:
$ref: '#/definitions/model.Contact'
type: array
retrieved:
type: integer
total:
type: integer
type: object
model.Endpoint:
properties:
accountCode:
Expand Down Expand Up @@ -149,6 +167,29 @@ paths:
provide details on how
tags:
- bouncer
/contacts:
get:
parameters:
- default: 0
description: Zero based page to fetch
in: query
name: page
type: integer
- default: 20
description: Max amount of results to be returned
in: query
name: pageSize
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/model.ContactPage'
summary: List all contacts in the system.
tags:
- contacts
/endpoints:
get:
parameters:
Expand Down
62 changes: 62 additions & 0 deletions internal/handler/contact.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package handler

import (
"encoding/json"
"github.com/crazybolillo/eryth/internal/query"
"github.com/crazybolillo/eryth/internal/service"
"github.com/go-chi/chi/v5"
"log/slog"
"net/http"
)

type Contact struct {
Service *service.Contact
}

func (p *Contact) Router() chi.Router {
r := chi.NewRouter()
r.Get("/", p.list)

return r
}

// @Summary List all contacts in the system.
// @Param page query int false "Zero based page to fetch" default(0)
// @Param pageSize query int false "Max amount of results to be returned" default(20)
// @Produce json
// @Success 200 {object} model.ContactPage
// @Tags contacts
// @Router /contacts [get]
func (p *Contact) list(w http.ResponseWriter, r *http.Request) {
page, err := query.GetIntOr(r.URL.Query(), "page", 0)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}

pageSize, err := query.GetIntOr(r.URL.Query(), "pageSize", 10)
if err != nil || page < 0 || pageSize < 0 {
w.WriteHeader(http.StatusBadRequest)
return
}

res, err := p.Service.Paginate(r.Context(), page, pageSize)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
slog.Error("Failed to list contacts", slog.String("path", r.URL.Path), slog.String("reason", err.Error()))
return
}

content, err := json.Marshal(res)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
slog.Error("Failed to marshall response", slog.String("path", r.URL.Path), slog.String("reason", err.Error()))
return
}

w.Header().Set("Content-Type", "application/json")
_, err = w.Write(content)
if err != nil {
slog.Error("Failed to write response", slog.String("path", r.URL.Path), slog.String("reason", err.Error()))
}
}
12 changes: 12 additions & 0 deletions internal/model/contact.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package model

type Contact struct {
Name string `json:"name"`
Phone string `json:"phone"`
}

type ContactPage struct {
Total int64 `json:"total"`
Retrieved int `json:"retrieved"`
Contacts []Contact `json:"contacts"`
}
45 changes: 45 additions & 0 deletions internal/service/contact.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package service

import (
"context"
"errors"
"github.com/crazybolillo/eryth/internal/model"
"github.com/crazybolillo/eryth/internal/sqlc"
"github.com/jackc/pgx/v5"
)

type Contact struct {
Cursor
}

func (c *Contact) Paginate(ctx context.Context, page, size int) (model.ContactPage, error) {
queries := sqlc.New(c.Cursor)

rows, err := queries.ListContacts(ctx, sqlc.ListContactsParams{
Limit: int32(size),
Offset: int32(page),
})
if err != nil && !errors.Is(err, pgx.ErrNoRows) {
return model.ContactPage{}, err
}

count, err := queries.CountEndpoints(ctx)
if err != nil {
return model.ContactPage{}, err
}

contacts := make([]model.Contact, len(rows))
for idx, row := range rows {
contacts[idx] = model.Contact{
Name: displayNameFromClid(row.Callerid.String),
Phone: row.Extension.String,
}
}
res := model.ContactPage{
Total: count,
Retrieved: len(rows),
Contacts: contacts,
}

return res, nil
}
59 changes: 59 additions & 0 deletions internal/sqlc/queries.sql.go

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

19 changes: 19 additions & 0 deletions queries.sql
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,22 @@ SET
WHERE
id = $2;

-- name: ListContacts :many
SELECT
pe.callerid, ee.extension
FROM
ps_endpoints pe
INNER JOIN
ery_extension ee ON ee.endpoint_id = pe.sid
LIMIT
$1
OFFSET
$2;

-- name: CountContacts :one
SELECT
COUNT(*)
FROM
ps_endpoints pe
INNER JOIN
ery_extension ee ON ee.endpoint_id = pe.sid;

0 comments on commit f75224d

Please sign in to comment.