├── .dockerignore ├── .github └── workflows │ └── docker.yaml ├── .gitignore ├── Dockerfile ├── Makefile ├── README.md ├── assets ├── app.js ├── index.html └── style.css ├── go.mod ├── go.sum ├── k8s └── knative.yaml ├── main.go └── prisma ├── db └── .gitignore └── schema.prisma /.dockerignore: -------------------------------------------------------------------------------- 1 | .env 2 | go-prisma-example 3 | go-prisma-example.exe 4 | test.sh 5 | prisma/db 6 | k8s 7 | -------------------------------------------------------------------------------- /.github/workflows/docker.yaml: -------------------------------------------------------------------------------- 1 | name: Publish Docker image 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | push_to_registry: 10 | name: Docker 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Check out the repo 14 | uses: actions/checkout@v3 15 | 16 | - name: Set up QEMU 17 | uses: docker/setup-qemu-action@v1 18 | 19 | - name: Set up Docker Buildx 20 | id: buildx 21 | uses: docker/setup-buildx-action@v1 22 | 23 | - name: Log in to GitHub Container Registry 24 | uses: docker/login-action@v2 25 | with: 26 | registry: ghcr.io 27 | username: ${{ github.actor }} 28 | password: ${{ secrets.GITHUB_TOKEN }} 29 | 30 | - name: Extract metadata (tags, labels) for Docker 31 | id: meta 32 | uses: docker/metadata-action@v3 33 | with: 34 | images: ghcr.io/${{ github.repository }} 35 | 36 | - name: Build and push Docker image 37 | uses: docker/build-push-action@v2 38 | with: 39 | context: . 40 | platforms: linux/amd64 41 | builder: ${{ steps.buildx.outputs.name }} 42 | push: ${{ github.event_name != 'pull_request' }} 43 | tags: | 44 | ghcr.io/${{ github.repository }}:latest 45 | ${{ steps.meta.outputs.tags }} 46 | labels: ${{ steps.meta.outputs.labels }} 47 | cache-from: type=gha 48 | cache-to: type=gha,mode=max # mode=maxを有効にすると、中間ステージまで含めてキャッシュできる 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | go-prisma-example 3 | go-prisma-example.exe 4 | test.sh 5 | k8s/secret.yaml 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1.4 2 | 3 | FROM golang:1.22-alpine AS build-dev 4 | WORKDIR /go/src/app 5 | COPY go.mod go.sum ./ 6 | RUN go mod download 7 | RUN apk add --no-cache upx || \ 8 | RUN go run github.com/steebchen/prisma-client-go prefetch 9 | COPY ./ ./ 10 | RUN go run github.com/steebchen/prisma-client-go generate 11 | RUN CGO_ENABLED=0 go install -buildvcs=false -trimpath -ldflags '-w -s -extldflags "-static"' 12 | #RUN CGO_ENABLED=0 GOOS=linux go install -buildvcs=false -trimpath -installsuffix cgo -ldflags '-extldflags "-static"' -o app . 13 | 14 | RUN [ -e /usr/bin/upx ] && upx /go/bin/go-prisma-example || echo 15 | FROM scratch 16 | COPY --link --from=build-dev /go/bin/go-prisma-example /go/bin/go-prisma-example 17 | COPY --from=build-dev /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 18 | CMD ["/go/bin/go-prisma-example"] 19 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BIN := go-prisma-example 2 | VERSION := $$(make -s show-version) 3 | CURRENT_REVISION := $(shell git rev-parse --short HEAD) 4 | BUILD_LDFLAGS := "-s -w -X main.revision=$(CURRENT_REVISION)" 5 | GOBIN ?= $(shell go env GOPATH)/bin 6 | export GO111MODULE=on 7 | 8 | .PHONY: all 9 | all: clean build 10 | 11 | .PHONY: build 12 | build: 13 | go build -ldflags=$(BUILD_LDFLAGS) -o $(BIN) . 14 | 15 | .PHONY: install 16 | install: 17 | go install -ldflags=$(BUILD_LDFLAGS) . 18 | 19 | .PHONY: show-version 20 | show-version: $(GOBIN)/gobump 21 | gobump show -r . 22 | 23 | $(GOBIN)/gobump: 24 | go install github.com/x-motemen/gobump/cmd/gobump@latest 25 | 26 | .PHONY: cross 27 | cross: $(GOBIN)/goxz 28 | goxz -n $(BIN) -pv=v$(VERSION) -build-ldflags=$(BUILD_LDFLAGS) . 29 | 30 | $(GOBIN)/goxz: 31 | go install github.com/Songmu/goxz/cmd/goxz@latest 32 | 33 | .PHONY: test 34 | test: build 35 | go test -v ./... 36 | 37 | .PHONY: clean 38 | clean: 39 | rm -rf $(BIN) goxz 40 | go clean 41 | 42 | .PHONY: bump 43 | bump: $(GOBIN)/gobump 44 | ifneq ($(shell git status --porcelain),) 45 | $(error git workspace is dirty) 46 | endif 47 | ifneq ($(shell git rev-parse --abbrev-ref HEAD),main) 48 | $(error current branch is not main) 49 | endif 50 | @gobump up -w . 51 | git commit -am "bump up version to $(VERSION)" 52 | git tag "v$(VERSION)" 53 | git push origin main 54 | git push origin "refs/tags/v$(VERSION)" 55 | 56 | .PHONY: upload 57 | upload: $(GOBIN)/ghr 58 | ghr "v$(VERSION)" goxz 59 | 60 | $(GOBIN)/ghr: 61 | go install github.com/tcnksm/ghr@latest 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-prisma-example 2 | 3 | Example Go app using [prisma](https://www.prisma.io/), [lit-html](https://lit-html.polymer-project.org/), and [ky](https://github.com/sindresorhus/ky). 4 | 5 | ## Usage 6 | 7 | First of all, install PostgreSQL. 8 | Then try following commands. 9 | 10 | ```console 11 | $ go generate 12 | $ go build 13 | $ echo 'DATABASE_URL=postgresql://yourname:yourname@localhost:5432/postgres' > .env 14 | $ npx prisma db push 15 | $ ./go-prisma-example 16 | ``` 17 | 18 | Then you can use [TODO app](http://localhost:8989)! :tada: 19 | 20 | ## Reference 21 | 22 | * [Blog post(ja)](https://zenn.dev/mattn/articles/1c4eb193d81a3a) 23 | 24 | ## License 25 | 26 | MIT 27 | 28 | assets/app.js is based on @ryohey's [lit-html-todo](https://github.com/ryohey/lit-html-todo) 29 | 30 | ## Author 31 | 32 | Yasuhiro Matsumoto (a.k.a. mattn) 33 | -------------------------------------------------------------------------------- /assets/app.js: -------------------------------------------------------------------------------- 1 | import {html, render} from 'https://unpkg.com/lit-html@3.1.4/lit-html.js'; 2 | import {classMap} from 'https://unpkg.com/lit-html@3.1.4/directives/class-map.js'; 3 | import ky from 'https://unpkg.com/ky@1.3.0/distribution/index.js'; 4 | 5 | const TaskItem = ( 6 | task, 7 | onCheck, 8 | onClickRemove, 9 | ) => html` 10 |
11 | onCheck(e.currentTarget.checked)} 14 | .checked=${task.completed} 15 | /> 16 |
${task.text}
17 |
×
18 |
19 | ` 20 | 21 | const TaskList = ( 22 | tasks, 23 | onCheck, 24 | onClickRemove, 25 | ) => html` 26 |
27 | ${tasks.map(t => 28 | TaskItem(t, checked => onCheck(t.id, checked), () => onClickRemove(t.id)) 29 | )} 30 |
31 | ` 32 | 33 | const NewTask = ( 34 | inputText, 35 | onInput, 36 | onKeyPress, 37 | ) => html` 38 |
39 | onInput(e.currentTarget.value)} 43 | @keypress=${onKeyPress} 44 | placeholder="what we have to do?" 45 | /> 46 |
47 | ` 48 | 49 | const App = () => html` 50 |
51 |

