-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add Newsletter Scheduling Func
- Loading branch information
Showing
9 changed files
with
421 additions
and
163 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
name: cd | ||
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
|
||
concurrency: | ||
group: cd | ||
cancel-in-progress: true | ||
|
||
jobs: | ||
deploy_api_server: | ||
name: Push Docker image to Docker Hub, Deploy API Server. | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- name: Check out the repo | ||
uses: actions/checkout@v3 | ||
with: | ||
ref: ${{ github.ref_name }} | ||
|
||
- name: Set short git commit SHA | ||
id: vars | ||
run: | | ||
calculatedSha=$(git rev-parse --short ${{ github.sha }}) | ||
echo "::set-output name=short_sha::$calculatedSha" | ||
- name: Confirm git commit SHA output | ||
run: echo ${{ steps.vars.outputs.short_sha }} | ||
|
||
- name: Login to Docker Hub | ||
uses: docker/login-action@v2 | ||
with: | ||
username: ${{ secrets.DOCKERHUB_USERNAME }} | ||
password: ${{ secrets.DOCKERHUB_TOKEN }} | ||
|
||
- name: Build and push | ||
uses: docker/build-push-action@v4 | ||
with: | ||
context: . | ||
file: ./Dockerfile | ||
push: true | ||
tags: ${{ secrets.DOCKERHUB_USERNAME }}/send-server:${{ steps.vars.outputs.short_sha }} | ||
|
||
- name: Deploy to K8S | ||
uses: appleboy/[email protected] | ||
with: | ||
host: ${{ secrets.HOST }} | ||
username: ${{ secrets.USERNAME }} | ||
password: ${{ secrets.PASSWORD }} | ||
port: ${{ secrets.PORT }} | ||
script: kubectl -n ${{ secrets.NAMESPACE }} set image deploy/send-server send-server=${{ secrets.DOCKERHUB_USERNAME }}/send-server:${{ steps.vars.outputs.short_sha }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
name: ci | ||
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
|
||
concurrency: | ||
group: ci | ||
cancel-in-progress: true | ||
|
||
jobs: | ||
build: | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- name: Checkout | ||
uses: actions/checkout@v4 | ||
|
||
- name: Set up Go | ||
uses: actions/setup-go@v5 | ||
with: | ||
go-version-file: './go.mod' | ||
|
||
- name: Build | ||
run: go build -v -o ./server ./cmd/server | ||
|
||
- name: Test | ||
run: go test -v ./cmd/server |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
FROM golang:alpine | ||
|
||
WORKDIR /app | ||
|
||
COPY go.mod . | ||
COPY go.sum . | ||
|
||
RUN go mod download | ||
|
||
COPY . . | ||
|
||
RUN go build -o ./server ./cmd/server | ||
|
||
CMD [ "./server" ] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
{ | ||
"DATABASE": { | ||
"HOST": "localhost", | ||
"PORT": 5432, | ||
"USER_NAME": "nerd", | ||
"PASSWORD": "planet1!", | ||
"DB_NAME": "nerd_planet", | ||
"LOG_LEVEL": 4 | ||
}, | ||
"SMTP": { | ||
"HOST": "localhost", | ||
"PORT": 5000, | ||
"USER_NAME": "nerd", | ||
"PASSWORD": "planet1!" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
package main | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"log" | ||
"log/slog" | ||
"net/smtp" | ||
"os" | ||
"path" | ||
"runtime" | ||
"strconv" | ||
"strings" | ||
"text/template" | ||
"time" | ||
|
||
"github.com/jasonlvhit/gocron" | ||
"github.com/spf13/viper" | ||
"github.com/team-nerd-planet/send-server/entity" | ||
"gorm.io/driver/postgres" | ||
"gorm.io/gorm" | ||
"gorm.io/gorm/logger" | ||
) | ||
|
||
func main() { | ||
conf, err := NewConfig() | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable TimeZone=Asia/Seoul", | ||
conf.Database.Host, | ||
conf.Database.Port, | ||
conf.Database.UserName, | ||
conf.Database.Password, | ||
conf.Database.DbName, | ||
) | ||
|
||
newLogger := logger.New( | ||
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer | ||
logger.Config{ | ||
SlowThreshold: time.Second, // Slow SQL threshold | ||
LogLevel: logger.LogLevel(4), // Log level | ||
IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger | ||
ParameterizedQueries: false, // Don't include params in the SQL log | ||
Colorful: false, // Disable color | ||
}, | ||
) | ||
|
||
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{ | ||
Logger: newLogger, | ||
}) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
gocron.Every(1).Day().At("09:00").Do(func() { | ||
var subscriptionArr []entity.Subscription | ||
|
||
if err := db.Find(&subscriptionArr).Error; err != nil { | ||
panic(err) | ||
} | ||
|
||
for _, subscription := range subscriptionArr { | ||
publish(conf, db, subscription) | ||
} | ||
}) | ||
|
||
<-gocron.Start() | ||
} | ||
|
||
func publish(conf *Config, db *gorm.DB, subscription entity.Subscription) { | ||
var ( | ||
items []entity.ItemView | ||
where = make([]string, 0) | ||
param = make([]interface{}, 0) | ||
) | ||
|
||
if len(subscription.PreferredCompanyArr) > 0 { | ||
where = append(where, "feed_id IN ?") | ||
param = append(param, []int64(subscription.PreferredCompanyArr)) | ||
} | ||
|
||
if len(subscription.PreferredCompanySizeArr) > 0 { | ||
where = append(where, "company_size IN ?") | ||
param = append(param, []int64(subscription.PreferredCompanySizeArr)) | ||
} | ||
|
||
if len(subscription.PreferredJobArr) > 0 { | ||
where = append(where, "job_tags_id_arr && ?") // `&&`: overlap (have elements in common) | ||
param = append(param, getArrToString(subscription.PreferredJobArr)) | ||
} | ||
|
||
if len(subscription.PreferredSkillArr) > 0 { | ||
where = append(where, "skill_tags_id_arr && ?") // `&&`: overlap (have elements in common) | ||
param = append(param, getArrToString(subscription.PreferredSkillArr)) | ||
} | ||
|
||
if err := db.Select( | ||
"item_title", | ||
"LEFT(item_description, 50) as item_description", | ||
"item_link", | ||
"NULLIF(item_thumbnail, 'https://www.nerdplanet.app/images/feed-thumbnail.png') as item_thumbnail", | ||
"feed_name", | ||
).Where(strings.Join(where, " AND "), param...).Limit(10).Find(&items).Error; err != nil { | ||
slog.Error(err.Error(), "error", err) | ||
return | ||
} | ||
|
||
if len(items) > 0 { | ||
_, b, _, _ := runtime.Caller(0) | ||
configDirPath := path.Join(path.Dir(b)) | ||
t, err := template.ParseFiles(fmt.Sprintf("%s/template/newsletter.html", configDirPath)) | ||
if err != nil { | ||
slog.Error(err.Error(), "error", err) | ||
return | ||
} | ||
|
||
var body bytes.Buffer | ||
if err := t.Execute(&body, items); err != nil { | ||
slog.Error(err.Error(), "error", err) | ||
return | ||
} | ||
|
||
auth := smtp.PlainAuth("", conf.Smtp.UserName, conf.Smtp.Password, conf.Smtp.Host) | ||
from := conf.Smtp.UserName | ||
to := []string{subscription.Email} | ||
subject := "Subject: 너드플래닛 기술블로그 뉴스레터 \n" | ||
mime := "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n" | ||
msg := []byte(subject + mime + body.String()) | ||
err = smtp.SendMail(fmt.Sprintf("%s:%d", conf.Smtp.Host, conf.Smtp.Port), auth, from, to, msg) | ||
if err != nil { | ||
slog.Error(err.Error(), "error", err) | ||
return | ||
} | ||
} | ||
|
||
subscription.Published = time.Now() | ||
if err := db.Save(subscription).Error; err != nil { | ||
slog.Error(err.Error(), "error", err) | ||
return | ||
} | ||
} | ||
|
||
func getArrToString(arr []int64) string { | ||
strArr := make([]string, len(arr)) | ||
for i, v := range arr { | ||
strArr[i] = strconv.FormatInt(v, 10) | ||
} | ||
|
||
return fmt.Sprintf("{%s}", strings.Join(strArr, ",")) | ||
} | ||
|
||
type Config struct { | ||
Database Database `mapstructure:"DATABASE"` | ||
Smtp Smtp `mapstructure:"SMTP"` | ||
} | ||
|
||
type Database struct { | ||
Host string `mapstructure:"HOST"` | ||
Port int `mapstructure:"PORT"` | ||
LogLevel int `mapstructure:"LOG_LEVEL"` // 1:Silent, 2:Error, 3:Warn, 4:Info | ||
UserName string `mapstructure:"USER_NAME"` | ||
Password string `mapstructure:"PASSWORD"` | ||
DbName string `mapstructure:"DB_NAME"` | ||
} | ||
|
||
type Smtp struct { | ||
Host string `mapstructure:"HOST"` | ||
Port int `mapstructure:"PORT"` | ||
UserName string `mapstructure:"USER_NAME"` | ||
Password string `mapstructure:"PASSWORD"` | ||
} | ||
|
||
func NewConfig() (*Config, error) { | ||
_, b, _, _ := runtime.Caller(0) | ||
configDirPath := path.Join(path.Dir(b)) | ||
|
||
conf := Config{} | ||
viper.SetConfigName("config") | ||
viper.SetConfigType("json") | ||
viper.AddConfigPath(configDirPath) | ||
|
||
err := viper.ReadInConfig() | ||
if err != nil { | ||
slog.Error("Read config file.", "err", err) | ||
return nil, err | ||
} | ||
|
||
viper.AutomaticEnv() | ||
|
||
err = viper.Unmarshal(&conf) | ||
if err != nil { | ||
slog.Error("Unmarshal config file.", "err", err) | ||
return nil, err | ||
} | ||
|
||
return &conf, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.