This commit is contained in:
Gregor Lohaus
2026-04-08 04:29:35 +02:00
commit 8254a28baa
480 changed files with 13386 additions and 0 deletions

View File

@@ -0,0 +1,58 @@
#:schema https://json.schemastore.org/any.json
env_files = []
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"
[build]
args_bin = []
bin = "./tmp/main"
cmd = "go build -o ./tmp/main ."
delay = 1000
entrypoint = ["./tmp/main","serve"]
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
ignore_dangerous_root_dir = false
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html"]
include_file = []
kill_delay = "0s"
log = "build-errors.log"
poll = false
poll_interval = 0
post_cmd = []
pre_cmd = []
rerun = false
rerun_delay = 500
send_interrupt = false
stop_on_error = false
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
main_only = false
silent = false
time = false
[misc]
clean_on_exit = false
[proxy]
app_port = 0
app_start_timeout = 0
enabled = false
proxy_port = 0
[screen]
clear_on_rebuild = false
keep_scroll = true

1
template/services/api/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
tmp

View File

View File

@@ -0,0 +1,45 @@
/*
Copyright © 2026 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"fmt"
"os"
"<@var(context.project.goprefix)>/<@var(context.project.name)>/config"
"github.com/spf13/cobra"
)
// configInitCmd represents the configInit command
var configInitCmd = &cobra.Command{
Use: "configInit",
Short: "A brief description of your command",
Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
Run: func(cmd *cobra.Command, args []string) {
err := config.Write(*config.DefaultConf)
if err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
},
}
func init() {
rootCmd.AddCommand(configInitCmd)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// configInitCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// configInitCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

View File

@@ -0,0 +1,37 @@
/*
Copyright © 2026 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"<@var(context.project.goprefix)>/<@var(context.project.name)>/db"
"github.com/spf13/cobra"
)
// migrateCmd represents the migrate command
var migrateCmd = &cobra.Command{
Use: "migrate",
Short: "run database migrations",
Long: ``,
RunE: func(cmd *cobra.Command, args []string) error {
err := db.RunDbMigrations()
if err != nil {
return err
}
return nil
},
}
func init() {
rootCmd.AddCommand(migrateCmd)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// migrateCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// migrateCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

View File

@@ -0,0 +1,45 @@
/*
Copyright © 2026 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"github.com/spf13/cobra"
"os"
)
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "<@var(context.project.name)>",
Short: "A brief description of your application",
Long: `A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
// Uncomment the following line if your bare application
// has an action associated with it:
// Run: func(cmd *cobra.Command, args []string) { },
}
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
}
}
func init() {
// Here you will define your flags and configuration settings.
// Cobra supports persistent flags, which, if defined here,
// will be global for your application.
// rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.<@var(context.project.name)>.yaml)")
// Cobra also supports local flags, which will only run
// when this action is called directly.
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

View File

@@ -0,0 +1,81 @@
/*
Copyright © 2026 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"<@var(context.project.goprefix)>/<@var(context.project.name)>/config"
"<@var(context.project.goprefix)>/<@var(context.project.name)>/db"
"<@var(context.project.goprefix)>/<@var(context.project.name)>/server/todo"
connectors "connectrpc.com/cors"
"context"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/rs/cors"
"github.com/spf13/cobra"
"net/http"
"strconv"
"strings"
)
// serveCmd represents the serve command
var serveCmd = &cobra.Command{
Use: "serve",
Short: "start server",
Long: ``,
RunE: func(cmd *cobra.Command, args []string) error {
err := config.Load()
if err != nil {
err := config.Write(*config.DefaultConf)
if err != nil {
return err
}
}
conf := *config.Conf
ctx := context.Background()
db_url := "postgres://" + conf.Database.User + ":" + conf.Database.Password + "@" + conf.Database.Host + ":" + strconv.Itoa(conf.Database.Port) + "/" + conf.Database.Name
db_url = db_url + "?sslmode=disable"
conn, err := pgxpool.New(ctx, db_url)
if err != nil {
panic(err.Error())
}
err = db.RunDbMigrations()
if err != nil {
return err
}
db.Q = db.New(conn)
middleware := cors.New(cors.Options{
AllowedOrigins: conf.Server.FrontendUrls,
AllowedMethods: connectors.AllowedMethods(),
AllowedHeaders: connectors.AllowedHeaders(),
ExposedHeaders: connectors.ExposedHeaders(),
Debug: true,
})
mux := http.NewServeMux()
todoPath, todoHandler := todo.GetPathHandler()
mux.Handle(todoPath, todoHandler)
p := new(http.Protocols)
p.SetHTTP1(true)
p.SetUnencryptedHTTP2(true)
s := http.Server{
Addr: strings.Join([]string{conf.Server.Host, strconv.Itoa(conf.Server.Port)}, ":"),
Handler: middleware.Handler(mux),
Protocols: p,
}
s.ListenAndServe()
return nil
},
}
func init() {
rootCmd.AddCommand(serveCmd)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// serveCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// serveCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

View File

@@ -0,0 +1,81 @@
package config
import (
"github.com/adrg/xdg"
"github.com/pelletier/go-toml"
"os"
"path/filepath"
)
var confPath string
var Conf *Config
var DefaultConf *Config
func init() {
confPath = filepath.Join(xdg.ConfigHome, "<@var(context.project.name)>", "config.toml")
Conf = &Config{}
DefaultConf = &Config{
Server: Server{
Host: "127.0.0.1",
Port: 8080,
FrontendUrls: []string{"http://localhost:5173"},
},
Database: Database{
User: "pp",
Password: "<@var(context.project.name)>",
Host: "127.0.0.1",
Port: 5432,
Name: "<@var(context.project.name)>",
},
}
}
func Load() error {
confContent, err := os.ReadFile(confPath)
if err != nil {
return err
}
return toml.Unmarshal(confContent, Conf)
}
func Write(config Config) error {
confContent, err := toml.Marshal(config)
if err != nil {
return err
}
dir := filepath.Dir(confPath)
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
if err := os.WriteFile(confPath, confContent, 0644); err != nil {
return err
}
if err := Load(); err != nil {
return err
}
return nil
}
type Server struct {
Host string
Port int
FrontendUrls []string
}
type Database struct {
User string
Password string
Host string
Port int
Name string
}
type Config struct {
Server Server
Database Database
}

View File

@@ -0,0 +1,32 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
package db
import (
"context"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
)
type DBTX interface {
Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error)
Query(context.Context, string, ...interface{}) (pgx.Rows, error)
QueryRow(context.Context, string, ...interface{}) pgx.Row
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
type Queries struct {
db DBTX
}
func (q *Queries) WithTx(tx pgx.Tx) *Queries {
return &Queries{
db: tx,
}
}

View File

@@ -0,0 +1,6 @@
package db
import "embed"
//go:embed migrations/*.sql
var MigrationsFs embed.FS

View File

@@ -0,0 +1,60 @@
package db
import (
"<@var(context.project.goprefix)>/<@var(context.project.name)>/config"
"github.com/amacneil/dbmate/v2/pkg/dbmate"
_ "github.com/amacneil/dbmate/v2/pkg/driver/postgres"
"log"
"net/url"
"strconv"
"strings"
)
var Q *Queries
func RunDbMigrations() error {
err := config.Load()
if err != nil {
return err
}
conf := *config.Conf
dbUrl, err := url.Parse(strings.Join([]string{
"postgresql://",
conf.Database.User,
":",
conf.Database.Password,
"@",
conf.Database.Host,
":",
strconv.Itoa(conf.Database.Port),
"/",
conf.Database.Name,
}, ""))
if err != nil {
return err
}
db := dbmate.New(dbUrl)
db.FS = MigrationsFs
db.MigrationsDir = []string{"migrations"}
migrations, err := db.FindMigrations()
if err != nil {
return err
}
for _, m := range migrations {
log.Default().Println(m.Version, m.FilePath)
}
db.AutoDumpSchema = false
log.Default().Println("\nApplying...")
err = db.CreateAndMigrate()
if err != nil {
log.Default().Println(err.Error())
}
err = db.DumpSchema()
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,10 @@
-- migrate:up
CREATE TABLE todo (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
task VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
done bool DEFAULT false
);
-- migrate:down
DROP TABLE todo;

View File

@@ -0,0 +1,17 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
package db
import (
"github.com/jackc/pgx/v5/pgtype"
)
type Todo struct {
ID pgtype.UUID
Task string
CreatedAt pgtype.Timestamp
UpdatedAt pgtype.Timestamp
Done pgtype.Bool
}

View File

@@ -0,0 +1,14 @@
-- name: CreateTodo :one
insert into todo (id,task) values ($1,$2) returning *;
-- name: ListTodos :many
select * from todo;
-- name: GetTodo :one
select * from todo where id = $1 limit 1;
-- name: UpdateTodo :one
update todo set task = $1, done = $2 where id = $3 returning *;
-- name: DeleteTodo :exec
delete from todo where id = $1;

View File

@@ -0,0 +1,86 @@
\restrict dbmate
-- Dumped from database version 17.9
-- Dumped by pg_dump version 17.9
SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;
SET transaction_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SELECT pg_catalog.set_config('search_path', '', false);
SET check_function_bodies = false;
SET xmloption = content;
SET client_min_messages = warning;
SET row_security = off;
--
-- Name: pgcrypto; Type: EXTENSION; Schema: -; Owner: -
--
CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA public;
--
-- Name: EXTENSION pgcrypto; Type: COMMENT; Schema: -; Owner: -
--
COMMENT ON EXTENSION pgcrypto IS 'cryptographic functions';
SET default_tablespace = '';
SET default_table_access_method = heap;
--
-- Name: schema_migrations; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public.schema_migrations (
version character varying NOT NULL
);
--
-- Name: todo; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public.todo (
id uuid DEFAULT gen_random_uuid() NOT NULL,
task character varying(255) NOT NULL,
created_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP,
updated_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP,
done boolean DEFAULT false
);
--
-- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.schema_migrations
ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version);
--
-- Name: todo todo_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.todo
ADD CONSTRAINT todo_pkey PRIMARY KEY (id);
--
-- PostgreSQL database dump complete
--
\unrestrict dbmate
--
-- Dbmate schema migrations
--
INSERT INTO public.schema_migrations (version) VALUES
('20260404121052');

View File

@@ -0,0 +1,113 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: todo.sql
package db
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const createTodo = `-- name: CreateTodo :one
insert into todo (id,task) values ($1,$2) returning id, task, created_at, updated_at, done
`
type CreateTodoParams struct {
ID pgtype.UUID
Task string
}
func (q *Queries) CreateTodo(ctx context.Context, arg CreateTodoParams) (Todo, error) {
row := q.db.QueryRow(ctx, createTodo, arg.ID, arg.Task)
var i Todo
err := row.Scan(
&i.ID,
&i.Task,
&i.CreatedAt,
&i.UpdatedAt,
&i.Done,
)
return i, err
}
const deleteTodo = `-- name: DeleteTodo :exec
delete from todo where id = $1
`
func (q *Queries) DeleteTodo(ctx context.Context, id pgtype.UUID) error {
_, err := q.db.Exec(ctx, deleteTodo, id)
return err
}
const getTodo = `-- name: GetTodo :one
select id, task, created_at, updated_at, done from todo where id = $1 limit 1
`
func (q *Queries) GetTodo(ctx context.Context, id pgtype.UUID) (Todo, error) {
row := q.db.QueryRow(ctx, getTodo, id)
var i Todo
err := row.Scan(
&i.ID,
&i.Task,
&i.CreatedAt,
&i.UpdatedAt,
&i.Done,
)
return i, err
}
const listTodos = `-- name: ListTodos :many
select id, task, created_at, updated_at, done from todo
`
func (q *Queries) ListTodos(ctx context.Context) ([]Todo, error) {
rows, err := q.db.Query(ctx, listTodos)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Todo
for rows.Next() {
var i Todo
if err := rows.Scan(
&i.ID,
&i.Task,
&i.CreatedAt,
&i.UpdatedAt,
&i.Done,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const updateTodo = `-- name: UpdateTodo :one
update todo set task = $1, done = $2 where id = $3 returning id, task, created_at, updated_at, done
`
type UpdateTodoParams struct {
Task string
Done pgtype.Bool
ID pgtype.UUID
}
func (q *Queries) UpdateTodo(ctx context.Context, arg UpdateTodoParams) (Todo, error) {
row := q.db.QueryRow(ctx, updateTodo, arg.Task, arg.Done, arg.ID)
var i Todo
err := row.Scan(
&i.ID,
&i.Task,
&i.CreatedAt,
&i.UpdatedAt,
&i.Done,
)
return i, err
}

View File

@@ -0,0 +1,34 @@
module <@var(context.project.goprefix)>/<@var(context.project.name)>
go 1.25.7
require (
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.9-20250912141014-52f32327d4b0.1 // indirect
buf.build/go/protovalidate v1.0.0 // indirect
cel.dev/expr v0.24.0 // indirect
connectrpc.com/connect v1.19.1 // indirect
connectrpc.com/cors v0.1.0 // indirect
connectrpc.com/validate v0.6.0 // indirect
github.com/adrg/xdg v0.5.3 // indirect
github.com/amacneil/dbmate/v2 v2.32.0 // indirect
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
github.com/google/cel-go v0.26.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.9.1 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/lib/pq v1.12.0 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/rs/cors v1.11.1 // indirect
github.com/spf13/cobra v1.10.2 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/stoewer/go-strcase v1.3.1 // indirect
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.35.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 // indirect
google.golang.org/protobuf v1.36.11 // indirect
)

View File

@@ -0,0 +1,79 @@
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.9-20250912141014-52f32327d4b0.1 h1:DQLS/rRxLHuugVzjJU5AvOwD57pdFl9he/0O7e5P294=
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.9-20250912141014-52f32327d4b0.1/go.mod h1:aY3zbkNan5F+cGm9lITDP6oxJIwu0dn9KjJuJjWaHkg=
buf.build/go/protovalidate v1.0.0 h1:IAG1etULddAy93fiBsFVhpj7es5zL53AfB/79CVGtyY=
buf.build/go/protovalidate v1.0.0/go.mod h1:KQmEUrcQuC99hAw+juzOEAmILScQiKBP1Oc36vvCLW8=
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
connectrpc.com/connect v1.19.1 h1:R5M57z05+90EfEvCY1b7hBxDVOUl45PrtXtAV2fOC14=
connectrpc.com/connect v1.19.1/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w=
connectrpc.com/cors v0.1.0 h1:f3gTXJyDZPrDIZCQ567jxfD9PAIpopHiRDnJRt3QuOQ=
connectrpc.com/cors v0.1.0/go.mod h1:v8SJZCPfHtGH1zsm+Ttajpozd4cYIUryl4dFB6QEpfg=
connectrpc.com/validate v0.6.0 h1:DcrgDKt2ZScrUs/d/mh9itD2yeEa0UbBBa+i0mwzx+4=
connectrpc.com/validate v0.6.0/go.mod h1:ihrpI+8gVbLH1fvVWJL1I3j0CfWnF8P/90LsmluRiZs=
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
github.com/amacneil/dbmate/v2 v2.32.0 h1:DJWt+NRWlphTv1HXWWcAU7gsJKm/Ov8/+8k6yqLz8vA=
github.com/amacneil/dbmate/v2 v2.32.0/go.mod h1:Kwax92bT+ZgqIXll9M+0QGuHVuu7O7AhsE+5p6PTqDc=
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ=
github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc=
github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/lib/pq v1.12.0 h1:mC1zeiNamwKBecjHarAr26c/+d8V5w/u4J0I/yASbJo=
github.com/lib/pq v1.12.0/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs=
github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA=
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0=
google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 h1:41r6JMbpzBMen0R/4TZeeAmGXSJC7DftGINUodzTkPI=
google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 h1:ndE4FoJqsIceKP2oYSnUZqhTdYufCYYkqwtFzfrhI7w=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -0,0 +1,11 @@
/*
Copyright © 2026 NAME HERE <EMAIL ADDRESS>
*/
package main
import "<@var(context.project.goprefix)>/<@var(context.project.name)>/cmd"
func main() {
cmd.Execute()
}

