Skip to content
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
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,8 @@ PG_PORT=5432
POSTGRES_DB=timecodes_development

GOOGLE_API_KEY=

POSTGRES_USER=
POSTGRES_PASSWORD=

APP_HOST=
2 changes: 2 additions & 0 deletions .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ PG_PASSWORD=postgres
PG_HOST=db
PG_PORT=5432

POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
POSTGRES_DB=timecodes_test

GOOGLE_API_KEY=
26 changes: 24 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,12 @@ jobs:
push_image_and_stages: false
pull_image_and_stages: false
- name: Build docker image for tests
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
cp .env.example .env
echo ${{ secrets.GITHUB_TOKEN }} | docker login docker.pkg.github.com -u vaihtovirta --password-stdin
cp .env.test .env
docker login docker.pkg.github.com -u vaihtovirta -p $GITHUB_TOKEN
docker network create traefik-public
docker-compose build --pull app_test
- name: Run tests
run: docker-compose run --rm app_test go test ./... -covermode=count -coverprofile=tmp/coverage.out
Expand All @@ -46,3 +49,22 @@ jobs:
with:
github-token: ${{ secrets.github_token }}
path-to-lcov: coverage.lcov
deployment:
if: github.ref == 'refs/heads/master'
needs: [lint, test]
runs-on: ubuntu-latest
steps:
- name: Connect by ssh and rebuild docker image
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
passphrase: ${{ secrets.PASSPHRASE }}
key: ${{ secrets.PRIVATE_KEY }}
script: |
cd /home/deploy/applications/timecodes/timecodes-api
git fetch --all
git reset --hard origin/master
dotenvp
docker-compose -f docker-compose.prod.yml pull app
docker-compose -f docker-compose.prod.yml up -d
25 changes: 0 additions & 25 deletions .github/workflows/ssh_deploy.yml

This file was deleted.

6 changes: 5 additions & 1 deletion cmd/db_migrations.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package main

import "github.com/jinzhu/gorm"
import (
"github.com/jinzhu/gorm"
)

