Skip to content

Commit

Permalink
Added stats endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
ncpleslie committed Mar 31, 2024
1 parent 1e08a64 commit 5ae3a05
Show file tree
Hide file tree
Showing 21 changed files with 192 additions and 41 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/extension.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ jobs:

build-dev:
environment: development
if: github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main'
continue-on-error: true
needs: [test, lint]
runs-on: ubuntu-latest
env:
Expand Down Expand Up @@ -119,6 +121,7 @@ jobs:

build-prod:
environment: production
if: github.event_name == 'release'
continue-on-error: true
needs: [test, lint]
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules
developer-scripts
5 changes: 4 additions & 1 deletion backend/clients/db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (

"cloud.google.com/go/firestore"
firebase "firebase.google.com/go/v4"
"github.com/ncpleslie/application-tracker/models/entities"
"github.com/ncpleslie/job-down/models/entities"
"google.golang.org/api/iterator"
)

Expand Down Expand Up @@ -46,6 +46,7 @@ func (d *DB) AddJob(ctx context.Context, userId string, job entities.Job) (entit
CreatedAt: job.CreatedAt,
UpdatedAt: job.UpdatedAt,
Statuses: job.Statuses,
Notes: job.Notes,
}, nil
}

Expand Down Expand Up @@ -73,6 +74,7 @@ func (d *DB) GetJob(ctx context.Context, userId string, jobId string) (entities.
CreatedAt: job.CreatedAt,
UpdatedAt: job.UpdatedAt,
Statuses: job.Statuses,
Notes: job.Notes,
}, nil
}

Expand All @@ -93,6 +95,7 @@ func (d *DB) UpdateJob(ctx context.Context, userId string, jobId string, job ent
CreatedAt: job.CreatedAt,
UpdatedAt: job.UpdatedAt,
Statuses: job.Statuses,
Notes: job.Notes,
}, nil
}

Expand Down
2 changes: 1 addition & 1 deletion backend/clients/firebase.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
b64 "encoding/base64"

firebase "firebase.google.com/go/v4"
"github.com/ncpleslie/application-tracker/config"
"github.com/ncpleslie/job-down/config"
"google.golang.org/api/option"
)

Expand Down
4 changes: 2 additions & 2 deletions backend/go.mod
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module github.com/ncpleslie/application-tracker
module github.com/ncpleslie/job-down

go 1.22.0