View File

@@ -0,0 +1,116 @@
package todo
import (
"connectrpc.com/connect"
"connectrpc.com/validate"
"context"
"<@var(context.project.goprefix)>/<@var(context.project.name)>/db"
todov1 "<@var(context.project.goprefix)>/<@var(context.project.name)>/gen/todo/v1"
"<@var(context.project.goprefix)>/<@var(context.project.name)>/gen/todo/v1/todov1connect"
. "<@var(context.project.goprefix)>/<@var(context.project.name)>/utils"
"github.com/jackc/pgx/v5/pgtype"
"net/http"
"time"
)
type TodoServer struct{}
func (srv *TodoServer) CreateTodo(ctx context.Context, req *connect.Request[todov1.CreateTodoRequest]) (*connect.Response[todov1.CreateTodoResponse], error) {
var id pgtype.UUID
err := id.Scan(*req.Msg.Todo.Id)
if err != nil {
return nil, err
}
todo, err := db.Q.CreateTodo(ctx, db.CreateTodoParams{
ID: id,
Task: req.Msg.Todo.Task,
})
if err != nil {
return nil, err
}
return &connect.Response[todov1.CreateTodoResponse]{
Msg: &todov1.CreateTodoResponse{
Todo: &todov1.Todo{
Id: StrPtr(todo.ID.String()),
Task: todo.Task,
CreatedAt: StrPtr(todo.CreatedAt.Time.Format(time.RFC3339)),
UpdatesAt: StrPtr(todo.UpdatedAt.Time.Format(time.RFC3339)),
Done: BoolPtr(todo.Done.Bool),
},
},
}, nil
}
func (srv *TodoServer) ListTodos(ctx context.Context, req *connect.Request[todov1.ListTodosRequest]) (*connect.Response[todov1.ListTodosResponse], error) {
todos, err := db.Q.ListTodos(ctx)
if err != nil {
return nil, err
}
reponseTodos := []*todov1.Todo{}
for _, todo := range todos {
reponseTodos = append(reponseTodos, &todov1.Todo{
Id: StrPtr(todo.ID.String()),
Task: todo.Task,
CreatedAt: StrPtr(todo.CreatedAt.Time.Format(time.RFC3339)),
UpdatesAt: StrPtr(todo.UpdatedAt.Time.Format(time.RFC3339)),
Done: BoolPtr(todo.Done.Bool),
})
}
return &connect.Response[todov1.ListTodosResponse]{
Msg: &todov1.ListTodosResponse{
Todos: reponseTodos,
},
}, nil
}
func (srv *TodoServer) UpdateTodo(ctx context.Context, req *connect.Request[todov1.UpdateTodoRequest]) (*connect.Response[todov1.UpdateTodoResponse], error) {
var id pgtype.UUID
err := id.Scan(*req.Msg.Todo.Id)
if err != nil {
return nil, err
}
todo, err := db.Q.UpdateTodo(ctx, db.UpdateTodoParams{
Task: req.Msg.Todo.Task,
Done: pgtype.Bool{
Bool: *req.Msg.Todo.Done,
Valid: true,
},
ID: id,
})
if err != nil {
return nil, err
}
return &connect.Response[todov1.UpdateTodoResponse]{
Msg: &todov1.UpdateTodoResponse{
Todo: &todov1.Todo{
Id: StrPtr(todo.ID.String()),
Task: todo.Task,
CreatedAt: StrPtr(todo.CreatedAt.Time.Format(time.RFC3339)),
UpdatesAt: StrPtr(todo.UpdatedAt.Time.Format(time.RFC3339)),
Done: BoolPtr(todo.Done.Bool),
},
},
}, nil
}
func (srv *TodoServer) DeleteTodo(ctx context.Context, req *connect.Request[todov1.DeleteTodoRequest]) (*connect.Response[todov1.DeleteTodoResponse], error) {
var id pgtype.UUID
err := id.Scan(*req.Msg.Todo.Id)
if err != nil {
return nil, err
}
err = db.Q.DeleteTodo(ctx, id)
if err != nil {
return nil, err
}
return &connect.Response[todov1.DeleteTodoResponse]{
Msg: &todov1.DeleteTodoResponse{},
}, nil
}
func GetPathHandler() (path string, handler http.Handler) {
path, handler = todov1connect.NewTodoServiceHandler(&TodoServer{}, connect.WithInterceptors(validate.NewInterceptor()))
return
}

View File

@@ -0,0 +1,10 @@
version: "2"
sql:
- engine: "postgresql"
queries: "db/query"
schema: "db/migrations"
gen:
go:
package: "db"
out: "db"
sql_package: "pgx/v5"

View File

@@ -0,0 +1,9 @@
package utils
func StrPtr(v string) *string {
return &v
}
func BoolPtr(v bool) *bool {
return &v
}