func runMigrations(db *gorm.DB) {
applyTimecodesMigrations(db)
Expand All @@ -22,6 +24,8 @@ func applyTimecodesMigrations(db *gorm.DB) {

func applyUsersMigrations(db *gorm.DB) {
db.AutoMigrate(&User{})

getAdminUser(db)
}

func applyTimecodeLikesMigrations(db *gorm.DB) {
Expand Down
45 changes: 11 additions & 34 deletions cmd/timecode_likes_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,43 +27,26 @@ type MockTimecodeLikeRepository struct {

func (mr *MockTimecodeLikeRepository) Create(like *TimecodeLike, userID uint) (*TimecodeLike, error) {
args := mr.Called(like, userID)
err := args.Error(1)

like.UserID = userID

if like.TimecodeID == invalidTimecodeID {
return nil, err
}

_ = args.Get(0).(*TimecodeLike)

return like, nil
return args.Get(0).(*TimecodeLike), args.Error(1)
}

func (mr *MockTimecodeLikeRepository) Delete(like *TimecodeLike, userID uint) (*TimecodeLike, error) {
args := mr.Called(like, userID)
err := args.Error(1)

like.UserID = userID

if like.TimecodeID == invalidTimecodeID {
return nil, err
}

_ = args.Get(0).(*TimecodeLike)

return like, nil
return args.Get(0).(*TimecodeLike), args.Error(1)
}

func Test_handleCreateTimecodeLike(t *testing.T) {
url := "/auth/timecode_likes"
currentUser := &User{}
currentUser.ID = 1

t.Run("when creation is successful", func(t *testing.T) {
currentUser := &User{}
currentUser.ID = 1
likeParams := &TimecodeLike{TimecodeID: 5}
like := &TimecodeLike{TimecodeID: 5, UserID: currentUser.ID}

like := &TimecodeLike{TimecodeID: 5}
mockTKRepo.On("Create", like, currentUser.ID).Return(like, nil)
mockTKRepo.On("Create", likeParams, currentUser.ID).Return(like, nil)

params := []byte(`{ "timecodeId": 5 }`)
req, _ := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(params))
Expand All @@ -75,11 +58,8 @@ func Test_handleCreateTimecodeLike(t *testing.T) {
})

t.Run("when creation has been failed", func(t *testing.T) {
currentUser := &User{}
currentUser.ID = 1

like := &TimecodeLike{TimecodeID: invalidTimecodeID}
mockTKRepo.On("Create", like, currentUser.ID).Return(nil, errors.New(""))
mockTKRepo.On("Create", like, currentUser.ID).Return(&TimecodeLike{}, errors.New(""))

params := []byte(fmt.Sprintf(`{ "timecodeId": %d }`, invalidTimecodeID))
req, _ := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(params))
Expand All @@ -103,10 +83,10 @@ func Test_handleCreateTimecodeLike(t *testing.T) {

func Test_handleDeleteTimecodeLike(t *testing.T) {
url := "/auth/timecode_likes"
currentUser := &User{}
currentUser.ID = 1

t.Run("when deletion is successful", func(t *testing.T) {
currentUser := &User{}
currentUser.ID = 1

like := &TimecodeLike{TimecodeID: 5}
mockTKRepo.On("Delete", like, currentUser.ID).Return(like, nil)
Expand All @@ -121,11 +101,8 @@ func Test_handleDeleteTimecodeLike(t *testing.T) {
})

t.Run("when deletion has been failed", func(t *testing.T) {
currentUser := &User{}
currentUser.ID = 1

like := &TimecodeLike{TimecodeID: invalidTimecodeID}
mockTKRepo.On("Delete", like, currentUser.ID).Return(nil, errors.New(""))
mockTKRepo.On("Delete", like, currentUser.ID).Return(&TimecodeLike{}, errors.New(""))

params := []byte(fmt.Sprintf(`{ "timecodeId": %d }`, invalidTimecodeID))
req, _ := http.NewRequest(http.MethodDelete, url, bytes.NewBuffer(params))
Expand Down
10 changes: 9 additions & 1 deletion cmd/timecode_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type Timecode struct {
Seconds int `json:"seconds" gorm:"not null"`
VideoID string `json:"videoId" gorm:"not null;index"`
Likes []TimecodeLike `json:"likes" gorm:"foreignkey:TimecodeID"`
UserID uint `json:"userId"`
}

type TimecodeRepository interface {
Expand Down Expand Up @@ -57,7 +58,14 @@ func (repo *DBTimecodeRepository) CreateFromParsedCodes(parsedTimecodes []timeco

seen[key] = struct{}{}

timecode := &Timecode{Seconds: code.Seconds, VideoID: videoId, Description: code.Description}
user := getAdminUser(repo.DB)

timecode := &Timecode{
Seconds: code.Seconds,
VideoID: videoId,
Description: code.Description,
UserID: user.ID,
}

err := repo.DB.Create(timecode).Error
if err != nil {
Expand Down
6 changes: 6 additions & 0 deletions cmd/timecodes_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type TimecodeJSON struct {
LikedByMe bool `json:"likedByMe,omitempty"`
Seconds int `json:"seconds"`
VideoID string `json:"videoId"`
UserID uint `json:"userId,omitempty"`
}

// GET /timecodes/{videoId}
Expand Down Expand Up @@ -63,6 +64,7 @@ func handleCreateTimecode(c *Container, w http.ResponseWriter, r *http.Request)
Description: timecodeRequest.Description,
Seconds: timecodeParser.ParseSeconds(timecodeRequest.RawSeconds),
VideoID: timecodeRequest.VideoID,
UserID: currentUser.ID,
}

_, err = c.TimecodeRepository.Create(timecode)
Expand All @@ -77,8 +79,11 @@ func handleCreateTimecode(c *Container, w http.ResponseWriter, r *http.Request)

func serializeTimecode(timecode *Timecode, currentUser *User) (timecodeJSON *TimecodeJSON) {
var likedByMe bool
var userId uint

if currentUser != nil {
likedByMe = getLikedByMe(timecode.Likes, currentUser.ID)
userId = timecode.UserID
}

return &TimecodeJSON{
Expand All @@ -88,6 +93,7 @@ func serializeTimecode(timecode *Timecode, currentUser *User) (timecodeJSON *Tim
LikedByMe: likedByMe,
Seconds: timecode.Seconds,
VideoID: timecode.VideoID,
UserID: userId,
}
}

Expand Down
38 changes: 12 additions & 26 deletions cmd/timecodes_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,28 +27,13 @@ type MockTimecodeRepository struct {
func (m *MockTimecodeRepository) FindByVideoId(videoID string) *[]*Timecode {
args := m.Called(videoID)

collection := args.Get(0).(*[]*Timecode)
if videoID == "no-items" {
return collection
}

collection = &[]*Timecode{{}, {}, {Likes: []TimecodeLike{{UserID: 1}}}}

return collection
return args.Get(0).(*[]*Timecode)
}

func (m *MockTimecodeRepository) Create(timecode *Timecode) (*Timecode, error) {
args := m.Called(timecode)

err := args.Error(1)

if len(timecode.Description) == 0 {
return nil, err
}

_ = args.Get(0).(*Timecode)

return timecode, nil
return args.Get(0).(*Timecode), args.Error(1)
}

func (m *MockTimecodeRepository) CreateFromParsedCodes(parsedCodes []timecodeParser.ParsedTimeCode, videoId string) *[]*Timecode {
Expand All @@ -64,17 +49,13 @@ type mockYT struct {
func (m *mockYT) FetchVideoDescription(videoId string) string {
args := m.Called(videoId)

_ = args.Get(0).(string)

return "description"
return args.Get(0).(string)
}

func (m *mockYT) FetchVideoComments(videoId string) []string {
args := m.Called(videoId)

_ = args.Get(0).([]string)

return []string{"comment one", "comment two"}
return args.Get(0).([]string)
}

func Test_handleGetTimecodes(t *testing.T) {
Expand Down Expand Up @@ -122,7 +103,12 @@ func Test_handleCreateTimecode(t *testing.T) {
currentUser.ID = 1

t.Run("when request params are valid", func(t *testing.T) {
timecode := &Timecode{VideoID: "video-id", Seconds: 71, Description: "ABC"}
timecode := &Timecode{
VideoID: "video-id",
Seconds: 71,
Description: "ABC",
UserID: currentUser.ID,
}

mockTimecodeRepo.On("Create", timecode).Return(timecode, nil)

Expand All @@ -136,9 +122,9 @@ func Test_handleCreateTimecode(t *testing.T) {
})

t.Run("when request params are invalid", func(t *testing.T) {
timecode := &Timecode{VideoID: "video-id", Seconds: 71, Description: ""}
timecode := &Timecode{VideoID: "video-id", Seconds: 71, Description: "", UserID: currentUser.ID}

mockTimecodeRepo.On("Create", timecode).Return(nil, errors.New(""))
mockTimecodeRepo.On("Create", timecode).Return(&Timecode{}, errors.New(""))

params := []byte(`{ "videoId": "video-id", "seconds": "1:11", "description": "" }`)
req, _ := http.NewRequest(http.MethodPost, "/auth/timecodes", bytes.NewBuffer(params))
Expand Down
8 changes: 8 additions & 0 deletions cmd/user_repository.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"os"
googleAPI "timecodes/cmd/google_api"

"github.com/jinzhu/gorm"
Expand Down Expand Up @@ -32,3 +33,10 @@ func (repo *DBUserRepository) FindOrCreateByGoogleInfo(userInfo *googleAPI.UserI

return user
}

func getAdminUser(db *gorm.DB) *User {
adminUser := &User{Email: os.Getenv("ADMIN_EMAIL"), GoogleID: os.Getenv("ADMIN_GOOGLE_ID")}
db.FirstOrCreate(adminUser)

return adminUser
}
Loading