Expand All @@ -22,11 +22,11 @@ require (
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golangci/golangci-lint v1.57.1 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.1 // indirect
github.com/stretchr/testify v1.9.0 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.48.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0 // indirect
Expand Down
9 changes: 1 addition & 8 deletions backend/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,6 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golangci/golangci-lint v1.57.1 h1:cqhpzkzjDwdN12rfMf1SUyyKyp88a1SltNqEYGS0nJw=
github.com/golangci/golangci-lint v1.57.1/go.mod h1:zLcHhz3NHc88T5zV2j75lyc0zH3LdOPOybblYa4p0oI=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
Expand Down Expand Up @@ -98,9 +96,8 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
Expand Down Expand Up @@ -155,8 +152,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
Expand Down Expand Up @@ -215,8 +210,6 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
Expand Down
14 changes: 7 additions & 7 deletions backend/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ import (
"net/http"
"os"

firebase "github.com/ncpleslie/application-tracker/clients"
db "github.com/ncpleslie/application-tracker/clients/db"
storage "github.com/ncpleslie/application-tracker/clients/storage"
cfg "github.com/ncpleslie/application-tracker/config"
server "github.com/ncpleslie/application-tracker/server"
"github.com/ncpleslie/application-tracker/services"
firebase "github.com/ncpleslie/job-down/clients"
db "github.com/ncpleslie/job-down/clients/db"
storage "github.com/ncpleslie/job-down/clients/storage"
cfg "github.com/ncpleslie/job-down/config"
server "github.com/ncpleslie/job-down/server"
"github.com/ncpleslie/job-down/services"
)

func main() {
log := log.New(os.Stdout, "application-tracker: ", log.LstdFlags|log.Lshortfile)
log.Println("Application starting...")
log.Println("Application initializing...")
config := cfg.MustGenerateConfig()

app := firebase.Must(config.Firebase, log)
Expand Down
4 changes: 2 additions & 2 deletions backend/models/entities/job.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package entities
import (
"time"

"github.com/ncpleslie/application-tracker/models/requests"
"github.com/ncpleslie/application-tracker/models/responses"
"github.com/ncpleslie/job-down/models/requests"
"github.com/ncpleslie/job-down/models/responses"
)

type Job struct {
Expand Down
16 changes: 16 additions & 0 deletions backend/models/responses/stats.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package responses

type Stats struct {
TotalJobs int `json:"totalJobs"`
Current StatTrack `json:"current"`
Historical StatTrack `json:"historical"`
}

type StatTrack struct {
Applied int `json:"applied"`
Interview int `json:"interview"`
Offer int `json:"offer"`
Rejected int `json:"rejected"`
Other int `json:"other"`
Accepted int `json:"accepted"`
}
23 changes: 20 additions & 3 deletions backend/server/job_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import (
"fmt"
"net/http"

"github.com/ncpleslie/application-tracker/models/requests"
"github.com/ncpleslie/application-tracker/models/responses"
"github.com/ncpleslie/application-tracker/services"
"github.com/ncpleslie/job-down/models/requests"
"github.com/ncpleslie/job-down/models/responses"
"github.com/ncpleslie/job-down/services"
)

// Returns a handler function for the GET /jobs/{jobId} route.
Expand Down Expand Up @@ -121,3 +121,20 @@ func handleJobDelete(jobService *services.JobService) http.HandlerFunc {
w.WriteHeader(http.StatusNoContent)
}
}

// Returns a handler function for the GET /jobs/stats route.
// It retrieves stats for the provided user ID from the JobService.
//
// GET /jobs/stats
func handleStatsGet(jobService *services.JobService) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user := services.GetCtxUser(r.Context())

stats, err := jobService.GetStats(r.Context(), user.UID)
if err != nil {
encode(w, r, http.StatusInternalServerError, responses.Error{Message: fmt.Sprintf("Error retrieving stats. Error: %s", err.Error())})
}

encode(w, r, http.StatusOK, stats)
}
}
2 changes: 1 addition & 1 deletion backend/server/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"net/http"
"strings"

"github.com/ncpleslie/application-tracker/services"
"github.com/ncpleslie/job-down/services"
)

