diff --git a/.github/workflows/qa.yaml b/.github/workflows/qa.yaml index 629739c..6544651 100644 --- a/.github/workflows/qa.yaml +++ b/.github/workflows/qa.yaml @@ -12,7 +12,7 @@ jobs: - uses: actions/setup-go@v5 with: go-version: '1.22' - - uses: golangci/golangci-lint-action@v5 + - uses: golangci/golangci-lint-action@v6 with: version: latest tests: diff --git a/cmd/main.go b/cmd/main.go index 01c885c..5be8fa2 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -81,5 +81,10 @@ func serve(ctx context.Context) error { r.Mount("/bouncer", authorization.Router()) slog.Info("Listening on :8080") - return http.ListenAndServe(":8080", r) + err = http.ListenAndServe(":8080", r) + if err != nil { + slog.Error("Failed to start server", "reason", err.Error()) + } + + return err } diff --git a/db/migrations/20240819195341_ery_extension_cascade.sql b/db/migrations/20240819195341_ery_extension_cascade.sql new file mode 100644 index 0000000..b5bface --- /dev/null +++ b/db/migrations/20240819195341_ery_extension_cascade.sql @@ -0,0 +1,14 @@ +-- migrate:up +ALTER TABLE ery_extension DROP CONSTRAINT ery_extension_endpoint_id_fkey; + +ALTER TABLE + ery_extension +ADD CONSTRAINT + ry_extension_endpoint_id_fkey +FOREIGN KEY + (endpoint_id) +REFERENCES + ps_endpoints(sid) +ON DELETE CASCADE; + +-- migrate:down diff --git a/docs/swagger.yaml b/docs/swagger.yaml index a25214a..7df916f 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -33,8 +33,27 @@ definitions: type: integer password: type: string - realm: + transport: + type: string + type: object + handler.getEndpointResponse: + properties: + codecs: + items: + type: string + type: array + context: + type: string + displayName: + type: string + extension: + type: string + id: type: string + maxContacts: + type: integer + sid: + type: integer transport: type: string type: object @@ -48,13 +67,38 @@ definitions: type: string id: type: string + sid: + type: integer type: object - handler.listEndpointsRequest: + handler.listEndpointsResponse: properties: endpoints: items: $ref: '#/definitions/handler.listEndpointEntry' type: array + retrieved: + type: integer + total: + type: integer + type: object + handler.updateEndpointRequest: + properties: + codecs: + items: + type: string + type: array + context: + type: string + displayName: + type: string + extension: + type: string + maxContacts: + type: integer + password: + type: string + transport: + type: string type: object host: localhost:8080 info: @@ -92,10 +136,15 @@ paths: /endpoints: get: parameters: - - default: 15 - description: Limit the amount of endpoints returned + - default: 0 + description: Zero based page to fetch in: query - name: limit + name: page + type: integer + - default: 10 + description: Max amount of results to be returned + in: query + name: pageSize type: integer produces: - application/json @@ -103,7 +152,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/handler.listEndpointsRequest' + $ref: '#/definitions/handler.listEndpointsResponse' "400": description: Bad Request "500": @@ -122,8 +171,10 @@ paths: schema: $ref: '#/definitions/handler.createEndpointRequest' responses: - "204": - description: No Content + "201": + description: Created + schema: + $ref: '#/definitions/handler.getEndpointResponse' "400": description: Bad Request "500": @@ -131,14 +182,14 @@ paths: summary: Create a new endpoint. tags: - endpoints - /endpoints/{id}: + /endpoints/{sid}: delete: parameters: - - description: ID of the endpoint to be deleted + - description: Sid of the endpoint to be deleted in: path - name: id + name: sid required: true - type: string + type: integer responses: "204": description: No Content @@ -149,4 +200,46 @@ paths: summary: Delete an endpoint and its associated resources. tags: - endpoints + get: + parameters: + - description: Requested endpoint's sid + in: path + name: sid + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.getEndpointResponse' + "400": + description: Bad Request + "500": + description: Internal Server Error + summary: Get information from a specific endpoint. + tags: + - endpoints + patch: + parameters: + - description: Sid of the endpoint to be updated + in: path + name: sid + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.updateEndpointRequest' + "400": + description: Bad Request + "404": + description: Not Found + "500": + description: Internal Server Error + summary: Update the specified endpoint. Omitted or null fields will remain unchanged. + tags: + - endpoints swagger: "2.0" diff --git a/internal/handler/endpoint.go b/internal/handler/endpoint.go index 42939d4..44b7089 100644 --- a/internal/handler/endpoint.go +++ b/internal/handler/endpoint.go @@ -4,8 +4,10 @@ import ( "crypto/md5" "encoding/hex" "encoding/json" + "errors" "fmt" "github.com/crazybolillo/eryth/internal/db" + "github.com/crazybolillo/eryth/internal/query" "github.com/crazybolillo/eryth/internal/sqlc" "github.com/go-chi/chi/v5" "github.com/jackc/pgx/v5" @@ -15,6 +17,8 @@ import ( "strings" ) +const defaultRealm = "asterisk" + type Endpoint struct { *pgx.Conn } @@ -22,7 +26,6 @@ type Endpoint struct { type createEndpointRequest struct { ID string `json:"id"` Password string `json:"password"` - Realm string `json:"realm,omitempty"` Transport string `json:"transport,omitempty"` Context string `json:"context"` Codecs []string `json:"codecs"` @@ -32,21 +35,47 @@ type createEndpointRequest struct { } type listEndpointEntry struct { + Sid int32 `json:"sid"` ID string `json:"id"` Extension string `json:"extension"` Context string `json:"context"` DisplayName string `json:"displayName"` } -type listEndpointsRequest struct { +type listEndpointsResponse struct { + Total int64 `json:"total"` + Retrieved int `json:"retrieved"` Endpoints []listEndpointEntry `json:"endpoints"` } +type getEndpointResponse struct { + Sid int32 `json:"sid"` + ID string `json:"id"` + DisplayName string `json:"displayName"` + Transport string `json:"transport"` + Context string `json:"context"` + Codecs []string `json:"codecs"` + MaxContacts int32 `json:"maxContacts"` + Extension string `json:"extension"` +} + +type updateEndpointRequest struct { + Password *string `json:"password,omitempty"` + DisplayName *string `json:"displayName,omitempty"` + Transport *string `json:"transport,omitempty"` + Context *string `json:"context,omitempty"` + Codecs []string `json:"codecs,omitempty"` + MaxContacts *int32 `json:"maxContacts,omitempty"` + Extension *string `json:"extension,omitempty"` +} + func (e *Endpoint) Router() chi.Router { r := chi.NewRouter() r.Post("/", e.create) r.Get("/", e.list) - r.Delete("/{id}", e.delete) + r.Get("/{sid}", e.get) + r.Delete("/{sid}", e.delete) + r.Patch("/{sid}", e.update) return r } @@ -72,28 +101,101 @@ func displayNameFromClid(callerID string) string { return callerID[1:end] } +func hashPassword(user, password, realm string) string { + hash := md5.Sum([]byte(user + ":" + realm + ":" + password)) + return hex.EncodeToString(hash[:]) +} + +// @Summary Get information from a specific endpoint. +// @Param sid path int true "Requested endpoint's sid" +// @Produce json +// @Success 200 {object} getEndpointResponse +// @Failure 400 +// @Failure 500 +// @Tags endpoints +// @Router /endpoints/{sid} [get] +func (e *Endpoint) get(w http.ResponseWriter, r *http.Request) { + sid := chi.URLParam(r, "sid") + if sid == "" { + w.WriteHeader(http.StatusBadRequest) + return + } + id, err := strconv.ParseInt(sid, 10, 32) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + tx, err := e.Begin(r.Context()) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + defer tx.Rollback(r.Context()) + + queries := sqlc.New(tx) + + row, err := queries.GetEndpointByID(r.Context(), int32(id)) + if errors.Is(err, pgx.ErrNoRows) { + w.WriteHeader(http.StatusNotFound) + return + } else if err != nil { + w.WriteHeader(http.StatusInternalServerError) + slog.Error("Failed to retrieve endpoint", slog.String("path", r.URL.Path), slog.String("reason", err.Error())) + return + } + + endpoint := getEndpointResponse{ + Sid: int32(id), + ID: row.ID, + Transport: row.Transport.String, + Context: row.Context.String, + Codecs: strings.Split(row.Allow.String, ","), + MaxContacts: row.MaxContacts.Int32, + Extension: row.Extension.String, + DisplayName: displayNameFromClid(row.Callerid.String), + } + content, err := json.Marshal(endpoint) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + slog.Error("Failed to marshall response", slog.String("path", r.URL.Path)) + 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())) + } +} + // @Summary List existing endpoints. -// @Param limit query int false "Limit the amount of endpoints returned" default(15) +// @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(10) // @Produce json -// @Success 200 {object} listEndpointsRequest +// @Success 200 {object} listEndpointsResponse // @Failure 400 // @Failure 500 // @Tags endpoints // @Router /endpoints [get] func (e *Endpoint) list(w http.ResponseWriter, r *http.Request) { - qlim := r.URL.Query().Get("limit") - limit := 15 - if qlim != "" { - conv, err := strconv.Atoi(qlim) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - return - } - limit = conv + 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 } queries := sqlc.New(e.Conn) - rows, err := queries.ListEndpoints(r.Context(), int32(limit)) + rows, err := queries.ListEndpoints(r.Context(), sqlc.ListEndpointsParams{ + Limit: int32(pageSize), + Offset: int32(page * pageSize), + }) if err != nil { slog.Error("Query execution failed", slog.String("path", r.URL.Path), slog.String("msg", err.Error())) w.WriteHeader(http.StatusInternalServerError) @@ -102,18 +204,27 @@ func (e *Endpoint) list(w http.ResponseWriter, r *http.Request) { if rows == nil { rows = []sqlc.ListEndpointsRow{} } + total, err := queries.CountEndpoints(r.Context()) + if err != nil { + slog.Error("Query execution failed", slog.String("path", r.URL.Path), slog.String("msg", err.Error())) + w.WriteHeader(http.StatusInternalServerError) + return + } endpoints := make([]listEndpointEntry, len(rows)) for idx := range len(rows) { row := rows[idx] endpoints[idx] = listEndpointEntry{ + Sid: row.Sid, ID: row.ID, Extension: row.Extension.String, Context: row.Context.String, DisplayName: displayNameFromClid(row.Callerid.String), } } - response := listEndpointsRequest{ + response := listEndpointsResponse{ + Total: total, + Retrieved: len(rows), Endpoints: endpoints, } content, err := json.Marshal(response) @@ -133,7 +244,7 @@ func (e *Endpoint) list(w http.ResponseWriter, r *http.Request) { // @Summary Create a new endpoint. // @Accept json // @Param payload body createEndpointRequest true "Endpoint's information" -// @Success 204 +// @Success 201 {object} getEndpointResponse // @Failure 400 // @Failure 500 // @Tags endpoints @@ -141,7 +252,6 @@ func (e *Endpoint) list(w http.ResponseWriter, r *http.Request) { func (e *Endpoint) create(w http.ResponseWriter, r *http.Request) { decoder := json.NewDecoder(r.Body) payload := createEndpointRequest{ - Realm: "asterisk", MaxContacts: 1, } @@ -160,12 +270,11 @@ func (e *Endpoint) create(w http.ResponseWriter, r *http.Request) { queries := sqlc.New(tx) - hash := md5.Sum([]byte(payload.ID + ":" + payload.Realm + ":" + payload.Password)) err = queries.NewMD5Auth(r.Context(), sqlc.NewMD5AuthParams{ ID: payload.ID, Username: db.Text(payload.ID), - Realm: db.Text(payload.Realm), - Md5Cred: db.Text(hex.EncodeToString(hash[:])), + Realm: db.Text(defaultRealm), + Md5Cred: db.Text(hashPassword(payload.ID, payload.Password, defaultRealm)), }) if err != nil { w.WriteHeader(http.StatusInternalServerError) @@ -210,19 +319,60 @@ func (e *Endpoint) create(w http.ResponseWriter, r *http.Request) { return } - w.WriteHeader(http.StatusNoContent) + // TODO: Duplicate code, same as when fetching endpoint. Probably should put this into a service layer. + tx, err = e.Begin(r.Context()) + queries = sqlc.New(tx) + if err != nil { + slog.Error("Failed to create new transaction", slog.String("path", r.URL.Path), slog.String("reason", err.Error())) + w.WriteHeader(http.StatusInternalServerError) + return + } + res, err := queries.GetEndpointByID(r.Context(), sid) + if err != nil { + slog.Error( + "Failed to retrieve created endpoint", + slog.String("path", r.URL.Path), slog.String("reason", err.Error()), slog.Int("sid", int(sid)), + ) + w.WriteHeader(http.StatusInternalServerError) + return + } + + endpoint := getEndpointResponse{ + Sid: sid, + ID: res.ID, + Transport: res.Transport.String, + Context: res.Context.String, + Codecs: strings.Split(res.Allow.String, ","), + MaxContacts: res.MaxContacts.Int32, + Extension: res.Extension.String, + DisplayName: displayNameFromClid(res.Callerid.String), + } + content, err := json.Marshal(endpoint) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + slog.Error("Failed to marshall response", slog.String("path", r.URL.Path)) + 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())) + } + w.WriteHeader(http.StatusCreated) } // @Summary Delete an endpoint and its associated resources. -// @Param id path string true "ID of the endpoint to be deleted" +// @Param sid path int true "Sid of the endpoint to be deleted" // @Success 204 // @Failure 400 // @Failure 500 // @Tags endpoints -// @Router /endpoints/{id} [delete] +// @Router /endpoints/{sid} [delete] func (e *Endpoint) delete(w http.ResponseWriter, r *http.Request) { - id := chi.URLParam(r, "id") - if id == "" { + urlSid := chi.URLParam(r, "sid") + sid, err := strconv.Atoi(urlSid) + if err != nil || sid <= 0 { w.WriteHeader(http.StatusBadRequest) return } @@ -236,7 +386,7 @@ func (e *Endpoint) delete(w http.ResponseWriter, r *http.Request) { queries := sqlc.New(tx) - err = queries.DeleteEndpoint(r.Context(), id) + id, err := queries.DeleteEndpoint(r.Context(), int32(sid)) if err != nil { w.WriteHeader(http.StatusInternalServerError) return @@ -262,3 +412,179 @@ func (e *Endpoint) delete(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) } + +// @Summary Update the specified endpoint. Omitted or null fields will remain unchanged. +// @Param sid path int true "Sid of the endpoint to be updated" +// @Success 200 {object} updateEndpointRequest +// @Failure 400 +// @Failure 404 +// @Failure 500 +// @Tags endpoints +// @Router /endpoints/{sid} [patch] +func (e *Endpoint) update(w http.ResponseWriter, r *http.Request) { + decoder := json.NewDecoder(r.Body) + var payload updateEndpointRequest + + err := decoder.Decode(&payload) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + urlSid := chi.URLParam(r, "sid") + sid, err := strconv.Atoi(urlSid) + if err != nil || sid <= 0 { + w.WriteHeader(http.StatusBadRequest) + return + } + + tx, err := e.Begin(r.Context()) + if err != nil { + slog.Error("Failed to start transaction", slog.String("reason", err.Error()), slog.String("path", r.URL.Path)) + w.WriteHeader(http.StatusInternalServerError) + return + } + + queries := sqlc.New(tx) + endpoint, err := queries.GetEndpointByID(r.Context(), int32(sid)) + if errors.Is(err, pgx.ErrNoRows) { + w.WriteHeader(http.StatusNotFound) + return + } else if err != nil { + w.WriteHeader(http.StatusInternalServerError) + slog.Error("Failed to retrieve endpoint", slog.String("path", r.URL.Path), slog.String("reason", err.Error())) + return + } + + // Sorry for the incoming boilerplate but no dynamic SQL yet + var patchedEndpoint = sqlc.UpdateEndpointBySidParams{Sid: int32(sid)} + if payload.DisplayName != nil { + if *payload.DisplayName == "" { + patchedEndpoint.Callerid = db.Text("") + } else { + patchedEndpoint.Callerid = db.Text(fmt.Sprintf(`"%s" <%s>`, *payload.DisplayName, endpoint.ID)) + } + } else { + patchedEndpoint.Callerid = endpoint.Callerid + } + if payload.Context != nil { + patchedEndpoint.Context = db.Text(*payload.Context) + } else { + patchedEndpoint.Context = endpoint.Context + } + if payload.Transport != nil { + patchedEndpoint.Transport = db.Text(*payload.Transport) + } else { + patchedEndpoint.Transport = endpoint.Transport + } + if payload.Codecs != nil { + patchedEndpoint.Allow = db.Text(strings.Join(payload.Codecs, ",")) + } else { + patchedEndpoint.Allow = endpoint.Allow + } + err = queries.UpdateEndpointBySid(r.Context(), patchedEndpoint) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + slog.Error("Failed to update endpoint", slog.String("path", r.URL.Path), slog.String("reason", err.Error())) + return + } + + if payload.MaxContacts != nil { + err = queries.UpdateAORById( + r.Context(), + sqlc.UpdateAORByIdParams{ + ID: endpoint.ID, + MaxContacts: db.Int4(*payload.MaxContacts), + }, + ) + } + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + slog.Error("Failed to update AOR", slog.String("path", r.URL.Path), slog.String("reason", err.Error())) + return + } + + if payload.Extension != nil { + err = queries.UpdateExtensionByEndpointId( + r.Context(), + sqlc.UpdateExtensionByEndpointIdParams{ + EndpointID: int32(sid), + Extension: db.Text(*payload.Extension), + }, + ) + } + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + slog.Error("Failed to update extension", slog.String("path", r.URL.Path), slog.String("reason", err.Error())) + return + } + + if payload.Password != nil { + if len(*payload.Password) < 12 { + w.WriteHeader(http.StatusBadRequest) + slog.Info("Invalid password provided", slog.String("path", r.URL.Path)) + return + } + err = queries.UpdateMD5AuthById( + r.Context(), + sqlc.UpdateMD5AuthByIdParams{ + ID: endpoint.ID, + Md5Cred: db.Text(hashPassword(endpoint.ID, *payload.Password, defaultRealm)), + }, + ) + } + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + slog.Error("Failed to update password", slog.String("path", r.URL.Path), slog.String("reason", err.Error())) + return + } + + err = tx.Commit(r.Context()) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + slog.Error("Failed to commit update", slog.String("path", r.URL.Path), slog.String("reason", err.Error())) + return + } + + // TODO: Duplicate code, same as when fetching endpoint. Probably should put this into a service layer. + tx, err = e.Begin(r.Context()) + queries = sqlc.New(tx) + if err != nil { + slog.Error("Failed to create new transaction", slog.String("path", r.URL.Path), slog.String("reason", err.Error())) + w.WriteHeader(http.StatusInternalServerError) + return + } + res, err := queries.GetEndpointByID(r.Context(), int32(sid)) + if err != nil { + slog.Error( + "Failed to retrieve created endpoint", + slog.String("path", r.URL.Path), slog.String("reason", err.Error()), slog.Int("sid", int(sid)), + ) + w.WriteHeader(http.StatusInternalServerError) + return + } + + result := getEndpointResponse{ + Sid: int32(sid), + ID: res.ID, + Transport: res.Transport.String, + Context: res.Context.String, + Codecs: strings.Split(res.Allow.String, ","), + MaxContacts: res.MaxContacts.Int32, + Extension: res.Extension.String, + DisplayName: displayNameFromClid(res.Callerid.String), + } + content, err := json.Marshal(result) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + slog.Error("Failed to marshall response", slog.String("path", r.URL.Path)) + 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())) + } + w.WriteHeader(http.StatusOK) +} diff --git a/internal/query/query.go b/internal/query/query.go new file mode 100644 index 0000000..1897918 --- /dev/null +++ b/internal/query/query.go @@ -0,0 +1,20 @@ +package query + +import ( + "net/url" + "strconv" +) + +func GetIntOr(values url.Values, name string, defaultValue int) (int, error) { + value := values.Get(name) + if value == "" { + return defaultValue, nil + } + + intValue, err := strconv.Atoi(value) + if err != nil { + return defaultValue, err + } + + return intValue, nil +} diff --git a/internal/sqlc/queries.sql.go b/internal/sqlc/queries.sql.go index 697dd44..b0750f5 100644 --- a/internal/sqlc/queries.sql.go +++ b/internal/sqlc/queries.sql.go @@ -11,6 +11,17 @@ import ( "github.com/jackc/pgx/v5/pgtype" ) +const countEndpoints = `-- name: CountEndpoints :one +SELECT COUNT(*) FROM ps_endpoints +` + +func (q *Queries) CountEndpoints(ctx context.Context) (int64, error) { + row := q.db.QueryRow(ctx, countEndpoints) + var count int64 + err := row.Scan(&count) + return count, err +} + const deleteAOR = `-- name: DeleteAOR :exec DELETE FROM ps_aors WHERE id = $1 ` @@ -29,13 +40,15 @@ func (q *Queries) DeleteAuth(ctx context.Context, id string) error { return err } -const deleteEndpoint = `-- name: DeleteEndpoint :exec -DELETE FROM ps_endpoints WHERE id = $1 +const deleteEndpoint = `-- name: DeleteEndpoint :one +DELETE FROM ps_endpoints WHERE sid = $1 RETURNING id ` -func (q *Queries) DeleteEndpoint(ctx context.Context, id string) error { - _, err := q.db.Exec(ctx, deleteEndpoint, id) - return err +func (q *Queries) DeleteEndpoint(ctx context.Context, sid int32) (string, error) { + row := q.db.QueryRow(ctx, deleteEndpoint, sid) + var id string + err := row.Scan(&id) + return id, err } const getEndpointByExtension = `-- name: GetEndpointByExtension :one @@ -68,26 +81,70 @@ func (q *Queries) GetEndpointByExtension(ctx context.Context, arg GetEndpointByE return i, err } +const getEndpointByID = `-- name: GetEndpointByID :one +SELECT + pe.id, pe.callerid, pe.context, ee.extension, pe.transport, aor.max_contacts, pe.allow +FROM + ps_endpoints pe +INNER JOIN + ery_extension ee ON ee.endpoint_id = pe.sid +INNER JOIN + ps_aors aor ON aor.id = pe.id +WHERE + pe.sid = $1 +` + +type GetEndpointByIDRow struct { + ID string `json:"id"` + Callerid pgtype.Text `json:"callerid"` + Context pgtype.Text `json:"context"` + Extension pgtype.Text `json:"extension"` + Transport pgtype.Text `json:"transport"` + MaxContacts pgtype.Int4 `json:"max_contacts"` + Allow pgtype.Text `json:"allow"` +} + +func (q *Queries) GetEndpointByID(ctx context.Context, sid int32) (GetEndpointByIDRow, error) { + row := q.db.QueryRow(ctx, getEndpointByID, sid) + var i GetEndpointByIDRow + err := row.Scan( + &i.ID, + &i.Callerid, + &i.Context, + &i.Extension, + &i.Transport, + &i.MaxContacts, + &i.Allow, + ) + return i, err +} + const listEndpoints = `-- name: ListEndpoints :many SELECT - pe.id, pe.callerid, pe.context, ee.extension + pe.sid, pe.id, pe.callerid, pe.context, ee.extension FROM ps_endpoints pe LEFT JOIN ery_extension ee ON ee.endpoint_id = pe.sid -LIMIT $1 +LIMIT $1 OFFSET $2 ` +type ListEndpointsParams struct { + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` +} + type ListEndpointsRow struct { + Sid int32 `json:"sid"` ID string `json:"id"` Callerid pgtype.Text `json:"callerid"` Context pgtype.Text `json:"context"` Extension pgtype.Text `json:"extension"` } -func (q *Queries) ListEndpoints(ctx context.Context, limit int32) ([]ListEndpointsRow, error) { - rows, err := q.db.Query(ctx, listEndpoints, limit) +func (q *Queries) ListEndpoints(ctx context.Context, arg ListEndpointsParams) ([]ListEndpointsRow, error) { + rows, err := q.db.Query(ctx, listEndpoints, arg.Limit, arg.Offset) if err != nil { return nil, err } @@ -96,6 +153,7 @@ func (q *Queries) ListEndpoints(ctx context.Context, limit int32) ([]ListEndpoin for rows.Next() { var i ListEndpointsRow if err := rows.Scan( + &i.Sid, &i.ID, &i.Callerid, &i.Context, @@ -197,3 +255,91 @@ func (q *Queries) NewMD5Auth(ctx context.Context, arg NewMD5AuthParams) error { ) return err } + +const updateAORById = `-- name: UpdateAORById :exec +UPDATE + ps_aors +SET + max_contacts = $1 +WHERE + id = $2 +` + +type UpdateAORByIdParams struct { + MaxContacts pgtype.Int4 `json:"max_contacts"` + ID string `json:"id"` +} + +func (q *Queries) UpdateAORById(ctx context.Context, arg UpdateAORByIdParams) error { + _, err := q.db.Exec(ctx, updateAORById, arg.MaxContacts, arg.ID) + return err +} + +const updateEndpointBySid = `-- name: UpdateEndpointBySid :exec +UPDATE + ps_endpoints +SET + callerid = $1, + context = $2, + transport = $3, + allow = $4 +WHERE + sid = $5 +` + +type UpdateEndpointBySidParams struct { + Callerid pgtype.Text `json:"callerid"` + Context pgtype.Text `json:"context"` + Transport pgtype.Text `json:"transport"` + Allow pgtype.Text `json:"allow"` + Sid int32 `json:"sid"` +} + +func (q *Queries) UpdateEndpointBySid(ctx context.Context, arg UpdateEndpointBySidParams) error { + _, err := q.db.Exec(ctx, updateEndpointBySid, + arg.Callerid, + arg.Context, + arg.Transport, + arg.Allow, + arg.Sid, + ) + return err +} + +const updateExtensionByEndpointId = `-- name: UpdateExtensionByEndpointId :exec +UPDATE + ery_extension +SET + extension = $1 +WHERE + endpoint_id = $2 +` + +type UpdateExtensionByEndpointIdParams struct { + Extension pgtype.Text `json:"extension"` + EndpointID int32 `json:"endpoint_id"` +} + +func (q *Queries) UpdateExtensionByEndpointId(ctx context.Context, arg UpdateExtensionByEndpointIdParams) error { + _, err := q.db.Exec(ctx, updateExtensionByEndpointId, arg.Extension, arg.EndpointID) + return err +} + +const updateMD5AuthById = `-- name: UpdateMD5AuthById :exec +UPDATE + ps_auths +SET + md5_cred = $1 +WHERE + id = $2 +` + +type UpdateMD5AuthByIdParams struct { + Md5Cred pgtype.Text `json:"md5_cred"` + ID string `json:"id"` +} + +func (q *Queries) UpdateMD5AuthById(ctx context.Context, arg UpdateMD5AuthByIdParams) error { + _, err := q.db.Exec(ctx, updateMD5AuthById, arg.Md5Cred, arg.ID) + return err +} diff --git a/queries.sql b/queries.sql index 387d23a..b76dbc6 100644 --- a/queries.sql +++ b/queries.sql @@ -17,8 +17,8 @@ VALUES ($1, $2, $1, $1, $3, 'all', $4, $5) RETURNING sid; --- name: DeleteEndpoint :exec -DELETE FROM ps_endpoints WHERE id = $1; +-- name: DeleteEndpoint :one +DELETE FROM ps_endpoints WHERE sid = $1 RETURNING id; -- name: DeleteAOR :exec DELETE FROM ps_aors WHERE id = $1; @@ -28,13 +28,13 @@ DELETE FROM ps_auths WHERE id = $1; -- name: ListEndpoints :many SELECT - pe.id, pe.callerid, pe.context, ee.extension + pe.sid, pe.id, pe.callerid, pe.context, ee.extension FROM ps_endpoints pe LEFT JOIN ery_extension ee ON ee.endpoint_id = pe.sid -LIMIT $1; +LIMIT $1 OFFSET $2; -- name: NewExtension :exec INSERT INTO ery_extension @@ -53,3 +53,54 @@ INNER JOIN ps_endpoints src ON src.id = $1 WHERE ee.extension = $2; + +-- name: GetEndpointByID :one +SELECT + pe.id, pe.callerid, pe.context, ee.extension, pe.transport, aor.max_contacts, pe.allow +FROM + ps_endpoints pe +INNER JOIN + ery_extension ee ON ee.endpoint_id = pe.sid +INNER JOIN + ps_aors aor ON aor.id = pe.id +WHERE + pe.sid = $1; + +-- name: CountEndpoints :one +SELECT COUNT(*) FROM ps_endpoints; + +-- name: UpdateEndpointBySid :exec +UPDATE + ps_endpoints +SET + callerid = $1, + context = $2, + transport = $3, + allow = $4 +WHERE + sid = $5; + +-- name: UpdateExtensionByEndpointId :exec +UPDATE + ery_extension +SET + extension = $1 +WHERE + endpoint_id = $2; + +-- name: UpdateAORById :exec +UPDATE + ps_aors +SET + max_contacts = $1 +WHERE + id = $2; + +-- name: UpdateMD5AuthById :exec +UPDATE + ps_auths +SET + md5_cred = $1 +WHERE + id = $2; +