Compare commits
153 commits
dfefe65ec0
...
c72257132c
| Author | SHA1 | Date | |
|---|---|---|---|
| c72257132c | |||
| 4d5d45d1a4 | |||
|
|
05cf6c8318 | ||
|
|
9aa6784751 | ||
|
|
2a853c05d6 | ||
|
|
a8a9f5ee3a | ||
|
|
be5c1f9197 | ||
|
|
3ffdea90c3 | ||
|
|
9e6d9fd063 | ||
|
|
67a97832a5 | ||
|
|
7505598324 | ||
|
|
96ad22cda3 | ||
|
|
eba3d5e105 | ||
|
|
939e1ceed5 | ||
|
|
c1e9baabe6 | ||
|
|
fc5db0e95a | ||
|
|
9e15bceace | ||
|
|
3cfcb43031 | ||
|
|
52f87580a0 | ||
|
|
a53bdf9a1b | ||
|
|
77cabe5f12 | ||
|
|
1a6874fd82 | ||
|
|
60009df0bf | ||
|
|
4d7b780087 | ||
|
|
c8db468292 | ||
|
|
02236266f1 | ||
|
|
83127d590a | ||
|
|
051d032038 | ||
|
|
0bf9ab7189 | ||
|
|
acbc5e4bf6 | ||
|
|
3eab2f5562 | ||
|
|
09b8393c8a | ||
|
|
732e81f5a4 | ||
|
|
4e5c5127e8 | ||
|
|
f8192d3ad6 | ||
|
|
575c46db22 | ||
|
|
ca0743d8a6 | ||
|
|
eee36e3cbe | ||
|
|
42a07787f0 | ||
|
|
e4370e8ba0 | ||
|
|
5a6cec5294 | ||
|
|
bce426dd0a | ||
|
|
12c5662cb9 | ||
|
|
be997e53d8 | ||
|
|
a096abd195 | ||
|
|
5e9e502b42 | ||
|
|
a70003d290 | ||
|
|
8cae6e6beb | ||
|
|
75aefa8a0a | ||
|
|
8eafb6b666 | ||
|
|
baa391fb20 | ||
|
|
c8a3d64918 | ||
|
|
97bef0257e | ||
|
|
5f66b0ee71 | ||
|
|
26234c29a8 | ||
|
|
4540276472 | ||
|
|
ad4818aa8b | ||
|
|
59c10f1874 | ||
|
|
8ca11c90e1 | ||
|
|
da7044654c | ||
|
|
9213eb88c6 | ||
|
|
f3a95b4be7 | ||
|
|
6e6d2e3f09 | ||
|
|
4488b2255a | ||
|
|
f718a6b798 | ||
|
|
472dc0c358 | ||
|
|
740aebd1a9 | ||
|
|
203a856895 | ||
|
|
60c8aefd49 | ||
|
|
a787d5dc7f | ||
|
|
5f877c3d38 | ||
|
|
29fbadbadd | ||
|
|
11f5ddcee4 | ||
|
|
524d1a4915 | ||
|
|
ff82ba532a | ||
|
|
ed327dc066 | ||
|
|
9c0f0087d2 | ||
|
|
a75b07485c | ||
|
|
2618920e85 | ||
|
|
ef5aecc4b0 | ||
|
|
d815040397 | ||
|
|
bc917d4788 | ||
|
|
3aad9e5efb | ||
|
|
aed537d84e | ||
|
|
180b237b05 | ||
|
|
bdbef15593 | ||
|
|
b7de694716 | ||
|
|
bb880ef091 | ||
|
|
50ea32fe70 | ||
|
|
7bb28427d6 | ||
|
|
e676c48458 | ||
|
|
d052aba309 | ||
|
|
c90ffac0e8 | ||
|
|
b03beccfb2 | ||
|
|
7e4d92fc92 | ||
|
|
1f947eae7a | ||
|
|
fd0487abac | ||
|
|
70f96b4735 | ||
|
|
76ccf3b8db | ||
|
|
bf8df2624b | ||
|
|
2a69237f17 | ||
|
|
071ede73dd | ||
|
|
dceb232cb2 | ||
|
|
965fb540c7 | ||
|
|
99a7ec8a9e | ||
|
|
b33ef2e8d7 | ||
|
|
3c6bff3a3d | ||
|
|
521fea2530 | ||
|
|
9dc83a65cf | ||
|
|
b4621afbd9 | ||
|
|
ac0adde9e6 | ||
|
|
ea1ee27b89 | ||
|
|
11e0514d33 | ||
|
|
2c5d7039d3 | ||
|
|
bfe56d7a31 | ||
|
|
c0a7c9e3b1 | ||
|
|
d6748ed1db | ||
|
|
02d306fae3 | ||
|
|
77e9b60cbf | ||
|
|
7c8ecc2ebe | ||
|
|
ecd0120920 | ||
|
|
31a3503021 | ||
|
|
b4b8153d06 | ||
|
|
24ae49b54f | ||
|
|
a6289fe4cc | ||
|
|
271c0e9699 | ||
|
|
1809b154e3 | ||
|
|
0cb52b6e12 | ||
|
|
cb58b89b6c | ||
|
|
90408d68a7 | ||
|
|
27a2389e2c | ||
|
|
5def458946 | ||
|
|
a8bd9f8b2d | ||
|
|
eb1e42bb02 | ||
|
|
f4c98ba523 | ||
|
|
5c64cd6191 | ||
|
|
e90434edd5 | ||
|
|
22cece2d01 | ||
|
|
e8d73421aa | ||
|
|
3f053711ba | ||
|
|
bfbb9669aa | ||
|
|
d412e06dad | ||
|
|
acd38c8205 | ||
|
|
0f7da0864e | ||
|
|
ea46a38f68 | ||
|
|
feb11bbe5b | ||
|
|
3f123cd69e | ||
|
|
95cf930720 | ||
|
|
a986686247 | ||
|
|
f3545473af | ||
|
|
970769c57f | ||
|
|
849bad562e | ||
|
|
119640ad5b |
209 changed files with 10816 additions and 8579 deletions
52
.air.toml
Normal file
52
.air.toml
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
root = "."
|
||||||
|
testdata_dir = "testdata"
|
||||||
|
tmp_dir = "tmp"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
args_bin = []
|
||||||
|
bin = "./tmp/main"
|
||||||
|
cmd = "make build"
|
||||||
|
delay = 1000
|
||||||
|
exclude_dir = ["assets", "tmp", "vendor", "testdata", "uploads", "dbs", "public"]
|
||||||
|
exclude_file = []
|
||||||
|
exclude_regex = ["_test.go"]
|
||||||
|
exclude_unchanged = false
|
||||||
|
follow_symlink = false
|
||||||
|
full_bin = ""
|
||||||
|
include_dir = []
|
||||||
|
include_ext = ["go", "tpl", "tmpl", "html", "css"]
|
||||||
|
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 = true
|
||||||
|
|
||||||
|
[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
|
||||||
|
enabled = false
|
||||||
|
proxy_port = 0
|
||||||
|
|
||||||
|
[screen]
|
||||||
|
clear_on_rebuild = false
|
||||||
|
keep_scroll = true
|
||||||
31
.github/workflows/test.yml
vendored
Normal file
31
.github/workflows/test.yml
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
name: Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: 1.23
|
||||||
|
|
||||||
|
- uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cache/go-build
|
||||||
|
~/go/pkg/mod
|
||||||
|
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-go-
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: make test
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -1 +1,6 @@
|
||||||
.idea
|
.idea
|
||||||
|
dbs
|
||||||
|
uploads
|
||||||
|
tmp
|
||||||
|
tailwindcss
|
||||||
|
daisyui*.env
|
||||||
|
|
|
||||||
19
Dockerfile
Normal file
19
Dockerfile
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Stage 1: Build the Go binary
|
||||||
|
FROM golang:1.26-alpine AS builder
|
||||||
|
RUN apk add --no-cache gcc musl-dev # for go-sqlite3 (CGO)
|
||||||
|
WORKDIR /build
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
COPY . .
|
||||||
|
RUN CGO_ENABLED=1 go build -o /app/server ./cmd/web
|
||||||
|
|
||||||
|
# Stage 2: Minimal runtime image
|
||||||
|
FROM alpine:latest
|
||||||
|
RUN apk --no-cache add ca-certificates tzdata
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=builder /app/server /app/server
|
||||||
|
COPY --from=builder /build/config /app/config
|
||||||
|
COPY --from=builder /build/public /app/public
|
||||||
|
RUN mkdir -p /app/dbs /app/uploads
|
||||||
|
EXPOSE 8000
|
||||||
|
CMD ["/app/server"]
|
||||||
104
Makefile
104
Makefile
|
|
@ -1,52 +1,82 @@
|
||||||
# Connect to the primary database
|
# Get the OS name in lowercase (linux, darwin)
|
||||||
.PHONY: db
|
OS_SYSNAME := $(shell uname -s | tr A-Z a-z)
|
||||||
db:
|
# Get the machine architecture (x86_64, arm64)
|
||||||
psql postgresql://admin:admin@localhost:5432/app
|
OS_MACHINE := $(shell uname -m)
|
||||||
|
|
||||||
# Connect to the test database
|
# If mac OS, use `macos-arm64` or `macos-x64`
|
||||||
.PHONY: db-test
|
ifeq ($(OS_SYSNAME),darwin)
|
||||||
db-test:
|
OS_SYSNAME = macos
|
||||||
psql postgresql://admin:admin@localhost:5432/app_test
|
ifneq ($(OS_MACHINE),arm64)
|
||||||
|
OS_MACHINE = x64
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
# Connect to the cache
|
# If Linux, use `linux-x64`
|
||||||
.PHONY: cache
|
ifeq ($(OS_SYSNAME),linux)
|
||||||
cache:
|
OS_MACHINE = x64
|
||||||
redis-cli
|
endif
|
||||||
|
|
||||||
|
# The appropriate Tailwind package for your OS will attempt to be automatically determined.
|
||||||
|
# If this is not working, hard-code the package you want using these options:
|
||||||
|
# https://github.com/tailwindlabs/tailwindcss/releases/latest
|
||||||
|
TAILWIND_PACKAGE = tailwindcss-$(OS_SYSNAME)-$(OS_MACHINE)
|
||||||
|
|
||||||
|
.PHONY: help
|
||||||
|
help: ## Print make targets
|
||||||
|
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||||
|
|
||||||
|
.PHONY: install
|
||||||
|
install: ent-install air-install tailwind-install ## Install all dependencies
|
||||||
|
|
||||||
|
.PHONY: tailwind-install
|
||||||
|
tailwind-install: ## Install the Tailwind CSS CLI
|
||||||
|
curl -sLo tailwindcss https://github.com/tailwindlabs/tailwindcss/releases/latest/download/$(TAILWIND_PACKAGE)
|
||||||
|
chmod +x tailwindcss
|
||||||
|
curl -sLO https://github.com/saadeghi/daisyui/releases/latest/download/daisyui.js
|
||||||
|
curl -sLO https://github.com/saadeghi/daisyui/releases/latest/download/daisyui-theme.js
|
||||||
|
|
||||||
# Install Ent code-generation module
|
|
||||||
.PHONY: ent-install
|
.PHONY: ent-install
|
||||||
ent-install:
|
ent-install: ## Install Ent code-generation module
|
||||||
go get -d entgo.io/ent/cmd/ent
|
go get entgo.io/ent/cmd/ent
|
||||||
|
|
||||||
|
.PHONY: air-install
|
||||||
|
air-install: ## Install air
|
||||||
|
go install github.com/air-verse/air@latest
|
||||||
|
|
||||||
# Generate Ent code
|
|
||||||
.PHONY: ent-gen
|
.PHONY: ent-gen
|
||||||
ent-gen:
|
ent-gen: ## Generate Ent code
|
||||||
go generate ./ent
|
go generate ./ent
|
||||||
|
|
||||||
# Create a new Ent entity
|
|
||||||
.PHONY: ent-new
|
.PHONY: ent-new
|
||||||
ent-new:
|
ent-new: ## Create a new Ent entity (ie, make ent-new name=MyEntity)
|
||||||
go run entgo.io/ent/cmd/ent init $(name)
|
go run entgo.io/ent/cmd/ent new $(name)
|
||||||
|
|
||||||
# Start the Docker containers
|
.PHONY: admin
|
||||||
.PHONY: up
|
admin: ## Create a new admin user (ie, make admin email=myemail@web.com)
|
||||||
up:
|
go run cmd/admin/main.go --email=$(email)
|
||||||
docker-compose up -d
|
|
||||||
sleep 3
|
|
||||||
|
|
||||||
# Rebuild Docker containers to wipe all data
|
|
||||||
.PHONY: reset
|
|
||||||
reset:
|
|
||||||
docker-compose down
|
|
||||||
make up
|
|
||||||
|
|
||||||
# Run the application
|
|
||||||
.PHONY: run
|
.PHONY: run
|
||||||
run:
|
run: ## Run the application
|
||||||
clear
|
clear
|
||||||
go run main.go
|
go run cmd/web/main.go
|
||||||
|
|
||||||
|
.PHONY: watch
|
||||||
|
watch: ## Run the application and watch for changes with air to automatically rebuild
|
||||||
|
clear
|
||||||
|
air
|
||||||
|
|
||||||
# Run all tests
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test:
|
test: ## Run all tests
|
||||||
go test -p 1 ./...
|
go test ./...
|
||||||
|
|
||||||
|
.PHONY: check-updates
|
||||||
|
check-updates: ## Check for direct dependency updates
|
||||||
|
go list -u -m -f '{{if not .Indirect}}{{.}}{{end}}' all | grep "\["
|
||||||
|
|
||||||
|
.PHONY: css
|
||||||
|
css: ## Build and minify Tailwind CSS
|
||||||
|
./tailwindcss -i tailwind.css -o public/static/main.css -m
|
||||||
|
|
||||||
|
.PHONY: build
|
||||||
|
build: css ## Build CSS and compile the application binary
|
||||||
|
go build -o ./tmp/main ./cmd/web
|
||||||
|
|
|
||||||
923
README.md
923
README.md
|
|
@ -1,922 +1,11 @@
|
||||||
## (NAME) - Rapid, easy full-stack web development starter kit in Go
|
# personal-site
|
||||||
|
|
||||||
## Table of Contents
|
My personal site, built on Pagoda (https://github.com/mikestefanello/pagoda).
|
||||||
* [Introduction](#introduction)
|
|
||||||
* [Overview](#overview)
|
|
||||||
* [Motivation](#motivation)
|
|
||||||
* [Foundation](#foundation)
|
|
||||||
* [Backend](#backend)
|
|
||||||
* [Frontend](#frontend)
|
|
||||||
* [Storage](#storage)
|
|
||||||
* [Screenshots](#screenshots)
|
|
||||||
* [Getting started](#getting-started)
|
|
||||||
* [Dependencies](#dependencies)
|
|
||||||
* [Start the application](#start-the-application)
|
|
||||||
* [Running tests](#running-tests)
|
|
||||||
* [Clients](#clients)
|
|
||||||
* [Service container](#service-container)
|
|
||||||
* [Dependency injection](#dependency-injection)
|
|
||||||
* [Test dependencies](#test-dependencies)
|
|
||||||
* [Configuration](#configuration)
|
|
||||||
* [Environment overrides](#environment-overrides)
|
|
||||||
* [Environments](#environments)
|
|
||||||
* [Database](#database)
|
|
||||||
* [Auto-migrations](#auto-migrations)
|
|
||||||
* [Separate test database](#separate-test-database)
|
|
||||||
* [ORM](#orm)
|
|
||||||
* [Entity types](#entity-types)
|
|
||||||
* [New entity type](#new-entity-type)
|
|
||||||
* [Sessions](#sessions)
|
|
||||||
* [Authentication](#authentication)
|
|
||||||
* [Login / Logout](#login--logout)
|
|
||||||
* [Forgot password](#forgot-password)
|
|
||||||
* [Registration](#registration)
|
|
||||||
* [Authenticated user](#authenticated-user)
|
|
||||||
* [Middleware](#middleware)
|
|
||||||
* [Routes](#routes)
|
|
||||||
* [Custom middleware](#custom-middleware)
|
|
||||||
* [Controller / Dependencies](#controller--dependencies)
|
|
||||||
* [Patterns](#patterns)
|
|
||||||
* [Custom middleware](#custom-middleware)
|
|
||||||
* [Testing](#testing)
|
|
||||||
* [HTTP server](#http-server)
|
|
||||||
* [Request / Request helpers](#request--response-helpers)
|
|
||||||
* [Goquery](#goquery)
|
|
||||||
* [Controller](#controller)
|
|
||||||
* [Page](#page)
|
|
||||||
* [Flash messaging](#flash-messaging)
|
|
||||||
* [Pager](#pager)
|
|
||||||
* [CSRF](#csrf)
|
|
||||||
* [Automatic template parsing](#automatic-template-parsing)
|
|
||||||
* [Cached responses](#cached-responses)
|
|
||||||
* [Cache tags](#cache-tags)
|
|
||||||
* [Cache middleware](#cache-middleware)
|
|
||||||
* [Data](#data)
|
|
||||||
* [Forms](#forms)
|
|
||||||
* [Submission processing](#submission-processing)
|
|
||||||
* [Inline validation](#inline-validation)
|
|
||||||
* [Headers](#headers)
|
|
||||||
* [Status code](#status-code)
|
|
||||||
* [Metatags](#metatags)
|
|
||||||
* [URL and link generation](#url-and-link-generation)
|
|
||||||
* [HTMX support](#htmx-support)
|
|
||||||
* [Rendering the page](#rendering-the-page)
|
|
||||||
* [Template renderer](#template-renderer)
|
|
||||||
* [Caching](#caching)
|
|
||||||
* [Hot-reload for development](#hot-reload-for-development)
|
|
||||||
* [File configuration](#file-configuration)
|
|
||||||
* [Funcmap](#funcmap)
|
|
||||||
* [Cache](#cache)
|
|
||||||
* [Static files](#static-files)
|
|
||||||
* [Cache control headers](#cache-control-headers)
|
|
||||||
* [Cache-buster](#cache-buster)
|
|
||||||
* [Email](#email)
|
|
||||||
* [HTTPS](#https)
|
|
||||||
* [Logging](#logging)
|
|
||||||
* [Roadmap](#roadmap)
|
|
||||||
* [Credits](#credits)
|
|
||||||
|
|
||||||
## Introduction
|
## Deploy
|
||||||
|
|
||||||
### Overview
|
See `docker-compose.yml` and `Dockerfile` for the production setup.
|
||||||
|
|
||||||
(NAME) is not a framework but rather a base starter-kit for rapid, easy full-stack web development in Go, aiming to provide much of the functionality you would expect from a complete web framework as well as establishing patterns, procedures and structure for your web application.
|
## License
|
||||||
|
|
||||||
todo
|
MIT — see LICENSE. Original Pagoda code Copyright (c) 2021 Mike Stefanello.
|
||||||
|
|
||||||
### Motivation
|
|
||||||
|
|
||||||
It started with [this post](https://news.ycombinator.com/item?id=29311761) on _Hacker News_, asking the community what the _simplest stack to build web apps in 2021_ is. After leaving PHP for Go over a year ago, I didn't have an answer for what I would use if I were to start building a web app tomorrow. If I was still using PHP, _Laravel_ would most likely be the easy answer, but there's nothing quite like that available for Go, especially in terms of adoption and maturity. For good reasons, the community also seems mostly opposed to mega-frameworks.
|
|
||||||
|
|
||||||
todo
|
|
||||||
|
|
||||||
### Foundation
|
|
||||||
|
|
||||||
While many great projects were used to build this, all of which are listed in the _credits_ section, the following provide the foundation of the back and frontend. It's important to note that you are **not required to use any of these**. Swapping any of them out will be relatively easy.
|
|
||||||
|
|
||||||
#### Backend
|
|
||||||
|
|
||||||
- [Echo](https://echo.labstack.com/): High performance, extensible, minimalist Go web framework.
|
|
||||||
- [Ent](https://entgo.io/): Simple, yet powerful ORM for modeling and querying data.
|
|
||||||
|
|
||||||
#### Frontend
|
|
||||||
|
|
||||||
Go server-side rendered HTML combined with the projects below enable you to create slick, modern UIs without writing any JavaScript or CSS.
|
|
||||||
|
|
||||||
- [HTMX](https://htmx.org/): Access AJAX, CSS Transitions, WebSockets and Server Sent Events directly in HTML, using attributes, so you can build modern user interfaces with the simplicity and power of hypertext.
|
|
||||||
- [Alpine.js](https://alpinejs.dev/): Rugged, minimal tool for composing behavior directly in your markup. Think of it like jQuery for the modern web. Plop in a script tag and get going.
|
|
||||||
- [Bulma](https://bulma.io/): Provides ready-to-use frontend components that you can easily combine to build responsive web interfaces. No JavaScript dependencies.
|
|
||||||
|
|
||||||
#### Storage
|
|
||||||
|
|
||||||
- [PostgreSQL](https://www.postgresql.org/): The world's most advanced open source relational database.
|
|
||||||
- [Redis](https://redis.io/): In-memory data structure store, used as a database, cache, and message broker.
|
|
||||||
|
|
||||||
### Screenshots
|
|
||||||
|
|
||||||
todo
|
|
||||||
|
|
||||||
## Getting started
|
|
||||||
|
|
||||||
### Dependencies
|
|
||||||
|
|
||||||
Ensure the following are installed on your system:
|
|
||||||
|
|
||||||
- [Go](https://go.dev/)
|
|
||||||
- [Docker](https://www.docker.com/)
|
|
||||||
- [Docker Compose](https://docs.docker.com/compose/install/)
|
|
||||||
- [psql](https://www.postgresql.org/docs/13/app-psql.html) _(optional)_
|
|
||||||
- [redis-cli](https://redis.io/topics/rediscli) _(optional)_
|
|
||||||
|
|
||||||
### Start the application
|
|
||||||
|
|
||||||
After checking out the repository, from within the root, start the Docker containers for the database and cache by executing `make up`.
|
|
||||||
|
|
||||||
Once that completes, you can start the application by executing `make run`. By default, you should be able to access the application in your browser at `localhost:8000`.
|
|
||||||
|
|
||||||
If you ever want to quickly drop the Docker containers and restart them in order to wipe all data, execute `make reset`.
|
|
||||||
|
|
||||||
### Running tests
|
|
||||||
|
|
||||||
To run all tests in the application, execute `make test`. This ensures that the tests from each package are not run in parallel. This is required since many packages contain tests that connect to the test database which is dropped and recreated automatically for each package.
|
|
||||||
|
|
||||||
### Clients
|
|
||||||
|
|
||||||
The following _make_ commands are available to make it easy to connect to the database and cache.
|
|
||||||
|
|
||||||
- `make db`: Connects to the primary database
|
|
||||||
- `make db-test`: Connects to the test database
|
|
||||||
- `make cache`: Connects to the cache
|
|
||||||
|
|
||||||
## Service container
|
|
||||||
|
|
||||||
The container is located at `services/container.go` and is meant to house all of your application's services and/or dependencies. It is easily extensible and can be created and initialized in a single call. The services currently included in the container are:
|
|
||||||
|
|
||||||
- Configuration
|
|
||||||
- Cache
|
|
||||||
- Database
|
|
||||||
- ORM
|
|
||||||
- Web
|
|
||||||
- Validator
|
|
||||||
- Authentication
|
|
||||||
- Mail
|
|
||||||
- Template renderer
|
|
||||||
|
|
||||||
A new container can be created and initialized via `services.NewContainer()`. It can be later shutdown via `Shutdown()`.
|
|
||||||
|
|
||||||
### Dependency injection
|
|
||||||
|
|
||||||
The container exists to faciliate easy dependency-injection both for services within the container as well as areas of your application that require any of these dependencies. For example, the container is passed to and stored within the `Controller`
|
|
||||||
so that the controller and the route using it have full, easy access to all services.
|
|
||||||
|
|
||||||
### Test dependencies
|
|
||||||
|
|
||||||
It is common that your tests will require access to dependencies, like the database, or any of the other services available within the container. Keeping all services in a container makes it especially easy to initialize everything within your tests. You can see an example pattern for doing this [here](#environments).
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
The `config` package provides a flexible, extensible way to store all configuration for the application. Configuration is added to the `Container` as a _Service_, making it accessible across most of the application.
|
|
||||||
|
|
||||||
Be sure to review and adjust all of the default configuration values provided.
|
|
||||||
|
|
||||||
### Environment overrides
|
|
||||||
|
|
||||||
Leveraging the functionality of [envdecode](https://github.com/joeshaw/envdecode), all configuration values can be overridden by environment variables. Here is an example of what a configuration value looks like, each of which is a field on a struct:
|
|
||||||
|
|
||||||
```go
|
|
||||||
Port uint16 `env:"HTTP_PORT,default=8000"`
|
|
||||||
```
|
|
||||||
|
|
||||||
The value for this field will be set to `8000`, the default, unless the `HTTP_PORT` environment variable is set, in which case the value of the variable will be used. This allows you to easily override configuration values per-environment.
|
|
||||||
|
|
||||||
### Environments
|
|
||||||
|
|
||||||
The configuration value for the current _environment_ (`Config.App.Environment`) is an important one as it can influence some behavior significantly (will be explained in later sections).
|
|
||||||
|
|
||||||
A helper function (`config.SwitchEnvironment`) is available to make switching the environment easy, but this must be executed prior to loading the configuration. The common use-case for this is to switch the environment to `Test` before tests are executed:
|
|
||||||
|
|
||||||
```go
|
|
||||||
func TestMain(m *testing.M) {
|
|
||||||
// Set the environment to test
|
|
||||||
config.SwitchEnvironment(config.EnvTest)
|
|
||||||
|
|
||||||
// Start a new container
|
|
||||||
c = services.NewContainer()
|
|
||||||
defer func() {
|
|
||||||
if err := c.Shutdown(); err != nil {
|
|
||||||
c.Web.Logger.Fatal(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Run tests
|
|
||||||
exitVal := m.Run()
|
|
||||||
os.Exit(exitVal)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Database
|
|
||||||
|
|
||||||
The database currently used is [PostgreSQL](https://www.postgresql.org/) but you are free to use whatever you prefer. If you plan to continue using [Ent](https://entgo.io/), the incredible ORM, you can check their supported databases [here](https://entgo.io/docs/dialects). The database-driver and client is provided by [pgx](github.com/jackc/pgx/v4) and included in the `Container`.
|
|
||||||
|
|
||||||
Database configuration can be found and managed within the `config` package.
|
|
||||||
|
|
||||||
### Auto-migrations
|
|
||||||
|
|
||||||
[Ent](https://entgo.io/) provides automatic migrations which are executed on the database whenever the `Container` is created, which means they will run when the application starts.
|
|
||||||
|
|
||||||
### Separate test database
|
|
||||||
|
|
||||||
Since many tests can require a database, this application supports a separate database specifically for tests. Within the `config`, the test database name can be specified at `Config.Database.TestDatabase`.
|
|
||||||
|
|
||||||
When a `Container` is created, if the [environment](#environments) is set to `config.EnvTest`, the database client will connect to the test database instead, drop the database, recreate it, and run migrations so your tests start with a clean, ready-to-go database. Another benefit is that after the tests execute in a given package, you can connect to the test database to audit the data which can be useful for debugging.
|
|
||||||
|
|
||||||
## ORM
|
|
||||||
|
|
||||||
As previously mentioned, [Ent](https://entgo.io/) is the supplied ORM. It can swapped out, but I highly recommend it. I don't think there is anything comparable for Go, at the current time. If you're not familiar with Ent, take a look through their top-notch [documentation](https://entgo.io/docs/getting-started).
|
|
||||||
|
|
||||||
An Ent client is included in the `Container` to provide easy access to the ORM throughout the application.
|
|
||||||
|
|
||||||
Ent relies on code-generation for the entities you create to provide robust, type-safe data operations. Everything within the `ent` package in this repository is generated code for the two entity types listed below with the exception of the schema declaration.
|
|
||||||
|
|
||||||
### Entity types
|
|
||||||
|
|
||||||
The two included entity types are:
|
|
||||||
- User
|
|
||||||
- PasswordToken
|
|
||||||
|
|
||||||
### New entity type
|
|
||||||
|
|
||||||
While you should refer to their [documentation](https://entgo.io/docs/getting-started) for detailed usage, it's helpful to understand how to create an entity type and generate code. To make this easier, the `Makefile` contains some helpers.
|
|
||||||
|
|
||||||
1. Ensure all Ent code is downloaded by executing `make ent-install`.
|
|
||||||
2. Create the new entity type by executing `make ent-new name=User` where `User` is the name of the entity type. This will generate a file like you can see in `ent/schema/user.go` though the `Fields()` and `Edges()` will be left empty.
|
|
||||||
3. Populate the `Fields()` and optionally the `Edges()` (which are the relationships to other entity types).
|
|
||||||
4. When done, generate all code by executing `make ent-gen`.
|
|
||||||
|
|
||||||
The generated code is extremely flexible and impressive. An example to highlight this is one used within this application:
|
|
||||||
|
|
||||||
```go
|
|
||||||
entity, err := c.ORM.PasswordToken.
|
|
||||||
Query().
|
|
||||||
Where(passwordtoken.HasUserWith(user.ID(userID))).
|
|
||||||
Where(passwordtoken.CreatedAtGTE(expiration)).
|
|
||||||
All(ctx.Request().Context())
|
|
||||||
```
|
|
||||||
|
|
||||||
This executes a database query to return all _password token_ entities that belong to a user with a given ID and have a _created at_ timestamp field that is greater than or equal to a given time.
|
|
||||||
|
|
||||||
## Sessions
|
|
||||||
|
|
||||||
Sessions are provided and handled via [Gorilla sessions](https://github.com/gorilla/sessions) and configured as middleware in the router located at `routes/router.go`. Session data is currently stored in cookies but there are many [options](https://github.com/gorilla/sessions#store-implementations) available if you wish to use something else.
|
|
||||||
|
|
||||||
Here's a simple example of loading data from a session and saving new values:
|
|
||||||
|
|
||||||
```go
|
|
||||||
func SomeFunction(ctx echo.Context) error {
|
|
||||||
sess, err := session.Get("some-session-key", ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sess.Values["hello"] = "world"
|
|
||||||
sess.Values["isSomething"] = true
|
|
||||||
return sess.Save(ctx.Request(), ctx.Response())
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Authentication
|
|
||||||
|
|
||||||
Included are standard authentication features you expect in any web application. Authentication functionality is bundled as a _Service_ within `services/AuthClient` and added to the `Container`. If you wish to handle authentication in a different manner, you could swap this client out or modify it as needed.
|
|
||||||
|
|
||||||
Authentication currently requires [sessions](#sessions) and the session middleware.
|
|
||||||
|
|
||||||
### Login / Logout
|
|
||||||
|
|
||||||
The `AuthClient` has methods `Login()` and `Logout()` to log a user in or out. To track a user's authentication state, data is stored in the session including the user ID and authentication status.
|
|
||||||
|
|
||||||
Prior to logging a user in, the method `CheckPassword()` can be used to determine if a user's password matches the hash stored in the database and on the `User` entity.
|
|
||||||
|
|
||||||
Routes are provided for the user to login and logout at `user/login` and `user/logout`.
|
|
||||||
|
|
||||||
### Forgot password
|
|
||||||
|
|
||||||
Users can reset their password in a secure manner by issuing a new password token via the method `GeneratePasswordResetToken()`. This creates a new `PasswordToken` entity in the database belonging to the user. The actual token itself, however, is not stored in the database for security purposes. It is only returned via the method so it can be used to build the reset URL for the email. Rather, a hash of the token is stored, using `bcrypt` the same package used to hash user passwords. The reason for doing this is the same as passwords. You do not want to store a plain-text value in the database that can be used to access an account.
|
|
||||||
|
|
||||||
Tokens have a configurable expiration. By default, they expire within 1 hour. This can be controlled in the `config` package. The expiration of the token is not stored in the database, but rather is used only when tokens are loaded for potential usage. This allows you to change the expiration duration and affect existing tokens.
|
|
||||||
|
|
||||||
Since the actual tokens are not stored in the database, the reset URL must contain the user's ID. Using that, `GetValidPasswordToken()` will load all non-expired _password token_ entities belonging to the user, and use `bcrypt` to determine if the token in the URL matches any of the stored hashes.
|
|
||||||
|
|
||||||
Once a user claims a valid password token, all tokens for that user should be deleted using `DeletePasswordTokens()`.
|
|
||||||
|
|
||||||
Routes are provided to request a password reset email at `user/password` and to reset your password at `user/password/reset/token/:uid/:password_token`.
|
|
||||||
|
|
||||||
### Registration
|
|
||||||
|
|
||||||
The actual registration of a user is not handled within the `AuthClient` but rather just by creating a `User` entity. When creating a user, use `HashPassword()` to create a hash of the user's password, which is what will be stored in the database.
|
|
||||||
|
|
||||||
A route is provided for the user to register at `user/register`.
|
|
||||||
|
|
||||||
### Authenticated user
|
|
||||||
|
|
||||||
The `AuthClient` has two methods available to get either the `User` entity or the ID of the user currently logged in for a given request. Those methods are `GetAuthenticatedUser()` and `GetAuthenticatedUserID()`.
|
|
||||||
|
|
||||||
#### Middleware
|
|
||||||
|
|
||||||
Registered for all routes is middleware that will load the currently logged in user entity and store it within the request context. The middleware is located at `middleware.LoadAuthenticatedUser()` and, if authenticated, the `User` entity is stored within the context using the key `context.AuthenticatedUserKey`.
|
|
||||||
|
|
||||||
If you wish to require either authentication or non-authentication for a given route, you can use either `middleware.RequireAuthentication()` or `middleware.RequireNoAuthentication()`.
|
|
||||||
|
|
||||||
## Routes
|
|
||||||
|
|
||||||
The router functionality is provided by [Echo](https://echo.labstack.com/guide/routing/) and constructed within via the `BuildRouter()` function inside `routes/router.go`. Since the _Echo_ instance is a _Service_ on the `Container` which is passed in to `BuildRouter()`, middleware and routes can be added directly to it.
|
|
||||||
|
|
||||||
### Custom middleware
|
|
||||||
|
|
||||||
By default, a middleware stack is included in the router that makes sense for most web applications. Be sure to review what has been included and what else is available within _Echo_ and the other projects mentioned.
|
|
||||||
|
|
||||||
A `middleware` package is included which you can easily add to along with the custom middleware provided.
|
|
||||||
|
|
||||||
### Controller / Dependencies
|
|
||||||
|
|
||||||
The `Controller`, which is described in a section below, serves two purposes for routes:
|
|
||||||
|
|
||||||
1) It provides base functionality which can be embedded in each route, most importantly `Page` rendering (described in the `Controller` section below)
|
|
||||||
2) It stores a pointer to the `Container`, making all _Services_ available within your route
|
|
||||||
|
|
||||||
While using the `Controller` is not required for your routes, it will certainly make development easier.
|
|
||||||
|
|
||||||
See the following section for the proposed pattern.
|
|
||||||
|
|
||||||
### Patterns
|
|
||||||
|
|
||||||
These patterns are not required, but were designed to make development as easy as possible.
|
|
||||||
|
|
||||||
To declare a new route that will have methods to handle a GET and POST request, for example, start with a new _struct_ type, that embeds the `Controller`:
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Home struct {
|
|
||||||
controller.Controller
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Home) Get(ctx echo.Context) error {}
|
|
||||||
|
|
||||||
func (c *Home) Post(ctx echo.Context) error {}
|
|
||||||
```
|
|
||||||
|
|
||||||
Then create the route and add to the router:
|
|
||||||
|
|
||||||
```go
|
|
||||||
home := Home{Controller: controller.NewController(c)}
|
|
||||||
g.GET("/", home.Get).Name = "home"
|
|
||||||
g.POST("/", home.Post).Name = "home.post"
|
|
||||||
```
|
|
||||||
|
|
||||||
Your route will now have all methods available on the `Controller` as well as access to the `Container`. It's not required to name the route methods to match the HTTP method.
|
|
||||||
|
|
||||||
**It is highly recommended** that you provide a `Name` for your routes. Most methods on the back and frontend leverage the route name and parameters in order to generate URLs.
|
|
||||||
|
|
||||||
### Testing
|
|
||||||
|
|
||||||
Since most of your web application logic will live in your routes, being able to easily test them is important. The following aims to help facilitate that.
|
|
||||||
|
|
||||||
The test setup and helpers reside in `routes/router_test.go`.
|
|
||||||
|
|
||||||
Only a brief example of route tests were provided in order to highlight what is available. Adding full tests did not seem logical since these routes will most likely be changed or removed in your project.
|
|
||||||
|
|
||||||
#### HTTP server
|
|
||||||
|
|
||||||
When the route tests initialize, a new `Container` is created which provides full access to all of the _Services_ that will be available during normal application execution. Also provided is a test HTTP server with the router added. This means your tests can make requests and expect responses exactly as the application would behave outside of tests. You do not need to mock the requests and responses.
|
|
||||||
|
|
||||||
#### Request / Response helpers
|
|
||||||
|
|
||||||
With the test HTTP server setup, test helpers for making HTTP requests and evaluating responses are made available to reduce the amount of code you need to write. See `httpRequest` and `httpResponse` within `routes/router_test.go`.
|
|
||||||
|
|
||||||
Here is an example how to easily make a request and evaluate the response:
|
|
||||||
|
|
||||||
```go
|
|
||||||
func TestAbout_Get(t *testing.T) {
|
|
||||||
doc := request(t).
|
|
||||||
setRoute("about").
|
|
||||||
get().
|
|
||||||
assertStatusCode(http.StatusOK).
|
|
||||||
toDoc()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Goquery
|
|
||||||
|
|
||||||
A helpful, included package to test HTML markup from HTTP responses is [goquery](https://github.com/PuerkitoBio/goquery). This allows you to use jQuery-style selectors to parse and extract HTML values, attributes, and so on.
|
|
||||||
|
|
||||||
In the example above, `toDoc()` will return a `*goquery.Document` created from the HTML response of the test HTTP server.
|
|
||||||
|
|
||||||
Here is a simple example of how to use it, along with [testify](https://github.com/stretchr/testify) for making assertions:
|
|
||||||
|
|
||||||
```go
|
|
||||||
h1 := doc.Find("h1.title")
|
|
||||||
assert.Len(t, h1.Nodes, 1)
|
|
||||||
assert.Equal(t, "About", h1.Text())
|
|
||||||
```
|
|
||||||
|
|
||||||
## Controller
|
|
||||||
|
|
||||||
As previously mentioned, the `Controller` acts as a base for your routes, though it is optional. It stores the `Container` which houses all _Services_ (_dependencies_) but also a wide array of functionality aimed at allowing you to build complex responses with ease and consistency.
|
|
||||||
|
|
||||||
### Page
|
|
||||||
|
|
||||||
The `Page` is the major building block of your `Controller` responses. It is a _struct_ type located at `controller/page.go`. The concept of the `Page` is that it provides a consistent structure for building responses and transmitting data and functionality to the templates.
|
|
||||||
|
|
||||||
All example routes provided construct and _render_ a `Page`. It's recommended that you review both the `Page` and the example routes as they try to illustrate all included functionality.
|
|
||||||
|
|
||||||
As you develop your application, the `Page` can be easily extended to include whatever data or functions you want to provide to your templates.
|
|
||||||
|
|
||||||
Initializing a new page is simple:
|
|
||||||
|
|
||||||
```go
|
|
||||||
func (c *Home) Get(ctx echo.Context) error {
|
|
||||||
page := controller.NewPage(ctx)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Using the `echo.Context`, the `Page` will be initialized with the following fields populated:
|
|
||||||
|
|
||||||
- `Context`: The passed in _context_
|
|
||||||
- `ToURL`: A function the templates can use to generate a URL with a given route name and parameters
|
|
||||||
- `Path`: The requested URL path
|
|
||||||
- `URL`: The requested URL
|
|
||||||
- `StatusCode`: Defaults to 200
|
|
||||||
- `Pager`: Initialized `Pager` (see below)
|
|
||||||
- `RequestID`: The request ID, if the middleware is being used
|
|
||||||
- `IsHome`: If the request was for the homepage
|
|
||||||
- `IsAuth`: If the user is authenticated
|
|
||||||
- `AuthUser`: The logged in user entity, if one
|
|
||||||
- `CSRF`: The CSRF token, if the middleware is being used
|
|
||||||
- `HTMX.Request`: Data from the HTMX headers, if HTMX made the request (see below)
|
|
||||||
|
|
||||||
### Flash messaging
|
|
||||||
|
|
||||||
While flash messaging functionality is provided outside of the `Controller` and `Page`, within the `msg` package, it's really only used within this context.
|
|
||||||
|
|
||||||
Flash messaging requires that [sessions](#sessions) and the session middleware are in place since that is where the messages are stored.
|
|
||||||
|
|
||||||
#### Creating messages
|
|
||||||
|
|
||||||
There are four types of messages, and each can be created as follows:
|
|
||||||
- Success: `msg.Success(ctx echo.Context, message string)`
|
|
||||||
- Info: `msg.Info(ctx echo.Context, message string)`
|
|
||||||
- Warning: `msg.Warning(ctx echo.Context, message string)`
|
|
||||||
- Danger: `msg.Danger(ctx echo.Context, message string)`
|
|
||||||
|
|
||||||
The _message_ string can contain HTML.
|
|
||||||
|
|
||||||
#### Rendering messages
|
|
||||||
|
|
||||||
When a flash message is retrieved from storage in order to be rendered, it is deleted from storage so that it cannot be rendered again.
|
|
||||||
|
|
||||||
The `Page` has a method that can be used to fetch messages for a given type from within the template: `Page.GetMessages(typ msg.Type)`. This is used rather than the _funcmap_ because the `Page` contains the request context which is required in order to access the session data. Since the `Page` is the data destined for the templates, you can use: `{{.GetMessages "success"}}` for example.
|
|
||||||
|
|
||||||
To make things easier, a template _component_ is already provided, located at `templates/components/messages.gohtml`. This will render all messages of all types simply by using `{{template "messages" .}}` either within your page or layout template.
|
|
||||||
|
|
||||||
### Pager
|
|
||||||
|
|
||||||
A very basic mechanism is provided to handle and facilitate paging located in `controller/pager.go`. When a `Page` is initialized, so is a `Pager` at `Page.Pager`. If the requested URL contains a `page` query parameter with a numeric value, that will be set as the page number in the pager.
|
|
||||||
|
|
||||||
During initialization, the _items per page_ amount will be set to the default, controlled via constant, which has a value of 20. It can be overridden by changing `Pager.ItemsPerPage` but should be done before other values are set in order to not provide incorrect calculations.
|
|
||||||
|
|
||||||
Methods include:
|
|
||||||
|
|
||||||
- `SetItems(items int)`: Set the total amount of items in the entire result-set
|
|
||||||
- `IsBeginning()`: Determine if the pager is at the beginning of the pages
|
|
||||||
- `IsEnd()`: Determine if the pager is at the end of the pages
|
|
||||||
- `GetOffset()`: Get the offset which can be useful is constructing a paged database query
|
|
||||||
|
|
||||||
There is currently no template (yet) to easily render a pager.
|
|
||||||
|
|
||||||
### CSRF
|
|
||||||
|
|
||||||
By default, all non GET requests will require a CSRF token be provided as a form value. This is provided by middleware and can be adjusted or removed in the router.
|
|
||||||
|
|
||||||
The `Page` will contain the CSRF token for the given request. There is a CSRF helper component template which can be used to easily render a hidden form element in your form which will contain the CSRF token and the proper element name. Simply include `{{template "csrf" .}}` within your form.
|
|
||||||
|
|
||||||
### Automatic template parsing
|
|
||||||
|
|
||||||
Dealing with templates can be quite tedious and annoying so the `Page` aims to make it as simple as possible with the help of the [template renderer](#template-renderer). To start, templates for _pages_ are grouped in the following directories within the `templates` directory:
|
|
||||||
|
|
||||||
- `layouts`: Base templates that provide the entire HTML wrapper/layout. This template should include a call to `{{template "content" .}}` to render the content of the `Page`.
|
|
||||||
- `pages`: Templates that are specific for a given route/page. These must contain `{{define "content"}}{{end}}` which will be injected in to the _layout_ template.
|
|
||||||
- `components`: A shared library of common components that the layout and base template can leverage.
|
|
||||||
|
|
||||||
Specifying which templates to render for a given `Page` is as easy as:
|
|
||||||
|
|
||||||
```go
|
|
||||||
page.Name = "home"
|
|
||||||
page.Layout = "main"
|
|
||||||
```
|
|
||||||
|
|
||||||
That alone will result in the following templates being parsed and executed when the `Page` is rendered:
|
|
||||||
|
|
||||||
1) `layouts/main.gohtml` as the base template
|
|
||||||
2) `pages/home.gohtml` to provide the `content` template for the layout
|
|
||||||
3) All template files located within the `components` directory
|
|
||||||
4) The entire [funcmap](#funcmap)
|
|
||||||
|
|
||||||
The [template renderer](#template-renderer) also provides caching and local hot-reloading.
|
|
||||||
|
|
||||||
### Cached responses
|
|
||||||
|
|
||||||
A `Page` can have cached enabled just by setting `Page.Cache.Enabled` to `true`. The `Controller` will automatically handle caching the HTML output, headers and status code. Cached pages are stored using a key that matches the full request URL and [middleware](#cache-middleware) is used to serve it on matching requests.
|
|
||||||
|
|
||||||
By default, the cache expiration time will be set according to the configuration value located at `Config.Cache.Expiration.Page` but it can be set per-page at `Page.Cache.Expiration`.
|
|
||||||
|
|
||||||
#### Cache tags
|
|
||||||
|
|
||||||
You can optionally specify cache tags for the `Page` by setting a slice of strings on `Page.Cache.Tags`. This provides the ability to build in cache invalidation logic in your application driven by events such as entity operations, for example.
|
|
||||||
|
|
||||||
The cache client on the `Container` is currently handled by [gocache](https://github.com/eko/gocache) which makes it easy to perform operations such as tag-invalidation, for example:
|
|
||||||
|
|
||||||
```go
|
|
||||||
c.Cache.Invalidate(ctx, store.InvalidateOptions{
|
|
||||||
Tags: []string{"my-tag"},
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Cache middleware
|
|
||||||
|
|
||||||
Cached pages are served via the middleware `ServeCachedPage()` in the `middleware` package.
|
|
||||||
|
|
||||||
The cache is bypassed if the requests meet any of the following criteria:
|
|
||||||
1) Is not a GET request
|
|
||||||
2) Is made by an authenticated user
|
|
||||||
|
|
||||||
Cached pages are looked up for a key that matches the exact, full URL of the given request.
|
|
||||||
|
|
||||||
### Data
|
|
||||||
|
|
||||||
The `Data` field on the `Page` is of type `interface{}` and is what allows your route to pass whatever it requires to the templates, alongside the `Page` itself.
|
|
||||||
|
|
||||||
### Forms
|
|
||||||
|
|
||||||
The `Form` field on the `Page` is similar to the `Data` field in that it's an `interface{}` type but it's meant to store a struct that represents a form being rendered on the page.
|
|
||||||
|
|
||||||
An example of this pattern is:
|
|
||||||
|
|
||||||
```go
|
|
||||||
type ContactForm struct {
|
|
||||||
Email string `form:"email" validate:"required,email"`
|
|
||||||
Message string `form:"message" validate:"required"`
|
|
||||||
Submission controller.FormSubmission
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Then in your page:
|
|
||||||
|
|
||||||
```go
|
|
||||||
page := controller.NewPage(ctx)
|
|
||||||
page.Form = ContactForm{}
|
|
||||||
```
|
|
||||||
|
|
||||||
How the _form_ gets populated with values so that your template can render them is covered in the next section.
|
|
||||||
|
|
||||||
#### Submission processing
|
|
||||||
|
|
||||||
Form submission processing is made extremely simple by leveraging functionality provided by [Echo binding](https://echo.labstack.com/guide/binding/), [validator](https://github.com/go-playground/validator) and the `FormSubmission` struct located in `controller/form.go`.
|
|
||||||
|
|
||||||
Using the example form above, these are the steps you would take within the _POST_ callback for your route:
|
|
||||||
|
|
||||||
Start by storing a pointer to the form in the conetxt so that your _GET_ callback can access the form values, which will be showed at the end:
|
|
||||||
```go
|
|
||||||
var form ContactForm
|
|
||||||
ctx.Set(context.FormKey, &form)
|
|
||||||
```
|
|
||||||
|
|
||||||
Parse the input in the POST data to map to the struct so it becomes populated. This uses the `form` struct tags to map form values to the struct fields.
|
|
||||||
```go
|
|
||||||
if err := ctx.Bind(&form); err != nil {
|
|
||||||
// Something went wrong...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Process the submission which uses [validator](https://github.com/go-playground/validator) to check for validation errors:
|
|
||||||
```go
|
|
||||||
if err := form.Submission.Process(ctx, form); err != nil {
|
|
||||||
// Something went wrong...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Check if the form submission has any validation errors:
|
|
||||||
```go
|
|
||||||
if !form.Submission.HasErrors() {
|
|
||||||
// All good, now execute something!
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
In the event of a validation error, you most likely want to re-render the form with the values provided and any error messages. Since you stored a pointer to the _form_ in the context in the first step, you can first have the _POST_ handler call the _GET_:
|
|
||||||
```go
|
|
||||||
if form.Submission.HasErrors() {
|
|
||||||
return c.Get(ctx)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Then, in your _GET_ handler, extract the form from the context so it can be passed to the templates:
|
|
||||||
```go
|
|
||||||
page := controller.NewPage(ctx)
|
|
||||||
page.Form = ContactForm{}
|
|
||||||
|
|
||||||
if form := ctx.Get(context.FormKey); form != nil {
|
|
||||||
page.Form = form.(*ContactForm)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
And finally, your template:
|
|
||||||
```
|
|
||||||
<input id="email" name="email" type="email" class="input" value="{{.Form.Email}}">
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Inline validation
|
|
||||||
|
|
||||||
The `FormSubmission` makes inline validation easier because it will store all validation errors in a map, keyed by the form struct field name. It also contains helper methods that your templates can use to provide classes and extract the error messages.
|
|
||||||
|
|
||||||
While [validator](https://github.com/go-playground/validator) is an incredible package that is used to validate based on struct tags, the downside is that the messaging, by default, is not very human-readable or easy to override. Within `FormSubmission.setErrorMessages()` the validation errors are converted to more readable messages based on the tag that failed validation. Only a few tags are provided as an example, so be sure to expand on that as needed.
|
|
||||||
|
|
||||||
To provide the inline validation in your template, there are two things that need to be done.
|
|
||||||
|
|
||||||
First, include a status class on the element so it will highlight green or red based on the validation:
|
|
||||||
```
|
|
||||||
<input id="email" name="email" type="email" class="input {{.Form.Submission.GetFieldStatusClass "Email"}}" value="{{.Form.Email}}">
|
|
||||||
```
|
|
||||||
|
|
||||||
Second, render the error messages, if there are any for a given field:
|
|
||||||
```go
|
|
||||||
{{template "field-errors" (.Form.Submission.GetFieldErrors "Email")}}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Headers
|
|
||||||
|
|
||||||
HTTP headers can be set either via the `Page` or the _context_:
|
|
||||||
|
|
||||||
```go
|
|
||||||
page := controller.NewPage(ctx)
|
|
||||||
page.Headers["HeaderName"] = "header-value"
|
|
||||||
```
|
|
||||||
|
|
||||||
```go
|
|
||||||
ctx.Response().Header().Set("HeaderName", "header-value")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Status code
|
|
||||||
|
|
||||||
The HTTP response status code can be set either via the `Page` or the _context_:
|
|
||||||
|
|
||||||
```go
|
|
||||||
page := controller.NewPage(ctx)
|
|
||||||
page.StatusCode = http.StatusTooManyRequests
|
|
||||||
```
|
|
||||||
|
|
||||||
```go
|
|
||||||
ctx.Response().Status = http.StatusTooManyRequests
|
|
||||||
```
|
|
||||||
|
|
||||||
### Metatags
|
|
||||||
|
|
||||||
The `Page` provides the ability to set basic HTML metatags which can be especially useful if your web application is publicly accessible. Only fields for the _description_ and _keywords_ are provided but adding additional fields is very easy.
|
|
||||||
|
|
||||||
```go
|
|
||||||
page := controller.NewPage(ctx)
|
|
||||||
page.Metatags.Description = "The page description."
|
|
||||||
page.Metatags.Keywords = []string{"Go", "Software"}
|
|
||||||
```
|
|
||||||
|
|
||||||
A _component_ template is included to render metatags in `core.gohtml` which can be used by adding `{{template "metatags" .}}` to your _layout_.
|
|
||||||
|
|
||||||
### URL and link generation
|
|
||||||
|
|
||||||
Generating URLs in the templates is made easy if you follow the [routing patterns](#patterns) and provide names for your routes. Echo provides a `Reverse` function to generate a route URL with a given route name and optional parameters. This function is made accessible to the templates via the `Page` field `ToURL`.
|
|
||||||
|
|
||||||
As an example, if you have route such as:
|
|
||||||
```go
|
|
||||||
profile := Profile{Controller: ctr}
|
|
||||||
e.GET("/user/profile/:user", profile.Get).Name = "user_profile"
|
|
||||||
```
|
|
||||||
|
|
||||||
And you want to generate a URL in the template, you can:
|
|
||||||
```go
|
|
||||||
{{call .ToURL "user_profile" 1}
|
|
||||||
```
|
|
||||||
|
|
||||||
Which will generate: `/user/profile/1`
|
|
||||||
|
|
||||||
There is also a helper function provided in the [funcmap](#funcmap) to generate links which has the benefit of adding an _active_ class if the link URL matches the current path. This is especially useful for navigation menus.
|
|
||||||
|
|
||||||
```go
|
|
||||||
{{link (call .ToURL "user_profile" .AuthUser.ID) "Profile" .Path "extra-class"}}
|
|
||||||
```
|
|
||||||
|
|
||||||
Will generate:
|
|
||||||
```html
|
|
||||||
<a href="/user/profile/1" class="is-active extra-class">Profile</a>
|
|
||||||
```
|
|
||||||
Assuming the current _path_ is `/user/profile/1`; otherwise the `is-active` class will be excluded.
|
|
||||||
|
|
||||||
### HTMX support
|
|
||||||
|
|
||||||
[HTMX](https://htmx.org/) is an incredible JavaScript library allows you to access AJAX, CSS Transitions, WebSockets and Server Sent Events directly in HTML, using attributes, so you can build modern user interfaces with the simplicity and power of hypertext.
|
|
||||||
|
|
||||||
Many examples of its usage are available in the included examples:
|
|
||||||
- All navigation links use [boost](https://htmx.org/docs/#boosting) which dynamically replaces the page content with an AJAX request, providing a SPA-like experience.
|
|
||||||
- All forms use either [boost](https://htmx.org/docs/#boosting) or [hx-post](https://htmx.org/docs/#triggers) to submit via AJAX.
|
|
||||||
- The mock search autocomplete modal uses [hx-get](https://htmx.org/docs/#targets) to fetch search results from the server via AJAX and update the UI.
|
|
||||||
|
|
||||||
All of this can be easily accomplished without writing any JavaScript at all.
|
|
||||||
|
|
||||||
Another benefit of [HTMX](https://htmx.org/) is that it's completely backend-agnostic and does not require any special tools or integrations on the backend. But to make things easier, included is a small package to read and write [HTTP headers](https://htmx.org/docs/#requests) that HTMX uses to communicate additional information and commands.
|
|
||||||
|
|
||||||
The `htmx` package contains the headers for the _request_ and _response_. When a `Page` is initialized, `Page.HTMX.Request` will also be initialized and populated with the headers that HTMX provides, if HTMX made the request. This allows you to determine if HTMX is making the given request and what exactly it is doing, which could be useful both in your _route_ as well as your _templates_.
|
|
||||||
|
|
||||||
If you need to set any HTMX headers in your `Page` response, this can be done by altering `Page.HTMX.Response`.
|
|
||||||
|
|
||||||
#### Layout template override
|
|
||||||
|
|
||||||
To faciliate easy partial rendering for HTMX requests, the `Page` will automatically change your _Layout_ template to use `htmx.gohtml`, which currently only renders `{{template "content" .}}`. This allows you to use an HTMX request to only update the content portion of the page, rather than the entire HTML.
|
|
||||||
|
|
||||||
This override only happens if the HTMX request being made is **not a boost** request because **boost** requests replace the entire `body` element so there is no need to do a partial render.
|
|
||||||
|
|
||||||
#### Conditional processing / rendering
|
|
||||||
|
|
||||||
Since HTMX communicates what it is doing with the server, you can use the request headers to conditionally process in your _route_ or render in your _template_, if needed. If your routes aren't doing multiple things, you may not need this, but it's worth knowing how flexible you can be.
|
|
||||||
|
|
||||||
A simple example of this:
|
|
||||||
|
|
||||||
```go
|
|
||||||
if page.HTMX.Request.Target == "search" {
|
|
||||||
// You know this request HTMX is fetching content just for the #search element
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```go
|
|
||||||
{{if eq .page.HTMX.Request.Target "search"}}
|
|
||||||
// Render content for the #search element
|
|
||||||
{{end}}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Rendering the page
|
|
||||||
|
|
||||||
Once your `Page` is fully built, rendering it via the embedded `Controller` in your _route_ can be done simply by calling `RenderPage()`:
|
|
||||||
|
|
||||||
```go
|
|
||||||
func (c *Home) Get(ctx echo.Context) error {
|
|
||||||
page := controller.NewPage(ctx)
|
|
||||||
page.Layout = "main"
|
|
||||||
page.Name = "home"
|
|
||||||
return c.RenderPage(ctx, page)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Template renderer
|
|
||||||
|
|
||||||
The _template renderer_ is a _Service_ on the `Container` that aims to make template parsing and rendering easy and flexible. It is the mechanism that allows the `Page` to do [automatic template parsing](#automatic-template-parsing). The standard `html/template` is still the engine used behind the scenes. The code can be found in `services/template_renderer.go`.
|
|
||||||
|
|
||||||
While there are several methods available, the following is the primary one used:
|
|
||||||
|
|
||||||
`ParseAndExecute(cacheGroup, cacheID, baseName string, files []string, directories []string, data interface{})`
|
|
||||||
|
|
||||||
Using the example from the [page rendering](#rendering-the-page), this is what the `Controller` will execute:
|
|
||||||
|
|
||||||
```go
|
|
||||||
buf, err = c.TemplateRenderer.ParseAndExecute(
|
|
||||||
"page",
|
|
||||||
page.Name,
|
|
||||||
page.Layout,
|
|
||||||
[]string{
|
|
||||||
fmt.Sprintf("layouts/%s", page.Layout),
|
|
||||||
fmt.Sprintf("pages/%s", page.Name),
|
|
||||||
},
|
|
||||||
[]string{"components"},
|
|
||||||
page,
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
The parameters represent:
|
|
||||||
- `cacheGroup`: The _group_ to cache the parsed templates in
|
|
||||||
- `cacheID`: The _ID_ of the cache within the _group_
|
|
||||||
- `baseName`: The name of the base template, excluding the extension
|
|
||||||
- `files`: A list of individual template files to include, excluding the extension and template directory
|
|
||||||
- `directories`: A list of directories to include all templates contained
|
|
||||||
- `data`: The data object to send to the templates
|
|
||||||
|
|
||||||
All templates will be parsed with the [funcap](#funcmap).
|
|
||||||
|
|
||||||
### Caching
|
|
||||||
|
|
||||||
Parsed templates will be cached within a `sync.Map` so the operation will only happen once per cache _group_ and _ID_. Be careful with your cache _group_ and _ID_ parameters to avoid collisions.
|
|
||||||
|
|
||||||
### Hot-reload for development
|
|
||||||
|
|
||||||
If the current [environment](#environments) is set to `config.EnvLocal`, which is the default, the cache will be bypassed and templates will be parsed every time they are requested. This allows you to have hot-reloading without having to restart the application so you can see your HTML changes in the browser immediately.
|
|
||||||
|
|
||||||
### File configuration
|
|
||||||
|
|
||||||
To make things easier and less repetitive, parameters given to the _template renderer_ must not include the `templates` directory or the template file extensions. These are stored as constants within the `config` package. If your project has a need to change either of these, simply adjust the `TemplateDir` and `TemplateExt` constants.
|
|
||||||
|
|
||||||
## Funcmap
|
|
||||||
|
|
||||||
The `funcmap` package provides a _function map_ (`template.FuncMap`) which will be included for all templates rendered with the [template renderer](#template-renderer). Aside from a few custom functions, [sprig](https://github.com/Masterminds/sprig) is included which provides over 100 commonly used template functions. The full list is available [here](http://masterminds.github.io/sprig/).
|
|
||||||
|
|
||||||
To include additional custom functions, add to the slice in `GetFuncMap()` and define the function in the package. It will then become automatically available in all templates.
|
|
||||||
|
|
||||||
## Cache
|
|
||||||
|
|
||||||
As previously mentioned, [Redis](https://redis.io/) was chosen as the cache but it can be easily swapped out for something else. [go-redis](https://github.com/go-redis/redis) is used as the underlying client but the `Container` currently only exposes [gocache](https://github.com/eko/gocache) which was chosen because it makes interfacing with the cache client much easier, and it provides a consistent interface if you were to use a cache backend other than Redis.
|
|
||||||
|
|
||||||
The built-in usage of the cache is currently only for optional [page caching](#cached-responses) but it can be used for practically anything.
|
|
||||||
|
|
||||||
## Static files
|
|
||||||
|
|
||||||
Static files are currently configured in the router (`routes/router.go`) to be served from the `static` directory. If you wish to change the directory, alter the constant `config.StaticDir`. The URL prefix for static files is `/files` which is controlled via the `config.StaticPrefix` constant.
|
|
||||||
|
|
||||||
### Cache control headers
|
|
||||||
|
|
||||||
Static files are grouped separately so you can apply middleware only to them. Included is a custom middleware to set cache control headers (`middleware.CacheControl`) which has been added to the static files router group.
|
|
||||||
|
|
||||||
The cache max-life is controlled by the configuration at `Config.Cache.Expiration.StaticFile` and defaults to 6 months.
|
|
||||||
|
|
||||||
### Cache-buster
|
|
||||||
|
|
||||||
While it's ideal to use cache control headers on your static files so browsers cache the files, you need a way to bust the cache in case the files are changed. In order to do this, a function is provided in the [funcmap](#funcmap) to generate a static file URL for a given file that appends a cache-buster query. This query string is randomly generated and persisted until the application restarts.
|
|
||||||
|
|
||||||
For example, to render a file located in `static/picture.png`, you would use:
|
|
||||||
```go
|
|
||||||
<img src="{{File "picture.png"}"/>
|
|
||||||
```
|
|
||||||
|
|
||||||
Which would result in:
|
|
||||||
```html
|
|
||||||
<img src="/files/picture.png?v=9fhe73kaf3"/>
|
|
||||||
```
|
|
||||||
|
|
||||||
Where `9fhe73kaf3` is the randomly-generated cache-buster.
|
|
||||||
|
|
||||||
## Email
|
|
||||||
|
|
||||||
An email client was added as a _Service_ to the `Container` but it is just a skeleton without any actual email-sending functionality. The reason is because there are a lot of ways to send email and most prefer using a SaaS solution for that. That makes it difficult to provide a generic solution that will work for most applications.
|
|
||||||
|
|
||||||
Two starter methods were added to the `MailClient`, one to send an email via plain-text and one to send via a template by leveraging the [template renderer](#template-renderer). The standard library can be used if you wish to send email via SMTP and most SaaS providers have a Go package that can be used if you choose to go that direction.
|
|
||||||
|
|
||||||
## HTTPS
|
|
||||||
|
|
||||||
By default, the application will not use HTTPS but it can be enabled easily. Just alter the following configuration:
|
|
||||||
|
|
||||||
- `Config.HTTP.TLS.Enabled`: `true`
|
|
||||||
- `Config.HTTP.TLS.Certificate`: Full path to the certificate file
|
|
||||||
- `Config.HTTP.TLS.Key`: Full path to the key file
|
|
||||||
|
|
||||||
To use _Let's Encrypt_ follow [this guide](https://echo.labstack.com/cookbook/auto-tls/#server).
|
|
||||||
|
|
||||||
## Logging
|
|
||||||
|
|
||||||
Logging is provided by [Echo](https://echo.labstack.com/guide/customization/#logging) and is accessible within the _Echo_ instance, which is located in the `Web` field of the `Container`, or within any of the _context_ parameters, for example:
|
|
||||||
|
|
||||||
```go
|
|
||||||
func (c *Home) Get(ctx echo.Context) error {
|
|
||||||
ctx.Logger().Info("something happened")
|
|
||||||
|
|
||||||
if err := someOperation(); err != nil {
|
|
||||||
ctx.Logger().Errorf("the operation failed: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The logger can be swapped out for another, as long as it implements Echo's logging [interface](https://github.com/labstack/echo/blob/master/log.go). There are projects that provide this bridge for popular logging packages such as [zerolog](https://github.com/rs/zerolog).
|
|
||||||
|
|
||||||
### Request ID
|
|
||||||
|
|
||||||
By default, Echo's [request ID middleware](https://echo.labstack.com/middleware/request-id/) is enabled on the router but it only adds a request ID to the log entry for the HTTP request itself. Log entries that are created during the course of that request do not contain the request ID. `LogRequestID()` is custom middleware included which adds that request ID to all logs created throughout the request.
|
|
||||||
|
|
||||||
## Roadmap
|
|
||||||
|
|
||||||
Future work includes but is not limited to:
|
|
||||||
|
|
||||||
- Email verification
|
|
||||||
- Flexible pager templates
|
|
||||||
- Expanded HTMX examples
|
|
||||||
- Admin section
|
|
||||||
|
|
||||||
## Credits
|
|
||||||
|
|
||||||
Thank you to all of the following amazing projects for making this possible.
|
|
||||||
|
|
||||||
- [go](https://go.dev/)
|
|
||||||
- [echo](https://github.com/labstack/echo)
|
|
||||||
- [ent](https://github.com/ent/ent)
|
|
||||||
- [sprig](https://github.com/Masterminds/sprig)
|
|
||||||
- [goquery](https://github.com/PuerkitoBio/goquery)
|
|
||||||
- [validator](https://github.com/go-playground/validator)
|
|
||||||
- [go-redis](https://github.com/go-redis/redis)
|
|
||||||
- [gocache](https://github.com/eko/gocache)
|
|
||||||
- [sessions](https://github.com/gorilla/sessions)
|
|
||||||
- [pgx](https://github.com/jackc/pgx)
|
|
||||||
- [envdecode](https://github.com/joeshaw/envdecode)
|
|
||||||
- [testify](https://github.com/stretchr/testify)
|
|
||||||
- [htmx](https://github.com/bigskysoftware/htmx)
|
|
||||||
- [alpinejs](https://github.com/alpinejs/alpine)
|
|
||||||
- [bulma](https://github.com/jgthms/bulma)
|
|
||||||
- [docker](https://www.docker.com/)
|
|
||||||
- [postgresql](https://www.postgresql.org/)
|
|
||||||
- [redis](https://redis.io/)
|
|
||||||
|
|
|
||||||
63
cmd/admin/main.go
Normal file
63
cmd/admin/main.go
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/camzawacki/personal-site/pkg/log"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/services"
|
||||||
|
)
|
||||||
|
|
||||||
|
// main creates a new admin user with the email passed in via the flag.
|
||||||
|
func main() {
|
||||||
|
// Start a new container.
|
||||||
|
c := services.NewContainer()
|
||||||
|
defer func() {
|
||||||
|
// Gracefully shutdown all services.
|
||||||
|
if err := c.Shutdown(); err != nil {
|
||||||
|
log.Default().Error("shutdown failed", "error", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var email string
|
||||||
|
flag.StringVar(&email, "email", "", "email address for the admin user")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if len(email) == 0 {
|
||||||
|
invalid("email is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a password.
|
||||||
|
pw, err := c.Auth.RandomToken(10)
|
||||||
|
if err != nil {
|
||||||
|
invalid("failed to generate a random password")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the admin user.
|
||||||
|
err = c.ORM.User.
|
||||||
|
Create().
|
||||||
|
SetEmail(email).
|
||||||
|
SetName("Admin").
|
||||||
|
SetAdmin(true).
|
||||||
|
SetVerified(true).
|
||||||
|
SetPassword(pw).
|
||||||
|
Exec(context.Background())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
invalid(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("")
|
||||||
|
fmt.Println("-- ADMIN USER CREATED --")
|
||||||
|
fmt.Printf("Email: %s\n", email)
|
||||||
|
fmt.Printf("Password: %s\n", pw)
|
||||||
|
fmt.Println("----")
|
||||||
|
fmt.Println("")
|
||||||
|
}
|
||||||
|
|
||||||
|
func invalid(msg string) {
|
||||||
|
fmt.Printf("[ERROR] %s\n", msg)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
74
cmd/web/main.go
Normal file
74
cmd/web/main.go
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
|
||||||
|
"github.com/camzawacki/personal-site/pkg/handlers"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/log"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/services"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/tasks"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Start a new container.
|
||||||
|
c := services.NewContainer()
|
||||||
|
defer func() {
|
||||||
|
// Gracefully shutdown all services.
|
||||||
|
fatal("shutdown failed", c.Shutdown())
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Build the router.
|
||||||
|
if err := handlers.BuildRouter(c); err != nil {
|
||||||
|
fatal("failed to build the router", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register all task queues.
|
||||||
|
tasks.Register(c)
|
||||||
|
|
||||||
|
// Start the task runner to execute queued tasks.
|
||||||
|
c.Tasks.Start(context.Background())
|
||||||
|
|
||||||
|
// Start the server.
|
||||||
|
go func() {
|
||||||
|
srv := http.Server{
|
||||||
|
Addr: fmt.Sprintf("%s:%d", c.Config.HTTP.Hostname, c.Config.HTTP.Port),
|
||||||
|
Handler: c.Web,
|
||||||
|
ReadTimeout: c.Config.HTTP.ReadTimeout,
|
||||||
|
WriteTimeout: c.Config.HTTP.WriteTimeout,
|
||||||
|
IdleTimeout: c.Config.HTTP.IdleTimeout,
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Config.HTTP.TLS.Enabled {
|
||||||
|
certs, err := tls.LoadX509KeyPair(c.Config.HTTP.TLS.Certificate, c.Config.HTTP.TLS.Key)
|
||||||
|
fatal("cannot load TLS certificate", err)
|
||||||
|
|
||||||
|
srv.TLSConfig = &tls.Config{
|
||||||
|
Certificates: []tls.Certificate{certs},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.Web.StartServer(&srv); errors.Is(err, http.ErrServerClosed) {
|
||||||
|
fatal("shutting down the server", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Wait for interrupt signal to gracefully shut down the web server and task runner.
|
||||||
|
quit := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(quit, os.Interrupt)
|
||||||
|
signal.Notify(quit, os.Kill)
|
||||||
|
<-quit
|
||||||
|
}
|
||||||
|
|
||||||
|
// fatal logs an error and terminates the application, if the error is not nil.
|
||||||
|
func fatal(msg string, err error) {
|
||||||
|
if err != nil {
|
||||||
|
log.Default().Error(msg, "error", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
161
config/config.go
161
config/config.go
|
|
@ -2,115 +2,146 @@ package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/joeshaw/envdecode"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
type environment string
|
||||||
// TemplateDir stores the name of the directory that contains templates
|
|
||||||
TemplateDir = "templates"
|
|
||||||
|
|
||||||
// TemplateExt stores the extension used for the template files
|
|
||||||
TemplateExt = ".gohtml"
|
|
||||||
|
|
||||||
// StaticDir stores the name of the directory that will serve static files
|
|
||||||
StaticDir = "static"
|
|
||||||
|
|
||||||
// StaticPrefix stores the URL prefix used when serving static files
|
|
||||||
StaticPrefix = "files"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Environment string
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
EnvLocal Environment = "local"
|
// EnvLocal represents the local environment.
|
||||||
EnvTest Environment = "test"
|
EnvLocal environment = "local"
|
||||||
EnvDevelop Environment = "dev"
|
|
||||||
EnvStaging Environment = "staging"
|
// EnvTest represents the test environment.
|
||||||
EnvQA Environment = "qa"
|
EnvTest environment = "test"
|
||||||
EnvProduction Environment = "prod"
|
|
||||||
|
// EnvDevelopment represents the development environment.
|
||||||
|
EnvDevelopment environment = "dev"
|
||||||
|
|
||||||
|
// EnvStaging represents the staging environment.
|
||||||
|
EnvStaging environment = "staging"
|
||||||
|
|
||||||
|
// EnvQA represents the qa environment.
|
||||||
|
EnvQA environment = "qa"
|
||||||
|
|
||||||
|
// EnvProduction represents the production environment.
|
||||||
|
EnvProduction environment = "prod"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SwitchEnvironment sets the environment variable used to dictate which environment the application is
|
// SwitchEnvironment sets the environment variable used to dictate which environment the application is
|
||||||
// currently running in.
|
// currently running in.
|
||||||
// This must be called prior to loading the configuration in order for it to take effect.
|
// This must be called prior to loading the configuration in order for it to take effect.
|
||||||
func SwitchEnvironment(env Environment) {
|
func SwitchEnvironment(env environment) {
|
||||||
if err := os.Setenv("APP_ENVIRONMENT", string(env)); err != nil {
|
if err := os.Setenv("PAGODA_APP_ENVIRONMENT", string(env)); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// Config stores complete configuration
|
// Config stores complete configuration.
|
||||||
Config struct {
|
Config struct {
|
||||||
HTTP HTTPConfig
|
HTTP HTTPConfig
|
||||||
App AppConfig
|
App AppConfig
|
||||||
Cache CacheConfig
|
Cache CacheConfig
|
||||||
Database DatabaseConfig
|
Database DatabaseConfig
|
||||||
|
Files FilesConfig
|
||||||
|
Tasks TasksConfig
|
||||||
Mail MailConfig
|
Mail MailConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPConfig stores HTTP configuration
|
// HTTPConfig stores HTTP configuration.
|
||||||
HTTPConfig struct {
|
HTTPConfig struct {
|
||||||
Hostname string `env:"HTTP_HOSTNAME"`
|
Hostname string
|
||||||
Port uint16 `env:"HTTP_PORT,default=8000"`
|
Port uint16
|
||||||
ReadTimeout time.Duration `env:"HTTP_READ_TIMEOUT,default=5s"`
|
ReadTimeout time.Duration
|
||||||
WriteTimeout time.Duration `env:"HTTP_WRITE_TIMEOUT,default=10s"`
|
WriteTimeout time.Duration
|
||||||
IdleTimeout time.Duration `env:"HTTP_IDLE_TIMEOUT,default=2m"`
|
IdleTimeout time.Duration
|
||||||
TLS struct {
|
ShutdownTimeout time.Duration
|
||||||
Enabled bool `env:"HTTP_TLS_ENABLED,default=false"`
|
TLS struct {
|
||||||
Certificate string `env:"HTTP_TLS_CERTIFICATE"`
|
Enabled bool
|
||||||
Key string `env:"HTTP_TLS_KEY"`
|
Certificate string
|
||||||
|
Key string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppConfig stores application configuration
|
// AppConfig stores application configuration.
|
||||||
AppConfig struct {
|
AppConfig struct {
|
||||||
Name string `env:"APP_NAME,default=Goweb"`
|
Name string
|
||||||
Environment Environment `env:"APP_ENVIRONMENT,default=local"`
|
Host string
|
||||||
EncryptionKey string `env:"APP_ENCRYPTION_KEY,default=?E(G+KbPeShVmYq3t6w9z$C&F)J@McQf"`
|
Environment environment
|
||||||
Timeout time.Duration `env:"APP_TIMEOUT,default=20s"`
|
EncryptionKey string
|
||||||
|
Timeout time.Duration
|
||||||
PasswordToken struct {
|
PasswordToken struct {
|
||||||
Expiration time.Duration `env:"APP_PASSWORD_TOKEN_EXPIRATION,default=60m"`
|
Expiration time.Duration
|
||||||
Length int `env:"APP_PASSWORD_TOKEN_LENGTH,default=64"`
|
Length int
|
||||||
}
|
}
|
||||||
|
EmailVerificationTokenExpiration time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// CacheConfig stores the cache configuration
|
// CacheConfig stores the cache configuration.
|
||||||
CacheConfig struct {
|
CacheConfig struct {
|
||||||
Hostname string `env:"CACHE_HOSTNAME,default=localhost"`
|
Capacity int
|
||||||
Port uint16 `env:"CACHE_PORT,default=6379"`
|
|
||||||
Password string `env:"CACHE_PASSWORD"`
|
|
||||||
Expiration struct {
|
Expiration struct {
|
||||||
StaticFile time.Duration `env:"CACHE_EXPIRATION_STATIC_FILE,default=4380h"`
|
PublicFile time.Duration
|
||||||
Page time.Duration `env:"CACHE_EXPIRATION_PAGE,default=24h"`
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DatabaseConfig stores the database configuration
|
// DatabaseConfig stores the database configuration.
|
||||||
DatabaseConfig struct {
|
DatabaseConfig struct {
|
||||||
Hostname string `env:"DB_HOSTNAME,default=localhost"`
|
Driver string
|
||||||
Port uint16 `env:"DB_PORT,default=5432"`
|
Connection string
|
||||||
User string `env:"DB_USER,default=admin"`
|
TestConnection string
|
||||||
Password string `env:"DB_PASSWORD,default=admin"`
|
|
||||||
Database string `env:"DB_NAME,default=app"`
|
|
||||||
TestDatabase string `env:"DB_NAME_TEST,default=app_test"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MailConfig stores the mail configuration
|
// FilesConfig stores the file system configuration.
|
||||||
|
FilesConfig struct {
|
||||||
|
Directory string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TasksConfig stores the tasks configuration.
|
||||||
|
TasksConfig struct {
|
||||||
|
Goroutines int
|
||||||
|
ReleaseAfter time.Duration
|
||||||
|
CleanupInterval time.Duration
|
||||||
|
ShutdownTimeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// MailConfig stores the mail configuration.
|
||||||
MailConfig struct {
|
MailConfig struct {
|
||||||
Hostname string `env:"MAIL_HOSTNAME,default=localhost"`
|
Hostname string
|
||||||
Port uint16 `env:"MAIL_PORT,default=25"`
|
Port uint16
|
||||||
User string `env:"MAIL_USER,default=admin"`
|
User string
|
||||||
Password string `env:"MAIL_PASSWORD,default=admin"`
|
Password string
|
||||||
FromAddress string `env:"MAIL_FROM_ADDRESS,default=admin@localhost"`
|
FromAddress string
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetConfig loads and returns configuration
|
// GetConfig loads and returns configuration.
|
||||||
func GetConfig() (Config, error) {
|
func GetConfig() (Config, error) {
|
||||||
var cfg Config
|
var c Config
|
||||||
err := envdecode.StrictDecode(&cfg)
|
|
||||||
return cfg, err
|
// Load the config file.
|
||||||
|
viper.SetConfigName("config")
|
||||||
|
viper.SetConfigType("yaml")
|
||||||
|
viper.AddConfigPath(".")
|
||||||
|
viper.AddConfigPath("config")
|
||||||
|
viper.AddConfigPath("../config")
|
||||||
|
viper.AddConfigPath("../../config")
|
||||||
|
|
||||||
|
// Load env variables.
|
||||||
|
viper.SetEnvPrefix("pagoda")
|
||||||
|
viper.AutomaticEnv()
|
||||||
|
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||||
|
|
||||||
|
if err := viper.ReadInConfig(); err != nil {
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := viper.Unmarshal(&c); err != nil {
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
54
config/config.yaml
Normal file
54
config/config.yaml
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
http:
|
||||||
|
hostname: ""
|
||||||
|
port: 8000
|
||||||
|
readTimeout: "5s"
|
||||||
|
writeTimeout: "10s"
|
||||||
|
idleTimeout: "2m"
|
||||||
|
shutdownTimeout: "10s"
|
||||||
|
tls:
|
||||||
|
enabled: false
|
||||||
|
certificate: ""
|
||||||
|
key: ""
|
||||||
|
|
||||||
|
app:
|
||||||
|
name: "Pagoda"
|
||||||
|
# We manually set this rather than using the HTTP settings in order to build absolute URLs for users
|
||||||
|
# since it's likely your app's HTTP settings are not identical to what is exposed by your server.
|
||||||
|
host: "http://localhost:8000"
|
||||||
|
environment: "local"
|
||||||
|
# Change this on any live environments.
|
||||||
|
encryptionKey: "?E(G+KbPeShVmYq3t6w9z$C&F)J@McQf"
|
||||||
|
timeout: "20s"
|
||||||
|
passwordToken:
|
||||||
|
expiration: "60m"
|
||||||
|
length: 64
|
||||||
|
emailVerificationTokenExpiration: "12h"
|
||||||
|
|
||||||
|
cache:
|
||||||
|
capacity: 100000
|
||||||
|
expiration:
|
||||||
|
publicFile: "4380h"
|
||||||
|
|
||||||
|
database:
|
||||||
|
driver: "sqlite3"
|
||||||
|
connection: "dbs/main.db?_journal=WAL&_timeout=5000&_fk=true"
|
||||||
|
# $RAND will be automatically replaced with a random value.
|
||||||
|
# memdb is more robust for an in-memory database rather than :memory: because the latter has the potential
|
||||||
|
# retain data even after you close and re-open the connection.
|
||||||
|
testConnection: "file:/$RAND?vfs=memdb&_timeout=1000&_fk=true"
|
||||||
|
|
||||||
|
files:
|
||||||
|
directory: "uploads"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
goroutines: 1
|
||||||
|
releaseAfter: "15m"
|
||||||
|
cleanupInterval: "1h"
|
||||||
|
shutdownTimeout: "10s"
|
||||||
|
|
||||||
|
mail:
|
||||||
|
hostname: "localhost"
|
||||||
|
port: 25
|
||||||
|
user: "admin"
|
||||||
|
password: "admin"
|
||||||
|
fromAddress: "admin@localhost"
|
||||||
|
|
@ -11,7 +11,7 @@ func TestGetConfig(t *testing.T) {
|
||||||
_, err := GetConfig()
|
_, err := GetConfig()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var env Environment
|
var env environment
|
||||||
env = "abc"
|
env = "abc"
|
||||||
SwitchEnvironment(env)
|
SwitchEnvironment(env)
|
||||||
cfg, err := GetConfig()
|
cfg, err := GetConfig()
|
||||||
|
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
package context
|
|
||||||
|
|
||||||
const (
|
|
||||||
// AuthenticatedUserKey is the key value used to store the authenticated user in context
|
|
||||||
AuthenticatedUserKey = "auth_user"
|
|
||||||
|
|
||||||
// UserKey is the key value used to store a user in context
|
|
||||||
UserKey = "user"
|
|
||||||
|
|
||||||
// FormKey is the key value used to store a form in context
|
|
||||||
FormKey = "form"
|
|
||||||
|
|
||||||
// PasswordTokenKey is the key value used to store a password token in context
|
|
||||||
PasswordTokenKey = "password_token"
|
|
||||||
)
|
|
||||||
|
|
@ -1,163 +0,0 @@
|
||||||
package controller
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"goweb/middleware"
|
|
||||||
"goweb/services"
|
|
||||||
|
|
||||||
"github.com/eko/gocache/v2/marshaler"
|
|
||||||
|
|
||||||
"github.com/eko/gocache/v2/store"
|
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Controller provides base functionality and dependencies to routes.
|
|
||||||
// The proposed pattern is to embed a Controller in each individual route struct and to use
|
|
||||||
// the router to inject the container so your routes have access to the services within the container
|
|
||||||
type Controller struct {
|
|
||||||
// Container stores a services container which contains dependencies
|
|
||||||
Container *services.Container
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewController creates a new Controller
|
|
||||||
func NewController(c *services.Container) Controller {
|
|
||||||
return Controller{
|
|
||||||
Container: c,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RenderPage renders a Page as an HTTP response
|
|
||||||
func (c *Controller) RenderPage(ctx echo.Context, page Page) error {
|
|
||||||
var buf *bytes.Buffer
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// Page name is required
|
|
||||||
if page.Name == "" {
|
|
||||||
ctx.Logger().Error("page render failed due to missing name")
|
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use the app name in configuration if a value was not set
|
|
||||||
if page.AppName == "" {
|
|
||||||
page.AppName = c.Container.Config.App.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this is an HTMX non-boosted request which indicates that only partial
|
|
||||||
// content should be rendered
|
|
||||||
if page.HTMX.Request.Enabled && !page.HTMX.Request.Boosted {
|
|
||||||
// Parse and execute the templates only for the content portion of the page
|
|
||||||
// The templates used for this partial request will be:
|
|
||||||
// 1. The base htmx template which omits the layout and only includes the content template
|
|
||||||
// 2. The content template specified in Page.Name
|
|
||||||
// 3. All templates within the components directory
|
|
||||||
// Also included is the function map provided by the funcmap package
|
|
||||||
buf, err = c.Container.TemplateRenderer.ParseAndExecute(
|
|
||||||
"page:htmx",
|
|
||||||
page.Name,
|
|
||||||
"htmx",
|
|
||||||
[]string{
|
|
||||||
"htmx",
|
|
||||||
fmt.Sprintf("pages/%s", page.Name),
|
|
||||||
},
|
|
||||||
[]string{"components"},
|
|
||||||
page,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// Parse and execute the templates for the Page
|
|
||||||
// As mentioned in the documentation for the Page struct, the templates used for the page will be:
|
|
||||||
// 1. The layout/base template specified in Page.Layout
|
|
||||||
// 2. The content template specified in Page.Name
|
|
||||||
// 3. All templates within the components directory
|
|
||||||
// Also included is the function map provided by the funcmap package
|
|
||||||
buf, err = c.Container.TemplateRenderer.ParseAndExecute(
|
|
||||||
"page",
|
|
||||||
page.Name,
|
|
||||||
page.Layout,
|
|
||||||
[]string{
|
|
||||||
fmt.Sprintf("layouts/%s", page.Layout),
|
|
||||||
fmt.Sprintf("pages/%s", page.Name),
|
|
||||||
},
|
|
||||||
[]string{"components"},
|
|
||||||
page,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ctx.Logger().Errorf("failed to parse and execute templates: %v", err)
|
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the status code
|
|
||||||
ctx.Response().Status = page.StatusCode
|
|
||||||
|
|
||||||
// Set any headers
|
|
||||||
for k, v := range page.Headers {
|
|
||||||
ctx.Response().Header().Set(k, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply the HTMX response, if one
|
|
||||||
if page.HTMX.Response != nil {
|
|
||||||
page.HTMX.Response.Apply(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cache this page, if caching was enabled
|
|
||||||
c.cachePage(ctx, page, buf)
|
|
||||||
|
|
||||||
return ctx.HTMLBlob(ctx.Response().Status, buf.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
// cachePage caches the HTML for a given Page if the Page has caching enabled
|
|
||||||
func (c *Controller) cachePage(ctx echo.Context, page Page, html *bytes.Buffer) {
|
|
||||||
if !page.Cache.Enabled {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no expiration time was provided, default to the configuration value
|
|
||||||
if page.Cache.Expiration == 0 {
|
|
||||||
page.Cache.Expiration = c.Container.Config.Cache.Expiration.Page
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the headers
|
|
||||||
headers := make(map[string]string)
|
|
||||||
for k, v := range ctx.Response().Header() {
|
|
||||||
headers[k] = v[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// The request URL is used as the cache key so the middleware can serve the
|
|
||||||
// cached page on matching requests
|
|
||||||
key := ctx.Request().URL.String()
|
|
||||||
opts := &store.Options{
|
|
||||||
Expiration: page.Cache.Expiration,
|
|
||||||
Tags: page.Cache.Tags,
|
|
||||||
}
|
|
||||||
cp := middleware.CachedPage{
|
|
||||||
URL: key,
|
|
||||||
HTML: html.Bytes(),
|
|
||||||
Headers: headers,
|
|
||||||
StatusCode: ctx.Response().Status,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := marshaler.New(c.Container.Cache).Set(ctx.Request().Context(), key, cp, opts)
|
|
||||||
if err != nil {
|
|
||||||
ctx.Logger().Errorf("failed to cache page: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Logger().Infof("cached page")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Redirect redirects to a given route name with optional route parameters
|
|
||||||
func (c *Controller) Redirect(ctx echo.Context, route string, routeParams ...interface{}) error {
|
|
||||||
url := ctx.Echo().Reverse(route, routeParams)
|
|
||||||
return ctx.Redirect(http.StatusFound, url)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fail is a helper to fail a request by returning a 500 error and logging the error
|
|
||||||
func (c *Controller) Fail(ctx echo.Context, err error, log string) error {
|
|
||||||
ctx.Logger().Errorf("%s: %v", log, err)
|
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
@ -1,177 +0,0 @@
|
||||||
package controller
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"goweb/config"
|
|
||||||
"goweb/htmx"
|
|
||||||
"goweb/middleware"
|
|
||||||
"goweb/services"
|
|
||||||
"goweb/tests"
|
|
||||||
|
|
||||||
"github.com/eko/gocache/v2/store"
|
|
||||||
|
|
||||||
"github.com/eko/gocache/v2/marshaler"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
c *services.Container
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
|
||||||
// Set the environment to test
|
|
||||||
config.SwitchEnvironment(config.EnvTest)
|
|
||||||
|
|
||||||
// Create a new container
|
|
||||||
c = services.NewContainer()
|
|
||||||
defer func() {
|
|
||||||
if err := c.Shutdown(); err != nil {
|
|
||||||
c.Web.Logger.Fatal(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Run tests
|
|
||||||
exitVal := m.Run()
|
|
||||||
os.Exit(exitVal)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestController_Redirect(t *testing.T) {
|
|
||||||
ctx, _ := tests.NewContext(c.Web, "/abc")
|
|
||||||
ctr := NewController(c)
|
|
||||||
err := ctr.Redirect(ctx, "home")
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, "", ctx.Response().Header().Get(echo.HeaderLocation))
|
|
||||||
assert.Equal(t, http.StatusFound, ctx.Response().Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestController_RenderPage(t *testing.T) {
|
|
||||||
setup := func() (echo.Context, *httptest.ResponseRecorder, Controller, Page) {
|
|
||||||
ctx, rec := tests.NewContext(c.Web, "/test/TestController_RenderPage")
|
|
||||||
tests.InitSession(ctx)
|
|
||||||
ctr := NewController(c)
|
|
||||||
|
|
||||||
p := NewPage(ctx)
|
|
||||||
p.Name = "home"
|
|
||||||
p.Layout = "main"
|
|
||||||
p.Cache.Enabled = false
|
|
||||||
p.Headers["A"] = "b"
|
|
||||||
p.Headers["C"] = "d"
|
|
||||||
p.StatusCode = http.StatusCreated
|
|
||||||
return ctx, rec, ctr, p
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("missing name", func(t *testing.T) {
|
|
||||||
// Rendering should fail if the Page has no name
|
|
||||||
ctx, _, ctr, p := setup()
|
|
||||||
p.Name = ""
|
|
||||||
err := ctr.RenderPage(ctx, p)
|
|
||||||
assert.Error(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("no page cache", func(t *testing.T) {
|
|
||||||
ctx, _, ctr, p := setup()
|
|
||||||
err := ctr.RenderPage(ctx, p)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Check status code and headers
|
|
||||||
assert.Equal(t, http.StatusCreated, ctx.Response().Status)
|
|
||||||
for k, v := range p.Headers {
|
|
||||||
assert.Equal(t, v, ctx.Response().Header().Get(k))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the template cache
|
|
||||||
parsed, err := c.TemplateRenderer.Load("page", p.Name)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
// Check that all expected templates were parsed.
|
|
||||||
// This includes the name, layout and all components
|
|
||||||
expectedTemplates := make(map[string]bool)
|
|
||||||
expectedTemplates[p.Name+config.TemplateExt] = true
|
|
||||||
expectedTemplates[p.Layout+config.TemplateExt] = true
|
|
||||||
components, err := ioutil.ReadDir(c.TemplateRenderer.GetTemplatesPath() + "/components")
|
|
||||||
require.NoError(t, err)
|
|
||||||
for _, f := range components {
|
|
||||||
expectedTemplates[f.Name()] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range parsed.Templates() {
|
|
||||||
delete(expectedTemplates, v.Name())
|
|
||||||
}
|
|
||||||
assert.Empty(t, expectedTemplates)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("htmx rendering", func(t *testing.T) {
|
|
||||||
ctx, _, ctr, p := setup()
|
|
||||||
p.HTMX.Request.Enabled = true
|
|
||||||
p.HTMX.Response = &htmx.Response{
|
|
||||||
Trigger: "trigger",
|
|
||||||
}
|
|
||||||
err := ctr.RenderPage(ctx, p)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Check HTMX header
|
|
||||||
assert.Equal(t, "trigger", ctx.Response().Header().Get(htmx.HeaderTrigger))
|
|
||||||
|
|
||||||
// Check the template cache
|
|
||||||
parsed, err := c.TemplateRenderer.Load("page:htmx", p.Name)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
// Check that all expected templates were parsed.
|
|
||||||
// This includes the name, htmx and all components
|
|
||||||
expectedTemplates := make(map[string]bool)
|
|
||||||
expectedTemplates[p.Name+config.TemplateExt] = true
|
|
||||||
expectedTemplates["htmx"+config.TemplateExt] = true
|
|
||||||
components, err := ioutil.ReadDir(c.TemplateRenderer.GetTemplatesPath() + "/components")
|
|
||||||
require.NoError(t, err)
|
|
||||||
for _, f := range components {
|
|
||||||
expectedTemplates[f.Name()] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range parsed.Templates() {
|
|
||||||
delete(expectedTemplates, v.Name())
|
|
||||||
}
|
|
||||||
assert.Empty(t, expectedTemplates)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("page cache", func(t *testing.T) {
|
|
||||||
ctx, rec, ctr, p := setup()
|
|
||||||
p.Cache.Enabled = true
|
|
||||||
p.Cache.Tags = []string{"tag1"}
|
|
||||||
err := ctr.RenderPage(ctx, p)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Fetch from the cache
|
|
||||||
res, err := marshaler.New(c.Cache).
|
|
||||||
Get(context.Background(), p.URL, new(middleware.CachedPage))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Compare the cached page
|
|
||||||
cp, ok := res.(*middleware.CachedPage)
|
|
||||||
require.True(t, ok)
|
|
||||||
assert.Equal(t, p.URL, cp.URL)
|
|
||||||
assert.Equal(t, p.Headers, cp.Headers)
|
|
||||||
assert.Equal(t, p.StatusCode, cp.StatusCode)
|
|
||||||
assert.Equal(t, rec.Body.Bytes(), cp.HTML)
|
|
||||||
|
|
||||||
// Clear the tag
|
|
||||||
err = c.Cache.Invalidate(context.Background(), store.InvalidateOptions{
|
|
||||||
Tags: []string{p.Cache.Tags[0]},
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Refetch from the cache and expect no results
|
|
||||||
_, err = marshaler.New(c.Cache).
|
|
||||||
Get(context.Background(), p.URL, new(middleware.CachedPage))
|
|
||||||
assert.Error(t, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,104 +0,0 @@
|
||||||
package controller
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/go-playground/validator/v10"
|
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FormSubmission represents the state of the submission of a form, not including the form itself
|
|
||||||
type FormSubmission struct {
|
|
||||||
// IsSubmitted indicates if the form has been submitted
|
|
||||||
IsSubmitted bool
|
|
||||||
|
|
||||||
// Errors stores a slice of error message strings keyed by form struct field name
|
|
||||||
Errors map[string][]string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process processes a submission for a form
|
|
||||||
func (f *FormSubmission) Process(ctx echo.Context, form interface{}) error {
|
|
||||||
f.Errors = make(map[string][]string)
|
|
||||||
f.IsSubmitted = true
|
|
||||||
|
|
||||||
// Validate the form
|
|
||||||
if err := ctx.Validate(form); err != nil {
|
|
||||||
f.setErrorMessages(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasErrors indicates if the submission has any validation errors
|
|
||||||
func (f FormSubmission) HasErrors() bool {
|
|
||||||
if f.Errors == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return len(f.Errors) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// FieldHasErrors indicates if a given field on the form has any validation errors
|
|
||||||
func (f FormSubmission) FieldHasErrors(fieldName string) bool {
|
|
||||||
return len(f.GetFieldErrors(fieldName)) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFieldError sets an error message for a given field name
|
|
||||||
func (f *FormSubmission) SetFieldError(fieldName string, message string) {
|
|
||||||
if f.Errors == nil {
|
|
||||||
f.Errors = make(map[string][]string)
|
|
||||||
}
|
|
||||||
f.Errors[fieldName] = append(f.Errors[fieldName], message)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFieldErrors gets the errors for a given field name
|
|
||||||
func (f FormSubmission) GetFieldErrors(fieldName string) []string {
|
|
||||||
if f.Errors == nil {
|
|
||||||
return []string{}
|
|
||||||
}
|
|
||||||
return f.Errors[fieldName]
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFieldStatusClass returns an HTML class based on the status of the field
|
|
||||||
func (f FormSubmission) GetFieldStatusClass(fieldName string) string {
|
|
||||||
if f.IsSubmitted {
|
|
||||||
if f.FieldHasErrors(fieldName) {
|
|
||||||
return "is-danger"
|
|
||||||
}
|
|
||||||
return "is-success"
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsDone indicates if the submission is considered done which is when it has been submitted
|
|
||||||
// and there are no errors.
|
|
||||||
func (f FormSubmission) IsDone() bool {
|
|
||||||
return f.IsSubmitted && !f.HasErrors()
|
|
||||||
}
|
|
||||||
|
|
||||||
// setErrorMessages sets errors messages on the submission for all fields that failed validation
|
|
||||||
func (f *FormSubmission) setErrorMessages(err error) {
|
|
||||||
// Only this is supported right now
|
|
||||||
ves, ok := err.(validator.ValidationErrors)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ve := range ves {
|
|
||||||
var message string
|
|
||||||
|
|
||||||
// Provide better error messages depending on the failed validation tag
|
|
||||||
// This should be expanded as you use additional tags in your validation
|
|
||||||
switch ve.Tag() {
|
|
||||||
case "required":
|
|
||||||
message = "This field is required."
|
|
||||||
case "email":
|
|
||||||
message = "Enter a valid email address."
|
|
||||||
case "eqfield":
|
|
||||||
message = "Does not match."
|
|
||||||
default:
|
|
||||||
message = "Invalid value."
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the error
|
|
||||||
f.SetFieldError(ve.Field(), message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
package controller
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"goweb/tests"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFormSubmission(t *testing.T) {
|
|
||||||
type formTest struct {
|
|
||||||
Name string `validate:"required"`
|
|
||||||
Email string `validate:"required,email"`
|
|
||||||
Submission FormSubmission
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, _ := tests.NewContext(c.Web, "/")
|
|
||||||
form := formTest{
|
|
||||||
Name: "",
|
|
||||||
Email: "a@a.com",
|
|
||||||
}
|
|
||||||
err := form.Submission.Process(ctx, form)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
assert.True(t, form.Submission.HasErrors())
|
|
||||||
assert.True(t, form.Submission.FieldHasErrors("Name"))
|
|
||||||
assert.False(t, form.Submission.FieldHasErrors("Email"))
|
|
||||||
require.Len(t, form.Submission.GetFieldErrors("Name"), 1)
|
|
||||||
assert.Len(t, form.Submission.GetFieldErrors("Email"), 0)
|
|
||||||
assert.Equal(t, "This field is required.", form.Submission.GetFieldErrors("Name")[0])
|
|
||||||
assert.Equal(t, "is-danger", form.Submission.GetFieldStatusClass("Name"))
|
|
||||||
assert.Equal(t, "is-success", form.Submission.GetFieldStatusClass("Email"))
|
|
||||||
assert.False(t, form.Submission.IsDone())
|
|
||||||
}
|
|
||||||
|
|
@ -1,161 +0,0 @@
|
||||||
package controller
|
|
||||||
|
|
||||||
import (
|
|
||||||
"html/template"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"goweb/context"
|
|
||||||
"goweb/ent"
|
|
||||||
"goweb/htmx"
|
|
||||||
"goweb/msg"
|
|
||||||
|
|
||||||
echomw "github.com/labstack/echo/v4/middleware"
|
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Page consists of all data that will be used to render a page response for a given controller.
|
|
||||||
// While it's not required for a controller to render a Page on a route, this is the common data
|
|
||||||
// object that will be passed to the templates, making it easy for all controllers to share
|
|
||||||
// functionality both on the back and frontend. The Page can be expanded to include anything else
|
|
||||||
// your app wants to support.
|
|
||||||
// Methods on this page also then become available in the templates, which can be more useful than
|
|
||||||
// the funcmap if your methods require data stored in the page, such as the context.
|
|
||||||
type Page struct {
|
|
||||||
// AppName stores the name of the application.
|
|
||||||
// If omitted, the configuration value will be used.
|
|
||||||
AppName string
|
|
||||||
|
|
||||||
// Title stores the title of the page
|
|
||||||
Title string
|
|
||||||
|
|
||||||
// Context stores the request context
|
|
||||||
Context echo.Context
|
|
||||||
|
|
||||||
// ToURL is a function to convert a route name and optional route parameters to a URL
|
|
||||||
ToURL func(name string, params ...interface{}) string
|
|
||||||
|
|
||||||
// Path stores the path of the current request
|
|
||||||
Path string
|
|
||||||
|
|
||||||
// URL stores the URL of the current request
|
|
||||||
URL string
|
|
||||||
|
|
||||||
// Data stores whatever additional data that needs to be passed to the templates.
|
|
||||||
// This is what the controller uses to pass the content of the page.
|
|
||||||
Data interface{}
|
|
||||||
|
|
||||||
// Form stores a struct that represents a form on the page.
|
|
||||||
// This should be a struct with fields for each form field, using both "form" and "validate" tags
|
|
||||||
// It should also contain a Submission field of type FormSubmission if you wish to have validation
|
|
||||||
// messagesa and markup presented to the user
|
|
||||||
Form interface{}
|
|
||||||
|
|
||||||
// Layout stores the name of the layout base template file which will be used when the page is rendered.
|
|
||||||
// This should match a template file located within the layouts directory inside the templates directory.
|
|
||||||
// The template extension should not be included in this value.
|
|
||||||
Layout string
|
|
||||||
|
|
||||||
// Name stores the name of the page as well as the name of the template file which will be used to render
|
|
||||||
// the content portion of the layout template.
|
|
||||||
// This should match a template file located within the pages directory inside the templates directory.
|
|
||||||
// The template extension should not be included in this value.
|
|
||||||
Name string
|
|
||||||
|
|
||||||
// IsHome stores whether the requested page is the home page or not
|
|
||||||
IsHome bool
|
|
||||||
|
|
||||||
// IsAuth stores whether or not the user is authenticated
|
|
||||||
IsAuth bool
|
|
||||||
|
|
||||||
// AuthUser stores the authenticated user
|
|
||||||
AuthUser *ent.User
|
|
||||||
|
|
||||||
// StatusCode stores the HTTP status code that will be returned
|
|
||||||
StatusCode int
|
|
||||||
|
|
||||||
// Metatags stores metatag values
|
|
||||||
Metatags struct {
|
|
||||||
// Description stores the description metatag value
|
|
||||||
Description string
|
|
||||||
|
|
||||||
// Keywords stores the keywords metatag values
|
|
||||||
Keywords []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pager stores a pager which can be used to page lists of results
|
|
||||||
Pager Pager
|
|
||||||
|
|
||||||
// CSRF stores the CSRF token for the given request.
|
|
||||||
// This will only be populated if the CSRF middleware is in effect for the given request.
|
|
||||||
// If this is populated, all forms must include this value otherwise the requests will be rejected.
|
|
||||||
CSRF string
|
|
||||||
|
|
||||||
// Headers stores a list of HTTP headers and values to be set on the response
|
|
||||||
Headers map[string]string
|
|
||||||
|
|
||||||
// RequestID stores the ID of the given request.
|
|
||||||
// This will only be populated if the request ID middleware is in effect for the given request.
|
|
||||||
RequestID string
|
|
||||||
|
|
||||||
HTMX struct {
|
|
||||||
Request htmx.Request
|
|
||||||
Response *htmx.Response
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cache stores values for caching the response of this page
|
|
||||||
Cache struct {
|
|
||||||
// Enabled dictates if the response of this page should be cached.
|
|
||||||
// Cached responses are served via middleware.
|
|
||||||
Enabled bool
|
|
||||||
|
|
||||||
// Expiration stores the amount of time that the cache entry should live for before expiring.
|
|
||||||
// If omitted, the configuration value will be used.
|
|
||||||
Expiration time.Duration
|
|
||||||
|
|
||||||
// Tags stores a list of tags to apply to the cache entry.
|
|
||||||
// These are useful when invalidating cache for dynamic events such as entity operations.
|
|
||||||
Tags []string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPage creates and initiatizes a new Page for a given request context
|
|
||||||
func NewPage(ctx echo.Context) Page {
|
|
||||||
p := Page{
|
|
||||||
Context: ctx,
|
|
||||||
ToURL: ctx.Echo().Reverse,
|
|
||||||
Path: ctx.Request().URL.Path,
|
|
||||||
URL: ctx.Request().URL.String(),
|
|
||||||
StatusCode: http.StatusOK,
|
|
||||||
Pager: NewPager(ctx, DefaultItemsPerPage),
|
|
||||||
Headers: make(map[string]string),
|
|
||||||
RequestID: ctx.Response().Header().Get(echo.HeaderXRequestID),
|
|
||||||
}
|
|
||||||
|
|
||||||
p.IsHome = p.Path == "/"
|
|
||||||
|
|
||||||
if csrf := ctx.Get(echomw.DefaultCSRFConfig.ContextKey); csrf != nil {
|
|
||||||
p.CSRF = csrf.(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
if u := ctx.Get(context.AuthenticatedUserKey); u != nil {
|
|
||||||
p.IsAuth = true
|
|
||||||
p.AuthUser = u.(*ent.User)
|
|
||||||
}
|
|
||||||
|
|
||||||
p.HTMX.Request = htmx.GetRequest(ctx)
|
|
||||||
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMessages gets all flash messages for a given type.
|
|
||||||
// This allows for easy access to flash messages from the templates.
|
|
||||||
func (p Page) GetMessages(typ msg.Type) []template.HTML {
|
|
||||||
strs := msg.Get(p.Context, typ)
|
|
||||||
ret := make([]template.HTML, len(strs))
|
|
||||||
for k, v := range strs {
|
|
||||||
ret[k] = template.HTML(v)
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
package controller
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"goweb/context"
|
|
||||||
"goweb/msg"
|
|
||||||
"goweb/tests"
|
|
||||||
|
|
||||||
echomw "github.com/labstack/echo/v4/middleware"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNewPage(t *testing.T) {
|
|
||||||
ctx, _ := tests.NewContext(c.Web, "/")
|
|
||||||
p := NewPage(ctx)
|
|
||||||
assert.Same(t, ctx, p.Context)
|
|
||||||
assert.NotNil(t, p.ToURL)
|
|
||||||
assert.Equal(t, "/", p.Path)
|
|
||||||
assert.Equal(t, "/", p.URL)
|
|
||||||
assert.Equal(t, http.StatusOK, p.StatusCode)
|
|
||||||
assert.Equal(t, NewPager(ctx, DefaultItemsPerPage), p.Pager)
|
|
||||||
assert.Empty(t, p.Headers)
|
|
||||||
assert.True(t, p.IsHome)
|
|
||||||
assert.False(t, p.IsAuth)
|
|
||||||
assert.Empty(t, p.CSRF)
|
|
||||||
assert.Empty(t, p.RequestID)
|
|
||||||
assert.False(t, p.Cache.Enabled)
|
|
||||||
|
|
||||||
ctx, _ = tests.NewContext(c.Web, "/abc?def=123")
|
|
||||||
usr, err := tests.CreateUser(c.ORM)
|
|
||||||
require.NoError(t, err)
|
|
||||||
ctx.Set(context.AuthenticatedUserKey, usr)
|
|
||||||
ctx.Set(echomw.DefaultCSRFConfig.ContextKey, "csrf")
|
|
||||||
p = NewPage(ctx)
|
|
||||||
assert.Equal(t, "/abc", p.Path)
|
|
||||||
assert.Equal(t, "/abc?def=123", p.URL)
|
|
||||||
assert.False(t, p.IsHome)
|
|
||||||
assert.True(t, p.IsAuth)
|
|
||||||
assert.Equal(t, usr, p.AuthUser)
|
|
||||||
assert.Equal(t, "csrf", p.CSRF)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPage_GetMessages(t *testing.T) {
|
|
||||||
ctx, _ := tests.NewContext(c.Web, "/")
|
|
||||||
tests.InitSession(ctx)
|
|
||||||
p := NewPage(ctx)
|
|
||||||
|
|
||||||
// Set messages
|
|
||||||
msgTests := make(map[msg.Type][]string)
|
|
||||||
msgTests[msg.TypeWarning] = []string{
|
|
||||||
"abc",
|
|
||||||
"def",
|
|
||||||
}
|
|
||||||
msgTests[msg.TypeInfo] = []string{
|
|
||||||
"123",
|
|
||||||
"456",
|
|
||||||
}
|
|
||||||
for typ, values := range msgTests {
|
|
||||||
for _, value := range values {
|
|
||||||
msg.Set(ctx, typ, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the messages
|
|
||||||
for typ, values := range msgTests {
|
|
||||||
msgs := p.GetMessages(typ)
|
|
||||||
|
|
||||||
for i, message := range msgs {
|
|
||||||
assert.Equal(t, values[i], string(message))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +1,24 @@
|
||||||
version: "3"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
cache:
|
app:
|
||||||
image: "redis:alpine"
|
build: .
|
||||||
ports:
|
container_name: personal-site
|
||||||
- "6379:6379"
|
restart: unless-stopped
|
||||||
db:
|
|
||||||
image: postgres:alpine
|
|
||||||
ports:
|
|
||||||
- "5432:5432"
|
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_USER=admin
|
- PAGODA_APP_ENVIRONMENT=production
|
||||||
- POSTGRES_PASSWORD=admin
|
- PAGODA_APP_HOST=camzalewski.com
|
||||||
- POSTGRES_DB=app
|
- PAGODA_HTTP_HOSTNAME=0.0.0.0
|
||||||
|
- PAGODA_HTTP_PORT=8000
|
||||||
|
- PAGODA_APP_ENCRYPTIONKEY=${ENCRYPTION_KEY}
|
||||||
|
volumes:
|
||||||
|
- sqlite_data:/app/dbs
|
||||||
|
- uploads:/app/uploads
|
||||||
|
networks:
|
||||||
|
- web
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
sqlite_data:
|
||||||
|
uploads:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
web:
|
||||||
|
external: true
|
||||||
|
|
|
||||||
97
ent/admin/extension.go
Normal file
97
ent/admin/extension.go
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"entgo.io/ent/entc"
|
||||||
|
"entgo.io/ent/entc/gen"
|
||||||
|
"entgo.io/ent/schema/field"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
//go:embed templates
|
||||||
|
templateDir embed.FS
|
||||||
|
)
|
||||||
|
|
||||||
|
// Extension is the Ent extension that generates code to support the entity admin panel.
|
||||||
|
type Extension struct {
|
||||||
|
entc.DefaultExtension
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Extension) Templates() []*gen.Template {
|
||||||
|
return []*gen.Template{
|
||||||
|
gen.MustParse(
|
||||||
|
gen.NewTemplate("admin").
|
||||||
|
Funcs(template.FuncMap{
|
||||||
|
"fieldName": fieldName,
|
||||||
|
"fieldLabel": FieldLabel,
|
||||||
|
"fieldIsPointer": fieldIsPointer,
|
||||||
|
}).
|
||||||
|
ParseFS(templateDir, "templates/*tmpl"),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fieldName provides a struct field name from an entity field name (ie, user_id -> UserID).
|
||||||
|
func fieldName(name string) string {
|
||||||
|
if len(name) == 0 {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(name, "_")
|
||||||
|
for i := 0; i < len(parts); i++ {
|
||||||
|
if parts[i] == "id" {
|
||||||
|
parts[i] = "ID"
|
||||||
|
} else {
|
||||||
|
parts[i] = upperFirst(parts[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(parts, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// FieldLabel provides a label for an entity field name (ie, user_id -> User ID).
|
||||||
|
func FieldLabel(name string) string {
|
||||||
|
if len(name) == 0 {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(name, "_")
|
||||||
|
for i := 0; i < len(parts); i++ {
|
||||||
|
if parts[i] == "id" {
|
||||||
|
parts[i] = "ID"
|
||||||
|
}
|
||||||
|
if i == 0 {
|
||||||
|
parts[i] = upperFirst(parts[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(parts, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// fieldIsPointer determines if a given entity field should be a pointer on the struct.
|
||||||
|
func fieldIsPointer(f *gen.Field) bool {
|
||||||
|
switch {
|
||||||
|
case f.Type.Type == field.TypeBool:
|
||||||
|
return false
|
||||||
|
case f.Optional,
|
||||||
|
f.Default,
|
||||||
|
f.Sensitive(),
|
||||||
|
f.Nillable:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// upperFirst uppercases the first character of a given string.
|
||||||
|
func upperFirst(s string) string {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
out := []rune(s)
|
||||||
|
out[0] = unicode.ToUpper(out[0])
|
||||||
|
return string(out)
|
||||||
|
}
|
||||||
319
ent/admin/handler.go
Normal file
319
ent/admin/handler.go
Normal file
|
|
@ -0,0 +1,319 @@
|
||||||
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"entgo.io/ent/dialect/sql"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
|
||||||
|
"github.com/camzawacki/personal-site/ent"
|
||||||
|
"github.com/camzawacki/personal-site/ent/passwordtoken"
|
||||||
|
"github.com/camzawacki/personal-site/ent/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
const dateTimeFormat = "2006-01-02T15:04:05"
|
||||||
|
const dateTimeFormatNoSeconds = "2006-01-02T15:04"
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
client *ent.Client
|
||||||
|
Config HandlerConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHandler(client *ent.Client, cfg HandlerConfig) *Handler {
|
||||||
|
return &Handler{
|
||||||
|
client: client,
|
||||||
|
Config: cfg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) Create(ctx echo.Context, entityType EntityType) error {
|
||||||
|
switch entityType.(type) {
|
||||||
|
case *PasswordToken:
|
||||||
|
return h.PasswordTokenCreate(ctx)
|
||||||
|
case *User:
|
||||||
|
return h.UserCreate(ctx)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported entity type: %s", entityType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) Get(ctx echo.Context, entityType EntityType, id int) (url.Values, error) {
|
||||||
|
switch entityType.(type) {
|
||||||
|
case *PasswordToken:
|
||||||
|
return h.PasswordTokenGet(ctx, id)
|
||||||
|
case *User:
|
||||||
|
return h.UserGet(ctx, id)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported entity type: %s", entityType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) Delete(ctx echo.Context, entityType EntityType, id int) error {
|
||||||
|
switch entityType.(type) {
|
||||||
|
case *PasswordToken:
|
||||||
|
return h.PasswordTokenDelete(ctx, id)
|
||||||
|
case *User:
|
||||||
|
return h.UserDelete(ctx, id)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported entity type: %s", entityType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) Update(ctx echo.Context, entityType EntityType, id int) error {
|
||||||
|
switch entityType.(type) {
|
||||||
|
case *PasswordToken:
|
||||||
|
return h.PasswordTokenUpdate(ctx, id)
|
||||||
|
case *User:
|
||||||
|
return h.UserUpdate(ctx, id)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported entity type: %s", entityType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) List(ctx echo.Context, entityType EntityType) (*EntityList, error) {
|
||||||
|
switch entityType.(type) {
|
||||||
|
case *PasswordToken:
|
||||||
|
return h.PasswordTokenList(ctx)
|
||||||
|
case *User:
|
||||||
|
return h.UserList(ctx)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported entity type: %s", entityType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) PasswordTokenCreate(ctx echo.Context) error {
|
||||||
|
var payload PasswordToken
|
||||||
|
if err := h.bind(ctx, &payload); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
op := h.client.PasswordToken.Create()
|
||||||
|
if payload.Token != nil {
|
||||||
|
op.SetToken(*payload.Token)
|
||||||
|
}
|
||||||
|
op.SetUserID(payload.UserID)
|
||||||
|
if payload.CreatedAt != nil {
|
||||||
|
op.SetCreatedAt(*payload.CreatedAt)
|
||||||
|
}
|
||||||
|
_, err := op.Save(ctx.Request().Context())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) PasswordTokenUpdate(ctx echo.Context, id int) error {
|
||||||
|
entity, err := h.client.PasswordToken.Get(ctx.Request().Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload PasswordToken
|
||||||
|
if err = h.bind(ctx, &payload); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
op := entity.Update()
|
||||||
|
if payload.Token != nil {
|
||||||
|
op.SetToken(*payload.Token)
|
||||||
|
}
|
||||||
|
op.SetUserID(payload.UserID)
|
||||||
|
if payload.CreatedAt == nil {
|
||||||
|
var empty time.Time
|
||||||
|
op.SetCreatedAt(empty)
|
||||||
|
} else {
|
||||||
|
op.SetCreatedAt(*payload.CreatedAt)
|
||||||
|
}
|
||||||
|
_, err = op.Save(ctx.Request().Context())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) PasswordTokenDelete(ctx echo.Context, id int) error {
|
||||||
|
return h.client.PasswordToken.DeleteOneID(id).
|
||||||
|
Exec(ctx.Request().Context())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) PasswordTokenList(ctx echo.Context) (*EntityList, error) {
|
||||||
|
page, offset := h.getPageAndOffset(ctx)
|
||||||
|
res, err := h.client.PasswordToken.
|
||||||
|
Query().
|
||||||
|
Limit(h.Config.ItemsPerPage + 1).
|
||||||
|
Offset(offset).
|
||||||
|
Order(passwordtoken.ByID(sql.OrderDesc())).
|
||||||
|
All(ctx.Request().Context())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
list := &EntityList{
|
||||||
|
Columns: []string{
|
||||||
|
"User ID",
|
||||||
|
"Created at",
|
||||||
|
},
|
||||||
|
Entities: make([]EntityValues, 0, len(res)),
|
||||||
|
Page: page,
|
||||||
|
HasNextPage: len(res) > h.Config.ItemsPerPage,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i <= len(res)-1; i++ {
|
||||||
|
list.Entities = append(list.Entities, EntityValues{
|
||||||
|
ID: res[i].ID,
|
||||||
|
Values: []string{
|
||||||
|
fmt.Sprint(res[i].UserID),
|
||||||
|
res[i].CreatedAt.Format(h.Config.TimeFormat),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return list, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) PasswordTokenGet(ctx echo.Context, id int) (url.Values, error) {
|
||||||
|
entity, err := h.client.PasswordToken.Get(ctx.Request().Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
v := url.Values{}
|
||||||
|
v.Set("user_id", fmt.Sprint(entity.UserID))
|
||||||
|
v.Set("created_at", entity.CreatedAt.Format(dateTimeFormat))
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) UserCreate(ctx echo.Context) error {
|
||||||
|
var payload User
|
||||||
|
if err := h.bind(ctx, &payload); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
op := h.client.User.Create()
|
||||||
|
op.SetName(payload.Name)
|
||||||
|
op.SetEmail(payload.Email)
|
||||||
|
if payload.Password != nil {
|
||||||
|
op.SetPassword(*payload.Password)
|
||||||
|
}
|
||||||
|
op.SetVerified(payload.Verified)
|
||||||
|
op.SetAdmin(payload.Admin)
|
||||||
|
if payload.CreatedAt != nil {
|
||||||
|
op.SetCreatedAt(*payload.CreatedAt)
|
||||||
|
}
|
||||||
|
_, err := op.Save(ctx.Request().Context())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) UserUpdate(ctx echo.Context, id int) error {
|
||||||
|
entity, err := h.client.User.Get(ctx.Request().Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload User
|
||||||
|
if err = h.bind(ctx, &payload); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
op := entity.Update()
|
||||||
|
op.SetName(payload.Name)
|
||||||
|
op.SetEmail(payload.Email)
|
||||||
|
if payload.Password != nil {
|
||||||
|
op.SetPassword(*payload.Password)
|
||||||
|
}
|
||||||
|
op.SetVerified(payload.Verified)
|
||||||
|
op.SetAdmin(payload.Admin)
|
||||||
|
_, err = op.Save(ctx.Request().Context())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) UserDelete(ctx echo.Context, id int) error {
|
||||||
|
return h.client.User.DeleteOneID(id).
|
||||||
|
Exec(ctx.Request().Context())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) UserList(ctx echo.Context) (*EntityList, error) {
|
||||||
|
page, offset := h.getPageAndOffset(ctx)
|
||||||
|
res, err := h.client.User.
|
||||||
|
Query().
|
||||||
|
Limit(h.Config.ItemsPerPage + 1).
|
||||||
|
Offset(offset).
|
||||||
|
Order(user.ByID(sql.OrderDesc())).
|
||||||
|
All(ctx.Request().Context())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
list := &EntityList{
|
||||||
|
Columns: []string{
|
||||||
|
"Name",
|
||||||
|
"Email",
|
||||||
|
"Verified",
|
||||||
|
"Admin",
|
||||||
|
"Created at",
|
||||||
|
},
|
||||||
|
Entities: make([]EntityValues, 0, len(res)),
|
||||||
|
Page: page,
|
||||||
|
HasNextPage: len(res) > h.Config.ItemsPerPage,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i <= len(res)-1; i++ {
|
||||||
|
list.Entities = append(list.Entities, EntityValues{
|
||||||
|
ID: res[i].ID,
|
||||||
|
Values: []string{
|
||||||
|
res[i].Name,
|
||||||
|
res[i].Email,
|
||||||
|
fmt.Sprint(res[i].Verified),
|
||||||
|
fmt.Sprint(res[i].Admin),
|
||||||
|
res[i].CreatedAt.Format(h.Config.TimeFormat),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return list, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) UserGet(ctx echo.Context, id int) (url.Values, error) {
|
||||||
|
entity, err := h.client.User.Get(ctx.Request().Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
v := url.Values{}
|
||||||
|
v.Set("name", entity.Name)
|
||||||
|
v.Set("email", entity.Email)
|
||||||
|
v.Set("verified", fmt.Sprint(entity.Verified))
|
||||||
|
v.Set("admin", fmt.Sprint(entity.Admin))
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) getPageAndOffset(ctx echo.Context) (int, int) {
|
||||||
|
if page, err := strconv.Atoi(ctx.QueryParam(h.Config.PageQueryKey)); err == nil {
|
||||||
|
if page > 1 {
|
||||||
|
return page, (page - 1) * h.Config.ItemsPerPage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) bind(ctx echo.Context, entity any) error {
|
||||||
|
// Echo requires some pre-processing of form values to avoid problems.
|
||||||
|
for k, v := range ctx.Request().Form {
|
||||||
|
// Remove empty field values so Echo's bind does not fail when trying to parse things like
|
||||||
|
// times, etc.
|
||||||
|
if len(v) == 1 && len(v[0]) == 0 {
|
||||||
|
delete(ctx.Request().Form, k)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Echo expects datetime values to be in a certain format but that does not align with the datetime-local
|
||||||
|
// HTML form element format, so we will attempt to convert it here.
|
||||||
|
for _, format := range []string{dateTimeFormatNoSeconds, dateTimeFormat} {
|
||||||
|
if t, err := time.Parse(format, v[0]); err == nil {
|
||||||
|
ctx.Request().Form[k][0] = t.Format(time.RFC3339)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ctx.Bind(entity)
|
||||||
|
}
|
||||||
101
ent/admin/schema.go
Normal file
101
ent/admin/schema.go
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"entgo.io/ent/schema/field"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Enum struct {
|
||||||
|
Label, Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
type FieldSchema struct {
|
||||||
|
Name string
|
||||||
|
Type field.Type
|
||||||
|
Optional bool
|
||||||
|
Immutable bool
|
||||||
|
Sensitive bool
|
||||||
|
Enums []string
|
||||||
|
}
|
||||||
|
|
||||||
|
const NamePasswordToken = "PasswordToken"
|
||||||
|
|
||||||
|
var fieldsPasswordToken = []*FieldSchema{
|
||||||
|
{
|
||||||
|
Name: "token",
|
||||||
|
Type: field.TypeString,
|
||||||
|
Optional: false,
|
||||||
|
Immutable: false,
|
||||||
|
Sensitive: true,
|
||||||
|
Enums: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "user_id",
|
||||||
|
Type: field.TypeInt,
|
||||||
|
Optional: false,
|
||||||
|
Immutable: false,
|
||||||
|
Sensitive: false,
|
||||||
|
Enums: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "created_at",
|
||||||
|
Type: field.TypeTime,
|
||||||
|
Optional: false,
|
||||||
|
Immutable: false,
|
||||||
|
Sensitive: false,
|
||||||
|
Enums: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const NameUser = "User"
|
||||||
|
|
||||||
|
var fieldsUser = []*FieldSchema{
|
||||||
|
{
|
||||||
|
Name: "name",
|
||||||
|
Type: field.TypeString,
|
||||||
|
Optional: false,
|
||||||
|
Immutable: false,
|
||||||
|
Sensitive: false,
|
||||||
|
Enums: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "email",
|
||||||
|
Type: field.TypeString,
|
||||||
|
Optional: false,
|
||||||
|
Immutable: false,
|
||||||
|
Sensitive: false,
|
||||||
|
Enums: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "password",
|
||||||
|
Type: field.TypeString,
|
||||||
|
Optional: false,
|
||||||
|
Immutable: false,
|
||||||
|
Sensitive: true,
|
||||||
|
Enums: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "verified",
|
||||||
|
Type: field.TypeBool,
|
||||||
|
Optional: false,
|
||||||
|
Immutable: false,
|
||||||
|
Sensitive: false,
|
||||||
|
Enums: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "admin",
|
||||||
|
Type: field.TypeBool,
|
||||||
|
Optional: false,
|
||||||
|
Immutable: false,
|
||||||
|
Sensitive: false,
|
||||||
|
Enums: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "created_at",
|
||||||
|
Type: field.TypeTime,
|
||||||
|
Optional: false,
|
||||||
|
Immutable: true,
|
||||||
|
Sensitive: false,
|
||||||
|
Enums: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
262
ent/admin/templates/handler.tmpl
Normal file
262
ent/admin/templates/handler.tmpl
Normal file
|
|
@ -0,0 +1,262 @@
|
||||||
|
{{/* Tell Intellij/GoLand to enable the autocompletion based on the *gen.Graph type. */}}
|
||||||
|
{{/* gotype: entgo.io/ent/entc/gen.Graph */}}
|
||||||
|
|
||||||
|
{{ define "admin/handler" }}
|
||||||
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
{{- $pkg := base $.Config.Package }}
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"entgo.io/ent/dialect/sql"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
|
||||||
|
"{{ $.Config.Package }}"
|
||||||
|
{{- range $n := $.Nodes }}
|
||||||
|
"{{ $.Config.Package }}/{{ $n.Package }}"
|
||||||
|
{{- end }}
|
||||||
|
)
|
||||||
|
|
||||||
|
const dateTimeFormat = "2006-01-02T15:04:05"
|
||||||
|
const dateTimeFormatNoSeconds = "2006-01-02T15:04"
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
client *{{ $pkg }}.Client
|
||||||
|
Config HandlerConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHandler(client *{{ $pkg }}.Client, cfg HandlerConfig) *Handler {
|
||||||
|
return &Handler{
|
||||||
|
client: client,
|
||||||
|
Config: cfg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) Create(ctx echo.Context, entityType EntityType) error {
|
||||||
|
switch entityType.(type) {
|
||||||
|
{{- range $n := $.Nodes }}
|
||||||
|
case *{{ $n.Name }}:
|
||||||
|
return h.{{ $n.Name }}Create(ctx)
|
||||||
|
{{- end }}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported entity type: %s", entityType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) Get(ctx echo.Context, entityType EntityType, id int) (url.Values, error) {
|
||||||
|
switch entityType.(type) {
|
||||||
|
{{- range $n := $.Nodes }}
|
||||||
|
case *{{ $n.Name }}:
|
||||||
|
return h.{{ $n.Name }}Get(ctx, id)
|
||||||
|
{{- end }}
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported entity type: %s", entityType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) Delete(ctx echo.Context, entityType EntityType, id int) error {
|
||||||
|
switch entityType.(type) {
|
||||||
|
{{- range $n := $.Nodes }}
|
||||||
|
case *{{ $n.Name }}:
|
||||||
|
return h.{{ $n.Name }}Delete(ctx, id)
|
||||||
|
{{- end }}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported entity type: %s", entityType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) Update(ctx echo.Context, entityType EntityType, id int) error {
|
||||||
|
switch entityType.(type) {
|
||||||
|
{{- range $n := $.Nodes }}
|
||||||
|
case *{{ $n.Name }}:
|
||||||
|
return h.{{ $n.Name }}Update(ctx, id)
|
||||||
|
{{- end }}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported entity type: %s", entityType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) List(ctx echo.Context, entityType EntityType) (*EntityList, error) {
|
||||||
|
switch entityType.(type) {
|
||||||
|
{{- range $n := $.Nodes }}
|
||||||
|
case *{{ $n.Name }}:
|
||||||
|
return h.{{ $n.Name }}List(ctx)
|
||||||
|
{{- end }}
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported entity type: %s", entityType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{{ range $n := $.Nodes }}
|
||||||
|
func (h *Handler) {{ $n.Name }}Create(ctx echo.Context) error {
|
||||||
|
var payload {{ $n.Name }}
|
||||||
|
if err := h.bind(ctx, &payload); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
op := h.client.{{ $n.Name }}.Create()
|
||||||
|
{{- range $f := $n.Fields }}
|
||||||
|
{{- if (fieldIsPointer $f) }}
|
||||||
|
if payload.{{ fieldName $f.Name }} != nil {
|
||||||
|
op.Set{{ fieldName $f.Name }}(*payload.{{ fieldName $f.Name }})
|
||||||
|
}
|
||||||
|
{{- else }}
|
||||||
|
op.Set{{ fieldName $f.Name }}(payload.{{ fieldName $f.Name }})
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
_, err := op.Save(ctx.Request().Context())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) {{ $n.Name }}Update(ctx echo.Context, id int) error {
|
||||||
|
entity, err := h.client.{{ $n.Name }}.Get(ctx.Request().Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload {{ $n.Name }}
|
||||||
|
if err = h.bind(ctx, &payload); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
op := entity.Update()
|
||||||
|
{{- range $f := $n.Fields }}
|
||||||
|
{{- if not $f.Immutable }}
|
||||||
|
{{- if $f.Sensitive }}
|
||||||
|
if payload.{{ fieldName $f.Name }} != nil {
|
||||||
|
op.Set{{ fieldName $f.Name }}(*payload.{{ fieldName $f.Name }})
|
||||||
|
}
|
||||||
|
{{- else if $f.Nillable }}
|
||||||
|
op.SetNillable{{ fieldName $f.Name }}(payload.{{ fieldName $f.Name }})
|
||||||
|
{{- else if $f.Optional }}
|
||||||
|
if payload.{{ fieldName $f.Name }} == nil {
|
||||||
|
op.Clear{{ fieldName $f.Name }}()
|
||||||
|
} else {
|
||||||
|
op.Set{{ fieldName $f.Name }}(*payload.{{ fieldName $f.Name }})
|
||||||
|
}
|
||||||
|
{{- else if (fieldIsPointer $f) }}
|
||||||
|
if payload.{{ fieldName $f.Name }} == nil {
|
||||||
|
var empty {{ $f.Type }}
|
||||||
|
op.Set{{ fieldName $f.Name }}(empty)
|
||||||
|
} else {
|
||||||
|
op.Set{{ fieldName $f.Name }}(*payload.{{ fieldName $f.Name }})
|
||||||
|
}
|
||||||
|
{{- else }}
|
||||||
|
op.Set{{ fieldName $f.Name }}(payload.{{ fieldName $f.Name }})
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
_, err = op.Save(ctx.Request().Context())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) {{ $n.Name }}Delete(ctx echo.Context, id int) error {
|
||||||
|
return h.client.{{ $n.Name }}.DeleteOneID(id).
|
||||||
|
Exec(ctx.Request().Context())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) {{ $n.Name }}List(ctx echo.Context) (*EntityList, error) {
|
||||||
|
page, offset := h.getPageAndOffset(ctx)
|
||||||
|
res, err := h.client.{{ $n.Name }}.
|
||||||
|
Query().
|
||||||
|
Limit(h.Config.ItemsPerPage+1).
|
||||||
|
Offset(offset).
|
||||||
|
Order({{ $n.Package }}.ByID(sql.OrderDesc())).
|
||||||
|
All(ctx.Request().Context())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
list := &EntityList{
|
||||||
|
Columns: []string{
|
||||||
|
{{- range $f := $n.Fields }}
|
||||||
|
{{- if not $f.Sensitive }}
|
||||||
|
"{{ fieldLabel $f.Name }}",
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
},
|
||||||
|
Entities: make([]EntityValues, 0, len(res)),
|
||||||
|
Page: page,
|
||||||
|
HasNextPage: len(res) > h.Config.ItemsPerPage,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i <= len(res)-1; i++ {
|
||||||
|
list.Entities = append(list.Entities, EntityValues{
|
||||||
|
ID: res[i].ID,
|
||||||
|
Values: []string{
|
||||||
|
{{- range $f := $n.Fields }}
|
||||||
|
{{- if not $f.Sensitive }}
|
||||||
|
{{- if eq $f.Type.String "string" }}
|
||||||
|
res[i].{{ fieldName $f.Name }},
|
||||||
|
{{- else if eq $f.Type.String "time.Time" }}
|
||||||
|
res[i].{{ fieldName $f.Name }}.Format(h.Config.TimeFormat),
|
||||||
|
{{- else }}
|
||||||
|
fmt.Sprint(res[i].{{ fieldName $f.Name }}),
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return list, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) {{ $n.Name }}Get(ctx echo.Context, id int) (url.Values, error) {
|
||||||
|
entity, err := h.client.{{ $n.Name }}.Get(ctx.Request().Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
v := url.Values{}
|
||||||
|
{{- range $f := $n.Fields }}
|
||||||
|
{{- if and (not $f.Sensitive) (not $f.Immutable) }}
|
||||||
|
{{- if eq $f.Type.String "string" }}
|
||||||
|
v.Set("{{ $f.Name }}", entity.{{ fieldName $f.Name }})
|
||||||
|
{{- else if eq $f.Type.String "time.Time" }}
|
||||||
|
v.Set("{{ $f.Name }}", entity.{{ fieldName $f.Name }}.Format(dateTimeFormat))
|
||||||
|
{{- else }}
|
||||||
|
v.Set("{{ $f.Name }}", fmt.Sprint(entity.{{ fieldName $f.Name }}))
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
func (h *Handler) getPageAndOffset(ctx echo.Context) (int, int) {
|
||||||
|
if page, err := strconv.Atoi(ctx.QueryParam(h.Config.PageQueryKey)); err == nil {
|
||||||
|
if page > 1 {
|
||||||
|
return page, (page-1) * h.Config.ItemsPerPage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) bind(ctx echo.Context, entity any) error {
|
||||||
|
// Echo requires some pre-processing of form values to avoid problems.
|
||||||
|
for k, v := range ctx.Request().Form {
|
||||||
|
// Remove empty field values so Echo's bind does not fail when trying to parse things like
|
||||||
|
// times, etc.
|
||||||
|
if len(v) == 1 && len(v[0]) == 0 {
|
||||||
|
delete(ctx.Request().Form, k)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Echo expects datetime values to be in a certain format but that does not align with the datetime-local
|
||||||
|
// HTML form element format, so we will attempt to convert it here.
|
||||||
|
for _, format := range []string{dateTimeFormatNoSeconds, dateTimeFormat} {
|
||||||
|
if t, err := time.Parse(format, v[0]); err == nil {
|
||||||
|
ctx.Request().Form[k][0] = t.Format(time.RFC3339)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ctx.Bind(entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
{{ end }}
|
||||||
51
ent/admin/templates/schema.tmpl
Normal file
51
ent/admin/templates/schema.tmpl
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
{{/* Tell Intellij/GoLand to enable the autocompletion based on the *gen.Graph type. */}}
|
||||||
|
{{/* gotype: entgo.io/ent/entc/gen.Graph */}}
|
||||||
|
|
||||||
|
{{ define "admin/schema" }}
|
||||||
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"entgo.io/ent/schema/field"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Enum struct {
|
||||||
|
Label, Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
type FieldSchema struct {
|
||||||
|
Name string
|
||||||
|
Type field.Type
|
||||||
|
Optional bool
|
||||||
|
Immutable bool
|
||||||
|
Sensitive bool
|
||||||
|
Enums []string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
{{- range $n := $.Nodes }}
|
||||||
|
const Name{{ $n.Name }} = "{{ $n.Name }}"
|
||||||
|
|
||||||
|
var fields{{ $n.Name }} = []*FieldSchema{
|
||||||
|
{{- range $f := $n.Fields }}
|
||||||
|
{
|
||||||
|
Name: "{{ $f.Name }}",
|
||||||
|
Type: field.{{ $f.Type.Type.ConstName }},
|
||||||
|
Optional: {{ $f.Optional }},
|
||||||
|
Immutable: {{ $f.Immutable }},
|
||||||
|
Sensitive: {{ $f.Sensitive }},
|
||||||
|
{{- if len $f.Enums }}
|
||||||
|
Enums: []string{
|
||||||
|
{{- range $e := $f.Enums }}
|
||||||
|
"{{ $e.Value }}",
|
||||||
|
{{- end }}
|
||||||
|
},
|
||||||
|
{{- else }}
|
||||||
|
Enums: nil,
|
||||||
|
{{- end }}
|
||||||
|
},
|
||||||
|
{{- end }}
|
||||||
|
}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ end }}
|
||||||
56
ent/admin/templates/types.tmpl
Normal file
56
ent/admin/templates/types.tmpl
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
{{/* Tell Intellij/GoLand to enable the autocompletion based on the *gen.Graph type. */}}
|
||||||
|
{{/* gotype: entgo.io/ent/entc/gen.Graph */}}
|
||||||
|
|
||||||
|
{{ define "admin/types" }}
|
||||||
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
package admin
|
||||||
|
|
||||||
|
{{- range $n := $.Nodes }}
|
||||||
|
type {{ $n.Name }} struct {
|
||||||
|
{{- range $f := $n.Fields }}
|
||||||
|
{{ fieldName $f.Name }} {{ if (fieldIsPointer $f) }}*{{ end }}{{ $f.Type }} `form:"{{ $f.Name }}"`
|
||||||
|
{{- end }}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *{{ $n.Name }}) GetName() string {
|
||||||
|
return Name{{ $n.Name }}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *{{ $n.Name }}) GetSchema() []*FieldSchema {
|
||||||
|
return fields{{ $n.Name }}
|
||||||
|
}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
type EntityType interface {
|
||||||
|
GetName() string
|
||||||
|
GetSchema() []*FieldSchema
|
||||||
|
}
|
||||||
|
|
||||||
|
var entityTypes = []EntityType{
|
||||||
|
{{- range $n := $.Nodes }}
|
||||||
|
&{{ $n.Name }}{},
|
||||||
|
{{- end }}
|
||||||
|
}
|
||||||
|
|
||||||
|
type EntityList struct {
|
||||||
|
Columns []string
|
||||||
|
Entities []EntityValues
|
||||||
|
Page int
|
||||||
|
HasNextPage bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type EntityValues struct {
|
||||||
|
ID int
|
||||||
|
Values []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type HandlerConfig struct {
|
||||||
|
ItemsPerPage int
|
||||||
|
PageQueryKey string
|
||||||
|
TimeFormat string
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetEntityTypes() []EntityType {
|
||||||
|
return entityTypes
|
||||||
|
}
|
||||||
|
{{ end }}
|
||||||
67
ent/admin/types.go
Normal file
67
ent/admin/types.go
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type PasswordToken struct {
|
||||||
|
Token *string `form:"token"`
|
||||||
|
UserID int `form:"user_id"`
|
||||||
|
CreatedAt *time.Time `form:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *PasswordToken) GetName() string {
|
||||||
|
return NamePasswordToken
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *PasswordToken) GetSchema() []*FieldSchema {
|
||||||
|
return fieldsPasswordToken
|
||||||
|
}
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Name string `form:"name"`
|
||||||
|
Email string `form:"email"`
|
||||||
|
Password *string `form:"password"`
|
||||||
|
Verified bool `form:"verified"`
|
||||||
|
Admin bool `form:"admin"`
|
||||||
|
CreatedAt *time.Time `form:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *User) GetName() string {
|
||||||
|
return NameUser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *User) GetSchema() []*FieldSchema {
|
||||||
|
return fieldsUser
|
||||||
|
}
|
||||||
|
|
||||||
|
type EntityType interface {
|
||||||
|
GetName() string
|
||||||
|
GetSchema() []*FieldSchema
|
||||||
|
}
|
||||||
|
|
||||||
|
var entityTypes = []EntityType{
|
||||||
|
&PasswordToken{},
|
||||||
|
&User{},
|
||||||
|
}
|
||||||
|
|
||||||
|
type EntityList struct {
|
||||||
|
Columns []string
|
||||||
|
Entities []EntityValues
|
||||||
|
Page int
|
||||||
|
HasNextPage bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type EntityValues struct {
|
||||||
|
ID int
|
||||||
|
Values []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type HandlerConfig struct {
|
||||||
|
ItemsPerPage int
|
||||||
|
PageQueryKey string
|
||||||
|
TimeFormat string
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetEntityTypes() []EntityType {
|
||||||
|
return entityTypes
|
||||||
|
}
|
||||||
247
ent/client.go
247
ent/client.go
|
|
@ -1,20 +1,22 @@
|
||||||
// Code generated by entc, DO NOT EDIT.
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
package ent
|
package ent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
"goweb/ent/migrate"
|
"github.com/camzawacki/personal-site/ent/migrate"
|
||||||
|
|
||||||
"goweb/ent/passwordtoken"
|
|
||||||
"goweb/ent/user"
|
|
||||||
|
|
||||||
|
"entgo.io/ent"
|
||||||
"entgo.io/ent/dialect"
|
"entgo.io/ent/dialect"
|
||||||
"entgo.io/ent/dialect/sql"
|
"entgo.io/ent/dialect/sql"
|
||||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||||
|
"github.com/camzawacki/personal-site/ent/passwordtoken"
|
||||||
|
"github.com/camzawacki/personal-site/ent/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Client is the client that holds all ent builders.
|
// Client is the client that holds all ent builders.
|
||||||
|
|
@ -30,9 +32,7 @@ type Client struct {
|
||||||
|
|
||||||
// NewClient creates a new client configured with the given options.
|
// NewClient creates a new client configured with the given options.
|
||||||
func NewClient(opts ...Option) *Client {
|
func NewClient(opts ...Option) *Client {
|
||||||
cfg := config{log: log.Println, hooks: &hooks{}}
|
client := &Client{config: newConfig(opts...)}
|
||||||
cfg.options(opts...)
|
|
||||||
client := &Client{config: cfg}
|
|
||||||
client.init()
|
client.init()
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
@ -43,6 +43,62 @@ func (c *Client) init() {
|
||||||
c.User = NewUserClient(c.config)
|
c.User = NewUserClient(c.config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
// config is the configuration for the client and its builder.
|
||||||
|
config struct {
|
||||||
|
// driver used for executing database requests.
|
||||||
|
driver dialect.Driver
|
||||||
|
// debug enable a debug logging.
|
||||||
|
debug bool
|
||||||
|
// log used for logging on debug mode.
|
||||||
|
log func(...any)
|
||||||
|
// hooks to execute on mutations.
|
||||||
|
hooks *hooks
|
||||||
|
// interceptors to execute on queries.
|
||||||
|
inters *inters
|
||||||
|
}
|
||||||
|
// Option function to configure the client.
|
||||||
|
Option func(*config)
|
||||||
|
)
|
||||||
|
|
||||||
|
// newConfig creates a new config for the client.
|
||||||
|
func newConfig(opts ...Option) config {
|
||||||
|
cfg := config{log: log.Println, hooks: &hooks{}, inters: &inters{}}
|
||||||
|
cfg.options(opts...)
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
// options applies the options on the config object.
|
||||||
|
func (c *config) options(opts ...Option) {
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(c)
|
||||||
|
}
|
||||||
|
if c.debug {
|
||||||
|
c.driver = dialect.Debug(c.driver, c.log)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug enables debug logging on the ent.Driver.
|
||||||
|
func Debug() Option {
|
||||||
|
return func(c *config) {
|
||||||
|
c.debug = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log sets the logging function for debug mode.
|
||||||
|
func Log(fn func(...any)) Option {
|
||||||
|
return func(c *config) {
|
||||||
|
c.log = fn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Driver configures the client driver.
|
||||||
|
func Driver(driver dialect.Driver) Option {
|
||||||
|
return func(c *config) {
|
||||||
|
c.driver = driver
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Open opens a database/sql.DB specified by the driver name and
|
// Open opens a database/sql.DB specified by the driver name and
|
||||||
// the data source name, and returns a new client attached to it.
|
// the data source name, and returns a new client attached to it.
|
||||||
// Optional parameters can be added for configuring the client.
|
// Optional parameters can be added for configuring the client.
|
||||||
|
|
@ -59,11 +115,14 @@ func Open(driverName, dataSourceName string, options ...Option) (*Client, error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrTxStarted is returned when trying to start a new transaction from a transactional client.
|
||||||
|
var ErrTxStarted = errors.New("ent: cannot start a transaction within a transaction")
|
||||||
|
|
||||||
// Tx returns a new transactional client. The provided context
|
// Tx returns a new transactional client. The provided context
|
||||||
// is used until the transaction is committed or rolled back.
|
// is used until the transaction is committed or rolled back.
|
||||||
func (c *Client) Tx(ctx context.Context) (*Tx, error) {
|
func (c *Client) Tx(ctx context.Context) (*Tx, error) {
|
||||||
if _, ok := c.driver.(*txDriver); ok {
|
if _, ok := c.driver.(*txDriver); ok {
|
||||||
return nil, fmt.Errorf("ent: cannot start a transaction within a transaction")
|
return nil, ErrTxStarted
|
||||||
}
|
}
|
||||||
tx, err := newTx(ctx, c.driver)
|
tx, err := newTx(ctx, c.driver)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -82,7 +141,7 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) {
|
||||||
// BeginTx returns a transactional client with specified options.
|
// BeginTx returns a transactional client with specified options.
|
||||||
func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) {
|
func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) {
|
||||||
if _, ok := c.driver.(*txDriver); ok {
|
if _, ok := c.driver.(*txDriver); ok {
|
||||||
return nil, fmt.Errorf("ent: cannot start a transaction within a transaction")
|
return nil, errors.New("ent: cannot start a transaction within a transaction")
|
||||||
}
|
}
|
||||||
tx, err := c.driver.(interface {
|
tx, err := c.driver.(interface {
|
||||||
BeginTx(context.Context, *sql.TxOptions) (dialect.Tx, error)
|
BeginTx(context.Context, *sql.TxOptions) (dialect.Tx, error)
|
||||||
|
|
@ -93,6 +152,7 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error)
|
||||||
cfg := c.config
|
cfg := c.config
|
||||||
cfg.driver = &txDriver{tx: tx, drv: c.driver}
|
cfg.driver = &txDriver{tx: tx, drv: c.driver}
|
||||||
return &Tx{
|
return &Tx{
|
||||||
|
ctx: ctx,
|
||||||
config: cfg,
|
config: cfg,
|
||||||
PasswordToken: NewPasswordTokenClient(cfg),
|
PasswordToken: NewPasswordTokenClient(cfg),
|
||||||
User: NewUserClient(cfg),
|
User: NewUserClient(cfg),
|
||||||
|
|
@ -105,7 +165,6 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error)
|
||||||
// PasswordToken.
|
// PasswordToken.
|
||||||
// Query().
|
// Query().
|
||||||
// Count(ctx)
|
// Count(ctx)
|
||||||
//
|
|
||||||
func (c *Client) Debug() *Client {
|
func (c *Client) Debug() *Client {
|
||||||
if c.debug {
|
if c.debug {
|
||||||
return c
|
return c
|
||||||
|
|
@ -129,6 +188,25 @@ func (c *Client) Use(hooks ...Hook) {
|
||||||
c.User.Use(hooks...)
|
c.User.Use(hooks...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Intercept adds the query interceptors to all the entity clients.
|
||||||
|
// In order to add interceptors to a specific client, call: `client.Node.Intercept(...)`.
|
||||||
|
func (c *Client) Intercept(interceptors ...Interceptor) {
|
||||||
|
c.PasswordToken.Intercept(interceptors...)
|
||||||
|
c.User.Intercept(interceptors...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mutate implements the ent.Mutator interface.
|
||||||
|
func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) {
|
||||||
|
switch m := m.(type) {
|
||||||
|
case *PasswordTokenMutation:
|
||||||
|
return c.PasswordToken.mutate(ctx, m)
|
||||||
|
case *UserMutation:
|
||||||
|
return c.User.mutate(ctx, m)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("ent: unknown mutation type %T", m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// PasswordTokenClient is a client for the PasswordToken schema.
|
// PasswordTokenClient is a client for the PasswordToken schema.
|
||||||
type PasswordTokenClient struct {
|
type PasswordTokenClient struct {
|
||||||
config
|
config
|
||||||
|
|
@ -145,7 +223,13 @@ func (c *PasswordTokenClient) Use(hooks ...Hook) {
|
||||||
c.hooks.PasswordToken = append(c.hooks.PasswordToken, hooks...)
|
c.hooks.PasswordToken = append(c.hooks.PasswordToken, hooks...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create returns a create builder for PasswordToken.
|
// Intercept adds a list of query interceptors to the interceptors stack.
|
||||||
|
// A call to `Intercept(f, g, h)` equals to `passwordtoken.Intercept(f(g(h())))`.
|
||||||
|
func (c *PasswordTokenClient) Intercept(interceptors ...Interceptor) {
|
||||||
|
c.inters.PasswordToken = append(c.inters.PasswordToken, interceptors...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create returns a builder for creating a PasswordToken entity.
|
||||||
func (c *PasswordTokenClient) Create() *PasswordTokenCreate {
|
func (c *PasswordTokenClient) Create() *PasswordTokenCreate {
|
||||||
mutation := newPasswordTokenMutation(c.config, OpCreate)
|
mutation := newPasswordTokenMutation(c.config, OpCreate)
|
||||||
return &PasswordTokenCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
return &PasswordTokenCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||||
|
|
@ -156,6 +240,21 @@ func (c *PasswordTokenClient) CreateBulk(builders ...*PasswordTokenCreate) *Pass
|
||||||
return &PasswordTokenCreateBulk{config: c.config, builders: builders}
|
return &PasswordTokenCreateBulk{config: c.config, builders: builders}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
|
||||||
|
// a builder and applies setFunc on it.
|
||||||
|
func (c *PasswordTokenClient) MapCreateBulk(slice any, setFunc func(*PasswordTokenCreate, int)) *PasswordTokenCreateBulk {
|
||||||
|
rv := reflect.ValueOf(slice)
|
||||||
|
if rv.Kind() != reflect.Slice {
|
||||||
|
return &PasswordTokenCreateBulk{err: fmt.Errorf("calling to PasswordTokenClient.MapCreateBulk with wrong type %T, need slice", slice)}
|
||||||
|
}
|
||||||
|
builders := make([]*PasswordTokenCreate, rv.Len())
|
||||||
|
for i := 0; i < rv.Len(); i++ {
|
||||||
|
builders[i] = c.Create()
|
||||||
|
setFunc(builders[i], i)
|
||||||
|
}
|
||||||
|
return &PasswordTokenCreateBulk{config: c.config, builders: builders}
|
||||||
|
}
|
||||||
|
|
||||||
// Update returns an update builder for PasswordToken.
|
// Update returns an update builder for PasswordToken.
|
||||||
func (c *PasswordTokenClient) Update() *PasswordTokenUpdate {
|
func (c *PasswordTokenClient) Update() *PasswordTokenUpdate {
|
||||||
mutation := newPasswordTokenMutation(c.config, OpUpdate)
|
mutation := newPasswordTokenMutation(c.config, OpUpdate)
|
||||||
|
|
@ -163,8 +262,8 @@ func (c *PasswordTokenClient) Update() *PasswordTokenUpdate {
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateOne returns an update builder for the given entity.
|
// UpdateOne returns an update builder for the given entity.
|
||||||
func (c *PasswordTokenClient) UpdateOne(pt *PasswordToken) *PasswordTokenUpdateOne {
|
func (c *PasswordTokenClient) UpdateOne(_m *PasswordToken) *PasswordTokenUpdateOne {
|
||||||
mutation := newPasswordTokenMutation(c.config, OpUpdateOne, withPasswordToken(pt))
|
mutation := newPasswordTokenMutation(c.config, OpUpdateOne, withPasswordToken(_m))
|
||||||
return &PasswordTokenUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
return &PasswordTokenUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -180,12 +279,12 @@ func (c *PasswordTokenClient) Delete() *PasswordTokenDelete {
|
||||||
return &PasswordTokenDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
return &PasswordTokenDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteOne returns a delete builder for the given entity.
|
// DeleteOne returns a builder for deleting the given entity.
|
||||||
func (c *PasswordTokenClient) DeleteOne(pt *PasswordToken) *PasswordTokenDeleteOne {
|
func (c *PasswordTokenClient) DeleteOne(_m *PasswordToken) *PasswordTokenDeleteOne {
|
||||||
return c.DeleteOneID(pt.ID)
|
return c.DeleteOneID(_m.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteOneID returns a delete builder for the given id.
|
// DeleteOneID returns a builder for deleting the given entity by its id.
|
||||||
func (c *PasswordTokenClient) DeleteOneID(id int) *PasswordTokenDeleteOne {
|
func (c *PasswordTokenClient) DeleteOneID(id int) *PasswordTokenDeleteOne {
|
||||||
builder := c.Delete().Where(passwordtoken.ID(id))
|
builder := c.Delete().Where(passwordtoken.ID(id))
|
||||||
builder.mutation.id = &id
|
builder.mutation.id = &id
|
||||||
|
|
@ -197,6 +296,8 @@ func (c *PasswordTokenClient) DeleteOneID(id int) *PasswordTokenDeleteOne {
|
||||||
func (c *PasswordTokenClient) Query() *PasswordTokenQuery {
|
func (c *PasswordTokenClient) Query() *PasswordTokenQuery {
|
||||||
return &PasswordTokenQuery{
|
return &PasswordTokenQuery{
|
||||||
config: c.config,
|
config: c.config,
|
||||||
|
ctx: &QueryContext{Type: TypePasswordToken},
|
||||||
|
inters: c.Interceptors(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -215,16 +316,16 @@ func (c *PasswordTokenClient) GetX(ctx context.Context, id int) *PasswordToken {
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryUser queries the user edge of a PasswordToken.
|
// QueryUser queries the user edge of a PasswordToken.
|
||||||
func (c *PasswordTokenClient) QueryUser(pt *PasswordToken) *UserQuery {
|
func (c *PasswordTokenClient) QueryUser(_m *PasswordToken) *UserQuery {
|
||||||
query := &UserQuery{config: c.config}
|
query := (&UserClient{config: c.config}).Query()
|
||||||
query.path = func(ctx context.Context) (fromV *sql.Selector, _ error) {
|
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
|
||||||
id := pt.ID
|
id := _m.ID
|
||||||
step := sqlgraph.NewStep(
|
step := sqlgraph.NewStep(
|
||||||
sqlgraph.From(passwordtoken.Table, passwordtoken.FieldID, id),
|
sqlgraph.From(passwordtoken.Table, passwordtoken.FieldID, id),
|
||||||
sqlgraph.To(user.Table, user.FieldID),
|
sqlgraph.To(user.Table, user.FieldID),
|
||||||
sqlgraph.Edge(sqlgraph.M2O, false, passwordtoken.UserTable, passwordtoken.UserColumn),
|
sqlgraph.Edge(sqlgraph.M2O, false, passwordtoken.UserTable, passwordtoken.UserColumn),
|
||||||
)
|
)
|
||||||
fromV = sqlgraph.Neighbors(pt.driver.Dialect(), step)
|
fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
|
||||||
return fromV, nil
|
return fromV, nil
|
||||||
}
|
}
|
||||||
return query
|
return query
|
||||||
|
|
@ -232,7 +333,28 @@ func (c *PasswordTokenClient) QueryUser(pt *PasswordToken) *UserQuery {
|
||||||
|
|
||||||
// Hooks returns the client hooks.
|
// Hooks returns the client hooks.
|
||||||
func (c *PasswordTokenClient) Hooks() []Hook {
|
func (c *PasswordTokenClient) Hooks() []Hook {
|
||||||
return c.hooks.PasswordToken
|
hooks := c.hooks.PasswordToken
|
||||||
|
return append(hooks[:len(hooks):len(hooks)], passwordtoken.Hooks[:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interceptors returns the client interceptors.
|
||||||
|
func (c *PasswordTokenClient) Interceptors() []Interceptor {
|
||||||
|
return c.inters.PasswordToken
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PasswordTokenClient) mutate(ctx context.Context, m *PasswordTokenMutation) (Value, error) {
|
||||||
|
switch m.Op() {
|
||||||
|
case OpCreate:
|
||||||
|
return (&PasswordTokenCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||||
|
case OpUpdate:
|
||||||
|
return (&PasswordTokenUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||||
|
case OpUpdateOne:
|
||||||
|
return (&PasswordTokenUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||||
|
case OpDelete, OpDeleteOne:
|
||||||
|
return (&PasswordTokenDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("ent: unknown PasswordToken mutation op: %q", m.Op())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserClient is a client for the User schema.
|
// UserClient is a client for the User schema.
|
||||||
|
|
@ -251,7 +373,13 @@ func (c *UserClient) Use(hooks ...Hook) {
|
||||||
c.hooks.User = append(c.hooks.User, hooks...)
|
c.hooks.User = append(c.hooks.User, hooks...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create returns a create builder for User.
|
// Intercept adds a list of query interceptors to the interceptors stack.
|
||||||
|
// A call to `Intercept(f, g, h)` equals to `user.Intercept(f(g(h())))`.
|
||||||
|
func (c *UserClient) Intercept(interceptors ...Interceptor) {
|
||||||
|
c.inters.User = append(c.inters.User, interceptors...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create returns a builder for creating a User entity.
|
||||||
func (c *UserClient) Create() *UserCreate {
|
func (c *UserClient) Create() *UserCreate {
|
||||||
mutation := newUserMutation(c.config, OpCreate)
|
mutation := newUserMutation(c.config, OpCreate)
|
||||||
return &UserCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
return &UserCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||||
|
|
@ -262,6 +390,21 @@ func (c *UserClient) CreateBulk(builders ...*UserCreate) *UserCreateBulk {
|
||||||
return &UserCreateBulk{config: c.config, builders: builders}
|
return &UserCreateBulk{config: c.config, builders: builders}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
|
||||||
|
// a builder and applies setFunc on it.
|
||||||
|
func (c *UserClient) MapCreateBulk(slice any, setFunc func(*UserCreate, int)) *UserCreateBulk {
|
||||||
|
rv := reflect.ValueOf(slice)
|
||||||
|
if rv.Kind() != reflect.Slice {
|
||||||
|
return &UserCreateBulk{err: fmt.Errorf("calling to UserClient.MapCreateBulk with wrong type %T, need slice", slice)}
|
||||||
|
}
|
||||||
|
builders := make([]*UserCreate, rv.Len())
|
||||||
|
for i := 0; i < rv.Len(); i++ {
|
||||||
|
builders[i] = c.Create()
|
||||||
|
setFunc(builders[i], i)
|
||||||
|
}
|
||||||
|
return &UserCreateBulk{config: c.config, builders: builders}
|
||||||
|
}
|
||||||
|
|
||||||
// Update returns an update builder for User.
|
// Update returns an update builder for User.
|
||||||
func (c *UserClient) Update() *UserUpdate {
|
func (c *UserClient) Update() *UserUpdate {
|
||||||
mutation := newUserMutation(c.config, OpUpdate)
|
mutation := newUserMutation(c.config, OpUpdate)
|
||||||
|
|
@ -269,8 +412,8 @@ func (c *UserClient) Update() *UserUpdate {
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateOne returns an update builder for the given entity.
|
// UpdateOne returns an update builder for the given entity.
|
||||||
func (c *UserClient) UpdateOne(u *User) *UserUpdateOne {
|
func (c *UserClient) UpdateOne(_m *User) *UserUpdateOne {
|
||||||
mutation := newUserMutation(c.config, OpUpdateOne, withUser(u))
|
mutation := newUserMutation(c.config, OpUpdateOne, withUser(_m))
|
||||||
return &UserUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
return &UserUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -286,12 +429,12 @@ func (c *UserClient) Delete() *UserDelete {
|
||||||
return &UserDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
return &UserDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteOne returns a delete builder for the given entity.
|
// DeleteOne returns a builder for deleting the given entity.
|
||||||
func (c *UserClient) DeleteOne(u *User) *UserDeleteOne {
|
func (c *UserClient) DeleteOne(_m *User) *UserDeleteOne {
|
||||||
return c.DeleteOneID(u.ID)
|
return c.DeleteOneID(_m.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteOneID returns a delete builder for the given id.
|
// DeleteOneID returns a builder for deleting the given entity by its id.
|
||||||
func (c *UserClient) DeleteOneID(id int) *UserDeleteOne {
|
func (c *UserClient) DeleteOneID(id int) *UserDeleteOne {
|
||||||
builder := c.Delete().Where(user.ID(id))
|
builder := c.Delete().Where(user.ID(id))
|
||||||
builder.mutation.id = &id
|
builder.mutation.id = &id
|
||||||
|
|
@ -303,6 +446,8 @@ func (c *UserClient) DeleteOneID(id int) *UserDeleteOne {
|
||||||
func (c *UserClient) Query() *UserQuery {
|
func (c *UserClient) Query() *UserQuery {
|
||||||
return &UserQuery{
|
return &UserQuery{
|
||||||
config: c.config,
|
config: c.config,
|
||||||
|
ctx: &QueryContext{Type: TypeUser},
|
||||||
|
inters: c.Interceptors(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -321,16 +466,16 @@ func (c *UserClient) GetX(ctx context.Context, id int) *User {
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryOwner queries the owner edge of a User.
|
// QueryOwner queries the owner edge of a User.
|
||||||
func (c *UserClient) QueryOwner(u *User) *PasswordTokenQuery {
|
func (c *UserClient) QueryOwner(_m *User) *PasswordTokenQuery {
|
||||||
query := &PasswordTokenQuery{config: c.config}
|
query := (&PasswordTokenClient{config: c.config}).Query()
|
||||||
query.path = func(ctx context.Context) (fromV *sql.Selector, _ error) {
|
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
|
||||||
id := u.ID
|
id := _m.ID
|
||||||
step := sqlgraph.NewStep(
|
step := sqlgraph.NewStep(
|
||||||
sqlgraph.From(user.Table, user.FieldID, id),
|
sqlgraph.From(user.Table, user.FieldID, id),
|
||||||
sqlgraph.To(passwordtoken.Table, passwordtoken.FieldID),
|
sqlgraph.To(passwordtoken.Table, passwordtoken.FieldID),
|
||||||
sqlgraph.Edge(sqlgraph.O2M, true, user.OwnerTable, user.OwnerColumn),
|
sqlgraph.Edge(sqlgraph.O2M, true, user.OwnerTable, user.OwnerColumn),
|
||||||
)
|
)
|
||||||
fromV = sqlgraph.Neighbors(u.driver.Dialect(), step)
|
fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
|
||||||
return fromV, nil
|
return fromV, nil
|
||||||
}
|
}
|
||||||
return query
|
return query
|
||||||
|
|
@ -341,3 +486,33 @@ func (c *UserClient) Hooks() []Hook {
|
||||||
hooks := c.hooks.User
|
hooks := c.hooks.User
|
||||||
return append(hooks[:len(hooks):len(hooks)], user.Hooks[:]...)
|
return append(hooks[:len(hooks):len(hooks)], user.Hooks[:]...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Interceptors returns the client interceptors.
|
||||||
|
func (c *UserClient) Interceptors() []Interceptor {
|
||||||
|
return c.inters.User
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UserClient) mutate(ctx context.Context, m *UserMutation) (Value, error) {
|
||||||
|
switch m.Op() {
|
||||||
|
case OpCreate:
|
||||||
|
return (&UserCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||||
|
case OpUpdate:
|
||||||
|
return (&UserUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||||
|
case OpUpdateOne:
|
||||||
|
return (&UserUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||||
|
case OpDelete, OpDeleteOne:
|
||||||
|
return (&UserDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("ent: unknown User mutation op: %q", m.Op())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// hooks and interceptors per client, for fast access.
|
||||||
|
type (
|
||||||
|
hooks struct {
|
||||||
|
PasswordToken, User []ent.Hook
|
||||||
|
}
|
||||||
|
inters struct {
|
||||||
|
PasswordToken, User []ent.Interceptor
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
// Code generated by entc, DO NOT EDIT.
|
|
||||||
|
|
||||||
package ent
|
|
||||||
|
|
||||||
import (
|
|
||||||
"entgo.io/ent"
|
|
||||||
"entgo.io/ent/dialect"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Option function to configure the client.
|
|
||||||
type Option func(*config)
|
|
||||||
|
|
||||||
// Config is the configuration for the client and its builder.
|
|
||||||
type config struct {
|
|
||||||
// driver used for executing database requests.
|
|
||||||
driver dialect.Driver
|
|
||||||
// debug enable a debug logging.
|
|
||||||
debug bool
|
|
||||||
// log used for logging on debug mode.
|
|
||||||
log func(...interface{})
|
|
||||||
// hooks to execute on mutations.
|
|
||||||
hooks *hooks
|
|
||||||
}
|
|
||||||
|
|
||||||
// hooks per client, for fast access.
|
|
||||||
type hooks struct {
|
|
||||||
PasswordToken []ent.Hook
|
|
||||||
User []ent.Hook
|
|
||||||
}
|
|
||||||
|
|
||||||
// Options applies the options on the config object.
|
|
||||||
func (c *config) options(opts ...Option) {
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(c)
|
|
||||||
}
|
|
||||||
if c.debug {
|
|
||||||
c.driver = dialect.Debug(c.driver, c.log)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug enables debug logging on the ent.Driver.
|
|
||||||
func Debug() Option {
|
|
||||||
return func(c *config) {
|
|
||||||
c.debug = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log sets the logging function for debug mode.
|
|
||||||
func Log(fn func(...interface{})) Option {
|
|
||||||
return func(c *config) {
|
|
||||||
c.log = fn
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Driver configures the client driver.
|
|
||||||
func Driver(driver dialect.Driver) Option {
|
|
||||||
return func(c *config) {
|
|
||||||
c.driver = driver
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
// Code generated by entc, DO NOT EDIT.
|
|
||||||
|
|
||||||
package ent
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
)
|
|
||||||
|
|
||||||
type clientCtxKey struct{}
|
|
||||||
|
|
||||||
// FromContext returns a Client stored inside a context, or nil if there isn't one.
|
|
||||||
func FromContext(ctx context.Context) *Client {
|
|
||||||
c, _ := ctx.Value(clientCtxKey{}).(*Client)
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewContext returns a new context with the given Client attached.
|
|
||||||
func NewContext(parent context.Context, c *Client) context.Context {
|
|
||||||
return context.WithValue(parent, clientCtxKey{}, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
type txCtxKey struct{}
|
|
||||||
|
|
||||||
// TxFromContext returns a Tx stored inside a context, or nil if there isn't one.
|
|
||||||
func TxFromContext(ctx context.Context) *Tx {
|
|
||||||
tx, _ := ctx.Value(txCtxKey{}).(*Tx)
|
|
||||||
return tx
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTxContext returns a new context with the given Tx attached.
|
|
||||||
func NewTxContext(parent context.Context, tx *Tx) context.Context {
|
|
||||||
return context.WithValue(parent, txCtxKey{}, tx)
|
|
||||||
}
|
|
||||||
439
ent/ent.go
439
ent/ent.go
|
|
@ -1,58 +1,91 @@
|
||||||
// Code generated by entc, DO NOT EDIT.
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
package ent
|
package ent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"goweb/ent/passwordtoken"
|
"reflect"
|
||||||
"goweb/ent/user"
|
"sync"
|
||||||
|
|
||||||
"entgo.io/ent"
|
"entgo.io/ent"
|
||||||
"entgo.io/ent/dialect/sql"
|
"entgo.io/ent/dialect/sql"
|
||||||
|
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||||
|
"github.com/camzawacki/personal-site/ent/passwordtoken"
|
||||||
|
"github.com/camzawacki/personal-site/ent/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ent aliases to avoid import conflicts in user's code.
|
// ent aliases to avoid import conflicts in user's code.
|
||||||
type (
|
type (
|
||||||
Op = ent.Op
|
Op = ent.Op
|
||||||
Hook = ent.Hook
|
Hook = ent.Hook
|
||||||
Value = ent.Value
|
Value = ent.Value
|
||||||
Query = ent.Query
|
Query = ent.Query
|
||||||
Policy = ent.Policy
|
QueryContext = ent.QueryContext
|
||||||
Mutator = ent.Mutator
|
Querier = ent.Querier
|
||||||
Mutation = ent.Mutation
|
QuerierFunc = ent.QuerierFunc
|
||||||
MutateFunc = ent.MutateFunc
|
Interceptor = ent.Interceptor
|
||||||
|
InterceptFunc = ent.InterceptFunc
|
||||||
|
Traverser = ent.Traverser
|
||||||
|
TraverseFunc = ent.TraverseFunc
|
||||||
|
Policy = ent.Policy
|
||||||
|
Mutator = ent.Mutator
|
||||||
|
Mutation = ent.Mutation
|
||||||
|
MutateFunc = ent.MutateFunc
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type clientCtxKey struct{}
|
||||||
|
|
||||||
|
// FromContext returns a Client stored inside a context, or nil if there isn't one.
|
||||||
|
func FromContext(ctx context.Context) *Client {
|
||||||
|
c, _ := ctx.Value(clientCtxKey{}).(*Client)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewContext returns a new context with the given Client attached.
|
||||||
|
func NewContext(parent context.Context, c *Client) context.Context {
|
||||||
|
return context.WithValue(parent, clientCtxKey{}, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
type txCtxKey struct{}
|
||||||
|
|
||||||
|
// TxFromContext returns a Tx stored inside a context, or nil if there isn't one.
|
||||||
|
func TxFromContext(ctx context.Context) *Tx {
|
||||||
|
tx, _ := ctx.Value(txCtxKey{}).(*Tx)
|
||||||
|
return tx
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTxContext returns a new context with the given Tx attached.
|
||||||
|
func NewTxContext(parent context.Context, tx *Tx) context.Context {
|
||||||
|
return context.WithValue(parent, txCtxKey{}, tx)
|
||||||
|
}
|
||||||
|
|
||||||
// OrderFunc applies an ordering on the sql selector.
|
// OrderFunc applies an ordering on the sql selector.
|
||||||
|
// Deprecated: Use Asc/Desc functions or the package builders instead.
|
||||||
type OrderFunc func(*sql.Selector)
|
type OrderFunc func(*sql.Selector)
|
||||||
|
|
||||||
// columnChecker returns a function indicates if the column exists in the given column.
|
var (
|
||||||
func columnChecker(table string) func(string) error {
|
initCheck sync.Once
|
||||||
checks := map[string]func(string) bool{
|
columnCheck sql.ColumnCheck
|
||||||
passwordtoken.Table: passwordtoken.ValidColumn,
|
)
|
||||||
user.Table: user.ValidColumn,
|
|
||||||
}
|
// checkColumn checks if the column exists in the given table.
|
||||||
check, ok := checks[table]
|
func checkColumn(t, c string) error {
|
||||||
if !ok {
|
initCheck.Do(func() {
|
||||||
return func(string) error {
|
columnCheck = sql.NewColumnCheck(map[string]func(string) bool{
|
||||||
return fmt.Errorf("unknown table %q", table)
|
passwordtoken.Table: passwordtoken.ValidColumn,
|
||||||
}
|
user.Table: user.ValidColumn,
|
||||||
}
|
})
|
||||||
return func(column string) error {
|
})
|
||||||
if !check(column) {
|
return columnCheck(t, c)
|
||||||
return fmt.Errorf("unknown column %q for table %q", column, table)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Asc applies the given fields in ASC order.
|
// Asc applies the given fields in ASC order.
|
||||||
func Asc(fields ...string) OrderFunc {
|
func Asc(fields ...string) func(*sql.Selector) {
|
||||||
return func(s *sql.Selector) {
|
return func(s *sql.Selector) {
|
||||||
check := columnChecker(s.TableName())
|
|
||||||
for _, f := range fields {
|
for _, f := range fields {
|
||||||
if err := check(f); err != nil {
|
if err := checkColumn(s.TableName(), f); err != nil {
|
||||||
s.AddError(&ValidationError{Name: f, err: fmt.Errorf("ent: %w", err)})
|
s.AddError(&ValidationError{Name: f, err: fmt.Errorf("ent: %w", err)})
|
||||||
}
|
}
|
||||||
s.OrderBy(sql.Asc(s.C(f)))
|
s.OrderBy(sql.Asc(s.C(f)))
|
||||||
|
|
@ -61,11 +94,10 @@ func Asc(fields ...string) OrderFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Desc applies the given fields in DESC order.
|
// Desc applies the given fields in DESC order.
|
||||||
func Desc(fields ...string) OrderFunc {
|
func Desc(fields ...string) func(*sql.Selector) {
|
||||||
return func(s *sql.Selector) {
|
return func(s *sql.Selector) {
|
||||||
check := columnChecker(s.TableName())
|
|
||||||
for _, f := range fields {
|
for _, f := range fields {
|
||||||
if err := check(f); err != nil {
|
if err := checkColumn(s.TableName(), f); err != nil {
|
||||||
s.AddError(&ValidationError{Name: f, err: fmt.Errorf("ent: %w", err)})
|
s.AddError(&ValidationError{Name: f, err: fmt.Errorf("ent: %w", err)})
|
||||||
}
|
}
|
||||||
s.OrderBy(sql.Desc(s.C(f)))
|
s.OrderBy(sql.Desc(s.C(f)))
|
||||||
|
|
@ -81,7 +113,6 @@ type AggregateFunc func(*sql.Selector) string
|
||||||
// GroupBy(field1, field2).
|
// GroupBy(field1, field2).
|
||||||
// Aggregate(ent.As(ent.Sum(field1), "sum_field1"), (ent.As(ent.Sum(field2), "sum_field2")).
|
// Aggregate(ent.As(ent.Sum(field1), "sum_field1"), (ent.As(ent.Sum(field2), "sum_field2")).
|
||||||
// Scan(ctx, &v)
|
// Scan(ctx, &v)
|
||||||
//
|
|
||||||
func As(fn AggregateFunc, end string) AggregateFunc {
|
func As(fn AggregateFunc, end string) AggregateFunc {
|
||||||
return func(s *sql.Selector) string {
|
return func(s *sql.Selector) string {
|
||||||
return sql.As(fn(s), end)
|
return sql.As(fn(s), end)
|
||||||
|
|
@ -98,8 +129,7 @@ func Count() AggregateFunc {
|
||||||
// Max applies the "max" aggregation function on the given field of each group.
|
// Max applies the "max" aggregation function on the given field of each group.
|
||||||
func Max(field string) AggregateFunc {
|
func Max(field string) AggregateFunc {
|
||||||
return func(s *sql.Selector) string {
|
return func(s *sql.Selector) string {
|
||||||
check := columnChecker(s.TableName())
|
if err := checkColumn(s.TableName(), field); err != nil {
|
||||||
if err := check(field); err != nil {
|
|
||||||
s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)})
|
s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)})
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
@ -110,8 +140,7 @@ func Max(field string) AggregateFunc {
|
||||||
// Mean applies the "mean" aggregation function on the given field of each group.
|
// Mean applies the "mean" aggregation function on the given field of each group.
|
||||||
func Mean(field string) AggregateFunc {
|
func Mean(field string) AggregateFunc {
|
||||||
return func(s *sql.Selector) string {
|
return func(s *sql.Selector) string {
|
||||||
check := columnChecker(s.TableName())
|
if err := checkColumn(s.TableName(), field); err != nil {
|
||||||
if err := check(field); err != nil {
|
|
||||||
s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)})
|
s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)})
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
@ -122,8 +151,7 @@ func Mean(field string) AggregateFunc {
|
||||||
// Min applies the "min" aggregation function on the given field of each group.
|
// Min applies the "min" aggregation function on the given field of each group.
|
||||||
func Min(field string) AggregateFunc {
|
func Min(field string) AggregateFunc {
|
||||||
return func(s *sql.Selector) string {
|
return func(s *sql.Selector) string {
|
||||||
check := columnChecker(s.TableName())
|
if err := checkColumn(s.TableName(), field); err != nil {
|
||||||
if err := check(field); err != nil {
|
|
||||||
s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)})
|
s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)})
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
@ -134,8 +162,7 @@ func Min(field string) AggregateFunc {
|
||||||
// Sum applies the "sum" aggregation function on the given field of each group.
|
// Sum applies the "sum" aggregation function on the given field of each group.
|
||||||
func Sum(field string) AggregateFunc {
|
func Sum(field string) AggregateFunc {
|
||||||
return func(s *sql.Selector) string {
|
return func(s *sql.Selector) string {
|
||||||
check := columnChecker(s.TableName())
|
if err := checkColumn(s.TableName(), field); err != nil {
|
||||||
if err := check(field); err != nil {
|
|
||||||
s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)})
|
s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)})
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
@ -143,7 +170,7 @@ func Sum(field string) AggregateFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidationError returns when validating a field fails.
|
// ValidationError returns when validating a field or edge fails.
|
||||||
type ValidationError struct {
|
type ValidationError struct {
|
||||||
Name string // Field or edge name.
|
Name string // Field or edge name.
|
||||||
err error
|
err error
|
||||||
|
|
@ -259,3 +286,325 @@ func IsConstraintError(err error) bool {
|
||||||
var e *ConstraintError
|
var e *ConstraintError
|
||||||
return errors.As(err, &e)
|
return errors.As(err, &e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// selector embedded by the different Select/GroupBy builders.
|
||||||
|
type selector struct {
|
||||||
|
label string
|
||||||
|
flds *[]string
|
||||||
|
fns []AggregateFunc
|
||||||
|
scan func(context.Context, any) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScanX is like Scan, but panics if an error occurs.
|
||||||
|
func (s *selector) ScanX(ctx context.Context, v any) {
|
||||||
|
if err := s.scan(ctx, v); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strings returns list of strings from a selector. It is only allowed when selecting one field.
|
||||||
|
func (s *selector) Strings(ctx context.Context) ([]string, error) {
|
||||||
|
if len(*s.flds) > 1 {
|
||||||
|
return nil, errors.New("ent: Strings is not achievable when selecting more than 1 field")
|
||||||
|
}
|
||||||
|
var v []string
|
||||||
|
if err := s.scan(ctx, &v); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringsX is like Strings, but panics if an error occurs.
|
||||||
|
func (s *selector) StringsX(ctx context.Context) []string {
|
||||||
|
v, err := s.Strings(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a single string from a selector. It is only allowed when selecting one field.
|
||||||
|
func (s *selector) String(ctx context.Context) (_ string, err error) {
|
||||||
|
var v []string
|
||||||
|
if v, err = s.Strings(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch len(v) {
|
||||||
|
case 1:
|
||||||
|
return v[0], nil
|
||||||
|
case 0:
|
||||||
|
err = &NotFoundError{s.label}
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("ent: Strings returned %d results when one was expected", len(v))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringX is like String, but panics if an error occurs.
|
||||||
|
func (s *selector) StringX(ctx context.Context) string {
|
||||||
|
v, err := s.String(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ints returns list of ints from a selector. It is only allowed when selecting one field.
|
||||||
|
func (s *selector) Ints(ctx context.Context) ([]int, error) {
|
||||||
|
if len(*s.flds) > 1 {
|
||||||
|
return nil, errors.New("ent: Ints is not achievable when selecting more than 1 field")
|
||||||
|
}
|
||||||
|
var v []int
|
||||||
|
if err := s.scan(ctx, &v); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntsX is like Ints, but panics if an error occurs.
|
||||||
|
func (s *selector) IntsX(ctx context.Context) []int {
|
||||||
|
v, err := s.Ints(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int returns a single int from a selector. It is only allowed when selecting one field.
|
||||||
|
func (s *selector) Int(ctx context.Context) (_ int, err error) {
|
||||||
|
var v []int
|
||||||
|
if v, err = s.Ints(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch len(v) {
|
||||||
|
case 1:
|
||||||
|
return v[0], nil
|
||||||
|
case 0:
|
||||||
|
err = &NotFoundError{s.label}
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("ent: Ints returned %d results when one was expected", len(v))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntX is like Int, but panics if an error occurs.
|
||||||
|
func (s *selector) IntX(ctx context.Context) int {
|
||||||
|
v, err := s.Int(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64s returns list of float64s from a selector. It is only allowed when selecting one field.
|
||||||
|
func (s *selector) Float64s(ctx context.Context) ([]float64, error) {
|
||||||
|
if len(*s.flds) > 1 {
|
||||||
|
return nil, errors.New("ent: Float64s is not achievable when selecting more than 1 field")
|
||||||
|
}
|
||||||
|
var v []float64
|
||||||
|
if err := s.scan(ctx, &v); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64sX is like Float64s, but panics if an error occurs.
|
||||||
|
func (s *selector) Float64sX(ctx context.Context) []float64 {
|
||||||
|
v, err := s.Float64s(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64 returns a single float64 from a selector. It is only allowed when selecting one field.
|
||||||
|
func (s *selector) Float64(ctx context.Context) (_ float64, err error) {
|
||||||
|
var v []float64
|
||||||
|
if v, err = s.Float64s(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch len(v) {
|
||||||
|
case 1:
|
||||||
|
return v[0], nil
|
||||||
|
case 0:
|
||||||
|
err = &NotFoundError{s.label}
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("ent: Float64s returned %d results when one was expected", len(v))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64X is like Float64, but panics if an error occurs.
|
||||||
|
func (s *selector) Float64X(ctx context.Context) float64 {
|
||||||
|
v, err := s.Float64(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bools returns list of bools from a selector. It is only allowed when selecting one field.
|
||||||
|
func (s *selector) Bools(ctx context.Context) ([]bool, error) {
|
||||||
|
if len(*s.flds) > 1 {
|
||||||
|
return nil, errors.New("ent: Bools is not achievable when selecting more than 1 field")
|
||||||
|
}
|
||||||
|
var v []bool
|
||||||
|
if err := s.scan(ctx, &v); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolsX is like Bools, but panics if an error occurs.
|
||||||
|
func (s *selector) BoolsX(ctx context.Context) []bool {
|
||||||
|
v, err := s.Bools(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool returns a single bool from a selector. It is only allowed when selecting one field.
|
||||||
|
func (s *selector) Bool(ctx context.Context) (_ bool, err error) {
|
||||||
|
var v []bool
|
||||||
|
if v, err = s.Bools(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch len(v) {
|
||||||
|
case 1:
|
||||||
|
return v[0], nil
|
||||||
|
case 0:
|
||||||
|
err = &NotFoundError{s.label}
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("ent: Bools returned %d results when one was expected", len(v))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolX is like Bool, but panics if an error occurs.
|
||||||
|
func (s *selector) BoolX(ctx context.Context) bool {
|
||||||
|
v, err := s.Bool(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// withHooks invokes the builder operation with the given hooks, if any.
|
||||||
|
func withHooks[V Value, M any, PM interface {
|
||||||
|
*M
|
||||||
|
Mutation
|
||||||
|
}](ctx context.Context, exec func(context.Context) (V, error), mutation PM, hooks []Hook) (value V, err error) {
|
||||||
|
if len(hooks) == 0 {
|
||||||
|
return exec(ctx)
|
||||||
|
}
|
||||||
|
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
|
||||||
|
mutationT, ok := any(m).(PM)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unexpected mutation type %T", m)
|
||||||
|
}
|
||||||
|
// Set the mutation to the builder.
|
||||||
|
*mutation = *mutationT
|
||||||
|
return exec(ctx)
|
||||||
|
})
|
||||||
|
for i := len(hooks) - 1; i >= 0; i-- {
|
||||||
|
if hooks[i] == nil {
|
||||||
|
return value, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)")
|
||||||
|
}
|
||||||
|
mut = hooks[i](mut)
|
||||||
|
}
|
||||||
|
v, err := mut.Mutate(ctx, mutation)
|
||||||
|
if err != nil {
|
||||||
|
return value, err
|
||||||
|
}
|
||||||
|
nv, ok := v.(V)
|
||||||
|
if !ok {
|
||||||
|
return value, fmt.Errorf("unexpected node type %T returned from %T", v, mutation)
|
||||||
|
}
|
||||||
|
return nv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// setContextOp returns a new context with the given QueryContext attached (including its op) in case it does not exist.
|
||||||
|
func setContextOp(ctx context.Context, qc *QueryContext, op string) context.Context {
|
||||||
|
if ent.QueryFromContext(ctx) == nil {
|
||||||
|
qc.Op = op
|
||||||
|
ctx = ent.NewQueryContext(ctx, qc)
|
||||||
|
}
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func querierAll[V Value, Q interface {
|
||||||
|
sqlAll(context.Context, ...queryHook) (V, error)
|
||||||
|
}]() Querier {
|
||||||
|
return QuerierFunc(func(ctx context.Context, q Query) (Value, error) {
|
||||||
|
query, ok := q.(Q)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unexpected query type %T", q)
|
||||||
|
}
|
||||||
|
return query.sqlAll(ctx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func querierCount[Q interface {
|
||||||
|
sqlCount(context.Context) (int, error)
|
||||||
|
}]() Querier {
|
||||||
|
return QuerierFunc(func(ctx context.Context, q Query) (Value, error) {
|
||||||
|
query, ok := q.(Q)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unexpected query type %T", q)
|
||||||
|
}
|
||||||
|
return query.sqlCount(ctx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func withInterceptors[V Value](ctx context.Context, q Query, qr Querier, inters []Interceptor) (v V, err error) {
|
||||||
|
for i := len(inters) - 1; i >= 0; i-- {
|
||||||
|
qr = inters[i].Intercept(qr)
|
||||||
|
}
|
||||||
|
rv, err := qr.Query(ctx, q)
|
||||||
|
if err != nil {
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
vt, ok := rv.(V)
|
||||||
|
if !ok {
|
||||||
|
return v, fmt.Errorf("unexpected type %T returned from %T. expected type: %T", vt, q, v)
|
||||||
|
}
|
||||||
|
return vt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanWithInterceptors[Q1 ent.Query, Q2 interface {
|
||||||
|
sqlScan(context.Context, Q1, any) error
|
||||||
|
}](ctx context.Context, rootQuery Q1, selectOrGroup Q2, inters []Interceptor, v any) error {
|
||||||
|
rv := reflect.ValueOf(v)
|
||||||
|
var qr Querier = QuerierFunc(func(ctx context.Context, q Query) (Value, error) {
|
||||||
|
query, ok := q.(Q1)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unexpected query type %T", q)
|
||||||
|
}
|
||||||
|
if err := selectOrGroup.sqlScan(ctx, query, v); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if k := rv.Kind(); k == reflect.Pointer && rv.Elem().CanInterface() {
|
||||||
|
return rv.Elem().Interface(), nil
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
})
|
||||||
|
for i := len(inters) - 1; i >= 0; i-- {
|
||||||
|
qr = inters[i].Intercept(qr)
|
||||||
|
}
|
||||||
|
vv, err := qr.Query(ctx, rootQuery)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch rv2 := reflect.ValueOf(vv); {
|
||||||
|
case rv.IsNil(), rv2.IsNil(), rv.Kind() != reflect.Pointer:
|
||||||
|
case rv.Type() == rv2.Type():
|
||||||
|
rv.Elem().Set(rv2.Elem())
|
||||||
|
case rv.Elem().Type() == rv2.Type():
|
||||||
|
rv.Elem().Set(rv2)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// queryHook describes an internal hook for the different sqlAll methods.
|
||||||
|
type queryHook func(context.Context, *sqlgraph.QuerySpec)
|
||||||
|
|
|
||||||
22
ent/entc.go
Normal file
22
ent/entc.go
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
//go:build ignore
|
||||||
|
// +build ignore
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"entgo.io/ent/entc"
|
||||||
|
"entgo.io/ent/entc/gen"
|
||||||
|
"github.com/camzawacki/personal-site/ent/admin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
err := entc.Generate("./schema",
|
||||||
|
&gen.Config{},
|
||||||
|
entc.Extensions(&admin.Extension{}),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("running ent codegen:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,14 +1,16 @@
|
||||||
// Code generated by entc, DO NOT EDIT.
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
package enttest
|
package enttest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"goweb/ent"
|
|
||||||
|
"github.com/camzawacki/personal-site/ent"
|
||||||
// required by schema hooks.
|
// required by schema hooks.
|
||||||
_ "goweb/ent/runtime"
|
_ "github.com/camzawacki/personal-site/ent/runtime"
|
||||||
|
|
||||||
"entgo.io/ent/dialect/sql/schema"
|
"entgo.io/ent/dialect/sql/schema"
|
||||||
|
"github.com/camzawacki/personal-site/ent/migrate"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
|
@ -16,7 +18,7 @@ type (
|
||||||
// testing.T and testing.B and used by enttest.
|
// testing.T and testing.B and used by enttest.
|
||||||
TestingT interface {
|
TestingT interface {
|
||||||
FailNow()
|
FailNow()
|
||||||
Error(...interface{})
|
Error(...any)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Option configures client creation.
|
// Option configures client creation.
|
||||||
|
|
@ -58,10 +60,7 @@ func Open(t TestingT, driverName, dataSourceName string, opts ...Option) *ent.Cl
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
if err := c.Schema.Create(context.Background(), o.migrateOpts...); err != nil {
|
migrateSchema(t, c, o)
|
||||||
t.Error(err)
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,9 +68,17 @@ func Open(t TestingT, driverName, dataSourceName string, opts ...Option) *ent.Cl
|
||||||
func NewClient(t TestingT, opts ...Option) *ent.Client {
|
func NewClient(t TestingT, opts ...Option) *ent.Client {
|
||||||
o := newOptions(opts)
|
o := newOptions(opts)
|
||||||
c := ent.NewClient(o.opts...)
|
c := ent.NewClient(o.opts...)
|
||||||
if err := c.Schema.Create(context.Background(), o.migrateOpts...); err != nil {
|
migrateSchema(t, c, o)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
func migrateSchema(t TestingT, c *ent.Client, o *options) {
|
||||||
|
tables, err := schema.CopyTables(migrate.Tables)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if err := migrate.Create(context.Background(), c.Schema, tables, o.migrateOpts...); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
return c
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
package ent
|
package ent
|
||||||
|
|
||||||
//go:generate go run -mod=mod entgo.io/ent/cmd/ent generate ./schema
|
//go:generate go run -mod=mod entc.go
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
// Code generated by entc, DO NOT EDIT.
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
package hook
|
package hook
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"goweb/ent"
|
|
||||||
|
"github.com/camzawacki/personal-site/ent"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The PasswordTokenFunc type is an adapter to allow the use of ordinary
|
// The PasswordTokenFunc type is an adapter to allow the use of ordinary
|
||||||
|
|
@ -14,11 +15,10 @@ type PasswordTokenFunc func(context.Context, *ent.PasswordTokenMutation) (ent.Va
|
||||||
|
|
||||||
// Mutate calls f(ctx, m).
|
// Mutate calls f(ctx, m).
|
||||||
func (f PasswordTokenFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
|
func (f PasswordTokenFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
|
||||||
mv, ok := m.(*ent.PasswordTokenMutation)
|
if mv, ok := m.(*ent.PasswordTokenMutation); ok {
|
||||||
if !ok {
|
return f(ctx, mv)
|
||||||
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.PasswordTokenMutation", m)
|
|
||||||
}
|
}
|
||||||
return f(ctx, mv)
|
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.PasswordTokenMutation", m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The UserFunc type is an adapter to allow the use of ordinary
|
// The UserFunc type is an adapter to allow the use of ordinary
|
||||||
|
|
@ -27,11 +27,10 @@ type UserFunc func(context.Context, *ent.UserMutation) (ent.Value, error)
|
||||||
|
|
||||||
// Mutate calls f(ctx, m).
|
// Mutate calls f(ctx, m).
|
||||||
func (f UserFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
|
func (f UserFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
|
||||||
mv, ok := m.(*ent.UserMutation)
|
if mv, ok := m.(*ent.UserMutation); ok {
|
||||||
if !ok {
|
return f(ctx, mv)
|
||||||
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.UserMutation", m)
|
|
||||||
}
|
}
|
||||||
return f(ctx, mv)
|
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.UserMutation", m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Condition is a hook condition function.
|
// Condition is a hook condition function.
|
||||||
|
|
@ -129,7 +128,6 @@ func HasFields(field string, fields ...string) Condition {
|
||||||
// If executes the given hook under condition.
|
// If executes the given hook under condition.
|
||||||
//
|
//
|
||||||
// hook.If(ComputeAverage, And(HasFields(...), HasAddedFields(...)))
|
// hook.If(ComputeAverage, And(HasFields(...), HasAddedFields(...)))
|
||||||
//
|
|
||||||
func If(hk ent.Hook, cond Condition) ent.Hook {
|
func If(hk ent.Hook, cond Condition) ent.Hook {
|
||||||
return func(next ent.Mutator) ent.Mutator {
|
return func(next ent.Mutator) ent.Mutator {
|
||||||
return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.Value, error) {
|
return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.Value, error) {
|
||||||
|
|
@ -144,7 +142,6 @@ func If(hk ent.Hook, cond Condition) ent.Hook {
|
||||||
// On executes the given hook only for the given operation.
|
// On executes the given hook only for the given operation.
|
||||||
//
|
//
|
||||||
// hook.On(Log, ent.Delete|ent.Create)
|
// hook.On(Log, ent.Delete|ent.Create)
|
||||||
//
|
|
||||||
func On(hk ent.Hook, op ent.Op) ent.Hook {
|
func On(hk ent.Hook, op ent.Op) ent.Hook {
|
||||||
return If(hk, HasOp(op))
|
return If(hk, HasOp(op))
|
||||||
}
|
}
|
||||||
|
|
@ -152,7 +149,6 @@ func On(hk ent.Hook, op ent.Op) ent.Hook {
|
||||||
// Unless skips the given hook only for the given operation.
|
// Unless skips the given hook only for the given operation.
|
||||||
//
|
//
|
||||||
// hook.Unless(Log, ent.Update|ent.UpdateOne)
|
// hook.Unless(Log, ent.Update|ent.UpdateOne)
|
||||||
//
|
|
||||||
func Unless(hk ent.Hook, op ent.Op) ent.Hook {
|
func Unless(hk ent.Hook, op ent.Op) ent.Hook {
|
||||||
return If(hk, Not(HasOp(op)))
|
return If(hk, Not(HasOp(op)))
|
||||||
}
|
}
|
||||||
|
|
@ -173,7 +169,6 @@ func FixedError(err error) ent.Hook {
|
||||||
// Reject(ent.Delete|ent.Update),
|
// Reject(ent.Delete|ent.Update),
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
//
|
|
||||||
func Reject(op ent.Op) ent.Hook {
|
func Reject(op ent.Op) ent.Hook {
|
||||||
hk := FixedError(fmt.Errorf("%s operation is not allowed", op))
|
hk := FixedError(fmt.Errorf("%s operation is not allowed", op))
|
||||||
return On(hk, op)
|
return On(hk, op)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Code generated by entc, DO NOT EDIT.
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
package migrate
|
package migrate
|
||||||
|
|
||||||
|
|
@ -28,17 +28,13 @@ var (
|
||||||
// and therefore, it's recommended to enable this option to get more
|
// and therefore, it's recommended to enable this option to get more
|
||||||
// flexibility in the schema changes.
|
// flexibility in the schema changes.
|
||||||
WithDropIndex = schema.WithDropIndex
|
WithDropIndex = schema.WithDropIndex
|
||||||
// WithFixture sets the foreign-key renaming option to the migration when upgrading
|
|
||||||
// ent from v0.1.0 (issue-#285). Defaults to false.
|
|
||||||
WithFixture = schema.WithFixture
|
|
||||||
// WithForeignKeys enables creating foreign-key in schema DDL. This defaults to true.
|
// WithForeignKeys enables creating foreign-key in schema DDL. This defaults to true.
|
||||||
WithForeignKeys = schema.WithForeignKeys
|
WithForeignKeys = schema.WithForeignKeys
|
||||||
)
|
)
|
||||||
|
|
||||||
// Schema is the API for creating, migrating and dropping a schema.
|
// Schema is the API for creating, migrating and dropping a schema.
|
||||||
type Schema struct {
|
type Schema struct {
|
||||||
drv dialect.Driver
|
drv dialect.Driver
|
||||||
universalID bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSchema creates a new schema client.
|
// NewSchema creates a new schema client.
|
||||||
|
|
@ -46,27 +42,23 @@ func NewSchema(drv dialect.Driver) *Schema { return &Schema{drv: drv} }
|
||||||
|
|
||||||
// Create creates all schema resources.
|
// Create creates all schema resources.
|
||||||
func (s *Schema) Create(ctx context.Context, opts ...schema.MigrateOption) error {
|
func (s *Schema) Create(ctx context.Context, opts ...schema.MigrateOption) error {
|
||||||
|
return Create(ctx, s, Tables, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create creates all table resources using the given schema driver.
|
||||||
|
func Create(ctx context.Context, s *Schema, tables []*schema.Table, opts ...schema.MigrateOption) error {
|
||||||
migrate, err := schema.NewMigrate(s.drv, opts...)
|
migrate, err := schema.NewMigrate(s.drv, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("ent/migrate: %w", err)
|
return fmt.Errorf("ent/migrate: %w", err)
|
||||||
}
|
}
|
||||||
return migrate.Create(ctx, Tables...)
|
return migrate.Create(ctx, tables...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteTo writes the schema changes to w instead of running them against the database.
|
// WriteTo writes the schema changes to w instead of running them against the database.
|
||||||
//
|
//
|
||||||
// if err := client.Schema.WriteTo(context.Background(), os.Stdout); err != nil {
|
// if err := client.Schema.WriteTo(context.Background(), os.Stdout); err != nil {
|
||||||
// log.Fatal(err)
|
// log.Fatal(err)
|
||||||
// }
|
// }
|
||||||
//
|
|
||||||
func (s *Schema) WriteTo(ctx context.Context, w io.Writer, opts ...schema.MigrateOption) error {
|
func (s *Schema) WriteTo(ctx context.Context, w io.Writer, opts ...schema.MigrateOption) error {
|
||||||
drv := &schema.WriteDriver{
|
return Create(ctx, &Schema{drv: &schema.WriteDriver{Writer: w, Driver: s.drv}}, Tables, opts...)
|
||||||
Writer: w,
|
|
||||||
Driver: s.drv,
|
|
||||||
}
|
|
||||||
migrate, err := schema.NewMigrate(drv, opts...)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("ent/migrate: %w", err)
|
|
||||||
}
|
|
||||||
return migrate.Create(ctx, Tables...)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Code generated by entc, DO NOT EDIT.
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
package migrate
|
package migrate
|
||||||
|
|
||||||
|
|
@ -11,9 +11,9 @@ var (
|
||||||
// PasswordTokensColumns holds the columns for the "password_tokens" table.
|
// PasswordTokensColumns holds the columns for the "password_tokens" table.
|
||||||
PasswordTokensColumns = []*schema.Column{
|
PasswordTokensColumns = []*schema.Column{
|
||||||
{Name: "id", Type: field.TypeInt, Increment: true},
|
{Name: "id", Type: field.TypeInt, Increment: true},
|
||||||
{Name: "hash", Type: field.TypeString},
|
{Name: "token", Type: field.TypeString},
|
||||||
{Name: "created_at", Type: field.TypeTime},
|
{Name: "created_at", Type: field.TypeTime},
|
||||||
{Name: "password_token_user", Type: field.TypeInt, Nullable: true},
|
{Name: "user_id", Type: field.TypeInt},
|
||||||
}
|
}
|
||||||
// PasswordTokensTable holds the schema information for the "password_tokens" table.
|
// PasswordTokensTable holds the schema information for the "password_tokens" table.
|
||||||
PasswordTokensTable = &schema.Table{
|
PasswordTokensTable = &schema.Table{
|
||||||
|
|
@ -25,7 +25,7 @@ var (
|
||||||
Symbol: "password_tokens_users_user",
|
Symbol: "password_tokens_users_user",
|
||||||
Columns: []*schema.Column{PasswordTokensColumns[3]},
|
Columns: []*schema.Column{PasswordTokensColumns[3]},
|
||||||
RefColumns: []*schema.Column{UsersColumns[0]},
|
RefColumns: []*schema.Column{UsersColumns[0]},
|
||||||
OnDelete: schema.SetNull,
|
OnDelete: schema.NoAction,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -35,6 +35,8 @@ var (
|
||||||
{Name: "name", Type: field.TypeString},
|
{Name: "name", Type: field.TypeString},
|
||||||
{Name: "email", Type: field.TypeString, Unique: true},
|
{Name: "email", Type: field.TypeString, Unique: true},
|
||||||
{Name: "password", Type: field.TypeString},
|
{Name: "password", Type: field.TypeString},
|
||||||
|
{Name: "verified", Type: field.TypeBool, Default: false},
|
||||||
|
{Name: "admin", Type: field.TypeBool, Default: false},
|
||||||
{Name: "created_at", Type: field.TypeTime},
|
{Name: "created_at", Type: field.TypeTime},
|
||||||
}
|
}
|
||||||
// UsersTable holds the schema information for the "users" table.
|
// UsersTable holds the schema information for the "users" table.
|
||||||
|
|
|
||||||
344
ent/mutation.go
344
ent/mutation.go
|
|
@ -1,17 +1,19 @@
|
||||||
// Code generated by entc, DO NOT EDIT.
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
package ent
|
package ent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"goweb/ent/passwordtoken"
|
|
||||||
"goweb/ent/predicate"
|
|
||||||
"goweb/ent/user"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"entgo.io/ent"
|
"entgo.io/ent"
|
||||||
|
"entgo.io/ent/dialect/sql"
|
||||||
|
"github.com/camzawacki/personal-site/ent/passwordtoken"
|
||||||
|
"github.com/camzawacki/personal-site/ent/predicate"
|
||||||
|
"github.com/camzawacki/personal-site/ent/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -33,7 +35,7 @@ type PasswordTokenMutation struct {
|
||||||
op Op
|
op Op
|
||||||
typ string
|
typ string
|
||||||
id *int
|
id *int
|
||||||
hash *string
|
token *string
|
||||||
created_at *time.Time
|
created_at *time.Time
|
||||||
clearedFields map[string]struct{}
|
clearedFields map[string]struct{}
|
||||||
user *int
|
user *int
|
||||||
|
|
@ -73,7 +75,7 @@ func withPasswordTokenID(id int) passwordtokenOption {
|
||||||
m.oldValue = func(ctx context.Context) (*PasswordToken, error) {
|
m.oldValue = func(ctx context.Context) (*PasswordToken, error) {
|
||||||
once.Do(func() {
|
once.Do(func() {
|
||||||
if m.done {
|
if m.done {
|
||||||
err = fmt.Errorf("querying old values post mutation is not allowed")
|
err = errors.New("querying old values post mutation is not allowed")
|
||||||
} else {
|
} else {
|
||||||
value, err = m.Client().PasswordToken.Get(ctx, id)
|
value, err = m.Client().PasswordToken.Get(ctx, id)
|
||||||
}
|
}
|
||||||
|
|
@ -106,7 +108,7 @@ func (m PasswordTokenMutation) Client() *Client {
|
||||||
// it returns an error otherwise.
|
// it returns an error otherwise.
|
||||||
func (m PasswordTokenMutation) Tx() (*Tx, error) {
|
func (m PasswordTokenMutation) Tx() (*Tx, error) {
|
||||||
if _, ok := m.driver.(*txDriver); !ok {
|
if _, ok := m.driver.(*txDriver); !ok {
|
||||||
return nil, fmt.Errorf("ent: mutation is not running in a transaction")
|
return nil, errors.New("ent: mutation is not running in a transaction")
|
||||||
}
|
}
|
||||||
tx := &Tx{config: m.config}
|
tx := &Tx{config: m.config}
|
||||||
tx.init()
|
tx.init()
|
||||||
|
|
@ -122,40 +124,95 @@ func (m *PasswordTokenMutation) ID() (id int, exists bool) {
|
||||||
return *m.id, true
|
return *m.id, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetHash sets the "hash" field.
|
// IDs queries the database and returns the entity ids that match the mutation's predicate.
|
||||||
func (m *PasswordTokenMutation) SetHash(s string) {
|
// That means, if the mutation is applied within a transaction with an isolation level such
|
||||||
m.hash = &s
|
// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated
|
||||||
|
// or updated by the mutation.
|
||||||
|
func (m *PasswordTokenMutation) IDs(ctx context.Context) ([]int, error) {
|
||||||
|
switch {
|
||||||
|
case m.op.Is(OpUpdateOne | OpDeleteOne):
|
||||||
|
id, exists := m.ID()
|
||||||
|
if exists {
|
||||||
|
return []int{id}, nil
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case m.op.Is(OpUpdate | OpDelete):
|
||||||
|
return m.Client().PasswordToken.Query().Where(m.predicates...).IDs(ctx)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash returns the value of the "hash" field in the mutation.
|
// SetToken sets the "token" field.
|
||||||
func (m *PasswordTokenMutation) Hash() (r string, exists bool) {
|
func (m *PasswordTokenMutation) SetToken(s string) {
|
||||||
v := m.hash
|
m.token = &s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token returns the value of the "token" field in the mutation.
|
||||||
|
func (m *PasswordTokenMutation) Token() (r string, exists bool) {
|
||||||
|
v := m.token
|
||||||
if v == nil {
|
if v == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return *v, true
|
return *v, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// OldHash returns the old "hash" field's value of the PasswordToken entity.
|
// OldToken returns the old "token" field's value of the PasswordToken entity.
|
||||||
// If the PasswordToken object wasn't provided to the builder, the object is fetched from the database.
|
// If the PasswordToken object wasn't provided to the builder, the object is fetched from the database.
|
||||||
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
|
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
|
||||||
func (m *PasswordTokenMutation) OldHash(ctx context.Context) (v string, err error) {
|
func (m *PasswordTokenMutation) OldToken(ctx context.Context) (v string, err error) {
|
||||||
if !m.op.Is(OpUpdateOne) {
|
if !m.op.Is(OpUpdateOne) {
|
||||||
return v, fmt.Errorf("OldHash is only allowed on UpdateOne operations")
|
return v, errors.New("OldToken is only allowed on UpdateOne operations")
|
||||||
}
|
}
|
||||||
if m.id == nil || m.oldValue == nil {
|
if m.id == nil || m.oldValue == nil {
|
||||||
return v, fmt.Errorf("OldHash requires an ID field in the mutation")
|
return v, errors.New("OldToken requires an ID field in the mutation")
|
||||||
}
|
}
|
||||||
oldValue, err := m.oldValue(ctx)
|
oldValue, err := m.oldValue(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return v, fmt.Errorf("querying old value for OldHash: %w", err)
|
return v, fmt.Errorf("querying old value for OldToken: %w", err)
|
||||||
}
|
}
|
||||||
return oldValue.Hash, nil
|
return oldValue.Token, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResetHash resets all changes to the "hash" field.
|
// ResetToken resets all changes to the "token" field.
|
||||||
func (m *PasswordTokenMutation) ResetHash() {
|
func (m *PasswordTokenMutation) ResetToken() {
|
||||||
m.hash = nil
|
m.token = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUserID sets the "user_id" field.
|
||||||
|
func (m *PasswordTokenMutation) SetUserID(i int) {
|
||||||
|
m.user = &i
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserID returns the value of the "user_id" field in the mutation.
|
||||||
|
func (m *PasswordTokenMutation) UserID() (r int, exists bool) {
|
||||||
|
v := m.user
|
||||||
|
if v == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return *v, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// OldUserID returns the old "user_id" field's value of the PasswordToken entity.
|
||||||
|
// If the PasswordToken object wasn't provided to the builder, the object is fetched from the database.
|
||||||
|
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
|
||||||
|
func (m *PasswordTokenMutation) OldUserID(ctx context.Context) (v int, err error) {
|
||||||
|
if !m.op.Is(OpUpdateOne) {
|
||||||
|
return v, errors.New("OldUserID is only allowed on UpdateOne operations")
|
||||||
|
}
|
||||||
|
if m.id == nil || m.oldValue == nil {
|
||||||
|
return v, errors.New("OldUserID requires an ID field in the mutation")
|
||||||
|
}
|
||||||
|
oldValue, err := m.oldValue(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return v, fmt.Errorf("querying old value for OldUserID: %w", err)
|
||||||
|
}
|
||||||
|
return oldValue.UserID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetUserID resets all changes to the "user_id" field.
|
||||||
|
func (m *PasswordTokenMutation) ResetUserID() {
|
||||||
|
m.user = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCreatedAt sets the "created_at" field.
|
// SetCreatedAt sets the "created_at" field.
|
||||||
|
|
@ -177,10 +234,10 @@ func (m *PasswordTokenMutation) CreatedAt() (r time.Time, exists bool) {
|
||||||
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
|
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
|
||||||
func (m *PasswordTokenMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) {
|
func (m *PasswordTokenMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) {
|
||||||
if !m.op.Is(OpUpdateOne) {
|
if !m.op.Is(OpUpdateOne) {
|
||||||
return v, fmt.Errorf("OldCreatedAt is only allowed on UpdateOne operations")
|
return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations")
|
||||||
}
|
}
|
||||||
if m.id == nil || m.oldValue == nil {
|
if m.id == nil || m.oldValue == nil {
|
||||||
return v, fmt.Errorf("OldCreatedAt requires an ID field in the mutation")
|
return v, errors.New("OldCreatedAt requires an ID field in the mutation")
|
||||||
}
|
}
|
||||||
oldValue, err := m.oldValue(ctx)
|
oldValue, err := m.oldValue(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -194,14 +251,10 @@ func (m *PasswordTokenMutation) ResetCreatedAt() {
|
||||||
m.created_at = nil
|
m.created_at = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetUserID sets the "user" edge to the User entity by id.
|
|
||||||
func (m *PasswordTokenMutation) SetUserID(id int) {
|
|
||||||
m.user = &id
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClearUser clears the "user" edge to the User entity.
|
// ClearUser clears the "user" edge to the User entity.
|
||||||
func (m *PasswordTokenMutation) ClearUser() {
|
func (m *PasswordTokenMutation) ClearUser() {
|
||||||
m.cleareduser = true
|
m.cleareduser = true
|
||||||
|
m.clearedFields[passwordtoken.FieldUserID] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserCleared reports if the "user" edge to the User entity was cleared.
|
// UserCleared reports if the "user" edge to the User entity was cleared.
|
||||||
|
|
@ -209,14 +262,6 @@ func (m *PasswordTokenMutation) UserCleared() bool {
|
||||||
return m.cleareduser
|
return m.cleareduser
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserID returns the "user" edge ID in the mutation.
|
|
||||||
func (m *PasswordTokenMutation) UserID() (id int, exists bool) {
|
|
||||||
if m.user != nil {
|
|
||||||
return *m.user, true
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserIDs returns the "user" edge IDs in the mutation.
|
// UserIDs returns the "user" edge IDs in the mutation.
|
||||||
// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use
|
// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use
|
||||||
// UserID instead. It exists only for internal usage by the builders.
|
// UserID instead. It exists only for internal usage by the builders.
|
||||||
|
|
@ -238,11 +283,26 @@ func (m *PasswordTokenMutation) Where(ps ...predicate.PasswordToken) {
|
||||||
m.predicates = append(m.predicates, ps...)
|
m.predicates = append(m.predicates, ps...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WhereP appends storage-level predicates to the PasswordTokenMutation builder. Using this method,
|
||||||
|
// users can use type-assertion to append predicates that do not depend on any generated package.
|
||||||
|
func (m *PasswordTokenMutation) WhereP(ps ...func(*sql.Selector)) {
|
||||||
|
p := make([]predicate.PasswordToken, len(ps))
|
||||||
|
for i := range ps {
|
||||||
|
p[i] = ps[i]
|
||||||
|
}
|
||||||
|
m.Where(p...)
|
||||||
|
}
|
||||||
|
|
||||||
// Op returns the operation name.
|
// Op returns the operation name.
|
||||||
func (m *PasswordTokenMutation) Op() Op {
|
func (m *PasswordTokenMutation) Op() Op {
|
||||||
return m.op
|
return m.op
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetOp allows setting the mutation operation.
|
||||||
|
func (m *PasswordTokenMutation) SetOp(op Op) {
|
||||||
|
m.op = op
|
||||||
|
}
|
||||||
|
|
||||||
// Type returns the node type of this mutation (PasswordToken).
|
// Type returns the node type of this mutation (PasswordToken).
|
||||||
func (m *PasswordTokenMutation) Type() string {
|
func (m *PasswordTokenMutation) Type() string {
|
||||||
return m.typ
|
return m.typ
|
||||||
|
|
@ -252,9 +312,12 @@ func (m *PasswordTokenMutation) Type() string {
|
||||||
// order to get all numeric fields that were incremented/decremented, call
|
// order to get all numeric fields that were incremented/decremented, call
|
||||||
// AddedFields().
|
// AddedFields().
|
||||||
func (m *PasswordTokenMutation) Fields() []string {
|
func (m *PasswordTokenMutation) Fields() []string {
|
||||||
fields := make([]string, 0, 2)
|
fields := make([]string, 0, 3)
|
||||||
if m.hash != nil {
|
if m.token != nil {
|
||||||
fields = append(fields, passwordtoken.FieldHash)
|
fields = append(fields, passwordtoken.FieldToken)
|
||||||
|
}
|
||||||
|
if m.user != nil {
|
||||||
|
fields = append(fields, passwordtoken.FieldUserID)
|
||||||
}
|
}
|
||||||
if m.created_at != nil {
|
if m.created_at != nil {
|
||||||
fields = append(fields, passwordtoken.FieldCreatedAt)
|
fields = append(fields, passwordtoken.FieldCreatedAt)
|
||||||
|
|
@ -267,8 +330,10 @@ func (m *PasswordTokenMutation) Fields() []string {
|
||||||
// schema.
|
// schema.
|
||||||
func (m *PasswordTokenMutation) Field(name string) (ent.Value, bool) {
|
func (m *PasswordTokenMutation) Field(name string) (ent.Value, bool) {
|
||||||
switch name {
|
switch name {
|
||||||
case passwordtoken.FieldHash:
|
case passwordtoken.FieldToken:
|
||||||
return m.Hash()
|
return m.Token()
|
||||||
|
case passwordtoken.FieldUserID:
|
||||||
|
return m.UserID()
|
||||||
case passwordtoken.FieldCreatedAt:
|
case passwordtoken.FieldCreatedAt:
|
||||||
return m.CreatedAt()
|
return m.CreatedAt()
|
||||||
}
|
}
|
||||||
|
|
@ -280,8 +345,10 @@ func (m *PasswordTokenMutation) Field(name string) (ent.Value, bool) {
|
||||||
// database failed.
|
// database failed.
|
||||||
func (m *PasswordTokenMutation) OldField(ctx context.Context, name string) (ent.Value, error) {
|
func (m *PasswordTokenMutation) OldField(ctx context.Context, name string) (ent.Value, error) {
|
||||||
switch name {
|
switch name {
|
||||||
case passwordtoken.FieldHash:
|
case passwordtoken.FieldToken:
|
||||||
return m.OldHash(ctx)
|
return m.OldToken(ctx)
|
||||||
|
case passwordtoken.FieldUserID:
|
||||||
|
return m.OldUserID(ctx)
|
||||||
case passwordtoken.FieldCreatedAt:
|
case passwordtoken.FieldCreatedAt:
|
||||||
return m.OldCreatedAt(ctx)
|
return m.OldCreatedAt(ctx)
|
||||||
}
|
}
|
||||||
|
|
@ -293,12 +360,19 @@ func (m *PasswordTokenMutation) OldField(ctx context.Context, name string) (ent.
|
||||||
// type.
|
// type.
|
||||||
func (m *PasswordTokenMutation) SetField(name string, value ent.Value) error {
|
func (m *PasswordTokenMutation) SetField(name string, value ent.Value) error {
|
||||||
switch name {
|
switch name {
|
||||||
case passwordtoken.FieldHash:
|
case passwordtoken.FieldToken:
|
||||||
v, ok := value.(string)
|
v, ok := value.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||||
}
|
}
|
||||||
m.SetHash(v)
|
m.SetToken(v)
|
||||||
|
return nil
|
||||||
|
case passwordtoken.FieldUserID:
|
||||||
|
v, ok := value.(int)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||||
|
}
|
||||||
|
m.SetUserID(v)
|
||||||
return nil
|
return nil
|
||||||
case passwordtoken.FieldCreatedAt:
|
case passwordtoken.FieldCreatedAt:
|
||||||
v, ok := value.(time.Time)
|
v, ok := value.(time.Time)
|
||||||
|
|
@ -314,13 +388,16 @@ func (m *PasswordTokenMutation) SetField(name string, value ent.Value) error {
|
||||||
// AddedFields returns all numeric fields that were incremented/decremented during
|
// AddedFields returns all numeric fields that were incremented/decremented during
|
||||||
// this mutation.
|
// this mutation.
|
||||||
func (m *PasswordTokenMutation) AddedFields() []string {
|
func (m *PasswordTokenMutation) AddedFields() []string {
|
||||||
return nil
|
var fields []string
|
||||||
|
return fields
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddedField returns the numeric value that was incremented/decremented on a field
|
// AddedField returns the numeric value that was incremented/decremented on a field
|
||||||
// with the given name. The second boolean return value indicates that this field
|
// with the given name. The second boolean return value indicates that this field
|
||||||
// was not set, or was not defined in the schema.
|
// was not set, or was not defined in the schema.
|
||||||
func (m *PasswordTokenMutation) AddedField(name string) (ent.Value, bool) {
|
func (m *PasswordTokenMutation) AddedField(name string) (ent.Value, bool) {
|
||||||
|
switch name {
|
||||||
|
}
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -356,8 +433,11 @@ func (m *PasswordTokenMutation) ClearField(name string) error {
|
||||||
// It returns an error if the field is not defined in the schema.
|
// It returns an error if the field is not defined in the schema.
|
||||||
func (m *PasswordTokenMutation) ResetField(name string) error {
|
func (m *PasswordTokenMutation) ResetField(name string) error {
|
||||||
switch name {
|
switch name {
|
||||||
case passwordtoken.FieldHash:
|
case passwordtoken.FieldToken:
|
||||||
m.ResetHash()
|
m.ResetToken()
|
||||||
|
return nil
|
||||||
|
case passwordtoken.FieldUserID:
|
||||||
|
m.ResetUserID()
|
||||||
return nil
|
return nil
|
||||||
case passwordtoken.FieldCreatedAt:
|
case passwordtoken.FieldCreatedAt:
|
||||||
m.ResetCreatedAt()
|
m.ResetCreatedAt()
|
||||||
|
|
@ -396,8 +476,6 @@ func (m *PasswordTokenMutation) RemovedEdges() []string {
|
||||||
// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with
|
// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with
|
||||||
// the given name in this mutation.
|
// the given name in this mutation.
|
||||||
func (m *PasswordTokenMutation) RemovedIDs(name string) []ent.Value {
|
func (m *PasswordTokenMutation) RemovedIDs(name string) []ent.Value {
|
||||||
switch name {
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -451,6 +529,8 @@ type UserMutation struct {
|
||||||
name *string
|
name *string
|
||||||
email *string
|
email *string
|
||||||
password *string
|
password *string
|
||||||
|
verified *bool
|
||||||
|
admin *bool
|
||||||
created_at *time.Time
|
created_at *time.Time
|
||||||
clearedFields map[string]struct{}
|
clearedFields map[string]struct{}
|
||||||
owner map[int]struct{}
|
owner map[int]struct{}
|
||||||
|
|
@ -491,7 +571,7 @@ func withUserID(id int) userOption {
|
||||||
m.oldValue = func(ctx context.Context) (*User, error) {
|
m.oldValue = func(ctx context.Context) (*User, error) {
|
||||||
once.Do(func() {
|
once.Do(func() {
|
||||||
if m.done {
|
if m.done {
|
||||||
err = fmt.Errorf("querying old values post mutation is not allowed")
|
err = errors.New("querying old values post mutation is not allowed")
|
||||||
} else {
|
} else {
|
||||||
value, err = m.Client().User.Get(ctx, id)
|
value, err = m.Client().User.Get(ctx, id)
|
||||||
}
|
}
|
||||||
|
|
@ -524,7 +604,7 @@ func (m UserMutation) Client() *Client {
|
||||||
// it returns an error otherwise.
|
// it returns an error otherwise.
|
||||||
func (m UserMutation) Tx() (*Tx, error) {
|
func (m UserMutation) Tx() (*Tx, error) {
|
||||||
if _, ok := m.driver.(*txDriver); !ok {
|
if _, ok := m.driver.(*txDriver); !ok {
|
||||||
return nil, fmt.Errorf("ent: mutation is not running in a transaction")
|
return nil, errors.New("ent: mutation is not running in a transaction")
|
||||||
}
|
}
|
||||||
tx := &Tx{config: m.config}
|
tx := &Tx{config: m.config}
|
||||||
tx.init()
|
tx.init()
|
||||||
|
|
@ -540,6 +620,25 @@ func (m *UserMutation) ID() (id int, exists bool) {
|
||||||
return *m.id, true
|
return *m.id, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IDs queries the database and returns the entity ids that match the mutation's predicate.
|
||||||
|
// That means, if the mutation is applied within a transaction with an isolation level such
|
||||||
|
// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated
|
||||||
|
// or updated by the mutation.
|
||||||
|
func (m *UserMutation) IDs(ctx context.Context) ([]int, error) {
|
||||||
|
switch {
|
||||||
|
case m.op.Is(OpUpdateOne | OpDeleteOne):
|
||||||
|
id, exists := m.ID()
|
||||||
|
if exists {
|
||||||
|
return []int{id}, nil
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case m.op.Is(OpUpdate | OpDelete):
|
||||||
|
return m.Client().User.Query().Where(m.predicates...).IDs(ctx)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SetName sets the "name" field.
|
// SetName sets the "name" field.
|
||||||
func (m *UserMutation) SetName(s string) {
|
func (m *UserMutation) SetName(s string) {
|
||||||
m.name = &s
|
m.name = &s
|
||||||
|
|
@ -559,10 +658,10 @@ func (m *UserMutation) Name() (r string, exists bool) {
|
||||||
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
|
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
|
||||||
func (m *UserMutation) OldName(ctx context.Context) (v string, err error) {
|
func (m *UserMutation) OldName(ctx context.Context) (v string, err error) {
|
||||||
if !m.op.Is(OpUpdateOne) {
|
if !m.op.Is(OpUpdateOne) {
|
||||||
return v, fmt.Errorf("OldName is only allowed on UpdateOne operations")
|
return v, errors.New("OldName is only allowed on UpdateOne operations")
|
||||||
}
|
}
|
||||||
if m.id == nil || m.oldValue == nil {
|
if m.id == nil || m.oldValue == nil {
|
||||||
return v, fmt.Errorf("OldName requires an ID field in the mutation")
|
return v, errors.New("OldName requires an ID field in the mutation")
|
||||||
}
|
}
|
||||||
oldValue, err := m.oldValue(ctx)
|
oldValue, err := m.oldValue(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -595,10 +694,10 @@ func (m *UserMutation) Email() (r string, exists bool) {
|
||||||
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
|
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
|
||||||
func (m *UserMutation) OldEmail(ctx context.Context) (v string, err error) {
|
func (m *UserMutation) OldEmail(ctx context.Context) (v string, err error) {
|
||||||
if !m.op.Is(OpUpdateOne) {
|
if !m.op.Is(OpUpdateOne) {
|
||||||
return v, fmt.Errorf("OldEmail is only allowed on UpdateOne operations")
|
return v, errors.New("OldEmail is only allowed on UpdateOne operations")
|
||||||
}
|
}
|
||||||
if m.id == nil || m.oldValue == nil {
|
if m.id == nil || m.oldValue == nil {
|
||||||
return v, fmt.Errorf("OldEmail requires an ID field in the mutation")
|
return v, errors.New("OldEmail requires an ID field in the mutation")
|
||||||
}
|
}
|
||||||
oldValue, err := m.oldValue(ctx)
|
oldValue, err := m.oldValue(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -631,10 +730,10 @@ func (m *UserMutation) Password() (r string, exists bool) {
|
||||||
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
|
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
|
||||||
func (m *UserMutation) OldPassword(ctx context.Context) (v string, err error) {
|
func (m *UserMutation) OldPassword(ctx context.Context) (v string, err error) {
|
||||||
if !m.op.Is(OpUpdateOne) {
|
if !m.op.Is(OpUpdateOne) {
|
||||||
return v, fmt.Errorf("OldPassword is only allowed on UpdateOne operations")
|
return v, errors.New("OldPassword is only allowed on UpdateOne operations")
|
||||||
}
|
}
|
||||||
if m.id == nil || m.oldValue == nil {
|
if m.id == nil || m.oldValue == nil {
|
||||||
return v, fmt.Errorf("OldPassword requires an ID field in the mutation")
|
return v, errors.New("OldPassword requires an ID field in the mutation")
|
||||||
}
|
}
|
||||||
oldValue, err := m.oldValue(ctx)
|
oldValue, err := m.oldValue(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -648,6 +747,78 @@ func (m *UserMutation) ResetPassword() {
|
||||||
m.password = nil
|
m.password = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetVerified sets the "verified" field.
|
||||||
|
func (m *UserMutation) SetVerified(b bool) {
|
||||||
|
m.verified = &b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verified returns the value of the "verified" field in the mutation.
|
||||||
|
func (m *UserMutation) Verified() (r bool, exists bool) {
|
||||||
|
v := m.verified
|
||||||
|
if v == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return *v, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// OldVerified returns the old "verified" field's value of the User entity.
|
||||||
|
// If the User object wasn't provided to the builder, the object is fetched from the database.
|
||||||
|
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
|
||||||
|
func (m *UserMutation) OldVerified(ctx context.Context) (v bool, err error) {
|
||||||
|
if !m.op.Is(OpUpdateOne) {
|
||||||
|
return v, errors.New("OldVerified is only allowed on UpdateOne operations")
|
||||||
|
}
|
||||||
|
if m.id == nil || m.oldValue == nil {
|
||||||
|
return v, errors.New("OldVerified requires an ID field in the mutation")
|
||||||
|
}
|
||||||
|
oldValue, err := m.oldValue(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return v, fmt.Errorf("querying old value for OldVerified: %w", err)
|
||||||
|
}
|
||||||
|
return oldValue.Verified, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetVerified resets all changes to the "verified" field.
|
||||||
|
func (m *UserMutation) ResetVerified() {
|
||||||
|
m.verified = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAdmin sets the "admin" field.
|
||||||
|
func (m *UserMutation) SetAdmin(b bool) {
|
||||||
|
m.admin = &b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Admin returns the value of the "admin" field in the mutation.
|
||||||
|
func (m *UserMutation) Admin() (r bool, exists bool) {
|
||||||
|
v := m.admin
|
||||||
|
if v == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return *v, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// OldAdmin returns the old "admin" field's value of the User entity.
|
||||||
|
// If the User object wasn't provided to the builder, the object is fetched from the database.
|
||||||
|
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
|
||||||
|
func (m *UserMutation) OldAdmin(ctx context.Context) (v bool, err error) {
|
||||||
|
if !m.op.Is(OpUpdateOne) {
|
||||||
|
return v, errors.New("OldAdmin is only allowed on UpdateOne operations")
|
||||||
|
}
|
||||||
|
if m.id == nil || m.oldValue == nil {
|
||||||
|
return v, errors.New("OldAdmin requires an ID field in the mutation")
|
||||||
|
}
|
||||||
|
oldValue, err := m.oldValue(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return v, fmt.Errorf("querying old value for OldAdmin: %w", err)
|
||||||
|
}
|
||||||
|
return oldValue.Admin, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetAdmin resets all changes to the "admin" field.
|
||||||
|
func (m *UserMutation) ResetAdmin() {
|
||||||
|
m.admin = nil
|
||||||
|
}
|
||||||
|
|
||||||
// SetCreatedAt sets the "created_at" field.
|
// SetCreatedAt sets the "created_at" field.
|
||||||
func (m *UserMutation) SetCreatedAt(t time.Time) {
|
func (m *UserMutation) SetCreatedAt(t time.Time) {
|
||||||
m.created_at = &t
|
m.created_at = &t
|
||||||
|
|
@ -667,10 +838,10 @@ func (m *UserMutation) CreatedAt() (r time.Time, exists bool) {
|
||||||
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
|
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
|
||||||
func (m *UserMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) {
|
func (m *UserMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) {
|
||||||
if !m.op.Is(OpUpdateOne) {
|
if !m.op.Is(OpUpdateOne) {
|
||||||
return v, fmt.Errorf("OldCreatedAt is only allowed on UpdateOne operations")
|
return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations")
|
||||||
}
|
}
|
||||||
if m.id == nil || m.oldValue == nil {
|
if m.id == nil || m.oldValue == nil {
|
||||||
return v, fmt.Errorf("OldCreatedAt requires an ID field in the mutation")
|
return v, errors.New("OldCreatedAt requires an ID field in the mutation")
|
||||||
}
|
}
|
||||||
oldValue, err := m.oldValue(ctx)
|
oldValue, err := m.oldValue(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -743,11 +914,26 @@ func (m *UserMutation) Where(ps ...predicate.User) {
|
||||||
m.predicates = append(m.predicates, ps...)
|
m.predicates = append(m.predicates, ps...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WhereP appends storage-level predicates to the UserMutation builder. Using this method,
|
||||||
|
// users can use type-assertion to append predicates that do not depend on any generated package.
|
||||||
|
func (m *UserMutation) WhereP(ps ...func(*sql.Selector)) {
|
||||||
|
p := make([]predicate.User, len(ps))
|
||||||
|
for i := range ps {
|
||||||
|
p[i] = ps[i]
|
||||||
|
}
|
||||||
|
m.Where(p...)
|
||||||
|
}
|
||||||
|
|
||||||
// Op returns the operation name.
|
// Op returns the operation name.
|
||||||
func (m *UserMutation) Op() Op {
|
func (m *UserMutation) Op() Op {
|
||||||
return m.op
|
return m.op
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetOp allows setting the mutation operation.
|
||||||
|
func (m *UserMutation) SetOp(op Op) {
|
||||||
|
m.op = op
|
||||||
|
}
|
||||||
|
|
||||||
// Type returns the node type of this mutation (User).
|
// Type returns the node type of this mutation (User).
|
||||||
func (m *UserMutation) Type() string {
|
func (m *UserMutation) Type() string {
|
||||||
return m.typ
|
return m.typ
|
||||||
|
|
@ -757,7 +943,7 @@ func (m *UserMutation) Type() string {
|
||||||
// order to get all numeric fields that were incremented/decremented, call
|
// order to get all numeric fields that were incremented/decremented, call
|
||||||
// AddedFields().
|
// AddedFields().
|
||||||
func (m *UserMutation) Fields() []string {
|
func (m *UserMutation) Fields() []string {
|
||||||
fields := make([]string, 0, 4)
|
fields := make([]string, 0, 6)
|
||||||
if m.name != nil {
|
if m.name != nil {
|
||||||
fields = append(fields, user.FieldName)
|
fields = append(fields, user.FieldName)
|
||||||
}
|
}
|
||||||
|
|
@ -767,6 +953,12 @@ func (m *UserMutation) Fields() []string {
|
||||||
if m.password != nil {
|
if m.password != nil {
|
||||||
fields = append(fields, user.FieldPassword)
|
fields = append(fields, user.FieldPassword)
|
||||||
}
|
}
|
||||||
|
if m.verified != nil {
|
||||||
|
fields = append(fields, user.FieldVerified)
|
||||||
|
}
|
||||||
|
if m.admin != nil {
|
||||||
|
fields = append(fields, user.FieldAdmin)
|
||||||
|
}
|
||||||
if m.created_at != nil {
|
if m.created_at != nil {
|
||||||
fields = append(fields, user.FieldCreatedAt)
|
fields = append(fields, user.FieldCreatedAt)
|
||||||
}
|
}
|
||||||
|
|
@ -784,6 +976,10 @@ func (m *UserMutation) Field(name string) (ent.Value, bool) {
|
||||||
return m.Email()
|
return m.Email()
|
||||||
case user.FieldPassword:
|
case user.FieldPassword:
|
||||||
return m.Password()
|
return m.Password()
|
||||||
|
case user.FieldVerified:
|
||||||
|
return m.Verified()
|
||||||
|
case user.FieldAdmin:
|
||||||
|
return m.Admin()
|
||||||
case user.FieldCreatedAt:
|
case user.FieldCreatedAt:
|
||||||
return m.CreatedAt()
|
return m.CreatedAt()
|
||||||
}
|
}
|
||||||
|
|
@ -801,6 +997,10 @@ func (m *UserMutation) OldField(ctx context.Context, name string) (ent.Value, er
|
||||||
return m.OldEmail(ctx)
|
return m.OldEmail(ctx)
|
||||||
case user.FieldPassword:
|
case user.FieldPassword:
|
||||||
return m.OldPassword(ctx)
|
return m.OldPassword(ctx)
|
||||||
|
case user.FieldVerified:
|
||||||
|
return m.OldVerified(ctx)
|
||||||
|
case user.FieldAdmin:
|
||||||
|
return m.OldAdmin(ctx)
|
||||||
case user.FieldCreatedAt:
|
case user.FieldCreatedAt:
|
||||||
return m.OldCreatedAt(ctx)
|
return m.OldCreatedAt(ctx)
|
||||||
}
|
}
|
||||||
|
|
@ -833,6 +1033,20 @@ func (m *UserMutation) SetField(name string, value ent.Value) error {
|
||||||
}
|
}
|
||||||
m.SetPassword(v)
|
m.SetPassword(v)
|
||||||
return nil
|
return nil
|
||||||
|
case user.FieldVerified:
|
||||||
|
v, ok := value.(bool)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||||
|
}
|
||||||
|
m.SetVerified(v)
|
||||||
|
return nil
|
||||||
|
case user.FieldAdmin:
|
||||||
|
v, ok := value.(bool)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||||
|
}
|
||||||
|
m.SetAdmin(v)
|
||||||
|
return nil
|
||||||
case user.FieldCreatedAt:
|
case user.FieldCreatedAt:
|
||||||
v, ok := value.(time.Time)
|
v, ok := value.(time.Time)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
@ -898,6 +1112,12 @@ func (m *UserMutation) ResetField(name string) error {
|
||||||
case user.FieldPassword:
|
case user.FieldPassword:
|
||||||
m.ResetPassword()
|
m.ResetPassword()
|
||||||
return nil
|
return nil
|
||||||
|
case user.FieldVerified:
|
||||||
|
m.ResetVerified()
|
||||||
|
return nil
|
||||||
|
case user.FieldAdmin:
|
||||||
|
m.ResetAdmin()
|
||||||
|
return nil
|
||||||
case user.FieldCreatedAt:
|
case user.FieldCreatedAt:
|
||||||
m.ResetCreatedAt()
|
m.ResetCreatedAt()
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
// Code generated by entc, DO NOT EDIT.
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
package ent
|
package ent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"goweb/ent/passwordtoken"
|
|
||||||
"goweb/ent/user"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"entgo.io/ent"
|
||||||
"entgo.io/ent/dialect/sql"
|
"entgo.io/ent/dialect/sql"
|
||||||
|
"github.com/camzawacki/personal-site/ent/passwordtoken"
|
||||||
|
"github.com/camzawacki/personal-site/ent/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PasswordToken is the model entity for the PasswordToken schema.
|
// PasswordToken is the model entity for the PasswordToken schema.
|
||||||
|
|
@ -17,14 +18,16 @@ type PasswordToken struct {
|
||||||
config `json:"-"`
|
config `json:"-"`
|
||||||
// ID of the ent.
|
// ID of the ent.
|
||||||
ID int `json:"id,omitempty"`
|
ID int `json:"id,omitempty"`
|
||||||
// Hash holds the value of the "hash" field.
|
// Token holds the value of the "token" field.
|
||||||
Hash string `json:"-"`
|
Token string `json:"-"`
|
||||||
|
// UserID holds the value of the "user_id" field.
|
||||||
|
UserID int `json:"user_id,omitempty"`
|
||||||
// CreatedAt holds the value of the "created_at" field.
|
// CreatedAt holds the value of the "created_at" field.
|
||||||
CreatedAt time.Time `json:"created_at,omitempty"`
|
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||||
// Edges holds the relations/edges for other nodes in the graph.
|
// Edges holds the relations/edges for other nodes in the graph.
|
||||||
// The values are being populated by the PasswordTokenQuery when eager-loading is set.
|
// The values are being populated by the PasswordTokenQuery when eager-loading is set.
|
||||||
Edges PasswordTokenEdges `json:"edges"`
|
Edges PasswordTokenEdges `json:"edges"`
|
||||||
password_token_user *int
|
selectValues sql.SelectValues
|
||||||
}
|
}
|
||||||
|
|
||||||
// PasswordTokenEdges holds the relations/edges for other nodes in the graph.
|
// PasswordTokenEdges holds the relations/edges for other nodes in the graph.
|
||||||
|
|
@ -39,32 +42,27 @@ type PasswordTokenEdges struct {
|
||||||
// UserOrErr returns the User value or an error if the edge
|
// UserOrErr returns the User value or an error if the edge
|
||||||
// was not loaded in eager-loading, or loaded but was not found.
|
// was not loaded in eager-loading, or loaded but was not found.
|
||||||
func (e PasswordTokenEdges) UserOrErr() (*User, error) {
|
func (e PasswordTokenEdges) UserOrErr() (*User, error) {
|
||||||
if e.loadedTypes[0] {
|
if e.User != nil {
|
||||||
if e.User == nil {
|
|
||||||
// The edge user was loaded in eager-loading,
|
|
||||||
// but was not found.
|
|
||||||
return nil, &NotFoundError{label: user.Label}
|
|
||||||
}
|
|
||||||
return e.User, nil
|
return e.User, nil
|
||||||
|
} else if e.loadedTypes[0] {
|
||||||
|
return nil, &NotFoundError{label: user.Label}
|
||||||
}
|
}
|
||||||
return nil, &NotLoadedError{edge: "user"}
|
return nil, &NotLoadedError{edge: "user"}
|
||||||
}
|
}
|
||||||
|
|
||||||
// scanValues returns the types for scanning values from sql.Rows.
|
// scanValues returns the types for scanning values from sql.Rows.
|
||||||
func (*PasswordToken) scanValues(columns []string) ([]interface{}, error) {
|
func (*PasswordToken) scanValues(columns []string) ([]any, error) {
|
||||||
values := make([]interface{}, len(columns))
|
values := make([]any, len(columns))
|
||||||
for i := range columns {
|
for i := range columns {
|
||||||
switch columns[i] {
|
switch columns[i] {
|
||||||
case passwordtoken.FieldID:
|
case passwordtoken.FieldID, passwordtoken.FieldUserID:
|
||||||
values[i] = new(sql.NullInt64)
|
values[i] = new(sql.NullInt64)
|
||||||
case passwordtoken.FieldHash:
|
case passwordtoken.FieldToken:
|
||||||
values[i] = new(sql.NullString)
|
values[i] = new(sql.NullString)
|
||||||
case passwordtoken.FieldCreatedAt:
|
case passwordtoken.FieldCreatedAt:
|
||||||
values[i] = new(sql.NullTime)
|
values[i] = new(sql.NullTime)
|
||||||
case passwordtoken.ForeignKeys[0]: // password_token_user
|
|
||||||
values[i] = new(sql.NullInt64)
|
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unexpected column %q for type PasswordToken", columns[i])
|
values[i] = new(sql.UnknownType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return values, nil
|
return values, nil
|
||||||
|
|
@ -72,7 +70,7 @@ func (*PasswordToken) scanValues(columns []string) ([]interface{}, error) {
|
||||||
|
|
||||||
// assignValues assigns the values that were returned from sql.Rows (after scanning)
|
// assignValues assigns the values that were returned from sql.Rows (after scanning)
|
||||||
// to the PasswordToken fields.
|
// to the PasswordToken fields.
|
||||||
func (pt *PasswordToken) assignValues(columns []string, values []interface{}) error {
|
func (_m *PasswordToken) assignValues(columns []string, values []any) error {
|
||||||
if m, n := len(values), len(columns); m < n {
|
if m, n := len(values), len(columns); m < n {
|
||||||
return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
|
return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
|
||||||
}
|
}
|
||||||
|
|
@ -83,71 +81,76 @@ func (pt *PasswordToken) assignValues(columns []string, values []interface{}) er
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("unexpected type %T for field id", value)
|
return fmt.Errorf("unexpected type %T for field id", value)
|
||||||
}
|
}
|
||||||
pt.ID = int(value.Int64)
|
_m.ID = int(value.Int64)
|
||||||
case passwordtoken.FieldHash:
|
case passwordtoken.FieldToken:
|
||||||
if value, ok := values[i].(*sql.NullString); !ok {
|
if value, ok := values[i].(*sql.NullString); !ok {
|
||||||
return fmt.Errorf("unexpected type %T for field hash", values[i])
|
return fmt.Errorf("unexpected type %T for field token", values[i])
|
||||||
} else if value.Valid {
|
} else if value.Valid {
|
||||||
pt.Hash = value.String
|
_m.Token = value.String
|
||||||
|
}
|
||||||
|
case passwordtoken.FieldUserID:
|
||||||
|
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field user_id", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.UserID = int(value.Int64)
|
||||||
}
|
}
|
||||||
case passwordtoken.FieldCreatedAt:
|
case passwordtoken.FieldCreatedAt:
|
||||||
if value, ok := values[i].(*sql.NullTime); !ok {
|
if value, ok := values[i].(*sql.NullTime); !ok {
|
||||||
return fmt.Errorf("unexpected type %T for field created_at", values[i])
|
return fmt.Errorf("unexpected type %T for field created_at", values[i])
|
||||||
} else if value.Valid {
|
} else if value.Valid {
|
||||||
pt.CreatedAt = value.Time
|
_m.CreatedAt = value.Time
|
||||||
}
|
|
||||||
case passwordtoken.ForeignKeys[0]:
|
|
||||||
if value, ok := values[i].(*sql.NullInt64); !ok {
|
|
||||||
return fmt.Errorf("unexpected type %T for edge-field password_token_user", value)
|
|
||||||
} else if value.Valid {
|
|
||||||
pt.password_token_user = new(int)
|
|
||||||
*pt.password_token_user = int(value.Int64)
|
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
_m.selectValues.Set(columns[i], values[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Value returns the ent.Value that was dynamically selected and assigned to the PasswordToken.
|
||||||
|
// This includes values selected through modifiers, order, etc.
|
||||||
|
func (_m *PasswordToken) Value(name string) (ent.Value, error) {
|
||||||
|
return _m.selectValues.Get(name)
|
||||||
|
}
|
||||||
|
|
||||||
// QueryUser queries the "user" edge of the PasswordToken entity.
|
// QueryUser queries the "user" edge of the PasswordToken entity.
|
||||||
func (pt *PasswordToken) QueryUser() *UserQuery {
|
func (_m *PasswordToken) QueryUser() *UserQuery {
|
||||||
return (&PasswordTokenClient{config: pt.config}).QueryUser(pt)
|
return NewPasswordTokenClient(_m.config).QueryUser(_m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update returns a builder for updating this PasswordToken.
|
// Update returns a builder for updating this PasswordToken.
|
||||||
// Note that you need to call PasswordToken.Unwrap() before calling this method if this PasswordToken
|
// Note that you need to call PasswordToken.Unwrap() before calling this method if this PasswordToken
|
||||||
// was returned from a transaction, and the transaction was committed or rolled back.
|
// was returned from a transaction, and the transaction was committed or rolled back.
|
||||||
func (pt *PasswordToken) Update() *PasswordTokenUpdateOne {
|
func (_m *PasswordToken) Update() *PasswordTokenUpdateOne {
|
||||||
return (&PasswordTokenClient{config: pt.config}).UpdateOne(pt)
|
return NewPasswordTokenClient(_m.config).UpdateOne(_m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unwrap unwraps the PasswordToken entity that was returned from a transaction after it was closed,
|
// Unwrap unwraps the PasswordToken entity that was returned from a transaction after it was closed,
|
||||||
// so that all future queries will be executed through the driver which created the transaction.
|
// so that all future queries will be executed through the driver which created the transaction.
|
||||||
func (pt *PasswordToken) Unwrap() *PasswordToken {
|
func (_m *PasswordToken) Unwrap() *PasswordToken {
|
||||||
tx, ok := pt.config.driver.(*txDriver)
|
_tx, ok := _m.config.driver.(*txDriver)
|
||||||
if !ok {
|
if !ok {
|
||||||
panic("ent: PasswordToken is not a transactional entity")
|
panic("ent: PasswordToken is not a transactional entity")
|
||||||
}
|
}
|
||||||
pt.config.driver = tx.drv
|
_m.config.driver = _tx.drv
|
||||||
return pt
|
return _m
|
||||||
}
|
}
|
||||||
|
|
||||||
// String implements the fmt.Stringer.
|
// String implements the fmt.Stringer.
|
||||||
func (pt *PasswordToken) String() string {
|
func (_m *PasswordToken) String() string {
|
||||||
var builder strings.Builder
|
var builder strings.Builder
|
||||||
builder.WriteString("PasswordToken(")
|
builder.WriteString("PasswordToken(")
|
||||||
builder.WriteString(fmt.Sprintf("id=%v", pt.ID))
|
builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
|
||||||
builder.WriteString(", hash=<sensitive>")
|
builder.WriteString("token=<sensitive>")
|
||||||
builder.WriteString(", created_at=")
|
builder.WriteString(", ")
|
||||||
builder.WriteString(pt.CreatedAt.Format(time.ANSIC))
|
builder.WriteString("user_id=")
|
||||||
|
builder.WriteString(fmt.Sprintf("%v", _m.UserID))
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("created_at=")
|
||||||
|
builder.WriteString(_m.CreatedAt.Format(time.ANSIC))
|
||||||
builder.WriteByte(')')
|
builder.WriteByte(')')
|
||||||
return builder.String()
|
return builder.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// PasswordTokens is a parsable slice of PasswordToken.
|
// PasswordTokens is a parsable slice of PasswordToken.
|
||||||
type PasswordTokens []*PasswordToken
|
type PasswordTokens []*PasswordToken
|
||||||
|
|
||||||
func (pt PasswordTokens) config(cfg config) {
|
|
||||||
for _i := range pt {
|
|
||||||
pt[_i].config = cfg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
// Code generated by entc, DO NOT EDIT.
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
package passwordtoken
|
package passwordtoken
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"entgo.io/ent"
|
||||||
|
"entgo.io/ent/dialect/sql"
|
||||||
|
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -11,8 +15,10 @@ const (
|
||||||
Label = "password_token"
|
Label = "password_token"
|
||||||
// FieldID holds the string denoting the id field in the database.
|
// FieldID holds the string denoting the id field in the database.
|
||||||
FieldID = "id"
|
FieldID = "id"
|
||||||
// FieldHash holds the string denoting the hash field in the database.
|
// FieldToken holds the string denoting the token field in the database.
|
||||||
FieldHash = "hash"
|
FieldToken = "token"
|
||||||
|
// FieldUserID holds the string denoting the user_id field in the database.
|
||||||
|
FieldUserID = "user_id"
|
||||||
// FieldCreatedAt holds the string denoting the created_at field in the database.
|
// FieldCreatedAt holds the string denoting the created_at field in the database.
|
||||||
FieldCreatedAt = "created_at"
|
FieldCreatedAt = "created_at"
|
||||||
// EdgeUser holds the string denoting the user edge name in mutations.
|
// EdgeUser holds the string denoting the user edge name in mutations.
|
||||||
|
|
@ -25,22 +31,17 @@ const (
|
||||||
// It exists in this package in order to avoid circular dependency with the "user" package.
|
// It exists in this package in order to avoid circular dependency with the "user" package.
|
||||||
UserInverseTable = "users"
|
UserInverseTable = "users"
|
||||||
// UserColumn is the table column denoting the user relation/edge.
|
// UserColumn is the table column denoting the user relation/edge.
|
||||||
UserColumn = "password_token_user"
|
UserColumn = "user_id"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Columns holds all SQL columns for passwordtoken fields.
|
// Columns holds all SQL columns for passwordtoken fields.
|
||||||
var Columns = []string{
|
var Columns = []string{
|
||||||
FieldID,
|
FieldID,
|
||||||
FieldHash,
|
FieldToken,
|
||||||
|
FieldUserID,
|
||||||
FieldCreatedAt,
|
FieldCreatedAt,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForeignKeys holds the SQL foreign-keys that are owned by the "password_tokens"
|
|
||||||
// table and are not defined as standalone fields in the schema.
|
|
||||||
var ForeignKeys = []string{
|
|
||||||
"password_token_user",
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidColumn reports if the column name is valid (part of the table columns).
|
// ValidColumn reports if the column name is valid (part of the table columns).
|
||||||
func ValidColumn(column string) bool {
|
func ValidColumn(column string) bool {
|
||||||
for i := range Columns {
|
for i := range Columns {
|
||||||
|
|
@ -48,17 +49,55 @@ func ValidColumn(column string) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i := range ForeignKeys {
|
|
||||||
if column == ForeignKeys[i] {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note that the variables below are initialized by the runtime
|
||||||
|
// package on the initialization of the application. Therefore,
|
||||||
|
// it should be imported in the main as follows:
|
||||||
|
//
|
||||||
|
// import _ "github.com/camzawacki/personal-site/ent/runtime"
|
||||||
var (
|
var (
|
||||||
// HashValidator is a validator for the "hash" field. It is called by the builders before save.
|
Hooks [1]ent.Hook
|
||||||
HashValidator func(string) error
|
// TokenValidator is a validator for the "token" field. It is called by the builders before save.
|
||||||
|
TokenValidator func(string) error
|
||||||
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
|
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
|
||||||
DefaultCreatedAt func() time.Time
|
DefaultCreatedAt func() time.Time
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// OrderOption defines the ordering options for the PasswordToken queries.
|
||||||
|
type OrderOption func(*sql.Selector)
|
||||||
|
|
||||||
|
// ByID orders the results by the id field.
|
||||||
|
func ByID(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldID, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByToken orders the results by the token field.
|
||||||
|
func ByToken(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldToken, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByUserID orders the results by the user_id field.
|
||||||
|
func ByUserID(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldUserID, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByCreatedAt orders the results by the created_at field.
|
||||||
|
func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldCreatedAt, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByUserField orders the results by user field.
|
||||||
|
func ByUserField(field string, opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return func(s *sql.Selector) {
|
||||||
|
sqlgraph.OrderByNeighborTerms(s, newUserStep(), sql.OrderByField(field, opts...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func newUserStep() *sqlgraph.Step {
|
||||||
|
return sqlgraph.NewStep(
|
||||||
|
sqlgraph.From(Table, FieldID),
|
||||||
|
sqlgraph.To(UserInverseTable, FieldID),
|
||||||
|
sqlgraph.Edge(sqlgraph.M2O, false, UserTable, UserColumn),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,297 +1,198 @@
|
||||||
// Code generated by entc, DO NOT EDIT.
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
package passwordtoken
|
package passwordtoken
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"goweb/ent/predicate"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"entgo.io/ent/dialect/sql"
|
"entgo.io/ent/dialect/sql"
|
||||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||||
|
"github.com/camzawacki/personal-site/ent/predicate"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ID filters vertices based on their ID field.
|
// ID filters vertices based on their ID field.
|
||||||
func ID(id int) predicate.PasswordToken {
|
func ID(id int) predicate.PasswordToken {
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
return predicate.PasswordToken(sql.FieldEQ(FieldID, id))
|
||||||
s.Where(sql.EQ(s.C(FieldID), id))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IDEQ applies the EQ predicate on the ID field.
|
// IDEQ applies the EQ predicate on the ID field.
|
||||||
func IDEQ(id int) predicate.PasswordToken {
|
func IDEQ(id int) predicate.PasswordToken {
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
return predicate.PasswordToken(sql.FieldEQ(FieldID, id))
|
||||||
s.Where(sql.EQ(s.C(FieldID), id))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IDNEQ applies the NEQ predicate on the ID field.
|
// IDNEQ applies the NEQ predicate on the ID field.
|
||||||
func IDNEQ(id int) predicate.PasswordToken {
|
func IDNEQ(id int) predicate.PasswordToken {
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
return predicate.PasswordToken(sql.FieldNEQ(FieldID, id))
|
||||||
s.Where(sql.NEQ(s.C(FieldID), id))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IDIn applies the In predicate on the ID field.
|
// IDIn applies the In predicate on the ID field.
|
||||||
func IDIn(ids ...int) predicate.PasswordToken {
|
func IDIn(ids ...int) predicate.PasswordToken {
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
return predicate.PasswordToken(sql.FieldIn(FieldID, ids...))
|
||||||
// if not arguments were provided, append the FALSE constants,
|
|
||||||
// since we can't apply "IN ()". This will make this predicate falsy.
|
|
||||||
if len(ids) == 0 {
|
|
||||||
s.Where(sql.False())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
v := make([]interface{}, len(ids))
|
|
||||||
for i := range v {
|
|
||||||
v[i] = ids[i]
|
|
||||||
}
|
|
||||||
s.Where(sql.In(s.C(FieldID), v...))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IDNotIn applies the NotIn predicate on the ID field.
|
// IDNotIn applies the NotIn predicate on the ID field.
|
||||||
func IDNotIn(ids ...int) predicate.PasswordToken {
|
func IDNotIn(ids ...int) predicate.PasswordToken {
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
return predicate.PasswordToken(sql.FieldNotIn(FieldID, ids...))
|
||||||
// if not arguments were provided, append the FALSE constants,
|
|
||||||
// since we can't apply "IN ()". This will make this predicate falsy.
|
|
||||||
if len(ids) == 0 {
|
|
||||||
s.Where(sql.False())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
v := make([]interface{}, len(ids))
|
|
||||||
for i := range v {
|
|
||||||
v[i] = ids[i]
|
|
||||||
}
|
|
||||||
s.Where(sql.NotIn(s.C(FieldID), v...))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IDGT applies the GT predicate on the ID field.
|
// IDGT applies the GT predicate on the ID field.
|
||||||
func IDGT(id int) predicate.PasswordToken {
|
func IDGT(id int) predicate.PasswordToken {
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
return predicate.PasswordToken(sql.FieldGT(FieldID, id))
|
||||||
s.Where(sql.GT(s.C(FieldID), id))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IDGTE applies the GTE predicate on the ID field.
|
// IDGTE applies the GTE predicate on the ID field.
|
||||||
func IDGTE(id int) predicate.PasswordToken {
|
func IDGTE(id int) predicate.PasswordToken {
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
return predicate.PasswordToken(sql.FieldGTE(FieldID, id))
|
||||||
s.Where(sql.GTE(s.C(FieldID), id))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IDLT applies the LT predicate on the ID field.
|
// IDLT applies the LT predicate on the ID field.
|
||||||
func IDLT(id int) predicate.PasswordToken {
|
func IDLT(id int) predicate.PasswordToken {
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
return predicate.PasswordToken(sql.FieldLT(FieldID, id))
|
||||||
s.Where(sql.LT(s.C(FieldID), id))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IDLTE applies the LTE predicate on the ID field.
|
// IDLTE applies the LTE predicate on the ID field.
|
||||||
func IDLTE(id int) predicate.PasswordToken {
|
func IDLTE(id int) predicate.PasswordToken {
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
return predicate.PasswordToken(sql.FieldLTE(FieldID, id))
|
||||||
s.Where(sql.LTE(s.C(FieldID), id))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash applies equality check predicate on the "hash" field. It's identical to HashEQ.
|
// Token applies equality check predicate on the "token" field. It's identical to TokenEQ.
|
||||||
func Hash(v string) predicate.PasswordToken {
|
func Token(v string) predicate.PasswordToken {
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
return predicate.PasswordToken(sql.FieldEQ(FieldToken, v))
|
||||||
s.Where(sql.EQ(s.C(FieldHash), v))
|
}
|
||||||
})
|
|
||||||
|
// UserID applies equality check predicate on the "user_id" field. It's identical to UserIDEQ.
|
||||||
|
func UserID(v int) predicate.PasswordToken {
|
||||||
|
return predicate.PasswordToken(sql.FieldEQ(FieldUserID, v))
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
|
// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
|
||||||
func CreatedAt(v time.Time) predicate.PasswordToken {
|
func CreatedAt(v time.Time) predicate.PasswordToken {
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
return predicate.PasswordToken(sql.FieldEQ(FieldCreatedAt, v))
|
||||||
s.Where(sql.EQ(s.C(FieldCreatedAt), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HashEQ applies the EQ predicate on the "hash" field.
|
// TokenEQ applies the EQ predicate on the "token" field.
|
||||||
func HashEQ(v string) predicate.PasswordToken {
|
func TokenEQ(v string) predicate.PasswordToken {
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
return predicate.PasswordToken(sql.FieldEQ(FieldToken, v))
|
||||||
s.Where(sql.EQ(s.C(FieldHash), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HashNEQ applies the NEQ predicate on the "hash" field.
|
// TokenNEQ applies the NEQ predicate on the "token" field.
|
||||||
func HashNEQ(v string) predicate.PasswordToken {
|
func TokenNEQ(v string) predicate.PasswordToken {
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
return predicate.PasswordToken(sql.FieldNEQ(FieldToken, v))
|
||||||
s.Where(sql.NEQ(s.C(FieldHash), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HashIn applies the In predicate on the "hash" field.
|
// TokenIn applies the In predicate on the "token" field.
|
||||||
func HashIn(vs ...string) predicate.PasswordToken {
|
func TokenIn(vs ...string) predicate.PasswordToken {
|
||||||
v := make([]interface{}, len(vs))
|
return predicate.PasswordToken(sql.FieldIn(FieldToken, vs...))
|
||||||
for i := range v {
|
|
||||||
v[i] = vs[i]
|
|
||||||
}
|
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
|
||||||
// if not arguments were provided, append the FALSE constants,
|
|
||||||
// since we can't apply "IN ()". This will make this predicate falsy.
|
|
||||||
if len(v) == 0 {
|
|
||||||
s.Where(sql.False())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.Where(sql.In(s.C(FieldHash), v...))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HashNotIn applies the NotIn predicate on the "hash" field.
|
// TokenNotIn applies the NotIn predicate on the "token" field.
|
||||||
func HashNotIn(vs ...string) predicate.PasswordToken {
|
func TokenNotIn(vs ...string) predicate.PasswordToken {
|
||||||
v := make([]interface{}, len(vs))
|
return predicate.PasswordToken(sql.FieldNotIn(FieldToken, vs...))
|
||||||
for i := range v {
|
|
||||||
v[i] = vs[i]
|
|
||||||
}
|
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
|
||||||
// if not arguments were provided, append the FALSE constants,
|
|
||||||
// since we can't apply "IN ()". This will make this predicate falsy.
|
|
||||||
if len(v) == 0 {
|
|
||||||
s.Where(sql.False())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.Where(sql.NotIn(s.C(FieldHash), v...))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HashGT applies the GT predicate on the "hash" field.
|
// TokenGT applies the GT predicate on the "token" field.
|
||||||
func HashGT(v string) predicate.PasswordToken {
|
func TokenGT(v string) predicate.PasswordToken {
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
return predicate.PasswordToken(sql.FieldGT(FieldToken, v))
|
||||||
s.Where(sql.GT(s.C(FieldHash), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HashGTE applies the GTE predicate on the "hash" field.
|
// TokenGTE applies the GTE predicate on the "token" field.
|
||||||
func HashGTE(v string) predicate.PasswordToken {
|
func TokenGTE(v string) predicate.PasswordToken {
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
return predicate.PasswordToken(sql.FieldGTE(FieldToken, v))
|
||||||
s.Where(sql.GTE(s.C(FieldHash), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HashLT applies the LT predicate on the "hash" field.
|
// TokenLT applies the LT predicate on the "token" field.
|
||||||
func HashLT(v string) predicate.PasswordToken {
|
func TokenLT(v string) predicate.PasswordToken {
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
return predicate.PasswordToken(sql.FieldLT(FieldToken, v))
|
||||||
s.Where(sql.LT(s.C(FieldHash), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HashLTE applies the LTE predicate on the "hash" field.
|
// TokenLTE applies the LTE predicate on the "token" field.
|
||||||
func HashLTE(v string) predicate.PasswordToken {
|
func TokenLTE(v string) predicate.PasswordToken {
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
return predicate.PasswordToken(sql.FieldLTE(FieldToken, v))
|
||||||
s.Where(sql.LTE(s.C(FieldHash), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HashContains applies the Contains predicate on the "hash" field.
|
// TokenContains applies the Contains predicate on the "token" field.
|
||||||
func HashContains(v string) predicate.PasswordToken {
|
func TokenContains(v string) predicate.PasswordToken {
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
return predicate.PasswordToken(sql.FieldContains(FieldToken, v))
|
||||||
s.Where(sql.Contains(s.C(FieldHash), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HashHasPrefix applies the HasPrefix predicate on the "hash" field.
|
// TokenHasPrefix applies the HasPrefix predicate on the "token" field.
|
||||||
func HashHasPrefix(v string) predicate.PasswordToken {
|
func TokenHasPrefix(v string) predicate.PasswordToken {
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
return predicate.PasswordToken(sql.FieldHasPrefix(FieldToken, v))
|
||||||
s.Where(sql.HasPrefix(s.C(FieldHash), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HashHasSuffix applies the HasSuffix predicate on the "hash" field.
|
// TokenHasSuffix applies the HasSuffix predicate on the "token" field.
|
||||||
func HashHasSuffix(v string) predicate.PasswordToken {
|
func TokenHasSuffix(v string) predicate.PasswordToken {
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
return predicate.PasswordToken(sql.FieldHasSuffix(FieldToken, v))
|
||||||
s.Where(sql.HasSuffix(s.C(FieldHash), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HashEqualFold applies the EqualFold predicate on the "hash" field.
|
// TokenEqualFold applies the EqualFold predicate on the "token" field.
|
||||||
func HashEqualFold(v string) predicate.PasswordToken {
|
func TokenEqualFold(v string) predicate.PasswordToken {
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
return predicate.PasswordToken(sql.FieldEqualFold(FieldToken, v))
|
||||||
s.Where(sql.EqualFold(s.C(FieldHash), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HashContainsFold applies the ContainsFold predicate on the "hash" field.
|
// TokenContainsFold applies the ContainsFold predicate on the "token" field.
|
||||||
func HashContainsFold(v string) predicate.PasswordToken {
|
func TokenContainsFold(v string) predicate.PasswordToken {
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
return predicate.PasswordToken(sql.FieldContainsFold(FieldToken, v))
|
||||||
s.Where(sql.ContainsFold(s.C(FieldHash), v))
|
}
|
||||||
})
|
|
||||||
|
// UserIDEQ applies the EQ predicate on the "user_id" field.
|
||||||
|
func UserIDEQ(v int) predicate.PasswordToken {
|
||||||
|
return predicate.PasswordToken(sql.FieldEQ(FieldUserID, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserIDNEQ applies the NEQ predicate on the "user_id" field.
|
||||||
|
func UserIDNEQ(v int) predicate.PasswordToken {
|
||||||
|
return predicate.PasswordToken(sql.FieldNEQ(FieldUserID, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserIDIn applies the In predicate on the "user_id" field.
|
||||||
|
func UserIDIn(vs ...int) predicate.PasswordToken {
|
||||||
|
return predicate.PasswordToken(sql.FieldIn(FieldUserID, vs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserIDNotIn applies the NotIn predicate on the "user_id" field.
|
||||||
|
func UserIDNotIn(vs ...int) predicate.PasswordToken {
|
||||||
|
return predicate.PasswordToken(sql.FieldNotIn(FieldUserID, vs...))
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
|
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
|
||||||
func CreatedAtEQ(v time.Time) predicate.PasswordToken {
|
func CreatedAtEQ(v time.Time) predicate.PasswordToken {
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
return predicate.PasswordToken(sql.FieldEQ(FieldCreatedAt, v))
|
||||||
s.Where(sql.EQ(s.C(FieldCreatedAt), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
|
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
|
||||||
func CreatedAtNEQ(v time.Time) predicate.PasswordToken {
|
func CreatedAtNEQ(v time.Time) predicate.PasswordToken {
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
return predicate.PasswordToken(sql.FieldNEQ(FieldCreatedAt, v))
|
||||||
s.Where(sql.NEQ(s.C(FieldCreatedAt), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatedAtIn applies the In predicate on the "created_at" field.
|
// CreatedAtIn applies the In predicate on the "created_at" field.
|
||||||
func CreatedAtIn(vs ...time.Time) predicate.PasswordToken {
|
func CreatedAtIn(vs ...time.Time) predicate.PasswordToken {
|
||||||
v := make([]interface{}, len(vs))
|
return predicate.PasswordToken(sql.FieldIn(FieldCreatedAt, vs...))
|
||||||
for i := range v {
|
|
||||||
v[i] = vs[i]
|
|
||||||
}
|
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
|
||||||
// if not arguments were provided, append the FALSE constants,
|
|
||||||
// since we can't apply "IN ()". This will make this predicate falsy.
|
|
||||||
if len(v) == 0 {
|
|
||||||
s.Where(sql.False())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.Where(sql.In(s.C(FieldCreatedAt), v...))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
|
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
|
||||||
func CreatedAtNotIn(vs ...time.Time) predicate.PasswordToken {
|
func CreatedAtNotIn(vs ...time.Time) predicate.PasswordToken {
|
||||||
v := make([]interface{}, len(vs))
|
return predicate.PasswordToken(sql.FieldNotIn(FieldCreatedAt, vs...))
|
||||||
for i := range v {
|
|
||||||
v[i] = vs[i]
|
|
||||||
}
|
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
|
||||||
// if not arguments were provided, append the FALSE constants,
|
|
||||||
// since we can't apply "IN ()". This will make this predicate falsy.
|
|
||||||
if len(v) == 0 {
|
|
||||||
s.Where(sql.False())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.Where(sql.NotIn(s.C(FieldCreatedAt), v...))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatedAtGT applies the GT predicate on the "created_at" field.
|
// CreatedAtGT applies the GT predicate on the "created_at" field.
|
||||||
func CreatedAtGT(v time.Time) predicate.PasswordToken {
|
func CreatedAtGT(v time.Time) predicate.PasswordToken {
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
return predicate.PasswordToken(sql.FieldGT(FieldCreatedAt, v))
|
||||||
s.Where(sql.GT(s.C(FieldCreatedAt), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
|
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
|
||||||
func CreatedAtGTE(v time.Time) predicate.PasswordToken {
|
func CreatedAtGTE(v time.Time) predicate.PasswordToken {
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
return predicate.PasswordToken(sql.FieldGTE(FieldCreatedAt, v))
|
||||||
s.Where(sql.GTE(s.C(FieldCreatedAt), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatedAtLT applies the LT predicate on the "created_at" field.
|
// CreatedAtLT applies the LT predicate on the "created_at" field.
|
||||||
func CreatedAtLT(v time.Time) predicate.PasswordToken {
|
func CreatedAtLT(v time.Time) predicate.PasswordToken {
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
return predicate.PasswordToken(sql.FieldLT(FieldCreatedAt, v))
|
||||||
s.Where(sql.LT(s.C(FieldCreatedAt), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
|
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
|
||||||
func CreatedAtLTE(v time.Time) predicate.PasswordToken {
|
func CreatedAtLTE(v time.Time) predicate.PasswordToken {
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
return predicate.PasswordToken(sql.FieldLTE(FieldCreatedAt, v))
|
||||||
s.Where(sql.LTE(s.C(FieldCreatedAt), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasUser applies the HasEdge predicate on the "user" edge.
|
// HasUser applies the HasEdge predicate on the "user" edge.
|
||||||
|
|
@ -299,7 +200,6 @@ func HasUser() predicate.PasswordToken {
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
return predicate.PasswordToken(func(s *sql.Selector) {
|
||||||
step := sqlgraph.NewStep(
|
step := sqlgraph.NewStep(
|
||||||
sqlgraph.From(Table, FieldID),
|
sqlgraph.From(Table, FieldID),
|
||||||
sqlgraph.To(UserTable, FieldID),
|
|
||||||
sqlgraph.Edge(sqlgraph.M2O, false, UserTable, UserColumn),
|
sqlgraph.Edge(sqlgraph.M2O, false, UserTable, UserColumn),
|
||||||
)
|
)
|
||||||
sqlgraph.HasNeighbors(s, step)
|
sqlgraph.HasNeighbors(s, step)
|
||||||
|
|
@ -309,11 +209,7 @@ func HasUser() predicate.PasswordToken {
|
||||||
// HasUserWith applies the HasEdge predicate on the "user" edge with a given conditions (other predicates).
|
// HasUserWith applies the HasEdge predicate on the "user" edge with a given conditions (other predicates).
|
||||||
func HasUserWith(preds ...predicate.User) predicate.PasswordToken {
|
func HasUserWith(preds ...predicate.User) predicate.PasswordToken {
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
return predicate.PasswordToken(func(s *sql.Selector) {
|
||||||
step := sqlgraph.NewStep(
|
step := newUserStep()
|
||||||
sqlgraph.From(Table, FieldID),
|
|
||||||
sqlgraph.To(UserInverseTable, FieldID),
|
|
||||||
sqlgraph.Edge(sqlgraph.M2O, false, UserTable, UserColumn),
|
|
||||||
)
|
|
||||||
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
|
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
|
||||||
for _, p := range preds {
|
for _, p := range preds {
|
||||||
p(s)
|
p(s)
|
||||||
|
|
@ -324,32 +220,15 @@ func HasUserWith(preds ...predicate.User) predicate.PasswordToken {
|
||||||
|
|
||||||
// And groups predicates with the AND operator between them.
|
// And groups predicates with the AND operator between them.
|
||||||
func And(predicates ...predicate.PasswordToken) predicate.PasswordToken {
|
func And(predicates ...predicate.PasswordToken) predicate.PasswordToken {
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
return predicate.PasswordToken(sql.AndPredicates(predicates...))
|
||||||
s1 := s.Clone().SetP(nil)
|
|
||||||
for _, p := range predicates {
|
|
||||||
p(s1)
|
|
||||||
}
|
|
||||||
s.Where(s1.P())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Or groups predicates with the OR operator between them.
|
// Or groups predicates with the OR operator between them.
|
||||||
func Or(predicates ...predicate.PasswordToken) predicate.PasswordToken {
|
func Or(predicates ...predicate.PasswordToken) predicate.PasswordToken {
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
return predicate.PasswordToken(sql.OrPredicates(predicates...))
|
||||||
s1 := s.Clone().SetP(nil)
|
|
||||||
for i, p := range predicates {
|
|
||||||
if i > 0 {
|
|
||||||
s1.Or()
|
|
||||||
}
|
|
||||||
p(s1)
|
|
||||||
}
|
|
||||||
s.Where(s1.P())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not applies the not operator on the given predicate.
|
// Not applies the not operator on the given predicate.
|
||||||
func Not(p predicate.PasswordToken) predicate.PasswordToken {
|
func Not(p predicate.PasswordToken) predicate.PasswordToken {
|
||||||
return predicate.PasswordToken(func(s *sql.Selector) {
|
return predicate.PasswordToken(sql.NotPredicates(p))
|
||||||
p(s.Not())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Code generated by entc, DO NOT EDIT.
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
package ent
|
package ent
|
||||||
|
|
||||||
|
|
@ -6,12 +6,12 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"goweb/ent/passwordtoken"
|
|
||||||
"goweb/ent/user"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||||
"entgo.io/ent/schema/field"
|
"entgo.io/ent/schema/field"
|
||||||
|
"github.com/camzawacki/personal-site/ent/passwordtoken"
|
||||||
|
"github.com/camzawacki/personal-site/ent/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PasswordTokenCreate is the builder for creating a PasswordToken entity.
|
// PasswordTokenCreate is the builder for creating a PasswordToken entity.
|
||||||
|
|
@ -21,87 +21,53 @@ type PasswordTokenCreate struct {
|
||||||
hooks []Hook
|
hooks []Hook
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetHash sets the "hash" field.
|
// SetToken sets the "token" field.
|
||||||
func (ptc *PasswordTokenCreate) SetHash(s string) *PasswordTokenCreate {
|
func (_c *PasswordTokenCreate) SetToken(v string) *PasswordTokenCreate {
|
||||||
ptc.mutation.SetHash(s)
|
_c.mutation.SetToken(v)
|
||||||
return ptc
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUserID sets the "user_id" field.
|
||||||
|
func (_c *PasswordTokenCreate) SetUserID(v int) *PasswordTokenCreate {
|
||||||
|
_c.mutation.SetUserID(v)
|
||||||
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCreatedAt sets the "created_at" field.
|
// SetCreatedAt sets the "created_at" field.
|
||||||
func (ptc *PasswordTokenCreate) SetCreatedAt(t time.Time) *PasswordTokenCreate {
|
func (_c *PasswordTokenCreate) SetCreatedAt(v time.Time) *PasswordTokenCreate {
|
||||||
ptc.mutation.SetCreatedAt(t)
|
_c.mutation.SetCreatedAt(v)
|
||||||
return ptc
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
|
// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
|
||||||
func (ptc *PasswordTokenCreate) SetNillableCreatedAt(t *time.Time) *PasswordTokenCreate {
|
func (_c *PasswordTokenCreate) SetNillableCreatedAt(v *time.Time) *PasswordTokenCreate {
|
||||||
if t != nil {
|
if v != nil {
|
||||||
ptc.SetCreatedAt(*t)
|
_c.SetCreatedAt(*v)
|
||||||
}
|
}
|
||||||
return ptc
|
return _c
|
||||||
}
|
|
||||||
|
|
||||||
// SetUserID sets the "user" edge to the User entity by ID.
|
|
||||||
func (ptc *PasswordTokenCreate) SetUserID(id int) *PasswordTokenCreate {
|
|
||||||
ptc.mutation.SetUserID(id)
|
|
||||||
return ptc
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetUser sets the "user" edge to the User entity.
|
// SetUser sets the "user" edge to the User entity.
|
||||||
func (ptc *PasswordTokenCreate) SetUser(u *User) *PasswordTokenCreate {
|
func (_c *PasswordTokenCreate) SetUser(v *User) *PasswordTokenCreate {
|
||||||
return ptc.SetUserID(u.ID)
|
return _c.SetUserID(v.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mutation returns the PasswordTokenMutation object of the builder.
|
// Mutation returns the PasswordTokenMutation object of the builder.
|
||||||
func (ptc *PasswordTokenCreate) Mutation() *PasswordTokenMutation {
|
func (_c *PasswordTokenCreate) Mutation() *PasswordTokenMutation {
|
||||||
return ptc.mutation
|
return _c.mutation
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save creates the PasswordToken in the database.
|
// Save creates the PasswordToken in the database.
|
||||||
func (ptc *PasswordTokenCreate) Save(ctx context.Context) (*PasswordToken, error) {
|
func (_c *PasswordTokenCreate) Save(ctx context.Context) (*PasswordToken, error) {
|
||||||
var (
|
if err := _c.defaults(); err != nil {
|
||||||
err error
|
return nil, err
|
||||||
node *PasswordToken
|
|
||||||
)
|
|
||||||
ptc.defaults()
|
|
||||||
if len(ptc.hooks) == 0 {
|
|
||||||
if err = ptc.check(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
node, err = ptc.sqlSave(ctx)
|
|
||||||
} else {
|
|
||||||
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
|
|
||||||
mutation, ok := m.(*PasswordTokenMutation)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("unexpected mutation type %T", m)
|
|
||||||
}
|
|
||||||
if err = ptc.check(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ptc.mutation = mutation
|
|
||||||
if node, err = ptc.sqlSave(ctx); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
mutation.id = &node.ID
|
|
||||||
mutation.done = true
|
|
||||||
return node, err
|
|
||||||
})
|
|
||||||
for i := len(ptc.hooks) - 1; i >= 0; i-- {
|
|
||||||
if ptc.hooks[i] == nil {
|
|
||||||
return nil, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)")
|
|
||||||
}
|
|
||||||
mut = ptc.hooks[i](mut)
|
|
||||||
}
|
|
||||||
if _, err := mut.Mutate(ctx, ptc.mutation); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return node, err
|
return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveX calls Save and panics if Save returns an error.
|
// SaveX calls Save and panics if Save returns an error.
|
||||||
func (ptc *PasswordTokenCreate) SaveX(ctx context.Context) *PasswordToken {
|
func (_c *PasswordTokenCreate) SaveX(ctx context.Context) *PasswordToken {
|
||||||
v, err := ptc.Save(ctx)
|
v, err := _c.Save(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
@ -109,86 +75,84 @@ func (ptc *PasswordTokenCreate) SaveX(ctx context.Context) *PasswordToken {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exec executes the query.
|
// Exec executes the query.
|
||||||
func (ptc *PasswordTokenCreate) Exec(ctx context.Context) error {
|
func (_c *PasswordTokenCreate) Exec(ctx context.Context) error {
|
||||||
_, err := ptc.Save(ctx)
|
_, err := _c.Save(ctx)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecX is like Exec, but panics if an error occurs.
|
// ExecX is like Exec, but panics if an error occurs.
|
||||||
func (ptc *PasswordTokenCreate) ExecX(ctx context.Context) {
|
func (_c *PasswordTokenCreate) ExecX(ctx context.Context) {
|
||||||
if err := ptc.Exec(ctx); err != nil {
|
if err := _c.Exec(ctx); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaults sets the default values of the builder before save.
|
// defaults sets the default values of the builder before save.
|
||||||
func (ptc *PasswordTokenCreate) defaults() {
|
func (_c *PasswordTokenCreate) defaults() error {
|
||||||
if _, ok := ptc.mutation.CreatedAt(); !ok {
|
if _, ok := _c.mutation.CreatedAt(); !ok {
|
||||||
v := passwordtoken.DefaultCreatedAt()
|
if passwordtoken.DefaultCreatedAt == nil {
|
||||||
ptc.mutation.SetCreatedAt(v)
|
return fmt.Errorf("ent: uninitialized passwordtoken.DefaultCreatedAt (forgotten import ent/runtime?)")
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check runs all checks and user-defined validators on the builder.
|
|
||||||
func (ptc *PasswordTokenCreate) check() error {
|
|
||||||
if _, ok := ptc.mutation.Hash(); !ok {
|
|
||||||
return &ValidationError{Name: "hash", err: errors.New(`ent: missing required field "hash"`)}
|
|
||||||
}
|
|
||||||
if v, ok := ptc.mutation.Hash(); ok {
|
|
||||||
if err := passwordtoken.HashValidator(v); err != nil {
|
|
||||||
return &ValidationError{Name: "hash", err: fmt.Errorf(`ent: validator failed for field "hash": %w`, err)}
|
|
||||||
}
|
}
|
||||||
}
|
v := passwordtoken.DefaultCreatedAt()
|
||||||
if _, ok := ptc.mutation.CreatedAt(); !ok {
|
_c.mutation.SetCreatedAt(v)
|
||||||
return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "created_at"`)}
|
|
||||||
}
|
|
||||||
if _, ok := ptc.mutation.UserID(); !ok {
|
|
||||||
return &ValidationError{Name: "user", err: errors.New("ent: missing required edge \"user\"")}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ptc *PasswordTokenCreate) sqlSave(ctx context.Context) (*PasswordToken, error) {
|
// check runs all checks and user-defined validators on the builder.
|
||||||
_node, _spec := ptc.createSpec()
|
func (_c *PasswordTokenCreate) check() error {
|
||||||
if err := sqlgraph.CreateNode(ctx, ptc.driver, _spec); err != nil {
|
if _, ok := _c.mutation.Token(); !ok {
|
||||||
|
return &ValidationError{Name: "token", err: errors.New(`ent: missing required field "PasswordToken.token"`)}
|
||||||
|
}
|
||||||
|
if v, ok := _c.mutation.Token(); ok {
|
||||||
|
if err := passwordtoken.TokenValidator(v); err != nil {
|
||||||
|
return &ValidationError{Name: "token", err: fmt.Errorf(`ent: validator failed for field "PasswordToken.token": %w`, err)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ok := _c.mutation.UserID(); !ok {
|
||||||
|
return &ValidationError{Name: "user_id", err: errors.New(`ent: missing required field "PasswordToken.user_id"`)}
|
||||||
|
}
|
||||||
|
if _, ok := _c.mutation.CreatedAt(); !ok {
|
||||||
|
return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "PasswordToken.created_at"`)}
|
||||||
|
}
|
||||||
|
if len(_c.mutation.UserIDs()) == 0 {
|
||||||
|
return &ValidationError{Name: "user", err: errors.New(`ent: missing required edge "PasswordToken.user"`)}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *PasswordTokenCreate) sqlSave(ctx context.Context) (*PasswordToken, error) {
|
||||||
|
if err := _c.check(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_node, _spec := _c.createSpec()
|
||||||
|
if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil {
|
||||||
if sqlgraph.IsConstraintError(err) {
|
if sqlgraph.IsConstraintError(err) {
|
||||||
err = &ConstraintError{err.Error(), err}
|
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
id := _spec.ID.Value.(int64)
|
id := _spec.ID.Value.(int64)
|
||||||
_node.ID = int(id)
|
_node.ID = int(id)
|
||||||
|
_c.mutation.id = &_node.ID
|
||||||
|
_c.mutation.done = true
|
||||||
return _node, nil
|
return _node, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ptc *PasswordTokenCreate) createSpec() (*PasswordToken, *sqlgraph.CreateSpec) {
|
func (_c *PasswordTokenCreate) createSpec() (*PasswordToken, *sqlgraph.CreateSpec) {
|
||||||
var (
|
var (
|
||||||
_node = &PasswordToken{config: ptc.config}
|
_node = &PasswordToken{config: _c.config}
|
||||||
_spec = &sqlgraph.CreateSpec{
|
_spec = sqlgraph.NewCreateSpec(passwordtoken.Table, sqlgraph.NewFieldSpec(passwordtoken.FieldID, field.TypeInt))
|
||||||
Table: passwordtoken.Table,
|
|
||||||
ID: &sqlgraph.FieldSpec{
|
|
||||||
Type: field.TypeInt,
|
|
||||||
Column: passwordtoken.FieldID,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
if value, ok := ptc.mutation.Hash(); ok {
|
if value, ok := _c.mutation.Token(); ok {
|
||||||
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
|
_spec.SetField(passwordtoken.FieldToken, field.TypeString, value)
|
||||||
Type: field.TypeString,
|
_node.Token = value
|
||||||
Value: value,
|
|
||||||
Column: passwordtoken.FieldHash,
|
|
||||||
})
|
|
||||||
_node.Hash = value
|
|
||||||
}
|
}
|
||||||
if value, ok := ptc.mutation.CreatedAt(); ok {
|
if value, ok := _c.mutation.CreatedAt(); ok {
|
||||||
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
|
_spec.SetField(passwordtoken.FieldCreatedAt, field.TypeTime, value)
|
||||||
Type: field.TypeTime,
|
|
||||||
Value: value,
|
|
||||||
Column: passwordtoken.FieldCreatedAt,
|
|
||||||
})
|
|
||||||
_node.CreatedAt = value
|
_node.CreatedAt = value
|
||||||
}
|
}
|
||||||
if nodes := ptc.mutation.UserIDs(); len(nodes) > 0 {
|
if nodes := _c.mutation.UserIDs(); len(nodes) > 0 {
|
||||||
edge := &sqlgraph.EdgeSpec{
|
edge := &sqlgraph.EdgeSpec{
|
||||||
Rel: sqlgraph.M2O,
|
Rel: sqlgraph.M2O,
|
||||||
Inverse: false,
|
Inverse: false,
|
||||||
|
|
@ -196,16 +160,13 @@ func (ptc *PasswordTokenCreate) createSpec() (*PasswordToken, *sqlgraph.CreateSp
|
||||||
Columns: []string{passwordtoken.UserColumn},
|
Columns: []string{passwordtoken.UserColumn},
|
||||||
Bidi: false,
|
Bidi: false,
|
||||||
Target: &sqlgraph.EdgeTarget{
|
Target: &sqlgraph.EdgeTarget{
|
||||||
IDSpec: &sqlgraph.FieldSpec{
|
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt),
|
||||||
Type: field.TypeInt,
|
|
||||||
Column: user.FieldID,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, k := range nodes {
|
for _, k := range nodes {
|
||||||
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||||
}
|
}
|
||||||
_node.password_token_user = &nodes[0]
|
_node.UserID = nodes[0]
|
||||||
_spec.Edges = append(_spec.Edges, edge)
|
_spec.Edges = append(_spec.Edges, edge)
|
||||||
}
|
}
|
||||||
return _node, _spec
|
return _node, _spec
|
||||||
|
|
@ -214,17 +175,21 @@ func (ptc *PasswordTokenCreate) createSpec() (*PasswordToken, *sqlgraph.CreateSp
|
||||||
// PasswordTokenCreateBulk is the builder for creating many PasswordToken entities in bulk.
|
// PasswordTokenCreateBulk is the builder for creating many PasswordToken entities in bulk.
|
||||||
type PasswordTokenCreateBulk struct {
|
type PasswordTokenCreateBulk struct {
|
||||||
config
|
config
|
||||||
|
err error
|
||||||
builders []*PasswordTokenCreate
|
builders []*PasswordTokenCreate
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save creates the PasswordToken entities in the database.
|
// Save creates the PasswordToken entities in the database.
|
||||||
func (ptcb *PasswordTokenCreateBulk) Save(ctx context.Context) ([]*PasswordToken, error) {
|
func (_c *PasswordTokenCreateBulk) Save(ctx context.Context) ([]*PasswordToken, error) {
|
||||||
specs := make([]*sqlgraph.CreateSpec, len(ptcb.builders))
|
if _c.err != nil {
|
||||||
nodes := make([]*PasswordToken, len(ptcb.builders))
|
return nil, _c.err
|
||||||
mutators := make([]Mutator, len(ptcb.builders))
|
}
|
||||||
for i := range ptcb.builders {
|
specs := make([]*sqlgraph.CreateSpec, len(_c.builders))
|
||||||
|
nodes := make([]*PasswordToken, len(_c.builders))
|
||||||
|
mutators := make([]Mutator, len(_c.builders))
|
||||||
|
for i := range _c.builders {
|
||||||
func(i int, root context.Context) {
|
func(i int, root context.Context) {
|
||||||
builder := ptcb.builders[i]
|
builder := _c.builders[i]
|
||||||
builder.defaults()
|
builder.defaults()
|
||||||
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
|
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
|
||||||
mutation, ok := m.(*PasswordTokenMutation)
|
mutation, ok := m.(*PasswordTokenMutation)
|
||||||
|
|
@ -235,16 +200,16 @@ func (ptcb *PasswordTokenCreateBulk) Save(ctx context.Context) ([]*PasswordToken
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
builder.mutation = mutation
|
builder.mutation = mutation
|
||||||
nodes[i], specs[i] = builder.createSpec()
|
|
||||||
var err error
|
var err error
|
||||||
|
nodes[i], specs[i] = builder.createSpec()
|
||||||
if i < len(mutators)-1 {
|
if i < len(mutators)-1 {
|
||||||
_, err = mutators[i+1].Mutate(root, ptcb.builders[i+1].mutation)
|
_, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation)
|
||||||
} else {
|
} else {
|
||||||
spec := &sqlgraph.BatchCreateSpec{Nodes: specs}
|
spec := &sqlgraph.BatchCreateSpec{Nodes: specs}
|
||||||
// Invoke the actual operation on the latest mutation in the chain.
|
// Invoke the actual operation on the latest mutation in the chain.
|
||||||
if err = sqlgraph.BatchCreate(ctx, ptcb.driver, spec); err != nil {
|
if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil {
|
||||||
if sqlgraph.IsConstraintError(err) {
|
if sqlgraph.IsConstraintError(err) {
|
||||||
err = &ConstraintError{err.Error(), err}
|
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -252,11 +217,11 @@ func (ptcb *PasswordTokenCreateBulk) Save(ctx context.Context) ([]*PasswordToken
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
mutation.id = &nodes[i].ID
|
mutation.id = &nodes[i].ID
|
||||||
mutation.done = true
|
|
||||||
if specs[i].ID.Value != nil {
|
if specs[i].ID.Value != nil {
|
||||||
id := specs[i].ID.Value.(int64)
|
id := specs[i].ID.Value.(int64)
|
||||||
nodes[i].ID = int(id)
|
nodes[i].ID = int(id)
|
||||||
}
|
}
|
||||||
|
mutation.done = true
|
||||||
return nodes[i], nil
|
return nodes[i], nil
|
||||||
})
|
})
|
||||||
for i := len(builder.hooks) - 1; i >= 0; i-- {
|
for i := len(builder.hooks) - 1; i >= 0; i-- {
|
||||||
|
|
@ -266,7 +231,7 @@ func (ptcb *PasswordTokenCreateBulk) Save(ctx context.Context) ([]*PasswordToken
|
||||||
}(i, ctx)
|
}(i, ctx)
|
||||||
}
|
}
|
||||||
if len(mutators) > 0 {
|
if len(mutators) > 0 {
|
||||||
if _, err := mutators[0].Mutate(ctx, ptcb.builders[0].mutation); err != nil {
|
if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -274,8 +239,8 @@ func (ptcb *PasswordTokenCreateBulk) Save(ctx context.Context) ([]*PasswordToken
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveX is like Save, but panics if an error occurs.
|
// SaveX is like Save, but panics if an error occurs.
|
||||||
func (ptcb *PasswordTokenCreateBulk) SaveX(ctx context.Context) []*PasswordToken {
|
func (_c *PasswordTokenCreateBulk) SaveX(ctx context.Context) []*PasswordToken {
|
||||||
v, err := ptcb.Save(ctx)
|
v, err := _c.Save(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
@ -283,14 +248,14 @@ func (ptcb *PasswordTokenCreateBulk) SaveX(ctx context.Context) []*PasswordToken
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exec executes the query.
|
// Exec executes the query.
|
||||||
func (ptcb *PasswordTokenCreateBulk) Exec(ctx context.Context) error {
|
func (_c *PasswordTokenCreateBulk) Exec(ctx context.Context) error {
|
||||||
_, err := ptcb.Save(ctx)
|
_, err := _c.Save(ctx)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecX is like Exec, but panics if an error occurs.
|
// ExecX is like Exec, but panics if an error occurs.
|
||||||
func (ptcb *PasswordTokenCreateBulk) ExecX(ctx context.Context) {
|
func (_c *PasswordTokenCreateBulk) ExecX(ctx context.Context) {
|
||||||
if err := ptcb.Exec(ctx); err != nil {
|
if err := _c.Exec(ctx); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,15 @@
|
||||||
// Code generated by entc, DO NOT EDIT.
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
package ent
|
package ent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"goweb/ent/passwordtoken"
|
|
||||||
"goweb/ent/predicate"
|
|
||||||
|
|
||||||
"entgo.io/ent/dialect/sql"
|
"entgo.io/ent/dialect/sql"
|
||||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||||
"entgo.io/ent/schema/field"
|
"entgo.io/ent/schema/field"
|
||||||
|
"github.com/camzawacki/personal-site/ent/passwordtoken"
|
||||||
|
"github.com/camzawacki/personal-site/ent/predicate"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PasswordTokenDelete is the builder for deleting a PasswordToken entity.
|
// PasswordTokenDelete is the builder for deleting a PasswordToken entity.
|
||||||
|
|
@ -21,80 +20,56 @@ type PasswordTokenDelete struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Where appends a list predicates to the PasswordTokenDelete builder.
|
// Where appends a list predicates to the PasswordTokenDelete builder.
|
||||||
func (ptd *PasswordTokenDelete) Where(ps ...predicate.PasswordToken) *PasswordTokenDelete {
|
func (_d *PasswordTokenDelete) Where(ps ...predicate.PasswordToken) *PasswordTokenDelete {
|
||||||
ptd.mutation.Where(ps...)
|
_d.mutation.Where(ps...)
|
||||||
return ptd
|
return _d
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exec executes the deletion query and returns how many vertices were deleted.
|
// Exec executes the deletion query and returns how many vertices were deleted.
|
||||||
func (ptd *PasswordTokenDelete) Exec(ctx context.Context) (int, error) {
|
func (_d *PasswordTokenDelete) Exec(ctx context.Context) (int, error) {
|
||||||
var (
|
return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks)
|
||||||
err error
|
|
||||||
affected int
|
|
||||||
)
|
|
||||||
if len(ptd.hooks) == 0 {
|
|
||||||
affected, err = ptd.sqlExec(ctx)
|
|
||||||
} else {
|
|
||||||
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
|
|
||||||
mutation, ok := m.(*PasswordTokenMutation)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("unexpected mutation type %T", m)
|
|
||||||
}
|
|
||||||
ptd.mutation = mutation
|
|
||||||
affected, err = ptd.sqlExec(ctx)
|
|
||||||
mutation.done = true
|
|
||||||
return affected, err
|
|
||||||
})
|
|
||||||
for i := len(ptd.hooks) - 1; i >= 0; i-- {
|
|
||||||
if ptd.hooks[i] == nil {
|
|
||||||
return 0, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)")
|
|
||||||
}
|
|
||||||
mut = ptd.hooks[i](mut)
|
|
||||||
}
|
|
||||||
if _, err := mut.Mutate(ctx, ptd.mutation); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return affected, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecX is like Exec, but panics if an error occurs.
|
// ExecX is like Exec, but panics if an error occurs.
|
||||||
func (ptd *PasswordTokenDelete) ExecX(ctx context.Context) int {
|
func (_d *PasswordTokenDelete) ExecX(ctx context.Context) int {
|
||||||
n, err := ptd.Exec(ctx)
|
n, err := _d.Exec(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ptd *PasswordTokenDelete) sqlExec(ctx context.Context) (int, error) {
|
func (_d *PasswordTokenDelete) sqlExec(ctx context.Context) (int, error) {
|
||||||
_spec := &sqlgraph.DeleteSpec{
|
_spec := sqlgraph.NewDeleteSpec(passwordtoken.Table, sqlgraph.NewFieldSpec(passwordtoken.FieldID, field.TypeInt))
|
||||||
Node: &sqlgraph.NodeSpec{
|
if ps := _d.mutation.predicates; len(ps) > 0 {
|
||||||
Table: passwordtoken.Table,
|
|
||||||
ID: &sqlgraph.FieldSpec{
|
|
||||||
Type: field.TypeInt,
|
|
||||||
Column: passwordtoken.FieldID,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if ps := ptd.mutation.predicates; len(ps) > 0 {
|
|
||||||
_spec.Predicate = func(selector *sql.Selector) {
|
_spec.Predicate = func(selector *sql.Selector) {
|
||||||
for i := range ps {
|
for i := range ps {
|
||||||
ps[i](selector)
|
ps[i](selector)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sqlgraph.DeleteNodes(ctx, ptd.driver, _spec)
|
affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec)
|
||||||
|
if err != nil && sqlgraph.IsConstraintError(err) {
|
||||||
|
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||||
|
}
|
||||||
|
_d.mutation.done = true
|
||||||
|
return affected, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// PasswordTokenDeleteOne is the builder for deleting a single PasswordToken entity.
|
// PasswordTokenDeleteOne is the builder for deleting a single PasswordToken entity.
|
||||||
type PasswordTokenDeleteOne struct {
|
type PasswordTokenDeleteOne struct {
|
||||||
ptd *PasswordTokenDelete
|
_d *PasswordTokenDelete
|
||||||
|
}
|
||||||
|
|
||||||
|
// Where appends a list predicates to the PasswordTokenDelete builder.
|
||||||
|
func (_d *PasswordTokenDeleteOne) Where(ps ...predicate.PasswordToken) *PasswordTokenDeleteOne {
|
||||||
|
_d._d.mutation.Where(ps...)
|
||||||
|
return _d
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exec executes the deletion query.
|
// Exec executes the deletion query.
|
||||||
func (ptdo *PasswordTokenDeleteOne) Exec(ctx context.Context) error {
|
func (_d *PasswordTokenDeleteOne) Exec(ctx context.Context) error {
|
||||||
n, err := ptdo.ptd.Exec(ctx)
|
n, err := _d._d.Exec(ctx)
|
||||||
switch {
|
switch {
|
||||||
case err != nil:
|
case err != nil:
|
||||||
return err
|
return err
|
||||||
|
|
@ -106,6 +81,8 @@ func (ptdo *PasswordTokenDeleteOne) Exec(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecX is like Exec, but panics if an error occurs.
|
// ExecX is like Exec, but panics if an error occurs.
|
||||||
func (ptdo *PasswordTokenDeleteOne) ExecX(ctx context.Context) {
|
func (_d *PasswordTokenDeleteOne) ExecX(ctx context.Context) {
|
||||||
ptdo.ptd.ExecX(ctx)
|
if err := _d.Exec(ctx); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,4 @@
|
||||||
// Code generated by entc, DO NOT EDIT.
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
package ent
|
package ent
|
||||||
|
|
||||||
|
|
@ -6,14 +6,14 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"goweb/ent/passwordtoken"
|
|
||||||
"goweb/ent/predicate"
|
|
||||||
"goweb/ent/user"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"entgo.io/ent/dialect/sql"
|
"entgo.io/ent/dialect/sql"
|
||||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||||
"entgo.io/ent/schema/field"
|
"entgo.io/ent/schema/field"
|
||||||
|
"github.com/camzawacki/personal-site/ent/passwordtoken"
|
||||||
|
"github.com/camzawacki/personal-site/ent/predicate"
|
||||||
|
"github.com/camzawacki/personal-site/ent/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PasswordTokenUpdate is the builder for updating PasswordToken entities.
|
// PasswordTokenUpdate is the builder for updating PasswordToken entities.
|
||||||
|
|
@ -24,94 +24,77 @@ type PasswordTokenUpdate struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Where appends a list predicates to the PasswordTokenUpdate builder.
|
// Where appends a list predicates to the PasswordTokenUpdate builder.
|
||||||
func (ptu *PasswordTokenUpdate) Where(ps ...predicate.PasswordToken) *PasswordTokenUpdate {
|
func (_u *PasswordTokenUpdate) Where(ps ...predicate.PasswordToken) *PasswordTokenUpdate {
|
||||||
ptu.mutation.Where(ps...)
|
_u.mutation.Where(ps...)
|
||||||
return ptu
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetHash sets the "hash" field.
|
// SetToken sets the "token" field.
|
||||||
func (ptu *PasswordTokenUpdate) SetHash(s string) *PasswordTokenUpdate {
|
func (_u *PasswordTokenUpdate) SetToken(v string) *PasswordTokenUpdate {
|
||||||
ptu.mutation.SetHash(s)
|
_u.mutation.SetToken(v)
|
||||||
return ptu
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableToken sets the "token" field if the given value is not nil.
|
||||||
|
func (_u *PasswordTokenUpdate) SetNillableToken(v *string) *PasswordTokenUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetToken(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUserID sets the "user_id" field.
|
||||||
|
func (_u *PasswordTokenUpdate) SetUserID(v int) *PasswordTokenUpdate {
|
||||||
|
_u.mutation.SetUserID(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableUserID sets the "user_id" field if the given value is not nil.
|
||||||
|
func (_u *PasswordTokenUpdate) SetNillableUserID(v *int) *PasswordTokenUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetUserID(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCreatedAt sets the "created_at" field.
|
// SetCreatedAt sets the "created_at" field.
|
||||||
func (ptu *PasswordTokenUpdate) SetCreatedAt(t time.Time) *PasswordTokenUpdate {
|
func (_u *PasswordTokenUpdate) SetCreatedAt(v time.Time) *PasswordTokenUpdate {
|
||||||
ptu.mutation.SetCreatedAt(t)
|
_u.mutation.SetCreatedAt(v)
|
||||||
return ptu
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
|
// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
|
||||||
func (ptu *PasswordTokenUpdate) SetNillableCreatedAt(t *time.Time) *PasswordTokenUpdate {
|
func (_u *PasswordTokenUpdate) SetNillableCreatedAt(v *time.Time) *PasswordTokenUpdate {
|
||||||
if t != nil {
|
if v != nil {
|
||||||
ptu.SetCreatedAt(*t)
|
_u.SetCreatedAt(*v)
|
||||||
}
|
}
|
||||||
return ptu
|
return _u
|
||||||
}
|
|
||||||
|
|
||||||
// SetUserID sets the "user" edge to the User entity by ID.
|
|
||||||
func (ptu *PasswordTokenUpdate) SetUserID(id int) *PasswordTokenUpdate {
|
|
||||||
ptu.mutation.SetUserID(id)
|
|
||||||
return ptu
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetUser sets the "user" edge to the User entity.
|
// SetUser sets the "user" edge to the User entity.
|
||||||
func (ptu *PasswordTokenUpdate) SetUser(u *User) *PasswordTokenUpdate {
|
func (_u *PasswordTokenUpdate) SetUser(v *User) *PasswordTokenUpdate {
|
||||||
return ptu.SetUserID(u.ID)
|
return _u.SetUserID(v.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mutation returns the PasswordTokenMutation object of the builder.
|
// Mutation returns the PasswordTokenMutation object of the builder.
|
||||||
func (ptu *PasswordTokenUpdate) Mutation() *PasswordTokenMutation {
|
func (_u *PasswordTokenUpdate) Mutation() *PasswordTokenMutation {
|
||||||
return ptu.mutation
|
return _u.mutation
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClearUser clears the "user" edge to the User entity.
|
// ClearUser clears the "user" edge to the User entity.
|
||||||
func (ptu *PasswordTokenUpdate) ClearUser() *PasswordTokenUpdate {
|
func (_u *PasswordTokenUpdate) ClearUser() *PasswordTokenUpdate {
|
||||||
ptu.mutation.ClearUser()
|
_u.mutation.ClearUser()
|
||||||
return ptu
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save executes the query and returns the number of nodes affected by the update operation.
|
// Save executes the query and returns the number of nodes affected by the update operation.
|
||||||
func (ptu *PasswordTokenUpdate) Save(ctx context.Context) (int, error) {
|
func (_u *PasswordTokenUpdate) Save(ctx context.Context) (int, error) {
|
||||||
var (
|
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
|
||||||
err error
|
|
||||||
affected int
|
|
||||||
)
|
|
||||||
if len(ptu.hooks) == 0 {
|
|
||||||
if err = ptu.check(); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
affected, err = ptu.sqlSave(ctx)
|
|
||||||
} else {
|
|
||||||
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
|
|
||||||
mutation, ok := m.(*PasswordTokenMutation)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("unexpected mutation type %T", m)
|
|
||||||
}
|
|
||||||
if err = ptu.check(); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
ptu.mutation = mutation
|
|
||||||
affected, err = ptu.sqlSave(ctx)
|
|
||||||
mutation.done = true
|
|
||||||
return affected, err
|
|
||||||
})
|
|
||||||
for i := len(ptu.hooks) - 1; i >= 0; i-- {
|
|
||||||
if ptu.hooks[i] == nil {
|
|
||||||
return 0, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)")
|
|
||||||
}
|
|
||||||
mut = ptu.hooks[i](mut)
|
|
||||||
}
|
|
||||||
if _, err := mut.Mutate(ctx, ptu.mutation); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return affected, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveX is like Save, but panics if an error occurs.
|
// SaveX is like Save, but panics if an error occurs.
|
||||||
func (ptu *PasswordTokenUpdate) SaveX(ctx context.Context) int {
|
func (_u *PasswordTokenUpdate) SaveX(ctx context.Context) int {
|
||||||
affected, err := ptu.Save(ctx)
|
affected, err := _u.Save(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
@ -119,64 +102,50 @@ func (ptu *PasswordTokenUpdate) SaveX(ctx context.Context) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exec executes the query.
|
// Exec executes the query.
|
||||||
func (ptu *PasswordTokenUpdate) Exec(ctx context.Context) error {
|
func (_u *PasswordTokenUpdate) Exec(ctx context.Context) error {
|
||||||
_, err := ptu.Save(ctx)
|
_, err := _u.Save(ctx)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecX is like Exec, but panics if an error occurs.
|
// ExecX is like Exec, but panics if an error occurs.
|
||||||
func (ptu *PasswordTokenUpdate) ExecX(ctx context.Context) {
|
func (_u *PasswordTokenUpdate) ExecX(ctx context.Context) {
|
||||||
if err := ptu.Exec(ctx); err != nil {
|
if err := _u.Exec(ctx); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check runs all checks and user-defined validators on the builder.
|
// check runs all checks and user-defined validators on the builder.
|
||||||
func (ptu *PasswordTokenUpdate) check() error {
|
func (_u *PasswordTokenUpdate) check() error {
|
||||||
if v, ok := ptu.mutation.Hash(); ok {
|
if v, ok := _u.mutation.Token(); ok {
|
||||||
if err := passwordtoken.HashValidator(v); err != nil {
|
if err := passwordtoken.TokenValidator(v); err != nil {
|
||||||
return &ValidationError{Name: "hash", err: fmt.Errorf("ent: validator failed for field \"hash\": %w", err)}
|
return &ValidationError{Name: "token", err: fmt.Errorf(`ent: validator failed for field "PasswordToken.token": %w`, err)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, ok := ptu.mutation.UserID(); ptu.mutation.UserCleared() && !ok {
|
if _u.mutation.UserCleared() && len(_u.mutation.UserIDs()) > 0 {
|
||||||
return errors.New("ent: clearing a required unique edge \"user\"")
|
return errors.New(`ent: clearing a required unique edge "PasswordToken.user"`)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ptu *PasswordTokenUpdate) sqlSave(ctx context.Context) (n int, err error) {
|
func (_u *PasswordTokenUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
||||||
_spec := &sqlgraph.UpdateSpec{
|
if err := _u.check(); err != nil {
|
||||||
Node: &sqlgraph.NodeSpec{
|
return _node, err
|
||||||
Table: passwordtoken.Table,
|
|
||||||
Columns: passwordtoken.Columns,
|
|
||||||
ID: &sqlgraph.FieldSpec{
|
|
||||||
Type: field.TypeInt,
|
|
||||||
Column: passwordtoken.FieldID,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
if ps := ptu.mutation.predicates; len(ps) > 0 {
|
_spec := sqlgraph.NewUpdateSpec(passwordtoken.Table, passwordtoken.Columns, sqlgraph.NewFieldSpec(passwordtoken.FieldID, field.TypeInt))
|
||||||
|
if ps := _u.mutation.predicates; len(ps) > 0 {
|
||||||
_spec.Predicate = func(selector *sql.Selector) {
|
_spec.Predicate = func(selector *sql.Selector) {
|
||||||
for i := range ps {
|
for i := range ps {
|
||||||
ps[i](selector)
|
ps[i](selector)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if value, ok := ptu.mutation.Hash(); ok {
|
if value, ok := _u.mutation.Token(); ok {
|
||||||
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
|
_spec.SetField(passwordtoken.FieldToken, field.TypeString, value)
|
||||||
Type: field.TypeString,
|
|
||||||
Value: value,
|
|
||||||
Column: passwordtoken.FieldHash,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
if value, ok := ptu.mutation.CreatedAt(); ok {
|
if value, ok := _u.mutation.CreatedAt(); ok {
|
||||||
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
|
_spec.SetField(passwordtoken.FieldCreatedAt, field.TypeTime, value)
|
||||||
Type: field.TypeTime,
|
|
||||||
Value: value,
|
|
||||||
Column: passwordtoken.FieldCreatedAt,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
if ptu.mutation.UserCleared() {
|
if _u.mutation.UserCleared() {
|
||||||
edge := &sqlgraph.EdgeSpec{
|
edge := &sqlgraph.EdgeSpec{
|
||||||
Rel: sqlgraph.M2O,
|
Rel: sqlgraph.M2O,
|
||||||
Inverse: false,
|
Inverse: false,
|
||||||
|
|
@ -184,15 +153,12 @@ func (ptu *PasswordTokenUpdate) sqlSave(ctx context.Context) (n int, err error)
|
||||||
Columns: []string{passwordtoken.UserColumn},
|
Columns: []string{passwordtoken.UserColumn},
|
||||||
Bidi: false,
|
Bidi: false,
|
||||||
Target: &sqlgraph.EdgeTarget{
|
Target: &sqlgraph.EdgeTarget{
|
||||||
IDSpec: &sqlgraph.FieldSpec{
|
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt),
|
||||||
Type: field.TypeInt,
|
|
||||||
Column: user.FieldID,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||||
}
|
}
|
||||||
if nodes := ptu.mutation.UserIDs(); len(nodes) > 0 {
|
if nodes := _u.mutation.UserIDs(); len(nodes) > 0 {
|
||||||
edge := &sqlgraph.EdgeSpec{
|
edge := &sqlgraph.EdgeSpec{
|
||||||
Rel: sqlgraph.M2O,
|
Rel: sqlgraph.M2O,
|
||||||
Inverse: false,
|
Inverse: false,
|
||||||
|
|
@ -200,10 +166,7 @@ func (ptu *PasswordTokenUpdate) sqlSave(ctx context.Context) (n int, err error)
|
||||||
Columns: []string{passwordtoken.UserColumn},
|
Columns: []string{passwordtoken.UserColumn},
|
||||||
Bidi: false,
|
Bidi: false,
|
||||||
Target: &sqlgraph.EdgeTarget{
|
Target: &sqlgraph.EdgeTarget{
|
||||||
IDSpec: &sqlgraph.FieldSpec{
|
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt),
|
||||||
Type: field.TypeInt,
|
|
||||||
Column: user.FieldID,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, k := range nodes {
|
for _, k := range nodes {
|
||||||
|
|
@ -211,15 +174,16 @@ func (ptu *PasswordTokenUpdate) sqlSave(ctx context.Context) (n int, err error)
|
||||||
}
|
}
|
||||||
_spec.Edges.Add = append(_spec.Edges.Add, edge)
|
_spec.Edges.Add = append(_spec.Edges.Add, edge)
|
||||||
}
|
}
|
||||||
if n, err = sqlgraph.UpdateNodes(ctx, ptu.driver, _spec); err != nil {
|
if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {
|
||||||
if _, ok := err.(*sqlgraph.NotFoundError); ok {
|
if _, ok := err.(*sqlgraph.NotFoundError); ok {
|
||||||
err = &NotFoundError{passwordtoken.Label}
|
err = &NotFoundError{passwordtoken.Label}
|
||||||
} else if sqlgraph.IsConstraintError(err) {
|
} else if sqlgraph.IsConstraintError(err) {
|
||||||
err = &ConstraintError{err.Error(), err}
|
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||||
}
|
}
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return n, nil
|
_u.mutation.done = true
|
||||||
|
return _node, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PasswordTokenUpdateOne is the builder for updating a single PasswordToken entity.
|
// PasswordTokenUpdateOne is the builder for updating a single PasswordToken entity.
|
||||||
|
|
@ -230,96 +194,85 @@ type PasswordTokenUpdateOne struct {
|
||||||
mutation *PasswordTokenMutation
|
mutation *PasswordTokenMutation
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetHash sets the "hash" field.
|
// SetToken sets the "token" field.
|
||||||
func (ptuo *PasswordTokenUpdateOne) SetHash(s string) *PasswordTokenUpdateOne {
|
func (_u *PasswordTokenUpdateOne) SetToken(v string) *PasswordTokenUpdateOne {
|
||||||
ptuo.mutation.SetHash(s)
|
_u.mutation.SetToken(v)
|
||||||
return ptuo
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableToken sets the "token" field if the given value is not nil.
|
||||||
|
func (_u *PasswordTokenUpdateOne) SetNillableToken(v *string) *PasswordTokenUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetToken(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUserID sets the "user_id" field.
|
||||||
|
func (_u *PasswordTokenUpdateOne) SetUserID(v int) *PasswordTokenUpdateOne {
|
||||||
|
_u.mutation.SetUserID(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableUserID sets the "user_id" field if the given value is not nil.
|
||||||
|
func (_u *PasswordTokenUpdateOne) SetNillableUserID(v *int) *PasswordTokenUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetUserID(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCreatedAt sets the "created_at" field.
|
// SetCreatedAt sets the "created_at" field.
|
||||||
func (ptuo *PasswordTokenUpdateOne) SetCreatedAt(t time.Time) *PasswordTokenUpdateOne {
|
func (_u *PasswordTokenUpdateOne) SetCreatedAt(v time.Time) *PasswordTokenUpdateOne {
|
||||||
ptuo.mutation.SetCreatedAt(t)
|
_u.mutation.SetCreatedAt(v)
|
||||||
return ptuo
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
|
// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
|
||||||
func (ptuo *PasswordTokenUpdateOne) SetNillableCreatedAt(t *time.Time) *PasswordTokenUpdateOne {
|
func (_u *PasswordTokenUpdateOne) SetNillableCreatedAt(v *time.Time) *PasswordTokenUpdateOne {
|
||||||
if t != nil {
|
if v != nil {
|
||||||
ptuo.SetCreatedAt(*t)
|
_u.SetCreatedAt(*v)
|
||||||
}
|
}
|
||||||
return ptuo
|
return _u
|
||||||
}
|
|
||||||
|
|
||||||
// SetUserID sets the "user" edge to the User entity by ID.
|
|
||||||
func (ptuo *PasswordTokenUpdateOne) SetUserID(id int) *PasswordTokenUpdateOne {
|
|
||||||
ptuo.mutation.SetUserID(id)
|
|
||||||
return ptuo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetUser sets the "user" edge to the User entity.
|
// SetUser sets the "user" edge to the User entity.
|
||||||
func (ptuo *PasswordTokenUpdateOne) SetUser(u *User) *PasswordTokenUpdateOne {
|
func (_u *PasswordTokenUpdateOne) SetUser(v *User) *PasswordTokenUpdateOne {
|
||||||
return ptuo.SetUserID(u.ID)
|
return _u.SetUserID(v.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mutation returns the PasswordTokenMutation object of the builder.
|
// Mutation returns the PasswordTokenMutation object of the builder.
|
||||||
func (ptuo *PasswordTokenUpdateOne) Mutation() *PasswordTokenMutation {
|
func (_u *PasswordTokenUpdateOne) Mutation() *PasswordTokenMutation {
|
||||||
return ptuo.mutation
|
return _u.mutation
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClearUser clears the "user" edge to the User entity.
|
// ClearUser clears the "user" edge to the User entity.
|
||||||
func (ptuo *PasswordTokenUpdateOne) ClearUser() *PasswordTokenUpdateOne {
|
func (_u *PasswordTokenUpdateOne) ClearUser() *PasswordTokenUpdateOne {
|
||||||
ptuo.mutation.ClearUser()
|
_u.mutation.ClearUser()
|
||||||
return ptuo
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// Where appends a list predicates to the PasswordTokenUpdate builder.
|
||||||
|
func (_u *PasswordTokenUpdateOne) Where(ps ...predicate.PasswordToken) *PasswordTokenUpdateOne {
|
||||||
|
_u.mutation.Where(ps...)
|
||||||
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select allows selecting one or more fields (columns) of the returned entity.
|
// Select allows selecting one or more fields (columns) of the returned entity.
|
||||||
// The default is selecting all fields defined in the entity schema.
|
// The default is selecting all fields defined in the entity schema.
|
||||||
func (ptuo *PasswordTokenUpdateOne) Select(field string, fields ...string) *PasswordTokenUpdateOne {
|
func (_u *PasswordTokenUpdateOne) Select(field string, fields ...string) *PasswordTokenUpdateOne {
|
||||||
ptuo.fields = append([]string{field}, fields...)
|
_u.fields = append([]string{field}, fields...)
|
||||||
return ptuo
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save executes the query and returns the updated PasswordToken entity.
|
// Save executes the query and returns the updated PasswordToken entity.
|
||||||
func (ptuo *PasswordTokenUpdateOne) Save(ctx context.Context) (*PasswordToken, error) {
|
func (_u *PasswordTokenUpdateOne) Save(ctx context.Context) (*PasswordToken, error) {
|
||||||
var (
|
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
|
||||||
err error
|
|
||||||
node *PasswordToken
|
|
||||||
)
|
|
||||||
if len(ptuo.hooks) == 0 {
|
|
||||||
if err = ptuo.check(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
node, err = ptuo.sqlSave(ctx)
|
|
||||||
} else {
|
|
||||||
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
|
|
||||||
mutation, ok := m.(*PasswordTokenMutation)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("unexpected mutation type %T", m)
|
|
||||||
}
|
|
||||||
if err = ptuo.check(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ptuo.mutation = mutation
|
|
||||||
node, err = ptuo.sqlSave(ctx)
|
|
||||||
mutation.done = true
|
|
||||||
return node, err
|
|
||||||
})
|
|
||||||
for i := len(ptuo.hooks) - 1; i >= 0; i-- {
|
|
||||||
if ptuo.hooks[i] == nil {
|
|
||||||
return nil, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)")
|
|
||||||
}
|
|
||||||
mut = ptuo.hooks[i](mut)
|
|
||||||
}
|
|
||||||
if _, err := mut.Mutate(ctx, ptuo.mutation); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return node, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveX is like Save, but panics if an error occurs.
|
// SaveX is like Save, but panics if an error occurs.
|
||||||
func (ptuo *PasswordTokenUpdateOne) SaveX(ctx context.Context) *PasswordToken {
|
func (_u *PasswordTokenUpdateOne) SaveX(ctx context.Context) *PasswordToken {
|
||||||
node, err := ptuo.Save(ctx)
|
node, err := _u.Save(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
@ -327,48 +280,42 @@ func (ptuo *PasswordTokenUpdateOne) SaveX(ctx context.Context) *PasswordToken {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exec executes the query on the entity.
|
// Exec executes the query on the entity.
|
||||||
func (ptuo *PasswordTokenUpdateOne) Exec(ctx context.Context) error {
|
func (_u *PasswordTokenUpdateOne) Exec(ctx context.Context) error {
|
||||||
_, err := ptuo.Save(ctx)
|
_, err := _u.Save(ctx)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecX is like Exec, but panics if an error occurs.
|
// ExecX is like Exec, but panics if an error occurs.
|
||||||
func (ptuo *PasswordTokenUpdateOne) ExecX(ctx context.Context) {
|
func (_u *PasswordTokenUpdateOne) ExecX(ctx context.Context) {
|
||||||
if err := ptuo.Exec(ctx); err != nil {
|
if err := _u.Exec(ctx); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check runs all checks and user-defined validators on the builder.
|
// check runs all checks and user-defined validators on the builder.
|
||||||
func (ptuo *PasswordTokenUpdateOne) check() error {
|
func (_u *PasswordTokenUpdateOne) check() error {
|
||||||
if v, ok := ptuo.mutation.Hash(); ok {
|
if v, ok := _u.mutation.Token(); ok {
|
||||||
if err := passwordtoken.HashValidator(v); err != nil {
|
if err := passwordtoken.TokenValidator(v); err != nil {
|
||||||
return &ValidationError{Name: "hash", err: fmt.Errorf("ent: validator failed for field \"hash\": %w", err)}
|
return &ValidationError{Name: "token", err: fmt.Errorf(`ent: validator failed for field "PasswordToken.token": %w`, err)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, ok := ptuo.mutation.UserID(); ptuo.mutation.UserCleared() && !ok {
|
if _u.mutation.UserCleared() && len(_u.mutation.UserIDs()) > 0 {
|
||||||
return errors.New("ent: clearing a required unique edge \"user\"")
|
return errors.New(`ent: clearing a required unique edge "PasswordToken.user"`)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ptuo *PasswordTokenUpdateOne) sqlSave(ctx context.Context) (_node *PasswordToken, err error) {
|
func (_u *PasswordTokenUpdateOne) sqlSave(ctx context.Context) (_node *PasswordToken, err error) {
|
||||||
_spec := &sqlgraph.UpdateSpec{
|
if err := _u.check(); err != nil {
|
||||||
Node: &sqlgraph.NodeSpec{
|
return _node, err
|
||||||
Table: passwordtoken.Table,
|
|
||||||
Columns: passwordtoken.Columns,
|
|
||||||
ID: &sqlgraph.FieldSpec{
|
|
||||||
Type: field.TypeInt,
|
|
||||||
Column: passwordtoken.FieldID,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
id, ok := ptuo.mutation.ID()
|
_spec := sqlgraph.NewUpdateSpec(passwordtoken.Table, passwordtoken.Columns, sqlgraph.NewFieldSpec(passwordtoken.FieldID, field.TypeInt))
|
||||||
|
id, ok := _u.mutation.ID()
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, &ValidationError{Name: "ID", err: fmt.Errorf("missing PasswordToken.ID for update")}
|
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "PasswordToken.id" for update`)}
|
||||||
}
|
}
|
||||||
_spec.Node.ID.Value = id
|
_spec.Node.ID.Value = id
|
||||||
if fields := ptuo.fields; len(fields) > 0 {
|
if fields := _u.fields; len(fields) > 0 {
|
||||||
_spec.Node.Columns = make([]string, 0, len(fields))
|
_spec.Node.Columns = make([]string, 0, len(fields))
|
||||||
_spec.Node.Columns = append(_spec.Node.Columns, passwordtoken.FieldID)
|
_spec.Node.Columns = append(_spec.Node.Columns, passwordtoken.FieldID)
|
||||||
for _, f := range fields {
|
for _, f := range fields {
|
||||||
|
|
@ -380,28 +327,20 @@ func (ptuo *PasswordTokenUpdateOne) sqlSave(ctx context.Context) (_node *Passwor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ps := ptuo.mutation.predicates; len(ps) > 0 {
|
if ps := _u.mutation.predicates; len(ps) > 0 {
|
||||||
_spec.Predicate = func(selector *sql.Selector) {
|
_spec.Predicate = func(selector *sql.Selector) {
|
||||||
for i := range ps {
|
for i := range ps {
|
||||||
ps[i](selector)
|
ps[i](selector)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if value, ok := ptuo.mutation.Hash(); ok {
|
if value, ok := _u.mutation.Token(); ok {
|
||||||
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
|
_spec.SetField(passwordtoken.FieldToken, field.TypeString, value)
|
||||||
Type: field.TypeString,
|
|
||||||
Value: value,
|
|
||||||
Column: passwordtoken.FieldHash,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
if value, ok := ptuo.mutation.CreatedAt(); ok {
|
if value, ok := _u.mutation.CreatedAt(); ok {
|
||||||
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
|
_spec.SetField(passwordtoken.FieldCreatedAt, field.TypeTime, value)
|
||||||
Type: field.TypeTime,
|
|
||||||
Value: value,
|
|
||||||
Column: passwordtoken.FieldCreatedAt,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
if ptuo.mutation.UserCleared() {
|
if _u.mutation.UserCleared() {
|
||||||
edge := &sqlgraph.EdgeSpec{
|
edge := &sqlgraph.EdgeSpec{
|
||||||
Rel: sqlgraph.M2O,
|
Rel: sqlgraph.M2O,
|
||||||
Inverse: false,
|
Inverse: false,
|
||||||
|
|
@ -409,15 +348,12 @@ func (ptuo *PasswordTokenUpdateOne) sqlSave(ctx context.Context) (_node *Passwor
|
||||||
Columns: []string{passwordtoken.UserColumn},
|
Columns: []string{passwordtoken.UserColumn},
|
||||||
Bidi: false,
|
Bidi: false,
|
||||||
Target: &sqlgraph.EdgeTarget{
|
Target: &sqlgraph.EdgeTarget{
|
||||||
IDSpec: &sqlgraph.FieldSpec{
|
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt),
|
||||||
Type: field.TypeInt,
|
|
||||||
Column: user.FieldID,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||||
}
|
}
|
||||||
if nodes := ptuo.mutation.UserIDs(); len(nodes) > 0 {
|
if nodes := _u.mutation.UserIDs(); len(nodes) > 0 {
|
||||||
edge := &sqlgraph.EdgeSpec{
|
edge := &sqlgraph.EdgeSpec{
|
||||||
Rel: sqlgraph.M2O,
|
Rel: sqlgraph.M2O,
|
||||||
Inverse: false,
|
Inverse: false,
|
||||||
|
|
@ -425,10 +361,7 @@ func (ptuo *PasswordTokenUpdateOne) sqlSave(ctx context.Context) (_node *Passwor
|
||||||
Columns: []string{passwordtoken.UserColumn},
|
Columns: []string{passwordtoken.UserColumn},
|
||||||
Bidi: false,
|
Bidi: false,
|
||||||
Target: &sqlgraph.EdgeTarget{
|
Target: &sqlgraph.EdgeTarget{
|
||||||
IDSpec: &sqlgraph.FieldSpec{
|
IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt),
|
||||||
Type: field.TypeInt,
|
|
||||||
Column: user.FieldID,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, k := range nodes {
|
for _, k := range nodes {
|
||||||
|
|
@ -436,16 +369,17 @@ func (ptuo *PasswordTokenUpdateOne) sqlSave(ctx context.Context) (_node *Passwor
|
||||||
}
|
}
|
||||||
_spec.Edges.Add = append(_spec.Edges.Add, edge)
|
_spec.Edges.Add = append(_spec.Edges.Add, edge)
|
||||||
}
|
}
|
||||||
_node = &PasswordToken{config: ptuo.config}
|
_node = &PasswordToken{config: _u.config}
|
||||||
_spec.Assign = _node.assignValues
|
_spec.Assign = _node.assignValues
|
||||||
_spec.ScanValues = _node.scanValues
|
_spec.ScanValues = _node.scanValues
|
||||||
if err = sqlgraph.UpdateNode(ctx, ptuo.driver, _spec); err != nil {
|
if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil {
|
||||||
if _, ok := err.(*sqlgraph.NotFoundError); ok {
|
if _, ok := err.(*sqlgraph.NotFoundError); ok {
|
||||||
err = &NotFoundError{passwordtoken.Label}
|
err = &NotFoundError{passwordtoken.Label}
|
||||||
} else if sqlgraph.IsConstraintError(err) {
|
} else if sqlgraph.IsConstraintError(err) {
|
||||||
err = &ConstraintError{err.Error(), err}
|
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
_u.mutation.done = true
|
||||||
return _node, nil
|
return _node, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Code generated by entc, DO NOT EDIT.
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
package predicate
|
package predicate
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// Code generated by entc, DO NOT EDIT.
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
package ent
|
package ent
|
||||||
|
|
||||||
// The schema-stitching logic is generated in goweb/ent/runtime/runtime.go
|
// The schema-stitching logic is generated in github.com/camzawacki/personal-site/ent/runtime/runtime.go
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,29 @@
|
||||||
// Code generated by entc, DO NOT EDIT.
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"goweb/ent/passwordtoken"
|
|
||||||
"goweb/ent/schema"
|
|
||||||
"goweb/ent/user"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/camzawacki/personal-site/ent/passwordtoken"
|
||||||
|
"github.com/camzawacki/personal-site/ent/schema"
|
||||||
|
"github.com/camzawacki/personal-site/ent/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The init function reads all schema descriptors with runtime code
|
// The init function reads all schema descriptors with runtime code
|
||||||
// (default values, validators, hooks and policies) and stitches it
|
// (default values, validators, hooks and policies) and stitches it
|
||||||
// to their package variables.
|
// to their package variables.
|
||||||
func init() {
|
func init() {
|
||||||
|
passwordtokenHooks := schema.PasswordToken{}.Hooks()
|
||||||
|
passwordtoken.Hooks[0] = passwordtokenHooks[0]
|
||||||
passwordtokenFields := schema.PasswordToken{}.Fields()
|
passwordtokenFields := schema.PasswordToken{}.Fields()
|
||||||
_ = passwordtokenFields
|
_ = passwordtokenFields
|
||||||
// passwordtokenDescHash is the schema descriptor for hash field.
|
// passwordtokenDescToken is the schema descriptor for token field.
|
||||||
passwordtokenDescHash := passwordtokenFields[0].Descriptor()
|
passwordtokenDescToken := passwordtokenFields[0].Descriptor()
|
||||||
// passwordtoken.HashValidator is a validator for the "hash" field. It is called by the builders before save.
|
// passwordtoken.TokenValidator is a validator for the "token" field. It is called by the builders before save.
|
||||||
passwordtoken.HashValidator = passwordtokenDescHash.Validators[0].(func(string) error)
|
passwordtoken.TokenValidator = passwordtokenDescToken.Validators[0].(func(string) error)
|
||||||
// passwordtokenDescCreatedAt is the schema descriptor for created_at field.
|
// passwordtokenDescCreatedAt is the schema descriptor for created_at field.
|
||||||
passwordtokenDescCreatedAt := passwordtokenFields[1].Descriptor()
|
passwordtokenDescCreatedAt := passwordtokenFields[2].Descriptor()
|
||||||
// passwordtoken.DefaultCreatedAt holds the default value on creation for the created_at field.
|
// passwordtoken.DefaultCreatedAt holds the default value on creation for the created_at field.
|
||||||
passwordtoken.DefaultCreatedAt = passwordtokenDescCreatedAt.Default.(func() time.Time)
|
passwordtoken.DefaultCreatedAt = passwordtokenDescCreatedAt.Default.(func() time.Time)
|
||||||
userHooks := schema.User{}.Hooks()
|
userHooks := schema.User{}.Hooks()
|
||||||
|
|
@ -34,18 +37,40 @@ func init() {
|
||||||
// userDescEmail is the schema descriptor for email field.
|
// userDescEmail is the schema descriptor for email field.
|
||||||
userDescEmail := userFields[1].Descriptor()
|
userDescEmail := userFields[1].Descriptor()
|
||||||
// user.EmailValidator is a validator for the "email" field. It is called by the builders before save.
|
// user.EmailValidator is a validator for the "email" field. It is called by the builders before save.
|
||||||
user.EmailValidator = userDescEmail.Validators[0].(func(string) error)
|
user.EmailValidator = func() func(string) error {
|
||||||
|
validators := userDescEmail.Validators
|
||||||
|
fns := [...]func(string) error{
|
||||||
|
validators[0].(func(string) error),
|
||||||
|
validators[1].(func(string) error),
|
||||||
|
}
|
||||||
|
return func(email string) error {
|
||||||
|
for _, fn := range fns {
|
||||||
|
if err := fn(email); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}()
|
||||||
// userDescPassword is the schema descriptor for password field.
|
// userDescPassword is the schema descriptor for password field.
|
||||||
userDescPassword := userFields[2].Descriptor()
|
userDescPassword := userFields[2].Descriptor()
|
||||||
// user.PasswordValidator is a validator for the "password" field. It is called by the builders before save.
|
// user.PasswordValidator is a validator for the "password" field. It is called by the builders before save.
|
||||||
user.PasswordValidator = userDescPassword.Validators[0].(func(string) error)
|
user.PasswordValidator = userDescPassword.Validators[0].(func(string) error)
|
||||||
|
// userDescVerified is the schema descriptor for verified field.
|
||||||
|
userDescVerified := userFields[3].Descriptor()
|
||||||
|
// user.DefaultVerified holds the default value on creation for the verified field.
|
||||||
|
user.DefaultVerified = userDescVerified.Default.(bool)
|
||||||
|
// userDescAdmin is the schema descriptor for admin field.
|
||||||
|
userDescAdmin := userFields[4].Descriptor()
|
||||||
|
// user.DefaultAdmin holds the default value on creation for the admin field.
|
||||||
|
user.DefaultAdmin = userDescAdmin.Default.(bool)
|
||||||
// userDescCreatedAt is the schema descriptor for created_at field.
|
// userDescCreatedAt is the schema descriptor for created_at field.
|
||||||
userDescCreatedAt := userFields[3].Descriptor()
|
userDescCreatedAt := userFields[5].Descriptor()
|
||||||
// user.DefaultCreatedAt holds the default value on creation for the created_at field.
|
// user.DefaultCreatedAt holds the default value on creation for the created_at field.
|
||||||
user.DefaultCreatedAt = userDescCreatedAt.Default.(func() time.Time)
|
user.DefaultCreatedAt = userDescCreatedAt.Default.(func() time.Time)
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Version = "v0.9.1" // Version of ent codegen.
|
Version = "v0.14.5" // Version of ent codegen.
|
||||||
Sum = "h1:IG8andyeD79GG24U8Q+1Y45hQXj6gY5evSBcva5gtBk=" // Sum of ent codegen.
|
Sum = "h1:Rj2WOYJtCkWyFo6a+5wB3EfBRP0rnx1fMk6gGA0UUe4=" // Sum of ent codegen.
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
package schema
|
package schema
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"entgo.io/ent"
|
"entgo.io/ent"
|
||||||
"entgo.io/ent/schema/edge"
|
"entgo.io/ent/schema/edge"
|
||||||
"entgo.io/ent/schema/field"
|
"entgo.io/ent/schema/field"
|
||||||
|
ge "github.com/camzawacki/personal-site/ent"
|
||||||
|
"github.com/camzawacki/personal-site/ent/hook"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PasswordToken holds the schema definition for the PasswordToken entity.
|
// PasswordToken holds the schema definition for the PasswordToken entity.
|
||||||
|
|
@ -16,9 +20,10 @@ type PasswordToken struct {
|
||||||
// Fields of the PasswordToken.
|
// Fields of the PasswordToken.
|
||||||
func (PasswordToken) Fields() []ent.Field {
|
func (PasswordToken) Fields() []ent.Field {
|
||||||
return []ent.Field{
|
return []ent.Field{
|
||||||
field.String("hash").
|
field.String("token").
|
||||||
Sensitive().
|
Sensitive().
|
||||||
NotEmpty(),
|
NotEmpty(),
|
||||||
|
field.Int("user_id"),
|
||||||
field.Time("created_at").
|
field.Time("created_at").
|
||||||
Default(time.Now),
|
Default(time.Now),
|
||||||
}
|
}
|
||||||
|
|
@ -28,7 +33,30 @@ func (PasswordToken) Fields() []ent.Field {
|
||||||
func (PasswordToken) Edges() []ent.Edge {
|
func (PasswordToken) Edges() []ent.Edge {
|
||||||
return []ent.Edge{
|
return []ent.Edge{
|
||||||
edge.To("user", User.Type).
|
edge.To("user", User.Type).
|
||||||
|
Field("user_id").
|
||||||
Required().
|
Required().
|
||||||
Unique(),
|
Unique(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hooks of the PasswordToken.
|
||||||
|
func (PasswordToken) Hooks() []ent.Hook {
|
||||||
|
return []ent.Hook{
|
||||||
|
hook.On(
|
||||||
|
func(next ent.Mutator) ent.Mutator {
|
||||||
|
return hook.PasswordTokenFunc(func(ctx context.Context, m *ge.PasswordTokenMutation) (ent.Value, error) {
|
||||||
|
if v, exists := m.Token(); exists {
|
||||||
|
hash, err := bcrypt.GenerateFromPassword([]byte(v), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
m.SetToken(string(hash))
|
||||||
|
}
|
||||||
|
return next.Mutate(ctx, m)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// Limit the hook only for these operations.
|
||||||
|
ent.OpCreate|ent.OpUpdate|ent.OpUpdateOne,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,13 @@ package schema
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net/mail"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
ge "goweb/ent"
|
ge "github.com/camzawacki/personal-site/ent"
|
||||||
"goweb/ent/hook"
|
"github.com/camzawacki/personal-site/ent/hook"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
"entgo.io/ent"
|
"entgo.io/ent"
|
||||||
"entgo.io/ent/schema/edge"
|
"entgo.io/ent/schema/edge"
|
||||||
|
|
@ -25,10 +27,18 @@ func (User) Fields() []ent.Field {
|
||||||
NotEmpty(),
|
NotEmpty(),
|
||||||
field.String("email").
|
field.String("email").
|
||||||
NotEmpty().
|
NotEmpty().
|
||||||
Unique(),
|
Unique().
|
||||||
|
Validate(func(s string) error {
|
||||||
|
_, err := mail.ParseAddress(s)
|
||||||
|
return err
|
||||||
|
}),
|
||||||
field.String("password").
|
field.String("password").
|
||||||
Sensitive().
|
Sensitive().
|
||||||
NotEmpty(),
|
NotEmpty(),
|
||||||
|
field.Bool("verified").
|
||||||
|
Default(false),
|
||||||
|
field.Bool("admin").
|
||||||
|
Default(false),
|
||||||
field.Time("created_at").
|
field.Time("created_at").
|
||||||
Default(time.Now).
|
Default(time.Now).
|
||||||
Immutable(),
|
Immutable(),
|
||||||
|
|
@ -52,6 +62,14 @@ func (User) Hooks() []ent.Hook {
|
||||||
if v, exists := m.Email(); exists {
|
if v, exists := m.Email(); exists {
|
||||||
m.SetEmail(strings.ToLower(v))
|
m.SetEmail(strings.ToLower(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if v, exists := m.Password(); exists {
|
||||||
|
hash, err := bcrypt.GenerateFromPassword([]byte(v), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
m.SetPassword(string(hash))
|
||||||
|
}
|
||||||
return next.Mutate(ctx, m)
|
return next.Mutate(ctx, m)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
|
||||||
50
ent/tx.go
50
ent/tx.go
|
|
@ -1,4 +1,4 @@
|
||||||
// Code generated by entc, DO NOT EDIT.
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
package ent
|
package ent
|
||||||
|
|
||||||
|
|
@ -20,19 +20,13 @@ type Tx struct {
|
||||||
// lazily loaded.
|
// lazily loaded.
|
||||||
client *Client
|
client *Client
|
||||||
clientOnce sync.Once
|
clientOnce sync.Once
|
||||||
|
|
||||||
// completion callbacks.
|
|
||||||
mu sync.Mutex
|
|
||||||
onCommit []CommitHook
|
|
||||||
onRollback []RollbackHook
|
|
||||||
|
|
||||||
// ctx lives for the life of the transaction. It is
|
// ctx lives for the life of the transaction. It is
|
||||||
// the same context used by the underlying connection.
|
// the same context used by the underlying connection.
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// Committer is the interface that wraps the Committer method.
|
// Committer is the interface that wraps the Commit method.
|
||||||
Committer interface {
|
Committer interface {
|
||||||
Commit(context.Context, *Tx) error
|
Commit(context.Context, *Tx) error
|
||||||
}
|
}
|
||||||
|
|
@ -46,7 +40,7 @@ type (
|
||||||
// and returns a Committer. For example:
|
// and returns a Committer. For example:
|
||||||
//
|
//
|
||||||
// hook := func(next ent.Committer) ent.Committer {
|
// hook := func(next ent.Committer) ent.Committer {
|
||||||
// return ent.CommitFunc(func(context.Context, tx *ent.Tx) error {
|
// return ent.CommitFunc(func(ctx context.Context, tx *ent.Tx) error {
|
||||||
// // Do some stuff before.
|
// // Do some stuff before.
|
||||||
// if err := next.Commit(ctx, tx); err != nil {
|
// if err := next.Commit(ctx, tx); err != nil {
|
||||||
// return err
|
// return err
|
||||||
|
|
@ -70,9 +64,9 @@ func (tx *Tx) Commit() error {
|
||||||
var fn Committer = CommitFunc(func(context.Context, *Tx) error {
|
var fn Committer = CommitFunc(func(context.Context, *Tx) error {
|
||||||
return txDriver.tx.Commit()
|
return txDriver.tx.Commit()
|
||||||
})
|
})
|
||||||
tx.mu.Lock()
|
txDriver.mu.Lock()
|
||||||
hooks := append([]CommitHook(nil), tx.onCommit...)
|
hooks := append([]CommitHook(nil), txDriver.onCommit...)
|
||||||
tx.mu.Unlock()
|
txDriver.mu.Unlock()
|
||||||
for i := len(hooks) - 1; i >= 0; i-- {
|
for i := len(hooks) - 1; i >= 0; i-- {
|
||||||
fn = hooks[i](fn)
|
fn = hooks[i](fn)
|
||||||
}
|
}
|
||||||
|
|
@ -81,13 +75,14 @@ func (tx *Tx) Commit() error {
|
||||||
|
|
||||||
// OnCommit adds a hook to call on commit.
|
// OnCommit adds a hook to call on commit.
|
||||||
func (tx *Tx) OnCommit(f CommitHook) {
|
func (tx *Tx) OnCommit(f CommitHook) {
|
||||||
tx.mu.Lock()
|
txDriver := tx.config.driver.(*txDriver)
|
||||||
defer tx.mu.Unlock()
|
txDriver.mu.Lock()
|
||||||
tx.onCommit = append(tx.onCommit, f)
|
txDriver.onCommit = append(txDriver.onCommit, f)
|
||||||
|
txDriver.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// Rollbacker is the interface that wraps the Rollbacker method.
|
// Rollbacker is the interface that wraps the Rollback method.
|
||||||
Rollbacker interface {
|
Rollbacker interface {
|
||||||
Rollback(context.Context, *Tx) error
|
Rollback(context.Context, *Tx) error
|
||||||
}
|
}
|
||||||
|
|
@ -101,7 +96,7 @@ type (
|
||||||
// and returns a Rollbacker. For example:
|
// and returns a Rollbacker. For example:
|
||||||
//
|
//
|
||||||
// hook := func(next ent.Rollbacker) ent.Rollbacker {
|
// hook := func(next ent.Rollbacker) ent.Rollbacker {
|
||||||
// return ent.RollbackFunc(func(context.Context, tx *ent.Tx) error {
|
// return ent.RollbackFunc(func(ctx context.Context, tx *ent.Tx) error {
|
||||||
// // Do some stuff before.
|
// // Do some stuff before.
|
||||||
// if err := next.Rollback(ctx, tx); err != nil {
|
// if err := next.Rollback(ctx, tx); err != nil {
|
||||||
// return err
|
// return err
|
||||||
|
|
@ -125,9 +120,9 @@ func (tx *Tx) Rollback() error {
|
||||||
var fn Rollbacker = RollbackFunc(func(context.Context, *Tx) error {
|
var fn Rollbacker = RollbackFunc(func(context.Context, *Tx) error {
|
||||||
return txDriver.tx.Rollback()
|
return txDriver.tx.Rollback()
|
||||||
})
|
})
|
||||||
tx.mu.Lock()
|
txDriver.mu.Lock()
|
||||||
hooks := append([]RollbackHook(nil), tx.onRollback...)
|
hooks := append([]RollbackHook(nil), txDriver.onRollback...)
|
||||||
tx.mu.Unlock()
|
txDriver.mu.Unlock()
|
||||||
for i := len(hooks) - 1; i >= 0; i-- {
|
for i := len(hooks) - 1; i >= 0; i-- {
|
||||||
fn = hooks[i](fn)
|
fn = hooks[i](fn)
|
||||||
}
|
}
|
||||||
|
|
@ -136,9 +131,10 @@ func (tx *Tx) Rollback() error {
|
||||||
|
|
||||||
// OnRollback adds a hook to call on rollback.
|
// OnRollback adds a hook to call on rollback.
|
||||||
func (tx *Tx) OnRollback(f RollbackHook) {
|
func (tx *Tx) OnRollback(f RollbackHook) {
|
||||||
tx.mu.Lock()
|
txDriver := tx.config.driver.(*txDriver)
|
||||||
defer tx.mu.Unlock()
|
txDriver.mu.Lock()
|
||||||
tx.onRollback = append(tx.onRollback, f)
|
txDriver.onRollback = append(txDriver.onRollback, f)
|
||||||
|
txDriver.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client returns a Client that binds to current transaction.
|
// Client returns a Client that binds to current transaction.
|
||||||
|
|
@ -171,6 +167,10 @@ type txDriver struct {
|
||||||
drv dialect.Driver
|
drv dialect.Driver
|
||||||
// tx is the underlying transaction.
|
// tx is the underlying transaction.
|
||||||
tx dialect.Tx
|
tx dialect.Tx
|
||||||
|
// completion hooks.
|
||||||
|
mu sync.Mutex
|
||||||
|
onCommit []CommitHook
|
||||||
|
onRollback []RollbackHook
|
||||||
}
|
}
|
||||||
|
|
||||||
// newTx creates a new transactional driver.
|
// newTx creates a new transactional driver.
|
||||||
|
|
@ -201,12 +201,12 @@ func (*txDriver) Commit() error { return nil }
|
||||||
func (*txDriver) Rollback() error { return nil }
|
func (*txDriver) Rollback() error { return nil }
|
||||||
|
|
||||||
// Exec calls tx.Exec.
|
// Exec calls tx.Exec.
|
||||||
func (tx *txDriver) Exec(ctx context.Context, query string, args, v interface{}) error {
|
func (tx *txDriver) Exec(ctx context.Context, query string, args, v any) error {
|
||||||
return tx.tx.Exec(ctx, query, args, v)
|
return tx.tx.Exec(ctx, query, args, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query calls tx.Query.
|
// Query calls tx.Query.
|
||||||
func (tx *txDriver) Query(ctx context.Context, query string, args, v interface{}) error {
|
func (tx *txDriver) Query(ctx context.Context, query string, args, v any) error {
|
||||||
return tx.tx.Query(ctx, query, args, v)
|
return tx.tx.Query(ctx, query, args, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
101
ent/user.go
101
ent/user.go
|
|
@ -1,14 +1,15 @@
|
||||||
// Code generated by entc, DO NOT EDIT.
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
package ent
|
package ent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"goweb/ent/user"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"entgo.io/ent"
|
||||||
"entgo.io/ent/dialect/sql"
|
"entgo.io/ent/dialect/sql"
|
||||||
|
"github.com/camzawacki/personal-site/ent/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
// User is the model entity for the User schema.
|
// User is the model entity for the User schema.
|
||||||
|
|
@ -22,11 +23,16 @@ type User struct {
|
||||||
Email string `json:"email,omitempty"`
|
Email string `json:"email,omitempty"`
|
||||||
// Password holds the value of the "password" field.
|
// Password holds the value of the "password" field.
|
||||||
Password string `json:"-"`
|
Password string `json:"-"`
|
||||||
|
// Verified holds the value of the "verified" field.
|
||||||
|
Verified bool `json:"verified,omitempty"`
|
||||||
|
// Admin holds the value of the "admin" field.
|
||||||
|
Admin bool `json:"admin,omitempty"`
|
||||||
// CreatedAt holds the value of the "created_at" field.
|
// CreatedAt holds the value of the "created_at" field.
|
||||||
CreatedAt time.Time `json:"created_at,omitempty"`
|
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||||
// Edges holds the relations/edges for other nodes in the graph.
|
// Edges holds the relations/edges for other nodes in the graph.
|
||||||
// The values are being populated by the UserQuery when eager-loading is set.
|
// The values are being populated by the UserQuery when eager-loading is set.
|
||||||
Edges UserEdges `json:"edges"`
|
Edges UserEdges `json:"edges"`
|
||||||
|
selectValues sql.SelectValues
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserEdges holds the relations/edges for other nodes in the graph.
|
// UserEdges holds the relations/edges for other nodes in the graph.
|
||||||
|
|
@ -48,10 +54,12 @@ func (e UserEdges) OwnerOrErr() ([]*PasswordToken, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// scanValues returns the types for scanning values from sql.Rows.
|
// scanValues returns the types for scanning values from sql.Rows.
|
||||||
func (*User) scanValues(columns []string) ([]interface{}, error) {
|
func (*User) scanValues(columns []string) ([]any, error) {
|
||||||
values := make([]interface{}, len(columns))
|
values := make([]any, len(columns))
|
||||||
for i := range columns {
|
for i := range columns {
|
||||||
switch columns[i] {
|
switch columns[i] {
|
||||||
|
case user.FieldVerified, user.FieldAdmin:
|
||||||
|
values[i] = new(sql.NullBool)
|
||||||
case user.FieldID:
|
case user.FieldID:
|
||||||
values[i] = new(sql.NullInt64)
|
values[i] = new(sql.NullInt64)
|
||||||
case user.FieldName, user.FieldEmail, user.FieldPassword:
|
case user.FieldName, user.FieldEmail, user.FieldPassword:
|
||||||
|
|
@ -59,7 +67,7 @@ func (*User) scanValues(columns []string) ([]interface{}, error) {
|
||||||
case user.FieldCreatedAt:
|
case user.FieldCreatedAt:
|
||||||
values[i] = new(sql.NullTime)
|
values[i] = new(sql.NullTime)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unexpected column %q for type User", columns[i])
|
values[i] = new(sql.UnknownType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return values, nil
|
return values, nil
|
||||||
|
|
@ -67,7 +75,7 @@ func (*User) scanValues(columns []string) ([]interface{}, error) {
|
||||||
|
|
||||||
// assignValues assigns the values that were returned from sql.Rows (after scanning)
|
// assignValues assigns the values that were returned from sql.Rows (after scanning)
|
||||||
// to the User fields.
|
// to the User fields.
|
||||||
func (u *User) assignValues(columns []string, values []interface{}) error {
|
func (_m *User) assignValues(columns []string, values []any) error {
|
||||||
if m, n := len(values), len(columns); m < n {
|
if m, n := len(values), len(columns); m < n {
|
||||||
return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
|
return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
|
||||||
}
|
}
|
||||||
|
|
@ -78,80 +86,103 @@ func (u *User) assignValues(columns []string, values []interface{}) error {
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("unexpected type %T for field id", value)
|
return fmt.Errorf("unexpected type %T for field id", value)
|
||||||
}
|
}
|
||||||
u.ID = int(value.Int64)
|
_m.ID = int(value.Int64)
|
||||||
case user.FieldName:
|
case user.FieldName:
|
||||||
if value, ok := values[i].(*sql.NullString); !ok {
|
if value, ok := values[i].(*sql.NullString); !ok {
|
||||||
return fmt.Errorf("unexpected type %T for field name", values[i])
|
return fmt.Errorf("unexpected type %T for field name", values[i])
|
||||||
} else if value.Valid {
|
} else if value.Valid {
|
||||||
u.Name = value.String
|
_m.Name = value.String
|
||||||
}
|
}
|
||||||
case user.FieldEmail:
|
case user.FieldEmail:
|
||||||
if value, ok := values[i].(*sql.NullString); !ok {
|
if value, ok := values[i].(*sql.NullString); !ok {
|
||||||
return fmt.Errorf("unexpected type %T for field email", values[i])
|
return fmt.Errorf("unexpected type %T for field email", values[i])
|
||||||
} else if value.Valid {
|
} else if value.Valid {
|
||||||
u.Email = value.String
|
_m.Email = value.String
|
||||||
}
|
}
|
||||||
case user.FieldPassword:
|
case user.FieldPassword:
|
||||||
if value, ok := values[i].(*sql.NullString); !ok {
|
if value, ok := values[i].(*sql.NullString); !ok {
|
||||||
return fmt.Errorf("unexpected type %T for field password", values[i])
|
return fmt.Errorf("unexpected type %T for field password", values[i])
|
||||||
} else if value.Valid {
|
} else if value.Valid {
|
||||||
u.Password = value.String
|
_m.Password = value.String
|
||||||
|
}
|
||||||
|
case user.FieldVerified:
|
||||||
|
if value, ok := values[i].(*sql.NullBool); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field verified", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.Verified = value.Bool
|
||||||
|
}
|
||||||
|
case user.FieldAdmin:
|
||||||
|
if value, ok := values[i].(*sql.NullBool); !ok {
|
||||||
|
return fmt.Errorf("unexpected type %T for field admin", values[i])
|
||||||
|
} else if value.Valid {
|
||||||
|
_m.Admin = value.Bool
|
||||||
}
|
}
|
||||||
case user.FieldCreatedAt:
|
case user.FieldCreatedAt:
|
||||||
if value, ok := values[i].(*sql.NullTime); !ok {
|
if value, ok := values[i].(*sql.NullTime); !ok {
|
||||||
return fmt.Errorf("unexpected type %T for field created_at", values[i])
|
return fmt.Errorf("unexpected type %T for field created_at", values[i])
|
||||||
} else if value.Valid {
|
} else if value.Valid {
|
||||||
u.CreatedAt = value.Time
|
_m.CreatedAt = value.Time
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
_m.selectValues.Set(columns[i], values[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Value returns the ent.Value that was dynamically selected and assigned to the User.
|
||||||
|
// This includes values selected through modifiers, order, etc.
|
||||||
|
func (_m *User) Value(name string) (ent.Value, error) {
|
||||||
|
return _m.selectValues.Get(name)
|
||||||
|
}
|
||||||
|
|
||||||
// QueryOwner queries the "owner" edge of the User entity.
|
// QueryOwner queries the "owner" edge of the User entity.
|
||||||
func (u *User) QueryOwner() *PasswordTokenQuery {
|
func (_m *User) QueryOwner() *PasswordTokenQuery {
|
||||||
return (&UserClient{config: u.config}).QueryOwner(u)
|
return NewUserClient(_m.config).QueryOwner(_m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update returns a builder for updating this User.
|
// Update returns a builder for updating this User.
|
||||||
// Note that you need to call User.Unwrap() before calling this method if this User
|
// Note that you need to call User.Unwrap() before calling this method if this User
|
||||||
// was returned from a transaction, and the transaction was committed or rolled back.
|
// was returned from a transaction, and the transaction was committed or rolled back.
|
||||||
func (u *User) Update() *UserUpdateOne {
|
func (_m *User) Update() *UserUpdateOne {
|
||||||
return (&UserClient{config: u.config}).UpdateOne(u)
|
return NewUserClient(_m.config).UpdateOne(_m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unwrap unwraps the User entity that was returned from a transaction after it was closed,
|
// Unwrap unwraps the User entity that was returned from a transaction after it was closed,
|
||||||
// so that all future queries will be executed through the driver which created the transaction.
|
// so that all future queries will be executed through the driver which created the transaction.
|
||||||
func (u *User) Unwrap() *User {
|
func (_m *User) Unwrap() *User {
|
||||||
tx, ok := u.config.driver.(*txDriver)
|
_tx, ok := _m.config.driver.(*txDriver)
|
||||||
if !ok {
|
if !ok {
|
||||||
panic("ent: User is not a transactional entity")
|
panic("ent: User is not a transactional entity")
|
||||||
}
|
}
|
||||||
u.config.driver = tx.drv
|
_m.config.driver = _tx.drv
|
||||||
return u
|
return _m
|
||||||
}
|
}
|
||||||
|
|
||||||
// String implements the fmt.Stringer.
|
// String implements the fmt.Stringer.
|
||||||
func (u *User) String() string {
|
func (_m *User) String() string {
|
||||||
var builder strings.Builder
|
var builder strings.Builder
|
||||||
builder.WriteString("User(")
|
builder.WriteString("User(")
|
||||||
builder.WriteString(fmt.Sprintf("id=%v", u.ID))
|
builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
|
||||||
builder.WriteString(", name=")
|
builder.WriteString("name=")
|
||||||
builder.WriteString(u.Name)
|
builder.WriteString(_m.Name)
|
||||||
builder.WriteString(", email=")
|
builder.WriteString(", ")
|
||||||
builder.WriteString(u.Email)
|
builder.WriteString("email=")
|
||||||
builder.WriteString(", password=<sensitive>")
|
builder.WriteString(_m.Email)
|
||||||
builder.WriteString(", created_at=")
|
builder.WriteString(", ")
|
||||||
builder.WriteString(u.CreatedAt.Format(time.ANSIC))
|
builder.WriteString("password=<sensitive>")
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("verified=")
|
||||||
|
builder.WriteString(fmt.Sprintf("%v", _m.Verified))
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("admin=")
|
||||||
|
builder.WriteString(fmt.Sprintf("%v", _m.Admin))
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString("created_at=")
|
||||||
|
builder.WriteString(_m.CreatedAt.Format(time.ANSIC))
|
||||||
builder.WriteByte(')')
|
builder.WriteByte(')')
|
||||||
return builder.String()
|
return builder.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Users is a parsable slice of User.
|
// Users is a parsable slice of User.
|
||||||
type Users []*User
|
type Users []*User
|
||||||
|
|
||||||
func (u Users) config(cfg config) {
|
|
||||||
for _i := range u {
|
|
||||||
u[_i].config = cfg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Code generated by entc, DO NOT EDIT.
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
package user
|
package user
|
||||||
|
|
||||||
|
|
@ -6,6 +6,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"entgo.io/ent"
|
"entgo.io/ent"
|
||||||
|
"entgo.io/ent/dialect/sql"
|
||||||
|
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -19,6 +21,10 @@ const (
|
||||||
FieldEmail = "email"
|
FieldEmail = "email"
|
||||||
// FieldPassword holds the string denoting the password field in the database.
|
// FieldPassword holds the string denoting the password field in the database.
|
||||||
FieldPassword = "password"
|
FieldPassword = "password"
|
||||||
|
// FieldVerified holds the string denoting the verified field in the database.
|
||||||
|
FieldVerified = "verified"
|
||||||
|
// FieldAdmin holds the string denoting the admin field in the database.
|
||||||
|
FieldAdmin = "admin"
|
||||||
// FieldCreatedAt holds the string denoting the created_at field in the database.
|
// FieldCreatedAt holds the string denoting the created_at field in the database.
|
||||||
FieldCreatedAt = "created_at"
|
FieldCreatedAt = "created_at"
|
||||||
// EdgeOwner holds the string denoting the owner edge name in mutations.
|
// EdgeOwner holds the string denoting the owner edge name in mutations.
|
||||||
|
|
@ -31,7 +37,7 @@ const (
|
||||||
// It exists in this package in order to avoid circular dependency with the "passwordtoken" package.
|
// It exists in this package in order to avoid circular dependency with the "passwordtoken" package.
|
||||||
OwnerInverseTable = "password_tokens"
|
OwnerInverseTable = "password_tokens"
|
||||||
// OwnerColumn is the table column denoting the owner relation/edge.
|
// OwnerColumn is the table column denoting the owner relation/edge.
|
||||||
OwnerColumn = "password_token_user"
|
OwnerColumn = "user_id"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Columns holds all SQL columns for user fields.
|
// Columns holds all SQL columns for user fields.
|
||||||
|
|
@ -40,6 +46,8 @@ var Columns = []string{
|
||||||
FieldName,
|
FieldName,
|
||||||
FieldEmail,
|
FieldEmail,
|
||||||
FieldPassword,
|
FieldPassword,
|
||||||
|
FieldVerified,
|
||||||
|
FieldAdmin,
|
||||||
FieldCreatedAt,
|
FieldCreatedAt,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,8 +65,7 @@ func ValidColumn(column string) bool {
|
||||||
// package on the initialization of the application. Therefore,
|
// package on the initialization of the application. Therefore,
|
||||||
// it should be imported in the main as follows:
|
// it should be imported in the main as follows:
|
||||||
//
|
//
|
||||||
// import _ "goweb/ent/runtime"
|
// import _ "github.com/camzawacki/personal-site/ent/runtime"
|
||||||
//
|
|
||||||
var (
|
var (
|
||||||
Hooks [1]ent.Hook
|
Hooks [1]ent.Hook
|
||||||
// NameValidator is a validator for the "name" field. It is called by the builders before save.
|
// NameValidator is a validator for the "name" field. It is called by the builders before save.
|
||||||
|
|
@ -67,6 +74,69 @@ var (
|
||||||
EmailValidator func(string) error
|
EmailValidator func(string) error
|
||||||
// PasswordValidator is a validator for the "password" field. It is called by the builders before save.
|
// PasswordValidator is a validator for the "password" field. It is called by the builders before save.
|
||||||
PasswordValidator func(string) error
|
PasswordValidator func(string) error
|
||||||
|
// DefaultVerified holds the default value on creation for the "verified" field.
|
||||||
|
DefaultVerified bool
|
||||||
|
// DefaultAdmin holds the default value on creation for the "admin" field.
|
||||||
|
DefaultAdmin bool
|
||||||
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
|
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
|
||||||
DefaultCreatedAt func() time.Time
|
DefaultCreatedAt func() time.Time
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// OrderOption defines the ordering options for the User queries.
|
||||||
|
type OrderOption func(*sql.Selector)
|
||||||
|
|
||||||
|
// ByID orders the results by the id field.
|
||||||
|
func ByID(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldID, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByName orders the results by the name field.
|
||||||
|
func ByName(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldName, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByEmail orders the results by the email field.
|
||||||
|
func ByEmail(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldEmail, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByPassword orders the results by the password field.
|
||||||
|
func ByPassword(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldPassword, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByVerified orders the results by the verified field.
|
||||||
|
func ByVerified(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldVerified, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByAdmin orders the results by the admin field.
|
||||||
|
func ByAdmin(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldAdmin, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByCreatedAt orders the results by the created_at field.
|
||||||
|
func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return sql.OrderByField(FieldCreatedAt, opts...).ToFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByOwnerCount orders the results by owner count.
|
||||||
|
func ByOwnerCount(opts ...sql.OrderTermOption) OrderOption {
|
||||||
|
return func(s *sql.Selector) {
|
||||||
|
sqlgraph.OrderByNeighborsCount(s, newOwnerStep(), opts...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByOwner orders the results by owner terms.
|
||||||
|
func ByOwner(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
|
||||||
|
return func(s *sql.Selector) {
|
||||||
|
sqlgraph.OrderByNeighborTerms(s, newOwnerStep(), append([]sql.OrderTerm{term}, terms...)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func newOwnerStep() *sqlgraph.Step {
|
||||||
|
return sqlgraph.NewStep(
|
||||||
|
sqlgraph.From(Table, FieldID),
|
||||||
|
sqlgraph.To(OwnerInverseTable, FieldID),
|
||||||
|
sqlgraph.Edge(sqlgraph.O2M, true, OwnerTable, OwnerColumn),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,533 +1,343 @@
|
||||||
// Code generated by entc, DO NOT EDIT.
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
package user
|
package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"goweb/ent/predicate"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"entgo.io/ent/dialect/sql"
|
"entgo.io/ent/dialect/sql"
|
||||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||||
|
"github.com/camzawacki/personal-site/ent/predicate"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ID filters vertices based on their ID field.
|
// ID filters vertices based on their ID field.
|
||||||
func ID(id int) predicate.User {
|
func ID(id int) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldEQ(FieldID, id))
|
||||||
s.Where(sql.EQ(s.C(FieldID), id))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IDEQ applies the EQ predicate on the ID field.
|
// IDEQ applies the EQ predicate on the ID field.
|
||||||
func IDEQ(id int) predicate.User {
|
func IDEQ(id int) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldEQ(FieldID, id))
|
||||||
s.Where(sql.EQ(s.C(FieldID), id))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IDNEQ applies the NEQ predicate on the ID field.
|
// IDNEQ applies the NEQ predicate on the ID field.
|
||||||
func IDNEQ(id int) predicate.User {
|
func IDNEQ(id int) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldNEQ(FieldID, id))
|
||||||
s.Where(sql.NEQ(s.C(FieldID), id))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IDIn applies the In predicate on the ID field.
|
// IDIn applies the In predicate on the ID field.
|
||||||
func IDIn(ids ...int) predicate.User {
|
func IDIn(ids ...int) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldIn(FieldID, ids...))
|
||||||
// if not arguments were provided, append the FALSE constants,
|
|
||||||
// since we can't apply "IN ()". This will make this predicate falsy.
|
|
||||||
if len(ids) == 0 {
|
|
||||||
s.Where(sql.False())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
v := make([]interface{}, len(ids))
|
|
||||||
for i := range v {
|
|
||||||
v[i] = ids[i]
|
|
||||||
}
|
|
||||||
s.Where(sql.In(s.C(FieldID), v...))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IDNotIn applies the NotIn predicate on the ID field.
|
// IDNotIn applies the NotIn predicate on the ID field.
|
||||||
func IDNotIn(ids ...int) predicate.User {
|
func IDNotIn(ids ...int) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldNotIn(FieldID, ids...))
|
||||||
// if not arguments were provided, append the FALSE constants,
|
|
||||||
// since we can't apply "IN ()". This will make this predicate falsy.
|
|
||||||
if len(ids) == 0 {
|
|
||||||
s.Where(sql.False())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
v := make([]interface{}, len(ids))
|
|
||||||
for i := range v {
|
|
||||||
v[i] = ids[i]
|
|
||||||
}
|
|
||||||
s.Where(sql.NotIn(s.C(FieldID), v...))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IDGT applies the GT predicate on the ID field.
|
// IDGT applies the GT predicate on the ID field.
|
||||||
func IDGT(id int) predicate.User {
|
func IDGT(id int) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldGT(FieldID, id))
|
||||||
s.Where(sql.GT(s.C(FieldID), id))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IDGTE applies the GTE predicate on the ID field.
|
// IDGTE applies the GTE predicate on the ID field.
|
||||||
func IDGTE(id int) predicate.User {
|
func IDGTE(id int) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldGTE(FieldID, id))
|
||||||
s.Where(sql.GTE(s.C(FieldID), id))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IDLT applies the LT predicate on the ID field.
|
// IDLT applies the LT predicate on the ID field.
|
||||||
func IDLT(id int) predicate.User {
|
func IDLT(id int) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldLT(FieldID, id))
|
||||||
s.Where(sql.LT(s.C(FieldID), id))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IDLTE applies the LTE predicate on the ID field.
|
// IDLTE applies the LTE predicate on the ID field.
|
||||||
func IDLTE(id int) predicate.User {
|
func IDLTE(id int) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldLTE(FieldID, id))
|
||||||
s.Where(sql.LTE(s.C(FieldID), id))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name applies equality check predicate on the "name" field. It's identical to NameEQ.
|
// Name applies equality check predicate on the "name" field. It's identical to NameEQ.
|
||||||
func Name(v string) predicate.User {
|
func Name(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldEQ(FieldName, v))
|
||||||
s.Where(sql.EQ(s.C(FieldName), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Email applies equality check predicate on the "email" field. It's identical to EmailEQ.
|
// Email applies equality check predicate on the "email" field. It's identical to EmailEQ.
|
||||||
func Email(v string) predicate.User {
|
func Email(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldEQ(FieldEmail, v))
|
||||||
s.Where(sql.EQ(s.C(FieldEmail), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Password applies equality check predicate on the "password" field. It's identical to PasswordEQ.
|
// Password applies equality check predicate on the "password" field. It's identical to PasswordEQ.
|
||||||
func Password(v string) predicate.User {
|
func Password(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldEQ(FieldPassword, v))
|
||||||
s.Where(sql.EQ(s.C(FieldPassword), v))
|
}
|
||||||
})
|
|
||||||
|
// Verified applies equality check predicate on the "verified" field. It's identical to VerifiedEQ.
|
||||||
|
func Verified(v bool) predicate.User {
|
||||||
|
return predicate.User(sql.FieldEQ(FieldVerified, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Admin applies equality check predicate on the "admin" field. It's identical to AdminEQ.
|
||||||
|
func Admin(v bool) predicate.User {
|
||||||
|
return predicate.User(sql.FieldEQ(FieldAdmin, v))
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
|
// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
|
||||||
func CreatedAt(v time.Time) predicate.User {
|
func CreatedAt(v time.Time) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldEQ(FieldCreatedAt, v))
|
||||||
s.Where(sql.EQ(s.C(FieldCreatedAt), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NameEQ applies the EQ predicate on the "name" field.
|
// NameEQ applies the EQ predicate on the "name" field.
|
||||||
func NameEQ(v string) predicate.User {
|
func NameEQ(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldEQ(FieldName, v))
|
||||||
s.Where(sql.EQ(s.C(FieldName), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NameNEQ applies the NEQ predicate on the "name" field.
|
// NameNEQ applies the NEQ predicate on the "name" field.
|
||||||
func NameNEQ(v string) predicate.User {
|
func NameNEQ(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldNEQ(FieldName, v))
|
||||||
s.Where(sql.NEQ(s.C(FieldName), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NameIn applies the In predicate on the "name" field.
|
// NameIn applies the In predicate on the "name" field.
|
||||||
func NameIn(vs ...string) predicate.User {
|
func NameIn(vs ...string) predicate.User {
|
||||||
v := make([]interface{}, len(vs))
|
return predicate.User(sql.FieldIn(FieldName, vs...))
|
||||||
for i := range v {
|
|
||||||
v[i] = vs[i]
|
|
||||||
}
|
|
||||||
return predicate.User(func(s *sql.Selector) {
|
|
||||||
// if not arguments were provided, append the FALSE constants,
|
|
||||||
// since we can't apply "IN ()". This will make this predicate falsy.
|
|
||||||
if len(v) == 0 {
|
|
||||||
s.Where(sql.False())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.Where(sql.In(s.C(FieldName), v...))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NameNotIn applies the NotIn predicate on the "name" field.
|
// NameNotIn applies the NotIn predicate on the "name" field.
|
||||||
func NameNotIn(vs ...string) predicate.User {
|
func NameNotIn(vs ...string) predicate.User {
|
||||||
v := make([]interface{}, len(vs))
|
return predicate.User(sql.FieldNotIn(FieldName, vs...))
|
||||||
for i := range v {
|
|
||||||
v[i] = vs[i]
|
|
||||||
}
|
|
||||||
return predicate.User(func(s *sql.Selector) {
|
|
||||||
// if not arguments were provided, append the FALSE constants,
|
|
||||||
// since we can't apply "IN ()". This will make this predicate falsy.
|
|
||||||
if len(v) == 0 {
|
|
||||||
s.Where(sql.False())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.Where(sql.NotIn(s.C(FieldName), v...))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NameGT applies the GT predicate on the "name" field.
|
// NameGT applies the GT predicate on the "name" field.
|
||||||
func NameGT(v string) predicate.User {
|
func NameGT(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldGT(FieldName, v))
|
||||||
s.Where(sql.GT(s.C(FieldName), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NameGTE applies the GTE predicate on the "name" field.
|
// NameGTE applies the GTE predicate on the "name" field.
|
||||||
func NameGTE(v string) predicate.User {
|
func NameGTE(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldGTE(FieldName, v))
|
||||||
s.Where(sql.GTE(s.C(FieldName), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NameLT applies the LT predicate on the "name" field.
|
// NameLT applies the LT predicate on the "name" field.
|
||||||
func NameLT(v string) predicate.User {
|
func NameLT(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldLT(FieldName, v))
|
||||||
s.Where(sql.LT(s.C(FieldName), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NameLTE applies the LTE predicate on the "name" field.
|
// NameLTE applies the LTE predicate on the "name" field.
|
||||||
func NameLTE(v string) predicate.User {
|
func NameLTE(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldLTE(FieldName, v))
|
||||||
s.Where(sql.LTE(s.C(FieldName), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NameContains applies the Contains predicate on the "name" field.
|
// NameContains applies the Contains predicate on the "name" field.
|
||||||
func NameContains(v string) predicate.User {
|
func NameContains(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldContains(FieldName, v))
|
||||||
s.Where(sql.Contains(s.C(FieldName), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NameHasPrefix applies the HasPrefix predicate on the "name" field.
|
// NameHasPrefix applies the HasPrefix predicate on the "name" field.
|
||||||
func NameHasPrefix(v string) predicate.User {
|
func NameHasPrefix(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldHasPrefix(FieldName, v))
|
||||||
s.Where(sql.HasPrefix(s.C(FieldName), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NameHasSuffix applies the HasSuffix predicate on the "name" field.
|
// NameHasSuffix applies the HasSuffix predicate on the "name" field.
|
||||||
func NameHasSuffix(v string) predicate.User {
|
func NameHasSuffix(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldHasSuffix(FieldName, v))
|
||||||
s.Where(sql.HasSuffix(s.C(FieldName), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NameEqualFold applies the EqualFold predicate on the "name" field.
|
// NameEqualFold applies the EqualFold predicate on the "name" field.
|
||||||
func NameEqualFold(v string) predicate.User {
|
func NameEqualFold(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldEqualFold(FieldName, v))
|
||||||
s.Where(sql.EqualFold(s.C(FieldName), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NameContainsFold applies the ContainsFold predicate on the "name" field.
|
// NameContainsFold applies the ContainsFold predicate on the "name" field.
|
||||||
func NameContainsFold(v string) predicate.User {
|
func NameContainsFold(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldContainsFold(FieldName, v))
|
||||||
s.Where(sql.ContainsFold(s.C(FieldName), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmailEQ applies the EQ predicate on the "email" field.
|
// EmailEQ applies the EQ predicate on the "email" field.
|
||||||
func EmailEQ(v string) predicate.User {
|
func EmailEQ(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldEQ(FieldEmail, v))
|
||||||
s.Where(sql.EQ(s.C(FieldEmail), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmailNEQ applies the NEQ predicate on the "email" field.
|
// EmailNEQ applies the NEQ predicate on the "email" field.
|
||||||
func EmailNEQ(v string) predicate.User {
|
func EmailNEQ(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldNEQ(FieldEmail, v))
|
||||||
s.Where(sql.NEQ(s.C(FieldEmail), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmailIn applies the In predicate on the "email" field.
|
// EmailIn applies the In predicate on the "email" field.
|
||||||
func EmailIn(vs ...string) predicate.User {
|
func EmailIn(vs ...string) predicate.User {
|
||||||
v := make([]interface{}, len(vs))
|
return predicate.User(sql.FieldIn(FieldEmail, vs...))
|
||||||
for i := range v {
|
|
||||||
v[i] = vs[i]
|
|
||||||
}
|
|
||||||
return predicate.User(func(s *sql.Selector) {
|
|
||||||
// if not arguments were provided, append the FALSE constants,
|
|
||||||
// since we can't apply "IN ()". This will make this predicate falsy.
|
|
||||||
if len(v) == 0 {
|
|
||||||
s.Where(sql.False())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.Where(sql.In(s.C(FieldEmail), v...))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmailNotIn applies the NotIn predicate on the "email" field.
|
// EmailNotIn applies the NotIn predicate on the "email" field.
|
||||||
func EmailNotIn(vs ...string) predicate.User {
|
func EmailNotIn(vs ...string) predicate.User {
|
||||||
v := make([]interface{}, len(vs))
|
return predicate.User(sql.FieldNotIn(FieldEmail, vs...))
|
||||||
for i := range v {
|
|
||||||
v[i] = vs[i]
|
|
||||||
}
|
|
||||||
return predicate.User(func(s *sql.Selector) {
|
|
||||||
// if not arguments were provided, append the FALSE constants,
|
|
||||||
// since we can't apply "IN ()". This will make this predicate falsy.
|
|
||||||
if len(v) == 0 {
|
|
||||||
s.Where(sql.False())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.Where(sql.NotIn(s.C(FieldEmail), v...))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmailGT applies the GT predicate on the "email" field.
|
// EmailGT applies the GT predicate on the "email" field.
|
||||||
func EmailGT(v string) predicate.User {
|
func EmailGT(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldGT(FieldEmail, v))
|
||||||
s.Where(sql.GT(s.C(FieldEmail), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmailGTE applies the GTE predicate on the "email" field.
|
// EmailGTE applies the GTE predicate on the "email" field.
|
||||||
func EmailGTE(v string) predicate.User {
|
func EmailGTE(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldGTE(FieldEmail, v))
|
||||||
s.Where(sql.GTE(s.C(FieldEmail), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmailLT applies the LT predicate on the "email" field.
|
// EmailLT applies the LT predicate on the "email" field.
|
||||||
func EmailLT(v string) predicate.User {
|
func EmailLT(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldLT(FieldEmail, v))
|
||||||
s.Where(sql.LT(s.C(FieldEmail), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmailLTE applies the LTE predicate on the "email" field.
|
// EmailLTE applies the LTE predicate on the "email" field.
|
||||||
func EmailLTE(v string) predicate.User {
|
func EmailLTE(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldLTE(FieldEmail, v))
|
||||||
s.Where(sql.LTE(s.C(FieldEmail), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmailContains applies the Contains predicate on the "email" field.
|
// EmailContains applies the Contains predicate on the "email" field.
|
||||||
func EmailContains(v string) predicate.User {
|
func EmailContains(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldContains(FieldEmail, v))
|
||||||
s.Where(sql.Contains(s.C(FieldEmail), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmailHasPrefix applies the HasPrefix predicate on the "email" field.
|
// EmailHasPrefix applies the HasPrefix predicate on the "email" field.
|
||||||
func EmailHasPrefix(v string) predicate.User {
|
func EmailHasPrefix(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldHasPrefix(FieldEmail, v))
|
||||||
s.Where(sql.HasPrefix(s.C(FieldEmail), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmailHasSuffix applies the HasSuffix predicate on the "email" field.
|
// EmailHasSuffix applies the HasSuffix predicate on the "email" field.
|
||||||
func EmailHasSuffix(v string) predicate.User {
|
func EmailHasSuffix(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldHasSuffix(FieldEmail, v))
|
||||||
s.Where(sql.HasSuffix(s.C(FieldEmail), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmailEqualFold applies the EqualFold predicate on the "email" field.
|
// EmailEqualFold applies the EqualFold predicate on the "email" field.
|
||||||
func EmailEqualFold(v string) predicate.User {
|
func EmailEqualFold(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldEqualFold(FieldEmail, v))
|
||||||
s.Where(sql.EqualFold(s.C(FieldEmail), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmailContainsFold applies the ContainsFold predicate on the "email" field.
|
// EmailContainsFold applies the ContainsFold predicate on the "email" field.
|
||||||
func EmailContainsFold(v string) predicate.User {
|
func EmailContainsFold(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldContainsFold(FieldEmail, v))
|
||||||
s.Where(sql.ContainsFold(s.C(FieldEmail), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PasswordEQ applies the EQ predicate on the "password" field.
|
// PasswordEQ applies the EQ predicate on the "password" field.
|
||||||
func PasswordEQ(v string) predicate.User {
|
func PasswordEQ(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldEQ(FieldPassword, v))
|
||||||
s.Where(sql.EQ(s.C(FieldPassword), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PasswordNEQ applies the NEQ predicate on the "password" field.
|
// PasswordNEQ applies the NEQ predicate on the "password" field.
|
||||||
func PasswordNEQ(v string) predicate.User {
|
func PasswordNEQ(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldNEQ(FieldPassword, v))
|
||||||
s.Where(sql.NEQ(s.C(FieldPassword), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PasswordIn applies the In predicate on the "password" field.
|
// PasswordIn applies the In predicate on the "password" field.
|
||||||
func PasswordIn(vs ...string) predicate.User {
|
func PasswordIn(vs ...string) predicate.User {
|
||||||
v := make([]interface{}, len(vs))
|
return predicate.User(sql.FieldIn(FieldPassword, vs...))
|
||||||
for i := range v {
|
|
||||||
v[i] = vs[i]
|
|
||||||
}
|
|
||||||
return predicate.User(func(s *sql.Selector) {
|
|
||||||
// if not arguments were provided, append the FALSE constants,
|
|
||||||
// since we can't apply "IN ()". This will make this predicate falsy.
|
|
||||||
if len(v) == 0 {
|
|
||||||
s.Where(sql.False())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.Where(sql.In(s.C(FieldPassword), v...))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PasswordNotIn applies the NotIn predicate on the "password" field.
|
// PasswordNotIn applies the NotIn predicate on the "password" field.
|
||||||
func PasswordNotIn(vs ...string) predicate.User {
|
func PasswordNotIn(vs ...string) predicate.User {
|
||||||
v := make([]interface{}, len(vs))
|
return predicate.User(sql.FieldNotIn(FieldPassword, vs...))
|
||||||
for i := range v {
|
|
||||||
v[i] = vs[i]
|
|
||||||
}
|
|
||||||
return predicate.User(func(s *sql.Selector) {
|
|
||||||
// if not arguments were provided, append the FALSE constants,
|
|
||||||
// since we can't apply "IN ()". This will make this predicate falsy.
|
|
||||||
if len(v) == 0 {
|
|
||||||
s.Where(sql.False())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.Where(sql.NotIn(s.C(FieldPassword), v...))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PasswordGT applies the GT predicate on the "password" field.
|
// PasswordGT applies the GT predicate on the "password" field.
|
||||||
func PasswordGT(v string) predicate.User {
|
func PasswordGT(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldGT(FieldPassword, v))
|
||||||
s.Where(sql.GT(s.C(FieldPassword), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PasswordGTE applies the GTE predicate on the "password" field.
|
// PasswordGTE applies the GTE predicate on the "password" field.
|
||||||
func PasswordGTE(v string) predicate.User {
|
func PasswordGTE(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldGTE(FieldPassword, v))
|
||||||
s.Where(sql.GTE(s.C(FieldPassword), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PasswordLT applies the LT predicate on the "password" field.
|
// PasswordLT applies the LT predicate on the "password" field.
|
||||||
func PasswordLT(v string) predicate.User {
|
func PasswordLT(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldLT(FieldPassword, v))
|
||||||
s.Where(sql.LT(s.C(FieldPassword), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PasswordLTE applies the LTE predicate on the "password" field.
|
// PasswordLTE applies the LTE predicate on the "password" field.
|
||||||
func PasswordLTE(v string) predicate.User {
|
func PasswordLTE(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldLTE(FieldPassword, v))
|
||||||
s.Where(sql.LTE(s.C(FieldPassword), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PasswordContains applies the Contains predicate on the "password" field.
|
// PasswordContains applies the Contains predicate on the "password" field.
|
||||||
func PasswordContains(v string) predicate.User {
|
func PasswordContains(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldContains(FieldPassword, v))
|
||||||
s.Where(sql.Contains(s.C(FieldPassword), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PasswordHasPrefix applies the HasPrefix predicate on the "password" field.
|
// PasswordHasPrefix applies the HasPrefix predicate on the "password" field.
|
||||||
func PasswordHasPrefix(v string) predicate.User {
|
func PasswordHasPrefix(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldHasPrefix(FieldPassword, v))
|
||||||
s.Where(sql.HasPrefix(s.C(FieldPassword), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PasswordHasSuffix applies the HasSuffix predicate on the "password" field.
|
// PasswordHasSuffix applies the HasSuffix predicate on the "password" field.
|
||||||
func PasswordHasSuffix(v string) predicate.User {
|
func PasswordHasSuffix(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldHasSuffix(FieldPassword, v))
|
||||||
s.Where(sql.HasSuffix(s.C(FieldPassword), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PasswordEqualFold applies the EqualFold predicate on the "password" field.
|
// PasswordEqualFold applies the EqualFold predicate on the "password" field.
|
||||||
func PasswordEqualFold(v string) predicate.User {
|
func PasswordEqualFold(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldEqualFold(FieldPassword, v))
|
||||||
s.Where(sql.EqualFold(s.C(FieldPassword), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PasswordContainsFold applies the ContainsFold predicate on the "password" field.
|
// PasswordContainsFold applies the ContainsFold predicate on the "password" field.
|
||||||
func PasswordContainsFold(v string) predicate.User {
|
func PasswordContainsFold(v string) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldContainsFold(FieldPassword, v))
|
||||||
s.Where(sql.ContainsFold(s.C(FieldPassword), v))
|
}
|
||||||
})
|
|
||||||
|
// VerifiedEQ applies the EQ predicate on the "verified" field.
|
||||||
|
func VerifiedEQ(v bool) predicate.User {
|
||||||
|
return predicate.User(sql.FieldEQ(FieldVerified, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifiedNEQ applies the NEQ predicate on the "verified" field.
|
||||||
|
func VerifiedNEQ(v bool) predicate.User {
|
||||||
|
return predicate.User(sql.FieldNEQ(FieldVerified, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminEQ applies the EQ predicate on the "admin" field.
|
||||||
|
func AdminEQ(v bool) predicate.User {
|
||||||
|
return predicate.User(sql.FieldEQ(FieldAdmin, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminNEQ applies the NEQ predicate on the "admin" field.
|
||||||
|
func AdminNEQ(v bool) predicate.User {
|
||||||
|
return predicate.User(sql.FieldNEQ(FieldAdmin, v))
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
|
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
|
||||||
func CreatedAtEQ(v time.Time) predicate.User {
|
func CreatedAtEQ(v time.Time) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldEQ(FieldCreatedAt, v))
|
||||||
s.Where(sql.EQ(s.C(FieldCreatedAt), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
|
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
|
||||||
func CreatedAtNEQ(v time.Time) predicate.User {
|
func CreatedAtNEQ(v time.Time) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldNEQ(FieldCreatedAt, v))
|
||||||
s.Where(sql.NEQ(s.C(FieldCreatedAt), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatedAtIn applies the In predicate on the "created_at" field.
|
// CreatedAtIn applies the In predicate on the "created_at" field.
|
||||||
func CreatedAtIn(vs ...time.Time) predicate.User {
|
func CreatedAtIn(vs ...time.Time) predicate.User {
|
||||||
v := make([]interface{}, len(vs))
|
return predicate.User(sql.FieldIn(FieldCreatedAt, vs...))
|
||||||
for i := range v {
|
|
||||||
v[i] = vs[i]
|
|
||||||
}
|
|
||||||
return predicate.User(func(s *sql.Selector) {
|
|
||||||
// if not arguments were provided, append the FALSE constants,
|
|
||||||
// since we can't apply "IN ()". This will make this predicate falsy.
|
|
||||||
if len(v) == 0 {
|
|
||||||
s.Where(sql.False())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.Where(sql.In(s.C(FieldCreatedAt), v...))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
|
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
|
||||||
func CreatedAtNotIn(vs ...time.Time) predicate.User {
|
func CreatedAtNotIn(vs ...time.Time) predicate.User {
|
||||||
v := make([]interface{}, len(vs))
|
return predicate.User(sql.FieldNotIn(FieldCreatedAt, vs...))
|
||||||
for i := range v {
|
|
||||||
v[i] = vs[i]
|
|
||||||
}
|
|
||||||
return predicate.User(func(s *sql.Selector) {
|
|
||||||
// if not arguments were provided, append the FALSE constants,
|
|
||||||
// since we can't apply "IN ()". This will make this predicate falsy.
|
|
||||||
if len(v) == 0 {
|
|
||||||
s.Where(sql.False())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.Where(sql.NotIn(s.C(FieldCreatedAt), v...))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatedAtGT applies the GT predicate on the "created_at" field.
|
// CreatedAtGT applies the GT predicate on the "created_at" field.
|
||||||
func CreatedAtGT(v time.Time) predicate.User {
|
func CreatedAtGT(v time.Time) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldGT(FieldCreatedAt, v))
|
||||||
s.Where(sql.GT(s.C(FieldCreatedAt), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
|
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
|
||||||
func CreatedAtGTE(v time.Time) predicate.User {
|
func CreatedAtGTE(v time.Time) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldGTE(FieldCreatedAt, v))
|
||||||
s.Where(sql.GTE(s.C(FieldCreatedAt), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatedAtLT applies the LT predicate on the "created_at" field.
|
// CreatedAtLT applies the LT predicate on the "created_at" field.
|
||||||
func CreatedAtLT(v time.Time) predicate.User {
|
func CreatedAtLT(v time.Time) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldLT(FieldCreatedAt, v))
|
||||||
s.Where(sql.LT(s.C(FieldCreatedAt), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
|
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
|
||||||
func CreatedAtLTE(v time.Time) predicate.User {
|
func CreatedAtLTE(v time.Time) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.FieldLTE(FieldCreatedAt, v))
|
||||||
s.Where(sql.LTE(s.C(FieldCreatedAt), v))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasOwner applies the HasEdge predicate on the "owner" edge.
|
// HasOwner applies the HasEdge predicate on the "owner" edge.
|
||||||
|
|
@ -535,7 +345,6 @@ func HasOwner() predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(func(s *sql.Selector) {
|
||||||
step := sqlgraph.NewStep(
|
step := sqlgraph.NewStep(
|
||||||
sqlgraph.From(Table, FieldID),
|
sqlgraph.From(Table, FieldID),
|
||||||
sqlgraph.To(OwnerTable, FieldID),
|
|
||||||
sqlgraph.Edge(sqlgraph.O2M, true, OwnerTable, OwnerColumn),
|
sqlgraph.Edge(sqlgraph.O2M, true, OwnerTable, OwnerColumn),
|
||||||
)
|
)
|
||||||
sqlgraph.HasNeighbors(s, step)
|
sqlgraph.HasNeighbors(s, step)
|
||||||
|
|
@ -545,11 +354,7 @@ func HasOwner() predicate.User {
|
||||||
// HasOwnerWith applies the HasEdge predicate on the "owner" edge with a given conditions (other predicates).
|
// HasOwnerWith applies the HasEdge predicate on the "owner" edge with a given conditions (other predicates).
|
||||||
func HasOwnerWith(preds ...predicate.PasswordToken) predicate.User {
|
func HasOwnerWith(preds ...predicate.PasswordToken) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(func(s *sql.Selector) {
|
||||||
step := sqlgraph.NewStep(
|
step := newOwnerStep()
|
||||||
sqlgraph.From(Table, FieldID),
|
|
||||||
sqlgraph.To(OwnerInverseTable, FieldID),
|
|
||||||
sqlgraph.Edge(sqlgraph.O2M, true, OwnerTable, OwnerColumn),
|
|
||||||
)
|
|
||||||
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
|
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
|
||||||
for _, p := range preds {
|
for _, p := range preds {
|
||||||
p(s)
|
p(s)
|
||||||
|
|
@ -560,32 +365,15 @@ func HasOwnerWith(preds ...predicate.PasswordToken) predicate.User {
|
||||||
|
|
||||||
// And groups predicates with the AND operator between them.
|
// And groups predicates with the AND operator between them.
|
||||||
func And(predicates ...predicate.User) predicate.User {
|
func And(predicates ...predicate.User) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.AndPredicates(predicates...))
|
||||||
s1 := s.Clone().SetP(nil)
|
|
||||||
for _, p := range predicates {
|
|
||||||
p(s1)
|
|
||||||
}
|
|
||||||
s.Where(s1.P())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Or groups predicates with the OR operator between them.
|
// Or groups predicates with the OR operator between them.
|
||||||
func Or(predicates ...predicate.User) predicate.User {
|
func Or(predicates ...predicate.User) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.OrPredicates(predicates...))
|
||||||
s1 := s.Clone().SetP(nil)
|
|
||||||
for i, p := range predicates {
|
|
||||||
if i > 0 {
|
|
||||||
s1.Or()
|
|
||||||
}
|
|
||||||
p(s1)
|
|
||||||
}
|
|
||||||
s.Where(s1.P())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not applies the not operator on the given predicate.
|
// Not applies the not operator on the given predicate.
|
||||||
func Not(p predicate.User) predicate.User {
|
func Not(p predicate.User) predicate.User {
|
||||||
return predicate.User(func(s *sql.Selector) {
|
return predicate.User(sql.NotPredicates(p))
|
||||||
p(s.Not())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Code generated by entc, DO NOT EDIT.
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
package ent
|
package ent
|
||||||
|
|
||||||
|
|
@ -6,12 +6,12 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"goweb/ent/passwordtoken"
|
|
||||||
"goweb/ent/user"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||||
"entgo.io/ent/schema/field"
|
"entgo.io/ent/schema/field"
|
||||||
|
"github.com/camzawacki/personal-site/ent/passwordtoken"
|
||||||
|
"github.com/camzawacki/personal-site/ent/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UserCreate is the builder for creating a User entity.
|
// UserCreate is the builder for creating a User entity.
|
||||||
|
|
@ -22,104 +22,96 @@ type UserCreate struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetName sets the "name" field.
|
// SetName sets the "name" field.
|
||||||
func (uc *UserCreate) SetName(s string) *UserCreate {
|
func (_c *UserCreate) SetName(v string) *UserCreate {
|
||||||
uc.mutation.SetName(s)
|
_c.mutation.SetName(v)
|
||||||
return uc
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetEmail sets the "email" field.
|
// SetEmail sets the "email" field.
|
||||||
func (uc *UserCreate) SetEmail(s string) *UserCreate {
|
func (_c *UserCreate) SetEmail(v string) *UserCreate {
|
||||||
uc.mutation.SetEmail(s)
|
_c.mutation.SetEmail(v)
|
||||||
return uc
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPassword sets the "password" field.
|
// SetPassword sets the "password" field.
|
||||||
func (uc *UserCreate) SetPassword(s string) *UserCreate {
|
func (_c *UserCreate) SetPassword(v string) *UserCreate {
|
||||||
uc.mutation.SetPassword(s)
|
_c.mutation.SetPassword(v)
|
||||||
return uc
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetVerified sets the "verified" field.
|
||||||
|
func (_c *UserCreate) SetVerified(v bool) *UserCreate {
|
||||||
|
_c.mutation.SetVerified(v)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableVerified sets the "verified" field if the given value is not nil.
|
||||||
|
func (_c *UserCreate) SetNillableVerified(v *bool) *UserCreate {
|
||||||
|
if v != nil {
|
||||||
|
_c.SetVerified(*v)
|
||||||
|
}
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAdmin sets the "admin" field.
|
||||||
|
func (_c *UserCreate) SetAdmin(v bool) *UserCreate {
|
||||||
|
_c.mutation.SetAdmin(v)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableAdmin sets the "admin" field if the given value is not nil.
|
||||||
|
func (_c *UserCreate) SetNillableAdmin(v *bool) *UserCreate {
|
||||||
|
if v != nil {
|
||||||
|
_c.SetAdmin(*v)
|
||||||
|
}
|
||||||
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCreatedAt sets the "created_at" field.
|
// SetCreatedAt sets the "created_at" field.
|
||||||
func (uc *UserCreate) SetCreatedAt(t time.Time) *UserCreate {
|
func (_c *UserCreate) SetCreatedAt(v time.Time) *UserCreate {
|
||||||
uc.mutation.SetCreatedAt(t)
|
_c.mutation.SetCreatedAt(v)
|
||||||
return uc
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
|
// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
|
||||||
func (uc *UserCreate) SetNillableCreatedAt(t *time.Time) *UserCreate {
|
func (_c *UserCreate) SetNillableCreatedAt(v *time.Time) *UserCreate {
|
||||||
if t != nil {
|
if v != nil {
|
||||||
uc.SetCreatedAt(*t)
|
_c.SetCreatedAt(*v)
|
||||||
}
|
}
|
||||||
return uc
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddOwnerIDs adds the "owner" edge to the PasswordToken entity by IDs.
|
// AddOwnerIDs adds the "owner" edge to the PasswordToken entity by IDs.
|
||||||
func (uc *UserCreate) AddOwnerIDs(ids ...int) *UserCreate {
|
func (_c *UserCreate) AddOwnerIDs(ids ...int) *UserCreate {
|
||||||
uc.mutation.AddOwnerIDs(ids...)
|
_c.mutation.AddOwnerIDs(ids...)
|
||||||
return uc
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddOwner adds the "owner" edges to the PasswordToken entity.
|
// AddOwner adds the "owner" edges to the PasswordToken entity.
|
||||||
func (uc *UserCreate) AddOwner(p ...*PasswordToken) *UserCreate {
|
func (_c *UserCreate) AddOwner(v ...*PasswordToken) *UserCreate {
|
||||||
ids := make([]int, len(p))
|
ids := make([]int, len(v))
|
||||||
for i := range p {
|
for i := range v {
|
||||||
ids[i] = p[i].ID
|
ids[i] = v[i].ID
|
||||||
}
|
}
|
||||||
return uc.AddOwnerIDs(ids...)
|
return _c.AddOwnerIDs(ids...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mutation returns the UserMutation object of the builder.
|
// Mutation returns the UserMutation object of the builder.
|
||||||
func (uc *UserCreate) Mutation() *UserMutation {
|
func (_c *UserCreate) Mutation() *UserMutation {
|
||||||
return uc.mutation
|
return _c.mutation
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save creates the User in the database.
|
// Save creates the User in the database.
|
||||||
func (uc *UserCreate) Save(ctx context.Context) (*User, error) {
|
func (_c *UserCreate) Save(ctx context.Context) (*User, error) {
|
||||||
var (
|
if err := _c.defaults(); err != nil {
|
||||||
err error
|
|
||||||
node *User
|
|
||||||
)
|
|
||||||
if err := uc.defaults(); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(uc.hooks) == 0 {
|
return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)
|
||||||
if err = uc.check(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
node, err = uc.sqlSave(ctx)
|
|
||||||
} else {
|
|
||||||
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
|
|
||||||
mutation, ok := m.(*UserMutation)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("unexpected mutation type %T", m)
|
|
||||||
}
|
|
||||||
if err = uc.check(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
uc.mutation = mutation
|
|
||||||
if node, err = uc.sqlSave(ctx); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
mutation.id = &node.ID
|
|
||||||
mutation.done = true
|
|
||||||
return node, err
|
|
||||||
})
|
|
||||||
for i := len(uc.hooks) - 1; i >= 0; i-- {
|
|
||||||
if uc.hooks[i] == nil {
|
|
||||||
return nil, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)")
|
|
||||||
}
|
|
||||||
mut = uc.hooks[i](mut)
|
|
||||||
}
|
|
||||||
if _, err := mut.Mutate(ctx, uc.mutation); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return node, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveX calls Save and panics if Save returns an error.
|
// SaveX calls Save and panics if Save returns an error.
|
||||||
func (uc *UserCreate) SaveX(ctx context.Context) *User {
|
func (_c *UserCreate) SaveX(ctx context.Context) *User {
|
||||||
v, err := uc.Save(ctx)
|
v, err := _c.Save(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
@ -127,119 +119,124 @@ func (uc *UserCreate) SaveX(ctx context.Context) *User {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exec executes the query.
|
// Exec executes the query.
|
||||||
func (uc *UserCreate) Exec(ctx context.Context) error {
|
func (_c *UserCreate) Exec(ctx context.Context) error {
|
||||||
_, err := uc.Save(ctx)
|
_, err := _c.Save(ctx)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecX is like Exec, but panics if an error occurs.
|
// ExecX is like Exec, but panics if an error occurs.
|
||||||
func (uc *UserCreate) ExecX(ctx context.Context) {
|
func (_c *UserCreate) ExecX(ctx context.Context) {
|
||||||
if err := uc.Exec(ctx); err != nil {
|
if err := _c.Exec(ctx); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaults sets the default values of the builder before save.
|
// defaults sets the default values of the builder before save.
|
||||||
func (uc *UserCreate) defaults() error {
|
func (_c *UserCreate) defaults() error {
|
||||||
if _, ok := uc.mutation.CreatedAt(); !ok {
|
if _, ok := _c.mutation.Verified(); !ok {
|
||||||
|
v := user.DefaultVerified
|
||||||
|
_c.mutation.SetVerified(v)
|
||||||
|
}
|
||||||
|
if _, ok := _c.mutation.Admin(); !ok {
|
||||||
|
v := user.DefaultAdmin
|
||||||
|
_c.mutation.SetAdmin(v)
|
||||||
|
}
|
||||||
|
if _, ok := _c.mutation.CreatedAt(); !ok {
|
||||||
if user.DefaultCreatedAt == nil {
|
if user.DefaultCreatedAt == nil {
|
||||||
return fmt.Errorf("ent: uninitialized user.DefaultCreatedAt (forgotten import ent/runtime?)")
|
return fmt.Errorf("ent: uninitialized user.DefaultCreatedAt (forgotten import ent/runtime?)")
|
||||||
}
|
}
|
||||||
v := user.DefaultCreatedAt()
|
v := user.DefaultCreatedAt()
|
||||||
uc.mutation.SetCreatedAt(v)
|
_c.mutation.SetCreatedAt(v)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// check runs all checks and user-defined validators on the builder.
|
// check runs all checks and user-defined validators on the builder.
|
||||||
func (uc *UserCreate) check() error {
|
func (_c *UserCreate) check() error {
|
||||||
if _, ok := uc.mutation.Name(); !ok {
|
if _, ok := _c.mutation.Name(); !ok {
|
||||||
return &ValidationError{Name: "name", err: errors.New(`ent: missing required field "name"`)}
|
return &ValidationError{Name: "name", err: errors.New(`ent: missing required field "User.name"`)}
|
||||||
}
|
}
|
||||||
if v, ok := uc.mutation.Name(); ok {
|
if v, ok := _c.mutation.Name(); ok {
|
||||||
if err := user.NameValidator(v); err != nil {
|
if err := user.NameValidator(v); err != nil {
|
||||||
return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "name": %w`, err)}
|
return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "User.name": %w`, err)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, ok := uc.mutation.Email(); !ok {
|
if _, ok := _c.mutation.Email(); !ok {
|
||||||
return &ValidationError{Name: "email", err: errors.New(`ent: missing required field "email"`)}
|
return &ValidationError{Name: "email", err: errors.New(`ent: missing required field "User.email"`)}
|
||||||
}
|
}
|
||||||
if v, ok := uc.mutation.Email(); ok {
|
if v, ok := _c.mutation.Email(); ok {
|
||||||
if err := user.EmailValidator(v); err != nil {
|
if err := user.EmailValidator(v); err != nil {
|
||||||
return &ValidationError{Name: "email", err: fmt.Errorf(`ent: validator failed for field "email": %w`, err)}
|
return &ValidationError{Name: "email", err: fmt.Errorf(`ent: validator failed for field "User.email": %w`, err)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, ok := uc.mutation.Password(); !ok {
|
if _, ok := _c.mutation.Password(); !ok {
|
||||||
return &ValidationError{Name: "password", err: errors.New(`ent: missing required field "password"`)}
|
return &ValidationError{Name: "password", err: errors.New(`ent: missing required field "User.password"`)}
|
||||||
}
|
}
|
||||||
if v, ok := uc.mutation.Password(); ok {
|
if v, ok := _c.mutation.Password(); ok {
|
||||||
if err := user.PasswordValidator(v); err != nil {
|
if err := user.PasswordValidator(v); err != nil {
|
||||||
return &ValidationError{Name: "password", err: fmt.Errorf(`ent: validator failed for field "password": %w`, err)}
|
return &ValidationError{Name: "password", err: fmt.Errorf(`ent: validator failed for field "User.password": %w`, err)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, ok := uc.mutation.CreatedAt(); !ok {
|
if _, ok := _c.mutation.Verified(); !ok {
|
||||||
return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "created_at"`)}
|
return &ValidationError{Name: "verified", err: errors.New(`ent: missing required field "User.verified"`)}
|
||||||
|
}
|
||||||
|
if _, ok := _c.mutation.Admin(); !ok {
|
||||||
|
return &ValidationError{Name: "admin", err: errors.New(`ent: missing required field "User.admin"`)}
|
||||||
|
}
|
||||||
|
if _, ok := _c.mutation.CreatedAt(); !ok {
|
||||||
|
return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "User.created_at"`)}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uc *UserCreate) sqlSave(ctx context.Context) (*User, error) {
|
func (_c *UserCreate) sqlSave(ctx context.Context) (*User, error) {
|
||||||
_node, _spec := uc.createSpec()
|
if err := _c.check(); err != nil {
|
||||||
if err := sqlgraph.CreateNode(ctx, uc.driver, _spec); err != nil {
|
return nil, err
|
||||||
|
}
|
||||||
|
_node, _spec := _c.createSpec()
|
||||||
|
if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil {
|
||||||
if sqlgraph.IsConstraintError(err) {
|
if sqlgraph.IsConstraintError(err) {
|
||||||
err = &ConstraintError{err.Error(), err}
|
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
id := _spec.ID.Value.(int64)
|
id := _spec.ID.Value.(int64)
|
||||||
_node.ID = int(id)
|
_node.ID = int(id)
|
||||||
|
_c.mutation.id = &_node.ID
|
||||||
|
_c.mutation.done = true
|
||||||
return _node, nil
|
return _node, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uc *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) {
|
func (_c *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) {
|
||||||
var (
|
var (
|
||||||
_node = &User{config: uc.config}
|
_node = &User{config: _c.config}
|
||||||
_spec = &sqlgraph.CreateSpec{
|
_spec = sqlgraph.NewCreateSpec(user.Table, sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt))
|
||||||
Table: user.Table,
|
|
||||||
ID: &sqlgraph.FieldSpec{
|
|
||||||
Type: field.TypeInt,
|
|
||||||
Column: user.FieldID,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
if value, ok := uc.mutation.Name(); ok {
|
if value, ok := _c.mutation.Name(); ok {
|
||||||
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
|
_spec.SetField(user.FieldName, field.TypeString, value)
|
||||||
Type: field.TypeString,
|
|
||||||
Value: value,
|
|
||||||
Column: user.FieldName,
|
|
||||||
})
|
|
||||||
_node.Name = value
|
_node.Name = value
|
||||||
}
|
}
|
||||||
if value, ok := uc.mutation.Email(); ok {
|
if value, ok := _c.mutation.Email(); ok {
|
||||||
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
|
_spec.SetField(user.FieldEmail, field.TypeString, value)
|
||||||
Type: field.TypeString,
|
|
||||||
Value: value,
|
|
||||||
Column: user.FieldEmail,
|
|
||||||
})
|
|
||||||
_node.Email = value
|
_node.Email = value
|
||||||
}
|
}
|
||||||
if value, ok := uc.mutation.Password(); ok {
|
if value, ok := _c.mutation.Password(); ok {
|
||||||
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
|
_spec.SetField(user.FieldPassword, field.TypeString, value)
|
||||||
Type: field.TypeString,
|
|
||||||
Value: value,
|
|
||||||
Column: user.FieldPassword,
|
|
||||||
})
|
|
||||||
_node.Password = value
|
_node.Password = value
|
||||||
}
|
}
|
||||||
if value, ok := uc.mutation.CreatedAt(); ok {
|
if value, ok := _c.mutation.Verified(); ok {
|
||||||
_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
|
_spec.SetField(user.FieldVerified, field.TypeBool, value)
|
||||||
Type: field.TypeTime,
|
_node.Verified = value
|
||||||
Value: value,
|
}
|
||||||
Column: user.FieldCreatedAt,
|
if value, ok := _c.mutation.Admin(); ok {
|
||||||
})
|
_spec.SetField(user.FieldAdmin, field.TypeBool, value)
|
||||||
|
_node.Admin = value
|
||||||
|
}
|
||||||
|
if value, ok := _c.mutation.CreatedAt(); ok {
|
||||||
|
_spec.SetField(user.FieldCreatedAt, field.TypeTime, value)
|
||||||
_node.CreatedAt = value
|
_node.CreatedAt = value
|
||||||
}
|
}
|
||||||
if nodes := uc.mutation.OwnerIDs(); len(nodes) > 0 {
|
if nodes := _c.mutation.OwnerIDs(); len(nodes) > 0 {
|
||||||
edge := &sqlgraph.EdgeSpec{
|
edge := &sqlgraph.EdgeSpec{
|
||||||
Rel: sqlgraph.O2M,
|
Rel: sqlgraph.O2M,
|
||||||
Inverse: true,
|
Inverse: true,
|
||||||
|
|
@ -247,10 +244,7 @@ func (uc *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) {
|
||||||
Columns: []string{user.OwnerColumn},
|
Columns: []string{user.OwnerColumn},
|
||||||
Bidi: false,
|
Bidi: false,
|
||||||
Target: &sqlgraph.EdgeTarget{
|
Target: &sqlgraph.EdgeTarget{
|
||||||
IDSpec: &sqlgraph.FieldSpec{
|
IDSpec: sqlgraph.NewFieldSpec(passwordtoken.FieldID, field.TypeInt),
|
||||||
Type: field.TypeInt,
|
|
||||||
Column: passwordtoken.FieldID,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, k := range nodes {
|
for _, k := range nodes {
|
||||||
|
|
@ -264,17 +258,21 @@ func (uc *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) {
|
||||||
// UserCreateBulk is the builder for creating many User entities in bulk.
|
// UserCreateBulk is the builder for creating many User entities in bulk.
|
||||||
type UserCreateBulk struct {
|
type UserCreateBulk struct {
|
||||||
config
|
config
|
||||||
|
err error
|
||||||
builders []*UserCreate
|
builders []*UserCreate
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save creates the User entities in the database.
|
// Save creates the User entities in the database.
|
||||||
func (ucb *UserCreateBulk) Save(ctx context.Context) ([]*User, error) {
|
func (_c *UserCreateBulk) Save(ctx context.Context) ([]*User, error) {
|
||||||
specs := make([]*sqlgraph.CreateSpec, len(ucb.builders))
|
if _c.err != nil {
|
||||||
nodes := make([]*User, len(ucb.builders))
|
return nil, _c.err
|
||||||
mutators := make([]Mutator, len(ucb.builders))
|
}
|
||||||
for i := range ucb.builders {
|
specs := make([]*sqlgraph.CreateSpec, len(_c.builders))
|
||||||
|
nodes := make([]*User, len(_c.builders))
|
||||||
|
mutators := make([]Mutator, len(_c.builders))
|
||||||
|
for i := range _c.builders {
|
||||||
func(i int, root context.Context) {
|
func(i int, root context.Context) {
|
||||||
builder := ucb.builders[i]
|
builder := _c.builders[i]
|
||||||
builder.defaults()
|
builder.defaults()
|
||||||
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
|
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
|
||||||
mutation, ok := m.(*UserMutation)
|
mutation, ok := m.(*UserMutation)
|
||||||
|
|
@ -285,16 +283,16 @@ func (ucb *UserCreateBulk) Save(ctx context.Context) ([]*User, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
builder.mutation = mutation
|
builder.mutation = mutation
|
||||||
nodes[i], specs[i] = builder.createSpec()
|
|
||||||
var err error
|
var err error
|
||||||
|
nodes[i], specs[i] = builder.createSpec()
|
||||||
if i < len(mutators)-1 {
|
if i < len(mutators)-1 {
|
||||||
_, err = mutators[i+1].Mutate(root, ucb.builders[i+1].mutation)
|
_, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation)
|
||||||
} else {
|
} else {
|
||||||
spec := &sqlgraph.BatchCreateSpec{Nodes: specs}
|
spec := &sqlgraph.BatchCreateSpec{Nodes: specs}
|
||||||
// Invoke the actual operation on the latest mutation in the chain.
|
// Invoke the actual operation on the latest mutation in the chain.
|
||||||
if err = sqlgraph.BatchCreate(ctx, ucb.driver, spec); err != nil {
|
if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil {
|
||||||
if sqlgraph.IsConstraintError(err) {
|
if sqlgraph.IsConstraintError(err) {
|
||||||
err = &ConstraintError{err.Error(), err}
|
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -302,11 +300,11 @@ func (ucb *UserCreateBulk) Save(ctx context.Context) ([]*User, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
mutation.id = &nodes[i].ID
|
mutation.id = &nodes[i].ID
|
||||||
mutation.done = true
|
|
||||||
if specs[i].ID.Value != nil {
|
if specs[i].ID.Value != nil {
|
||||||
id := specs[i].ID.Value.(int64)
|
id := specs[i].ID.Value.(int64)
|
||||||
nodes[i].ID = int(id)
|
nodes[i].ID = int(id)
|
||||||
}
|
}
|
||||||
|
mutation.done = true
|
||||||
return nodes[i], nil
|
return nodes[i], nil
|
||||||
})
|
})
|
||||||
for i := len(builder.hooks) - 1; i >= 0; i-- {
|
for i := len(builder.hooks) - 1; i >= 0; i-- {
|
||||||
|
|
@ -316,7 +314,7 @@ func (ucb *UserCreateBulk) Save(ctx context.Context) ([]*User, error) {
|
||||||
}(i, ctx)
|
}(i, ctx)
|
||||||
}
|
}
|
||||||
if len(mutators) > 0 {
|
if len(mutators) > 0 {
|
||||||
if _, err := mutators[0].Mutate(ctx, ucb.builders[0].mutation); err != nil {
|
if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -324,8 +322,8 @@ func (ucb *UserCreateBulk) Save(ctx context.Context) ([]*User, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveX is like Save, but panics if an error occurs.
|
// SaveX is like Save, but panics if an error occurs.
|
||||||
func (ucb *UserCreateBulk) SaveX(ctx context.Context) []*User {
|
func (_c *UserCreateBulk) SaveX(ctx context.Context) []*User {
|
||||||
v, err := ucb.Save(ctx)
|
v, err := _c.Save(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
@ -333,14 +331,14 @@ func (ucb *UserCreateBulk) SaveX(ctx context.Context) []*User {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exec executes the query.
|
// Exec executes the query.
|
||||||
func (ucb *UserCreateBulk) Exec(ctx context.Context) error {
|
func (_c *UserCreateBulk) Exec(ctx context.Context) error {
|
||||||
_, err := ucb.Save(ctx)
|
_, err := _c.Save(ctx)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecX is like Exec, but panics if an error occurs.
|
// ExecX is like Exec, but panics if an error occurs.
|
||||||
func (ucb *UserCreateBulk) ExecX(ctx context.Context) {
|
func (_c *UserCreateBulk) ExecX(ctx context.Context) {
|
||||||
if err := ucb.Exec(ctx); err != nil {
|
if err := _c.Exec(ctx); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,15 @@
|
||||||
// Code generated by entc, DO NOT EDIT.
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
package ent
|
package ent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"goweb/ent/predicate"
|
|
||||||
"goweb/ent/user"
|
|
||||||
|
|
||||||
"entgo.io/ent/dialect/sql"
|
"entgo.io/ent/dialect/sql"
|
||||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||||
"entgo.io/ent/schema/field"
|
"entgo.io/ent/schema/field"
|
||||||
|
"github.com/camzawacki/personal-site/ent/predicate"
|
||||||
|
"github.com/camzawacki/personal-site/ent/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UserDelete is the builder for deleting a User entity.
|
// UserDelete is the builder for deleting a User entity.
|
||||||
|
|
@ -21,80 +20,56 @@ type UserDelete struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Where appends a list predicates to the UserDelete builder.
|
// Where appends a list predicates to the UserDelete builder.
|
||||||
func (ud *UserDelete) Where(ps ...predicate.User) *UserDelete {
|
func (_d *UserDelete) Where(ps ...predicate.User) *UserDelete {
|
||||||
ud.mutation.Where(ps...)
|
_d.mutation.Where(ps...)
|
||||||
return ud
|
return _d
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exec executes the deletion query and returns how many vertices were deleted.
|
// Exec executes the deletion query and returns how many vertices were deleted.
|
||||||
func (ud *UserDelete) Exec(ctx context.Context) (int, error) {
|
func (_d *UserDelete) Exec(ctx context.Context) (int, error) {
|
||||||
var (
|
return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks)
|
||||||
err error
|
|
||||||
affected int
|
|
||||||
)
|
|
||||||
if len(ud.hooks) == 0 {
|
|
||||||
affected, err = ud.sqlExec(ctx)
|
|
||||||
} else {
|
|
||||||
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
|
|
||||||
mutation, ok := m.(*UserMutation)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("unexpected mutation type %T", m)
|
|
||||||
}
|
|
||||||
ud.mutation = mutation
|
|
||||||
affected, err = ud.sqlExec(ctx)
|
|
||||||
mutation.done = true
|
|
||||||
return affected, err
|
|
||||||
})
|
|
||||||
for i := len(ud.hooks) - 1; i >= 0; i-- {
|
|
||||||
if ud.hooks[i] == nil {
|
|
||||||
return 0, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)")
|
|
||||||
}
|
|
||||||
mut = ud.hooks[i](mut)
|
|
||||||
}
|
|
||||||
if _, err := mut.Mutate(ctx, ud.mutation); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return affected, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecX is like Exec, but panics if an error occurs.
|
// ExecX is like Exec, but panics if an error occurs.
|
||||||
func (ud *UserDelete) ExecX(ctx context.Context) int {
|
func (_d *UserDelete) ExecX(ctx context.Context) int {
|
||||||
n, err := ud.Exec(ctx)
|
n, err := _d.Exec(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ud *UserDelete) sqlExec(ctx context.Context) (int, error) {
|
func (_d *UserDelete) sqlExec(ctx context.Context) (int, error) {
|
||||||
_spec := &sqlgraph.DeleteSpec{
|
_spec := sqlgraph.NewDeleteSpec(user.Table, sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt))
|
||||||
Node: &sqlgraph.NodeSpec{
|
if ps := _d.mutation.predicates; len(ps) > 0 {
|
||||||
Table: user.Table,
|
|
||||||
ID: &sqlgraph.FieldSpec{
|
|
||||||
Type: field.TypeInt,
|
|
||||||
Column: user.FieldID,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if ps := ud.mutation.predicates; len(ps) > 0 {
|
|
||||||
_spec.Predicate = func(selector *sql.Selector) {
|
_spec.Predicate = func(selector *sql.Selector) {
|
||||||
for i := range ps {
|
for i := range ps {
|
||||||
ps[i](selector)
|
ps[i](selector)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sqlgraph.DeleteNodes(ctx, ud.driver, _spec)
|
affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec)
|
||||||
|
if err != nil && sqlgraph.IsConstraintError(err) {
|
||||||
|
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||||
|
}
|
||||||
|
_d.mutation.done = true
|
||||||
|
return affected, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserDeleteOne is the builder for deleting a single User entity.
|
// UserDeleteOne is the builder for deleting a single User entity.
|
||||||
type UserDeleteOne struct {
|
type UserDeleteOne struct {
|
||||||
ud *UserDelete
|
_d *UserDelete
|
||||||
|
}
|
||||||
|
|
||||||
|
// Where appends a list predicates to the UserDelete builder.
|
||||||
|
func (_d *UserDeleteOne) Where(ps ...predicate.User) *UserDeleteOne {
|
||||||
|
_d._d.mutation.Where(ps...)
|
||||||
|
return _d
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exec executes the deletion query.
|
// Exec executes the deletion query.
|
||||||
func (udo *UserDeleteOne) Exec(ctx context.Context) error {
|
func (_d *UserDeleteOne) Exec(ctx context.Context) error {
|
||||||
n, err := udo.ud.Exec(ctx)
|
n, err := _d._d.Exec(ctx)
|
||||||
switch {
|
switch {
|
||||||
case err != nil:
|
case err != nil:
|
||||||
return err
|
return err
|
||||||
|
|
@ -106,6 +81,8 @@ func (udo *UserDeleteOne) Exec(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecX is like Exec, but panics if an error occurs.
|
// ExecX is like Exec, but panics if an error occurs.
|
||||||
func (udo *UserDeleteOne) ExecX(ctx context.Context) {
|
func (_d *UserDeleteOne) ExecX(ctx context.Context) {
|
||||||
udo.ud.ExecX(ctx)
|
if err := _d.Exec(ctx); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,17 +1,18 @@
|
||||||
// Code generated by entc, DO NOT EDIT.
|
// Code generated by ent, DO NOT EDIT.
|
||||||
|
|
||||||
package ent
|
package ent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"goweb/ent/passwordtoken"
|
|
||||||
"goweb/ent/predicate"
|
|
||||||
"goweb/ent/user"
|
|
||||||
|
|
||||||
"entgo.io/ent/dialect/sql"
|
"entgo.io/ent/dialect/sql"
|
||||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||||
"entgo.io/ent/schema/field"
|
"entgo.io/ent/schema/field"
|
||||||
|
"github.com/camzawacki/personal-site/ent/passwordtoken"
|
||||||
|
"github.com/camzawacki/personal-site/ent/predicate"
|
||||||
|
"github.com/camzawacki/personal-site/ent/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UserUpdate is the builder for updating User entities.
|
// UserUpdate is the builder for updating User entities.
|
||||||
|
|
@ -22,111 +23,130 @@ type UserUpdate struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Where appends a list predicates to the UserUpdate builder.
|
// Where appends a list predicates to the UserUpdate builder.
|
||||||
func (uu *UserUpdate) Where(ps ...predicate.User) *UserUpdate {
|
func (_u *UserUpdate) Where(ps ...predicate.User) *UserUpdate {
|
||||||
uu.mutation.Where(ps...)
|
_u.mutation.Where(ps...)
|
||||||
return uu
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetName sets the "name" field.
|
// SetName sets the "name" field.
|
||||||
func (uu *UserUpdate) SetName(s string) *UserUpdate {
|
func (_u *UserUpdate) SetName(v string) *UserUpdate {
|
||||||
uu.mutation.SetName(s)
|
_u.mutation.SetName(v)
|
||||||
return uu
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableName sets the "name" field if the given value is not nil.
|
||||||
|
func (_u *UserUpdate) SetNillableName(v *string) *UserUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetName(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetEmail sets the "email" field.
|
// SetEmail sets the "email" field.
|
||||||
func (uu *UserUpdate) SetEmail(s string) *UserUpdate {
|
func (_u *UserUpdate) SetEmail(v string) *UserUpdate {
|
||||||
uu.mutation.SetEmail(s)
|
_u.mutation.SetEmail(v)
|
||||||
return uu
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableEmail sets the "email" field if the given value is not nil.
|
||||||
|
func (_u *UserUpdate) SetNillableEmail(v *string) *UserUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetEmail(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPassword sets the "password" field.
|
// SetPassword sets the "password" field.
|
||||||
func (uu *UserUpdate) SetPassword(s string) *UserUpdate {
|
func (_u *UserUpdate) SetPassword(v string) *UserUpdate {
|
||||||
uu.mutation.SetPassword(s)
|
_u.mutation.SetPassword(v)
|
||||||
return uu
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillablePassword sets the "password" field if the given value is not nil.
|
||||||
|
func (_u *UserUpdate) SetNillablePassword(v *string) *UserUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetPassword(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetVerified sets the "verified" field.
|
||||||
|
func (_u *UserUpdate) SetVerified(v bool) *UserUpdate {
|
||||||
|
_u.mutation.SetVerified(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableVerified sets the "verified" field if the given value is not nil.
|
||||||
|
func (_u *UserUpdate) SetNillableVerified(v *bool) *UserUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetVerified(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAdmin sets the "admin" field.
|
||||||
|
func (_u *UserUpdate) SetAdmin(v bool) *UserUpdate {
|
||||||
|
_u.mutation.SetAdmin(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableAdmin sets the "admin" field if the given value is not nil.
|
||||||
|
func (_u *UserUpdate) SetNillableAdmin(v *bool) *UserUpdate {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetAdmin(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddOwnerIDs adds the "owner" edge to the PasswordToken entity by IDs.
|
// AddOwnerIDs adds the "owner" edge to the PasswordToken entity by IDs.
|
||||||
func (uu *UserUpdate) AddOwnerIDs(ids ...int) *UserUpdate {
|
func (_u *UserUpdate) AddOwnerIDs(ids ...int) *UserUpdate {
|
||||||
uu.mutation.AddOwnerIDs(ids...)
|
_u.mutation.AddOwnerIDs(ids...)
|
||||||
return uu
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddOwner adds the "owner" edges to the PasswordToken entity.
|
// AddOwner adds the "owner" edges to the PasswordToken entity.
|
||||||
func (uu *UserUpdate) AddOwner(p ...*PasswordToken) *UserUpdate {
|
func (_u *UserUpdate) AddOwner(v ...*PasswordToken) *UserUpdate {
|
||||||
ids := make([]int, len(p))
|
ids := make([]int, len(v))
|
||||||
for i := range p {
|
for i := range v {
|
||||||
ids[i] = p[i].ID
|
ids[i] = v[i].ID
|
||||||
}
|
}
|
||||||
return uu.AddOwnerIDs(ids...)
|
return _u.AddOwnerIDs(ids...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mutation returns the UserMutation object of the builder.
|
// Mutation returns the UserMutation object of the builder.
|
||||||
func (uu *UserUpdate) Mutation() *UserMutation {
|
func (_u *UserUpdate) Mutation() *UserMutation {
|
||||||
return uu.mutation
|
return _u.mutation
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClearOwner clears all "owner" edges to the PasswordToken entity.
|
// ClearOwner clears all "owner" edges to the PasswordToken entity.
|
||||||
func (uu *UserUpdate) ClearOwner() *UserUpdate {
|
func (_u *UserUpdate) ClearOwner() *UserUpdate {
|
||||||
uu.mutation.ClearOwner()
|
_u.mutation.ClearOwner()
|
||||||
return uu
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveOwnerIDs removes the "owner" edge to PasswordToken entities by IDs.
|
// RemoveOwnerIDs removes the "owner" edge to PasswordToken entities by IDs.
|
||||||
func (uu *UserUpdate) RemoveOwnerIDs(ids ...int) *UserUpdate {
|
func (_u *UserUpdate) RemoveOwnerIDs(ids ...int) *UserUpdate {
|
||||||
uu.mutation.RemoveOwnerIDs(ids...)
|
_u.mutation.RemoveOwnerIDs(ids...)
|
||||||
return uu
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveOwner removes "owner" edges to PasswordToken entities.
|
// RemoveOwner removes "owner" edges to PasswordToken entities.
|
||||||
func (uu *UserUpdate) RemoveOwner(p ...*PasswordToken) *UserUpdate {
|
func (_u *UserUpdate) RemoveOwner(v ...*PasswordToken) *UserUpdate {
|
||||||
ids := make([]int, len(p))
|
ids := make([]int, len(v))
|
||||||
for i := range p {
|
for i := range v {
|
||||||
ids[i] = p[i].ID
|
ids[i] = v[i].ID
|
||||||
}
|
}
|
||||||
return uu.RemoveOwnerIDs(ids...)
|
return _u.RemoveOwnerIDs(ids...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save executes the query and returns the number of nodes affected by the update operation.
|
// Save executes the query and returns the number of nodes affected by the update operation.
|
||||||
func (uu *UserUpdate) Save(ctx context.Context) (int, error) {
|
func (_u *UserUpdate) Save(ctx context.Context) (int, error) {
|
||||||
var (
|
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
|
||||||
err error
|
|
||||||
affected int
|
|
||||||
)
|
|
||||||
if len(uu.hooks) == 0 {
|
|
||||||
if err = uu.check(); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
affected, err = uu.sqlSave(ctx)
|
|
||||||
} else {
|
|
||||||
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
|
|
||||||
mutation, ok := m.(*UserMutation)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("unexpected mutation type %T", m)
|
|
||||||
}
|
|
||||||
if err = uu.check(); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
uu.mutation = mutation
|
|
||||||
affected, err = uu.sqlSave(ctx)
|
|
||||||
mutation.done = true
|
|
||||||
return affected, err
|
|
||||||
})
|
|
||||||
for i := len(uu.hooks) - 1; i >= 0; i-- {
|
|
||||||
if uu.hooks[i] == nil {
|
|
||||||
return 0, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)")
|
|
||||||
}
|
|
||||||
mut = uu.hooks[i](mut)
|
|
||||||
}
|
|
||||||
if _, err := mut.Mutate(ctx, uu.mutation); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return affected, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveX is like Save, but panics if an error occurs.
|
// SaveX is like Save, but panics if an error occurs.
|
||||||
func (uu *UserUpdate) SaveX(ctx context.Context) int {
|
func (_u *UserUpdate) SaveX(ctx context.Context) int {
|
||||||
affected, err := uu.Save(ctx)
|
affected, err := _u.Save(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
@ -134,78 +154,66 @@ func (uu *UserUpdate) SaveX(ctx context.Context) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exec executes the query.
|
// Exec executes the query.
|
||||||
func (uu *UserUpdate) Exec(ctx context.Context) error {
|
func (_u *UserUpdate) Exec(ctx context.Context) error {
|
||||||
_, err := uu.Save(ctx)
|
_, err := _u.Save(ctx)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecX is like Exec, but panics if an error occurs.
|
// ExecX is like Exec, but panics if an error occurs.
|
||||||
func (uu *UserUpdate) ExecX(ctx context.Context) {
|
func (_u *UserUpdate) ExecX(ctx context.Context) {
|
||||||
if err := uu.Exec(ctx); err != nil {
|
if err := _u.Exec(ctx); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check runs all checks and user-defined validators on the builder.
|
// check runs all checks and user-defined validators on the builder.
|
||||||
func (uu *UserUpdate) check() error {
|
func (_u *UserUpdate) check() error {
|
||||||
if v, ok := uu.mutation.Name(); ok {
|
if v, ok := _u.mutation.Name(); ok {
|
||||||
if err := user.NameValidator(v); err != nil {
|
if err := user.NameValidator(v); err != nil {
|
||||||
return &ValidationError{Name: "name", err: fmt.Errorf("ent: validator failed for field \"name\": %w", err)}
|
return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "User.name": %w`, err)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if v, ok := uu.mutation.Email(); ok {
|
if v, ok := _u.mutation.Email(); ok {
|
||||||
if err := user.EmailValidator(v); err != nil {
|
if err := user.EmailValidator(v); err != nil {
|
||||||
return &ValidationError{Name: "email", err: fmt.Errorf("ent: validator failed for field \"email\": %w", err)}
|
return &ValidationError{Name: "email", err: fmt.Errorf(`ent: validator failed for field "User.email": %w`, err)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if v, ok := uu.mutation.Password(); ok {
|
if v, ok := _u.mutation.Password(); ok {
|
||||||
if err := user.PasswordValidator(v); err != nil {
|
if err := user.PasswordValidator(v); err != nil {
|
||||||
return &ValidationError{Name: "password", err: fmt.Errorf("ent: validator failed for field \"password\": %w", err)}
|
return &ValidationError{Name: "password", err: fmt.Errorf(`ent: validator failed for field "User.password": %w`, err)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uu *UserUpdate) sqlSave(ctx context.Context) (n int, err error) {
|
func (_u *UserUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
||||||
_spec := &sqlgraph.UpdateSpec{
|
if err := _u.check(); err != nil {
|
||||||
Node: &sqlgraph.NodeSpec{
|
return _node, err
|
||||||
Table: user.Table,
|
|
||||||
Columns: user.Columns,
|
|
||||||
ID: &sqlgraph.FieldSpec{
|
|
||||||
Type: field.TypeInt,
|
|
||||||
Column: user.FieldID,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
if ps := uu.mutation.predicates; len(ps) > 0 {
|
_spec := sqlgraph.NewUpdateSpec(user.Table, user.Columns, sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt))
|
||||||
|
if ps := _u.mutation.predicates; len(ps) > 0 {
|
||||||
_spec.Predicate = func(selector *sql.Selector) {
|
_spec.Predicate = func(selector *sql.Selector) {
|
||||||
for i := range ps {
|
for i := range ps {
|
||||||
ps[i](selector)
|
ps[i](selector)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if value, ok := uu.mutation.Name(); ok {
|
if value, ok := _u.mutation.Name(); ok {
|
||||||
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
|
_spec.SetField(user.FieldName, field.TypeString, value)
|
||||||
Type: field.TypeString,
|
|
||||||
Value: value,
|
|
||||||
Column: user.FieldName,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
if value, ok := uu.mutation.Email(); ok {
|
if value, ok := _u.mutation.Email(); ok {
|
||||||
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
|
_spec.SetField(user.FieldEmail, field.TypeString, value)
|
||||||
Type: field.TypeString,
|
|
||||||
Value: value,
|
|
||||||
Column: user.FieldEmail,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
if value, ok := uu.mutation.Password(); ok {
|
if value, ok := _u.mutation.Password(); ok {
|
||||||
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
|
_spec.SetField(user.FieldPassword, field.TypeString, value)
|
||||||
Type: field.TypeString,
|
|
||||||
Value: value,
|
|
||||||
Column: user.FieldPassword,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
if uu.mutation.OwnerCleared() {
|
if value, ok := _u.mutation.Verified(); ok {
|
||||||
|
_spec.SetField(user.FieldVerified, field.TypeBool, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.Admin(); ok {
|
||||||
|
_spec.SetField(user.FieldAdmin, field.TypeBool, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.OwnerCleared() {
|
||||||
edge := &sqlgraph.EdgeSpec{
|
edge := &sqlgraph.EdgeSpec{
|
||||||
Rel: sqlgraph.O2M,
|
Rel: sqlgraph.O2M,
|
||||||
Inverse: true,
|
Inverse: true,
|
||||||
|
|
@ -213,15 +221,12 @@ func (uu *UserUpdate) sqlSave(ctx context.Context) (n int, err error) {
|
||||||
Columns: []string{user.OwnerColumn},
|
Columns: []string{user.OwnerColumn},
|
||||||
Bidi: false,
|
Bidi: false,
|
||||||
Target: &sqlgraph.EdgeTarget{
|
Target: &sqlgraph.EdgeTarget{
|
||||||
IDSpec: &sqlgraph.FieldSpec{
|
IDSpec: sqlgraph.NewFieldSpec(passwordtoken.FieldID, field.TypeInt),
|
||||||
Type: field.TypeInt,
|
|
||||||
Column: passwordtoken.FieldID,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||||
}
|
}
|
||||||
if nodes := uu.mutation.RemovedOwnerIDs(); len(nodes) > 0 && !uu.mutation.OwnerCleared() {
|
if nodes := _u.mutation.RemovedOwnerIDs(); len(nodes) > 0 && !_u.mutation.OwnerCleared() {
|
||||||
edge := &sqlgraph.EdgeSpec{
|
edge := &sqlgraph.EdgeSpec{
|
||||||
Rel: sqlgraph.O2M,
|
Rel: sqlgraph.O2M,
|
||||||
Inverse: true,
|
Inverse: true,
|
||||||
|
|
@ -229,10 +234,7 @@ func (uu *UserUpdate) sqlSave(ctx context.Context) (n int, err error) {
|
||||||
Columns: []string{user.OwnerColumn},
|
Columns: []string{user.OwnerColumn},
|
||||||
Bidi: false,
|
Bidi: false,
|
||||||
Target: &sqlgraph.EdgeTarget{
|
Target: &sqlgraph.EdgeTarget{
|
||||||
IDSpec: &sqlgraph.FieldSpec{
|
IDSpec: sqlgraph.NewFieldSpec(passwordtoken.FieldID, field.TypeInt),
|
||||||
Type: field.TypeInt,
|
|
||||||
Column: passwordtoken.FieldID,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, k := range nodes {
|
for _, k := range nodes {
|
||||||
|
|
@ -240,7 +242,7 @@ func (uu *UserUpdate) sqlSave(ctx context.Context) (n int, err error) {
|
||||||
}
|
}
|
||||||
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||||
}
|
}
|
||||||
if nodes := uu.mutation.OwnerIDs(); len(nodes) > 0 {
|
if nodes := _u.mutation.OwnerIDs(); len(nodes) > 0 {
|
||||||
edge := &sqlgraph.EdgeSpec{
|
edge := &sqlgraph.EdgeSpec{
|
||||||
Rel: sqlgraph.O2M,
|
Rel: sqlgraph.O2M,
|
||||||
Inverse: true,
|
Inverse: true,
|
||||||
|
|
@ -248,10 +250,7 @@ func (uu *UserUpdate) sqlSave(ctx context.Context) (n int, err error) {
|
||||||
Columns: []string{user.OwnerColumn},
|
Columns: []string{user.OwnerColumn},
|
||||||
Bidi: false,
|
Bidi: false,
|
||||||
Target: &sqlgraph.EdgeTarget{
|
Target: &sqlgraph.EdgeTarget{
|
||||||
IDSpec: &sqlgraph.FieldSpec{
|
IDSpec: sqlgraph.NewFieldSpec(passwordtoken.FieldID, field.TypeInt),
|
||||||
Type: field.TypeInt,
|
|
||||||
Column: passwordtoken.FieldID,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, k := range nodes {
|
for _, k := range nodes {
|
||||||
|
|
@ -259,15 +258,16 @@ func (uu *UserUpdate) sqlSave(ctx context.Context) (n int, err error) {
|
||||||
}
|
}
|
||||||
_spec.Edges.Add = append(_spec.Edges.Add, edge)
|
_spec.Edges.Add = append(_spec.Edges.Add, edge)
|
||||||
}
|
}
|
||||||
if n, err = sqlgraph.UpdateNodes(ctx, uu.driver, _spec); err != nil {
|
if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {
|
||||||
if _, ok := err.(*sqlgraph.NotFoundError); ok {
|
if _, ok := err.(*sqlgraph.NotFoundError); ok {
|
||||||
err = &NotFoundError{user.Label}
|
err = &NotFoundError{user.Label}
|
||||||
} else if sqlgraph.IsConstraintError(err) {
|
} else if sqlgraph.IsConstraintError(err) {
|
||||||
err = &ConstraintError{err.Error(), err}
|
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||||
}
|
}
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return n, nil
|
_u.mutation.done = true
|
||||||
|
return _node, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserUpdateOne is the builder for updating a single User entity.
|
// UserUpdateOne is the builder for updating a single User entity.
|
||||||
|
|
@ -279,112 +279,137 @@ type UserUpdateOne struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetName sets the "name" field.
|
// SetName sets the "name" field.
|
||||||
func (uuo *UserUpdateOne) SetName(s string) *UserUpdateOne {
|
func (_u *UserUpdateOne) SetName(v string) *UserUpdateOne {
|
||||||
uuo.mutation.SetName(s)
|
_u.mutation.SetName(v)
|
||||||
return uuo
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableName sets the "name" field if the given value is not nil.
|
||||||
|
func (_u *UserUpdateOne) SetNillableName(v *string) *UserUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetName(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetEmail sets the "email" field.
|
// SetEmail sets the "email" field.
|
||||||
func (uuo *UserUpdateOne) SetEmail(s string) *UserUpdateOne {
|
func (_u *UserUpdateOne) SetEmail(v string) *UserUpdateOne {
|
||||||
uuo.mutation.SetEmail(s)
|
_u.mutation.SetEmail(v)
|
||||||
return uuo
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableEmail sets the "email" field if the given value is not nil.
|
||||||
|
func (_u *UserUpdateOne) SetNillableEmail(v *string) *UserUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetEmail(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPassword sets the "password" field.
|
// SetPassword sets the "password" field.
|
||||||
func (uuo *UserUpdateOne) SetPassword(s string) *UserUpdateOne {
|
func (_u *UserUpdateOne) SetPassword(v string) *UserUpdateOne {
|
||||||
uuo.mutation.SetPassword(s)
|
_u.mutation.SetPassword(v)
|
||||||
return uuo
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillablePassword sets the "password" field if the given value is not nil.
|
||||||
|
func (_u *UserUpdateOne) SetNillablePassword(v *string) *UserUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetPassword(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetVerified sets the "verified" field.
|
||||||
|
func (_u *UserUpdateOne) SetVerified(v bool) *UserUpdateOne {
|
||||||
|
_u.mutation.SetVerified(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableVerified sets the "verified" field if the given value is not nil.
|
||||||
|
func (_u *UserUpdateOne) SetNillableVerified(v *bool) *UserUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetVerified(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAdmin sets the "admin" field.
|
||||||
|
func (_u *UserUpdateOne) SetAdmin(v bool) *UserUpdateOne {
|
||||||
|
_u.mutation.SetAdmin(v)
|
||||||
|
return _u
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNillableAdmin sets the "admin" field if the given value is not nil.
|
||||||
|
func (_u *UserUpdateOne) SetNillableAdmin(v *bool) *UserUpdateOne {
|
||||||
|
if v != nil {
|
||||||
|
_u.SetAdmin(*v)
|
||||||
|
}
|
||||||
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddOwnerIDs adds the "owner" edge to the PasswordToken entity by IDs.
|
// AddOwnerIDs adds the "owner" edge to the PasswordToken entity by IDs.
|
||||||
func (uuo *UserUpdateOne) AddOwnerIDs(ids ...int) *UserUpdateOne {
|
func (_u *UserUpdateOne) AddOwnerIDs(ids ...int) *UserUpdateOne {
|
||||||
uuo.mutation.AddOwnerIDs(ids...)
|
_u.mutation.AddOwnerIDs(ids...)
|
||||||
return uuo
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddOwner adds the "owner" edges to the PasswordToken entity.
|
// AddOwner adds the "owner" edges to the PasswordToken entity.
|
||||||
func (uuo *UserUpdateOne) AddOwner(p ...*PasswordToken) *UserUpdateOne {
|
func (_u *UserUpdateOne) AddOwner(v ...*PasswordToken) *UserUpdateOne {
|
||||||
ids := make([]int, len(p))
|
ids := make([]int, len(v))
|
||||||
for i := range p {
|
for i := range v {
|
||||||
ids[i] = p[i].ID
|
ids[i] = v[i].ID
|
||||||
}
|
}
|
||||||
return uuo.AddOwnerIDs(ids...)
|
return _u.AddOwnerIDs(ids...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mutation returns the UserMutation object of the builder.
|
// Mutation returns the UserMutation object of the builder.
|
||||||
func (uuo *UserUpdateOne) Mutation() *UserMutation {
|
func (_u *UserUpdateOne) Mutation() *UserMutation {
|
||||||
return uuo.mutation
|
return _u.mutation
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClearOwner clears all "owner" edges to the PasswordToken entity.
|
// ClearOwner clears all "owner" edges to the PasswordToken entity.
|
||||||
func (uuo *UserUpdateOne) ClearOwner() *UserUpdateOne {
|
func (_u *UserUpdateOne) ClearOwner() *UserUpdateOne {
|
||||||
uuo.mutation.ClearOwner()
|
_u.mutation.ClearOwner()
|
||||||
return uuo
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveOwnerIDs removes the "owner" edge to PasswordToken entities by IDs.
|
// RemoveOwnerIDs removes the "owner" edge to PasswordToken entities by IDs.
|
||||||
func (uuo *UserUpdateOne) RemoveOwnerIDs(ids ...int) *UserUpdateOne {
|
func (_u *UserUpdateOne) RemoveOwnerIDs(ids ...int) *UserUpdateOne {
|
||||||
uuo.mutation.RemoveOwnerIDs(ids...)
|
_u.mutation.RemoveOwnerIDs(ids...)
|
||||||
return uuo
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveOwner removes "owner" edges to PasswordToken entities.
|
// RemoveOwner removes "owner" edges to PasswordToken entities.
|
||||||
func (uuo *UserUpdateOne) RemoveOwner(p ...*PasswordToken) *UserUpdateOne {
|
func (_u *UserUpdateOne) RemoveOwner(v ...*PasswordToken) *UserUpdateOne {
|
||||||
ids := make([]int, len(p))
|
ids := make([]int, len(v))
|
||||||
for i := range p {
|
for i := range v {
|
||||||
ids[i] = p[i].ID
|
ids[i] = v[i].ID
|
||||||
}
|
}
|
||||||
return uuo.RemoveOwnerIDs(ids...)
|
return _u.RemoveOwnerIDs(ids...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Where appends a list predicates to the UserUpdate builder.
|
||||||
|
func (_u *UserUpdateOne) Where(ps ...predicate.User) *UserUpdateOne {
|
||||||
|
_u.mutation.Where(ps...)
|
||||||
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select allows selecting one or more fields (columns) of the returned entity.
|
// Select allows selecting one or more fields (columns) of the returned entity.
|
||||||
// The default is selecting all fields defined in the entity schema.
|
// The default is selecting all fields defined in the entity schema.
|
||||||
func (uuo *UserUpdateOne) Select(field string, fields ...string) *UserUpdateOne {
|
func (_u *UserUpdateOne) Select(field string, fields ...string) *UserUpdateOne {
|
||||||
uuo.fields = append([]string{field}, fields...)
|
_u.fields = append([]string{field}, fields...)
|
||||||
return uuo
|
return _u
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save executes the query and returns the updated User entity.
|
// Save executes the query and returns the updated User entity.
|
||||||
func (uuo *UserUpdateOne) Save(ctx context.Context) (*User, error) {
|
func (_u *UserUpdateOne) Save(ctx context.Context) (*User, error) {
|
||||||
var (
|
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
|
||||||
err error
|
|
||||||
node *User
|
|
||||||
)
|
|
||||||
if len(uuo.hooks) == 0 {
|
|
||||||
if err = uuo.check(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
node, err = uuo.sqlSave(ctx)
|
|
||||||
} else {
|
|
||||||
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
|
|
||||||
mutation, ok := m.(*UserMutation)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("unexpected mutation type %T", m)
|
|
||||||
}
|
|
||||||
if err = uuo.check(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
uuo.mutation = mutation
|
|
||||||
node, err = uuo.sqlSave(ctx)
|
|
||||||
mutation.done = true
|
|
||||||
return node, err
|
|
||||||
})
|
|
||||||
for i := len(uuo.hooks) - 1; i >= 0; i-- {
|
|
||||||
if uuo.hooks[i] == nil {
|
|
||||||
return nil, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)")
|
|
||||||
}
|
|
||||||
mut = uuo.hooks[i](mut)
|
|
||||||
}
|
|
||||||
if _, err := mut.Mutate(ctx, uuo.mutation); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return node, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveX is like Save, but panics if an error occurs.
|
// SaveX is like Save, but panics if an error occurs.
|
||||||
func (uuo *UserUpdateOne) SaveX(ctx context.Context) *User {
|
func (_u *UserUpdateOne) SaveX(ctx context.Context) *User {
|
||||||
node, err := uuo.Save(ctx)
|
node, err := _u.Save(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
@ -392,55 +417,49 @@ func (uuo *UserUpdateOne) SaveX(ctx context.Context) *User {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exec executes the query on the entity.
|
// Exec executes the query on the entity.
|
||||||
func (uuo *UserUpdateOne) Exec(ctx context.Context) error {
|
func (_u *UserUpdateOne) Exec(ctx context.Context) error {
|
||||||
_, err := uuo.Save(ctx)
|
_, err := _u.Save(ctx)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecX is like Exec, but panics if an error occurs.
|
// ExecX is like Exec, but panics if an error occurs.
|
||||||
func (uuo *UserUpdateOne) ExecX(ctx context.Context) {
|
func (_u *UserUpdateOne) ExecX(ctx context.Context) {
|
||||||
if err := uuo.Exec(ctx); err != nil {
|
if err := _u.Exec(ctx); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check runs all checks and user-defined validators on the builder.
|
// check runs all checks and user-defined validators on the builder.
|
||||||
func (uuo *UserUpdateOne) check() error {
|
func (_u *UserUpdateOne) check() error {
|
||||||
if v, ok := uuo.mutation.Name(); ok {
|
if v, ok := _u.mutation.Name(); ok {
|
||||||
if err := user.NameValidator(v); err != nil {
|
if err := user.NameValidator(v); err != nil {
|
||||||
return &ValidationError{Name: "name", err: fmt.Errorf("ent: validator failed for field \"name\": %w", err)}
|
return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "User.name": %w`, err)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if v, ok := uuo.mutation.Email(); ok {
|
if v, ok := _u.mutation.Email(); ok {
|
||||||
if err := user.EmailValidator(v); err != nil {
|
if err := user.EmailValidator(v); err != nil {
|
||||||
return &ValidationError{Name: "email", err: fmt.Errorf("ent: validator failed for field \"email\": %w", err)}
|
return &ValidationError{Name: "email", err: fmt.Errorf(`ent: validator failed for field "User.email": %w`, err)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if v, ok := uuo.mutation.Password(); ok {
|
if v, ok := _u.mutation.Password(); ok {
|
||||||
if err := user.PasswordValidator(v); err != nil {
|
if err := user.PasswordValidator(v); err != nil {
|
||||||
return &ValidationError{Name: "password", err: fmt.Errorf("ent: validator failed for field \"password\": %w", err)}
|
return &ValidationError{Name: "password", err: fmt.Errorf(`ent: validator failed for field "User.password": %w`, err)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uuo *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) {
|
func (_u *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) {
|
||||||
_spec := &sqlgraph.UpdateSpec{
|
if err := _u.check(); err != nil {
|
||||||
Node: &sqlgraph.NodeSpec{
|
return _node, err
|
||||||
Table: user.Table,
|
|
||||||
Columns: user.Columns,
|
|
||||||
ID: &sqlgraph.FieldSpec{
|
|
||||||
Type: field.TypeInt,
|
|
||||||
Column: user.FieldID,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
id, ok := uuo.mutation.ID()
|
_spec := sqlgraph.NewUpdateSpec(user.Table, user.Columns, sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt))
|
||||||
|
id, ok := _u.mutation.ID()
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, &ValidationError{Name: "ID", err: fmt.Errorf("missing User.ID for update")}
|
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "User.id" for update`)}
|
||||||
}
|
}
|
||||||
_spec.Node.ID.Value = id
|
_spec.Node.ID.Value = id
|
||||||
if fields := uuo.fields; len(fields) > 0 {
|
if fields := _u.fields; len(fields) > 0 {
|
||||||
_spec.Node.Columns = make([]string, 0, len(fields))
|
_spec.Node.Columns = make([]string, 0, len(fields))
|
||||||
_spec.Node.Columns = append(_spec.Node.Columns, user.FieldID)
|
_spec.Node.Columns = append(_spec.Node.Columns, user.FieldID)
|
||||||
for _, f := range fields {
|
for _, f := range fields {
|
||||||
|
|
@ -452,35 +471,29 @@ func (uuo *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ps := uuo.mutation.predicates; len(ps) > 0 {
|
if ps := _u.mutation.predicates; len(ps) > 0 {
|
||||||
_spec.Predicate = func(selector *sql.Selector) {
|
_spec.Predicate = func(selector *sql.Selector) {
|
||||||
for i := range ps {
|
for i := range ps {
|
||||||
ps[i](selector)
|
ps[i](selector)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if value, ok := uuo.mutation.Name(); ok {
|
if value, ok := _u.mutation.Name(); ok {
|
||||||
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
|
_spec.SetField(user.FieldName, field.TypeString, value)
|
||||||
Type: field.TypeString,
|
|
||||||
Value: value,
|
|
||||||
Column: user.FieldName,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
if value, ok := uuo.mutation.Email(); ok {
|
if value, ok := _u.mutation.Email(); ok {
|
||||||
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
|
_spec.SetField(user.FieldEmail, field.TypeString, value)
|
||||||
Type: field.TypeString,
|
|
||||||
Value: value,
|
|
||||||
Column: user.FieldEmail,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
if value, ok := uuo.mutation.Password(); ok {
|
if value, ok := _u.mutation.Password(); ok {
|
||||||
_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
|
_spec.SetField(user.FieldPassword, field.TypeString, value)
|
||||||
Type: field.TypeString,
|
|
||||||
Value: value,
|
|
||||||
Column: user.FieldPassword,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
if uuo.mutation.OwnerCleared() {
|
if value, ok := _u.mutation.Verified(); ok {
|
||||||
|
_spec.SetField(user.FieldVerified, field.TypeBool, value)
|
||||||
|
}
|
||||||
|
if value, ok := _u.mutation.Admin(); ok {
|
||||||
|
_spec.SetField(user.FieldAdmin, field.TypeBool, value)
|
||||||
|
}
|
||||||
|
if _u.mutation.OwnerCleared() {
|
||||||
edge := &sqlgraph.EdgeSpec{
|
edge := &sqlgraph.EdgeSpec{
|
||||||
Rel: sqlgraph.O2M,
|
Rel: sqlgraph.O2M,
|
||||||
Inverse: true,
|
Inverse: true,
|
||||||
|
|
@ -488,15 +501,12 @@ func (uuo *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error)
|
||||||
Columns: []string{user.OwnerColumn},
|
Columns: []string{user.OwnerColumn},
|
||||||
Bidi: false,
|
Bidi: false,
|
||||||
Target: &sqlgraph.EdgeTarget{
|
Target: &sqlgraph.EdgeTarget{
|
||||||
IDSpec: &sqlgraph.FieldSpec{
|
IDSpec: sqlgraph.NewFieldSpec(passwordtoken.FieldID, field.TypeInt),
|
||||||
Type: field.TypeInt,
|
|
||||||
Column: passwordtoken.FieldID,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||||
}
|
}
|
||||||
if nodes := uuo.mutation.RemovedOwnerIDs(); len(nodes) > 0 && !uuo.mutation.OwnerCleared() {
|
if nodes := _u.mutation.RemovedOwnerIDs(); len(nodes) > 0 && !_u.mutation.OwnerCleared() {
|
||||||
edge := &sqlgraph.EdgeSpec{
|
edge := &sqlgraph.EdgeSpec{
|
||||||
Rel: sqlgraph.O2M,
|
Rel: sqlgraph.O2M,
|
||||||
Inverse: true,
|
Inverse: true,
|
||||||
|
|
@ -504,10 +514,7 @@ func (uuo *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error)
|
||||||
Columns: []string{user.OwnerColumn},
|
Columns: []string{user.OwnerColumn},
|
||||||
Bidi: false,
|
Bidi: false,
|
||||||
Target: &sqlgraph.EdgeTarget{
|
Target: &sqlgraph.EdgeTarget{
|
||||||
IDSpec: &sqlgraph.FieldSpec{
|
IDSpec: sqlgraph.NewFieldSpec(passwordtoken.FieldID, field.TypeInt),
|
||||||
Type: field.TypeInt,
|
|
||||||
Column: passwordtoken.FieldID,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, k := range nodes {
|
for _, k := range nodes {
|
||||||
|
|
@ -515,7 +522,7 @@ func (uuo *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error)
|
||||||
}
|
}
|
||||||
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||||
}
|
}
|
||||||
if nodes := uuo.mutation.OwnerIDs(); len(nodes) > 0 {
|
if nodes := _u.mutation.OwnerIDs(); len(nodes) > 0 {
|
||||||
edge := &sqlgraph.EdgeSpec{
|
edge := &sqlgraph.EdgeSpec{
|
||||||
Rel: sqlgraph.O2M,
|
Rel: sqlgraph.O2M,
|
||||||
Inverse: true,
|
Inverse: true,
|
||||||
|
|
@ -523,10 +530,7 @@ func (uuo *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error)
|
||||||
Columns: []string{user.OwnerColumn},
|
Columns: []string{user.OwnerColumn},
|
||||||
Bidi: false,
|
Bidi: false,
|
||||||
Target: &sqlgraph.EdgeTarget{
|
Target: &sqlgraph.EdgeTarget{
|
||||||
IDSpec: &sqlgraph.FieldSpec{
|
IDSpec: sqlgraph.NewFieldSpec(passwordtoken.FieldID, field.TypeInt),
|
||||||
Type: field.TypeInt,
|
|
||||||
Column: passwordtoken.FieldID,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, k := range nodes {
|
for _, k := range nodes {
|
||||||
|
|
@ -534,16 +538,17 @@ func (uuo *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error)
|
||||||
}
|
}
|
||||||
_spec.Edges.Add = append(_spec.Edges.Add, edge)
|
_spec.Edges.Add = append(_spec.Edges.Add, edge)
|
||||||
}
|
}
|
||||||
_node = &User{config: uuo.config}
|
_node = &User{config: _u.config}
|
||||||
_spec.Assign = _node.assignValues
|
_spec.Assign = _node.assignValues
|
||||||
_spec.ScanValues = _node.scanValues
|
_spec.ScanValues = _node.scanValues
|
||||||
if err = sqlgraph.UpdateNode(ctx, uuo.driver, _spec); err != nil {
|
if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil {
|
||||||
if _, ok := err.(*sqlgraph.NotFoundError); ok {
|
if _, ok := err.(*sqlgraph.NotFoundError); ok {
|
||||||
err = &NotFoundError{user.Label}
|
err = &NotFoundError{user.Label}
|
||||||
} else if sqlgraph.IsConstraintError(err) {
|
} else if sqlgraph.IsConstraintError(err) {
|
||||||
err = &ConstraintError{err.Error(), err}
|
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
_u.mutation.done = true
|
||||||
return _node, nil
|
return _node, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
package funcmap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"html/template"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"goweb/config"
|
|
||||||
|
|
||||||
"github.com/Masterminds/sprig"
|
|
||||||
"github.com/labstack/gommon/random"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// CacheBuster stores a random string used as a cache buster for static files.
|
|
||||||
CacheBuster = random.String(10)
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetFuncMap provides a template function map
|
|
||||||
func GetFuncMap() template.FuncMap {
|
|
||||||
// See http://masterminds.github.io/sprig/ for available funcs
|
|
||||||
funcMap := sprig.FuncMap()
|
|
||||||
|
|
||||||
// Provide a list of custom functions
|
|
||||||
// Expand this as you add more functions to this package
|
|
||||||
// Avoid using a name already in use by sprig
|
|
||||||
f := template.FuncMap{
|
|
||||||
"hasField": HasField,
|
|
||||||
"file": File,
|
|
||||||
"link": Link,
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range f {
|
|
||||||
funcMap[k] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
return funcMap
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasField checks if an interface contains a given field
|
|
||||||
func HasField(v interface{}, name string) bool {
|
|
||||||
rv := reflect.ValueOf(v)
|
|
||||||
if rv.Kind() == reflect.Ptr {
|
|
||||||
rv = rv.Elem()
|
|
||||||
}
|
|
||||||
if rv.Kind() != reflect.Struct {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return rv.FieldByName(name).IsValid()
|
|
||||||
}
|
|
||||||
|
|
||||||
// File appends a cache buster to a given filepath so it can remain cached until the app is restarted
|
|
||||||
func File(filepath string) string {
|
|
||||||
return fmt.Sprintf("/%s/%s?v=%s", config.StaticPrefix, filepath, CacheBuster)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Link outputs HTML for a link element, providing the ability to dynamically set the active class
|
|
||||||
func Link(url, text, currentPath string, classes ...string) template.HTML {
|
|
||||||
if currentPath == url {
|
|
||||||
classes = append(classes, "is-active")
|
|
||||||
}
|
|
||||||
|
|
||||||
html := fmt.Sprintf(`<a class="%s" href="%s">%s</a>`, strings.Join(classes, " "), url, text)
|
|
||||||
return template.HTML(html)
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
package funcmap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"goweb/config"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestHasField(t *testing.T) {
|
|
||||||
type example struct {
|
|
||||||
name string
|
|
||||||
}
|
|
||||||
var e example
|
|
||||||
assert.True(t, HasField(e, "name"))
|
|
||||||
assert.False(t, HasField(e, "abcd"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLink(t *testing.T) {
|
|
||||||
link := string(Link("/abc", "Text", "/abc"))
|
|
||||||
expected := `<a class="is-active" href="/abc">Text</a>`
|
|
||||||
assert.Equal(t, expected, link)
|
|
||||||
|
|
||||||
link = string(Link("/abc", "Text", "/abc", "first", "second"))
|
|
||||||
expected = `<a class="first second is-active" href="/abc">Text</a>`
|
|
||||||
assert.Equal(t, expected, link)
|
|
||||||
|
|
||||||
link = string(Link("/abc", "Text", "/def"))
|
|
||||||
expected = `<a class="" href="/abc">Text</a>`
|
|
||||||
assert.Equal(t, expected, link)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetFuncMap(t *testing.T) {
|
|
||||||
file := File("test.png")
|
|
||||||
expected := fmt.Sprintf("/%s/test.png?v=%s", config.StaticPrefix, CacheBuster)
|
|
||||||
assert.Equal(t, expected, file)
|
|
||||||
}
|
|
||||||
130
go.mod
130
go.mod
|
|
@ -1,78 +1,68 @@
|
||||||
module goweb
|
module github.com/camzawacki/personal-site
|
||||||
|
|
||||||
go 1.17
|
go 1.24.6
|
||||||
|
|
||||||
require (
|
require (
|
||||||
entgo.io/ent v0.9.1
|
entgo.io/ent v0.14.5
|
||||||
github.com/Masterminds/sprig v2.22.0+incompatible
|
github.com/PuerkitoBio/goquery v1.10.3
|
||||||
github.com/PuerkitoBio/goquery v1.8.0
|
github.com/go-playground/validator/v10 v10.29.0
|
||||||
github.com/eko/gocache/v2 v2.1.0
|
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||||
github.com/go-playground/assert/v2 v2.0.1
|
github.com/gorilla/context v1.1.2
|
||||||
github.com/go-playground/validator/v10 v10.9.0
|
github.com/gorilla/sessions v1.4.0
|
||||||
github.com/go-redis/redis/v8 v8.11.4
|
github.com/labstack/echo/v4 v4.14.0
|
||||||
github.com/gorilla/sessions v1.2.1
|
github.com/mattn/go-sqlite3 v1.14.32
|
||||||
github.com/jackc/pgx/v4 v4.14.1
|
github.com/maypok86/otter v1.2.4
|
||||||
github.com/joeshaw/envdecode v0.0.0-20200121155833-099f1fc765bd
|
github.com/mikestefanello/backlite v0.6.0
|
||||||
github.com/labstack/echo-contrib v0.11.0
|
github.com/spf13/afero v1.15.0
|
||||||
github.com/labstack/echo/v4 v4.6.1
|
github.com/spf13/viper v1.21.0
|
||||||
github.com/labstack/gommon v0.3.1
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/stretchr/testify v1.7.0
|
golang.org/x/crypto v0.46.0
|
||||||
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871
|
maragu.dev/gomponents v1.2.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
ariga.io/atlas v0.38.0 // indirect
|
||||||
github.com/Masterminds/semver v1.5.0 // indirect
|
github.com/agext/levenshtein v1.2.3 // indirect
|
||||||
github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 // indirect
|
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||||
github.com/andybalholm/cascadia v1.3.1 // indirect
|
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/bmatcuk/doublestar v1.3.4 // indirect
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.1.0 // indirect
|
github.com/dolthub/maphash v0.1.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/gammazero/deque v1.2.0 // indirect
|
||||||
github.com/go-playground/locales v0.14.0 // indirect
|
github.com/go-openapi/inflect v0.21.5 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/golang/protobuf v1.5.2 // indirect
|
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||||
github.com/google/uuid v1.3.0 // indirect
|
github.com/google/go-cmp v0.7.0 // indirect
|
||||||
github.com/gorilla/context v1.1.1 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/gorilla/securecookie v1.1.1 // indirect
|
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||||
github.com/huandu/xstrings v1.3.2 // indirect
|
github.com/hashicorp/hcl/v2 v2.24.0 // indirect
|
||||||
github.com/imdario/mergo v0.3.12 // indirect
|
github.com/labstack/gommon v0.4.2 // indirect
|
||||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/jackc/pgconn v1.10.1 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/jackc/pgio v1.0.0 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||||
github.com/jackc/pgproto3/v2 v2.2.0 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/jackc/pgtype v1.9.1 // indirect
|
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3 // indirect
|
github.com/sagikazarmark/locafero v0.12.0 // indirect
|
||||||
github.com/leodido/go-urn v1.2.1 // indirect
|
github.com/spf13/cast v1.10.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
github.com/spf13/pflag v1.0.10 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
|
||||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
|
||||||
github.com/pegasus-kv/thrift v0.13.0 // indirect
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
|
||||||
github.com/prometheus/client_golang v1.10.0 // indirect
|
|
||||||
github.com/prometheus/client_model v0.2.0 // indirect
|
|
||||||
github.com/prometheus/common v0.25.0 // indirect
|
|
||||||
github.com/prometheus/procfs v0.6.0 // indirect
|
|
||||||
github.com/sirupsen/logrus v1.6.0 // indirect
|
|
||||||
github.com/spf13/cast v1.3.1 // indirect
|
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/valyala/fasttemplate v1.2.1 // indirect
|
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||||
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
|
github.com/zclconf/go-cty v1.17.0 // indirect
|
||||||
golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9 // indirect
|
github.com/zclconf/go-cty-yaml v1.1.0 // indirect
|
||||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881 // indirect
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
golang.org/x/text v0.3.7 // indirect
|
golang.org/x/mod v0.31.0 // indirect
|
||||||
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect
|
golang.org/x/net v0.48.0 // indirect
|
||||||
google.golang.org/appengine v1.6.1 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
google.golang.org/protobuf v1.26.0 // indirect
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
golang.org/x/text v0.32.0 // indirect
|
||||||
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 // indirect
|
golang.org/x/time v0.14.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
golang.org/x/tools v0.40.0 // indirect
|
||||||
k8s.io/apimachinery v0.0.0-20191123233150-4c4803ed55e3 // indirect
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
||||||
82
htmx/htmx.go
82
htmx/htmx.go
|
|
@ -1,82 +0,0 @@
|
||||||
package htmx
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Headers (https://htmx.org/docs/#requests)
|
|
||||||
const (
|
|
||||||
HeaderRequest = "HX-Request"
|
|
||||||
HeaderBoosted = "HX-Boosted"
|
|
||||||
HeaderTrigger = "HX-Trigger"
|
|
||||||
HeaderTriggerName = "HX-Trigger-Name"
|
|
||||||
HeaderTriggerAfterSwap = "HX-Trigger-After-Swap"
|
|
||||||
HeaderTriggerAfterSettle = "HX-Trigger-After-Settle"
|
|
||||||
HeaderTarget = "HX-Target"
|
|
||||||
HeaderPrompt = "HX-Prompt"
|
|
||||||
HeaderPush = "HX-Push"
|
|
||||||
HeaderRedirect = "HX-Redirect"
|
|
||||||
HeaderRefresh = "HX-Refresh"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
// Request contains data that HTMX provides during requests
|
|
||||||
Request struct {
|
|
||||||
Enabled bool
|
|
||||||
Boosted bool
|
|
||||||
Trigger string
|
|
||||||
TriggerName string
|
|
||||||
Target string
|
|
||||||
Prompt string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Response contain data that the server can communicate back to HTMX
|
|
||||||
Response struct {
|
|
||||||
Push string
|
|
||||||
Redirect string
|
|
||||||
Refresh bool
|
|
||||||
Trigger string
|
|
||||||
TriggerAfterSwap string
|
|
||||||
TriggerAfterSettle string
|
|
||||||
NoContent bool
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetRequest extracts HTMX data from the request
|
|
||||||
func GetRequest(ctx echo.Context) Request {
|
|
||||||
return Request{
|
|
||||||
Enabled: ctx.Request().Header.Get(HeaderRequest) == "true",
|
|
||||||
Boosted: ctx.Request().Header.Get(HeaderBoosted) == "true",
|
|
||||||
Trigger: ctx.Request().Header.Get(HeaderTrigger),
|
|
||||||
TriggerName: ctx.Request().Header.Get(HeaderTriggerName),
|
|
||||||
Target: ctx.Request().Header.Get(HeaderTarget),
|
|
||||||
Prompt: ctx.Request().Header.Get(HeaderPrompt),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply applies data from a Response to a server response
|
|
||||||
func (r Response) Apply(ctx echo.Context) {
|
|
||||||
if r.Push != "" {
|
|
||||||
ctx.Response().Header().Set(HeaderPush, r.Push)
|
|
||||||
}
|
|
||||||
if r.Redirect != "" {
|
|
||||||
ctx.Response().Header().Set(HeaderRedirect, r.Redirect)
|
|
||||||
}
|
|
||||||
if r.Refresh {
|
|
||||||
ctx.Response().Header().Set(HeaderRefresh, "true")
|
|
||||||
}
|
|
||||||
if r.Trigger != "" {
|
|
||||||
ctx.Response().Header().Set(HeaderTrigger, r.Trigger)
|
|
||||||
}
|
|
||||||
if r.TriggerAfterSwap != "" {
|
|
||||||
ctx.Response().Header().Set(HeaderTriggerAfterSwap, r.TriggerAfterSwap)
|
|
||||||
}
|
|
||||||
if r.TriggerAfterSettle != "" {
|
|
||||||
ctx.Response().Header().Set(HeaderTriggerAfterSettle, r.TriggerAfterSettle)
|
|
||||||
}
|
|
||||||
if r.NoContent {
|
|
||||||
ctx.Response().Status = http.StatusNoContent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
64
main.go
64
main.go
|
|
@ -1,64 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"goweb/routes"
|
|
||||||
"goweb/services"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// Start a new container
|
|
||||||
c := services.NewContainer()
|
|
||||||
defer func() {
|
|
||||||
if err := c.Shutdown(); err != nil {
|
|
||||||
c.Web.Logger.Fatal(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Build the router
|
|
||||||
routes.BuildRouter(c)
|
|
||||||
|
|
||||||
// Start the server
|
|
||||||
go func() {
|
|
||||||
srv := http.Server{
|
|
||||||
Addr: fmt.Sprintf("%s:%d", c.Config.HTTP.Hostname, c.Config.HTTP.Port),
|
|
||||||
Handler: c.Web,
|
|
||||||
ReadTimeout: c.Config.HTTP.ReadTimeout,
|
|
||||||
WriteTimeout: c.Config.HTTP.WriteTimeout,
|
|
||||||
IdleTimeout: c.Config.HTTP.IdleTimeout,
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Config.HTTP.TLS.Enabled {
|
|
||||||
certs, err := tls.LoadX509KeyPair(c.Config.HTTP.TLS.Certificate, c.Config.HTTP.TLS.Key)
|
|
||||||
if err != nil {
|
|
||||||
c.Web.Logger.Fatalf("cannot load TLS certificate: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
srv.TLSConfig = &tls.Config{
|
|
||||||
Certificates: []tls.Certificate{certs},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.Web.StartServer(&srv); err != http.ErrServerClosed {
|
|
||||||
c.Web.Logger.Fatalf("shutting down the server: v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Wait for interrupt signal to gracefully shutdown the server with a timeout of 10 seconds.
|
|
||||||
quit := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(quit, os.Interrupt)
|
|
||||||
signal.Notify(quit, os.Kill)
|
|
||||||
<-quit
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
if err := c.Web.Shutdown(ctx); err != nil {
|
|
||||||
c.Web.Logger.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
||||||
package middleware
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"goweb/context"
|
|
||||||
"goweb/ent"
|
|
||||||
"goweb/msg"
|
|
||||||
"goweb/services"
|
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
)
|
|
||||||
|
|
||||||
// LoadAuthenticatedUser loads the authenticated user, if one, and stores in context
|
|
||||||
func LoadAuthenticatedUser(authClient *services.AuthClient) echo.MiddlewareFunc {
|
|
||||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
|
||||||
return func(c echo.Context) error {
|
|
||||||
u, err := authClient.GetAuthenticatedUser(c)
|
|
||||||
switch err.(type) {
|
|
||||||
case *ent.NotFoundError:
|
|
||||||
c.Logger().Warn("auth user not found")
|
|
||||||
case services.NotAuthenticatedError:
|
|
||||||
case nil:
|
|
||||||
c.Set(context.AuthenticatedUserKey, u)
|
|
||||||
c.Logger().Infof("auth user loaded in to context: %d", u.ID)
|
|
||||||
default:
|
|
||||||
c.Logger().Errorf("error querying for authenticated user: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return next(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadValidPasswordToken loads a valid password token entity that matches the user and token
|
|
||||||
// provided in path parameters
|
|
||||||
// If the token is invalid, the user will be redirected to the forgot password route
|
|
||||||
// This requires that the user owning the token is loaded in to context
|
|
||||||
func LoadValidPasswordToken(authClient *services.AuthClient) echo.MiddlewareFunc {
|
|
||||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
|
||||||
return func(c echo.Context) error {
|
|
||||||
// Extract the user parameter
|
|
||||||
if c.Get(context.UserKey) == nil {
|
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
usr := c.Get(context.UserKey).(*ent.User)
|
|
||||||
|
|
||||||
token, err := authClient.GetValidPasswordToken(c, c.Param("password_token"), usr.ID)
|
|
||||||
|
|
||||||
switch err.(type) {
|
|
||||||
case nil:
|
|
||||||
c.Set(context.PasswordTokenKey, token)
|
|
||||||
return next(c)
|
|
||||||
case services.InvalidPasswordTokenError:
|
|
||||||
msg.Warning(c, "The link is either invalid or has expired. Please request a new one.")
|
|
||||||
return c.Redirect(http.StatusFound, c.Echo().Reverse("forgot_password"))
|
|
||||||
default:
|
|
||||||
c.Logger().Error(err)
|
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequireAuthentication requires that the user be authenticated in order to proceed
|
|
||||||
func RequireAuthentication() echo.MiddlewareFunc {
|
|
||||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
|
||||||
return func(c echo.Context) error {
|
|
||||||
if u := c.Get(context.AuthenticatedUserKey); u == nil {
|
|
||||||
return echo.NewHTTPError(http.StatusUnauthorized)
|
|
||||||
}
|
|
||||||
|
|
||||||
return next(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequireNoAuthentication requires that the user not be authenticated in order to proceed
|
|
||||||
func RequireNoAuthentication() echo.MiddlewareFunc {
|
|
||||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
|
||||||
return func(c echo.Context) error {
|
|
||||||
if u := c.Get(context.AuthenticatedUserKey); u != nil {
|
|
||||||
return echo.NewHTTPError(http.StatusForbidden)
|
|
||||||
}
|
|
||||||
|
|
||||||
return next(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
package middleware
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"goweb/context"
|
|
||||||
|
|
||||||
"github.com/eko/gocache/v2/cache"
|
|
||||||
"github.com/eko/gocache/v2/marshaler"
|
|
||||||
"github.com/go-redis/redis/v8"
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CachedPage is what is used to store a rendered Page in the cache
|
|
||||||
type CachedPage struct {
|
|
||||||
// URL stores the URL of the requested page
|
|
||||||
URL string
|
|
||||||
|
|
||||||
// HTML stores the complete HTML of the rendered Page
|
|
||||||
HTML []byte
|
|
||||||
|
|
||||||
// StatusCode stores the HTTP status code
|
|
||||||
StatusCode int
|
|
||||||
|
|
||||||
// Headers stores the HTTP headers
|
|
||||||
Headers map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServeCachedPage attempts to load a page from the cache by matching on the complete request URL
|
|
||||||
// If a page is cached for the requested URL, it will be served here and the request terminated.
|
|
||||||
// Any request made by an authenticated user or that is not a GET will be skipped.
|
|
||||||
func ServeCachedPage(ch *cache.Cache) echo.MiddlewareFunc {
|
|
||||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
|
||||||
return func(c echo.Context) error {
|
|
||||||
// Skip non GET requests
|
|
||||||
if c.Request().Method != http.MethodGet {
|
|
||||||
return next(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip if the user is authenticated
|
|
||||||
if c.Get(context.AuthenticatedUserKey) != nil {
|
|
||||||
return next(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt to load from cache
|
|
||||||
res, err := marshaler.New(ch).Get(
|
|
||||||
c.Request().Context(),
|
|
||||||
c.Request().URL.String(),
|
|
||||||
new(CachedPage),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
if err == redis.Nil {
|
|
||||||
c.Logger().Info("no cached page found")
|
|
||||||
} else {
|
|
||||||
c.Logger().Errorf("failed getting cached page: %v", err)
|
|
||||||
}
|
|
||||||
return next(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
page, ok := res.(*CachedPage)
|
|
||||||
if !ok {
|
|
||||||
c.Logger().Errorf("failed casting cached page")
|
|
||||||
return next(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set any headers
|
|
||||||
if page.Headers != nil {
|
|
||||||
for k, v := range page.Headers {
|
|
||||||
c.Response().Header().Set(k, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Logger().Info("serving cached page")
|
|
||||||
|
|
||||||
return c.HTMLBlob(page.StatusCode, page.HTML)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CacheControl sets a Cache-Control header with a given max age
|
|
||||||
func CacheControl(maxAge time.Duration) echo.MiddlewareFunc {
|
|
||||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
|
||||||
return func(c echo.Context) error {
|
|
||||||
v := "no-cache, no-store"
|
|
||||||
if maxAge > 0 {
|
|
||||||
v = fmt.Sprintf("public, max-age=%.0f", maxAge.Seconds())
|
|
||||||
}
|
|
||||||
c.Response().Header().Set("Cache-Control", v)
|
|
||||||
return next(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
package middleware
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"goweb/tests"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/eko/gocache/v2/marshaler"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestServeCachedPage(t *testing.T) {
|
|
||||||
// Cache a page
|
|
||||||
cp := CachedPage{
|
|
||||||
URL: "/cache",
|
|
||||||
HTML: []byte("html"),
|
|
||||||
Headers: make(map[string]string),
|
|
||||||
StatusCode: http.StatusCreated,
|
|
||||||
}
|
|
||||||
cp.Headers["a"] = "b"
|
|
||||||
cp.Headers["c"] = "d"
|
|
||||||
err := marshaler.New(c.Cache).Set(context.Background(), cp.URL, cp, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Request the URL of the cached page
|
|
||||||
ctx, rec := tests.NewContext(c.Web, cp.URL)
|
|
||||||
err = tests.ExecuteMiddleware(ctx, ServeCachedPage(c.Cache))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, cp.StatusCode, ctx.Response().Status)
|
|
||||||
assert.Equal(t, cp.Headers["a"], ctx.Response().Header().Get("a"))
|
|
||||||
assert.Equal(t, cp.Headers["c"], ctx.Response().Header().Get("c"))
|
|
||||||
assert.Equal(t, cp.HTML, rec.Body.Bytes())
|
|
||||||
|
|
||||||
// Login and try again
|
|
||||||
tests.InitSession(ctx)
|
|
||||||
err = c.Auth.Login(ctx, usr.ID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
_ = tests.ExecuteMiddleware(ctx, LoadAuthenticatedUser(c.Auth))
|
|
||||||
err = tests.ExecuteMiddleware(ctx, ServeCachedPage(c.Cache))
|
|
||||||
assert.Nil(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCacheControl(t *testing.T) {
|
|
||||||
ctx, _ := tests.NewContext(c.Web, "/")
|
|
||||||
_ = tests.ExecuteMiddleware(ctx, CacheControl(time.Second*5))
|
|
||||||
assert.Equal(t, "public, max-age=5", ctx.Response().Header().Get("Cache-Control"))
|
|
||||||
_ = tests.ExecuteMiddleware(ctx, CacheControl(0))
|
|
||||||
assert.Equal(t, "no-cache, no-store", ctx.Response().Header().Get("Cache-Control"))
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
package middleware
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
)
|
|
||||||
|
|
||||||
// LogRequestID includes the request ID in all logs for the given request
|
|
||||||
// This requires that middleware that includes the request ID first execute
|
|
||||||
func LogRequestID() echo.MiddlewareFunc {
|
|
||||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
|
||||||
return func(c echo.Context) error {
|
|
||||||
rID := c.Response().Header().Get(echo.HeaderXRequestID)
|
|
||||||
format := `{"time":"${time_rfc3339_nano}","id":"%s","level":"${level}","prefix":"${prefix}","file":"${short_file}","line":"${line}"}`
|
|
||||||
c.Logger().SetHeader(fmt.Sprintf(format, rID))
|
|
||||||
return next(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
package middleware
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"goweb/tests"
|
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
|
|
||||||
echomw "github.com/labstack/echo/v4/middleware"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestLogRequestID(t *testing.T) {
|
|
||||||
ctx, _ := tests.NewContext(c.Web, "/")
|
|
||||||
_ = tests.ExecuteMiddleware(ctx, echomw.RequestID())
|
|
||||||
_ = tests.ExecuteMiddleware(ctx, LogRequestID())
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
ctx.Logger().SetOutput(&buf)
|
|
||||||
ctx.Logger().Info("test")
|
|
||||||
rID := ctx.Response().Header().Get(echo.HeaderXRequestID)
|
|
||||||
assert.Contains(t, buf.String(), fmt.Sprintf(`id":"%s"`, rID))
|
|
||||||
}
|
|
||||||
62
pkg/context/context.go
Normal file
62
pkg/context/context.go
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// AuthenticatedUserKey is the key used to store the authenticated user in context.
|
||||||
|
AuthenticatedUserKey = "auth_user"
|
||||||
|
|
||||||
|
// UserKey is the key used to store a user in context.
|
||||||
|
UserKey = "user"
|
||||||
|
|
||||||
|
// FormKey is the key used to store a form in context.
|
||||||
|
FormKey = "form"
|
||||||
|
|
||||||
|
// PasswordTokenKey is the key used to store a password token in context.
|
||||||
|
PasswordTokenKey = "password_token"
|
||||||
|
|
||||||
|
// LoggerKey is the key used to store a structured logger in context.
|
||||||
|
LoggerKey = "logger"
|
||||||
|
|
||||||
|
// SessionKey is the key used to store the session data in context.
|
||||||
|
SessionKey = "session"
|
||||||
|
|
||||||
|
// HTMXRequestKey is the key used to store the HTMX request data in context.
|
||||||
|
HTMXRequestKey = "htmx"
|
||||||
|
|
||||||
|
// CSRFKey is the key used to store the CSRF token in context.
|
||||||
|
CSRFKey = "csrf"
|
||||||
|
|
||||||
|
// ConfigKey is the key used to store the configuration in context.
|
||||||
|
ConfigKey = "config"
|
||||||
|
|
||||||
|
// AdminEntityKey is the key used to store the entity being operated on in the admin panel.
|
||||||
|
AdminEntityKey = "admin:entity"
|
||||||
|
|
||||||
|
// AdminEntityIDKey is the key used to store the ID of the entity being operated on in the admin panel.
|
||||||
|
AdminEntityIDKey = "admin:entity_id"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsCanceledError determines if an error is due to a context cancellation.
|
||||||
|
func IsCanceledError(err error) bool {
|
||||||
|
return errors.Is(err, context.Canceled)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache checks if a value of a given type exists in the Echo context for a given key and returns that, otherwise
|
||||||
|
// it will use a callback to generate a value, which is stored in the context then returned. This allows you to
|
||||||
|
// only generate items only once for a given request.
|
||||||
|
func Cache[T any](ctx echo.Context, key string, gen func(echo.Context) T) T {
|
||||||
|
if val := ctx.Get(key); val != nil {
|
||||||
|
if v, ok := val.(T); ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val := gen(ctx)
|
||||||
|
ctx.Set(key, val)
|
||||||
|
return val
|
||||||
|
}
|
||||||
47
pkg/context/context_test.go
Normal file
47
pkg/context/context_test.go
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIsCanceled(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
assert.False(t, IsCanceledError(ctx.Err()))
|
||||||
|
cancel()
|
||||||
|
assert.True(t, IsCanceledError(ctx.Err()))
|
||||||
|
|
||||||
|
ctx, cancel = context.WithTimeout(context.Background(), time.Microsecond*5)
|
||||||
|
<-ctx.Done()
|
||||||
|
cancel()
|
||||||
|
assert.False(t, IsCanceledError(ctx.Err()))
|
||||||
|
|
||||||
|
assert.False(t, IsCanceledError(errors.New("test error")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCache(t *testing.T) {
|
||||||
|
ctx := echo.New().NewContext(nil, nil)
|
||||||
|
|
||||||
|
key := "testing"
|
||||||
|
value := "hello"
|
||||||
|
called := 0
|
||||||
|
callback := func(ctx echo.Context) string {
|
||||||
|
called++
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Nil(t, ctx.Get(key))
|
||||||
|
|
||||||
|
got := Cache(ctx, key, callback)
|
||||||
|
assert.Equal(t, value, got)
|
||||||
|
assert.Equal(t, 1, called)
|
||||||
|
|
||||||
|
got = Cache(ctx, key, callback)
|
||||||
|
assert.Equal(t, value, got)
|
||||||
|
assert.Equal(t, 1, called)
|
||||||
|
}
|
||||||
55
pkg/form/form.go
Normal file
55
pkg/form/form.go
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
package form
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Form represents a form that can be submitted and validated.
|
||||||
|
type Form interface {
|
||||||
|
// Submit marks the form as submitted, stores a pointer to it in the context, binds the request
|
||||||
|
// values to the struct fields, and validates the input based on the struct tags.
|
||||||
|
// Returns a validator.ValidationErrors, if the form values were not valid, or an echo.HTTPError,
|
||||||
|
// if the request failed to process.
|
||||||
|
Submit(c echo.Context, form any) error
|
||||||
|
|
||||||
|
// IsSubmitted returns true if the form was submitted.
|
||||||
|
IsSubmitted() bool
|
||||||
|
|
||||||
|
// IsValid returns true if the form has no validation errors.
|
||||||
|
IsValid() bool
|
||||||
|
|
||||||
|
// IsDone returns true if the form was submitted and has no validation errors.
|
||||||
|
IsDone() bool
|
||||||
|
|
||||||
|
// FieldHasErrors returns true if a given struct field has validation errors.
|
||||||
|
FieldHasErrors(fieldName string) bool
|
||||||
|
|
||||||
|
// SetFieldError sets a validation error message for a given struct field.
|
||||||
|
SetFieldError(fieldName string, message string)
|
||||||
|
|
||||||
|
// GetFieldErrors returns the validation errors for a given struct field.
|
||||||
|
GetFieldErrors(fieldName string) []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get gets a form from the context or initializes a new copy if one is not set.
|
||||||
|
func Get[T any](ctx echo.Context) *T {
|
||||||
|
if v := ctx.Get(context.FormKey); v != nil {
|
||||||
|
if form, ok := v.(*T); ok {
|
||||||
|
return form
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var v T
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear removes the form set in the context.
|
||||||
|
func Clear(ctx echo.Context) {
|
||||||
|
ctx.Set(context.FormKey, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Submit submits a form.
|
||||||
|
// See Form.Submit().
|
||||||
|
func Submit(ctx echo.Context, form Form) error {
|
||||||
|
return form.Submit(ctx, form)
|
||||||
|
}
|
||||||
67
pkg/form/form_test.go
Normal file
67
pkg/form/form_test.go
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
package form
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/context"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/tests"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockForm struct {
|
||||||
|
called bool
|
||||||
|
Submission
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockForm) Submit(_ echo.Context, _ any) error {
|
||||||
|
m.called = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSubmit(t *testing.T) {
|
||||||
|
m := mockForm{}
|
||||||
|
ctx, _ := tests.NewContext(echo.New(), "/")
|
||||||
|
err := Submit(ctx, &m)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.True(t, m.called)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetClear(t *testing.T) {
|
||||||
|
e := echo.New()
|
||||||
|
|
||||||
|
type example struct {
|
||||||
|
Name string `form:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("get empty context", func(t *testing.T) {
|
||||||
|
// Empty context, still return a form.
|
||||||
|
ctx, _ := tests.NewContext(e, "/")
|
||||||
|
form := Get[example](ctx)
|
||||||
|
assert.NotNil(t, form)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("get non-empty context", func(t *testing.T) {
|
||||||
|
form := example{
|
||||||
|
Name: "test",
|
||||||
|
}
|
||||||
|
ctx, _ := tests.NewContext(e, "/")
|
||||||
|
ctx.Set(context.FormKey, &form)
|
||||||
|
|
||||||
|
// Get again and expect the values were stored.
|
||||||
|
got := Get[example](ctx)
|
||||||
|
require.NotNil(t, got)
|
||||||
|
assert.Equal(t, "test", got.Name)
|
||||||
|
|
||||||
|
// Attempt getting a different type to ensure there's no panic.
|
||||||
|
ret := Get[int](ctx)
|
||||||
|
require.NotNil(t, ret)
|
||||||
|
|
||||||
|
// Clear.
|
||||||
|
Clear(ctx)
|
||||||
|
got = Get[example](ctx)
|
||||||
|
require.NotNil(t, got)
|
||||||
|
assert.Empty(t, got.Name)
|
||||||
|
})
|
||||||
|
}
|
||||||
105
pkg/form/submission.go
Normal file
105
pkg/form/submission.go
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
package form
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/context"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Submission represents the state of the submission of a form, not including the form itself.
|
||||||
|
// This satisfies the Form interface.
|
||||||
|
type Submission struct {
|
||||||
|
// isSubmitted indicates if the form has been submitted.
|
||||||
|
isSubmitted bool
|
||||||
|
|
||||||
|
// errors stores a slice of error message strings keyed by form struct field name.
|
||||||
|
errors map[string][]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Submission) Submit(ctx echo.Context, form any) error {
|
||||||
|
f.isSubmitted = true
|
||||||
|
|
||||||
|
// Set in context so the form can later be retrieved.
|
||||||
|
ctx.Set(context.FormKey, form)
|
||||||
|
|
||||||
|
// Bind the values from the incoming request to the form struct.
|
||||||
|
if err := ctx.Bind(form); err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("unable to bind form: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the form.
|
||||||
|
if err := ctx.Validate(form); err != nil {
|
||||||
|
f.setErrorMessages(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Submission) IsSubmitted() bool {
|
||||||
|
return f.isSubmitted
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Submission) IsValid() bool {
|
||||||
|
if f.errors == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return len(f.errors) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Submission) IsDone() bool {
|
||||||
|
return f.IsSubmitted() && f.IsValid()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Submission) FieldHasErrors(fieldName string) bool {
|
||||||
|
return len(f.GetFieldErrors(fieldName)) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Submission) SetFieldError(fieldName string, message string) {
|
||||||
|
if f.errors == nil {
|
||||||
|
f.errors = make(map[string][]string)
|
||||||
|
}
|
||||||
|
f.errors[fieldName] = append(f.errors[fieldName], message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Submission) GetFieldErrors(fieldName string) []string {
|
||||||
|
if f.errors == nil {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
return f.errors[fieldName]
|
||||||
|
}
|
||||||
|
|
||||||
|
// setErrorMessages sets errors messages on the submission for all fields that failed validation.
|
||||||
|
func (f *Submission) setErrorMessages(err error) {
|
||||||
|
// Only this is supported right now
|
||||||
|
ves, ok := err.(validator.ValidationErrors)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ve := range ves {
|
||||||
|
var message string
|
||||||
|
|
||||||
|
// Provide better error messages depending on the failed validation tag.
|
||||||
|
// This should be expanded as you use additional tags in your validation.
|
||||||
|
switch ve.Tag() {
|
||||||
|
case "required":
|
||||||
|
message = "This field is required."
|
||||||
|
case "email":
|
||||||
|
message = "Enter a valid email address."
|
||||||
|
case "eqfield":
|
||||||
|
message = "Does not match."
|
||||||
|
case "gte":
|
||||||
|
message = fmt.Sprintf("Must be greater than or equal to %v.", ve.Param())
|
||||||
|
default:
|
||||||
|
message = "Invalid value."
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the error.
|
||||||
|
f.SetFieldError(ve.Field(), message)
|
||||||
|
}
|
||||||
|
}
|
||||||
57
pkg/form/submission_test.go
Normal file
57
pkg/form/submission_test.go
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
package form
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/services"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFormSubmission(t *testing.T) {
|
||||||
|
type formTest struct {
|
||||||
|
Name string `form:"name" validate:"required"`
|
||||||
|
Email string `form:"email" validate:"required,email"`
|
||||||
|
Submission
|
||||||
|
}
|
||||||
|
|
||||||
|
e := echo.New()
|
||||||
|
e.Validator = services.NewValidator()
|
||||||
|
|
||||||
|
t.Run("valid request", func(t *testing.T) {
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("email=a@a.com"))
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
ctx := e.NewContext(req, httptest.NewRecorder())
|
||||||
|
|
||||||
|
var form formTest
|
||||||
|
err := form.Submit(ctx, &form)
|
||||||
|
assert.IsType(t, validator.ValidationErrors{}, err)
|
||||||
|
|
||||||
|
assert.Empty(t, form.Name)
|
||||||
|
assert.Equal(t, "a@a.com", form.Email)
|
||||||
|
assert.False(t, form.IsValid())
|
||||||
|
assert.True(t, form.FieldHasErrors("Name"))
|
||||||
|
assert.False(t, form.FieldHasErrors("Email"))
|
||||||
|
require.Len(t, form.GetFieldErrors("Name"), 1)
|
||||||
|
assert.Len(t, form.GetFieldErrors("Email"), 0)
|
||||||
|
assert.Equal(t, "This field is required.", form.GetFieldErrors("Name")[0])
|
||||||
|
assert.False(t, form.IsDone())
|
||||||
|
|
||||||
|
formInCtx := Get[formTest](ctx)
|
||||||
|
require.NotNil(t, formInCtx)
|
||||||
|
assert.Equal(t, form.Email, formInCtx.Email)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid request", func(t *testing.T) {
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("abc=abc"))
|
||||||
|
ctx := e.NewContext(req, httptest.NewRecorder())
|
||||||
|
var form formTest
|
||||||
|
err := form.Submit(ctx, &form)
|
||||||
|
assert.IsType(t, new(echo.HTTPError), err)
|
||||||
|
})
|
||||||
|
}
|
||||||
198
pkg/handlers/admin.go
Normal file
198
pkg/handlers/admin.go
Normal file
|
|
@ -0,0 +1,198 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/mikestefanello/backlite/ui"
|
||||||
|
"github.com/camzawacki/personal-site/ent"
|
||||||
|
"github.com/camzawacki/personal-site/ent/admin"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/context"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/middleware"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/msg"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/pager"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/redirect"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/routenames"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/services"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/ui/pages"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Admin struct {
|
||||||
|
orm *ent.Client
|
||||||
|
admin *admin.Handler
|
||||||
|
backlite *ui.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(new(Admin))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Admin) Init(c *services.Container) error {
|
||||||
|
var err error
|
||||||
|
h.orm = c.ORM
|
||||||
|
h.admin = admin.NewHandler(h.orm, admin.HandlerConfig{
|
||||||
|
ItemsPerPage: 25,
|
||||||
|
PageQueryKey: pager.QueryKey,
|
||||||
|
TimeFormat: time.DateTime,
|
||||||
|
})
|
||||||
|
h.backlite, err = ui.NewHandler(ui.Config{
|
||||||
|
DB: c.Database,
|
||||||
|
BasePath: "/admin/tasks",
|
||||||
|
ItemsPerPage: 25,
|
||||||
|
ReleaseAfter: c.Config.Tasks.ReleaseAfter,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Admin) Routes(g *echo.Group) {
|
||||||
|
ag := g.Group("/admin", middleware.RequireAdmin)
|
||||||
|
|
||||||
|
entities := ag.Group("/entity")
|
||||||
|
for _, n := range admin.GetEntityTypes() {
|
||||||
|
ng := entities.Group(fmt.Sprintf("/%s", strings.ToLower(n.GetName())))
|
||||||
|
ng.GET("", h.EntityList(n)).
|
||||||
|
Name = routenames.AdminEntityList(n.GetName())
|
||||||
|
ng.GET("/add", h.EntityAdd(n)).
|
||||||
|
Name = routenames.AdminEntityAdd(n.GetName())
|
||||||
|
ng.POST("/add", h.EntityAddSubmit(n)).
|
||||||
|
Name = routenames.AdminEntityAddSubmit(n.GetName())
|
||||||
|
ng.GET("/:id/edit", h.EntityEdit(n), h.middlewareEntityLoad(n)).
|
||||||
|
Name = routenames.AdminEntityEdit(n.GetName())
|
||||||
|
ng.POST("/:id/edit", h.EntityEditSubmit(n), h.middlewareEntityLoad(n)).
|
||||||
|
Name = routenames.AdminEntityEditSubmit(n.GetName())
|
||||||
|
ng.GET("/:id/delete", h.EntityDelete(n), h.middlewareEntityLoad(n)).
|
||||||
|
Name = routenames.AdminEntityDelete(n.GetName())
|
||||||
|
ng.POST("/:id/delete", h.EntityDeleteSubmit(n), h.middlewareEntityLoad(n)).
|
||||||
|
Name = routenames.AdminEntityDeleteSubmit(n.GetName())
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks := ag.Group("/tasks")
|
||||||
|
tasks.GET("", h.Backlite(h.backlite.Running)).Name = routenames.AdminTasks
|
||||||
|
tasks.GET("/succeeded", h.Backlite(h.backlite.Succeeded))
|
||||||
|
tasks.GET("/failed", h.Backlite(h.backlite.Failed))
|
||||||
|
tasks.GET("/upcoming", h.Backlite(h.backlite.Upcoming))
|
||||||
|
tasks.GET("/task/:id", h.Backlite(h.backlite.Task))
|
||||||
|
tasks.GET("/completed/:id", h.Backlite(h.backlite.TaskCompleted))
|
||||||
|
}
|
||||||
|
|
||||||
|
// middlewareEntityLoad is middleware to extract the entity ID and attempt to load the given entity.
|
||||||
|
func (h *Admin) middlewareEntityLoad(n admin.EntityType) echo.MiddlewareFunc {
|
||||||
|
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
|
return func(ctx echo.Context) error {
|
||||||
|
id, err := strconv.Atoi(ctx.Param("id"))
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, "invalid entity ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
entity, err := h.admin.Get(ctx, n, id)
|
||||||
|
switch {
|
||||||
|
case err == nil:
|
||||||
|
ctx.Set(context.AdminEntityIDKey, id)
|
||||||
|
ctx.Set(context.AdminEntityKey, map[string][]string(entity))
|
||||||
|
return next(ctx)
|
||||||
|
case ent.IsNotFound(err):
|
||||||
|
return echo.NewHTTPError(http.StatusNotFound, "entity not found")
|
||||||
|
default:
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Admin) EntityList(n admin.EntityType) echo.HandlerFunc {
|
||||||
|
return func(ctx echo.Context) error {
|
||||||
|
list, err := h.admin.List(ctx, n)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pages.AdminEntityList(ctx, n, list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Admin) EntityAdd(n admin.EntityType) echo.HandlerFunc {
|
||||||
|
return func(ctx echo.Context) error {
|
||||||
|
return pages.AdminEntityInput(ctx, n, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Admin) EntityAddSubmit(n admin.EntityType) echo.HandlerFunc {
|
||||||
|
return func(ctx echo.Context) error {
|
||||||
|
err := h.admin.Create(ctx, n)
|
||||||
|
if err != nil {
|
||||||
|
msg.Error(ctx, err.Error())
|
||||||
|
return h.EntityAdd(n)(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Success(ctx, fmt.Sprintf("Successfully added %s.", n.GetName()))
|
||||||
|
|
||||||
|
return redirect.
|
||||||
|
New(ctx).
|
||||||
|
Route(routenames.AdminEntityList(n.GetName())).
|
||||||
|
StatusCode(http.StatusFound).
|
||||||
|
Go()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Admin) EntityEdit(n admin.EntityType) echo.HandlerFunc {
|
||||||
|
return func(ctx echo.Context) error {
|
||||||
|
v := ctx.Get(context.AdminEntityKey).(map[string][]string)
|
||||||
|
return pages.AdminEntityInput(ctx, n, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Admin) EntityEditSubmit(n admin.EntityType) echo.HandlerFunc {
|
||||||
|
return func(ctx echo.Context) error {
|
||||||
|
id := ctx.Get(context.AdminEntityIDKey).(int)
|
||||||
|
err := h.admin.Update(ctx, n, id)
|
||||||
|
if err != nil {
|
||||||
|
msg.Error(ctx, err.Error())
|
||||||
|
return h.EntityEdit(n)(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Success(ctx, fmt.Sprintf("Updated %s.", n.GetName()))
|
||||||
|
|
||||||
|
return redirect.
|
||||||
|
New(ctx).
|
||||||
|
Route(routenames.AdminEntityList(n.GetName())).
|
||||||
|
StatusCode(http.StatusFound).
|
||||||
|
Go()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Admin) EntityDelete(n admin.EntityType) echo.HandlerFunc {
|
||||||
|
return func(ctx echo.Context) error {
|
||||||
|
return pages.AdminEntityDelete(ctx, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Admin) EntityDeleteSubmit(n admin.EntityType) echo.HandlerFunc {
|
||||||
|
return func(ctx echo.Context) error {
|
||||||
|
id := ctx.Get(context.AdminEntityIDKey).(int)
|
||||||
|
if err := h.admin.Delete(ctx, n, id); err != nil {
|
||||||
|
msg.Error(ctx, err.Error())
|
||||||
|
return h.EntityDelete(n)(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Success(ctx, fmt.Sprintf("Successfully deleted %s (ID %d).", n.GetName(), id))
|
||||||
|
|
||||||
|
return redirect.
|
||||||
|
New(ctx).
|
||||||
|
Route(routenames.AdminEntityList(n.GetName())).
|
||||||
|
StatusCode(http.StatusFound).
|
||||||
|
Go()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Admin) Backlite(handler func(http.ResponseWriter, *http.Request) error) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
if id := c.Param("id"); id != "" {
|
||||||
|
c.Request().SetPathValue("task", id)
|
||||||
|
}
|
||||||
|
return handler(c.Response().Writer, c.Request())
|
||||||
|
}
|
||||||
|
}
|
||||||
380
pkg/handlers/auth.go
Normal file
380
pkg/handlers/auth.go
Normal file
|
|
@ -0,0 +1,380 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/camzawacki/personal-site/config"
|
||||||
|
"github.com/camzawacki/personal-site/ent"
|
||||||
|
"github.com/camzawacki/personal-site/ent/user"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/context"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/form"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/log"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/middleware"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/msg"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/redirect"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/routenames"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/services"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/ui/emails"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/ui/forms"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/ui/pages"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Auth struct {
|
||||||
|
config *config.Config
|
||||||
|
auth *services.AuthClient
|
||||||
|
mail *services.MailClient
|
||||||
|
orm *ent.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(new(Auth))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Auth) Init(c *services.Container) error {
|
||||||
|
h.config = c.Config
|
||||||
|
h.orm = c.ORM
|
||||||
|
h.auth = c.Auth
|
||||||
|
h.mail = c.Mail
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Auth) Routes(g *echo.Group) {
|
||||||
|
g.GET("/logout", h.Logout, middleware.RequireAuthentication).Name = routenames.Logout
|
||||||
|
g.GET("/email/verify/:token", h.VerifyEmail).Name = routenames.VerifyEmail
|
||||||
|
|
||||||
|
noAuth := g.Group("/user", middleware.RequireNoAuthentication)
|
||||||
|
noAuth.GET("/login", h.LoginPage).Name = routenames.Login
|
||||||
|
noAuth.POST("/login", h.LoginSubmit).Name = routenames.LoginSubmit
|
||||||
|
noAuth.GET("/register", h.RegisterPage).Name = routenames.Register
|
||||||
|
noAuth.POST("/register", h.RegisterSubmit).Name = routenames.RegisterSubmit
|
||||||
|
noAuth.GET("/password", h.ForgotPasswordPage).Name = routenames.ForgotPassword
|
||||||
|
noAuth.POST("/password", h.ForgotPasswordSubmit).Name = routenames.ForgotPasswordSubmit
|
||||||
|
|
||||||
|
resetGroup := noAuth.Group("/password/reset",
|
||||||
|
middleware.LoadUser(h.orm),
|
||||||
|
middleware.LoadValidPasswordToken(h.auth),
|
||||||
|
)
|
||||||
|
resetGroup.GET("/token/:user/:password_token/:token", h.ResetPasswordPage).Name = routenames.ResetPassword
|
||||||
|
resetGroup.POST("/token/:user/:password_token/:token", h.ResetPasswordSubmit).Name = routenames.ResetPasswordSubmit
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Auth) ForgotPasswordPage(ctx echo.Context) error {
|
||||||
|
return pages.ForgotPassword(ctx, form.Get[forms.ForgotPassword](ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Auth) ForgotPasswordSubmit(ctx echo.Context) error {
|
||||||
|
var input forms.ForgotPassword
|
||||||
|
|
||||||
|
succeed := func() error {
|
||||||
|
form.Clear(ctx)
|
||||||
|
msg.Success(ctx, "An email containing a link to reset your password will be sent to this address if it exists in our system.")
|
||||||
|
return h.ForgotPasswordPage(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := form.Submit(ctx, &input)
|
||||||
|
|
||||||
|
switch err.(type) {
|
||||||
|
case nil:
|
||||||
|
case validator.ValidationErrors:
|
||||||
|
return h.ForgotPasswordPage(ctx)
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to load the user.
|
||||||
|
u, err := h.orm.User.
|
||||||
|
Query().
|
||||||
|
Where(user.Email(strings.ToLower(input.Email))).
|
||||||
|
Only(ctx.Request().Context())
|
||||||
|
|
||||||
|
switch err.(type) {
|
||||||
|
case *ent.NotFoundError:
|
||||||
|
return succeed()
|
||||||
|
case nil:
|
||||||
|
default:
|
||||||
|
return fail(err, "error querying user during forgot password")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the token.
|
||||||
|
token, pt, err := h.auth.GeneratePasswordResetToken(ctx, u.ID)
|
||||||
|
if err != nil {
|
||||||
|
return fail(err, "error generating password reset token")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Ctx(ctx).Info("generated password reset token",
|
||||||
|
"user_id", u.ID,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Email the user.
|
||||||
|
url := ctx.Echo().Reverse(routenames.ResetPassword, u.ID, pt.ID, token)
|
||||||
|
err = h.mail.
|
||||||
|
Compose().
|
||||||
|
To(u.Email).
|
||||||
|
Subject("Reset your password").
|
||||||
|
Body(fmt.Sprintf("Go here to reset your password: %s", h.config.App.Host+url)).
|
||||||
|
Send(ctx)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fail(err, "error sending password reset email")
|
||||||
|
}
|
||||||
|
|
||||||
|
return succeed()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Auth) LoginPage(ctx echo.Context) error {
|
||||||
|
return pages.Login(ctx, form.Get[forms.Login](ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Auth) LoginSubmit(ctx echo.Context) error {
|
||||||
|
var input forms.Login
|
||||||
|
|
||||||
|
authFailed := func() error {
|
||||||
|
input.SetFieldError("Email", "")
|
||||||
|
input.SetFieldError("Password", "")
|
||||||
|
msg.Error(ctx, "Invalid credentials. Please try again.")
|
||||||
|
return h.LoginPage(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := form.Submit(ctx, &input)
|
||||||
|
|
||||||
|
switch err.(type) {
|
||||||
|
case nil:
|
||||||
|
case validator.ValidationErrors:
|
||||||
|
return h.LoginPage(ctx)
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to load the user.
|
||||||
|
u, err := h.orm.User.
|
||||||
|
Query().
|
||||||
|
Where(user.Email(strings.ToLower(input.Email))).
|
||||||
|
Only(ctx.Request().Context())
|
||||||
|
|
||||||
|
switch err.(type) {
|
||||||
|
case *ent.NotFoundError:
|
||||||
|
return authFailed()
|
||||||
|
case nil:
|
||||||
|
default:
|
||||||
|
return fail(err, "error querying user during login")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the password is correct.
|
||||||
|
err = h.auth.CheckPassword(input.Password, u.Password)
|
||||||
|
if err != nil {
|
||||||
|
return authFailed()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log the user in.
|
||||||
|
err = h.auth.Login(ctx, u.ID)
|
||||||
|
if err != nil {
|
||||||
|
return fail(err, "unable to log in user")
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Success(ctx, fmt.Sprintf("Welcome back, %s. You are now logged in.", u.Name))
|
||||||
|
|
||||||
|
return redirect.New(ctx).
|
||||||
|
Route(routenames.Home).
|
||||||
|
Go()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Auth) Logout(ctx echo.Context) error {
|
||||||
|
if err := h.auth.Logout(ctx); err == nil {
|
||||||
|
msg.Success(ctx, "You have been logged out successfully.")
|
||||||
|
} else {
|
||||||
|
msg.Error(ctx, "An error occurred. Please try again.")
|
||||||
|
}
|
||||||
|
return redirect.New(ctx).
|
||||||
|
Route(routenames.Home).
|
||||||
|
Go()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Auth) RegisterPage(ctx echo.Context) error {
|
||||||
|
return pages.Register(ctx, form.Get[forms.Register](ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Auth) RegisterSubmit(ctx echo.Context) error {
|
||||||
|
var input forms.Register
|
||||||
|
|
||||||
|
err := form.Submit(ctx, &input)
|
||||||
|
|
||||||
|
switch err.(type) {
|
||||||
|
case nil:
|
||||||
|
case validator.ValidationErrors:
|
||||||
|
return h.RegisterPage(ctx)
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt creating the user.
|
||||||
|
u, err := h.orm.User.
|
||||||
|
Create().
|
||||||
|
SetName(input.Name).
|
||||||
|
SetEmail(input.Email).
|
||||||
|
SetPassword(input.Password).
|
||||||
|
Save(ctx.Request().Context())
|
||||||
|
|
||||||
|
switch err.(type) {
|
||||||
|
case nil:
|
||||||
|
log.Ctx(ctx).Info("user created",
|
||||||
|
"user_name", u.Name,
|
||||||
|
"user_id", u.ID,
|
||||||
|
)
|
||||||
|
case *ent.ConstraintError:
|
||||||
|
msg.Warning(ctx, "A user with this email address already exists. Please log in.")
|
||||||
|
return redirect.New(ctx).
|
||||||
|
Route(routenames.Login).
|
||||||
|
Go()
|
||||||
|
default:
|
||||||
|
return fail(err, "unable to create user")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log the user in.
|
||||||
|
err = h.auth.Login(ctx, u.ID)
|
||||||
|
if err != nil {
|
||||||
|
log.Ctx(ctx).Error("unable to log user in",
|
||||||
|
"error", err,
|
||||||
|
"user_id", u.ID,
|
||||||
|
)
|
||||||
|
msg.Info(ctx, "Your account has been created.")
|
||||||
|
return redirect.New(ctx).
|
||||||
|
Route(routenames.Login).
|
||||||
|
Go()
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Success(ctx, "Your account has been created. You are now logged in.")
|
||||||
|
|
||||||
|
// Send the verification email.
|
||||||
|
h.sendVerificationEmail(ctx, u)
|
||||||
|
|
||||||
|
return redirect.New(ctx).
|
||||||
|
Route(routenames.Home).
|
||||||
|
Go()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Auth) sendVerificationEmail(ctx echo.Context, usr *ent.User) {
|
||||||
|
// Generate a token.
|
||||||
|
token, err := h.auth.GenerateEmailVerificationToken(usr.Email)
|
||||||
|
if err != nil {
|
||||||
|
log.Ctx(ctx).Error("unable to generate email verification token",
|
||||||
|
"user_id", usr.ID,
|
||||||
|
"error", err,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the email.
|
||||||
|
err = h.mail.
|
||||||
|
Compose().
|
||||||
|
To(usr.Email).
|
||||||
|
Subject("Confirm your email address").
|
||||||
|
Component(emails.ConfirmEmailAddress(ctx, usr.Name, token)).
|
||||||
|
Send(ctx)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Ctx(ctx).Error("unable to send email verification link",
|
||||||
|
"user_id", usr.ID,
|
||||||
|
"error", err,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Info(ctx, "An email was sent to you to verify your email address.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Auth) ResetPasswordPage(ctx echo.Context) error {
|
||||||
|
return pages.ResetPassword(ctx, form.Get[forms.ResetPassword](ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Auth) ResetPasswordSubmit(ctx echo.Context) error {
|
||||||
|
var input forms.ResetPassword
|
||||||
|
|
||||||
|
err := form.Submit(ctx, &input)
|
||||||
|
|
||||||
|
switch err.(type) {
|
||||||
|
case nil:
|
||||||
|
case validator.ValidationErrors:
|
||||||
|
return h.ResetPasswordPage(ctx)
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the requesting user.
|
||||||
|
usr := ctx.Get(context.UserKey).(*ent.User)
|
||||||
|
|
||||||
|
// Update the user.
|
||||||
|
_, err = usr.
|
||||||
|
Update().
|
||||||
|
SetPassword(input.Password).
|
||||||
|
Save(ctx.Request().Context())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fail(err, "unable to update password")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete all password tokens for this user.
|
||||||
|
err = h.auth.DeletePasswordTokens(ctx, usr.ID)
|
||||||
|
if err != nil {
|
||||||
|
return fail(err, "unable to delete password tokens")
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Success(ctx, "Your password has been updated.")
|
||||||
|
return redirect.New(ctx).
|
||||||
|
Route(routenames.Login).
|
||||||
|
Go()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Auth) VerifyEmail(ctx echo.Context) error {
|
||||||
|
var usr *ent.User
|
||||||
|
|
||||||
|
// Validate the token.
|
||||||
|
token := ctx.Param("token")
|
||||||
|
email, err := h.auth.ValidateEmailVerificationToken(token)
|
||||||
|
if err != nil {
|
||||||
|
msg.Warning(ctx, "The link is either invalid or has expired.")
|
||||||
|
return redirect.New(ctx).
|
||||||
|
Route(routenames.Home).
|
||||||
|
Go()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it matches the authenticated user.
|
||||||
|
if u := ctx.Get(context.AuthenticatedUserKey); u != nil {
|
||||||
|
authUser := u.(*ent.User)
|
||||||
|
|
||||||
|
if authUser.Email == email {
|
||||||
|
usr = authUser
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query to find a matching user, if needed.
|
||||||
|
if usr == nil {
|
||||||
|
usr, err = h.orm.User.
|
||||||
|
Query().
|
||||||
|
Where(user.Email(email)).
|
||||||
|
Only(ctx.Request().Context())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fail(err, "query failed loading email verification token user")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the user, if needed.
|
||||||
|
if !usr.Verified {
|
||||||
|
usr, err = usr.
|
||||||
|
Update().
|
||||||
|
SetVerified(true).
|
||||||
|
Save(ctx.Request().Context())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fail(err, "failed to set user as verified")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Success(ctx, "Your email has been successfully verified.")
|
||||||
|
return redirect.New(ctx).
|
||||||
|
Route(routenames.Home).
|
||||||
|
Go()
|
||||||
|
}
|
||||||
76
pkg/handlers/cache.go
Normal file
76
pkg/handlers/cache.go
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/form"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/routenames"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/services"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/ui/forms"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/ui/pages"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Cache struct {
|
||||||
|
cache *services.CacheClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(new(Cache))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Cache) Init(c *services.Container) error {
|
||||||
|
h.cache = c.Cache
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Cache) Routes(g *echo.Group) {
|
||||||
|
g.GET("/cache", h.Page).Name = routenames.Cache
|
||||||
|
g.POST("/cache", h.Submit).Name = routenames.CacheSubmit
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Cache) Page(ctx echo.Context) error {
|
||||||
|
f := form.Get[forms.Cache](ctx)
|
||||||
|
|
||||||
|
// Fetch the value from the cache.
|
||||||
|
value, err := h.cache.
|
||||||
|
Get().
|
||||||
|
Key("page_cache_example").
|
||||||
|
Fetch(ctx.Request().Context())
|
||||||
|
|
||||||
|
// Store the value in the form, so it can be rendered, if found.
|
||||||
|
switch {
|
||||||
|
case err == nil:
|
||||||
|
f.CurrentValue = value.(string)
|
||||||
|
case errors.Is(err, services.ErrCacheMiss):
|
||||||
|
default:
|
||||||
|
return fail(err, "failed to fetch from cache")
|
||||||
|
}
|
||||||
|
|
||||||
|
return pages.UpdateCache(ctx, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Cache) Submit(ctx echo.Context) error {
|
||||||
|
var input forms.Cache
|
||||||
|
|
||||||
|
if err := form.Submit(ctx, &input); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the cache.
|
||||||
|
err := h.cache.
|
||||||
|
Set().
|
||||||
|
Key("page_cache_example").
|
||||||
|
Data(input.Value).
|
||||||
|
Expiration(30 * time.Minute).
|
||||||
|
Save(ctx.Request().Context())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fail(err, "unable to set cache")
|
||||||
|
}
|
||||||
|
|
||||||
|
form.Clear(ctx)
|
||||||
|
|
||||||
|
return h.Page(ctx)
|
||||||
|
}
|
||||||
62
pkg/handlers/contact.go
Normal file
62
pkg/handlers/contact.go
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/form"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/routenames"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/services"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/ui/forms"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/ui/pages"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Contact struct {
|
||||||
|
mail *services.MailClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(new(Contact))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Contact) Init(c *services.Container) error {
|
||||||
|
h.mail = c.Mail
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Contact) Routes(g *echo.Group) {
|
||||||
|
g.GET("/contact", h.Page).Name = routenames.Contact
|
||||||
|
g.POST("/contact", h.Submit).Name = routenames.ContactSubmit
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Contact) Page(ctx echo.Context) error {
|
||||||
|
return pages.ContactUs(ctx, form.Get[forms.Contact](ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Contact) Submit(ctx echo.Context) error {
|
||||||
|
var input forms.Contact
|
||||||
|
|
||||||
|
err := form.Submit(ctx, &input)
|
||||||
|
|
||||||
|
switch err.(type) {
|
||||||
|
case nil:
|
||||||
|
case validator.ValidationErrors:
|
||||||
|
return h.Page(ctx)
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.mail.
|
||||||
|
Compose().
|
||||||
|
To(input.Email).
|
||||||
|
Subject("Contact form submitted").
|
||||||
|
Body(fmt.Sprintf("The message is: %s", input.Message)).
|
||||||
|
Send(ctx)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fail(err, "unable to send email")
|
||||||
|
}
|
||||||
|
|
||||||
|
return h.Page(ctx)
|
||||||
|
}
|
||||||
43
pkg/handlers/error.go
Normal file
43
pkg/handlers/error.go
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/context"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/log"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/ui/pages"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Error struct{}
|
||||||
|
|
||||||
|
func (e *Error) Page(err error, ctx echo.Context) {
|
||||||
|
if ctx.Response().Committed || context.IsCanceledError(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the error status code.
|
||||||
|
code := http.StatusInternalServerError
|
||||||
|
if he, ok := err.(*echo.HTTPError); ok {
|
||||||
|
code = he.Code
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log the error.
|
||||||
|
logger := log.Ctx(ctx)
|
||||||
|
switch {
|
||||||
|
case code >= 500:
|
||||||
|
logger.Error(err.Error())
|
||||||
|
case code >= 400:
|
||||||
|
logger.Warn(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the status code.
|
||||||
|
ctx.Response().WriteHeader(code)
|
||||||
|
|
||||||
|
// Render the error page.
|
||||||
|
if err = pages.Error(ctx, code); err != nil {
|
||||||
|
log.Ctx(ctx).Error("failed to render error page",
|
||||||
|
"error", err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
80
pkg/handlers/files.go
Normal file
80
pkg/handlers/files.go
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/msg"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/routenames"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/services"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/ui/models"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/ui/pages"
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Files struct {
|
||||||
|
files afero.Fs
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(new(Files))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Files) Init(c *services.Container) error {
|
||||||
|
h.files = c.Files
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Files) Routes(g *echo.Group) {
|
||||||
|
g.GET("/files", h.Page).Name = routenames.Files
|
||||||
|
g.POST("/files", h.Submit).Name = routenames.FilesSubmit
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Files) Page(ctx echo.Context) error {
|
||||||
|
// Compile a list of all uploaded files to be rendered.
|
||||||
|
info, err := afero.ReadDir(h.files, "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
files := make([]*models.File, 0)
|
||||||
|
for _, file := range info {
|
||||||
|
files = append(files, &models.File{
|
||||||
|
Name: file.Name(),
|
||||||
|
Size: file.Size(),
|
||||||
|
Modified: file.ModTime().Format(time.DateTime),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return pages.UploadFile(ctx, files)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Files) Submit(ctx echo.Context) error {
|
||||||
|
file, err := ctx.FormFile("file")
|
||||||
|
if err != nil {
|
||||||
|
msg.Error(ctx, "A file is required.")
|
||||||
|
return h.Page(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
src, err := file.Open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer src.Close()
|
||||||
|
|
||||||
|
dst, err := h.files.Create(file.Filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer dst.Close()
|
||||||
|
|
||||||
|
if _, err = io.Copy(dst, src); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Success(ctx, fmt.Sprintf("%s was uploaded successfully.", file.Filename))
|
||||||
|
|
||||||
|
return h.Page(ctx)
|
||||||
|
}
|
||||||
36
pkg/handlers/handlers.go
Normal file
36
pkg/handlers/handlers.go
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/services"
|
||||||
|
)
|
||||||
|
|
||||||
|
var handlers []Handler
|
||||||
|
|
||||||
|
// Handler handles one or more HTTP routes
|
||||||
|
type Handler interface {
|
||||||
|
// Routes allows for self-registration of HTTP routes on the router
|
||||||
|
Routes(g *echo.Group)
|
||||||
|
|
||||||
|
// Init provides the service container to initialize
|
||||||
|
Init(*services.Container) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register registers a handler
|
||||||
|
func Register(h Handler) {
|
||||||
|
handlers = append(handlers, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHandlers returns all handlers
|
||||||
|
func GetHandlers() []Handler {
|
||||||
|
return handlers
|
||||||
|
}
|
||||||
|
|
||||||
|
// fail is a helper to fail a request by returning a 500 error and logging the error
|
||||||
|
func fail(err error, log string) error {
|
||||||
|
// The error handler will handle logging
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("%s: %v", log, err))
|
||||||
|
}
|
||||||
29
pkg/handlers/handlers_test.go
Normal file
29
pkg/handlers/handlers_test.go
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetSetHandlers(t *testing.T) {
|
||||||
|
handlers = []Handler{}
|
||||||
|
assert.Empty(t, GetHandlers())
|
||||||
|
h := new(Pages)
|
||||||
|
Register(h)
|
||||||
|
got := GetHandlers()
|
||||||
|
require.Len(t, got, 1)
|
||||||
|
assert.Equal(t, h, got[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFail(t *testing.T) {
|
||||||
|
err := fail(errors.New("err message"), "log message")
|
||||||
|
require.IsType(t, new(echo.HTTPError), err)
|
||||||
|
he := err.(*echo.HTTPError)
|
||||||
|
assert.Equal(t, http.StatusInternalServerError, he.Code)
|
||||||
|
assert.Equal(t, "log message: err message", he.Message)
|
||||||
|
}
|
||||||
55
pkg/handlers/pages.go
Normal file
55
pkg/handlers/pages.go
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/pager"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/routenames"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/services"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/ui/models"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/ui/pages"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Pages struct{}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(new(Pages))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Pages) Init(c *services.Container) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Pages) Routes(g *echo.Group) {
|
||||||
|
g.GET("/", h.Home).Name = routenames.Home
|
||||||
|
g.GET("/about", h.About).Name = routenames.About
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Pages) Home(ctx echo.Context) error {
|
||||||
|
pgr := pager.NewPager(ctx, 4)
|
||||||
|
|
||||||
|
return pages.Home(ctx, &models.Posts{
|
||||||
|
Posts: h.fetchPosts(&pgr),
|
||||||
|
Pager: pgr,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetchPosts is a mock example of fetching posts to illustrate how paging works.
|
||||||
|
func (h *Pages) fetchPosts(pager *pager.Pager) []models.Post {
|
||||||
|
pager.SetItems(20)
|
||||||
|
posts := make([]models.Post, 20)
|
||||||
|
|
||||||
|
for k := range posts {
|
||||||
|
posts[k] = models.Post{
|
||||||
|
ID: k + 1,
|
||||||
|
Title: fmt.Sprintf("Post example #%d", k+1),
|
||||||
|
Body: fmt.Sprintf("Lorem ipsum example #%d ddolor sit amet, consectetur adipiscing elit. Nam elementum vulputate tristique.", k+1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return posts[pager.GetOffset() : pager.GetOffset()+pager.ItemsPerPage]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Pages) About(ctx echo.Context) error {
|
||||||
|
return pages.About(ctx)
|
||||||
|
}
|
||||||
|
|
@ -1,23 +1,24 @@
|
||||||
package routes
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/camzawacki/personal-site/pkg/routenames"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Simple example of how to test routes and their markup using the test HTTP server spun up within
|
// Simple example of how to test routes and their markup using the test HTTP server spun up within
|
||||||
// this test package
|
// this test package
|
||||||
func TestAbout_Get(t *testing.T) {
|
func TestPages__About(t *testing.T) {
|
||||||
doc := request(t).
|
doc := request(t).
|
||||||
setRoute("about").
|
setRoute(routenames.About).
|
||||||
get().
|
get().
|
||||||
assertStatusCode(http.StatusOK).
|
assertStatusCode(http.StatusOK).
|
||||||
toDoc()
|
toDoc()
|
||||||
|
|
||||||
// Goquery is an excellent package to use for testing HTML markup
|
// Goquery is an excellent package to use for testing HTML markup
|
||||||
h1 := doc.Find("h1.title")
|
h1 := doc.Find("h1")
|
||||||
assert.Len(t, h1.Nodes, 1)
|
assert.Len(t, h1.Nodes, 1)
|
||||||
assert.Equal(t, "About", h1.Text())
|
assert.Equal(t, "About", h1.Text())
|
||||||
}
|
}
|
||||||
93
pkg/handlers/router.go
Normal file
93
pkg/handlers/router.go
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gorilla/sessions"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
echomw "github.com/labstack/echo/v4/middleware"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/context"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/middleware"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/services"
|
||||||
|
files "github.com/camzawacki/personal-site/public"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BuildRouter builds the router.
|
||||||
|
func BuildRouter(c *services.Container) error {
|
||||||
|
// Force HTTPS, if enabled.
|
||||||
|
if c.Config.HTTP.TLS.Enabled {
|
||||||
|
c.Web.Use(echomw.HTTPSRedirect())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serve public files with cache control.
|
||||||
|
c.Web.Group("", middleware.CacheControl(c.Config.Cache.Expiration.PublicFile)).
|
||||||
|
Static("files", "public/files")
|
||||||
|
|
||||||
|
// Serve static files.
|
||||||
|
// ui.StaticFile() should be used in ui components to append a cache key to the URL to break cache
|
||||||
|
// after each server reboot.
|
||||||
|
c.Web.Group(
|
||||||
|
"",
|
||||||
|
echomw.GzipWithConfig(echomw.GzipConfig{
|
||||||
|
Skipper: func(c echo.Context) bool {
|
||||||
|
for _, ext := range []string{
|
||||||
|
".js",
|
||||||
|
".css",
|
||||||
|
} {
|
||||||
|
if strings.HasSuffix(c.Request().URL.Path, ext) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
middleware.CacheControl(c.Config.Cache.Expiration.PublicFile),
|
||||||
|
).StaticFS("static", echo.MustSubFS(files.Static, "static"))
|
||||||
|
|
||||||
|
// Non-static file route group.
|
||||||
|
g := c.Web.Group("")
|
||||||
|
|
||||||
|
// Create a cookie store for session data.
|
||||||
|
cookieStore := sessions.NewCookieStore([]byte(c.Config.App.EncryptionKey))
|
||||||
|
cookieStore.Options.HttpOnly = true
|
||||||
|
cookieStore.Options.SameSite = http.SameSiteStrictMode
|
||||||
|
|
||||||
|
g.Use(
|
||||||
|
echomw.RemoveTrailingSlashWithConfig(echomw.TrailingSlashConfig{
|
||||||
|
RedirectCode: http.StatusMovedPermanently,
|
||||||
|
}),
|
||||||
|
echomw.Recover(),
|
||||||
|
echomw.Secure(),
|
||||||
|
echomw.RequestID(),
|
||||||
|
middleware.SetLogger(),
|
||||||
|
middleware.LogRequest(),
|
||||||
|
echomw.Gzip(),
|
||||||
|
echomw.TimeoutWithConfig(echomw.TimeoutConfig{
|
||||||
|
Timeout: c.Config.App.Timeout,
|
||||||
|
}),
|
||||||
|
middleware.Config(c.Config),
|
||||||
|
middleware.Session(cookieStore),
|
||||||
|
middleware.LoadAuthenticatedUser(c.Auth),
|
||||||
|
echomw.CSRFWithConfig(echomw.CSRFConfig{
|
||||||
|
TokenLookup: "form:csrf",
|
||||||
|
CookieHTTPOnly: true,
|
||||||
|
CookieSameSite: http.SameSiteStrictMode,
|
||||||
|
ContextKey: context.CSRFKey,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error handler.
|
||||||
|
c.Web.HTTPErrorHandler = new(Error).Page
|
||||||
|
|
||||||
|
// Initialize and register all handlers.
|
||||||
|
for _, h := range GetHandlers() {
|
||||||
|
if err := h.Init(c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
h.Routes(g)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
package routes
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/cookiejar"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"goweb/config"
|
"github.com/camzawacki/personal-site/config"
|
||||||
"goweb/services"
|
"github.com/camzawacki/personal-site/pkg/services"
|
||||||
|
|
||||||
"github.com/PuerkitoBio/goquery"
|
"github.com/PuerkitoBio/goquery"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
@ -27,19 +28,22 @@ func TestMain(m *testing.M) {
|
||||||
|
|
||||||
// Start a new container
|
// Start a new container
|
||||||
c = services.NewContainer()
|
c = services.NewContainer()
|
||||||
defer func() {
|
|
||||||
if err := c.Shutdown(); err != nil {
|
|
||||||
c.Web.Logger.Fatal(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Start a test HTTP server
|
// Start a test HTTP server
|
||||||
BuildRouter(c)
|
if err := BuildRouter(c); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
srv = httptest.NewServer(c.Web)
|
srv = httptest.NewServer(c.Web)
|
||||||
|
|
||||||
// Run tests
|
// Run tests
|
||||||
exitVal := m.Run()
|
exitVal := m.Run()
|
||||||
|
|
||||||
|
// Shutdown the container and test server
|
||||||
|
if err := c.Shutdown(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
srv.Close()
|
srv.Close()
|
||||||
|
|
||||||
os.Exit(exitVal)
|
os.Exit(exitVal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,8 +55,14 @@ type httpRequest struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func request(t *testing.T) *httpRequest {
|
func request(t *testing.T) *httpRequest {
|
||||||
|
jar, err := cookiejar.New(nil)
|
||||||
|
require.NoError(t, err)
|
||||||
r := httpRequest{
|
r := httpRequest{
|
||||||
t: t,
|
t: t,
|
||||||
|
body: url.Values{},
|
||||||
|
client: http.Client{
|
||||||
|
Jar: jar,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
return &r
|
return &r
|
||||||
}
|
}
|
||||||
|
|
@ -62,7 +72,7 @@ func (h *httpRequest) setClient(client http.Client) *httpRequest {
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpRequest) setRoute(route string, params ...interface{}) *httpRequest {
|
func (h *httpRequest) setRoute(route string, params ...any) *httpRequest {
|
||||||
h.route = srv.URL + c.Web.Reverse(route, params)
|
h.route = srv.URL + c.Web.Reverse(route, params)
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
@ -83,6 +93,18 @@ func (h *httpRequest) get() *httpResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpRequest) post() *httpResponse {
|
func (h *httpRequest) post() *httpResponse {
|
||||||
|
// Make a get request to get the CSRF token
|
||||||
|
doc := h.get().
|
||||||
|
assertStatusCode(http.StatusOK).
|
||||||
|
toDoc()
|
||||||
|
|
||||||
|
// Extract the CSRF and include it in the POST request body
|
||||||
|
csrf := doc.Find(`input[name="csrf"]`).First()
|
||||||
|
token, exists := csrf.Attr("value")
|
||||||
|
assert.True(h.t, exists)
|
||||||
|
h.body["csrf"] = []string{token}
|
||||||
|
|
||||||
|
// Make the POST requests
|
||||||
resp, err := h.client.PostForm(h.route, h.body)
|
resp, err := h.client.PostForm(h.route, h.body)
|
||||||
require.NoError(h.t, err)
|
require.NoError(h.t, err)
|
||||||
r := httpResponse{
|
r := httpResponse{
|
||||||
|
|
@ -102,7 +124,7 @@ func (h *httpResponse) assertStatusCode(code int) *httpResponse {
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpResponse) assertRedirect(t *testing.T, route string, params ...interface{}) *httpResponse {
|
func (h *httpResponse) assertRedirect(t *testing.T, route string, params ...any) *httpResponse {
|
||||||
assert.Equal(t, c.Web.Reverse(route, params), h.Header.Get("Location"))
|
assert.Equal(t, c.Web.Reverse(route, params), h.Header.Get("Location"))
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
44
pkg/handlers/search.go
Normal file
44
pkg/handlers/search.go
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/routenames"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/services"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/ui/models"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/ui/pages"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Search struct{}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(new(Search))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Search) Init(c *services.Container) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Search) Routes(g *echo.Group) {
|
||||||
|
g.GET("/search", h.Page).Name = routenames.Search
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Search) Page(ctx echo.Context) error {
|
||||||
|
// Fake search results.
|
||||||
|
results := make([]*models.SearchResult, 0, 5)
|
||||||
|
if search := ctx.QueryParam("query"); search != "" {
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
title := "Lorem ipsum example ddolor sit amet"
|
||||||
|
index := rand.Intn(len(title))
|
||||||
|
title = title[:index] + search + title[index:]
|
||||||
|
results = append(results, &models.SearchResult{
|
||||||
|
Title: title,
|
||||||
|
URL: fmt.Sprintf("https://www.%s.com", search),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pages.SearchResults(ctx, results)
|
||||||
|
}
|
||||||
71
pkg/handlers/task.go
Normal file
71
pkg/handlers/task.go
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mikestefanello/backlite"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/msg"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/routenames"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/ui/forms"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/ui/pages"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/form"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/services"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/tasks"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Task struct {
|
||||||
|
tasks *backlite.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(new(Task))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Task) Init(c *services.Container) error {
|
||||||
|
h.tasks = c.Tasks
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Task) Routes(g *echo.Group) {
|
||||||
|
g.GET("/task", h.Page).Name = routenames.Task
|
||||||
|
g.POST("/task", h.Submit).Name = routenames.TaskSubmit
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Task) Page(ctx echo.Context) error {
|
||||||
|
return pages.AddTask(ctx, form.Get[forms.Task](ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Task) Submit(ctx echo.Context) error {
|
||||||
|
var input forms.Task
|
||||||
|
|
||||||
|
err := form.Submit(ctx, &input)
|
||||||
|
|
||||||
|
switch err.(type) {
|
||||||
|
case nil:
|
||||||
|
case validator.ValidationErrors:
|
||||||
|
return h.Page(ctx)
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert the task
|
||||||
|
_, err = h.tasks.
|
||||||
|
Add(tasks.ExampleTask{
|
||||||
|
Message: input.Message,
|
||||||
|
}).
|
||||||
|
Wait(time.Duration(input.Delay) * time.Second).
|
||||||
|
Save()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fail(err, "unable to create a task")
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Success(ctx, fmt.Sprintf("The task has been created. Check the logs in %d seconds.", input.Delay))
|
||||||
|
form.Clear(ctx)
|
||||||
|
|
||||||
|
return h.Page(ctx)
|
||||||
|
}
|
||||||
97
pkg/htmx/htmx.go
Normal file
97
pkg/htmx/htmx.go
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
package htmx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Request headers: https://htmx.org/docs/#request-headers
|
||||||
|
const (
|
||||||
|
HeaderBoosted = "HX-Boosted"
|
||||||
|
HeaderHistoryRestoreRequest = "HX-History-Restore-Request"
|
||||||
|
HeaderPrompt = "HX-Prompt"
|
||||||
|
HeaderRequest = "HX-Request"
|
||||||
|
HeaderTarget = "HX-Target"
|
||||||
|
HeaderTrigger = "HX-Trigger"
|
||||||
|
HeaderTriggerName = "HX-Trigger-Name"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Response headers: https://htmx.org/docs/#response-headers
|
||||||
|
const (
|
||||||
|
HeaderPushURL = "HX-Push-Url"
|
||||||
|
HeaderRedirect = "HX-Redirect"
|
||||||
|
HeaderReplaceURL = "HX-Replace-Url"
|
||||||
|
HeaderRefresh = "HX-Refresh"
|
||||||
|
HeaderTriggerAfterSettle = "HX-Trigger-After-Settle"
|
||||||
|
HeaderTriggerAfterSwap = "HX-Trigger-After-Swap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Request contains data that HTMX provides during requests.
|
||||||
|
Request struct {
|
||||||
|
Enabled bool
|
||||||
|
Boosted bool
|
||||||
|
HistoryRestore bool
|
||||||
|
Trigger string
|
||||||
|
TriggerName string
|
||||||
|
Target string
|
||||||
|
Prompt string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response contain data that the server can communicate back to HTMX.
|
||||||
|
Response struct {
|
||||||
|
PushURL string
|
||||||
|
Redirect string
|
||||||
|
Refresh bool
|
||||||
|
ReplaceURL string
|
||||||
|
Trigger string
|
||||||
|
TriggerAfterSwap string
|
||||||
|
TriggerAfterSettle string
|
||||||
|
NoContent bool
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetRequest extracts HTMX data from the request,
|
||||||
|
func GetRequest(ctx echo.Context) *Request {
|
||||||
|
return context.Cache(ctx, context.HTMXRequestKey, func(ctx echo.Context) *Request {
|
||||||
|
return &Request{
|
||||||
|
Enabled: ctx.Request().Header.Get(HeaderRequest) == "true",
|
||||||
|
Boosted: ctx.Request().Header.Get(HeaderBoosted) == "true",
|
||||||
|
Trigger: ctx.Request().Header.Get(HeaderTrigger),
|
||||||
|
TriggerName: ctx.Request().Header.Get(HeaderTriggerName),
|
||||||
|
Target: ctx.Request().Header.Get(HeaderTarget),
|
||||||
|
Prompt: ctx.Request().Header.Get(HeaderPrompt),
|
||||||
|
HistoryRestore: ctx.Request().Header.Get(HeaderHistoryRestoreRequest) == "true",
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply applies data from a Response to a server response.
|
||||||
|
func (r Response) Apply(ctx echo.Context) {
|
||||||
|
if r.PushURL != "" {
|
||||||
|
ctx.Response().Header().Set(HeaderPushURL, r.PushURL)
|
||||||
|
}
|
||||||
|
if r.Redirect != "" {
|
||||||
|
ctx.Response().Header().Set(HeaderRedirect, r.Redirect)
|
||||||
|
}
|
||||||
|
if r.Refresh {
|
||||||
|
ctx.Response().Header().Set(HeaderRefresh, "true")
|
||||||
|
}
|
||||||
|
if r.Trigger != "" {
|
||||||
|
ctx.Response().Header().Set(HeaderTrigger, r.Trigger)
|
||||||
|
}
|
||||||
|
if r.TriggerAfterSwap != "" {
|
||||||
|
ctx.Response().Header().Set(HeaderTriggerAfterSwap, r.TriggerAfterSwap)
|
||||||
|
}
|
||||||
|
if r.TriggerAfterSettle != "" {
|
||||||
|
ctx.Response().Header().Set(HeaderTriggerAfterSettle, r.TriggerAfterSettle)
|
||||||
|
}
|
||||||
|
if r.ReplaceURL != "" {
|
||||||
|
ctx.Response().Header().Set(HeaderReplaceURL, r.ReplaceURL)
|
||||||
|
}
|
||||||
|
if r.NoContent {
|
||||||
|
ctx.Response().Status = http.StatusNoContent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,8 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"goweb/tests"
|
"github.com/camzawacki/personal-site/pkg/context"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/tests"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
|
@ -19,21 +20,29 @@ func TestSetRequest(t *testing.T) {
|
||||||
ctx.Request().Header.Set(HeaderTriggerName, "b")
|
ctx.Request().Header.Set(HeaderTriggerName, "b")
|
||||||
ctx.Request().Header.Set(HeaderTarget, "c")
|
ctx.Request().Header.Set(HeaderTarget, "c")
|
||||||
ctx.Request().Header.Set(HeaderPrompt, "d")
|
ctx.Request().Header.Set(HeaderPrompt, "d")
|
||||||
|
ctx.Request().Header.Set(HeaderHistoryRestoreRequest, "true")
|
||||||
|
|
||||||
r := GetRequest(ctx)
|
r := GetRequest(ctx)
|
||||||
assert.Equal(t, true, r.Enabled)
|
assert.Equal(t, true, r.Enabled)
|
||||||
assert.Equal(t, true, r.Boosted)
|
assert.Equal(t, true, r.Boosted)
|
||||||
|
assert.Equal(t, true, r.HistoryRestore)
|
||||||
assert.Equal(t, "a", r.Trigger)
|
assert.Equal(t, "a", r.Trigger)
|
||||||
assert.Equal(t, "b", r.TriggerName)
|
assert.Equal(t, "b", r.TriggerName)
|
||||||
assert.Equal(t, "c", r.Target)
|
assert.Equal(t, "c", r.Target)
|
||||||
assert.Equal(t, "d", r.Prompt)
|
assert.Equal(t, "d", r.Prompt)
|
||||||
|
|
||||||
|
cached := context.Cache(ctx, context.HTMXRequestKey, func(ctx echo.Context) *Request {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
assert.Equal(t, r, cached)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResponse_Apply(t *testing.T) {
|
func TestResponse_Apply(t *testing.T) {
|
||||||
ctx, _ := tests.NewContext(echo.New(), "/")
|
ctx, _ := tests.NewContext(echo.New(), "/")
|
||||||
r := Response{
|
r := Response{
|
||||||
Push: "a",
|
PushURL: "a",
|
||||||
Redirect: "b",
|
Redirect: "b",
|
||||||
|
ReplaceURL: "f",
|
||||||
Refresh: true,
|
Refresh: true,
|
||||||
Trigger: "c",
|
Trigger: "c",
|
||||||
TriggerAfterSwap: "d",
|
TriggerAfterSwap: "d",
|
||||||
|
|
@ -42,11 +51,12 @@ func TestResponse_Apply(t *testing.T) {
|
||||||
}
|
}
|
||||||
r.Apply(ctx)
|
r.Apply(ctx)
|
||||||
|
|
||||||
assert.Equal(t, "a", ctx.Response().Header().Get(HeaderPush))
|
assert.Equal(t, "a", ctx.Response().Header().Get(HeaderPushURL))
|
||||||
assert.Equal(t, "b", ctx.Response().Header().Get(HeaderRedirect))
|
assert.Equal(t, "b", ctx.Response().Header().Get(HeaderRedirect))
|
||||||
assert.Equal(t, "true", ctx.Response().Header().Get(HeaderRefresh))
|
assert.Equal(t, "true", ctx.Response().Header().Get(HeaderRefresh))
|
||||||
assert.Equal(t, "c", ctx.Response().Header().Get(HeaderTrigger))
|
assert.Equal(t, "c", ctx.Response().Header().Get(HeaderTrigger))
|
||||||
assert.Equal(t, "d", ctx.Response().Header().Get(HeaderTriggerAfterSwap))
|
assert.Equal(t, "d", ctx.Response().Header().Get(HeaderTriggerAfterSwap))
|
||||||
assert.Equal(t, "e", ctx.Response().Header().Get(HeaderTriggerAfterSettle))
|
assert.Equal(t, "e", ctx.Response().Header().Get(HeaderTriggerAfterSettle))
|
||||||
|
assert.Equal(t, "f", ctx.Response().Header().Get(HeaderReplaceURL))
|
||||||
assert.Equal(t, http.StatusNoContent, ctx.Response().Status)
|
assert.Equal(t, http.StatusNoContent, ctx.Response().Status)
|
||||||
}
|
}
|
||||||
27
pkg/log/log.go
Normal file
27
pkg/log/log.go
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Set sets a logger in the context.
|
||||||
|
func Set(ctx echo.Context, logger *slog.Logger) {
|
||||||
|
ctx.Set(context.LoggerKey, logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ctx returns the logger stored in context, or provides the default logger if one is not present.
|
||||||
|
func Ctx(ctx echo.Context) *slog.Logger {
|
||||||
|
if l, ok := ctx.Get(context.LoggerKey).(*slog.Logger); ok {
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
return Default()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default returns the default logger.
|
||||||
|
func Default() *slog.Logger {
|
||||||
|
return slog.Default()
|
||||||
|
}
|
||||||
21
pkg/log/log_test.go
Normal file
21
pkg/log/log_test.go
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/tests"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCtxSet(t *testing.T) {
|
||||||
|
ctx, _ := tests.NewContext(echo.New(), "/")
|
||||||
|
logger := Ctx(ctx)
|
||||||
|
assert.NotNil(t, logger)
|
||||||
|
|
||||||
|
logger = logger.With("a", "b")
|
||||||
|
Set(ctx, logger)
|
||||||
|
|
||||||
|
got := Ctx(ctx)
|
||||||
|
assert.Equal(t, got, logger)
|
||||||
|
}
|
||||||
120
pkg/middleware/auth.go
Normal file
120
pkg/middleware/auth.go
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/camzawacki/personal-site/ent"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/context"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/log"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/msg"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/routenames"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/services"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LoadAuthenticatedUser loads the authenticated user, if one, and stores in context.
|
||||||
|
func LoadAuthenticatedUser(authClient *services.AuthClient) echo.MiddlewareFunc {
|
||||||
|
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
u, err := authClient.GetAuthenticatedUser(c)
|
||||||
|
switch err.(type) {
|
||||||
|
case *ent.NotFoundError:
|
||||||
|
log.Ctx(c).Warn("auth user not found")
|
||||||
|
case services.NotAuthenticatedError:
|
||||||
|
case nil:
|
||||||
|
c.Set(context.AuthenticatedUserKey, u)
|
||||||
|
default:
|
||||||
|
return echo.NewHTTPError(
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
fmt.Sprintf("error querying for authenticated user: %v", err),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadValidPasswordToken loads a valid password token entity that matches the user and token
|
||||||
|
// provided in path parameters
|
||||||
|
// If the token is invalid, the user will be redirected to the forgot password route
|
||||||
|
// This requires that the user owning the token is loaded in to context.
|
||||||
|
func LoadValidPasswordToken(authClient *services.AuthClient) echo.MiddlewareFunc {
|
||||||
|
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
// Extract the user parameter
|
||||||
|
if c.Get(context.UserKey) == nil {
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
usr := c.Get(context.UserKey).(*ent.User)
|
||||||
|
|
||||||
|
// Extract the token ID.
|
||||||
|
tokenID, err := strconv.Atoi(c.Param("password_token"))
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to load a valid password token.
|
||||||
|
token, err := authClient.GetValidPasswordToken(
|
||||||
|
c,
|
||||||
|
usr.ID,
|
||||||
|
tokenID,
|
||||||
|
c.Param("token"),
|
||||||
|
)
|
||||||
|
|
||||||
|
switch err.(type) {
|
||||||
|
case nil:
|
||||||
|
c.Set(context.PasswordTokenKey, token)
|
||||||
|
return next(c)
|
||||||
|
case services.InvalidPasswordTokenError:
|
||||||
|
msg.Warning(c, "The link is either invalid or has expired. Please request a new one.")
|
||||||
|
return c.Redirect(http.StatusFound, c.Echo().Reverse(routenames.ForgotPassword))
|
||||||
|
default:
|
||||||
|
return echo.NewHTTPError(
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
fmt.Sprintf("error loading password token: %v", err),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireAuthentication requires that the user be authenticated in order to proceed.
|
||||||
|
func RequireAuthentication(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
if u := c.Get(context.AuthenticatedUserKey); u == nil {
|
||||||
|
return echo.NewHTTPError(http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireNoAuthentication requires that the user not be authenticated in order to proceed.
|
||||||
|
func RequireNoAuthentication(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
if u := c.Get(context.AuthenticatedUserKey); u != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusForbidden)
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireAdmin requires that the authenticated user be an admin in order to proceed.
|
||||||
|
func RequireAdmin(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
if u := c.Get(context.AuthenticatedUserKey); u != nil {
|
||||||
|
if user, ok := u.(*ent.User); ok {
|
||||||
|
if user.Admin {
|
||||||
|
return next(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return echo.NewHTTPError(http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
goctx "context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"goweb/context"
|
"github.com/camzawacki/personal-site/ent"
|
||||||
"goweb/ent"
|
"github.com/camzawacki/personal-site/pkg/context"
|
||||||
"goweb/tests"
|
"github.com/camzawacki/personal-site/pkg/tests"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
|
@ -40,7 +41,7 @@ func TestRequireAuthentication(t *testing.T) {
|
||||||
tests.InitSession(ctx)
|
tests.InitSession(ctx)
|
||||||
|
|
||||||
// Not logged in
|
// Not logged in
|
||||||
err := tests.ExecuteMiddleware(ctx, RequireAuthentication())
|
err := tests.ExecuteMiddleware(ctx, RequireAuthentication)
|
||||||
tests.AssertHTTPErrorCode(t, err, http.StatusUnauthorized)
|
tests.AssertHTTPErrorCode(t, err, http.StatusUnauthorized)
|
||||||
|
|
||||||
// Login
|
// Login
|
||||||
|
|
@ -49,7 +50,7 @@ func TestRequireAuthentication(t *testing.T) {
|
||||||
_ = tests.ExecuteMiddleware(ctx, LoadAuthenticatedUser(c.Auth))
|
_ = tests.ExecuteMiddleware(ctx, LoadAuthenticatedUser(c.Auth))
|
||||||
|
|
||||||
// Logged in
|
// Logged in
|
||||||
err = tests.ExecuteMiddleware(ctx, RequireAuthentication())
|
err = tests.ExecuteMiddleware(ctx, RequireAuthentication)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,7 +59,7 @@ func TestRequireNoAuthentication(t *testing.T) {
|
||||||
tests.InitSession(ctx)
|
tests.InitSession(ctx)
|
||||||
|
|
||||||
// Not logged in
|
// Not logged in
|
||||||
err := tests.ExecuteMiddleware(ctx, RequireNoAuthentication())
|
err := tests.ExecuteMiddleware(ctx, RequireNoAuthentication)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
// Login
|
// Login
|
||||||
|
|
@ -67,7 +68,7 @@ func TestRequireNoAuthentication(t *testing.T) {
|
||||||
_ = tests.ExecuteMiddleware(ctx, LoadAuthenticatedUser(c.Auth))
|
_ = tests.ExecuteMiddleware(ctx, LoadAuthenticatedUser(c.Auth))
|
||||||
|
|
||||||
// Logged in
|
// Logged in
|
||||||
err = tests.ExecuteMiddleware(ctx, RequireNoAuthentication())
|
err = tests.ExecuteMiddleware(ctx, RequireNoAuthentication)
|
||||||
tests.AssertHTTPErrorCode(t, err, http.StatusForbidden)
|
tests.AssertHTTPErrorCode(t, err, http.StatusForbidden)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,17 +80,17 @@ func TestLoadValidPasswordToken(t *testing.T) {
|
||||||
err := tests.ExecuteMiddleware(ctx, LoadValidPasswordToken(c.Auth))
|
err := tests.ExecuteMiddleware(ctx, LoadValidPasswordToken(c.Auth))
|
||||||
tests.AssertHTTPErrorCode(t, err, http.StatusInternalServerError)
|
tests.AssertHTTPErrorCode(t, err, http.StatusInternalServerError)
|
||||||
|
|
||||||
// Add user context but no password token and expect a redirect
|
// Add user and password token context but no token and expect a redirect
|
||||||
ctx.SetParamNames("user")
|
ctx.SetParamNames("user", "password_token")
|
||||||
ctx.SetParamValues(fmt.Sprintf("%d", usr.ID))
|
ctx.SetParamValues(fmt.Sprintf("%d", usr.ID), "1")
|
||||||
_ = tests.ExecuteMiddleware(ctx, LoadUser(c.ORM))
|
_ = tests.ExecuteMiddleware(ctx, LoadUser(c.ORM))
|
||||||
err = tests.ExecuteMiddleware(ctx, LoadValidPasswordToken(c.Auth))
|
err = tests.ExecuteMiddleware(ctx, LoadValidPasswordToken(c.Auth))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, http.StatusFound, ctx.Response().Status)
|
assert.Equal(t, http.StatusFound, ctx.Response().Status)
|
||||||
|
|
||||||
// Add user context and invalid password token and expect a redirect
|
// Add user context and invalid password token and expect a redirect
|
||||||
ctx.SetParamNames("user", "password_token")
|
ctx.SetParamNames("user", "password_token", "token")
|
||||||
ctx.SetParamValues(fmt.Sprintf("%d", usr.ID), "faketoken")
|
ctx.SetParamValues(fmt.Sprintf("%d", usr.ID), "1", "faketoken")
|
||||||
_ = tests.ExecuteMiddleware(ctx, LoadUser(c.ORM))
|
_ = tests.ExecuteMiddleware(ctx, LoadUser(c.ORM))
|
||||||
err = tests.ExecuteMiddleware(ctx, LoadValidPasswordToken(c.Auth))
|
err = tests.ExecuteMiddleware(ctx, LoadValidPasswordToken(c.Auth))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
@ -100,8 +101,8 @@ func TestLoadValidPasswordToken(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Add user and valid password token
|
// Add user and valid password token
|
||||||
ctx.SetParamNames("user", "password_token")
|
ctx.SetParamNames("user", "password_token", "token")
|
||||||
ctx.SetParamValues(fmt.Sprintf("%d", usr.ID), token)
|
ctx.SetParamValues(fmt.Sprintf("%d", usr.ID), fmt.Sprintf("%d", pt.ID), token)
|
||||||
_ = tests.ExecuteMiddleware(ctx, LoadUser(c.ORM))
|
_ = tests.ExecuteMiddleware(ctx, LoadUser(c.ORM))
|
||||||
err = tests.ExecuteMiddleware(ctx, LoadValidPasswordToken(c.Auth))
|
err = tests.ExecuteMiddleware(ctx, LoadValidPasswordToken(c.Auth))
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
@ -109,3 +110,36 @@ func TestLoadValidPasswordToken(t *testing.T) {
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
assert.Equal(t, pt.ID, ctxPt.ID)
|
assert.Equal(t, pt.ID, ctxPt.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRequireAdmin(t *testing.T) {
|
||||||
|
ctx, _ := tests.NewContext(c.Web, "/")
|
||||||
|
tests.InitSession(ctx)
|
||||||
|
|
||||||
|
// Not logged in
|
||||||
|
err := tests.ExecuteMiddleware(ctx, RequireAdmin)
|
||||||
|
tests.AssertHTTPErrorCode(t, err, http.StatusUnauthorized)
|
||||||
|
|
||||||
|
// Login as a non-admin
|
||||||
|
err = c.Auth.Login(ctx, usr.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
_ = tests.ExecuteMiddleware(ctx, LoadAuthenticatedUser(c.Auth))
|
||||||
|
|
||||||
|
// Logged in as a non-admin
|
||||||
|
err = tests.ExecuteMiddleware(ctx, RequireAdmin)
|
||||||
|
tests.AssertHTTPErrorCode(t, err, http.StatusUnauthorized)
|
||||||
|
|
||||||
|
// Create an admin and login
|
||||||
|
adm, err := tests.CreateUser(c.ORM)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = c.ORM.User.Update().
|
||||||
|
SetAdmin(true).
|
||||||
|
Exec(goctx.Background())
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = c.Auth.Login(ctx, adm.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
_ = tests.ExecuteMiddleware(ctx, LoadAuthenticatedUser(c.Auth))
|
||||||
|
|
||||||
|
// Logged in as an admin
|
||||||
|
err = tests.ExecuteMiddleware(ctx, RequireAdmin)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
22
pkg/middleware/cache.go
Normal file
22
pkg/middleware/cache.go
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CacheControl sets a Cache-Control header with a given max age.
|
||||||
|
func CacheControl(maxAge time.Duration) echo.MiddlewareFunc {
|
||||||
|
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
|
return func(ctx echo.Context) error {
|
||||||
|
v := "no-cache, no-store"
|
||||||
|
if maxAge > 0 {
|
||||||
|
v = fmt.Sprintf("public, max-age=%.0f", maxAge.Seconds())
|
||||||
|
}
|
||||||
|
ctx.Response().Header().Set("Cache-Control", v)
|
||||||
|
return next(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
pkg/middleware/cache_test.go
Normal file
18
pkg/middleware/cache_test.go
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/camzawacki/personal-site/pkg/tests"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCacheControl(t *testing.T) {
|
||||||
|
ctx, _ := tests.NewContext(c.Web, "/")
|
||||||
|
_ = tests.ExecuteMiddleware(ctx, CacheControl(time.Second*5))
|
||||||
|
assert.Equal(t, "public, max-age=5", ctx.Response().Header().Get("Cache-Control"))
|
||||||
|
_ = tests.ExecuteMiddleware(ctx, CacheControl(0))
|
||||||
|
assert.Equal(t, "no-cache, no-store", ctx.Response().Header().Get("Cache-Control"))
|
||||||
|
}
|
||||||
17
pkg/middleware/config.go
Normal file
17
pkg/middleware/config.go
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/camzawacki/personal-site/config"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config stores the configuration in the request so it can be accessed by the ui.
|
||||||
|
func Config(cfg *config.Config) echo.MiddlewareFunc {
|
||||||
|
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
|
return func(ctx echo.Context) error {
|
||||||
|
ctx.Set(context.ConfigKey, cfg)
|
||||||
|
return next(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
pkg/middleware/config_test.go
Normal file
22
pkg/middleware/config_test.go
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/camzawacki/personal-site/config"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/context"
|
||||||
|
"github.com/camzawacki/personal-site/pkg/tests"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfig(t *testing.T) {
|
||||||
|
ctx, _ := tests.NewContext(c.Web, "/")
|
||||||
|
cfg := &config.Config{}
|
||||||
|
err := tests.ExecuteMiddleware(ctx, Config(cfg))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
got, ok := ctx.Get(context.ConfigKey).(*config.Config)
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.Same(t, got, cfg)
|
||||||
|
}
|
||||||
|
|
@ -1,17 +1,18 @@
|
||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"goweb/context"
|
"github.com/camzawacki/personal-site/ent"
|
||||||
"goweb/ent"
|
"github.com/camzawacki/personal-site/ent/user"
|
||||||
"goweb/ent/user"
|
"github.com/camzawacki/personal-site/pkg/context"
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LoadUser loads the user based on the ID provided as a path parameter
|
// LoadUser loads the user based on the ID provided as a path parameter.
|
||||||
func LoadUser(orm *ent.Client) echo.MiddlewareFunc {
|
func LoadUser(orm *ent.Client) echo.MiddlewareFunc {
|
||||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c echo.Context) error {
|
||||||
|
|
@ -32,8 +33,10 @@ func LoadUser(orm *ent.Client) echo.MiddlewareFunc {
|
||||||
case *ent.NotFoundError:
|
case *ent.NotFoundError:
|
||||||
return echo.NewHTTPError(http.StatusNotFound)
|
return echo.NewHTTPError(http.StatusNotFound)
|
||||||
default:
|
default:
|
||||||
c.Logger().Error(err)
|
return echo.NewHTTPError(
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
http.StatusInternalServerError,
|
||||||
|
fmt.Sprintf("error querying user: %v", err),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,9 +4,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"goweb/context"
|
"github.com/camzawacki/personal-site/ent"
|
||||||
"goweb/ent"
|
"github.com/camzawacki/personal-site/pkg/context"
|
||||||
"goweb/tests"
|
"github.com/camzawacki/personal-site/pkg/tests"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue