feat(service): projects creation and getters implementation
This commit is contained in:
1
go.mod
1
go.mod
@@ -8,7 +8,6 @@ require (
|
||||
forge.capytal.company/loreddev/x v0.0.0-20250305165122-0ccb26ab783b
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.3
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.78.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/tursodatabase/go-libsql v0.0.0-20241221181756-6121e81fbf92
|
||||
)
|
||||
|
||||
|
||||
2
go.sum
2
go.sum
@@ -26,8 +26,6 @@ github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ=
|
||||
github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/libsql/sqlite-antlr4-parser v0.0.0-20240327125255-dbf53b6cbf06 h1:JLvn7D+wXjH9g4Jsjo+VqmzTUpl/LX7vfr6VOfSWTdM=
|
||||
github.com/libsql/sqlite-antlr4-parser v0.0.0-20240327125255-dbf53b6cbf06/go.mod h1:FUkZ5OHjlGPjnM2UyGJz9TypXQFgYqw6AFNO1UiROTM=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
|
||||
75
internals/randstr/randstr.go
Normal file
75
internals/randstr/randstr.go
Normal file
@@ -0,0 +1,75 @@
|
||||
// This file has code copied from the "randstr" Go module, which can be found at
|
||||
// https://github.com/thanhpk/randsr. The original code is licensed under the MIT
|
||||
// license, which a copy can be found at https://github.com/thanhpk/randstr/blob/master/LICENSE
|
||||
// and is provided below:
|
||||
//
|
||||
// # The MIT License
|
||||
//
|
||||
// Copyright (c) 2010-2018 Google, Inc. http://angularjs.org
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
// Package randstr provides basic functions for generating random bytes, string
|
||||
package randstr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
// HexChars holds a string containing all characters used in a hexadecimal value.
|
||||
const HexChars = "0123456789abcdef"
|
||||
|
||||
// NewHex generates a new Hexadecimal string with length of n
|
||||
//
|
||||
// Example: 67aab2d956bd7cc621af22cfb169cba8
|
||||
func NewHex(n int) (string, error) { return New(n, HexChars) }
|
||||
|
||||
// New generates a random string using only letters provided in the letters parameter.
|
||||
//
|
||||
// If the letters parameter is omitted, this function will use HexChars instead.
|
||||
func New(n int, chars ...string) (string, error) {
|
||||
runes := []rune(HexChars)
|
||||
if len(chars) > 0 {
|
||||
runes = []rune(chars[0])
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
b.Grow(n)
|
||||
l := uint32(len(runes))
|
||||
for range n {
|
||||
by, err := Bytes(4)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
b.WriteRune(runes[binary.BigEndian.Uint32(by)%l])
|
||||
}
|
||||
return b.String(), nil
|
||||
}
|
||||
|
||||
// Bytes generates n random bytes
|
||||
func Bytes(n int) ([]byte, error) {
|
||||
b := make([]byte, n)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
@@ -1,33 +1,118 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
|
||||
"forge.capytal.company/capytalcode/project-comicverse/database"
|
||||
"forge.capytal.company/capytalcode/project-comicverse/internals/randstr"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
)
|
||||
|
||||
func (s *service) NewProject() error {
|
||||
s.assert.NotNil(s.db)
|
||||
const projectIDLength = 6
|
||||
|
||||
id, err := uuid.NewV7()
|
||||
var (
|
||||
ErrProjectNotExists = errors.New("project does not exists in database")
|
||||
ErrProjectInvalidUUID = errors.New("UUID provided is invalid")
|
||||
)
|
||||
|
||||
type Project struct {
|
||||
XMLName xml.Name `xml:"body"`
|
||||
ID string `xml:"id,attr"`
|
||||
Title string `xml:"h1"`
|
||||
Contents string `xml:"-"`
|
||||
}
|
||||
|
||||
func (s *Service) CreateProject() (Project, error) {
|
||||
s.assert.NotNil(s.db)
|
||||
s.assert.NotNil(s.s3)
|
||||
s.assert.NotNil(s.ctx)
|
||||
s.assert.NotZero(s.bucket)
|
||||
|
||||
s.log.Debug("Creating new project")
|
||||
|
||||
id, err := randstr.NewHex(projectIDLength)
|
||||
if err != nil {
|
||||
return err
|
||||
return Project{}, err
|
||||
}
|
||||
|
||||
s.assert.NotZero(id.String(), "UUID should never be invalid")
|
||||
title := "New Project"
|
||||
|
||||
err = s.db.Insert(&database.Project{
|
||||
ID: id.String(),
|
||||
Title: "New Project",
|
||||
s.assert.NotZero(id, "ID should never be empty")
|
||||
|
||||
s.log.Debug("Creating project on database", slog.String("id", id))
|
||||
|
||||
_, err = s.db.CreateProject(id, title)
|
||||
if err != nil {
|
||||
return Project{}, err
|
||||
}
|
||||
|
||||
p := Project{
|
||||
ID: id,
|
||||
Title: title,
|
||||
}
|
||||
|
||||
c, err := xml.Marshal(p)
|
||||
if err != nil {
|
||||
return Project{}, err
|
||||
}
|
||||
|
||||
s.log.Debug("Creating project on storage", slog.String("id", id))
|
||||
|
||||
f := fmt.Sprintf("%s.comic.xml", id)
|
||||
_, err = s.s3.PutObject(s.ctx, &s3.PutObjectInput{
|
||||
Bucket: &s.bucket,
|
||||
Key: &f,
|
||||
Body: bytes.NewReader(c),
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Join(errors.New("database returned error while inserting new project"), err)
|
||||
return Project{}, err
|
||||
}
|
||||
|
||||
p.Contents = string(c)
|
||||
|
||||
return nil
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (s *service) ListProjects() {
|
||||
func (s *Service) GetProject(id string) (Project, error) {
|
||||
s.assert.NotNil(s.db)
|
||||
s.assert.NotNil(s.s3)
|
||||
s.assert.NotZero(s.bucket)
|
||||
s.assert.NotNil(s.ctx)
|
||||
s.assert.NotZero(id)
|
||||
|
||||
res, err := s.db.GetProject(id)
|
||||
if errors.Is(err, database.ErrNoRows) {
|
||||
return Project{}, errors.Join(ErrProjectNotExists, err)
|
||||
}
|
||||
if err != nil {
|
||||
return Project{}, err
|
||||
}
|
||||
|
||||
p := Project{
|
||||
ID: res.ID,
|
||||
Title: res.Title,
|
||||
}
|
||||
|
||||
f := fmt.Sprintf("%s.comic.xml", p.ID)
|
||||
file, err := s.s3.GetObject(s.ctx, &s3.GetObjectInput{
|
||||
Bucket: &s.bucket,
|
||||
Key: &f,
|
||||
})
|
||||
if err != nil {
|
||||
return p, err
|
||||
}
|
||||
|
||||
c, err := io.ReadAll(file.Body)
|
||||
if err != nil {
|
||||
return p, err
|
||||
}
|
||||
|
||||
p.Contents = string(c)
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user