diff --git a/backend/gqlgen.yml b/backend/gqlgen.yml
index 1f25230..4307b67 100644
--- a/backend/gqlgen.yml
+++ b/backend/gqlgen.yml
@@ -57,6 +57,12 @@ models:
fields:
deletedAt:
resolver: false
+ Bookmark:
+ model:
+ - adam-french.co.uk/backend/models.Bookmark
+ fields:
+ deletedAt:
+ resolver: false
JobApplication:
model:
- adam-french.co.uk/backend/models.JobApplication
diff --git a/backend/graph/bookmark.resolvers.go b/backend/graph/bookmark.resolvers.go
new file mode 100644
index 0000000..153d9a3
--- /dev/null
+++ b/backend/graph/bookmark.resolvers.go
@@ -0,0 +1,22 @@
+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"
+
+ "adam-french.co.uk/backend/models"
+)
+
+// ID is the resolver for the id field.
+func (r *bookmarkResolver) ID(ctx context.Context, obj *models.Bookmark) (int, error) {
+ return int(obj.ID), nil
+}
+
+// Bookmark returns BookmarkResolver implementation.
+func (r *Resolver) Bookmark() BookmarkResolver { return &bookmarkResolver{r} }
+
+type bookmarkResolver struct{ *Resolver }
diff --git a/backend/graph/generated.go b/backend/graph/generated.go
index a25adfa..8255da8 100644
--- a/backend/graph/generated.go
+++ b/backend/graph/generated.go
@@ -31,6 +31,7 @@ type Config = graphql.Config[ResolverRoot, DirectiveRoot, ComplexityRoot]
type ResolverRoot interface {
Activity() ActivityResolver
+ Bookmark() BookmarkResolver
Favorite() FavoriteResolver
JobApplication() JobApplicationResolver
Message() MessageResolver
@@ -58,6 +59,15 @@ type ComplexityRoot struct {
User func(childComplexity int) int
}
+ Bookmark struct {
+ Category func(childComplexity int) int
+ CreatedAt func(childComplexity int) int
+ ID func(childComplexity int) int
+ Link func(childComplexity int) int
+ Name func(childComplexity int) int
+ UpdatedAt func(childComplexity int) int
+ }
+
Favorite struct {
CreatedAt func(childComplexity int) int
ID func(childComplexity int) int
@@ -99,10 +109,12 @@ type ComplexityRoot struct {
Mutation struct {
CreateActivity func(childComplexity int, input model.CreateActivityInput) int
+ CreateBookmark func(childComplexity int, input model.CreateBookmarkInput) int
CreateFavorite func(childComplexity int, input model.CreateFavoriteInput) int
CreateJobApplication func(childComplexity int, input model.CreateJobApplicationInput) int
CreatePost func(childComplexity int, input model.CreatePostInput) int
CreateUser func(childComplexity int, input model.CreateUserInput) int
+ DeleteBookmark func(childComplexity int, id int) int
DeleteJobApplication func(childComplexity int, id int) int
DeletePost func(childComplexity int, id int) int
DeleteUser func(childComplexity int, id int) int
@@ -125,6 +137,7 @@ type ComplexityRoot struct {
Query struct {
Activities func(childComplexity int) int
+ Bookmarks func(childComplexity int) int
Favorites func(childComplexity int) int
GiteaFeed func(childComplexity int) int
JobApplication func(childComplexity int, id int) int
@@ -205,6 +218,9 @@ type ComplexityRoot struct {
type ActivityResolver interface {
ID(ctx context.Context, obj *models.Activity) (int, error)
}
+type BookmarkResolver interface {
+ ID(ctx context.Context, obj *models.Bookmark) (int, error)
+}
type FavoriteResolver interface {
ID(ctx context.Context, obj *models.Favorite) (int, error)
}
@@ -230,6 +246,8 @@ type MutationResolver interface {
CreateActivity(ctx context.Context, input model.CreateActivityInput) (*models.Activity, error)
CreateJobApplication(ctx context.Context, input model.CreateJobApplicationInput) (*models.JobApplication, error)
UpdateJobApplication(ctx context.Context, id int, input model.UpdateJobApplicationInput) (*models.JobApplication, error)
+ CreateBookmark(ctx context.Context, input model.CreateBookmarkInput) (*models.Bookmark, error)
+ DeleteBookmark(ctx context.Context, id int) (*models.Bookmark, error)
DeleteJobApplication(ctx context.Context, id int) (bool, error)
}
type PostResolver interface {
@@ -249,6 +267,7 @@ type QueryResolver interface {
GiteaFeed(ctx context.Context) (*model.GiteaFeedItem, error)
SteamStatus(ctx context.Context) (*model.SteamStatus, error)
Me(ctx context.Context) (*models.User, error)
+ Bookmarks(ctx context.Context) ([]*models.Bookmark, error)
JobApplications(ctx context.Context) ([]*models.JobApplication, error)
JobApplication(ctx context.Context, id int) (*models.JobApplication, error)
}
@@ -320,6 +339,43 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return e.ComplexityRoot.AuthPayload.User(childComplexity), true
+ case "Bookmark.category":
+ if e.ComplexityRoot.Bookmark.Category == nil {
+ break
+ }
+
+ return e.ComplexityRoot.Bookmark.Category(childComplexity), true
+ case "Bookmark.createdAt":
+ if e.ComplexityRoot.Bookmark.CreatedAt == nil {
+ break
+ }
+
+ return e.ComplexityRoot.Bookmark.CreatedAt(childComplexity), true
+ case "Bookmark.id":
+ if e.ComplexityRoot.Bookmark.ID == nil {
+ break
+ }
+
+ return e.ComplexityRoot.Bookmark.ID(childComplexity), true
+ case "Bookmark.link":
+ if e.ComplexityRoot.Bookmark.Link == nil {
+ break
+ }
+
+ return e.ComplexityRoot.Bookmark.Link(childComplexity), true
+ case "Bookmark.name":
+ if e.ComplexityRoot.Bookmark.Name == nil {
+ break
+ }
+
+ return e.ComplexityRoot.Bookmark.Name(childComplexity), true
+ case "Bookmark.updatedAt":
+ if e.ComplexityRoot.Bookmark.UpdatedAt == nil {
+ break
+ }
+
+ return e.ComplexityRoot.Bookmark.UpdatedAt(childComplexity), true
+
case "Favorite.createdAt":
if e.ComplexityRoot.Favorite.CreatedAt == nil {
break
@@ -497,6 +553,17 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
}
return e.ComplexityRoot.Mutation.CreateActivity(childComplexity, args["input"].(model.CreateActivityInput)), true
+ case "Mutation.createBookmark":
+ if e.ComplexityRoot.Mutation.CreateBookmark == nil {
+ break
+ }
+
+ args, err := ec.field_Mutation_createBookmark_args(ctx, rawArgs)
+ if err != nil {
+ return 0, false
+ }
+
+ return e.ComplexityRoot.Mutation.CreateBookmark(childComplexity, args["input"].(model.CreateBookmarkInput)), true
case "Mutation.createFavorite":
if e.ComplexityRoot.Mutation.CreateFavorite == nil {
break
@@ -541,6 +608,17 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
}
return e.ComplexityRoot.Mutation.CreateUser(childComplexity, args["input"].(model.CreateUserInput)), true
+ case "Mutation.deleteBookmark":
+ if e.ComplexityRoot.Mutation.DeleteBookmark == nil {
+ break
+ }
+
+ args, err := ec.field_Mutation_deleteBookmark_args(ctx, rawArgs)
+ if err != nil {
+ return 0, false
+ }
+
+ return e.ComplexityRoot.Mutation.DeleteBookmark(childComplexity, args["id"].(int)), true
case "Mutation.deleteJobApplication":
if e.ComplexityRoot.Mutation.DeleteJobApplication == nil {
break
@@ -674,6 +752,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
}
return e.ComplexityRoot.Query.Activities(childComplexity), true
+ case "Query.bookmarks":
+ if e.ComplexityRoot.Query.Bookmarks == nil {
+ break
+ }
+
+ return e.ComplexityRoot.Query.Bookmarks(childComplexity), true
case "Query.favorites":
if e.ComplexityRoot.Query.Favorites == nil {
break
@@ -974,6 +1058,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
ec := newExecutionContext(opCtx, e, make(chan graphql.DeferredResult))
inputUnmarshalMap := graphql.BuildUnmarshalerMap(
ec.unmarshalInputCreateActivityInput,
+ ec.unmarshalInputCreateBookmarkInput,
ec.unmarshalInputCreateFavoriteInput,
ec.unmarshalInputCreateJobApplicationInput,
ec.unmarshalInputCreatePostInput,
@@ -1055,7 +1140,7 @@ func newExecutionContext(
}
}
-//go:embed "schema/activity.graphql" "schema/auth.graphql" "schema/favorite.graphql" "schema/gitea.graphql" "schema/job_application.graphql" "schema/message.graphql" "schema/post.graphql" "schema/rowing.graphql" "schema/schema.graphql" "schema/spotify.graphql" "schema/steam.graphql" "schema/user.graphql"
+//go:embed "schema/activity.graphql" "schema/auth.graphql" "schema/bookmark.graphql" "schema/favorite.graphql" "schema/gitea.graphql" "schema/job_application.graphql" "schema/message.graphql" "schema/post.graphql" "schema/rowing.graphql" "schema/schema.graphql" "schema/spotify.graphql" "schema/steam.graphql" "schema/user.graphql"
var sourcesFS embed.FS
func sourceData(filename string) string {
@@ -1069,6 +1154,7 @@ func sourceData(filename string) string {
var sources = []*ast.Source{
{Name: "schema/activity.graphql", Input: sourceData("schema/activity.graphql"), BuiltIn: false},
{Name: "schema/auth.graphql", Input: sourceData("schema/auth.graphql"), BuiltIn: false},
+ {Name: "schema/bookmark.graphql", Input: sourceData("schema/bookmark.graphql"), BuiltIn: false},
{Name: "schema/favorite.graphql", Input: sourceData("schema/favorite.graphql"), BuiltIn: false},
{Name: "schema/gitea.graphql", Input: sourceData("schema/gitea.graphql"), BuiltIn: false},
{Name: "schema/job_application.graphql", Input: sourceData("schema/job_application.graphql"), BuiltIn: false},
@@ -1097,6 +1183,17 @@ func (ec *executionContext) field_Mutation_createActivity_args(ctx context.Conte
return args, nil
}
+func (ec *executionContext) field_Mutation_createBookmark_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
+ var err error
+ args := map[string]any{}
+ arg0, err := graphql.ProcessArgField(ctx, rawArgs, "input", ec.unmarshalNCreateBookmarkInput2adamᚑfrenchᚗcoᚗukᚋbackendᚋgraphᚋmodelᚐCreateBookmarkInput)
+ if err != nil {
+ return nil, err
+ }
+ args["input"] = arg0
+ return args, nil
+}
+
func (ec *executionContext) field_Mutation_createFavorite_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error
args := map[string]any{}
@@ -1141,6 +1238,17 @@ func (ec *executionContext) field_Mutation_createUser_args(ctx context.Context,
return args, nil
}
+func (ec *executionContext) field_Mutation_deleteBookmark_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
+ var err error
+ args := map[string]any{}
+ arg0, err := graphql.ProcessArgField(ctx, rawArgs, "id", ec.unmarshalNID2int)
+ if err != nil {
+ return nil, err
+ }
+ args["id"] = arg0
+ return args, nil
+}
+
func (ec *executionContext) field_Mutation_deleteJobApplication_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error
args := map[string]any{}
@@ -1544,6 +1652,180 @@ func (ec *executionContext) fieldContext_AuthPayload_user(_ context.Context, fie
return fc, nil
}
+func (ec *executionContext) _Bookmark_id(ctx context.Context, field graphql.CollectedField, obj *models.Bookmark) (ret graphql.Marshaler) {
+ return graphql.ResolveField(
+ ctx,
+ ec.OperationContext,
+ field,
+ ec.fieldContext_Bookmark_id,
+ func(ctx context.Context) (any, error) {
+ return ec.Resolvers.Bookmark().ID(ctx, obj)
+ },
+ nil,
+ ec.marshalNID2int,
+ true,
+ true,
+ )
+}
+
+func (ec *executionContext) fieldContext_Bookmark_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
+ Object: "Bookmark",
+ Field: field,
+ IsMethod: true,
+ IsResolver: true,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ return nil, errors.New("field of type ID does not have child fields")
+ },
+ }
+ return fc, nil
+}
+
+func (ec *executionContext) _Bookmark_createdAt(ctx context.Context, field graphql.CollectedField, obj *models.Bookmark) (ret graphql.Marshaler) {
+ return graphql.ResolveField(
+ ctx,
+ ec.OperationContext,
+ field,
+ ec.fieldContext_Bookmark_createdAt,
+ func(ctx context.Context) (any, error) {
+ return obj.CreatedAt, nil
+ },
+ nil,
+ ec.marshalNTime2timeᚐTime,
+ true,
+ true,
+ )
+}
+
+func (ec *executionContext) fieldContext_Bookmark_createdAt(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
+ Object: "Bookmark",
+ Field: field,
+ IsMethod: false,
+ IsResolver: false,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ return nil, errors.New("field of type Time does not have child fields")
+ },
+ }
+ return fc, nil
+}
+
+func (ec *executionContext) _Bookmark_updatedAt(ctx context.Context, field graphql.CollectedField, obj *models.Bookmark) (ret graphql.Marshaler) {
+ return graphql.ResolveField(
+ ctx,
+ ec.OperationContext,
+ field,
+ ec.fieldContext_Bookmark_updatedAt,
+ func(ctx context.Context) (any, error) {
+ return obj.UpdatedAt, nil
+ },
+ nil,
+ ec.marshalNTime2timeᚐTime,
+ true,
+ true,
+ )
+}
+
+func (ec *executionContext) fieldContext_Bookmark_updatedAt(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
+ Object: "Bookmark",
+ Field: field,
+ IsMethod: false,
+ IsResolver: false,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ return nil, errors.New("field of type Time does not have child fields")
+ },
+ }
+ return fc, nil
+}
+
+func (ec *executionContext) _Bookmark_category(ctx context.Context, field graphql.CollectedField, obj *models.Bookmark) (ret graphql.Marshaler) {
+ return graphql.ResolveField(
+ ctx,
+ ec.OperationContext,
+ field,
+ ec.fieldContext_Bookmark_category,
+ func(ctx context.Context) (any, error) {
+ return obj.Category, nil
+ },
+ nil,
+ ec.marshalNString2string,
+ true,
+ true,
+ )
+}
+
+func (ec *executionContext) fieldContext_Bookmark_category(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
+ Object: "Bookmark",
+ Field: field,
+ IsMethod: false,
+ IsResolver: false,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ return nil, errors.New("field of type String does not have child fields")
+ },
+ }
+ return fc, nil
+}
+
+func (ec *executionContext) _Bookmark_name(ctx context.Context, field graphql.CollectedField, obj *models.Bookmark) (ret graphql.Marshaler) {
+ return graphql.ResolveField(
+ ctx,
+ ec.OperationContext,
+ field,
+ ec.fieldContext_Bookmark_name,
+ func(ctx context.Context) (any, error) {
+ return obj.Name, nil
+ },
+ nil,
+ ec.marshalNString2string,
+ true,
+ true,
+ )
+}
+
+func (ec *executionContext) fieldContext_Bookmark_name(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
+ Object: "Bookmark",
+ Field: field,
+ IsMethod: false,
+ IsResolver: false,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ return nil, errors.New("field of type String does not have child fields")
+ },
+ }
+ return fc, nil
+}
+
+func (ec *executionContext) _Bookmark_link(ctx context.Context, field graphql.CollectedField, obj *models.Bookmark) (ret graphql.Marshaler) {
+ return graphql.ResolveField(
+ ctx,
+ ec.OperationContext,
+ field,
+ ec.fieldContext_Bookmark_link,
+ func(ctx context.Context) (any, error) {
+ return obj.Link, nil
+ },
+ nil,
+ ec.marshalNString2string,
+ true,
+ true,
+ )
+}
+
+func (ec *executionContext) fieldContext_Bookmark_link(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
+ Object: "Bookmark",
+ Field: field,
+ IsMethod: false,
+ IsResolver: false,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ return nil, errors.New("field of type String does not have child fields")
+ },
+ }
+ return fc, nil
+}
+
func (ec *executionContext) _Favorite_id(ctx context.Context, field graphql.CollectedField, obj *models.Favorite) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
@@ -2994,6 +3276,116 @@ func (ec *executionContext) fieldContext_Mutation_updateJobApplication(ctx conte
return fc, nil
}
+func (ec *executionContext) _Mutation_createBookmark(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
+ return graphql.ResolveField(
+ ctx,
+ ec.OperationContext,
+ field,
+ ec.fieldContext_Mutation_createBookmark,
+ func(ctx context.Context) (any, error) {
+ fc := graphql.GetFieldContext(ctx)
+ return ec.Resolvers.Mutation().CreateBookmark(ctx, fc.Args["input"].(model.CreateBookmarkInput))
+ },
+ nil,
+ ec.marshalNBookmark2ᚖadamᚑfrenchᚗcoᚗukᚋbackendᚋmodelsᚐBookmark,
+ true,
+ true,
+ )
+}
+
+func (ec *executionContext) fieldContext_Mutation_createBookmark(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
+ Object: "Mutation",
+ Field: field,
+ IsMethod: true,
+ IsResolver: true,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ switch field.Name {
+ case "id":
+ return ec.fieldContext_Bookmark_id(ctx, field)
+ case "createdAt":
+ return ec.fieldContext_Bookmark_createdAt(ctx, field)
+ case "updatedAt":
+ return ec.fieldContext_Bookmark_updatedAt(ctx, field)
+ case "category":
+ return ec.fieldContext_Bookmark_category(ctx, field)
+ case "name":
+ return ec.fieldContext_Bookmark_name(ctx, field)
+ case "link":
+ return ec.fieldContext_Bookmark_link(ctx, field)
+ }
+ return nil, fmt.Errorf("no field named %q was found under type Bookmark", field.Name)
+ },
+ }
+ defer func() {
+ if r := recover(); r != nil {
+ err = ec.Recover(ctx, r)
+ ec.Error(ctx, err)
+ }
+ }()
+ ctx = graphql.WithFieldContext(ctx, fc)
+ if fc.Args, err = ec.field_Mutation_createBookmark_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
+ ec.Error(ctx, err)
+ return fc, err
+ }
+ return fc, nil
+}
+
+func (ec *executionContext) _Mutation_deleteBookmark(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
+ return graphql.ResolveField(
+ ctx,
+ ec.OperationContext,
+ field,
+ ec.fieldContext_Mutation_deleteBookmark,
+ func(ctx context.Context) (any, error) {
+ fc := graphql.GetFieldContext(ctx)
+ return ec.Resolvers.Mutation().DeleteBookmark(ctx, fc.Args["id"].(int))
+ },
+ nil,
+ ec.marshalNBookmark2ᚖadamᚑfrenchᚗcoᚗukᚋbackendᚋmodelsᚐBookmark,
+ true,
+ true,
+ )
+}
+
+func (ec *executionContext) fieldContext_Mutation_deleteBookmark(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
+ Object: "Mutation",
+ Field: field,
+ IsMethod: true,
+ IsResolver: true,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ switch field.Name {
+ case "id":
+ return ec.fieldContext_Bookmark_id(ctx, field)
+ case "createdAt":
+ return ec.fieldContext_Bookmark_createdAt(ctx, field)
+ case "updatedAt":
+ return ec.fieldContext_Bookmark_updatedAt(ctx, field)
+ case "category":
+ return ec.fieldContext_Bookmark_category(ctx, field)
+ case "name":
+ return ec.fieldContext_Bookmark_name(ctx, field)
+ case "link":
+ return ec.fieldContext_Bookmark_link(ctx, field)
+ }
+ return nil, fmt.Errorf("no field named %q was found under type Bookmark", field.Name)
+ },
+ }
+ defer func() {
+ if r := recover(); r != nil {
+ err = ec.Recover(ctx, r)
+ ec.Error(ctx, err)
+ }
+ }()
+ ctx = graphql.WithFieldContext(ctx, fc)
+ if fc.Args, err = ec.field_Mutation_deleteBookmark_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
+ ec.Error(ctx, err)
+ return fc, err
+ }
+ return fc, nil
+}
+
func (ec *executionContext) _Mutation_deleteJobApplication(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
@@ -3774,6 +4166,49 @@ func (ec *executionContext) fieldContext_Query_me(_ context.Context, field graph
return fc, nil
}
+func (ec *executionContext) _Query_bookmarks(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
+ return graphql.ResolveField(
+ ctx,
+ ec.OperationContext,
+ field,
+ ec.fieldContext_Query_bookmarks,
+ func(ctx context.Context) (any, error) {
+ return ec.Resolvers.Query().Bookmarks(ctx)
+ },
+ nil,
+ ec.marshalNBookmark2ᚕᚖadamᚑfrenchᚗcoᚗukᚋbackendᚋmodelsᚐBookmarkᚄ,
+ true,
+ true,
+ )
+}
+
+func (ec *executionContext) fieldContext_Query_bookmarks(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
+ Object: "Query",
+ Field: field,
+ IsMethod: true,
+ IsResolver: true,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ switch field.Name {
+ case "id":
+ return ec.fieldContext_Bookmark_id(ctx, field)
+ case "createdAt":
+ return ec.fieldContext_Bookmark_createdAt(ctx, field)
+ case "updatedAt":
+ return ec.fieldContext_Bookmark_updatedAt(ctx, field)
+ case "category":
+ return ec.fieldContext_Bookmark_category(ctx, field)
+ case "name":
+ return ec.fieldContext_Bookmark_name(ctx, field)
+ case "link":
+ return ec.fieldContext_Bookmark_link(ctx, field)
+ }
+ return nil, fmt.Errorf("no field named %q was found under type Bookmark", field.Name)
+ },
+ }
+ return fc, nil
+}
+
func (ec *executionContext) _Query_jobApplications(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
@@ -6398,6 +6833,50 @@ func (ec *executionContext) unmarshalInputCreateActivityInput(ctx context.Contex
return it, nil
}
+func (ec *executionContext) unmarshalInputCreateBookmarkInput(ctx context.Context, obj any) (model.CreateBookmarkInput, error) {
+ var it model.CreateBookmarkInput
+ if obj == nil {
+ return it, nil
+ }
+
+ asMap := map[string]any{}
+ for k, v := range obj.(map[string]any) {
+ asMap[k] = v
+ }
+
+ fieldsInOrder := [...]string{"category", "name", "link"}
+ for _, k := range fieldsInOrder {
+ v, ok := asMap[k]
+ if !ok {
+ continue
+ }
+ switch k {
+ case "category":
+ ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("category"))
+ data, err := ec.unmarshalNString2string(ctx, v)
+ if err != nil {
+ return it, err
+ }
+ it.Category = data
+ case "name":
+ ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("name"))
+ data, err := ec.unmarshalNString2string(ctx, v)
+ if err != nil {
+ return it, err
+ }
+ it.Name = data
+ case "link":
+ ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("link"))
+ data, err := ec.unmarshalNString2string(ctx, v)
+ if err != nil {
+ return it, err
+ }
+ it.Link = data
+ }
+ }
+ return it, nil
+}
+
func (ec *executionContext) unmarshalInputCreateFavoriteInput(ctx context.Context, obj any) (model.CreateFavoriteInput, error) {
var it model.CreateFavoriteInput
if obj == nil {
@@ -6873,6 +7352,101 @@ func (ec *executionContext) _AuthPayload(ctx context.Context, sel ast.SelectionS
return out
}
+var bookmarkImplementors = []string{"Bookmark"}
+
+func (ec *executionContext) _Bookmark(ctx context.Context, sel ast.SelectionSet, obj *models.Bookmark) graphql.Marshaler {
+ fields := graphql.CollectFields(ec.OperationContext, sel, bookmarkImplementors)
+
+ out := graphql.NewFieldSet(fields)
+ deferred := make(map[string]*graphql.FieldSet)
+ for i, field := range fields {
+ switch field.Name {
+ case "__typename":
+ out.Values[i] = graphql.MarshalString("Bookmark")
+ case "id":
+ field := field
+
+ innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) {
+ defer func() {
+ if r := recover(); r != nil {
+ ec.Error(ctx, ec.Recover(ctx, r))
+ }
+ }()
+ res = ec._Bookmark_id(ctx, field, obj)
+ if res == graphql.Null {
+ atomic.AddUint32(&fs.Invalids, 1)
+ }
+ return res
+ }
+
+ if field.Deferrable != nil {
+ dfs, ok := deferred[field.Deferrable.Label]
+ di := 0
+ if ok {
+ dfs.AddField(field)
+ di = len(dfs.Values) - 1
+ } else {
+ dfs = graphql.NewFieldSet([]graphql.CollectedField{field})
+ deferred[field.Deferrable.Label] = dfs
+ }
+ dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler {
+ return innerFunc(ctx, dfs)
+ })
+
+ // don't run the out.Concurrently() call below
+ out.Values[i] = graphql.Null
+ continue
+ }
+
+ out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
+ case "createdAt":
+ out.Values[i] = ec._Bookmark_createdAt(ctx, field, obj)
+ if out.Values[i] == graphql.Null {
+ atomic.AddUint32(&out.Invalids, 1)
+ }
+ case "updatedAt":
+ out.Values[i] = ec._Bookmark_updatedAt(ctx, field, obj)
+ if out.Values[i] == graphql.Null {
+ atomic.AddUint32(&out.Invalids, 1)
+ }
+ case "category":
+ out.Values[i] = ec._Bookmark_category(ctx, field, obj)
+ if out.Values[i] == graphql.Null {
+ atomic.AddUint32(&out.Invalids, 1)
+ }
+ case "name":
+ out.Values[i] = ec._Bookmark_name(ctx, field, obj)
+ if out.Values[i] == graphql.Null {
+ atomic.AddUint32(&out.Invalids, 1)
+ }
+ case "link":
+ out.Values[i] = ec._Bookmark_link(ctx, field, obj)
+ if out.Values[i] == graphql.Null {
+ atomic.AddUint32(&out.Invalids, 1)
+ }
+ default:
+ panic("unknown field " + strconv.Quote(field.Name))
+ }
+ }
+ out.Dispatch(ctx)
+ if out.Invalids > 0 {
+ return graphql.Null
+ }
+
+ atomic.AddInt32(&ec.Deferred, int32(len(deferred)))
+
+ for label, dfs := range deferred {
+ ec.ProcessDeferredGroup(graphql.DeferredGroup{
+ Label: label,
+ Path: graphql.GetPath(ctx),
+ FieldSet: dfs,
+ Context: ctx,
+ })
+ }
+
+ return out
+}
+
var favoriteImplementors = []string{"Favorite"}
func (ec *executionContext) _Favorite(ctx context.Context, sel ast.SelectionSet, obj *models.Favorite) graphql.Marshaler {
@@ -7360,6 +7934,20 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
if out.Values[i] == graphql.Null {
out.Invalids++
}
+ case "createBookmark":
+ out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
+ return ec._Mutation_createBookmark(ctx, field)
+ })
+ if out.Values[i] == graphql.Null {
+ out.Invalids++
+ }
+ case "deleteBookmark":
+ out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
+ return ec._Mutation_deleteBookmark(ctx, field)
+ })
+ if out.Values[i] == graphql.Null {
+ out.Invalids++
+ }
case "deleteJobApplication":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_deleteJobApplication(ctx, field)
@@ -7765,6 +8353,28 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
}
+ out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) })
+ case "bookmarks":
+ field := field
+
+ innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) {
+ defer func() {
+ if r := recover(); r != nil {
+ ec.Error(ctx, ec.Recover(ctx, r))
+ }
+ }()
+ res = ec._Query_bookmarks(ctx, field)
+ if res == graphql.Null {
+ atomic.AddUint32(&fs.Invalids, 1)
+ }
+ return res
+ }
+
+ rrm := func(ctx context.Context) graphql.Marshaler {
+ return ec.OperationContext.RootResolverMiddleware(ctx,
+ func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
+ }
+
out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) })
case "jobApplications":
field := field
@@ -8828,6 +9438,36 @@ func (ec *executionContext) marshalNAuthPayload2ᚖadamᚑfrenchᚗcoᚗukᚋbac
return ec._AuthPayload(ctx, sel, v)
}
+func (ec *executionContext) marshalNBookmark2adamᚑfrenchᚗcoᚗukᚋbackendᚋmodelsᚐBookmark(ctx context.Context, sel ast.SelectionSet, v models.Bookmark) graphql.Marshaler {
+ return ec._Bookmark(ctx, sel, &v)
+}
+
+func (ec *executionContext) marshalNBookmark2ᚕᚖadamᚑfrenchᚗcoᚗukᚋbackendᚋmodelsᚐBookmarkᚄ(ctx context.Context, sel ast.SelectionSet, v []*models.Bookmark) graphql.Marshaler {
+ ret := graphql.MarshalSliceConcurrently(ctx, len(v), 0, false, func(ctx context.Context, i int) graphql.Marshaler {
+ fc := graphql.GetFieldContext(ctx)
+ fc.Result = &v[i]
+ return ec.marshalNBookmark2ᚖadamᚑfrenchᚗcoᚗukᚋbackendᚋmodelsᚐBookmark(ctx, sel, v[i])
+ })
+
+ for _, e := range ret {
+ if e == graphql.Null {
+ return graphql.Null
+ }
+ }
+
+ return ret
+}
+
+func (ec *executionContext) marshalNBookmark2ᚖadamᚑfrenchᚗcoᚗukᚋbackendᚋmodelsᚐBookmark(ctx context.Context, sel ast.SelectionSet, v *models.Bookmark) graphql.Marshaler {
+ if v == nil {
+ if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
+ graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
+ }
+ return graphql.Null
+ }
+ return ec._Bookmark(ctx, sel, v)
+}
+
func (ec *executionContext) unmarshalNBoolean2bool(ctx context.Context, v any) (bool, error) {
res, err := graphql.UnmarshalBoolean(v)
return res, graphql.ErrorOnPath(ctx, err)
@@ -8849,6 +9489,11 @@ func (ec *executionContext) unmarshalNCreateActivityInput2adamᚑfrenchᚗcoᚗu
return res, graphql.ErrorOnPath(ctx, err)
}
+func (ec *executionContext) unmarshalNCreateBookmarkInput2adamᚑfrenchᚗcoᚗukᚋbackendᚋgraphᚋmodelᚐCreateBookmarkInput(ctx context.Context, v any) (model.CreateBookmarkInput, error) {
+ res, err := ec.unmarshalInputCreateBookmarkInput(ctx, v)
+ return res, graphql.ErrorOnPath(ctx, err)
+}
+
func (ec *executionContext) unmarshalNCreateFavoriteInput2adamᚑfrenchᚗcoᚗukᚋbackendᚋgraphᚋmodelᚐCreateFavoriteInput(ctx context.Context, v any) (model.CreateFavoriteInput, error) {
res, err := ec.unmarshalInputCreateFavoriteInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
diff --git a/backend/graph/model/models_gen.go b/backend/graph/model/models_gen.go
index 9f84e68..d3957aa 100644
--- a/backend/graph/model/models_gen.go
+++ b/backend/graph/model/models_gen.go
@@ -18,6 +18,12 @@ type CreateActivityInput struct {
Link *string `json:"link,omitempty"`
}
+type CreateBookmarkInput struct {
+ Category string `json:"category"`
+ Name string `json:"name"`
+ Link string `json:"link"`
+}
+
type CreateFavoriteInput struct {
Type string `json:"type"`
Name string `json:"name"`
diff --git a/backend/graph/schema.resolvers.go b/backend/graph/schema.resolvers.go
index cff47d2..b776416 100644
--- a/backend/graph/schema.resolvers.go
+++ b/backend/graph/schema.resolvers.go
@@ -349,6 +349,33 @@ func (r *mutationResolver) UpdateJobApplication(ctx context.Context, id int, inp
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) {
@@ -571,6 +598,19 @@ func (r *queryResolver) Me(ctx context.Context) (*models.User, error) {
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) {
diff --git a/backend/graph/schema/bookmark.graphql b/backend/graph/schema/bookmark.graphql
new file mode 100644
index 0000000..614b31e
--- /dev/null
+++ b/backend/graph/schema/bookmark.graphql
@@ -0,0 +1,14 @@
+type Bookmark {
+ id: ID!
+ createdAt: Time!
+ updatedAt: Time!
+ category: String!
+ name: String!
+ link: String!
+}
+
+input CreateBookmarkInput {
+ category: String!
+ name: String!
+ link: String!
+}
diff --git a/backend/graph/schema/schema.graphql b/backend/graph/schema/schema.graphql
index 8f2d8e1..a06d126 100644
--- a/backend/graph/schema/schema.graphql
+++ b/backend/graph/schema/schema.graphql
@@ -14,6 +14,7 @@ type Query {
giteaFeed: GiteaFeedItem
steamStatus: SteamStatus
me: User
+ bookmarks: [Bookmark!]!
jobApplications: [JobApplication!]!
jobApplication(id: ID!): JobApplication
}
@@ -32,5 +33,7 @@ type Mutation {
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!
}
diff --git a/backend/models/models.go b/backend/models/models.go
index 1d80c5e..bdbce93 100644
--- a/backend/models/models.go
+++ b/backend/models/models.go
@@ -67,6 +67,16 @@ type Rowing struct {
Calories float64 `json:"calories"`
}
+type Bookmark struct {
+ ID uint `gorm:"primarykey" json:"id"`
+ CreatedAt time.Time `json:"createdAt"`
+ UpdatedAt time.Time `json:"updatedAt"`
+ DeletedAt gorm.DeletedAt `gorm:"index" json:"deletedAt"`
+ Category string `gorm:"not null" json:"category"`
+ Name string `gorm:"not null" json:"name"`
+ Link string `gorm:"not null" json:"link"`
+}
+
type JobApplication struct {
ID uint `gorm:"primarykey" json:"id"`
CreatedAt time.Time `json:"createdAt"`
diff --git a/backend/services/database.go b/backend/services/database.go
index 76fa187..56a0c5d 100644
--- a/backend/services/database.go
+++ b/backend/services/database.go
@@ -39,6 +39,7 @@ func migrateDatabase(db *gorm.DB) error {
&models.Rowing{},
&models.Message{},
&models.JobApplication{},
+ &models.Bookmark{},
)
if err != nil {
return err
diff --git a/vue/src/stores/homeData.js b/vue/src/stores/homeData.js
index 593aac8..5d723ed 100644
--- a/vue/src/stores/homeData.js
+++ b/vue/src/stores/homeData.js
@@ -15,6 +15,7 @@ export const useHomeDataStore = defineStore("homeData", () => {
const rowingSessions = ref([]);
const gitFeed = ref(null);
const steamStatus = ref(null);
+ const bookmarks = ref([]);
const radioLive = ref(false);
async function fetchAll() {
@@ -27,6 +28,7 @@ export const useHomeDataStore = defineStore("homeData", () => {
activities { id type name link createdAt }
spotifyRecent { track { name album { name images { url } } artists { name } } playedAt }
rowingSessions { id date time distance timePer500m calories }
+ bookmarks { id category name link }
giteaFeed { avatarUrl repoUrl repoName opType commitMessage createdAt }
steamStatus { online recentGames { appId name playtime2Weeks playtimeForever headerImageUrl } }
me { id username admin }
@@ -38,6 +40,7 @@ export const useHomeDataStore = defineStore("homeData", () => {
favorites.value = data.favorites;
activities.value = data.activities;
spotifyRecent.value = data.spotifyRecent || [];
+ bookmarks.value = data.bookmarks || [];
rowingSessions.value = data.rowingSessions;
gitFeed.value = data.giteaFeed || null;
steamStatus.value = data.steamStatus || null;
@@ -64,6 +67,7 @@ export const useHomeDataStore = defineStore("homeData", () => {
loaded,
error,
me,
+ bookmarks,
posts,
favorites,
activities,
diff --git a/vue/src/views/home/bookmarks/Bookmarks.vue b/vue/src/views/home/bookmarks/Bookmarks.vue
index 236e3e1..dfbc019 100644
--- a/vue/src/views/home/bookmarks/Bookmarks.vue
+++ b/vue/src/views/home/bookmarks/Bookmarks.vue
@@ -1,240 +1,18 @@
@@ -245,9 +23,9 @@ const links = [