Split schema.graphql and schema.resolvers.go into per-domain files
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 3m36s
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 3m36s
Move Query/Mutation field declarations from the monolithic schema.graphql into each domain's .graphql file using extend type, so gqlgen places resolvers in the matching *.resolvers.go files. Extract helper functions into *_helpers.go files to prevent gqlgen from commenting them out. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,9 @@ package graph
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"adam-french.co.uk/backend/graph/model"
|
||||||
"adam-french.co.uk/backend/models"
|
"adam-french.co.uk/backend/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,6 +18,33 @@ func (r *activityResolver) ID(ctx context.Context, obj *models.Activity) (int, e
|
|||||||
return int(obj.ID), nil
|
return int(obj.ID), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateActivity is the resolver for the createActivity field.
|
||||||
|
func (r *mutationResolver) CreateActivity(ctx context.Context, input model.CreateActivityInput) (*models.Activity, error) {
|
||||||
|
if !IsAdminFromCtx(ctx) {
|
||||||
|
return nil, fmt.Errorf("admin access required")
|
||||||
|
}
|
||||||
|
|
||||||
|
activity := models.Activity{Type: input.Type, Name: input.Name, Link: input.Link}
|
||||||
|
if err := r.Store.DB.Create(&activity).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &activity, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activities is the resolver for the activities field.
|
||||||
|
func (r *queryResolver) Activities(ctx context.Context) ([]*models.Activity, error) {
|
||||||
|
var activities []models.Activity
|
||||||
|
if err := r.Store.DB.Order("created_at DESC").Find(&activities).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := make([]*models.Activity, len(activities))
|
||||||
|
for i := range activities {
|
||||||
|
result[i] = &activities[i]
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Activity returns ActivityResolver implementation.
|
// Activity returns ActivityResolver implementation.
|
||||||
func (r *Resolver) Activity() ActivityResolver { return &activityResolver{r} }
|
func (r *Resolver) Activity() ActivityResolver { return &activityResolver{r} }
|
||||||
|
|
||||||
|
|||||||
146
backend/graph/auth.resolvers.go
Normal file
146
backend/graph/auth.resolvers.go
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
package graph
|
||||||
|
|
||||||
|
// This file will be automatically regenerated based on the schema, any resolver
|
||||||
|
// implementations
|
||||||
|
// will be copied through when generating and any unknown code will be moved to the end.
|
||||||
|
// Code generated by github.com/99designs/gqlgen version v0.17.88
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"adam-french.co.uk/backend/graph/model"
|
||||||
|
"adam-french.co.uk/backend/models"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Login is the resolver for the login field.
|
||||||
|
func (r *mutationResolver) Login(ctx context.Context, input model.LoginInput) (*model.AuthPayload, error) {
|
||||||
|
gc := GinContextFromCtx(ctx)
|
||||||
|
if gc == nil {
|
||||||
|
return nil, fmt.Errorf("could not get gin context")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !r.Store.LoginLimiter.Allow(gc.ClientIP()) {
|
||||||
|
return nil, fmt.Errorf("too many login attempts, please try again later")
|
||||||
|
}
|
||||||
|
|
||||||
|
var user models.User
|
||||||
|
if err := r.Store.DB.Where("username = ?", input.Username).First(&user).Error; err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid credentials")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := bcrypt.CompareHashAndPassword(user.Password, []byte(input.Password)); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid credentials")
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens, err := r.Store.Auth.GenerateJWT(&user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to generate tokens")
|
||||||
|
}
|
||||||
|
|
||||||
|
gc.SetSameSite(http.SameSiteLaxMode)
|
||||||
|
gc.SetCookie(
|
||||||
|
"access_token",
|
||||||
|
tokens.AccessToken,
|
||||||
|
int(r.Store.Auth.Config.AccessTokenLifetime.Seconds()),
|
||||||
|
"/",
|
||||||
|
r.Store.Auth.Config.Domain,
|
||||||
|
true, true,
|
||||||
|
)
|
||||||
|
gc.SetCookie(
|
||||||
|
"refresh_token",
|
||||||
|
tokens.RefreshToken,
|
||||||
|
int(r.Store.Auth.Config.RefreshTokenLifetime.Seconds()),
|
||||||
|
"/",
|
||||||
|
r.Store.Auth.Config.Domain,
|
||||||
|
true, true,
|
||||||
|
)
|
||||||
|
|
||||||
|
return &model.AuthPayload{User: &user}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logout is the resolver for the logout field.
|
||||||
|
func (r *mutationResolver) Logout(ctx context.Context) (bool, error) {
|
||||||
|
gc := GinContextFromCtx(ctx)
|
||||||
|
if gc == nil {
|
||||||
|
return false, fmt.Errorf("could not get gin context")
|
||||||
|
}
|
||||||
|
|
||||||
|
gc.SetSameSite(http.SameSiteLaxMode)
|
||||||
|
gc.SetCookie("access_token", "", -1, "/", r.Store.Auth.Config.Domain, true, true)
|
||||||
|
gc.SetCookie("refresh_token", "", -1, "/", r.Store.Auth.Config.Domain, true, true)
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RefreshToken is the resolver for the refreshToken field.
|
||||||
|
func (r *mutationResolver) RefreshToken(ctx context.Context) (*model.AuthPayload, error) {
|
||||||
|
gc := GinContextFromCtx(ctx)
|
||||||
|
if gc == nil {
|
||||||
|
return nil, fmt.Errorf("could not get gin context")
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshToken, err := gc.Cookie("refresh_token")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, err := r.Store.Auth.VerifyJWT(refreshToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
userIDF, ok := (*claims)["id"].(float64)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid token claims")
|
||||||
|
}
|
||||||
|
|
||||||
|
var user models.User
|
||||||
|
user.ID = uint(userIDF)
|
||||||
|
if err := r.Store.DB.First(&user).Error; err != nil {
|
||||||
|
return nil, fmt.Errorf("user not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens, err := r.Store.Auth.GenerateJWT(&user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to generate tokens")
|
||||||
|
}
|
||||||
|
|
||||||
|
gc.SetSameSite(http.SameSiteLaxMode)
|
||||||
|
gc.SetCookie(
|
||||||
|
"access_token",
|
||||||
|
tokens.AccessToken,
|
||||||
|
int(r.Store.Auth.Config.AccessTokenLifetime.Seconds()),
|
||||||
|
"/",
|
||||||
|
r.Store.Auth.Config.Domain,
|
||||||
|
true, true,
|
||||||
|
)
|
||||||
|
gc.SetCookie(
|
||||||
|
"refresh_token",
|
||||||
|
tokens.RefreshToken,
|
||||||
|
int(r.Store.Auth.Config.RefreshTokenLifetime.Seconds()),
|
||||||
|
"/",
|
||||||
|
r.Store.Auth.Config.Domain,
|
||||||
|
true, true,
|
||||||
|
)
|
||||||
|
|
||||||
|
return &model.AuthPayload{User: &user}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Me is the resolver for the me field.
|
||||||
|
func (r *queryResolver) Me(ctx context.Context) (*models.User, error) {
|
||||||
|
userID, ok := UserIDFromCtx(ctx)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
var user models.User
|
||||||
|
user.ID = userID
|
||||||
|
if err := r.Store.DB.First(&user).Error; err != nil {
|
||||||
|
return nil, fmt.Errorf("user not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
@@ -7,7 +7,9 @@ package graph
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"adam-french.co.uk/backend/graph/model"
|
||||||
"adam-french.co.uk/backend/models"
|
"adam-french.co.uk/backend/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,6 +18,46 @@ func (r *bookmarkResolver) ID(ctx context.Context, obj *models.Bookmark) (int, e
|
|||||||
return int(obj.ID), nil
|
return int(obj.ID), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateBookmark is the resolver for the createBookmark field.
|
||||||
|
func (r *mutationResolver) CreateBookmark(ctx context.Context, input model.CreateBookmarkInput) (*models.Bookmark, error) {
|
||||||
|
if !IsAdminFromCtx(ctx) {
|
||||||
|
return nil, fmt.Errorf("admin access required")
|
||||||
|
}
|
||||||
|
bookmark := models.Bookmark{Category: input.Category, Name: input.Name, Link: input.Link}
|
||||||
|
if err := r.Store.DB.Create(&bookmark).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &bookmark, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteBookmark is the resolver for the deleteBookmark field.
|
||||||
|
func (r *mutationResolver) DeleteBookmark(ctx context.Context, id int) (*models.Bookmark, error) {
|
||||||
|
if !IsAdminFromCtx(ctx) {
|
||||||
|
return nil, fmt.Errorf("admin access required")
|
||||||
|
}
|
||||||
|
var bookmark models.Bookmark
|
||||||
|
if err := r.Store.DB.First(&bookmark, id).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := r.Store.DB.Delete(&bookmark).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &bookmark, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bookmarks is the resolver for the bookmarks field.
|
||||||
|
func (r *queryResolver) Bookmarks(ctx context.Context) ([]*models.Bookmark, error) {
|
||||||
|
var bookmarks []models.Bookmark
|
||||||
|
if err := r.Store.DB.Order("category ASC, created_at ASC").Find(&bookmarks).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := make([]*models.Bookmark, len(bookmarks))
|
||||||
|
for i := range bookmarks {
|
||||||
|
result[i] = &bookmarks[i]
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Bookmark returns BookmarkResolver implementation.
|
// Bookmark returns BookmarkResolver implementation.
|
||||||
func (r *Resolver) Bookmark() BookmarkResolver { return &bookmarkResolver{r} }
|
func (r *Resolver) Bookmark() BookmarkResolver { return &bookmarkResolver{r} }
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ package graph
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"adam-french.co.uk/backend/graph/model"
|
||||||
"adam-french.co.uk/backend/models"
|
"adam-french.co.uk/backend/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,6 +18,33 @@ func (r *favoriteResolver) ID(ctx context.Context, obj *models.Favorite) (int, e
|
|||||||
return int(obj.ID), nil
|
return int(obj.ID), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateFavorite is the resolver for the createFavorite field.
|
||||||
|
func (r *mutationResolver) CreateFavorite(ctx context.Context, input model.CreateFavoriteInput) (*models.Favorite, error) {
|
||||||
|
if !IsAdminFromCtx(ctx) {
|
||||||
|
return nil, fmt.Errorf("admin access required")
|
||||||
|
}
|
||||||
|
|
||||||
|
favorite := models.Favorite{Type: input.Type, Name: input.Name, Link: input.Link}
|
||||||
|
if err := r.Store.DB.Create(&favorite).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &favorite, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Favorites is the resolver for the favorites field.
|
||||||
|
func (r *queryResolver) Favorites(ctx context.Context) ([]*models.Favorite, error) {
|
||||||
|
var favorites []models.Favorite
|
||||||
|
if err := r.Store.DB.Order("created_at DESC").Find(&favorites).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := make([]*models.Favorite, len(favorites))
|
||||||
|
for i := range favorites {
|
||||||
|
result[i] = &favorites[i]
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Favorite returns FavoriteResolver implementation.
|
// Favorite returns FavoriteResolver implementation.
|
||||||
func (r *Resolver) Favorite() FavoriteResolver { return &favoriteResolver{r} }
|
func (r *Resolver) Favorite() FavoriteResolver { return &favoriteResolver{r} }
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,10 @@
|
|||||||
package graph
|
package graph
|
||||||
|
|
||||||
|
// This file will be automatically regenerated based on the schema, any resolver
|
||||||
|
// implementations
|
||||||
|
// will be copied through when generating and any unknown code will be moved to the end.
|
||||||
|
// Code generated by github.com/99designs/gqlgen version v0.17.88
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
@@ -8,17 +13,6 @@ import (
|
|||||||
"adam-french.co.uk/backend/services"
|
"adam-french.co.uk/backend/services"
|
||||||
)
|
)
|
||||||
|
|
||||||
func mapGiteaFeed(feed *services.GiteaFeedResponse) *model.GiteaFeedItem {
|
|
||||||
return &model.GiteaFeedItem{
|
|
||||||
AvatarURL: feed.ActUser.AvatarURL,
|
|
||||||
RepoURL: feed.Repo.HTMLURL,
|
|
||||||
RepoName: feed.Repo.FullName,
|
|
||||||
OpType: feed.OpType,
|
|
||||||
CommitMessage: services.ParseCommitMessage(feed.Content),
|
|
||||||
CreatedAt: feed.Created,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GiteaFeed is the resolver for the giteaFeed field.
|
// GiteaFeed is the resolver for the giteaFeed field.
|
||||||
func (r *queryResolver) GiteaFeed(ctx context.Context) (*model.GiteaFeedItem, error) {
|
func (r *queryResolver) GiteaFeed(ctx context.Context) (*model.GiteaFeedItem, error) {
|
||||||
if r.Store.GiteaFeedFresh() {
|
if r.Store.GiteaFeedFresh() {
|
||||||
|
|||||||
17
backend/graph/gitea_helpers.go
Normal file
17
backend/graph/gitea_helpers.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package graph
|
||||||
|
|
||||||
|
import (
|
||||||
|
"adam-french.co.uk/backend/graph/model"
|
||||||
|
"adam-french.co.uk/backend/services"
|
||||||
|
)
|
||||||
|
|
||||||
|
func mapGiteaFeed(feed *services.GiteaFeedResponse) *model.GiteaFeedItem {
|
||||||
|
return &model.GiteaFeedItem{
|
||||||
|
AvatarURL: feed.ActUser.AvatarURL,
|
||||||
|
RepoURL: feed.Repo.HTMLURL,
|
||||||
|
RepoName: feed.Repo.FullName,
|
||||||
|
OpType: feed.OpType,
|
||||||
|
CommitMessage: services.ParseCommitMessage(feed.Content),
|
||||||
|
CreatedAt: feed.Created,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,9 @@ package graph
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"adam-french.co.uk/backend/graph/model"
|
||||||
"adam-french.co.uk/backend/models"
|
"adam-french.co.uk/backend/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,6 +18,75 @@ func (r *jobAppReferenceResolver) ID(ctx context.Context, obj *models.JobAppRefe
|
|||||||
return int(obj.ID), nil
|
return int(obj.ID), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateJobAppReference is the resolver for the createJobAppReference field.
|
||||||
|
func (r *mutationResolver) CreateJobAppReference(ctx context.Context, input model.CreateJobAppReferenceInput) (*models.JobAppReference, error) {
|
||||||
|
if !IsAdminFromCtx(ctx) {
|
||||||
|
return nil, fmt.Errorf("admin access required")
|
||||||
|
}
|
||||||
|
ref := models.JobAppReference{
|
||||||
|
Category: input.Category,
|
||||||
|
Label: input.Label,
|
||||||
|
Value: input.Value,
|
||||||
|
}
|
||||||
|
if input.SortOrder != nil {
|
||||||
|
ref.SortOrder = *input.SortOrder
|
||||||
|
}
|
||||||
|
if err := r.Store.DB.Create(&ref).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ref, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateJobAppReference is the resolver for the updateJobAppReference field.
|
||||||
|
func (r *mutationResolver) UpdateJobAppReference(ctx context.Context, id int, input model.UpdateJobAppReferenceInput) (*models.JobAppReference, error) {
|
||||||
|
if !IsAdminFromCtx(ctx) {
|
||||||
|
return nil, fmt.Errorf("admin access required")
|
||||||
|
}
|
||||||
|
var ref models.JobAppReference
|
||||||
|
if err := r.Store.DB.First(&ref, id).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if input.Category != nil {
|
||||||
|
ref.Category = *input.Category
|
||||||
|
}
|
||||||
|
if input.Label != nil {
|
||||||
|
ref.Label = *input.Label
|
||||||
|
}
|
||||||
|
if input.Value != nil {
|
||||||
|
ref.Value = *input.Value
|
||||||
|
}
|
||||||
|
if input.SortOrder != nil {
|
||||||
|
ref.SortOrder = *input.SortOrder
|
||||||
|
}
|
||||||
|
if err := r.Store.DB.Save(&ref).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ref, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteJobAppReference is the resolver for the deleteJobAppReference field.
|
||||||
|
func (r *mutationResolver) DeleteJobAppReference(ctx context.Context, id int) (bool, error) {
|
||||||
|
if !IsAdminFromCtx(ctx) {
|
||||||
|
return false, fmt.Errorf("admin access required")
|
||||||
|
}
|
||||||
|
if err := r.Store.DB.Delete(&models.JobAppReference{}, id).Error; err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// JobAppReferences is the resolver for the jobAppReferences field.
|
||||||
|
func (r *queryResolver) JobAppReferences(ctx context.Context) ([]*models.JobAppReference, error) {
|
||||||
|
if !IsAdminFromCtx(ctx) {
|
||||||
|
return nil, fmt.Errorf("admin access required")
|
||||||
|
}
|
||||||
|
var refs []*models.JobAppReference
|
||||||
|
if err := r.Store.DB.Order("category ASC, sort_order ASC, created_at ASC").Find(&refs).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return refs, nil
|
||||||
|
}
|
||||||
|
|
||||||
// JobAppReference returns JobAppReferenceResolver implementation.
|
// JobAppReference returns JobAppReferenceResolver implementation.
|
||||||
func (r *Resolver) JobAppReference() JobAppReferenceResolver { return &jobAppReferenceResolver{r} }
|
func (r *Resolver) JobAppReference() JobAppReferenceResolver { return &jobAppReferenceResolver{r} }
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ package graph
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"adam-french.co.uk/backend/graph/model"
|
||||||
"adam-french.co.uk/backend/models"
|
"adam-french.co.uk/backend/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,6 +18,97 @@ func (r *jobApplicationResolver) ID(ctx context.Context, obj *models.JobApplicat
|
|||||||
return int(obj.ID), nil
|
return int(obj.ID), 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
// JobApplication returns JobApplicationResolver implementation.
|
// JobApplication returns JobApplicationResolver implementation.
|
||||||
func (r *Resolver) JobApplication() JobApplicationResolver { return &jobApplicationResolver{r} }
|
func (r *Resolver) JobApplication() JobApplicationResolver { return &jobApplicationResolver{r} }
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,19 @@ func (r *messageResolver) AuthorID(ctx context.Context, obj *models.Message) (in
|
|||||||
return int(obj.AuthorID), nil
|
return int(obj.AuthorID), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Messages is the resolver for the messages field.
|
||||||
|
func (r *queryResolver) Messages(ctx context.Context) ([]*models.Message, error) {
|
||||||
|
var messages []models.Message
|
||||||
|
if err := r.Store.DB.Order("created_at DESC").Find(&messages).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := make([]*models.Message, len(messages))
|
||||||
|
for i := range messages {
|
||||||
|
result[i] = &messages[i]
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Message returns MessageResolver implementation.
|
// Message returns MessageResolver implementation.
|
||||||
func (r *Resolver) Message() MessageResolver { return &messageResolver{r} }
|
func (r *Resolver) Message() MessageResolver { return &messageResolver{r} }
|
||||||
|
|
||||||
|
|||||||
@@ -7,15 +7,111 @@ package graph
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"adam-french.co.uk/backend/graph/model"
|
||||||
"adam-french.co.uk/backend/models"
|
"adam-french.co.uk/backend/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// CreatePost is the resolver for the createPost field.
|
||||||
|
func (r *mutationResolver) CreatePost(ctx context.Context, input model.CreatePostInput) (*models.Post, error) {
|
||||||
|
if !IsAdminFromCtx(ctx) {
|
||||||
|
return nil, fmt.Errorf("admin access required")
|
||||||
|
}
|
||||||
|
|
||||||
|
userID, ok := UserIDFromCtx(ctx)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
post := models.Post{Title: input.Title, Content: input.Content, AuthorID: userID}
|
||||||
|
if err := r.Store.DB.Create(&post).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &post, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePost is the resolver for the updatePost field.
|
||||||
|
func (r *mutationResolver) UpdatePost(ctx context.Context, id int, input model.UpdatePostInput) (*models.Post, error) {
|
||||||
|
if !IsAdminFromCtx(ctx) {
|
||||||
|
return nil, fmt.Errorf("admin access required")
|
||||||
|
}
|
||||||
|
|
||||||
|
userID, ok := UserIDFromCtx(ctx)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
var post models.Post
|
||||||
|
if err := r.Store.DB.First(&post, id).Error; err != nil {
|
||||||
|
return nil, fmt.Errorf("post not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if post.AuthorID != userID {
|
||||||
|
return nil, fmt.Errorf("you can only update your own posts")
|
||||||
|
}
|
||||||
|
|
||||||
|
post.Title = input.Title
|
||||||
|
post.Content = input.Content
|
||||||
|
if err := r.Store.DB.Save(&post).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &post, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletePost is the resolver for the deletePost field.
|
||||||
|
func (r *mutationResolver) DeletePost(ctx context.Context, id int) (*models.Post, error) {
|
||||||
|
if !IsAdminFromCtx(ctx) {
|
||||||
|
return nil, fmt.Errorf("admin access required")
|
||||||
|
}
|
||||||
|
|
||||||
|
userID, ok := UserIDFromCtx(ctx)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
var post models.Post
|
||||||
|
if err := r.Store.DB.First(&post, id).Error; err != nil {
|
||||||
|
return nil, fmt.Errorf("post not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if post.AuthorID != userID {
|
||||||
|
return nil, fmt.Errorf("you can only delete your own posts")
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Store.DB.Delete(&post)
|
||||||
|
return &post, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ID is the resolver for the id field.
|
// ID is the resolver for the id field.
|
||||||
func (r *postResolver) ID(ctx context.Context, obj *models.Post) (int, error) {
|
func (r *postResolver) ID(ctx context.Context, obj *models.Post) (int, error) {
|
||||||
return int(obj.ID), nil
|
return int(obj.ID), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Posts is the resolver for the posts field.
|
||||||
|
func (r *queryResolver) Posts(ctx context.Context) ([]*models.Post, error) {
|
||||||
|
var posts []models.Post
|
||||||
|
if err := r.Store.DB.Preload("Author").Order("created_at DESC").Find(&posts).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := make([]*models.Post, len(posts))
|
||||||
|
for i := range posts {
|
||||||
|
result[i] = &posts[i]
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post is the resolver for the post field.
|
||||||
|
func (r *queryResolver) Post(ctx context.Context, id int) (*models.Post, error) {
|
||||||
|
var post models.Post
|
||||||
|
if err := r.Store.DB.Preload("Author").First(&post, id).Error; err != nil {
|
||||||
|
return nil, fmt.Errorf("post not found")
|
||||||
|
}
|
||||||
|
return &post, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Post returns PostResolver implementation.
|
// Post returns PostResolver implementation.
|
||||||
func (r *Resolver) Post() PostResolver { return &postResolver{r} }
|
func (r *Resolver) Post() PostResolver { return &postResolver{r} }
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,19 @@ import (
|
|||||||
"adam-french.co.uk/backend/models"
|
"adam-french.co.uk/backend/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// RowingSessions is the resolver for the rowingSessions field.
|
||||||
|
func (r *queryResolver) RowingSessions(ctx context.Context) ([]*models.Rowing, error) {
|
||||||
|
var rows []models.Rowing
|
||||||
|
if err := r.Store.DB.Order("created_at DESC").Find(&rows).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := make([]*models.Rowing, len(rows))
|
||||||
|
for i := range rows {
|
||||||
|
result[i] = &rows[i]
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ID is the resolver for the id field.
|
// ID is the resolver for the id field.
|
||||||
func (r *rowingResolver) ID(ctx context.Context, obj *models.Rowing) (int, error) {
|
func (r *rowingResolver) ID(ctx context.Context, obj *models.Rowing) (int, error) {
|
||||||
return int(obj.ID), nil
|
return int(obj.ID), nil
|
||||||
|
|||||||
@@ -5,620 +5,11 @@ package graph
|
|||||||
// will be copied through when generating and any unknown code will be moved to the end.
|
// will be copied through when generating and any unknown code will be moved to the end.
|
||||||
// Code generated by github.com/99designs/gqlgen version v0.17.88
|
// Code generated by github.com/99designs/gqlgen version v0.17.88
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"adam-french.co.uk/backend/graph/model"
|
|
||||||
"adam-french.co.uk/backend/models"
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Login is the resolver for the login field.
|
|
||||||
func (r *mutationResolver) Login(ctx context.Context, input model.LoginInput) (*model.AuthPayload, error) {
|
|
||||||
gc := GinContextFromCtx(ctx)
|
|
||||||
if gc == nil {
|
|
||||||
return nil, fmt.Errorf("could not get gin context")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !r.Store.LoginLimiter.Allow(gc.ClientIP()) {
|
|
||||||
return nil, fmt.Errorf("too many login attempts, please try again later")
|
|
||||||
}
|
|
||||||
|
|
||||||
var user models.User
|
|
||||||
if err := r.Store.DB.Where("username = ?", input.Username).First(&user).Error; err != nil {
|
|
||||||
return nil, fmt.Errorf("invalid credentials")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := bcrypt.CompareHashAndPassword(user.Password, []byte(input.Password)); err != nil {
|
|
||||||
return nil, fmt.Errorf("invalid credentials")
|
|
||||||
}
|
|
||||||
|
|
||||||
tokens, err := r.Store.Auth.GenerateJWT(&user)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to generate tokens")
|
|
||||||
}
|
|
||||||
|
|
||||||
gc.SetSameSite(http.SameSiteLaxMode)
|
|
||||||
gc.SetCookie(
|
|
||||||
"access_token",
|
|
||||||
tokens.AccessToken,
|
|
||||||
int(r.Store.Auth.Config.AccessTokenLifetime.Seconds()),
|
|
||||||
"/",
|
|
||||||
r.Store.Auth.Config.Domain,
|
|
||||||
true, true,
|
|
||||||
)
|
|
||||||
gc.SetCookie(
|
|
||||||
"refresh_token",
|
|
||||||
tokens.RefreshToken,
|
|
||||||
int(r.Store.Auth.Config.RefreshTokenLifetime.Seconds()),
|
|
||||||
"/",
|
|
||||||
r.Store.Auth.Config.Domain,
|
|
||||||
true, true,
|
|
||||||
)
|
|
||||||
|
|
||||||
return &model.AuthPayload{User: &user}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logout is the resolver for the logout field.
|
|
||||||
func (r *mutationResolver) Logout(ctx context.Context) (bool, error) {
|
|
||||||
gc := GinContextFromCtx(ctx)
|
|
||||||
if gc == nil {
|
|
||||||
return false, fmt.Errorf("could not get gin context")
|
|
||||||
}
|
|
||||||
|
|
||||||
gc.SetSameSite(http.SameSiteLaxMode)
|
|
||||||
gc.SetCookie("access_token", "", -1, "/", r.Store.Auth.Config.Domain, true, true)
|
|
||||||
gc.SetCookie("refresh_token", "", -1, "/", r.Store.Auth.Config.Domain, true, true)
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RefreshToken is the resolver for the refreshToken field.
|
|
||||||
func (r *mutationResolver) RefreshToken(ctx context.Context) (*model.AuthPayload, error) {
|
|
||||||
gc := GinContextFromCtx(ctx)
|
|
||||||
if gc == nil {
|
|
||||||
return nil, fmt.Errorf("could not get gin context")
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshToken, err := gc.Cookie("refresh_token")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unauthorized")
|
|
||||||
}
|
|
||||||
|
|
||||||
claims, err := r.Store.Auth.VerifyJWT(refreshToken)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unauthorized")
|
|
||||||
}
|
|
||||||
|
|
||||||
userIDF, ok := (*claims)["id"].(float64)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("invalid token claims")
|
|
||||||
}
|
|
||||||
|
|
||||||
var user models.User
|
|
||||||
user.ID = uint(userIDF)
|
|
||||||
if err := r.Store.DB.First(&user).Error; err != nil {
|
|
||||||
return nil, fmt.Errorf("user not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
tokens, err := r.Store.Auth.GenerateJWT(&user)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to generate tokens")
|
|
||||||
}
|
|
||||||
|
|
||||||
gc.SetSameSite(http.SameSiteLaxMode)
|
|
||||||
gc.SetCookie(
|
|
||||||
"access_token",
|
|
||||||
tokens.AccessToken,
|
|
||||||
int(r.Store.Auth.Config.AccessTokenLifetime.Seconds()),
|
|
||||||
"/",
|
|
||||||
r.Store.Auth.Config.Domain,
|
|
||||||
true, true,
|
|
||||||
)
|
|
||||||
gc.SetCookie(
|
|
||||||
"refresh_token",
|
|
||||||
tokens.RefreshToken,
|
|
||||||
int(r.Store.Auth.Config.RefreshTokenLifetime.Seconds()),
|
|
||||||
"/",
|
|
||||||
r.Store.Auth.Config.Domain,
|
|
||||||
true, true,
|
|
||||||
)
|
|
||||||
|
|
||||||
return &model.AuthPayload{User: &user}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreatePost is the resolver for the createPost field.
|
|
||||||
func (r *mutationResolver) CreatePost(ctx context.Context, input model.CreatePostInput) (*models.Post, error) {
|
|
||||||
if !IsAdminFromCtx(ctx) {
|
|
||||||
return nil, fmt.Errorf("admin access required")
|
|
||||||
}
|
|
||||||
|
|
||||||
userID, ok := UserIDFromCtx(ctx)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("unauthorized")
|
|
||||||
}
|
|
||||||
|
|
||||||
post := models.Post{Title: input.Title, Content: input.Content, AuthorID: userID}
|
|
||||||
if err := r.Store.DB.Create(&post).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &post, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdatePost is the resolver for the updatePost field.
|
|
||||||
func (r *mutationResolver) UpdatePost(ctx context.Context, id int, input model.UpdatePostInput) (*models.Post, error) {
|
|
||||||
if !IsAdminFromCtx(ctx) {
|
|
||||||
return nil, fmt.Errorf("admin access required")
|
|
||||||
}
|
|
||||||
|
|
||||||
userID, ok := UserIDFromCtx(ctx)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("unauthorized")
|
|
||||||
}
|
|
||||||
|
|
||||||
var post models.Post
|
|
||||||
if err := r.Store.DB.First(&post, id).Error; err != nil {
|
|
||||||
return nil, fmt.Errorf("post not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
if post.AuthorID != userID {
|
|
||||||
return nil, fmt.Errorf("you can only update your own posts")
|
|
||||||
}
|
|
||||||
|
|
||||||
post.Title = input.Title
|
|
||||||
post.Content = input.Content
|
|
||||||
if err := r.Store.DB.Save(&post).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &post, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeletePost is the resolver for the deletePost field.
|
|
||||||
func (r *mutationResolver) DeletePost(ctx context.Context, id int) (*models.Post, error) {
|
|
||||||
if !IsAdminFromCtx(ctx) {
|
|
||||||
return nil, fmt.Errorf("admin access required")
|
|
||||||
}
|
|
||||||
|
|
||||||
userID, ok := UserIDFromCtx(ctx)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("unauthorized")
|
|
||||||
}
|
|
||||||
|
|
||||||
var post models.Post
|
|
||||||
if err := r.Store.DB.First(&post, id).Error; err != nil {
|
|
||||||
return nil, fmt.Errorf("post not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
if post.AuthorID != userID {
|
|
||||||
return nil, fmt.Errorf("you can only delete your own posts")
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Store.DB.Delete(&post)
|
|
||||||
return &post, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateUser is the resolver for the createUser field.
|
|
||||||
func (r *mutationResolver) CreateUser(ctx context.Context, input model.CreateUserInput) (*models.User, error) {
|
|
||||||
if !IsAdminFromCtx(ctx) {
|
|
||||||
return nil, fmt.Errorf("admin access required")
|
|
||||||
}
|
|
||||||
|
|
||||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(input.Password), bcrypt.DefaultCost)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
user := models.User{Username: input.Username, Password: hashedPassword}
|
|
||||||
if err := r.Store.DB.Create(&user).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteUser is the resolver for the deleteUser field.
|
|
||||||
func (r *mutationResolver) DeleteUser(ctx context.Context, id int) (*models.User, error) {
|
|
||||||
if !IsAdminFromCtx(ctx) {
|
|
||||||
return nil, fmt.Errorf("admin access required")
|
|
||||||
}
|
|
||||||
|
|
||||||
var user models.User
|
|
||||||
if err := r.Store.DB.First(&user, id).Error; err != nil {
|
|
||||||
return nil, fmt.Errorf("user not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := r.Store.DB.Delete(&user).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetUserAdmin is the resolver for the setUserAdmin field.
|
|
||||||
func (r *mutationResolver) SetUserAdmin(ctx context.Context, id int, admin bool) (*models.User, error) {
|
|
||||||
if !IsAdminFromCtx(ctx) {
|
|
||||||
return nil, fmt.Errorf("admin access required")
|
|
||||||
}
|
|
||||||
|
|
||||||
callerID, ok := UserIDFromCtx(ctx)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("unauthorized")
|
|
||||||
}
|
|
||||||
|
|
||||||
if uint(id) == callerID {
|
|
||||||
return nil, fmt.Errorf("cannot change your own admin status")
|
|
||||||
}
|
|
||||||
|
|
||||||
var user models.User
|
|
||||||
if err := r.Store.DB.First(&user, id).Error; err != nil {
|
|
||||||
return nil, fmt.Errorf("user not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
user.Admin = admin
|
|
||||||
if err := r.Store.DB.Save(&user).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFavorite is the resolver for the createFavorite field.
|
|
||||||
func (r *mutationResolver) CreateFavorite(ctx context.Context, input model.CreateFavoriteInput) (*models.Favorite, error) {
|
|
||||||
if !IsAdminFromCtx(ctx) {
|
|
||||||
return nil, fmt.Errorf("admin access required")
|
|
||||||
}
|
|
||||||
|
|
||||||
favorite := models.Favorite{Type: input.Type, Name: input.Name, Link: input.Link}
|
|
||||||
if err := r.Store.DB.Create(&favorite).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &favorite, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateActivity is the resolver for the createActivity field.
|
|
||||||
func (r *mutationResolver) CreateActivity(ctx context.Context, input model.CreateActivityInput) (*models.Activity, error) {
|
|
||||||
if !IsAdminFromCtx(ctx) {
|
|
||||||
return nil, fmt.Errorf("admin access required")
|
|
||||||
}
|
|
||||||
|
|
||||||
activity := models.Activity{Type: input.Type, Name: input.Name, Link: input.Link}
|
|
||||||
if err := r.Store.DB.Create(&activity).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateBookmark is the resolver for the createBookmark field.
|
|
||||||
func (r *mutationResolver) CreateBookmark(ctx context.Context, input model.CreateBookmarkInput) (*models.Bookmark, error) {
|
|
||||||
if !IsAdminFromCtx(ctx) {
|
|
||||||
return nil, fmt.Errorf("admin access required")
|
|
||||||
}
|
|
||||||
bookmark := models.Bookmark{Category: input.Category, Name: input.Name, Link: input.Link}
|
|
||||||
if err := r.Store.DB.Create(&bookmark).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &bookmark, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteBookmark is the resolver for the deleteBookmark field.
|
|
||||||
func (r *mutationResolver) DeleteBookmark(ctx context.Context, id int) (*models.Bookmark, error) {
|
|
||||||
if !IsAdminFromCtx(ctx) {
|
|
||||||
return nil, fmt.Errorf("admin access required")
|
|
||||||
}
|
|
||||||
var bookmark models.Bookmark
|
|
||||||
if err := r.Store.DB.First(&bookmark, id).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := r.Store.DB.Delete(&bookmark).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &bookmark, 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateJobAppReference is the resolver for the createJobAppReference field.
|
|
||||||
func (r *mutationResolver) CreateJobAppReference(ctx context.Context, input model.CreateJobAppReferenceInput) (*models.JobAppReference, error) {
|
|
||||||
if !IsAdminFromCtx(ctx) {
|
|
||||||
return nil, fmt.Errorf("admin access required")
|
|
||||||
}
|
|
||||||
ref := models.JobAppReference{
|
|
||||||
Category: input.Category,
|
|
||||||
Label: input.Label,
|
|
||||||
Value: input.Value,
|
|
||||||
}
|
|
||||||
if input.SortOrder != nil {
|
|
||||||
ref.SortOrder = *input.SortOrder
|
|
||||||
}
|
|
||||||
if err := r.Store.DB.Create(&ref).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &ref, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateJobAppReference is the resolver for the updateJobAppReference field.
|
|
||||||
func (r *mutationResolver) UpdateJobAppReference(ctx context.Context, id int, input model.UpdateJobAppReferenceInput) (*models.JobAppReference, error) {
|
|
||||||
if !IsAdminFromCtx(ctx) {
|
|
||||||
return nil, fmt.Errorf("admin access required")
|
|
||||||
}
|
|
||||||
var ref models.JobAppReference
|
|
||||||
if err := r.Store.DB.First(&ref, id).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if input.Category != nil {
|
|
||||||
ref.Category = *input.Category
|
|
||||||
}
|
|
||||||
if input.Label != nil {
|
|
||||||
ref.Label = *input.Label
|
|
||||||
}
|
|
||||||
if input.Value != nil {
|
|
||||||
ref.Value = *input.Value
|
|
||||||
}
|
|
||||||
if input.SortOrder != nil {
|
|
||||||
ref.SortOrder = *input.SortOrder
|
|
||||||
}
|
|
||||||
if err := r.Store.DB.Save(&ref).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &ref, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteJobAppReference is the resolver for the deleteJobAppReference field.
|
|
||||||
func (r *mutationResolver) DeleteJobAppReference(ctx context.Context, id int) (bool, error) {
|
|
||||||
if !IsAdminFromCtx(ctx) {
|
|
||||||
return false, fmt.Errorf("admin access required")
|
|
||||||
}
|
|
||||||
if err := r.Store.DB.Delete(&models.JobAppReference{}, 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) {
|
|
||||||
if !IsAdminFromCtx(ctx) {
|
|
||||||
return nil, fmt.Errorf("admin access required")
|
|
||||||
}
|
|
||||||
var users []models.User
|
|
||||||
if err := r.Store.DB.Find(&users).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
result := make([]*models.User, len(users))
|
|
||||||
for i := range users {
|
|
||||||
result[i] = &users[i]
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// User is the resolver for the user field.
|
|
||||||
func (r *queryResolver) User(ctx context.Context, id int) (*models.User, error) {
|
|
||||||
if !IsAdminFromCtx(ctx) {
|
|
||||||
return nil, fmt.Errorf("admin access required")
|
|
||||||
}
|
|
||||||
var user models.User
|
|
||||||
if err := r.Store.DB.First(&user, id).Error; err != nil {
|
|
||||||
return nil, fmt.Errorf("user not found")
|
|
||||||
}
|
|
||||||
return &user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Posts is the resolver for the posts field.
|
|
||||||
func (r *queryResolver) Posts(ctx context.Context) ([]*models.Post, error) {
|
|
||||||
var posts []models.Post
|
|
||||||
if err := r.Store.DB.Preload("Author").Order("created_at DESC").Find(&posts).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
result := make([]*models.Post, len(posts))
|
|
||||||
for i := range posts {
|
|
||||||
result[i] = &posts[i]
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Post is the resolver for the post field.
|
|
||||||
func (r *queryResolver) Post(ctx context.Context, id int) (*models.Post, error) {
|
|
||||||
var post models.Post
|
|
||||||
if err := r.Store.DB.Preload("Author").First(&post, id).Error; err != nil {
|
|
||||||
return nil, fmt.Errorf("post not found")
|
|
||||||
}
|
|
||||||
return &post, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Activities is the resolver for the activities field.
|
|
||||||
func (r *queryResolver) Activities(ctx context.Context) ([]*models.Activity, error) {
|
|
||||||
var activities []models.Activity
|
|
||||||
if err := r.Store.DB.Order("created_at DESC").Find(&activities).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
result := make([]*models.Activity, len(activities))
|
|
||||||
for i := range activities {
|
|
||||||
result[i] = &activities[i]
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Favorites is the resolver for the favorites field.
|
|
||||||
func (r *queryResolver) Favorites(ctx context.Context) ([]*models.Favorite, error) {
|
|
||||||
var favorites []models.Favorite
|
|
||||||
if err := r.Store.DB.Order("created_at DESC").Find(&favorites).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
result := make([]*models.Favorite, len(favorites))
|
|
||||||
for i := range favorites {
|
|
||||||
result[i] = &favorites[i]
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RowingSessions is the resolver for the rowingSessions field.
|
|
||||||
func (r *queryResolver) RowingSessions(ctx context.Context) ([]*models.Rowing, error) {
|
|
||||||
var rows []models.Rowing
|
|
||||||
if err := r.Store.DB.Order("created_at DESC").Find(&rows).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
result := make([]*models.Rowing, len(rows))
|
|
||||||
for i := range rows {
|
|
||||||
result[i] = &rows[i]
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Messages is the resolver for the messages field.
|
|
||||||
func (r *queryResolver) Messages(ctx context.Context) ([]*models.Message, error) {
|
|
||||||
var messages []models.Message
|
|
||||||
if err := r.Store.DB.Order("created_at DESC").Find(&messages).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
result := make([]*models.Message, len(messages))
|
|
||||||
for i := range messages {
|
|
||||||
result[i] = &messages[i]
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Me is the resolver for the me field.
|
|
||||||
func (r *queryResolver) Me(ctx context.Context) (*models.User, error) {
|
|
||||||
userID, ok := UserIDFromCtx(ctx)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("unauthorized")
|
|
||||||
}
|
|
||||||
|
|
||||||
var user models.User
|
|
||||||
user.ID = userID
|
|
||||||
if err := r.Store.DB.First(&user).Error; err != nil {
|
|
||||||
return nil, fmt.Errorf("user not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bookmarks is the resolver for the bookmarks field.
|
|
||||||
func (r *queryResolver) Bookmarks(ctx context.Context) ([]*models.Bookmark, error) {
|
|
||||||
var bookmarks []models.Bookmark
|
|
||||||
if err := r.Store.DB.Order("category ASC, created_at ASC").Find(&bookmarks).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
result := make([]*models.Bookmark, len(bookmarks))
|
|
||||||
for i := range bookmarks {
|
|
||||||
result[i] = &bookmarks[i]
|
|
||||||
}
|
|
||||||
return result, 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// JobAppReferences is the resolver for the jobAppReferences field.
|
|
||||||
func (r *queryResolver) JobAppReferences(ctx context.Context) ([]*models.JobAppReference, error) {
|
|
||||||
if !IsAdminFromCtx(ctx) {
|
|
||||||
return nil, fmt.Errorf("admin access required")
|
|
||||||
}
|
|
||||||
var refs []*models.JobAppReference
|
|
||||||
if err := r.Store.DB.Order("category ASC, sort_order ASC, created_at ASC").Find(&refs).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return refs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mutation returns MutationResolver implementation.
|
// Mutation returns MutationResolver implementation.
|
||||||
func (r *Resolver) Mutation() MutationResolver { return &mutationResolver{r} }
|
func (r *Resolver) Mutation() MutationResolver { return &mutationResolver{r} }
|
||||||
|
|
||||||
// Query returns QueryResolver implementation.
|
// Query returns QueryResolver implementation.
|
||||||
func (r *Resolver) Query() QueryResolver { return &queryResolver{r} }
|
func (r *Resolver) Query() QueryResolver { return &queryResolver{r} }
|
||||||
|
|
||||||
type (
|
type mutationResolver struct{ *Resolver }
|
||||||
mutationResolver struct{ *Resolver }
|
type queryResolver struct{ *Resolver }
|
||||||
queryResolver struct{ *Resolver }
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -12,3 +12,11 @@ input CreateActivityInput {
|
|||||||
name: String!
|
name: String!
|
||||||
link: String
|
link: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extend type Query {
|
||||||
|
activities: [Activity!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
extend type Mutation {
|
||||||
|
createActivity(input: CreateActivityInput!): Activity!
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,3 +6,13 @@ input LoginInput {
|
|||||||
type AuthPayload {
|
type AuthPayload {
|
||||||
user: User!
|
user: User!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extend type Query {
|
||||||
|
me: User
|
||||||
|
}
|
||||||
|
|
||||||
|
extend type Mutation {
|
||||||
|
login(input: LoginInput!): AuthPayload!
|
||||||
|
logout: Boolean!
|
||||||
|
refreshToken: AuthPayload!
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,3 +12,12 @@ input CreateBookmarkInput {
|
|||||||
name: String!
|
name: String!
|
||||||
link: String!
|
link: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extend type Query {
|
||||||
|
bookmarks: [Bookmark!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
extend type Mutation {
|
||||||
|
createBookmark(input: CreateBookmarkInput!): Bookmark!
|
||||||
|
deleteBookmark(id: ID!): Bookmark!
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,3 +12,11 @@ input CreateFavoriteInput {
|
|||||||
name: String!
|
name: String!
|
||||||
link: String
|
link: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extend type Query {
|
||||||
|
favorites: [Favorite!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
extend type Mutation {
|
||||||
|
createFavorite(input: CreateFavoriteInput!): Favorite!
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,3 +6,7 @@ type GiteaFeedItem {
|
|||||||
commitMessage: String!
|
commitMessage: String!
|
||||||
createdAt: Time!
|
createdAt: Time!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extend type Query {
|
||||||
|
giteaFeed: GiteaFeedItem
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,3 +21,13 @@ input UpdateJobAppReferenceInput {
|
|||||||
value: String
|
value: String
|
||||||
sortOrder: Int
|
sortOrder: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extend type Query {
|
||||||
|
jobAppReferences: [JobAppReference!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
extend type Mutation {
|
||||||
|
createJobAppReference(input: CreateJobAppReferenceInput!): JobAppReference!
|
||||||
|
updateJobAppReference(id: ID!, input: UpdateJobAppReferenceInput!): JobAppReference!
|
||||||
|
deleteJobAppReference(id: ID!): Boolean!
|
||||||
|
}
|
||||||
|
|||||||
@@ -30,3 +30,14 @@ input UpdateJobApplicationInput {
|
|||||||
notes: String
|
notes: String
|
||||||
appliedAt: Time
|
appliedAt: Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extend type Query {
|
||||||
|
jobApplications: [JobApplication!]!
|
||||||
|
jobApplication(id: ID!): JobApplication
|
||||||
|
}
|
||||||
|
|
||||||
|
extend type Mutation {
|
||||||
|
createJobApplication(input: CreateJobApplicationInput!): JobApplication!
|
||||||
|
updateJobApplication(id: ID!, input: UpdateJobApplicationInput!): JobApplication!
|
||||||
|
deleteJobApplication(id: ID!): Boolean!
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,3 +5,7 @@ type Message {
|
|||||||
fileUrl: String
|
fileUrl: String
|
||||||
createdAt: Time!
|
createdAt: Time!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extend type Query {
|
||||||
|
messages: [Message!]!
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,3 +16,14 @@ input UpdatePostInput {
|
|||||||
title: String!
|
title: String!
|
||||||
content: String!
|
content: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extend type Query {
|
||||||
|
posts: [Post!]!
|
||||||
|
post(id: ID!): Post
|
||||||
|
}
|
||||||
|
|
||||||
|
extend type Mutation {
|
||||||
|
createPost(input: CreatePostInput!): Post!
|
||||||
|
updatePost(id: ID!, input: UpdatePostInput!): Post!
|
||||||
|
deletePost(id: ID!): Post!
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,3 +7,7 @@ type Rowing {
|
|||||||
timePer500m: Float!
|
timePer500m: Float!
|
||||||
calories: Float!
|
calories: Float!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extend type Query {
|
||||||
|
rowingSessions: [Rowing!]!
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,43 +1,4 @@
|
|||||||
scalar Time
|
scalar Time
|
||||||
|
|
||||||
type Query {
|
type Query
|
||||||
users: [User!]!
|
type Mutation
|
||||||
user(id: ID!): User
|
|
||||||
posts: [Post!]!
|
|
||||||
post(id: ID!): Post
|
|
||||||
activities: [Activity!]!
|
|
||||||
favorites: [Favorite!]!
|
|
||||||
rowingSessions: [Rowing!]!
|
|
||||||
messages: [Message!]!
|
|
||||||
spotifyListening: SpotifyPlaying
|
|
||||||
spotifyRecent: [SpotifyRecentItem!]
|
|
||||||
giteaFeed: GiteaFeedItem
|
|
||||||
steamStatus: SteamStatus
|
|
||||||
me: User
|
|
||||||
bookmarks: [Bookmark!]!
|
|
||||||
jobApplications: [JobApplication!]!
|
|
||||||
jobApplication(id: ID!): JobApplication
|
|
||||||
jobAppReferences: [JobAppReference!]!
|
|
||||||
}
|
|
||||||
|
|
||||||
type Mutation {
|
|
||||||
login(input: LoginInput!): AuthPayload!
|
|
||||||
logout: Boolean!
|
|
||||||
refreshToken: AuthPayload!
|
|
||||||
createPost(input: CreatePostInput!): Post!
|
|
||||||
updatePost(id: ID!, input: UpdatePostInput!): Post!
|
|
||||||
deletePost(id: ID!): Post!
|
|
||||||
createUser(input: CreateUserInput!): User!
|
|
||||||
deleteUser(id: ID!): User!
|
|
||||||
setUserAdmin(id: ID!, admin: Boolean!): User!
|
|
||||||
createFavorite(input: CreateFavoriteInput!): Favorite!
|
|
||||||
createActivity(input: CreateActivityInput!): Activity!
|
|
||||||
createJobApplication(input: CreateJobApplicationInput!): JobApplication!
|
|
||||||
updateJobApplication(id: ID!, input: UpdateJobApplicationInput!): JobApplication!
|
|
||||||
createBookmark(input: CreateBookmarkInput!): Bookmark!
|
|
||||||
deleteBookmark(id: ID!): Bookmark!
|
|
||||||
deleteJobApplication(id: ID!): Boolean!
|
|
||||||
createJobAppReference(input: CreateJobAppReferenceInput!): JobAppReference!
|
|
||||||
updateJobAppReference(id: ID!, input: UpdateJobAppReferenceInput!): JobAppReference!
|
|
||||||
deleteJobAppReference(id: ID!): Boolean!
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -26,3 +26,8 @@ type SpotifyRecentItem {
|
|||||||
track: SpotifyTrack!
|
track: SpotifyTrack!
|
||||||
playedAt: Time!
|
playedAt: Time!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extend type Query {
|
||||||
|
spotifyListening: SpotifyPlaying
|
||||||
|
spotifyRecent: [SpotifyRecentItem!]
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,3 +10,7 @@ type SteamStatus {
|
|||||||
online: Boolean!
|
online: Boolean!
|
||||||
recentGames: [SteamGame!]!
|
recentGames: [SteamGame!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extend type Query {
|
||||||
|
steamStatus: SteamStatus
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,3 +10,14 @@ input CreateUserInput {
|
|||||||
username: String!
|
username: String!
|
||||||
password: String!
|
password: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extend type Query {
|
||||||
|
users: [User!]!
|
||||||
|
user(id: ID!): User
|
||||||
|
}
|
||||||
|
|
||||||
|
extend type Mutation {
|
||||||
|
createUser(input: CreateUserInput!): User!
|
||||||
|
deleteUser(id: ID!): User!
|
||||||
|
setUserAdmin(id: ID!, admin: Boolean!): User!
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,58 +1,18 @@
|
|||||||
package graph
|
package graph
|
||||||
|
|
||||||
|
// This file will be automatically regenerated based on the schema, any resolver
|
||||||
|
// implementations
|
||||||
|
// will be copied through when generating and any unknown code will be moved to the end.
|
||||||
|
// Code generated by github.com/99designs/gqlgen version v0.17.88
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"adam-french.co.uk/backend/graph/model"
|
"adam-french.co.uk/backend/graph/model"
|
||||||
"github.com/zmb3/spotify/v2"
|
spotify "github.com/zmb3/spotify/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func mapSpotifyImages(images []spotify.Image) []*model.SpotifyImage {
|
|
||||||
result := make([]*model.SpotifyImage, len(images))
|
|
||||||
for i, img := range images {
|
|
||||||
result[i] = &model.SpotifyImage{URL: img.URL}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapSpotifyTrack(track *spotify.FullTrack) *model.SpotifyTrack {
|
|
||||||
artists := make([]*model.SpotifyArtist, len(track.Artists))
|
|
||||||
for i, a := range track.Artists {
|
|
||||||
artists[i] = &model.SpotifyArtist{Name: a.Name}
|
|
||||||
}
|
|
||||||
return &model.SpotifyTrack{
|
|
||||||
Name: track.Name,
|
|
||||||
Artists: artists,
|
|
||||||
Album: &model.SpotifyAlbum{
|
|
||||||
Name: track.Album.Name,
|
|
||||||
Images: mapSpotifyImages(track.Album.Images),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapRecentItems(items []spotify.RecentlyPlayedItem) []*model.SpotifyRecentItem {
|
|
||||||
result := make([]*model.SpotifyRecentItem, len(items))
|
|
||||||
for i, item := range items {
|
|
||||||
artists := make([]*model.SpotifyArtist, len(item.Track.Artists))
|
|
||||||
for j, a := range item.Track.Artists {
|
|
||||||
artists[j] = &model.SpotifyArtist{Name: a.Name}
|
|
||||||
}
|
|
||||||
result[i] = &model.SpotifyRecentItem{
|
|
||||||
PlayedAt: item.PlayedAt,
|
|
||||||
Track: &model.SpotifyTrack{
|
|
||||||
Name: item.Track.Name,
|
|
||||||
Artists: artists,
|
|
||||||
Album: &model.SpotifyAlbum{
|
|
||||||
Name: item.Track.Album.Name,
|
|
||||||
Images: mapSpotifyImages(item.Track.Album.Images),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// SpotifyListening is the resolver for the spotifyListening field.
|
// SpotifyListening is the resolver for the spotifyListening field.
|
||||||
func (r *queryResolver) SpotifyListening(ctx context.Context) (*model.SpotifyPlaying, error) {
|
func (r *queryResolver) SpotifyListening(ctx context.Context) (*model.SpotifyPlaying, error) {
|
||||||
if r.Store.SpotifyClient == nil {
|
if r.Store.SpotifyClient == nil {
|
||||||
|
|||||||
51
backend/graph/spotify_helpers.go
Normal file
51
backend/graph/spotify_helpers.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package graph
|
||||||
|
|
||||||
|
import (
|
||||||
|
"adam-french.co.uk/backend/graph/model"
|
||||||
|
"github.com/zmb3/spotify/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func mapSpotifyImages(images []spotify.Image) []*model.SpotifyImage {
|
||||||
|
result := make([]*model.SpotifyImage, len(images))
|
||||||
|
for i, img := range images {
|
||||||
|
result[i] = &model.SpotifyImage{URL: img.URL}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapSpotifyTrack(track *spotify.FullTrack) *model.SpotifyTrack {
|
||||||
|
artists := make([]*model.SpotifyArtist, len(track.Artists))
|
||||||
|
for i, a := range track.Artists {
|
||||||
|
artists[i] = &model.SpotifyArtist{Name: a.Name}
|
||||||
|
}
|
||||||
|
return &model.SpotifyTrack{
|
||||||
|
Name: track.Name,
|
||||||
|
Artists: artists,
|
||||||
|
Album: &model.SpotifyAlbum{
|
||||||
|
Name: track.Album.Name,
|
||||||
|
Images: mapSpotifyImages(track.Album.Images),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapRecentItems(items []spotify.RecentlyPlayedItem) []*model.SpotifyRecentItem {
|
||||||
|
result := make([]*model.SpotifyRecentItem, len(items))
|
||||||
|
for i, item := range items {
|
||||||
|
artists := make([]*model.SpotifyArtist, len(item.Track.Artists))
|
||||||
|
for j, a := range item.Track.Artists {
|
||||||
|
artists[j] = &model.SpotifyArtist{Name: a.Name}
|
||||||
|
}
|
||||||
|
result[i] = &model.SpotifyRecentItem{
|
||||||
|
PlayedAt: item.PlayedAt,
|
||||||
|
Track: &model.SpotifyTrack{
|
||||||
|
Name: item.Track.Name,
|
||||||
|
Artists: artists,
|
||||||
|
Album: &model.SpotifyAlbum{
|
||||||
|
Name: item.Track.Album.Name,
|
||||||
|
Images: mapSpotifyImages(item.Track.Album.Images),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
@@ -1,28 +1,18 @@
|
|||||||
package graph
|
package graph
|
||||||
|
|
||||||
|
// This file will be automatically regenerated based on the schema, any resolver
|
||||||
|
// implementations
|
||||||
|
// will be copied through when generating and any unknown code will be moved to the end.
|
||||||
|
// Code generated by github.com/99designs/gqlgen version v0.17.88
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"adam-french.co.uk/backend/graph/model"
|
"adam-french.co.uk/backend/graph/model"
|
||||||
"adam-french.co.uk/backend/services"
|
"adam-french.co.uk/backend/services"
|
||||||
)
|
)
|
||||||
|
|
||||||
func mapSteamGames(games []services.SteamRecentGame) []*model.SteamGame {
|
|
||||||
result := make([]*model.SteamGame, len(games))
|
|
||||||
for i, g := range games {
|
|
||||||
result[i] = &model.SteamGame{
|
|
||||||
AppID: g.AppID,
|
|
||||||
Name: g.Name,
|
|
||||||
Playtime2Weeks: g.Playtime2Weeks,
|
|
||||||
PlaytimeForever: g.PlaytimeForever,
|
|
||||||
HeaderImageURL: fmt.Sprintf("https://cdn.akamai.steamstatic.com/steam/apps/%d/header.jpg", g.AppID),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// SteamStatus is the resolver for the steamStatus field.
|
// SteamStatus is the resolver for the steamStatus field.
|
||||||
func (r *queryResolver) SteamStatus(ctx context.Context) (*model.SteamStatus, error) {
|
func (r *queryResolver) SteamStatus(ctx context.Context) (*model.SteamStatus, error) {
|
||||||
if r.Store.SteamAPIKey == "" {
|
if r.Store.SteamAPIKey == "" {
|
||||||
|
|||||||
22
backend/graph/steam_helpers.go
Normal file
22
backend/graph/steam_helpers.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package graph
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"adam-french.co.uk/backend/graph/model"
|
||||||
|
"adam-french.co.uk/backend/services"
|
||||||
|
)
|
||||||
|
|
||||||
|
func mapSteamGames(games []services.SteamRecentGame) []*model.SteamGame {
|
||||||
|
result := make([]*model.SteamGame, len(games))
|
||||||
|
for i, g := range games {
|
||||||
|
result[i] = &model.SteamGame{
|
||||||
|
AppID: g.AppID,
|
||||||
|
Name: g.Name,
|
||||||
|
Playtime2Weeks: g.Playtime2Weeks,
|
||||||
|
PlaytimeForever: g.PlaytimeForever,
|
||||||
|
HeaderImageURL: fmt.Sprintf("https://cdn.akamai.steamstatic.com/steam/apps/%d/header.jpg", g.AppID),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
@@ -7,10 +7,106 @@ package graph
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"adam-french.co.uk/backend/graph/model"
|
||||||
"adam-french.co.uk/backend/models"
|
"adam-french.co.uk/backend/models"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// CreateUser is the resolver for the createUser field.
|
||||||
|
func (r *mutationResolver) CreateUser(ctx context.Context, input model.CreateUserInput) (*models.User, error) {
|
||||||
|
if !IsAdminFromCtx(ctx) {
|
||||||
|
return nil, fmt.Errorf("admin access required")
|
||||||
|
}
|
||||||
|
|
||||||
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(input.Password), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
user := models.User{Username: input.Username, Password: hashedPassword}
|
||||||
|
if err := r.Store.DB.Create(&user).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteUser is the resolver for the deleteUser field.
|
||||||
|
func (r *mutationResolver) DeleteUser(ctx context.Context, id int) (*models.User, error) {
|
||||||
|
if !IsAdminFromCtx(ctx) {
|
||||||
|
return nil, fmt.Errorf("admin access required")
|
||||||
|
}
|
||||||
|
|
||||||
|
var user models.User
|
||||||
|
if err := r.Store.DB.First(&user, id).Error; err != nil {
|
||||||
|
return nil, fmt.Errorf("user not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.Store.DB.Delete(&user).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUserAdmin is the resolver for the setUserAdmin field.
|
||||||
|
func (r *mutationResolver) SetUserAdmin(ctx context.Context, id int, admin bool) (*models.User, error) {
|
||||||
|
if !IsAdminFromCtx(ctx) {
|
||||||
|
return nil, fmt.Errorf("admin access required")
|
||||||
|
}
|
||||||
|
|
||||||
|
callerID, ok := UserIDFromCtx(ctx)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
if uint(id) == callerID {
|
||||||
|
return nil, fmt.Errorf("cannot change your own admin status")
|
||||||
|
}
|
||||||
|
|
||||||
|
var user models.User
|
||||||
|
if err := r.Store.DB.First(&user, id).Error; err != nil {
|
||||||
|
return nil, fmt.Errorf("user not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
user.Admin = admin
|
||||||
|
if err := r.Store.DB.Save(&user).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Users is the resolver for the users field.
|
||||||
|
func (r *queryResolver) Users(ctx context.Context) ([]*models.User, error) {
|
||||||
|
if !IsAdminFromCtx(ctx) {
|
||||||
|
return nil, fmt.Errorf("admin access required")
|
||||||
|
}
|
||||||
|
var users []models.User
|
||||||
|
if err := r.Store.DB.Find(&users).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := make([]*models.User, len(users))
|
||||||
|
for i := range users {
|
||||||
|
result[i] = &users[i]
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// User is the resolver for the user field.
|
||||||
|
func (r *queryResolver) User(ctx context.Context, id int) (*models.User, error) {
|
||||||
|
if !IsAdminFromCtx(ctx) {
|
||||||
|
return nil, fmt.Errorf("admin access required")
|
||||||
|
}
|
||||||
|
var user models.User
|
||||||
|
if err := r.Store.DB.First(&user, id).Error; err != nil {
|
||||||
|
return nil, fmt.Errorf("user not found")
|
||||||
|
}
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ID is the resolver for the id field.
|
// ID is the resolver for the id field.
|
||||||
func (r *userResolver) ID(ctx context.Context, obj *models.User) (int, error) {
|
func (r *userResolver) ID(ctx context.Context, obj *models.User) (int, error) {
|
||||||
return int(obj.ID), nil
|
return int(obj.ID), nil
|
||||||
|
|||||||
Reference in New Issue
Block a user