Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: event types localization #13

Merged
merged 3 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,20 @@ listener:
event_types:
types:
- name: passport_scan
title: Points for passport scan
reward: 5
# there are default and localized configurations of texts
title: Points for passport scan
description: Get points for scan passport and share data
short_description: Short description
localized:
en-UK:
title: Points for passport scan
description: Get points for scan passport and share data
short_description: Short description
en-US:
title: Points for passport scan
description: Get points for scan passport and share data
short_description: Short description
frequency: one-time
action_url: https://...
logo: https://...
Expand Down
7 changes: 7 additions & 0 deletions docs/spec/components/parameters/headerLang.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
in: header
name: Accept-Language
description: Localization of event types. If locale is not configured for the type, the default is returned.
required: false
schema:
type: string
example: en-US
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ get:
for each event type in the system.
operationId: getEventTypes
parameters:
- $ref: '#/components/parameters/headerLang'
- in: query
name: 'filter[name]'
description: Filter by type name. Possible values should be hard-coded in the client.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ get:
summary: Get event type
description: Returns public configuration of event type by its unique name
operationId: getEventType
parameters:
- $ref: '#/components/parameters/headerLang'
- in: path
name: 'name'
required: true
schema:
type: string
example: "meetup_participation"
responses:
200:
description: Success
Expand All @@ -23,7 +31,7 @@ get:
500:
$ref: '#/components/responses/internalError'

patch:
put:
tags:
- Event types
summary: Update event type
Expand All @@ -34,6 +42,14 @@ patch:
in Go, because differentiating between `{}` and `{"field": null}`
requires custom unmarshalling implementation.
operationId: updateEventType
parameters:
- $ref: '#/components/parameters/headerLang'
- in: path
name: 'name'
required: true
schema:
type: string
example: "meetup_participation"
requestBody:
required: true
content:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ get:
operationId: getEvents
parameters:
- $ref: '#/components/parameters/filterNullifier'
- $ref: '#/components/parameters/headerLang'
- in: query
name: 'filter[status]'
description: |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ get:
summary: Get event
description: Returns a single event by ID.
operationId: getEvent
parameters:
- $ref: '#/components/parameters/headerLang'
- in: path
name: 'id'
required: true
schema:
type: string
example: "059c81dd-2a54-44a8-8142-c15ad8f88949"
responses:
200:
description: Success
Expand Down Expand Up @@ -34,6 +42,7 @@ patch:
User must be authorized, and event must be _fulfilled_ by him.
operationId: claimEvent
parameters:
- $ref: '#/components/parameters/headerLang'
- in: path
name: 'id'
required: true
Expand Down
58 changes: 40 additions & 18 deletions internal/data/evtypes/models/event_type.go
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
package models

import (
"strings"
"time"

"github.com/rarimo/geo-points-svc/resources"
)

type EventType struct {
Name string `fig:"name,required" db:"name"`
Description string `fig:"description,required" db:"description"`
ShortDescription string `fig:"short_description,required" db:"short_description"`
Reward int64 `fig:"reward,required" db:"reward"`
Title string `fig:"title,required" db:"title"`
Frequency Frequency `fig:"frequency,required" db:"frequency"`
StartsAt *time.Time `fig:"starts_at" db:"starts_at"`
ExpiresAt *time.Time `fig:"expires_at" db:"expires_at"`
NoAutoOpen bool `fig:"no_auto_open" db:"no_auto_open"`
AutoClaim bool `fig:"auto_claim" db:"auto_claim"`
Disabled bool `fig:"disabled" db:"disabled"`
ActionURL *string `fig:"action_url" db:"action_url"`
Logo *string `fig:"logo" db:"logo"`
QRCodeValue *string `fig:"qr_code_value" db:"qr_code_value"`
Name string `fig:"name,required" db:"name"`
Reward int64 `fig:"reward,required" db:"reward"`
Title string `fig:"title,required" db:"title"`
Description string `fig:"description,required" db:"description"`
ShortDescription string `fig:"short_description,required" db:"short_description"`
Localized LocalizationMap `fig:"localized" db:"localized"`
Frequency Frequency `fig:"frequency,required" db:"frequency"`
StartsAt *time.Time `fig:"starts_at" db:"starts_at"`
ExpiresAt *time.Time `fig:"expires_at" db:"expires_at"`
NoAutoOpen bool `fig:"no_auto_open" db:"no_auto_open"`
AutoClaim bool `fig:"auto_claim" db:"auto_claim"`
Disabled bool `fig:"disabled" db:"disabled"`
ActionURL *string `fig:"action_url" db:"action_url"`
Logo *string `fig:"logo" db:"logo"`
QRCodeValue *string `fig:"qr_code_value" db:"qr_code_value"`
}

func ResourceToModel(r resources.EventStaticMeta) EventType {
Expand Down Expand Up @@ -56,13 +58,14 @@ func (e EventType) Flag() string {
}
}

