Skip to content

Commit

Permalink
Merge pull request #2 from Digital-Certificates-DL/feature/tempaltes
Browse files Browse the repository at this point in the history
Feature/tempaltes
  • Loading branch information
MarkCherepovskyi authored Nov 3, 2023
2 parents 507221d + cd15851 commit 1269c1a
Show file tree
Hide file tree
Showing 26 changed files with 447 additions and 135 deletions.
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

0 comments on commit 1269c1a

Please sign in to comment.