Skip to content

Commit

Permalink
Add announcement API (#1)
Browse files Browse the repository at this point in the history
* feat: Add announcement API (unfinished)

* feat: Add announcement API (unfinished)

* fix: finishing announcements

+ linting

* fix(announcements): minor issues

Co-authored-by: Vilsol <[email protected]>
  • Loading branch information
Feyko and Vilsol authored Dec 2, 2021
1 parent 41d8fc1 commit 2e050f3
Show file tree
Hide file tree
Showing 11 changed files with 257 additions and 1 deletion.
6 changes: 6 additions & 0 deletions auth/permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ var (
ID: "7",
Description: "Allows user to bootstrap versions",
}
RoleEditAnnouncements = &Role{
ID: "8",
Description: "Allows user to manage announcements",
}
)

var (
Expand All @@ -54,6 +58,7 @@ var (
RoleEditUsers,
RoleEditSMLVersions,
RoleEditBootstrapVersions,
RoleEditAnnouncements,
},
}
GroupModerator = &Group{
Expand All @@ -62,6 +67,7 @@ var (
Roles: []*Role{
RoleApproveMods,
RoleApproveVersions,
RoleEditAnnouncements,
},
}
GroupSMLDev = &Group{
Expand Down
63 changes: 63 additions & 0 deletions db/postgres/announcement.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package postgres

import (
"context"

"github.com/patrickmn/go-cache"
"github.com/satisfactorymodding/smr-api/util"
)

func CreateAnnouncement(announcement *Announcement, ctx *context.Context) (*Announcement, error) {
announcement.ID = util.GenerateUniqueID()
DBCtx(ctx).Create(&announcement)
return announcement, nil
}

func GetAnnouncementByID(announcementID string, ctx *context.Context) *Announcement {
cacheKey := "GetAnnouncementByID_" + announcementID

if announcement, ok := dbCache.Get(cacheKey); ok {
return announcement.(*Announcement)
}

var announcement Announcement
DBCtx(ctx).Find(&announcement, "id = ?", announcementID)

if announcement.ID == "" {
return nil
}

dbCache.Set(cacheKey, announcement, cache.DefaultExpiration)

return &announcement
}

func GetAnnouncements(ctx *context.Context) []Announcement {
cacheKey := "GetAnnouncements"

if announcements, ok := dbCache.Get(cacheKey); ok {
return announcements.([]Announcement)
}

var announcements []Announcement
DBCtx(ctx).Find(&announcements)

dbCache.Set(cacheKey, announcements, cache.DefaultExpiration)

return announcements
}

func GetAnnouncementsByImportance(importance string, ctx *context.Context) []Announcement {
cacheKey := "GetAnnouncementsByImportance_" + importance

if announcements, ok := dbCache.Get(cacheKey); ok {
return announcements.([]Announcement)
}

var announcements []Announcement
DBCtx(ctx).Find(&announcements, "importance = ?", importance)

dbCache.Set(cacheKey, announcements, cache.DefaultExpiration)

return announcements
}
7 changes: 7 additions & 0 deletions db/postgres/postgres_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,10 @@ type BootstrapVersion struct {
Link string
Changelog string
}

type Announcement struct {
SMRModel

Message string
Importance string
}
2 changes: 2 additions & 0 deletions docker-compose-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,7 @@ services:
entrypoint: sh
command: -c 'mkdir -p /data/smr && /usr/bin/docker-entrypoint.sh minio server /data --console-address ":9001"'
environment:
MINIO_ROOT_USER: minio
MINIO_ROOT_PASSWORD: minio123
MINIO_ACCESS_KEY: REPLACE_ME_KEY
MINIO_SECRET_KEY: REPLACE_ME_SECRET
11 changes: 11 additions & 0 deletions gql/directive.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func MakeDirective() generated.DirectiveRoot {
CanEditUsers: canEditUsers,
CanEditSMLVersions: canEditSMLVersions,
CanEditBootstrapVersions: canEditBootstrapVersions,
CanEditAnnouncements: canEditAnnouncements,
}
}

Expand Down Expand Up @@ -212,3 +213,13 @@ func canEditBootstrapVersions(ctx context.Context, obj interface{}, next graphql

return nil, errors.New("user not authorized to perform this action")
}

func canEditAnnouncements(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) {
user := ctx.Value(postgres.UserKey{}).(*postgres.User)

if user.Has(auth.RoleEditAnnouncements, &ctx) {
return next(ctx)
}

return nil, errors.New("user not authorized to perform this action")
}
20 changes: 20 additions & 0 deletions gql/gql_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,23 @@ func DBVersionDependencyToGenerated(versionDependency *postgres.VersionDependenc
Optional: versionDependency.Optional,
}
}

func DBAnnouncementToGenerated(announcement *postgres.Announcement) *generated.Announcement {
if announcement == nil {
return nil
}

return &generated.Announcement{
ID: announcement.ID,
Message: announcement.Message,
Importance: announcement.Importance,
}
}

func DBAnnouncementsToGeneratedSlice(announcements []postgres.Announcement) []*generated.Announcement {
converted := make([]*generated.Announcement, len(announcements))
for i, announcement := range announcements {
converted[i] = DBAnnouncementToGenerated(&announcement)
}
return converted
}
98 changes: 98 additions & 0 deletions gql/resolver_announcements.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package gql

import (
"context"

"github.com/pkg/errors"
"github.com/satisfactorymodding/smr-api/util"
"gopkg.in/go-playground/validator.v9"

"github.com/satisfactorymodding/smr-api/db/postgres"
"github.com/satisfactorymodding/smr-api/generated"
)