ToDos

52 | ${NewTask( 53 | store().inputText, 54 | inputText => store({ inputText }), 55 | e => { 56 | if (e.key === "Enter") { 57 | (async () => { 58 | const state = store(); 59 | const task = await ky.post('/tasks', { 60 | json: { text: state.inputText } 61 | }).json(); 62 | store({ 63 | tasks: [...state.tasks, task], 64 | inputText: "" 65 | }) 66 | })(); 67 | } 68 | } 69 | )} 70 | ${TaskList( 71 | store().tasks, 72 | (id, completed) => { 73 | (async () => { 74 | await ky.post('/tasks/' + id, { 75 | json: { id: id, completed: completed } 76 | }).json(); 77 | store({ 78 | tasks: store().tasks.map(t => (t.id === id ? { ...t, completed } : t)) 79 | }) 80 | })() 81 | }, 82 | id => { 83 | (async () => { 84 | await ky.delete('/tasks/' + id, { 85 | json: { id: id } 86 | }).json(); 87 | store({ tasks: store().tasks.filter(t => t.id !== id) }) 88 | })() 89 | } 90 | )} 91 |
92 | ` 93 | 94 | const renderApp = () => render(App(), document.body) 95 | 96 | const createStore = (initialState) => { 97 | let data = initialState 98 | 99 | return (update) => { 100 | if (update) { 101 | data = { ...data, ...update } 102 | renderApp() 103 | } 104 | return data 105 | } 106 | } 107 | 108 | const store = createStore({ 109 | tasks: await ky.get('/tasks').json(), 110 | selectedTasks: [], 111 | inputText: "", 112 | }) 113 | 114 | renderApp() 115 | -------------------------------------------------------------------------------- /assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/style.css: -------------------------------------------------------------------------------- 1 | .task.done .title { 2 | text-decoration: line-through; 3 | opacity: 0.3; 4 | } 5 | 6 | .task { 7 | display: flex; 8 | align-items: center; 9 | padding: 1em 0; 10 | } 11 | 12 | .task .title { 13 | flex-grow: 1; 14 | } 15 | 16 | .task-list { 17 | border-top: 1px solid #2f49770d; 18 | min-height: 13em; 19 | } 20 | 21 | body { 22 | font-family: "system-ui"; 23 | } 24 | 25 | .task input[type="checkbox"] { 26 | margin-right: 1.5em; 27 | } 28 | 29 | .task .remove { 30 | font-size: 1.5em; 31 | font-weight: 100; 32 | cursor: pointer; 33 | opacity: 0.3; 34 | } 35 | 36 | body { 37 | background: white; 38 | color: #4f5375; 39 | width: 28em; 40 | margin: 3em auto; 41 | box-shadow: 0 2em 5em #132c531a; 42 | border-radius: 0.5em; 43 | padding: 1em 1.5em; 44 | } 45 | 46 | html { 47 | background: #f8f8f8; 48 | } 49 | 50 | .task { 51 | animation: 0.6s cubic-bezier(0.43, 0.51, 0.21, 0.97) 0s taskAppear; 52 | } 53 | @keyframes taskAppear { 54 | 0% { 55 | transform: translateX(-30%); 56 | opacity: 0; 57 | } 58 | 100% { 59 | transform: translateX(0); 60 | opacity: 1; 61 | } 62 | } 63 | 64 | .new-task { 65 | display: flex; 66 | } 67 | 68 | .new-task input[type="text"] { 69 | flex-grow: 1; 70 | margin: 2em 0; 71 | font-size: inherit; 72 | font-family: inherit; 73 | padding: 0.5em 0.7em; 74 | outline-width: 1px; 75 | border: 1px solid #0000001a; 76 | } 77 | 78 | .new-task input[type="text"]::-webkit-input-placeholder { 79 | color: #5a5d8038; 80 | } 81 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mattn/go-prisma-example 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/joho/godotenv v1.5.1 7 | github.com/labstack/echo/v4 v4.12.0 8 | github.com/shopspring/decimal v1.4.0 9 | github.com/steebchen/prisma-client-go v0.36.0 10 | ) 11 | 12 | require ( 13 | github.com/labstack/gommon v0.4.2 // indirect 14 | github.com/mattn/go-colorable v0.1.13 // indirect 15 | github.com/mattn/go-isatty v0.0.20 // indirect 16 | github.com/valyala/bytebufferpool v1.0.0 // indirect 17 | github.com/valyala/fasttemplate v1.2.2 // indirect 18 | golang.org/x/crypto v0.22.0 // indirect 19 | golang.org/x/net v0.24.0 // indirect 20 | golang.org/x/sys v0.19.0 // indirect 21 | golang.org/x/text v0.14.0 // indirect 22 | ) 23 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 4 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 5 | github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0= 6 | github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM= 7 | github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= 8 | github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= 9 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 10 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 11 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 12 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 13 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 14 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 15 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 16 | github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= 17 | github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= 18 | github.com/steebchen/prisma-client-go v0.36.0 h1:jy0S4Eqme4oHQS/ZCqXI2vYPEwW4eJRV/3bSA0BN5C0= 19 | github.com/steebchen/prisma-client-go v0.36.0/go.mod h1:ImB+P8NBQ3zlY6MgGC/+jrtntXbj1UXwRfWrHQwNKEo= 20 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 21 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 22 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 23 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 24 | github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= 25 | github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= 26 | golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= 27 | golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= 28 | golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= 29 | golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= 30 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 31 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 32 | golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= 33 | golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 34 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 35 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 36 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 37 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 38 | -------------------------------------------------------------------------------- /k8s/knative.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: serving.knative.dev/v1 2 | kind: Service 3 | metadata: 4 | name: todoapp 5 | spec: 6 | template: 7 | metadata: 8 | labels: 9 | app: todoapp 10 | spec: 11 | containers: 12 | - name: todoapp 13 | image: ghcr.io/mattn/go-prisma-example 14 | imagePullPolicy: Always 15 | env: 16 | - name: DATABASE_URL 17 | valueFrom: 18 | secretKeyRef: 19 | name: todoapp 20 | key: database-url 21 | ports: 22 | - containerPort: 8989 23 | protocol: TCP 24 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //go:generate npx prisma generate 4 | 5 | import ( 6 | "context" 7 | "embed" 8 | "io/fs" 9 | "log" 10 | "mime" 11 | "net/http" 12 | "strconv" 13 | 14 | "github.com/labstack/echo/v4" 15 | "github.com/mattn/go-prisma-example/prisma/db" 16 | ) 17 | 18 | const name = "go-prisma-example" 19 | 20 | const version = "0.0.4" 21 | 22 | var revision = "HEAD" 23 | 24 | //go:embed assets 25 | var assets embed.FS 26 | 27 | func main() { 28 | client := db.NewClient() 29 | if err := client.Prisma.Connect(); err != nil { 30 | log.Fatal(err) 31 | } 32 | 33 | defer func() { 34 | if err := client.Prisma.Disconnect(); err != nil { 35 | log.Fatal(err) 36 | } 37 | }() 38 | 39 | mime.AddExtensionType(".js", "application/javascript") 40 | 41 | e := echo.New() 42 | 43 | e.POST("/tasks", func(c echo.Context) error { 44 | var task db.TaskModel 45 | if err := c.Bind(&task); err != nil { 46 | c.Logger().Error("Bind: ", err) 47 | return c.String(http.StatusBadRequest, "Bind: "+err.Error()) 48 | } 49 | var text *string 50 | if newText, ok := task.Text(); ok { 51 | text = &newText 52 | } 53 | var completed *bool 54 | if newCompleted, ok := task.Completed(); ok { 55 | completed = &newCompleted 56 | } 57 | newTask, err := client.Task.CreateOne( 58 | db.Task.Text.SetIfPresent(text), 59 | db.Task.Completed.SetIfPresent(completed), 60 | ).Exec(context.Background()) 61 | if err != nil { 62 | return c.String(http.StatusBadRequest, err.Error()) 63 | } 64 | return c.JSON(http.StatusOK, newTask) 65 | }) 66 | 67 | e.GET("/tasks", func(c echo.Context) error { 68 | tasks, err := client.Task.FindMany().OrderBy( 69 | db.Task.ID.Order(db.ASC), 70 | ).Exec(context.Background()) 71 | if err != nil { 72 | return c.String(http.StatusBadRequest, err.Error()) 73 | } 74 | return c.JSON(http.StatusOK, tasks) 75 | }) 76 | 77 | e.POST("/tasks/:id", func(c echo.Context) error { 78 | var task db.TaskModel 79 | if err := c.Bind(&task); err != nil { 80 | c.Logger().Error("Bind: ", err) 81 | return c.String(http.StatusBadRequest, "Bind: "+err.Error()) 82 | } 83 | var text *string 84 | if newText, ok := task.Text(); ok { 85 | text = &newText 86 | } 87 | var completed *bool 88 | if newCompleted, ok := task.Completed(); ok { 89 | completed = &newCompleted 90 | } 91 | newTask, err := client.Task.FindUnique( 92 | db.Task.ID.Equals(task.ID), 93 | ).Update( 94 | db.Task.Text.SetIfPresent(text), 95 | db.Task.Completed.SetIfPresent(completed), 96 | ).Exec(context.Background()) 97 | if err != nil { 98 | return c.String(http.StatusBadRequest, err.Error()) 99 | } 100 | return c.JSON(http.StatusOK, newTask) 101 | }) 102 | 103 | e.DELETE("/tasks/:id", func(c echo.Context) error { 104 | id, err := strconv.Atoi(c.Param("id")) 105 | if err != nil { 106 | return c.String(http.StatusBadRequest, err.Error()) 107 | } 108 | task, err := client.Task.FindUnique( 109 | db.Task.ID.Equals(id), 110 | ).Delete().Exec(context.Background()) 111 | if err != nil { 112 | return c.String(http.StatusNotFound, err.Error()) 113 | } 114 | return c.JSON(http.StatusOK, task) 115 | }) 116 | e.GET("/tasks/:id", func(c echo.Context) error { 117 | id, err := strconv.Atoi(c.Param("id")) 118 | if err != nil { 119 | return c.String(http.StatusBadRequest, err.Error()) 120 | } 121 | task, err := client.Task.FindUnique( 122 | db.Task.ID.Equals(id), 123 | ).Exec(context.Background()) 124 | if err != nil { 125 | return c.String(http.StatusNotFound, err.Error()) 126 | } 127 | return c.JSON(http.StatusOK, task) 128 | }) 129 | 130 | sub, _ := fs.Sub(assets, "assets") 131 | e.GET("/*", echo.WrapHandler(http.FileServer(http.FS(sub)))) 132 | e.Logger.Fatal(e.Start(":8989")) 133 | } 134 | -------------------------------------------------------------------------------- /prisma/db/.gitignore: -------------------------------------------------------------------------------- 1 | # gitignore generated by Prisma Client Go. DO NOT EDIT. 2 | *_gen.go 3 | -------------------------------------------------------------------------------- /prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | datasource db { 5 | provider = "postgresql" 6 | url = env("DATABASE_URL") 7 | } 8 | 9 | generator client { 10 | provider = "go run github.com/steebchen/prisma-client-go" 11 | } 12 | 13 | model Task { 14 | id Int @id @default(autoincrement()) 15 | text String? 16 | completed Boolean? @default(false) 17 | } 18 | --------------------------------------------------------------------------------