added basic skeleton and two API endpoints to test backend stack.

This commit is contained in:
2022-04-12 20:30:16 +02:00
parent 22e8290371
commit 11c0bd62e2
40 changed files with 1480 additions and 8 deletions
+48
View File
@@ -0,0 +1,48 @@
package mysql
import (
"database/sql"
"fmt"
"git.aiterp.net/stufflog3/stufflog3-api/internal/database"
"time"
_ "github.com/go-sql-driver/mysql"
)
type Database struct {
db *sql.DB
}
func (d *Database) Scopes() database.ScopeRepository {
return &scopeRepository{db: d.db}
}
func (d *Database) Stats(scopeID int) database.StatRepository {
//TODO implement me
panic("implement me")
}
func (d *Database) Items(scopeID int) database.ItemRepository {
//TODO implement me
panic("implement me")
}
func Connect(host string, port int, username, password, database string) (*Database, error) {
db, err := sql.Open("mysql", fmt.Sprintf(
"%s:%s@(%s:%d)/%s?parseTime=true", username, password, host, port, database,
))
if err != nil {
return nil, err
}
db.SetMaxOpenConns(10)
db.SetMaxIdleConns(10)
db.SetConnMaxIdleTime(time.Minute)
err = db.Ping()
if err != nil {
return nil, err
}
return &Database{db: db}, nil
}
+158
View File
@@ -0,0 +1,158 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.13.0
package mysqlcore
import (
"context"
"database/sql"
"fmt"
)
type DBTX interface {
ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
PrepareContext(context.Context, string) (*sql.Stmt, error)
QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
QueryRowContext(context.Context, string, ...interface{}) *sql.Row
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
func Prepare(ctx context.Context, db DBTX) (*Queries, error) {
q := Queries{db: db}
var err error
if q.getScopeStmt, err = db.PrepareContext(ctx, getScope); err != nil {
return nil, fmt.Errorf("error preparing query GetScope: %w", err)
}
if q.getScopeDisplayNameStmt, err = db.PrepareContext(ctx, getScopeDisplayName); err != nil {
return nil, fmt.Errorf("error preparing query GetScopeDisplayName: %w", err)
}
if q.getScopeWithDisplayNameStmt, err = db.PrepareContext(ctx, getScopeWithDisplayName); err != nil {
return nil, fmt.Errorf("error preparing query GetScopeWithDisplayName: %w", err)
}
if q.listProjectEntriesStmt, err = db.PrepareContext(ctx, listProjectEntries); err != nil {
return nil, fmt.Errorf("error preparing query ListProjectEntries: %w", err)
}
if q.listScopeMembersStmt, err = db.PrepareContext(ctx, listScopeMembers); err != nil {
return nil, fmt.Errorf("error preparing query ListScopeMembers: %w", err)
}
if q.listScopesStmt, err = db.PrepareContext(ctx, listScopes); err != nil {
return nil, fmt.Errorf("error preparing query ListScopes: %w", err)
}
if q.listScopesByUserStmt, err = db.PrepareContext(ctx, listScopesByUser); err != nil {
return nil, fmt.Errorf("error preparing query ListScopesByUser: %w", err)
}
if q.listStatsStmt, err = db.PrepareContext(ctx, listStats); err != nil {
return nil, fmt.Errorf("error preparing query ListStats: %w", err)
}
return &q, nil
}
func (q *Queries) Close() error {
var err error
if q.getScopeStmt != nil {
if cerr := q.getScopeStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing getScopeStmt: %w", cerr)
}
}
if q.getScopeDisplayNameStmt != nil {
if cerr := q.getScopeDisplayNameStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing getScopeDisplayNameStmt: %w", cerr)
}
}
if q.getScopeWithDisplayNameStmt != nil {
if cerr := q.getScopeWithDisplayNameStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing getScopeWithDisplayNameStmt: %w", cerr)
}
}
if q.listProjectEntriesStmt != nil {
if cerr := q.listProjectEntriesStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing listProjectEntriesStmt: %w", cerr)
}
}
if q.listScopeMembersStmt != nil {
if cerr := q.listScopeMembersStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing listScopeMembersStmt: %w", cerr)
}
}
if q.listScopesStmt != nil {
if cerr := q.listScopesStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing listScopesStmt: %w", cerr)
}
}
if q.listScopesByUserStmt != nil {
if cerr := q.listScopesByUserStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing listScopesByUserStmt: %w", cerr)
}
}
if q.listStatsStmt != nil {
if cerr := q.listStatsStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing listStatsStmt: %w", cerr)
}
}
return err
}
func (q *Queries) exec(ctx context.Context, stmt *sql.Stmt, query string, args ...interface{}) (sql.Result, error) {
switch {
case stmt != nil && q.tx != nil:
return q.tx.StmtContext(ctx, stmt).ExecContext(ctx, args...)
case stmt != nil:
return stmt.ExecContext(ctx, args...)
default:
return q.db.ExecContext(ctx, query, args...)
}
}
func (q *Queries) query(ctx context.Context, stmt *sql.Stmt, query string, args ...interface{}) (*sql.Rows, error) {
switch {
case stmt != nil && q.tx != nil:
return q.tx.StmtContext(ctx, stmt).QueryContext(ctx, args...)
case stmt != nil:
return stmt.QueryContext(ctx, args...)
default:
return q.db.QueryContext(ctx, query, args...)
}
}
func (q *Queries) queryRow(ctx context.Context, stmt *sql.Stmt, query string, args ...interface{}) *sql.Row {
switch {
case stmt != nil && q.tx != nil:
return q.tx.StmtContext(ctx, stmt).QueryRowContext(ctx, args...)
case stmt != nil:
return stmt.QueryRowContext(ctx, args...)
default:
return q.db.QueryRowContext(ctx, query, args...)
}
}
type Queries struct {
db DBTX
tx *sql.Tx
getScopeStmt *sql.Stmt
getScopeDisplayNameStmt *sql.Stmt
getScopeWithDisplayNameStmt *sql.Stmt
listProjectEntriesStmt *sql.Stmt
listScopeMembersStmt *sql.Stmt
listScopesStmt *sql.Stmt
listScopesByUserStmt *sql.Stmt
listStatsStmt *sql.Stmt
}
func (q *Queries) WithTx(tx *sql.Tx) *Queries {
return &Queries{
db: tx,
tx: tx,
getScopeStmt: q.getScopeStmt,
getScopeDisplayNameStmt: q.getScopeDisplayNameStmt,
getScopeWithDisplayNameStmt: q.getScopeWithDisplayNameStmt,
listProjectEntriesStmt: q.listProjectEntriesStmt,
listScopeMembersStmt: q.listScopeMembersStmt,
listScopesStmt: q.listScopesStmt,
listScopesByUserStmt: q.listScopesByUserStmt,
listStatsStmt: q.listStatsStmt,
}
}
@@ -0,0 +1,78 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.13.0
package mysqlcore
import (
"database/sql"
"time"
"git.aiterp.net/stufflog3/stufflog3-api/internal/sqltypes"
)
type Item struct {
ID int `json:"id"`
ScopeID int `json:"scope_id"`
ProjectRequirementID sql.NullInt32 `json:"project_requirement_id"`
Name string `json:"name"`
Description string `json:"description"`
CreatedTime time.Time `json:"created_time"`
CreatedUserID string `json:"created_user_id"`
AcquiredTime sql.NullTime `json:"acquired_time"`
ScheduledDate sql.NullTime `json:"scheduled_date"`
}
type ItemStatProgress struct {
ItemID int `json:"item_id"`
StatID int `json:"stat_id"`
Acquired int `json:"acquired"`
Required int `json:"required"`
}
type Project struct {
ID int `json:"id"`
ScopeID int `json:"scope_id"`
AuthorID string `json:"author_id"`
Name string `json:"name"`
Status int `json:"status"`
Description string `json:"description"`
CreatedTime time.Time `json:"created_time"`
}
type ProjectRequirement struct {
ID int `json:"id"`
ScopeID int `json:"scope_id"`
ProjectID int `json:"project_id"`
Name string `json:"name"`
Status int `json:"status"`
Description string `json:"description"`
}
type ProjectRequirementStat struct {
ProjectRequirementID int `json:"project_requirement_id"`
StatID int `json:"stat_id"`
Required int `json:"required"`
}
type Scope struct {
ID int `json:"id"`
Name string `json:"name"`
Abbreviation string `json:"abbreviation"`
}
type ScopeMember struct {
ScopeID int `json:"scope_id"`
UserID string `json:"user_id"`
Name string `json:"name"`
Owner bool `json:"owner"`
}
type Stat struct {
ID int `json:"id"`
ScopeID int `json:"scope_id"`
Name string `json:"name"`
Description string `json:"description"`
Weight float64 `json:"weight"`
AllowedAmounts sqltypes.NullRawMessage `json:"allowed_amounts"`
}
@@ -0,0 +1,3 @@
// Package mysqlcore contains generated code.
package mysqlcore
@@ -0,0 +1,45 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.13.0
// source: project.sql
package mysqlcore
import (
"context"
)
const listProjectEntries = `-- name: ListProjectEntries :many
SELECT id, name, status FROM project
WHERE scope_id = ?
ORDER BY status, created_time
`
type ListProjectEntriesRow struct {
ID int `json:"id"`
Name string `json:"name"`
Status int `json:"status"`
}
func (q *Queries) ListProjectEntries(ctx context.Context, scopeID int) ([]ListProjectEntriesRow, error) {
rows, err := q.query(ctx, q.listProjectEntriesStmt, listProjectEntries, scopeID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListProjectEntriesRow{}
for rows.Next() {
var i ListProjectEntriesRow
if err := rows.Scan(&i.ID, &i.Name, &i.Status); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
@@ -0,0 +1,175 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.13.0
// source: scope.sql
package mysqlcore
import (
"context"
"database/sql"
)
const getScope = `-- name: GetScope :one
SELECT id, name, abbreviation FROM scope
WHERE id = ?
`
func (q *Queries) GetScope(ctx context.Context, id int) (Scope, error) {
row := q.queryRow(ctx, q.getScopeStmt, getScope, id)
var i Scope
err := row.Scan(&i.ID, &i.Name, &i.Abbreviation)
return i, err
}
const getScopeDisplayName = `-- name: GetScopeDisplayName :one
SELECT name FROM scope_member
WHERE user_id = ? AND scope_id = ?
`
type GetScopeDisplayNameParams struct {
UserID string `json:"user_id"`
ScopeID int `json:"scope_id"`
}
func (q *Queries) GetScopeDisplayName(ctx context.Context, arg GetScopeDisplayNameParams) (string, error) {
row := q.queryRow(ctx, q.getScopeDisplayNameStmt, getScopeDisplayName, arg.UserID, arg.ScopeID)
var name string
err := row.Scan(&name)
return name, err
}
const getScopeWithDisplayName = `-- name: GetScopeWithDisplayName :one
SELECT id, scope.name, abbreviation, sm.name AS display_name FROM scope
LEFT JOIN scope_member sm ON scope.id = sm.scope_id
WHERE id = ? AND sm.user_id = ?
`
type GetScopeWithDisplayNameParams struct {
ID int `json:"id"`
UserID string `json:"user_id"`
}
type GetScopeWithDisplayNameRow struct {
ID int `json:"id"`
Name string `json:"name"`
Abbreviation string `json:"abbreviation"`
DisplayName sql.NullString `json:"display_name"`
}
func (q *Queries) GetScopeWithDisplayName(ctx context.Context, arg GetScopeWithDisplayNameParams) (GetScopeWithDisplayNameRow, error) {
row := q.queryRow(ctx, q.getScopeWithDisplayNameStmt, getScopeWithDisplayName, arg.ID, arg.UserID)
var i GetScopeWithDisplayNameRow
err := row.Scan(
&i.ID,
&i.Name,
&i.Abbreviation,
&i.DisplayName,
)
return i, err
}
const listScopeMembers = `-- name: ListScopeMembers :many
SELECT user_id, name, owner FROM scope_member
WHERE scope_id = ?
ORDER BY name
`
type ListScopeMembersRow struct {
UserID string `json:"user_id"`
Name string `json:"name"`
Owner bool `json:"owner"`
}
func (q *Queries) ListScopeMembers(ctx context.Context, scopeID int) ([]ListScopeMembersRow, error) {
rows, err := q.query(ctx, q.listScopeMembersStmt, listScopeMembers, scopeID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListScopeMembersRow{}
for rows.Next() {
var i ListScopeMembersRow
if err := rows.Scan(&i.UserID, &i.Name, &i.Owner); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listScopes = `-- name: ListScopes :many
SELECT id, name, abbreviation FROM scope
ORDER BY name
`
func (q *Queries) ListScopes(ctx context.Context) ([]Scope, error) {
rows, err := q.query(ctx, q.listScopesStmt, listScopes)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Scope{}
for rows.Next() {
var i Scope
if err := rows.Scan(&i.ID, &i.Name, &i.Abbreviation); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listScopesByUser = `-- name: ListScopesByUser :many
SELECT id, s.name, abbreviation, sm.name as display_name FROM scope_member sm
RIGHT JOIN scope s on s.id = sm.scope_id
WHERE sm.user_id = ?
ORDER BY s.name
`
type ListScopesByUserRow struct {
ID int `json:"id"`
Name string `json:"name"`
Abbreviation string `json:"abbreviation"`
DisplayName sql.NullString `json:"display_name"`
}
func (q *Queries) ListScopesByUser(ctx context.Context, userID string) ([]ListScopesByUserRow, error) {
rows, err := q.query(ctx, q.listScopesByUserStmt, listScopesByUser, userID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListScopesByUserRow{}
for rows.Next() {
var i ListScopesByUserRow
if err := rows.Scan(
&i.ID,
&i.Name,
&i.Abbreviation,
&i.DisplayName,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
@@ -0,0 +1,54 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.13.0
// source: stats.sql
package mysqlcore
import (
"context"
"git.aiterp.net/stufflog3/stufflog3-api/internal/sqltypes"
)
const listStats = `-- name: ListStats :many
SELECT id, name, description, weight, allowed_amounts FROM stat
WHERE scope_id = ?
`
type ListStatsRow struct {
ID int `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Weight float64 `json:"weight"`
AllowedAmounts sqltypes.NullRawMessage `json:"allowed_amounts"`
}
func (q *Queries) ListStats(ctx context.Context, scopeID int) ([]ListStatsRow, error) {
rows, err := q.query(ctx, q.listStatsStmt, listStats, scopeID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListStatsRow{}
for rows.Next() {
var i ListStatsRow
if err := rows.Scan(
&i.ID,
&i.Name,
&i.Description,
&i.Weight,
&i.AllowedAmounts,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
@@ -0,0 +1,5 @@
-- name: ListProjectEntries :many
SELECT id, name, status FROM project
WHERE scope_id = ?
ORDER BY status, created_time;
+28
View File
@@ -0,0 +1,28 @@
-- name: GetScope :one
SELECT id, name, abbreviation FROM scope
WHERE id = ?;
-- name: ListScopes :many
SELECT id, name, abbreviation FROM scope
ORDER BY name;
-- name: ListScopesByUser :many
SELECT id, s.name, abbreviation, sm.name as display_name FROM scope_member sm
RIGHT JOIN scope s on s.id = sm.scope_id
WHERE sm.user_id = ?
ORDER BY s.name;
-- name: ListScopeMembers :many
SELECT user_id, name, owner FROM scope_member
WHERE scope_id = ?
ORDER BY name;
-- name: GetScopeWithDisplayName :one
SELECT id, scope.name, abbreviation, sm.name AS display_name FROM scope
LEFT JOIN scope_member sm ON scope.id = sm.scope_id
WHERE id = ? AND sm.user_id = ?;
-- name: GetScopeDisplayName :one
SELECT name FROM scope_member
WHERE user_id = ? AND scope_id = ?;
@@ -0,0 +1,3 @@
-- name: ListStats :many
SELECT id, name, description, weight, allowed_amounts FROM stat
WHERE scope_id = ?;
+175
View File
@@ -0,0 +1,175 @@
package mysql
import (
"context"
"database/sql"
"encoding/json"
"git.aiterp.net/stufflog3/stufflog3-api/internal/database/mysql/mysqlcore"
"git.aiterp.net/stufflog3/stufflog3-api/internal/models"
"git.aiterp.net/stufflog3/stufflog3-api/internal/slerrors"
"golang.org/x/sync/errgroup"
"log"
"time"
)
type scopeRepository struct {
db *sql.DB
}
func (r *scopeRepository) Find(ctx context.Context, id int) (*models.Scope, error) {
q := mysqlcore.New(r.db)
scope, err := q.GetScope(ctx, id)
if err != nil {
if err == sql.ErrNoRows {
return nil, slerrors.NotFound("Scope")
}
return nil, err
}
res := &models.Scope{
ScopeEntry: models.ScopeEntry{
ID: scope.ID,
Name: scope.Name,
Abbreviation: scope.Abbreviation,
},
}
eg, ctx := errgroup.WithContext(ctx)
eg.Go(func() error {
members, err := q.ListScopeMembers(ctx, id)
if err != nil && err != sql.ErrNoRows {
return err
}
for _, member := range members {
res.Members = append(res.Members, models.ScopeMember{
ID: member.UserID,
Name: member.Name,
Owner: member.Owner,
})
}
return nil
})
eg.Go(func() error {
projects, err := q.ListProjectEntries(ctx, id)
if err != nil && err != sql.ErrNoRows {
return err
}
for _, project := range projects {
res.Projects = append(res.Projects, models.ProjectEntry{
ID: project.ID,
Name: project.Name,
Status: models.Status(project.Status),
})
}
return nil
})
eg.Go(func() error {
stats, err := q.ListStats(ctx, id)
if err != nil && err != sql.ErrNoRows {
return err
}
for _, stat := range stats {
var amounts map[string]int
if stat.AllowedAmounts.Valid {
amounts = make(map[string]int)
err := json.Unmarshal(stat.AllowedAmounts.RawMessage, &amounts)
if err != nil {
return err
}
}
res.Stats = append(res.Stats, models.Stat{
StatEntry: models.StatEntry{
ID: stat.ID,
Name: stat.Name,
Weight: stat.Weight,
},
Description: stat.Description,
AllowedAmounts: amounts,
})
}
return nil
})
t := time.Now()
err = eg.Wait()
if err != nil {
return nil, err
}
log.Println(time.Since(t))
return res, nil
}
func (r *scopeRepository) List(ctx context.Context) ([]models.ScopeEntry, error) {
q := mysqlcore.New(r.db)
scopes, err := q.ListScopes(ctx)
if err != nil {
return nil, err
}
res := make([]models.ScopeEntry, 0, len(scopes))
for _, scope := range scopes {
res = append(res, models.ScopeEntry{
ID: scope.ID,
Name: scope.Name,
Abbreviation: scope.Abbreviation,
})
}
return res, nil
}
func (r *scopeRepository) ListByUser(ctx context.Context, userID string) ([]models.ScopeEntry, error) {
q := mysqlcore.New(r.db)
scopes, err := q.ListScopesByUser(ctx, userID)
if err != nil {
return nil, err
}
res := make([]models.ScopeEntry, 0, len(scopes))
for _, scope := range scopes {
res = append(res, models.ScopeEntry{
ID: scope.ID,
Name: scope.Name,
Abbreviation: scope.Abbreviation,
})
}
return res, nil
}
func (r *scopeRepository) Create(ctx context.Context, scope models.ScopeEntry, owner models.ScopeMember) (*models.Scope, error) {
//TODO implement me
panic("implement me")
}
func (r *scopeRepository) Update(ctx context.Context, scope models.ScopeEntry) error {
//TODO implement me
panic("implement me")
}
func (r *scopeRepository) Delete(ctx context.Context, scope models.Scope) error {
//TODO implement me
panic("implement me")
}
func (r *scopeRepository) UpdateMember(ctx context.Context, scope models.Scope, member models.ScopeMember) (*models.Scope, error) {
//TODO implement me
panic("implement me")
}
func (r *scopeRepository) DeleteMember(ctx context.Context, scope models.Scope, userID string) (*models.Scope, error) {
//TODO implement me
panic("implement me")
}