func (r *mutationResolver) CreateAnnouncement(ctx context.Context, announcement generated.NewAnnouncement) (*generated.Announcement, error) {
wrapper, newCtx := WrapMutationTrace(ctx, "createAnnouncement")
defer wrapper.end()

val := ctx.Value(util.ContextValidator{}).(*validator.Validate)
if err := val.Struct(&announcement); err != nil {
return nil, errors.Wrap(err, "validation failed")
}

dbAnnouncement := &postgres.Announcement{
Message: announcement.Message,
Importance: announcement.Importance,
}

resultAnnouncement, err := postgres.CreateAnnouncement(dbAnnouncement, &newCtx)
if err != nil {
return nil, err
}
return DBAnnouncementToGenerated(resultAnnouncement), nil
}

func (r *mutationResolver) DeleteAnnouncement(ctx context.Context, announcementID string) (bool, error) {
wrapper, newCtx := WrapMutationTrace(ctx, "deleteAnnouncement")
defer wrapper.end()

dbAnnouncement := postgres.GetAnnouncementByID(announcementID, &newCtx)

if dbAnnouncement == nil {
return false, errors.New("announcement not found")
}

postgres.Delete(&dbAnnouncement, &newCtx)

return true, nil
}

func (r *mutationResolver) UpdateAnnouncement(ctx context.Context, announcementID string, announcement generated.UpdateAnnouncement) (*generated.Announcement, error) {
wrapper, newCtx := WrapMutationTrace(ctx, "updateAnnouncement")
defer wrapper.end()

val := ctx.Value(util.ContextValidator{}).(*validator.Validate)
if err := val.Struct(&announcement); err != nil {
return nil, errors.Wrap(err, "validation failed")
}

dbAnnouncement := postgres.GetAnnouncementByID(announcementID, &newCtx)

if dbAnnouncement == nil {
return nil, errors.New("guide not found")
}

SetStringINNOE(announcement.Message, &dbAnnouncement.Message)
SetStringINNOE(announcement.Importance, &dbAnnouncement.Importance)

postgres.Save(&dbAnnouncement, &newCtx)

return DBAnnouncementToGenerated(dbAnnouncement), nil
}

func (r *queryResolver) GetAnnouncement(ctx context.Context, announcementID string) (*generated.Announcement, error) {
wrapper, newCtx := WrapQueryTrace(ctx, "getAnnouncement")
defer wrapper.end()

announcement := postgres.GetAnnouncementByID(announcementID, &newCtx)

return DBAnnouncementToGenerated(announcement), nil
}

func (r *queryResolver) GetAnnouncements(ctx context.Context) ([]*generated.Announcement, error) {
wrapper, newCtx := WrapQueryTrace(ctx, "getAnnouncements")
defer wrapper.end()

announcements := postgres.GetAnnouncements(&newCtx)

return DBAnnouncementsToGeneratedSlice(announcements), nil
}

func (r *queryResolver) GetAnnouncementsByImportance(ctx context.Context, importance string) ([]*generated.Announcement, error) {
wrapper, newCtx := WrapQueryTrace(ctx, "getAnnouncementsByImportance")
defer wrapper.end()

announcements := postgres.GetAnnouncementsByImportance(importance, &newCtx)

return DBAnnouncementsToGeneratedSlice(announcements), nil
}
1 change: 1 addition & 0 deletions migrations/sql/000018_add_announcements.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
drop table if exists announcements
10 changes: 10 additions & 0 deletions migrations/sql/000018_add_announcements.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
create table if not exists announcements
(
id varchar(14) not null
constraint announcements_pkey primary key,
message text not null,
importance text not null,
created_at timestamp with time zone,
updated_at timestamp with time zone,
deleted_at timestamp with time zone
)
37 changes: 37 additions & 0 deletions schemas/announcements.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
### Types

scalar AnnouncementID

type Announcement {
id: AnnouncementID!
message: String!
importance: String!
}

### Inputs

input NewAnnouncement {
message: String!
importance: String!
}

input UpdateAnnouncement {
message: String
importance: String
}

### Queries

extend type Query {
getAnnouncement(announcementId: AnnouncementID!): Announcement
getAnnouncements: [Announcement!]!
getAnnouncementsByImportance(importance: String!): [Announcement!]!
}

### Mutations

extend type Mutation {
createAnnouncement(announcement: NewAnnouncement!): Announcement @canEditAnnouncements @isLoggedIn
updateAnnouncement(announcementId: AnnouncementID!, announcement: UpdateAnnouncement!): Announcement! @canEditAnnouncements @isLoggedIn
deleteAnnouncement(announcementId: AnnouncementID!): Boolean! @canEditAnnouncements @isLoggedIn
}
3 changes: 2 additions & 1 deletion schemas/directives.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ directive @canApproveMods on FIELD_DEFINITION | INPUT_FIELD_DEFINITION
directive @canApproveVersions on FIELD_DEFINITION | INPUT_FIELD_DEFINITION
directive @canEditUsers on FIELD_DEFINITION | INPUT_FIELD_DEFINITION
directive @canEditSMLVersions on FIELD_DEFINITION | INPUT_FIELD_DEFINITION
directive @canEditBootstrapVersions on FIELD_DEFINITION | INPUT_FIELD_DEFINITION
directive @canEditBootstrapVersions on FIELD_DEFINITION | INPUT_FIELD_DEFINITION
directive @canEditAnnouncements on FIELD_DEFINITION | INPUT_FIELD_DEFINITION

0 comments on commit 2e050f3

Please sign in to comment.