Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Friends list page #96

Merged
merged 17 commits into from
May 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions api/graphs/api.graphqls
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ directive @authzByPolicy(policy: POLICY) on FIELD_DEFINITION

directive @authenticated on FIELD_DEFINITION

directive @extraStructTag on FIELD_DEFINITION

enum POLICY {
SERVICE_TOKEN
NONE
Expand Down Expand Up @@ -35,6 +37,8 @@ type User {
CoverURL: String
accounts: [Account]
features: [FEATURE!]!
following: [User]!
followers: [User]!
}

type Account {
Expand All @@ -54,12 +58,17 @@ type Account {

type Query {
me: User! @authenticated
searchUser(query: String!): [User!]! @authenticated

internalGetUserByAccount(provider: PROVIDER!, uid: String!): User! @authzByPolicy(policy: SERVICE_TOKEN)
internalGetUserByEmail(email: String!): User! @authzByPolicy(policy: SERVICE_TOKEN)
internalGetUser(id: UUID!): User! @authzByPolicy(policy: SERVICE_TOKEN)
}

type Mutation {
createFriendship(userID: UUID!): Boolean! @authenticated
deleteFriendship(userID: UUID!): Boolean! @authenticated

internalCreateUser(input: CreateUserInput!): UUID! @authzByPolicy(policy: SERVICE_TOKEN)
internalLinkAccount(input: LinkAccountInput!): Account! @authzByPolicy(policy: SERVICE_TOKEN)
inviteOnDiscord: Boolean! @authenticated
Expand Down
6 changes: 4 additions & 2 deletions build/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ WORKDIR /build
COPY . /build

RUN apt-get update && \
apt-get install -y unzip && \
apt-get install -y unzip jq && \
make -f build/Makefile deps generate build

# INTERFACE BUILD - DEPS
Expand All @@ -22,7 +22,9 @@ COPY . .

ENV NEXT_TELEMETRY_DISABLED 1

RUN make -f build/Makefile deps && \
RUN apt-get update -y && \
apt-get install -y jq && \
make -f build/Makefile deps && \
cd web/ui && \
yarn add -D @graphql-codegen/cli && \
yarn generate && yarn build
Expand Down
10 changes: 8 additions & 2 deletions build/Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# This makefile MUST be run from the root of the repository.
# You can do this by running:
# make -f build/Makefile [action]

generate:
go generate generate.go
Expand All @@ -18,8 +21,11 @@ certs:
openssl rsa -in certs/private.key -out certs/public.pem -pubout -outform PEM

deps:
wget -O /tmp/protoc.zip https://github.com/protocolbuffers/protobuf/releases/download/v3.20.1/protoc-3.20.1-linux-x86_64.zip
unzip -uo /tmp/protoc -d /usr/local bin/protoc
./build/install-dependencies.sh

local-dev-setup: deps generate certs
docker-compose -f docker-compose.local.yaml up -d
docker exec -it s42_postgres psql -c 'CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; CREATE EXTENSION IF NOT EXISTS "pg_trgm";' -U postgres -d s42

all:
deps
Expand Down
17 changes: 17 additions & 0 deletions build/install-dependencies.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/bash

OS=$([ "$(uname -s)" == 'Darwin' ] && echo "osx" || echo "$(uname -s)")
URL=$(curl -s https://api.github.com/repos/protocolbuffers/protobuf/releases/latest | \
jq -c ".assets | map(select( .name | contains(\"protoc\") and contains(\"$(uname -m)\") and contains(\"$OS\"))) | .[].browser_download_url" | \
tr -d \")

if [ -z "$URL" ]; then
echo "Could not find protoc binary for your platform. Fallback to linux"
URL=$(curl -s https://api.github.com/repos/protocolbuffers/protobuf/releases/latest | \
jq -c ".assets | map(select( .name | contains(\"protoc\") and contains(\"x86_64\") and contains(\"linux\"))) | .[].browser_download_url" | \
tr -d \")
fi

echo "Downloading protoc... $URL"
wget -qO /tmp/protoc.zip $URL
unzip -uo /tmp/protoc -d /usr/local bin/protoc
15 changes: 15 additions & 0 deletions docker-compose.local.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
version: '3'
services:
database:
container_name: s42_postgres
deploy:
replicas: 1
restart_policy:
condition: any
environment:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
POSTGRES_DB: s42
ports:
- "5432:5432"
image: postgres:14.2-alpine3.15
49 changes: 45 additions & 4 deletions generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,31 @@

package main

//go:generate echo "⌛ Auto-generating schema"
//go:generate go run generate.go
//go:generate echo "⌛ Auto-generating gql"
//go:generate go run -mod=mod github.com/99designs/gqlgen generate
//go:generate echo "✔️ Successfully generated"

import (
"fmt"
"log"
"os"

"entgo.io/contrib/entgql"
"entgo.io/ent/entc"
"entgo.io/ent/entc/gen"
gqlgenapi "github.com/99designs/gqlgen/api"
"github.com/99designs/gqlgen/codegen/config"
"github.com/99designs/gqlgen/plugin/modelgen"
"github.com/vektah/gqlparser/v2/ast"
)

func main() {
log.Println("⌛ Auto-generating schema")
generateEntc()
log.Println("⌛ Auto-generating gql")
generateGqlGen()
log.Println("✔️ Successfully generated")
}

func generateEntc() {
ex, err := entgql.NewExtension(
entgql.WithSchemaPath("./api/graphs/api.graphqls"),
)
Expand All @@ -39,3 +49,34 @@ func main() {
log.Fatalf("running ent codegen: %v", err)
}
}

func generateGqlGen() {
cfg, err := config.LoadConfigFromDefaultLocations()
if err != nil {
log.Fatalf("cannot load config: %v", err)
}

// Attaching the mutation function onto modelgen plugin
p := modelgen.Plugin{
FieldHook: extraStructTagFieldHook,
}

err = gqlgenapi.Generate(cfg, gqlgenapi.ReplacePlugin(&p))

if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(3)
}
}

func extraStructTagFieldHook(td *ast.Definition, fd *ast.FieldDefinition, f *modelgen.Field) (*modelgen.Field, error) {

c := fd.Directives.ForName("extraStructTag")
if c != nil {
for _, arg := range c.Arguments {
f.Tag += fmt.Sprintf(" %s:%s", arg.Name, arg.Value.String())
}
}

return f, nil
}
4 changes: 4 additions & 0 deletions gqlgen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ resolver:
# Optional: turn on use `gqlgen:"fieldName"` tags in your models
struct_tag: json

directives:
extraStructTag:
skip_runtime: true

# Optional: turn on to use []Thing instead of []*Thing
# omit_slice_element_pointers: false

Expand Down
62 changes: 55 additions & 7 deletions internal/api/api.resolvers.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,44 @@ import (
"context"
"os"

apigen "atomys.codes/stud42/internal/api/generated"
typesgen "atomys.codes/stud42/internal/api/generated/types"
"atomys.codes/stud42/internal/models/generated"
"atomys.codes/stud42/internal/models/generated/account"
"atomys.codes/stud42/internal/models/generated/user"
"github.com/bwmarrin/discordgo"
"github.com/google/uuid"
"github.com/rs/zerolog/log"
"github.com/shurcooL/githubv4"
"github.com/spf13/viper"
"golang.org/x/oauth2"

apigen "atomys.codes/stud42/internal/api/generated"
typesgen "atomys.codes/stud42/internal/api/generated/types"
"atomys.codes/stud42/internal/models/generated"
"atomys.codes/stud42/internal/models/generated/account"
"atomys.codes/stud42/internal/models/generated/user"
)

func (r *mutationResolver) CreateFriendship(ctx context.Context, userID uuid.UUID) (bool, error) {
cu, err := CurrentUserFromContext(ctx)
if err != nil {
return false, err
}

if _, err := r.client.User.UpdateOne(cu).AddFollowingIDs(userID).Save(ctx); err != nil {
return false, err
}
return true, nil
}

func (r *mutationResolver) DeleteFriendship(ctx context.Context, userID uuid.UUID) (bool, error) {
cu, err := CurrentUserFromContext(ctx)
if err != nil {
return false, err
}

if _, err := r.client.User.UpdateOne(cu).RemoveFollowingIDs(userID).Save(ctx); err != nil {
return false, err
}
return true, nil
}

func (r *mutationResolver) InternalCreateUser(ctx context.Context, input typesgen.CreateUserInput) (uuid.UUID, error) {
return r.client.User.Create().
SetEmail(input.Email).
Expand All @@ -32,8 +57,9 @@ func (r *mutationResolver) InternalCreateUser(ctx context.Context, input typesge
SetNillablePoolMonth(input.PoolMonth).
SetNillablePhone(input.Phone).
SetIsStaff(input.IsStaff).
SetIsAUser(true).
OnConflictColumns(user.FieldDuoID).
UpdateDuoID().
UpdateNewValues().
ID(ctx)
}

Expand Down Expand Up @@ -79,7 +105,29 @@ func (r *mutationResolver) InviteOnDiscord(ctx context.Context) (bool, error) {
}

func (r *queryResolver) Me(ctx context.Context) (*generated.User, error) {
return CurrentUserFromContext(ctx)
cu, err := CurrentUserFromContext(ctx)
if err != nil {
return nil, err
}

return r.client.User.Query().WithFollowing(func(q *generated.UserQuery) {
q.Order(generated.Asc(user.FieldDuoLogin))
}).Where(user.ID(cu.ID)).First(ctx)
}

func (r *queryResolver) SearchUser(ctx context.Context, query string) ([]*generated.User, error) {
cu, _ := CurrentUserFromContext(ctx)
return r.client.User.Query().
Where(user.Or(
user.DuoLoginContainsFold(query),
user.FirstNameContainsFold(query),
user.LastNameContainsFold(query),
user.UsualFirstNameContainsFold(query),
),
user.IDNEQ(cu.ID),
).
Limit(10).
All(ctx)
}

func (r *queryResolver) InternalGetUserByAccount(ctx context.Context, provider typesgen.Provider, uid string) (*generated.User, error) {
Expand Down
2 changes: 2 additions & 0 deletions internal/models/schema/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,14 @@ func (User) Fields() []ent.Field {
field.String("avatar_url").Optional().Nillable().MaxLen(255),
field.String("cover_url").Optional().Nillable().MaxLen(255),
field.Bool("is_staff").Default(false),
field.Bool("is_a_user").Default(false),
}
}

func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("accounts", Account.Type),
edge.To("following", User.Type).From("followers"),
}
}

Expand Down
Empty file removed tools/.keep
Empty file.
45 changes: 45 additions & 0 deletions tools/seeds/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package main

import (
"context"
"os"

"entgo.io/ent/dialect/sql/schema"
_ "github.com/lib/pq"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"

modelgen "atomys.codes/stud42/internal/models/generated"
)

var client *modelgen.Client

func init() {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
if os.Getenv("DEBUG") == "true" {
zerolog.SetGlobalLevel(zerolog.DebugLevel)
} else {
zerolog.SetGlobalLevel(zerolog.InfoLevel)
}

var err error
client, err = modelgen.Open(
"postgres",
os.Getenv("DATABASE_URL"),
modelgen.Debug(),
)
if err != nil {
log.Fatal().Err(err).Msg("failed to connect to database")
}

if err := client.Schema.Create(context.Background(), schema.WithAtlas(true)); err != nil {
log.Fatal().Err(err).Msg("running schema migration")
}
}

func main() {
if err := seedUsers(); err != nil {
log.Fatal().Err(err).Msg("failed to seed users")
}
}
19 changes: 19 additions & 0 deletions tools/seeds/seed_user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package main

import (
"context"

"atomys.codes/stud42/internal/models/generated"
)

func seedUsers() error {
var users = []*generated.UserCreate{
client.User.Create().SetEmail("15014@local.dev").SetDuoID(15014).SetDuoLogin("gdalmar").SetFirstName("Gregory").SetLastName("Dalmar"),
client.User.Create().SetEmail("12297@local.dev").SetDuoID(12297).SetDuoLogin("rgaiffe").SetFirstName("Remi").SetLastName("Gaiffe"),
client.User.Create().SetEmail("19265@local.dev").SetDuoID(19265).SetDuoLogin("jgengo").SetFirstName("Jordane").SetLastName("Gengo"),
client.User.Create().SetEmail("24007@local.dev").SetDuoID(24007).SetDuoLogin("titus").SetFirstName("Jordane").SetLastName("Gengo").SetIsStaff(true),
client.User.Create().SetEmail("-1@local.dev").SetDuoID(424242001).SetDuoLogin("aperez").SetFirstName("Alice").SetLastName("Perez"),
}

return client.User.CreateBulk(users...).OnConflict().DoNothing().Exec(context.Background())
}
1 change: 0 additions & 1 deletion web/ui/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ module.exports = {
},

extends: ['airbnb-typescript', 'next', 'next/core-web-vitals', 'prettier'],

rules: {
'react/no-danger': 'off', // it's self explainatory that no-danger should be used sparingly
'react/react-in-jsx-scope': 'off', // next.js does not require react in most components
Expand Down
1 change: 1 addition & 0 deletions web/ui/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

# Sentry
.sentryclirc
coverage*/
Loading