func cors(clientAddress string, next http.Handler) http.Handler {
Expand Down
5 changes: 3 additions & 2 deletions backend/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import (
"fmt"
"net/http"

"github.com/ncpleslie/application-tracker/config"
"github.com/ncpleslie/application-tracker/services"
"github.com/ncpleslie/job-down/config"
"github.com/ncpleslie/job-down/services"
)

// NewServer creates a new http.Handler.
Expand Down Expand Up @@ -48,6 +48,7 @@ func addJobRoutes(
mux.Handle("POST /", handleJobPost(jobService))
mux.Handle("PATCH /{jobId}", handleJobPatch(jobService))
mux.Handle("DELETE /{jobId}", handleJobDelete(jobService))
mux.Handle("GET /stats", handleStatsGet(jobService))

var handler http.Handler = mux
handler = userContext(authService, handler)
Expand Down
90 changes: 84 additions & 6 deletions backend/services/job_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ import (

"github.com/gen2brain/go-fitz"

"github.com/ncpleslie/application-tracker/clients/db"
store "github.com/ncpleslie/application-tracker/clients/storage"
"github.com/ncpleslie/application-tracker/models/entities"
requests "github.com/ncpleslie/application-tracker/models/requests"
"github.com/ncpleslie/application-tracker/models/responses"
"github.com/ncpleslie/job-down/clients/db"
store "github.com/ncpleslie/job-down/clients/storage"
"github.com/ncpleslie/job-down/models/entities"
requests "github.com/ncpleslie/job-down/models/requests"
"github.com/ncpleslie/job-down/models/responses"
)

type JobService struct {
Expand Down Expand Up @@ -149,14 +149,25 @@ func (s *JobService) UpdateJob(ctx context.Context, userId string, jobId string,
return responses.Job{}, err
}

// Don't allow the same status to be added multiple times in a row.
mostRecentStatus := jobEntity.Statuses[0]
for _, status := range jobEntity.Statuses {
if status.CreatedAt.After(mostRecentStatus.CreatedAt) {
mostRecentStatus = status
}
}

statuses := make([]entities.Status, 0)
statuses = append(statuses, entities.NewStatusEntity(job.Status))
statuses = append(statuses, jobEntity.Statuses...)
if mostRecentStatus.Status != job.Status {
statuses = append(statuses, entities.NewStatusEntity(job.Status))
}

jobEntity.Position = job.Position
jobEntity.Company = job.Company
jobEntity.Url = job.Url
jobEntity.Statuses = statuses
jobEntity.Notes = job.Notes

updatedJobEntity, err := s.DB.UpdateJob(ctx, userId, jobId, jobEntity)
if err != nil {
Expand Down Expand Up @@ -188,6 +199,73 @@ func (s *JobService) DeleteJob(ctx context.Context, userId string, jobId string)
return s.DB.DeleteJob(ctx, userId, jobId)
}

// GetStats returns the stats for a user.
// The stats include the total number of jobs, the number of jobs in each status, and the number of jobs in each status historically.
//
// TODO: Perhaps refactor this to be persistent in the database
//
// E.g have a collection of stats for each user and update it every time a job is added/updated.
func (s *JobService) GetStats(ctx context.Context, userId string) (responses.Stats, error) {
jobs, err := s.DB.GetJobs(ctx, userId)
if err != nil {
return responses.Stats{}, err
}

var stats responses.Stats
for _, job := range jobs {
mostRecentStatus := job.Statuses[0]
for _, status := range job.Statuses {
if status.CreatedAt.After(mostRecentStatus.CreatedAt) {
mostRecentStatus = status
}
}

// TODO: Refactor this to reduce duplication
switch mostRecentStatus.Status {
case "applied":
stats.Current.Applied++
case "phone_screen":
case "coding_challenge":
case "first_interview":
case "second_interview":
case "final_interview":
stats.Current.Interview++
case "offer":
stats.Current.Offer++
case "rejected":
stats.Current.Rejected++
case "accepted":
stats.Current.Accepted++
default:
stats.Current.Other++
}

for _, status := range job.Statuses {
switch status.Status {
case "applied":
stats.Historical.Applied++
case "phone_screen":
case "coding_challenge":
case "first_interview":
case "second_interview":
case "final_interview":
stats.Historical.Interview++
case "offer":
stats.Historical.Offer++
case "rejected":
stats.Historical.Rejected++
case "accepted":
stats.Historical.Accepted++
default:
stats.Historical.Other++
}
}
}

stats.TotalJobs = len(jobs)
return stats, nil
}

// Converts a PDF byte slice to a PNG byte slice.
// Returns the bytes of the new PNG image.
func pdfBytesToPngBytes(pdf []byte) ([]byte, error) {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/AllJobsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ const createColumns = (
return (
<div className="flex justify-center text-center capitalize">
<div
className={cn("w-full rounded-lg", statusToBadgeColor(status))}
className={cn("w-[25ch] rounded-lg", statusToBadgeColor(status))}
>
<DropdownSelectInput
title={snakeCaseToTitleCase(status)}
Expand Down
10 changes: 7 additions & 3 deletions frontend/src/components/JobForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,9 @@ const JobForm: React.FC<PropsWithChildren<JobFormProps>> = ({
defaultValues: defaultValuesClone || {
position: "",
company: "",
url: "",
url: "https://",
status: "applied",
notes: "",
},
disabled: disabled,
});
Expand Down Expand Up @@ -120,7 +121,10 @@ const JobForm: React.FC<PropsWithChildren<JobFormProps>> = ({
Engineer.
</FormDescription>
<FormControl>
<Input placeholder="Software Engineer" {...field} />
<Input
placeholder="e.g. Rockstar Full Snack Software Engineer"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
Expand All @@ -138,7 +142,7 @@ const JobForm: React.FC<PropsWithChildren<JobFormProps>> = ({
The name of the Company you applied for. E.g. XYZ Corp.
</FormDescription>
<FormControl>
<Input placeholder="XYZ Corp" {...field} />
<Input placeholder="e.g. XYZ Corp" {...field} />
</FormControl>

<FormMessage />
Expand Down
Loading

0 comments on commit 5ae3a05

Please sign in to comment.