Add job application tracker (admin-only)
Some checks failed
Deploy with Docker Compose / deploy (push) Has been cancelled

Full CRUD GraphQL API for tracking job applications with status workflow.
Frontend component in CV view, hidden from print. Login now redirects to
intended route after auth via query param.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-13 09:51:18 +01:00
parent 81f5fafb61
commit 8f3c369ed8
12 changed files with 1739 additions and 15 deletions

View File

@@ -293,6 +293,73 @@ func (r *mutationResolver) CreateActivity(ctx context.Context, input model.Creat
return &activity, nil
}
// CreateJobApplication is the resolver for the createJobApplication field.
func (r *mutationResolver) CreateJobApplication(ctx context.Context, input model.CreateJobApplicationInput) (*models.JobApplication, error) {
if !IsAdminFromCtx(ctx) {
return nil, fmt.Errorf("admin access required")
}
app := models.JobApplication{
JobTitle: input.JobTitle,
Company: input.Company,
Location: input.Location,
URL: input.URL,
Status: input.Status,
Notes: input.Notes,
AppliedAt: input.AppliedAt,
}
if err := r.Store.DB.Create(&app).Error; err != nil {
return nil, err
}
return &app, nil
}
// UpdateJobApplication is the resolver for the updateJobApplication field.
func (r *mutationResolver) UpdateJobApplication(ctx context.Context, id int, input model.UpdateJobApplicationInput) (*models.JobApplication, error) {
if !IsAdminFromCtx(ctx) {
return nil, fmt.Errorf("admin access required")
}
var app models.JobApplication
if err := r.Store.DB.First(&app, id).Error; err != nil {
return nil, err
}
if input.JobTitle != nil {
app.JobTitle = *input.JobTitle
}
if input.Company != nil {
app.Company = *input.Company
}
if input.Location != nil {
app.Location = input.Location
}
if input.URL != nil {
app.URL = input.URL
}
if input.Status != nil {
app.Status = *input.Status
}
if input.Notes != nil {
app.Notes = input.Notes
}
if input.AppliedAt != nil {
app.AppliedAt = input.AppliedAt
}
if err := r.Store.DB.Save(&app).Error; err != nil {
return nil, err
}
return &app, nil
}
// DeleteJobApplication is the resolver for the deleteJobApplication field.
func (r *mutationResolver) DeleteJobApplication(ctx context.Context, id int) (bool, error) {
if !IsAdminFromCtx(ctx) {
return false, fmt.Errorf("admin access required")
}
if err := r.Store.DB.Delete(&models.JobApplication{}, id).Error; err != nil {
return false, err
}
return true, nil
}
// Users is the resolver for the users field.
func (r *queryResolver) Users(ctx context.Context) ([]*models.User, error) {
var users []models.User
@@ -504,6 +571,30 @@ func (r *queryResolver) Me(ctx context.Context) (*models.User, error) {
return &user, nil
}
// JobApplications is the resolver for the jobApplications field.
func (r *queryResolver) JobApplications(ctx context.Context) ([]*models.JobApplication, error) {
if !IsAdminFromCtx(ctx) {
return nil, fmt.Errorf("admin access required")
}
var apps []*models.JobApplication
if err := r.Store.DB.Order("created_at desc").Find(&apps).Error; err != nil {
return nil, err
}
return apps, nil
}
// JobApplication is the resolver for the jobApplication field.
func (r *queryResolver) JobApplication(ctx context.Context, id int) (*models.JobApplication, error) {
if !IsAdminFromCtx(ctx) {
return nil, fmt.Errorf("admin access required")
}
var app models.JobApplication
if err := r.Store.DB.First(&app, id).Error; err != nil {
return nil, err
}
return &app, nil
}
// Mutation returns MutationResolver implementation.
func (r *Resolver) Mutation() MutationResolver { return &mutationResolver{r} }