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

Feature/tempaltes #2

Merged
merged 10 commits into from
Nov 3, 2023
Merged
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# certificates-svc

## Install

```
git clone https://github.com/Digital-Certificates-DL/certificates-svc
cd certificates-svc
go build main.go
export KV_VIPER_FILE=./config.yaml
./main migrate up
./main run service
```

## Documentation

We do use openapi:json standard for API. We use swagger for documenting our API.

To open online documentation, go to [swagger editor](http://localhost:8080/swagger-editor/) here is how you can start it
```
cd docs
npm install
npm start
```
To build documentation use `npm run build` command,
that will create open-api documentation in `web_deploy` folder.

To generate resources for Go models run `./generate.sh` script in root folder.
use `./generate.sh --help` to see all available options.



## Running from Source

* Set up environment value with config file path `KV_VIPER_FILE=./config.yaml`
* Provide valid config file
* Launch the service with `migrate up` command to create database schema
* Launch the service with `run service` command


### Database
For services, we do use ***PostgresSQL*** database.
You can [install it locally](https://www.postgresql.org/download/) or use [docker image](https://hub.docker.com/_/postgres/).


4 changes: 2 additions & 2 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ networks:
ipfs_pr_key: ""

sbt:
external_url: "https://dlt-academy.com/certificates"
external_url: "https://distributed.education/certificates"

google:
secret_path: "./client.json"
Expand All @@ -35,7 +35,7 @@ tables:

qr_code:
qr_path: "./qr/"
template: "message:\n%s\n\naddress:\n%s\n\nsignature:\n%s\n\ncertificate page:\nhttps://dlt-academy.com/certificates"
template: "message:\n%s\n\naddress:\n%s\n\nsignature:\n%s\n\ncertificate page:\nhttps://distributed.education/certificates"

templates:
list:
Expand Down
14 changes: 14 additions & 0 deletions docs/spec/components/schemas/ExpiredTokenError.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
allOf:
- $ref: '#/components/schemas/ExpiredTokenKey'
- type: object
required:
- attributes
properties:
attributes:
type: object
required:
- error
properties:
error:
type: boolean
example: false
9 changes: 9 additions & 0 deletions docs/spec/components/schemas/ExpiredTokenKey.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
type: object
required:
- type
- id
properties:
type:
type: string
enum:
- expired_token
6 changes: 5 additions & 1 deletion docs/spec/components/schemas/Template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@ allOf:
- user
properties:
user:
type: string
type: object
$ref: '#/components/schemas/UserKey'
attributes:
type: object
required:
- template
- background_img
- is_completed
- template_name
- template_short_name
properties:
template:
type: object
Expand All @@ -29,4 +31,6 @@ allOf:
type: boolean
template_name:
type: string
template_short_name:
type: string

3 changes: 2 additions & 1 deletion internal/assets/migrations/001_initial.sql
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ CREATE TABLE template (
user_id INTEGER REFERENCES users(id),
template TEXT,
img_bytes text,
name TEXT
name TEXT,
short_name TEXT
);

-- +migrate Down
Expand Down
7 changes: 7 additions & 0 deletions internal/data/pg/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,10 @@ func (q *TemplateQ) FilterByName(name string) data.TemplateQ {

return q
}

func (q *TemplateQ) FilterByShortName(name string) data.TemplateQ {
q.sql = q.sql.Where(sq.Eq{nameField: name})
q.upd = q.upd.Where(sq.Eq{nameField: name})

return q
}
12 changes: 7 additions & 5 deletions internal/data/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ type TemplateQ interface {
Select() ([]Template, error)
FilterByUser(ids int64) TemplateQ
FilterByName(name string) TemplateQ
FilterByShortName(name string) TemplateQ
FilterByID(ids int64) TemplateQ
}

type Template struct {
ID int64 `db:"id" structs:"-"`
UserID int64 `db:"user_id" structs:"user_id"`
Name string `db:"name" structs:"name"`
Template []byte `db:"template" structs:"template"`
ImgBytes []byte `db:"img_bytes" structs:"img_bytes"` //todo make better
ID int64 `db:"id" structs:"-"`
UserID int64 `db:"user_id" structs:"user_id"`
Name string `db:"name" structs:"name"`
ShortName string `db:"short_name" structs:"short_name"`
Template []byte `db:"template" structs:"template"`
ImgBytes []byte `db:"img_bytes" structs:"img_bytes"` //todo make better
}
2 changes: 1 addition & 1 deletion internal/service/api/handlers/check_container_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func CheckContainerState(w http.ResponseWriter, r *http.Request) {
container := PdfCreator(r).CheckContainerState(containerID)
if container == nil {
Log(r).WithError(err).Error("user not found")
w.WriteHeader(http.StatusProcessing)
w.WriteHeader(http.StatusNoContent)
return
}
Log(r).Debug("container on handler: ", container)
Expand Down
59 changes: 9 additions & 50 deletions internal/service/api/handlers/create_template.go
Original file line number Diff line number Diff line change
@@ -1,26 +1,23 @@
package handlers

import (
"encoding/base64"
"encoding/json"
"gitlab.com/distributed_lab/ape"
"gitlab.com/distributed_lab/ape/problems"
"gitlab.com/tokend/course-certificates/ccp/internal/data"
"gitlab.com/tokend/course-certificates/ccp/internal/service/api/requests"
"gitlab.com/tokend/course-certificates/ccp/internal/service/core/pdf"
"gitlab.com/tokend/course-certificates/ccp/resources"
"net/http"
"strings"
)

func CreateTemplate(w http.ResponseWriter, r *http.Request) {
template, backgroundImg, req, err := requests.NewGenerateTemplate(r)
template, _, req, err := requests.NewGenerateTemplate(r)
if err != nil {
Log(r).WithError(err).Error("failed to generate template")
ape.RenderErr(w, problems.BadRequest(err)...)
return
}
defaultData := pdf.DefaultData
client, err := MasterQ(r).ClientQ().FilterByName(req.Data.Relationships.User).Get()
client, err := MasterQ(r).ClientQ().FilterByName(req.Data.Relationships.User.Data.ID).Get()
if err != nil {
Log(r).WithError(err).Error("failed to get client")
ape.RenderErr(w, problems.InternalError())
Expand All @@ -32,35 +29,6 @@ func CreateTemplate(w http.ResponseWriter, r *http.Request) {
return
}

if template.Width == 0 || template.High == 0 {
tp := pdf.DefaultTemplateTall
_, _, imgBytes, err := tp.Prepare(defaultData, pdf.NewPDFConfig(Config(r)), MasterQ(r), backgroundImg, client.ID, StaticConfiger(r).Location)
if err != nil {
Log(r).WithError(err).Error("failed to prepare pdf")
ape.RenderErr(w, problems.InternalError())
return
}
ape.Render(w, newTemplateImageResp(imgBytes))
return
}

file := pdf.NewPDF(template.High, template.Width)

file.SetName(template.Name.X, template.Name.Y, template.Name.FontSize, template.Name.Font)
file.SetDate(template.Date.X, template.Date.Y, template.Date.FontSize, template.Date.Font)
file.SetCourse(template.Course.X, template.Course.Y, template.Course.FontSize, template.Course.Font)
file.SetCredits(template.Credits.X, template.Credits.Y, template.Credits.FontSize, template.Credits.Font)
file.SetExam(template.Exam.X, template.Exam.Y, template.Exam.FontSize, template.Exam.Font)
file.SetLevel(template.Level.X, template.Level.Y, template.Level.FontSize, template.Level.Font)
file.SetSerialNumber(template.SerialNumber.X, template.SerialNumber.Y, template.SerialNumber.FontSize, template.SerialNumber.Font)
file.SetPoints(template.Points.X, template.Points.Y, template.Points.FontSize, template.Points.Font)
file.SetQR(template.QR.X, template.QR.Y, template.QR.FontSize, template.QR.High, template.Width)
_, _, imgBytes, err := template.Prepare(defaultData, pdf.NewPDFConfig(Config(r)), MasterQ(r), backgroundImg, client.ID, StaticConfiger(r).Location)
if err != nil {
Log(r).WithError(err).Error("failed to prepare pdf")
ape.RenderErr(w, problems.InternalError())
return
}
if req.Data.Attributes.IsCompleted {
templateBytes, err := json.Marshal(template)
if err != nil {
Expand All @@ -70,27 +38,18 @@ func CreateTemplate(w http.ResponseWriter, r *http.Request) {
}

err = MasterQ(r).TemplateQ().Insert(&data.Template{
Template: templateBytes,
//ImgBytes: backgroundImg,
Name: req.Data.Attributes.TemplateName,
UserID: client.ID,
Template: templateBytes,
ImgBytes: []byte(strings.Replace(req.Data.Attributes.BackgroundImg, "data:image/png;base64,", "", 1)),
Name: req.Data.Attributes.TemplateName,
ShortName: req.Data.Attributes.TemplateShortName,
UserID: client.ID,
})
if err != nil {
Log(r).WithError(err).Error("failed to insert template")
ape.RenderErr(w, problems.InternalError())
return
}
}
ape.Render(w, newTemplateImageResp(imgBytes))
w.WriteHeader(http.StatusNoContent)
return
}

func newTemplateImageResp(img []byte) resources.TemplateResponse {
return resources.TemplateResponse{
Data: resources.Template{
Attributes: resources.TemplateAttributes{
BackgroundImg: base64.StdEncoding.EncodeToString(img),
},
},
}
}
6 changes: 3 additions & 3 deletions internal/service/api/handlers/get_templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,16 @@ func GetTemplates(w http.ResponseWriter, r *http.Request) {
}

func newTemlateListResp(tmps []data.Template) resources.TemplateListResponse {
var reponseTmpList []resources.Template
responseTmpList := make([]resources.Template, 0)
for _, tmp := range tmps {
reponseTmpList = append(reponseTmpList, resources.Template{
responseTmpList = append(responseTmpList, resources.Template{
Attributes: resources.TemplateAttributes{
BackgroundImg: string(tmp.ImgBytes),
TemplateName: tmp.Name,
},
})
}
return resources.TemplateListResponse{
Data: reponseTmpList,
Data: responseTmpList,
}
}
2 changes: 1 addition & 1 deletion internal/service/api/handlers/get_users.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func GetUsers(w http.ResponseWriter, r *http.Request) {
}

Log(r).Error("failed to parse table: Errors:", errs)
ape.RenderErr(w, problems.BadRequest(err)...)
ape.RenderErr(w, problems.Unauthorized())
return
}

Expand Down
4 changes: 2 additions & 2 deletions internal/service/api/requests/create_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"gitlab.com/tokend/course-certificates/ccp/internal/service/core/pdf"
"gitlab.com/tokend/course-certificates/ccp/resources"
"image"
"image/jpeg"
"image/png"
"net/http"
"strings"
)
Expand Down Expand Up @@ -52,7 +52,7 @@ func base64toJpg(data string) ([]byte, error) {
}

buf := new(bytes.Buffer)
if err = jpeg.Encode(buf, m, &jpeg.Options{Quality: 75}); err != nil {
if err = png.Encode(buf, m); err != nil {
return nil, err
}

Expand Down
3 changes: 0 additions & 3 deletions internal/service/api/requests/ipfs_upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"github.com/pkg/errors"
"gitlab.com/tokend/course-certificates/ccp/resources"
"net/http"
"regexp"
)

type IpfsFileUpload struct {
Expand All @@ -32,7 +31,5 @@ func validateIpfsData(request resources.IpfsFileUpload) error {
validation.Required),
"/attributes/img": validation.Validate(request.Attributes.Img,
validation.Required),
"/attributes/name": validation.Validate(request.Attributes.Name,
validation.Required, validation.Match(regexp.MustCompile("^([A-Za-z])[A-Za-z\\s]+$"))),
}).Filter()
}
1 change: 1 addition & 0 deletions internal/service/api/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ func (s *service) router(cfg config.Config) chi.Router {
r.Route("/certificate", func(r chi.Router) {
r.Post("/", handlers.PrepareCertificate)
r.Post("/bitcoin", handlers.UpdateCertificate)
r.Put("/", handlers.UpdateCertificate)
r.Post("/ipfs", handlers.UploadFileToIpfs)
r.Get("/{container}", handlers.CheckContainerState)

Expand Down
8 changes: 4 additions & 4 deletions internal/service/core/google/google_drive.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ func (g *Google) GetFiles() ([]*drive.File, error) {
if len(r.Files) == 0 {

return nil, errors.New("No files found.")
} else {
for _, i := range r.Files {
g.cfg.Log().Info("%s (%s)\n", i.Name, i.Id)
}
}
for _, i := range r.Files {
g.cfg.Log().Info("%s (%s)\n", i.Name, i.Id)
}

return r.Files, nil
}

Expand Down
18 changes: 14 additions & 4 deletions internal/service/core/pdf/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,14 @@ func (c *Container) Generate() error {
files = append(files, google.FilesBytes{File: file, Name: name, ID: user.ID, Type: "image/svg+xml"})

pdf := PDF{}
certificate := pdf.SetTemplateData(DefaultTemplateTall)
certificateTemplate, err := pdf.InitTemplate(c.masterQ, user.CourseTitle, c.owner.ID)
if err != nil {
return errors.Wrap(err, "failed to get template")
}

certificate := pdf.SetTemplateData(*certificateTemplate)

pdfData := NewData(user.Participant, user.CourseTitle, "45 hours / 1.5 ECTS Credit", user.Points, user.SerialNumber, user.Date, img, user.Note, "", "")
pdfData := NewData(user.Participant, user.CourseTitle, certificateTemplate.Credits.Text, user.Points, user.SerialNumber, user.Date, img, user.Note, "", "")
fileBytes, name, certificateImg, err := certificate.Prepare(pdfData, NewPDFConfig(c.config), c.masterQ, nil, c.owner.ID, c.config.StaticConfig().Location)
if err != nil {
return errors.Wrap(err, "failed to create pdf")
Expand Down Expand Up @@ -105,9 +110,14 @@ func (c *Container) Update() error {
files = append(files, google.FilesBytes{File: file, Name: name, ID: user.ID, Type: "image/svg+xml"})

pdf := PDF{}
certificate := pdf.SetTemplateData(DefaultTemplateTall)
certificateTemplate, err := pdf.InitTemplate(c.masterQ, user.CourseTitle, c.owner.ID)
if err != nil {
return errors.Wrap(err, "failed to get template")
}

certificate := pdf.SetTemplateData(*certificateTemplate)

pdfData := NewData(user.Participant, user.CourseTitle, "45 hours / 1.5 ECTS Credit", user.Points, user.SerialNumber, user.Date, img, user.Note, "", "")
pdfData := NewData(user.Participant, user.CourseTitle, certificateTemplate.Credits.Text, user.Points, user.SerialNumber, user.Date, img, user.Note, "", "")
fileBytes, name, certificateImg, err := certificate.Prepare(pdfData, NewPDFConfig(c.config), c.masterQ, nil, c.owner.ID, c.config.StaticConfig().Location)
if err != nil {
return errors.Wrap(err, "failed to create pdf")
Expand Down
Loading