diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 12a640a..42785c7 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -11,8 +11,8 @@ on:
- "**.md"
env:
- GO_VERSION: "1.22.5" # https://go.dev/dl/
- GOLANGCI_LINT_VERSION: "v1.59.1" # https://github.com/golangci/golangci-lint/releases
+ GO_VERSION: "1.23.4" # https://go.dev/dl/
+ GOLANGCI_LINT_VERSION: "v1.62.2" # https://github.com/golangci/golangci-lint/releases
jobs:
lint:
@@ -27,7 +27,7 @@ jobs:
with:
version: ${{ env.GOLANGCI_LINT_VERSION }}
skip-cache: true
- working-directory: ./ios-engineer/server
+ working-directory: ./client-challenge/server
security:
runs-on: ubuntu-latest
@@ -35,7 +35,7 @@ jobs:
- uses: golang/govulncheck-action@v1
with:
go-version-input: ${{ env.GO_VERSION }}
- work-dir: ./ios-engineer/server
+ work-dir: ./client-challenge/server
build:
runs-on: ubuntu-latest
@@ -45,5 +45,5 @@ jobs:
with:
go-version: ${{ env.GO_VERSION }}
- name: Build go code
- working-directory: ./ios-engineer/server
+ working-directory: ./client-challenge/server
run: go build
diff --git a/backend-engineer/readme.md b/backend-challenge/readme.md
similarity index 88%
rename from backend-engineer/readme.md
rename to backend-challenge/readme.md
index ae75a3b..7c33a65 100644
--- a/backend-engineer/readme.md
+++ b/backend-challenge/readme.md
@@ -1,7 +1,7 @@
# Backend Challenge
-This challenge is part of the backend hiring process at [Heart
-Hands](https://hearthands.tech/).
+_This challenge is part of the engineering hiring process at [Heart
+Hands](https://hearthands.tech/)._
## Why this challenge?
@@ -11,8 +11,9 @@ understanding of their technical stack, and excellent product intuitions to join
our team.
This exercise has been designed to give a glimpse of what it is like to build a
-messaging app, and the kind of technical challenges we face and care about. We
-are expecting you to spend between 4 and 6 hours on this challenge.
+messaging app, and the kind of technical challenges we face and care about.
+
+We expect you to spend 4-6 hours on this challenge, simulating real-world, time-boxed work.
## Instructions
@@ -50,7 +51,7 @@ will have to prioritize what you work on. A few things that are important for us
and that will be considered during the review:
- **documentation**: is the readme clear? are important parts of the code
documented?
-- **impact**: which features did you prioritize?
+- **impact**: what did you consciouslly decided to prioritize?
- **maintainability**: is the code well-structured and easy to read/evolve?
- **robustness**: is the code tested or easily testable? are edge-cases
considered? is static analysis leveraged?
diff --git a/ios-engineer/design.png b/client-challenge/design.png
similarity index 100%
rename from ios-engineer/design.png
rename to client-challenge/design.png
diff --git a/ios-engineer/readme.md b/client-challenge/readme.md
similarity index 70%
rename from ios-engineer/readme.md
rename to client-challenge/readme.md
index 23f92f0..b98e60d 100644
--- a/ios-engineer/readme.md
+++ b/client-challenge/readme.md
@@ -1,7 +1,7 @@
-# iOS Challenge
+# Client Challenge
-This challenge is part of the iOS hiring process at [Heart
-Hands](https://hearthands.tech/).
+_This challenge is part of the engineering hiring process at [Heart
+Hands](https://hearthands.tech/)._
## Why this challenge?
@@ -11,19 +11,24 @@ understanding of their technical stack, and excellent product intuitions to join
our team.
This exercise has been designed to give a glimpse of what it is like to build a
-messaging app, and the kind of technical challenges we face and care about. We
-are expecting you to spend between 4 and 6 hours on this challenge.
+messaging app, and the kind of technical challenges we face and care about.
+
+We expect you to spend 4-6 hours on this challenge, simulating real-world,
+time-boxed work.
## Instructions
-You are tasked with the implementation of an iOS messaging app that allows the user to
-communicate (send and receive text messages) with bots, each in their own 1:1
+You are tasked with the implementation of a messaging app that allows the user
+to communicate (send and receive text messages) with bots, each in their own 1:1
chat.
-A server is available for you to use. You can read more about it in
+A companion server is available for you to use. You can read more about it in
[`./server`](./server). Its documentation contains information on how it can be
run, and what kinds of API endpoints & entities are available.
+You can choose a target platform of your choice for this challenge: iOS, macOS,
+or web.
+
Functional requirements:
- [ ] The app should start on a screen showing the list of all chats
@@ -36,14 +41,14 @@ Functional requirements:
Some topics that we find interesting to dig:
-- [ ] Make the app work offline (both for app state and sending)
+- [ ] Make the app work offline using a persistence layer of your choice
- [ ] Make the app idempotent in regards to what you send and receive
- [ ] Integrate a splashscreen to hide chats while the app is loading
- [ ] Add support for optimistic sending to give instantaneity in the UI
- [ ] Add support for a local read/unread indicator
-- [ ] Avoid block changing states so the app feels fluid & snappy
-- [ ] Make the app compatible to run on iPad and macOS
-- [ ] Make the app runnable on multiple devices
+- [ ] Avoid to block changing states so the app feels fluid & snappy
+- [ ] Make the app available in multiple environments (e.g.: iOS/macOS,
+ web/electron)
- [ ] _Anything_ that you feel could improve the UX!
## Design
@@ -60,7 +65,7 @@ will have to prioritize what you work on. A few things that are important for us
and that will be considered during the review:
- **documentation**: is the readme clear? are important parts of the code
documented?
-- **impact**: which features did you prioritize?
+- **impact**: what did you consciouslly decided to prioritize?
- **maintainability**: is the code well-structured and easy to read/evolve?
- **robustness**: is the code tested or easily testable? are edge-cases
considered? is static analysis leveraged?
diff --git a/ios-engineer/server/.gitignore b/client-challenge/server/.gitignore
similarity index 100%
rename from ios-engineer/server/.gitignore
rename to client-challenge/server/.gitignore
diff --git a/client-challenge/server/data.json b/client-challenge/server/data.json
new file mode 100644
index 0000000..078a0d8
--- /dev/null
+++ b/client-challenge/server/data.json
@@ -0,0 +1,15 @@
+[
+ "I told my doctor that I broke my arm in two places – he told me to stop going to those places.",
+ "This is your captain speaking, AND THIS IS YOUR CAPTAIN SHOUTING.",
+ "Before you criticize someone, walk a mile in their shoes. That way, when you do criticize them, you're a mile away, and you have their shoes.",
+ "I was at the park wondering why this frisbee kept getting bigger… and then it hit me.",
+ "Two fish in a tank, one looks at the other and says, 'How do you drive this thing?'",
+ "Evening news is where they begin with 'Good evening,' and then proceed to tell you why it isn’t.",
+ "When I met my now wife, I asked if she was vegetarian because she really loved animals. She responded, 'No, I just really hate vegetables.'",
+ "I know they say that money talks, but all mine says is 'Goodbye.'",
+ "I have an inferiority complex, but it's not a very good one.",
+ "What do you call a lazy kangaroo? A pouch potato.",
+ "My wife and I laugh about how competitive we are. But I laugh more.",
+ "My wife told me to stop impersonating a flamingo. I had to put my foot down.",
+ "Have you heard about the guy who stole the calendar?! Well, he got 12 months!"
+]
diff --git a/ios-engineer/server/go.mod b/client-challenge/server/go.mod
similarity index 62%
rename from ios-engineer/server/go.mod
rename to client-challenge/server/go.mod
index 451f351..aeef84b 100644
--- a/ios-engineer/server/go.mod
+++ b/client-challenge/server/go.mod
@@ -3,14 +3,15 @@ module github.com/hearthands/challenges/ios-engineer/server
go 1.22.5
require (
- github.com/go-chi/chi/v5 v5.1.0
+ github.com/go-chi/chi/v5 v5.2.0
github.com/go-chi/render v1.0.3
+ github.com/google/uuid v1.6.0
github.com/r3labs/sse/v2 v2.10.0
- golang.org/x/exp v0.0.0-20240707233637-46b078467d37
+ golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67
)
require (
github.com/ajg/form v1.5.1 // indirect
- golang.org/x/net v0.27.0 // indirect
+ golang.org/x/net v0.33.0 // indirect
gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect
)
diff --git a/ios-engineer/server/go.sum b/client-challenge/server/go.sum
similarity index 75%
rename from ios-engineer/server/go.sum
rename to client-challenge/server/go.sum
index 3df15b6..8c3c74e 100644
--- a/ios-engineer/server/go.sum
+++ b/client-challenge/server/go.sum
@@ -2,10 +2,12 @@ github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
-github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
+github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0=
+github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/r3labs/sse/v2 v2.10.0 h1:hFEkLLFY4LDifoHdiCN/LlGBAdVJYsANaLqNYa1l/v0=
@@ -14,11 +16,11 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w=
-golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
+golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo=
+golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
golang.org/x/net v0.0.0-20191116160921-f9c825593386/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
-golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
+golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
+golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/cenkalti/backoff.v1 v1.1.0 h1:Arh75ttbsvlpVA7WtVpH4u9h6Zl46xuptxqLxPiSo4Y=
diff --git a/ios-engineer/server/main.go b/client-challenge/server/main.go
similarity index 66%
rename from ios-engineer/server/main.go
rename to client-challenge/server/main.go
index 44feffb..0d36e28 100644
--- a/ios-engineer/server/main.go
+++ b/client-challenge/server/main.go
@@ -1,6 +1,7 @@
package main
import (
+ "cmp"
_ "embed"
"encoding/json"
"fmt"
@@ -8,28 +9,28 @@ import (
"net"
"net/http"
"os"
- "strconv"
- "strings"
"sync"
- "sync/atomic"
"time"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/render"
+ "github.com/google/uuid"
"github.com/r3labs/sse/v2"
"golang.org/x/exp/maps"
)
+const (
+ // maximum number of entities returned in a single request
+ limit = 100
+)
+
var (
// hostname used by the server
- hostname = envOrDefault("HTTP_HOST", "localhost")
+ hostname = cmp.Or(os.Getenv("HTTP_HOST"), "localhost")
// port used by the server
- port = envOrDefault("PORT", "3000")
-
- // maximum number of entities returned in a single request
- limit = 100
+ port = cmp.Or(os.Getenv("PORT"), "3000")
)
//go:embed data.json
@@ -43,7 +44,7 @@ func main() {
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
- r.Use(middleware.Timeout(60 * time.Second))
+ r.Use(middleware.Timeout(30 * time.Second))
r.Handle("/events", http.HandlerFunc(events.ServeHTTP))
@@ -51,14 +52,10 @@ func main() {
r.Use(render.SetContentType(render.ContentTypeJSON))
r.Use(chaosMiddleware)
r.Get("/chats", app.GetChats)
- r.Get("/chats/{chatID}/messages", app.GetMessages)
- r.Post("/chats/{chatID}/messages", app.PostMessages)
+ r.Get("/chats/{chatID}/messages", app.GetChatMessages)
+ r.Post("/chats/{chatID}/messages", app.PostChatMessages)
})
- r.Get("/livez", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.WriteHeader(http.StatusOK)
- }))
-
addr := net.JoinHostPort(hostname, port)
fmt.Printf("Starting server on http://%s\n", addr)
if err := http.ListenAndServe(addr, r); err != nil {
@@ -71,18 +68,19 @@ func main() {
//
type Chat struct {
- ID uint32 `json:"id"`
- Name string `json:"name"`
+ ID uuid.UUID `json:"id"`
+ Name string `json:"name"`
messages []*Message
}
type Message struct {
- ID uint32 `json:"id"`
- ChatID uint32 `json:"chat_id"`
- Author string `json:"author"`
- Text string `json:"text"`
- SentAt time.Time `json:"sent_at"`
+ ID uuid.UUID `json:"id"`
+ ChatID uuid.UUID `json:"chat_id"`
+ Author string `json:"author"`
+ Text string `json:"text"`
+ SentAt time.Time `json:"sent_at"`
+ IdempotencyKey string `json:"idempotency_key"`
}
// App is both our controller and our data store. This coupling allows to keep
@@ -93,7 +91,7 @@ type App struct {
mu sync.RWMutex
idempotencyKeys map[string]struct{} // store idempotency keys
- store map[uint32]*Chat // in-memory store of chats and messages
+ store map[uuid.UUID]*Chat // in-memory store of chats and messages
}
func NewApp(events *sse.Server) *App {
@@ -107,22 +105,21 @@ func NewApp(events *sse.Server) *App {
now := time.Now()
// store contains all the chats, indexed by their ID
- store := map[uint32]*Chat{}
- for i, chat := range []*Chat{
- {Name: "John", messages: []*Message{
- {ID: newID(), Author: "bot", Text: "Sounds good 👍", SentAt: now},
+ store := map[uuid.UUID]*Chat{}
+ for _, chat := range []*Chat{
+ {ID: uuid.New(), Name: "John", messages: []*Message{
+ {ID: uuid.New(), Author: "bot", Text: "Sounds good 👍", SentAt: now, IdempotencyKey: uuid.New().String()},
}},
- {Name: "Jessica", messages: []*Message{
- {ID: newID(), Author: "bot", Text: "How are you!?", SentAt: now.Add(-1 * time.Minute)},
+ {ID: uuid.New(), Name: "Jessica", messages: []*Message{
+ {ID: uuid.New(), Author: "bot", Text: "How are you!?", SentAt: now.Add(-1 * time.Minute), IdempotencyKey: uuid.New().String()},
}},
- {Name: "Matt", messages: []*Message{
- {ID: newID(), Author: "bot", Text: "ok chat soon :)", SentAt: now.Add(-32 * time.Minute)},
+ {ID: uuid.New(), Name: "Matt", messages: []*Message{
+ {ID: uuid.New(), Author: "bot", Text: "ok chat soon :)", SentAt: now.Add(-32 * time.Minute), IdempotencyKey: uuid.New().String()},
}},
- {Name: "Sarah", messages: []*Message{
- {ID: newID(), Author: "bot", Text: "ok talk later!", SentAt: now.Add(-24 * time.Hour)},
+ {ID: uuid.New(), Name: "Sarah", messages: []*Message{
+ {ID: uuid.New(), Author: "bot", Text: "ok talk later!", SentAt: now.Add(-24 * time.Hour), IdempotencyKey: uuid.New().String()},
}},
} {
- chat.ID = uint32(i + 1) // we don't want to have a chat id == 0
for _, message := range chat.messages {
message.ChatID = chat.ID
}
@@ -147,7 +144,7 @@ func (app *App) GetChats(w http.ResponseWriter, r *http.Request) {
render.JSON(w, r, chats)
}
-func (app *App) GetMessages(w http.ResponseWriter, r *http.Request) {
+func (app *App) GetChatMessages(w http.ResponseWriter, r *http.Request) {
app.mu.RLock()
defer app.mu.RUnlock()
@@ -164,7 +161,7 @@ func (app *App) GetMessages(w http.ResponseWriter, r *http.Request) {
render.JSON(w, r, chat.messages[start:end])
}
-func (app *App) PostMessages(w http.ResponseWriter, r *http.Request) {
+func (app *App) PostChatMessages(w http.ResponseWriter, r *http.Request) {
app.mu.Lock()
defer app.mu.Unlock()
@@ -192,18 +189,21 @@ func (app *App) PostMessages(w http.ResponseWriter, r *http.Request) {
}
newMessage := &Message{
- ID: newID(),
- ChatID: chat.ID,
- Author: "user",
- Text: message.Text,
- SentAt: time.Now(),
+ ID: uuid.New(),
+ ChatID: chat.ID,
+ Author: "user",
+ Text: message.Text,
+ SentAt: time.Now(),
+ IdempotencyKey: idempotencyKey,
}
chat.messages = append(chat.messages, newMessage)
app.publishEvent("messages", newMessage)
- app.idempotencyKeys[idempotencyKey] = struct{}{}
+
go app.sendDelayedAnswer(chat)
+ app.idempotencyKeys[idempotencyKey] = struct{}{}
+
w.WriteHeader(http.StatusOK)
render.JSON(w, r, newMessage)
}
@@ -217,11 +217,12 @@ func (app *App) sendDelayedAnswer(chat *Chat) {
text := app.jokes[rand.Intn(len(app.jokes))]
newMessage := &Message{
- ID: newID(),
- ChatID: chat.ID,
- Author: "bot",
- Text: text,
- SentAt: time.Now(),
+ ID: uuid.New(),
+ ChatID: chat.ID,
+ Author: "bot",
+ Text: text,
+ SentAt: time.Now(),
+ IdempotencyKey: uuid.New().String(),
}
chat.messages = append(chat.messages, newMessage)
@@ -229,14 +230,14 @@ func (app *App) sendDelayedAnswer(chat *Chat) {
}
func (app *App) findChatByID(rawID string) (*Chat, error) {
- chatID, err := strconv.ParseUint(rawID, 10, 32)
+ parsedID, err := uuid.Parse(rawID)
if err != nil {
return nil, fmt.Errorf("invalid chat ID %s: %w", rawID, err)
}
- chat, ok := app.store[uint32(chatID)]
+ chat, ok := app.store[parsedID]
if !ok {
- return nil, fmt.Errorf("chat %s not found", rawID)
+ return nil, fmt.Errorf("chat not found with ID %s", rawID)
}
return chat, nil
@@ -253,28 +254,6 @@ func (app *App) publishEvent(stream string, payload any) {
}
}
-//
-// Helpers
-//
-
-// lastID is the last ID generated by newID. It starts counting from the current
-// timestamp in seconds.
-var lastID = uint32(time.Now().Unix())
-
-// newID generates a new ID, unique for the lifetime of this server.
-func newID() uint32 {
- return atomic.AddUint32(&lastID, 1)
-}
-
-// envOrDefault returns the value of the environment variable at the given key.
-// Fallbacks to the given default if the value found is missing or empty.
-func envOrDefault(key string, defaultValue string) string {
- if v := strings.TrimSpace(os.Getenv(key)); len(v) > 0 {
- return v
- }
- return defaultValue
-}
-
// chaosMiddleware makes sure to randomly return errors and adds artificial
// latency to simulate a real world system.
func chaosMiddleware(next http.Handler) http.Handler {
@@ -288,7 +267,7 @@ func chaosMiddleware(next http.Handler) http.Handler {
return
}
- // 5% chance of timeout
+ // 5% chance of timeout after 5 seconds
if rand.Intn(100) < 5 {
<-time.After(5 * time.Second)
http.Error(w, "timeout", http.StatusGatewayTimeout)
diff --git a/ios-engineer/server/readme.md b/client-challenge/server/readme.md
similarity index 69%
rename from ios-engineer/server/readme.md
rename to client-challenge/server/readme.md
index 087daac..3425e33 100644
--- a/ios-engineer/server/readme.md
+++ b/client-challenge/server/readme.md
@@ -1,7 +1,6 @@
-# iOS Challenge - Server Companion
+# Client Challenge - Server Companion
-This server is used as a companion for the iOS challenge. Persistence is
-in-memory, and restarting the server will reset its state.
+This server is used as a companion for the client challenge.
The server will sometime be a bit capricious, you might observe:
- failure to receive a response
@@ -10,6 +9,9 @@ The server will sometime be a bit capricious, you might observe:
When that happens, you should retry and react accordingly.
+_Note: persistence is in-memory only, and restarting the server will reset its
+state._
+
## How to start the server?
1. You need to install Go on your system: follow the [official documentation](https://go.dev/doc/install)
@@ -17,12 +19,14 @@ When that happens, you should retry and react accordingly.
## How to make the server reachable from another machine?
-You might want to allow the server to be reached from another machine
-(simulator or a phone for example). One way to do that is to use [ngrok]().
+You might want to allow the server to be reached from another machine (a
+simulator or a phone for example). One way to do that is to use
+[ngrok](https://ngrok.com/).
1. You need to install Ngrok on your system: follow the [official
documentation](https://ngrok.com/download)
-2. Then, after having started the server (see above), execute the command: `ngrok http 3000`
+2. Then, after having started the go server (see above), execute the command
+ `ngrok http 3000`.
This will give you a public URL of the form
`https://9f98-62-194-145-77.ngrok.io`, that can be used to reach your server
@@ -42,12 +46,15 @@ chats, and read & write messages.
### Endpoints
-- `GET /events?stream=messages`: an [SSE](https://en.wikipedia.org/wiki/Server-sent_events) stream that sends you `Message` entities as they are received by the server
+- `GET /events?stream=messages`: an
+ [SSE](https://en.wikipedia.org/wiki/Server-sent_events) stream that sends you
+ `Message` entities as they are ingested by the server
- `GET /chats`: returns a list of all `Chat` entities
-- `GET /chats/{chat_id}/messages`: returns a list of the 100 most recent `Message` entities in a chat
-- `POST /chats/{chat_id}/messages`: send a new message in a chat and returns
- the newly created `Message` entity. It expects a JSON payload of the form: `{ "text":
- "..." }`
+- `GET /chats/{chat_id}/messages`: returns a list of the 100 most recent
+ `Message` entities in a chat
+- `POST /chats/{chat_id}/messages`: send a new message in a chat and returns the
+ newly created `Message` entity. It expects a JSON payload of the form: `{
+ "text": "..." }`
### Entities
@@ -58,6 +65,9 @@ chats, and read & write messages.
#### `Message`
- `id` (`number`): the message id
- `chat_id` (`number`): the id of the chat this message belongs to
-- `author` (`string`): the message author (either the constant string `"user"` or `"bot"`)
+- `author` (`string`): the message author (either the constant string `"user"`
+ or `"bot"`)
- `text` (`string`): the actual content of the message
- `sent_at` (`string`): the date at which the message was sent
+- `idempotency_key` (`string`): the idempotency key that was passed during the
+ message creation
diff --git a/design-engineer/readme.md b/design-challenge/readme.md
similarity index 82%
rename from design-engineer/readme.md
rename to design-challenge/readme.md
index 8e722af..93e4c8e 100644
--- a/design-engineer/readme.md
+++ b/design-challenge/readme.md
@@ -1,8 +1,7 @@
-# Design Engineer Challenge
+# Design Challenge
-Welcome to the Heart Hands Design Engineer Challenge. We want to see your creativity, product instincts, strategic thinking, and technical skills in action.
-
-This challenge is part of the hiring process for Design Engineers at [Heart Hands](https://hearthands.tech).
+_This challenge is part of the engineering hiring process at [Heart
+Hands](https://hearthands.tech/)._
## Why this challenge?
@@ -42,9 +41,7 @@ Prototype with code: If you have ideas that are better expressed through code th
We have prepared a Figma file containing some of our existing designs for you to copy or build upon!
-[Open Figma](https://www.figma.com/design/LYHc8FzoYfAVo2tPdh9lc1/%5BStef%5D-Design-Engineer-Challenge?node-id=0-1&t=bPSKWg0mJOr1aq31-1)
-
-
+
## Challenge Review
diff --git a/ios-engineer/server/data.json b/ios-engineer/server/data.json
deleted file mode 100644
index fb7f833..0000000
--- a/ios-engineer/server/data.json
+++ /dev/null
@@ -1,26 +0,0 @@
-[
- "How did the doctor revive the developer?",
- "How did the web dev hurt Comic Sans feelings?",
- "How do you comfort a JavaScript bug?",
- "How do you make a Web App accessible?",
- "What do you call __proto__? Dunder proto. Michael Scott was the regional manager where?",
- "What does a React proposal mean?",
- "Why couldn’t the React component understand the joke?",
- "Why did Jason cover himself with bubble wrap?",
- "Why did the C# developer fall asleep?",
- "Why did the CoffeeScript developer keep getting lost?",
- "Why did the JavaScript boxer goto the chiropractor?",
- "Why did the React Higher Order Component give up?",
- "Why did the Web A11y Dev keep getting distracted?",
- "Why did the child component have such great self-esteem?",
- "Why did the developer go broke?",
- "Why did the functional component feel lost?",
- "Why did the react class component feel relieved?",
- "Why did the react developer have an addiction?",
- "Why did the software company hire drama majors from Starbucks?",
- "Why do C# and Java developers keep breaking their keyboards",
- "Why was Ember.js turning red?",
- "Why was the JavaScript developer sad?",
- "Why was the react developer late to everything?",
- "dev1 > What tool do you use to switch versions of node?"
-]
diff --git a/readme.md b/readme.md
index 8b209ab..d2aee20 100644
--- a/readme.md
+++ b/readme.md
@@ -1,8 +1,6 @@
# Challenges [](https://github.com/hearthandsinc/challenges/actions/workflows/ci.yml)
-This repository contains the [Heart Hands](https://hearthands.tech/) challenges
-for the [iOS](./ios-engineer), [design](./design-engineer), and
-[backend](./backend-engineer) engineers positions.
+This repository contains the [Heart Hands](https://hearthands.tech/) engineering challenges ([client](./client-challenge), [design](./design-challenge), and [backend](./backend-challenge)).
## Feedbacks