Skip to content
Open
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
13 changes: 13 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.git
.gitkeep
.gitignore
.idea
.vscode
third_party/*
*.pb.go
temp
*.md
env.list
Dockerfile
protoc_options_generate.txt
*.env
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
.vscode
third_party/*
*.pb.go
*.env
13 changes: 13 additions & 0 deletions Dockerfile.app
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM golang:1.19.3-alpine as builder
WORKDIR /app/src
COPY go.mod go.sum .
RUN go mod download && go mod verify
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /app/bin/certificate-server /app/src/cmd/rpc
###
FROM surnet/alpine-wkhtmltopdf:3.16.2-0.12.6-small
WORKDIR /app/bin
COPY --from=builder /app/bin/certificate-server /app/bin/certificate-server
RUN mkdir -p /storage
ENV STORAGE=local
ENTRYPOINT ["./certificate-server"]
23 changes: 23 additions & 0 deletions Dockerfile.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
FROM golang:1.19.3
WORKDIR /app
RUN apt-get update; \
apt-get install -y wget unzip
RUN wget -P /tmp https://github.com/protocolbuffers/protobuf/releases/download/v21.9/protoc-21.9-linux-x86_64.zip
RUN unzip /tmp/protoc-21.9-linux-x86_64.zip -d ./third_party
#
RUN go install google.golang.org/protobuf/cmd/[email protected]
RUN go install google.golang.org/grpc/cmd/[email protected]
#
RUN mkdir -p ./protobuf/transport/certificate /result
COPY ./api/proto/* ./api/proto/
RUN third_party/bin/protoc \
-Iapi/proto \
-Ithird_party/include \
--proto_path=api/proto \
--go_out=protobuf/transport/certificate \
--go_opt=paths=source_relative \
--go-grpc_out=protobuf/transport/certificate \
--go-grpc_opt=paths=source_relative \
api/proto/*.proto
#
ENTRYPOINT cp /app/protobuf/transport/certificate/* /result/
58 changes: 58 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Certificates service
===

### Запуск сервиса в Docker:
#### Создание образа и генерация **protobuf** файлов из **".proto"**:
`docker image build -f Dockerfile.proto -t cert-proto .`

#### Сохранение в локальном проекте **protobuf** файлов из Docker контейнера **cert-proto**:
`docker container run --rm -v $PWD/protobuf/transport/certificate:/result cert-proto` // Linux
`docker container run --rm -v %CD%/protobuf/transport/certificate:/result cert-proto` // Windows

#### Создание образа для запуска проекта:
`docker image build -f Dockerfile.app -t cert-app .`

#### Запуск проекта с сохранением в Local Storage:
`docker container run --rm --env-file env.list -p 1234:1234 -it -v certificate-storage:/storage cert-app`
В файле **env.list** задаются переменные откружения необходимые для запуска сервиса.
Тип Storage задается перемнной **STORAGE=local**, переменную **STORAGE** можно опустить, значение **local** будет присвоено по умолчанию.
Монтируется Docker VOLUME: **certificate-storage** на локальной машине/сервере, для сохранения шаблонов и сертификатов.

#### Запуск проекта с сохранением в AWS S3 :
`docker container run --rm --env-file env.list --env-file s3.env -p 1234:1234 -it cert-app`
В файле **env.list** обязательно задается переменная окружения **STORAGE=s3**.
В файле **s3.env** обязательно задаються переменные окружения:
```
S3_ENDPOINT=... // s3.amazonaws.com - если планируем использовать облачное хранилище от Amazon.
S3_BUCKET_NAME=... // Имя существующего Bucket, на который у вас есть права записи.
ACCESS_KEY_ID=...
SECRET_ACCESS_KEY=...
```
### Требования к шаблонам:
В шаблоне могут содержаться следующие теги замены:
```
{{.CourseName}}
{{.CourseType}}
{{.CourseHours}}
{{.CourseDate}}
{{.CourseMentors}}
{{.StudentFirstname}}
{{.StudentLastname}}
{{.QrCodeLink}}
```
Шаблоны с любыми другими тегами замены будут отклонены валидатором.
Вместо тега замены `{{.QrCodeLink}}` будет вставлен **QR** код в формате **PNG**: ссылка на сертификат.

### Пример простого HTML шаблона:
```
<html><body><h1 style="color:red;">Test html color<h1>
<p>{{.CourseName}}</p>
<p>{{.CourseType}}</p>
<p>{{.CourseHours}}</p>
<p>{{.CourseDate}}</p>
<p>{{.CourseMentors}}</p>
<p>{{.StudentFirstname}}</p>
<p>{{.StudentLastname}}</p>
<p><img src={{.QrCodeLink}} width="128" height="128"></p>
</body></html>
```
2 changes: 0 additions & 2 deletions api/proto/README.md

This file was deleted.

75 changes: 75 additions & 0 deletions api/proto/certificate.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
syntax = "proto3";

package certificate;

option go_package = "gus_certificates/protobuf/transport/certificate;certificate";

import "google/protobuf/empty.proto";

service Certificate {
rpc IssueCertificate(IssueCertificateReq) returns (IssueCertificateResp);
rpc GetCertificateFileByID(GetCertificateFileByIDReq) returns (GetCertificateFileByIDResp);
rpc GetCertificateLinkByID(GetCertificateLinkByIDReq) returns (GetCertificateLinkByIDResp);
rpc AddTemplate(AddTemplateReq) returns (AddTemplateResp);
rpc DeleteTemplate(DeleteTemplateReq) returns (DeleteTemplateResp);
}

message IssueCertificateReq {
StudentMessage student = 1;
string template_name = 2;
CourseMessage course = 3;
}

message IssueCertificateResp {
string id = 1;
}

message GetCertificateFileByIDReq {
string id = 1;
}

message GetCertificateLinkByIDReq {
string id = 1;
}

message GetCertificateFileByIDResp {
bytes certificate = 1;
}

message GetCertificateLinkByIDResp {
string link = 1;
}

message AddTemplateReq {
string template_name = 1;
bytes template = 2;
}

message DeleteTemplateReq {
string template_name = 1;
}

message AddTemplateResp {
Status status = 1;
}

message DeleteTemplateResp {
Status status = 1;
}

message StudentMessage {
string firstname = 1;
string lastname = 2;
}

message CourseMessage {
string course_name = 1;
string course_type = 2;
string hours = 3;
string date = 4;
repeated string mentors = 5;
}

message Status {
int32 code = 1;
}
47 changes: 43 additions & 4 deletions app/certgenerator/certgenerator.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,18 @@ package certgenerator

import (
"bytes"
"crypto/md5"
"encoding/base64"
"fmt"
"html/template"
"strings"

valid "github.com/go-ozzo/ozzo-validation/v4"
)

var shortTextFieldRule = []valid.Rule{valid.Required, valid.RuneLength(1, 50)}
var longTextFieldRule = []valid.Rule{valid.Required, valid.RuneLength(1, 250)}

type CertGenerator struct {
data certData
}
Expand All @@ -18,6 +26,19 @@ type certData struct {
CourseMentors string
StudentFirstname string
StudentLastname string
QrCodeLink template.URL
}

func (c *CertGenerator) ValidateData() error {
return valid.ValidateStruct(&c.data,
valid.Field(&c.data.CourseName, longTextFieldRule...),
valid.Field(&c.data.CourseType, shortTextFieldRule...),
valid.Field(&c.data.CourseHours, shortTextFieldRule...),
valid.Field(&c.data.CourseDate, shortTextFieldRule...),
valid.Field(&c.data.CourseMentors, longTextFieldRule...),
valid.Field(&c.data.StudentFirstname, shortTextFieldRule...),
valid.Field(&c.data.StudentLastname, shortTextFieldRule...),
)
}

func (c *CertGenerator) GenerateCertHTML(templateHTMLData []byte) ([]byte, error) {
Expand All @@ -38,10 +59,21 @@ func (c *CertGenerator) GenerateCertHTML(templateHTMLData []byte) ([]byte, error
return buf.Bytes(), nil
}

// func (c *CertGenerator) getDataForIDGenerator() string {
// return fmt.Sprintf("%s%s%s%s%s%s", c.data.CourseName, c.data.CourseType, c.data.CourseHours,
// c.data.CourseDate, c.data.StudentFirstname, c.data.StudentLastname)
// }
func (c *CertGenerator) CheckTemplateHTML(templateHTMLData []byte) error {
_, err := c.GenerateCertHTML(templateHTMLData)

return err
}

func (c *CertGenerator) GenerateID() string {
data := []byte(c.getDataForIDGenerator())
return fmt.Sprintf("%x", md5.Sum(data))
}

func (c *CertGenerator) getDataForIDGenerator() string {
return fmt.Sprintf("%s%s%s%s%s%s", c.data.CourseName, c.data.CourseType, c.data.CourseHours,
c.data.CourseDate, c.data.StudentFirstname, c.data.StudentLastname)
}

func (c *CertGenerator) SetCourseName(courseName string) {
c.data.CourseName = courseName
Expand Down Expand Up @@ -70,3 +102,10 @@ func (c *CertGenerator) SetStudentFirstname(studentFirstname string) {
func (c *CertGenerator) SetStudentLastname(studentLastname string) {
c.data.StudentLastname = studentLastname
}

func (c *CertGenerator) SetQrCodeLink(imgData []byte) {
htmlTagImagePngBase64 := "data:image/png;base64,"
imgBase64String := base64.StdEncoding.EncodeToString(imgData)

c.data.QrCodeLink = template.URL(fmt.Sprintf("%s%s", htmlTagImagePngBase64, imgBase64String))
}
37 changes: 32 additions & 5 deletions app/certgenerator/certgenerator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,24 @@ import (
)

var testData = struct {
certGenerator CertGenerator
certGenerator *CertGenerator
goodTemplate []byte
failTemplate []byte
expected []byte
expectedCert []byte
expectedId string
}{
certGenerator: CertGenerator{},
certGenerator: &CertGenerator{},
goodTemplate: []byte(`<html><body><h1 style="color:red;">Test html color<h1>
<p>{{.CourseName}}</p><p>{{.CourseType}}</p><p>{{.CourseHours}}</p><p>{{.CourseDate}}</p>
<p>{{.CourseMentors}}</p><p>{{.StudentFirstname}}</p><p>{{.StudentLastname}}</p>
</body></html>`),
failTemplate: []byte(`<html><body><h1 style="color:red;">Test html color<h1>
<p>{{.CourseName_Fail}}</p></body></html>`),
expected: []byte(`<html><body><h1 style="color:red;">Test html color<h1>
expectedCert: []byte(`<html><body><h1 style="color:red;">Test html color<h1>
<p>Golang</p><p>Theory</p><p>35</p><p>25.01.2023</p>
<p>Pavel Gordiyanov, Mikita Viarbovikau, Sergey Shtripling</p><p>Ivan</p><p>Ivanov</p>
</body></html>`),
expectedId: "612364afe471b3b1cc80083183fd381d",
}

func TestMain(m *testing.M) {
Expand Down Expand Up @@ -52,7 +54,32 @@ func TestGenerateCertificate(t *testing.T) {
t.Error(err)
}

if !reflect.DeepEqual(gotCertif, testData.expected) {
if !reflect.DeepEqual(gotCertif, testData.expectedCert) {
t.Errorf("%q and %q should be equal", "gotSertif", "expectedCert")
}
}

func TestGenerateID(t *testing.T) {
generator := testData.certGenerator

actualId := generator.GenerateID()
if actualId != testData.expectedId {
t.Errorf("expected:%q, actual:%q", testData.expectedId, actualId)
}
}

func TestCheckTemplateHTML_fail(t *testing.T) {
generator := testData.certGenerator
err := generator.CheckTemplateHTML(testData.failTemplate)
if err == nil {
t.Error("err must not be nil")
}
}

func TestCheckTemplateHTML(t *testing.T) {
generator := testData.certGenerator
err := generator.CheckTemplateHTML(testData.goodTemplate)
if err != nil {
t.Error(err)
}
}
2 changes: 0 additions & 2 deletions app/idgenerator/README.md

This file was deleted.

2 changes: 0 additions & 2 deletions app/server/README.md

This file was deleted.

Loading