func (e EventType) Resource() resources.EventStaticMeta {
func (e EventType) Resource(locale string) resources.EventStaticMeta {
l := e.GetLocalized(strings.ToLower(locale))
return resources.EventStaticMeta{
Name: e.Name,
Description: e.Description,
ShortDescription: e.ShortDescription,
Reward: e.Reward,
Title: e.Title,
Description: l.Description,
ShortDescription: l.ShortDescription,
Title: l.Title,
Frequency: e.Frequency.String(),
StartsAt: e.StartsAt,
ExpiresAt: e.ExpiresAt,
Expand Down Expand Up @@ -90,3 +93,22 @@ func (e EventType) ForUpdate() map[string]any {
"qr_code_value": e.QRCodeValue,
}
}

func (e EventType) GetLocalized(locale string) Localized {
def := Localized{
Title: e.Title,
Description: e.Description,
ShortDescription: e.ShortDescription,
}

if len(e.Localized) == 0 {
return def
}

v, ok := e.Localized[locale]
if !ok {
return def
}

return v
}
47 changes: 47 additions & 0 deletions internal/data/evtypes/models/localized.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package models

import (
"database/sql/driver"
"encoding/json"
"fmt"

"gitlab.com/distributed_lab/kit/pgdb"
)

// LocalizationMap maps 2-letter locales to Localized data and implements
// interfaces to work with DB
type LocalizationMap map[string]Localized

type Localized struct {
Title string `fig:"title,required" json:"title"`
Description string `fig:"description,required" json:"description"`
ShortDescription string `fig:"short_description,required" json:"short_description"`
}

func (l *LocalizationMap) Value() (driver.Value, error) {
if l == nil || len(*l) == 0 {
return nil, nil
}
return pgdb.JSONValue(l)
}

func (l *LocalizationMap) Scan(src interface{}) error {
var data []byte
switch rawData := src.(type) {
case []byte:
data = rawData
case string:
data = []byte(rawData)
case nil:
return nil
default:
return fmt.Errorf("unexpected type for jsonb: %T", src)
}

err := json.Unmarshal(data, l)
if err != nil {
return fmt.Errorf("failed to unmarshal: %w", err)
}

return nil
}
4 changes: 3 additions & 1 deletion internal/service/handlers/claim_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (
"gitlab.com/distributed_lab/ape/problems"
)

const langHeader = "Accept-Language"

func ClaimEvent(w http.ResponseWriter, r *http.Request) {
req, err := requests.NewClaimEvent(r)
if err != nil {
Expand Down Expand Up @@ -82,7 +84,7 @@ func ClaimEvent(w http.ResponseWriter, r *http.Request) {
return
}

ape.Render(w, newClaimEventResponse(*event, evType.Resource(), *balance))
ape.Render(w, newClaimEventResponse(*event, evType.Resource(r.Header.Get(langHeader)), *balance))
}

// claimEvent requires event to exist
Expand Down
2 changes: 1 addition & 1 deletion internal/service/handlers/get_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,5 @@ func GetEvent(w http.ResponseWriter, r *http.Request) {
return
}

ape.Render(w, resources.EventResponse{Data: newEventModel(*event, evType.Resource())})
ape.Render(w, resources.EventResponse{Data: newEventModel(*event, evType.Resource(r.Header.Get(langHeader)))})
}
2 changes: 1 addition & 1 deletion internal/service/handlers/get_event_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ func GetEventType(w http.ResponseWriter, r *http.Request) {
return
}

ape.Render(w, newEventTypeResponse(*evType))
ape.Render(w, newEventTypeResponse(*evType, r.Header.Get(langHeader)))
}
2 changes: 1 addition & 1 deletion internal/service/handlers/list_event_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func ListEventTypes(w http.ResponseWriter, r *http.Request) {
ID: t.Name,
Type: resources.EVENT_TYPE,
},
Attributes: t.Resource(),
Attributes: t.Resource(r.Header.Get(langHeader)),
}
}

Expand Down
2 changes: 1 addition & 1 deletion internal/service/handlers/list_events.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ func getOrderedEventsMeta(events []data.Event, r *http.Request) ([]resources.Eve
if evType == nil {
return nil, errors.New("wrong event type is stored in DB: might be bad event config")
}
res[i] = evType.Resource()
res[i] = evType.Resource(r.Header.Get(langHeader))
}

return res, nil
Expand Down
6 changes: 3 additions & 3 deletions internal/service/handlers/update_event_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,19 +53,19 @@ func UpdateEventType(w http.ResponseWriter, r *http.Request) {
}

EventTypes(r).Push(typeModel)
resp := newEventTypeResponse(res[0])
resp := newEventTypeResponse(res[0], r.Header.Get(langHeader))
resp.Data.Attributes.QrCodeValue = typeModel.QRCodeValue
ape.Render(w, resp)
}

func newEventTypeResponse(evType models.EventType) resources.EventTypeResponse {
func newEventTypeResponse(evType models.EventType, locale string) resources.EventTypeResponse {
return resources.EventTypeResponse{
Data: resources.EventType{
Key: resources.Key{
ID: evType.Name,
Type: resources.EVENT_TYPE,
},
Attributes: evType.Resource(),
Attributes: evType.Resource(locale),
},
}
}
3 changes: 2 additions & 1 deletion internal/service/handlers/verify_passport.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,8 @@ func getAndVerifyBalanceEligibility(
}

// never panics because of request validation
proof.PubSignals[zk.Nullifier] = mustHexToInt(nullifier)
ni := zk.Indexes(zk.GeorgianPassport)[zk.Nullifier]
proof.PubSignals[ni] = mustHexToInt(nullifier)
err = Verifier(r).VerifyProof(*proof)
if err != nil {
if errors.Is(err, identity.ErrContractCall) {
Expand Down
2 changes: 1 addition & 1 deletion internal/service/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func Run(ctx context.Context, cfg config.Config) {
r.Get("/", handlers.ListEventTypes)
r.With(authMW).Post("/", handlers.CreateEventType)
r.Get("/{name}", handlers.GetEventType)
r.With(authMW).Patch("/{name}", handlers.UpdateEventType)
r.With(authMW).Put("/{name}", handlers.UpdateEventType)
})
})
// must be accessible only within the cluster
Expand Down
Loading