├── .dockerignore ├── .github └── workflows │ └── docker.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README-en.md ├── README.md ├── cmd └── main.go ├── config └── config.go ├── deploy.md ├── doc ├── email-routing.webp ├── workers-create.webp ├── workers-edit.webp └── workers.js ├── ent ├── attachment.go ├── attachment │ ├── attachment.go │ └── where.go ├── attachment_create.go ├── attachment_delete.go ├── attachment_query.go ├── attachment_update.go ├── client.go ├── ent.go ├── enttest │ └── enttest.go ├── envelope.go ├── envelope │ ├── envelope.go │ └── where.go ├── envelope_create.go ├── envelope_delete.go ├── envelope_query.go ├── envelope_update.go ├── generate.go ├── hook │ └── hook.go ├── migrate │ ├── migrate.go │ └── schema.go ├── mutation.go ├── predicate │ └── predicate.go ├── runtime.go ├── runtime │ └── runtime.go ├── schema │ ├── attachment.go │ └── envelope.go └── tx.go ├── go.mod ├── go.sum ├── internal ├── api │ ├── context.go │ ├── domain.go │ ├── fetch.go │ └── report.go ├── app.go ├── constant │ └── constant.go ├── pubsub │ └── pubsub.go ├── route │ └── route.go ├── schedule │ └── schedule.go └── utils │ ├── utils.go │ └── utils_test.go └── web ├── .gitignore ├── .prettierignore ├── .prettierrc.mjs ├── astro.config.mjs ├── bun.lock ├── components.json ├── package.json ├── public ├── apple-touch-icon.png └── favicon.svg ├── src ├── components │ ├── Actions.tsx │ ├── Content.tsx │ ├── Detail.tsx │ ├── EditAddress.tsx │ ├── Header.astro │ ├── History.tsx │ ├── Mounted.tsx │ ├── ThemeIcon.astro │ ├── ToGithub.tsx │ ├── Umami.astro │ └── ui │ │ ├── alert-dialog.tsx │ │ ├── button.tsx │ │ ├── input.tsx │ │ ├── select.tsx │ │ ├── skeleton.tsx │ │ └── sonner.tsx ├── i18n │ └── ui.ts ├── layouts │ └── Layout.astro ├── lib │ ├── constant.ts │ ├── store │ │ └── store.ts │ ├── types.ts │ └── utils.ts ├── pages │ ├── [lang] │ │ └── index.astro │ └── index.astro └── style │ └── global.css ├── tsconfig.json └── web.go /.dockerignore: -------------------------------------------------------------------------------- 1 | .* 2 | dist 3 | node_modules 4 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Build Docker Images 2 | 3 | on: 4 | workflow_dispatch: 5 | release: 6 | types: [published] 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | - name: Set up QEMU 15 | uses: docker/setup-qemu-action@v3 16 | with: 17 | platforms: amd64,arm64 18 | - name: Set up Docker Buildx 19 | uses: docker/setup-buildx-action@v3 20 | with: 21 | platforms: linux/amd64,linux/arm64 22 | - name: Login to Docker Hub 23 | uses: docker/login-action@v3 24 | with: 25 | username: ${{ secrets.DOCKERHUB_USERNAME }} 26 | password: ${{ secrets.DOCKERHUB_TOKEN }} 27 | - name: Docker meta 28 | id: meta 29 | uses: docker/metadata-action@v5 30 | with: 31 | images: sunls24/tmail 32 | tags: | 33 | type=raw,value=latest 34 | type=ref,event=tag 35 | - name: Build and push 36 | uses: docker/build-push-action@v6 37 | with: 38 | context: . 39 | push: true 40 | platforms: linux/amd64,linux/arm64 41 | tags: ${{ steps.meta.outputs.tags }} 42 | labels: ${{ steps.meta.outputs.labels }} 43 | cache-from: type=gha 44 | cache-to: type=gha,mode=max 45 | build-args: | 46 | UMAMI_ID=${{ vars.UMAMI_ID }} 47 | UMAMI_URL=${{ vars.UMAMI_URL }} 48 | UMAMI_DOMAINS=${{ vars.UMAMI_DOMAINS }} 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | .env 4 | tmail -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM oven/bun:alpine AS bun-builder 2 | WORKDIR /app 3 | ARG UMAMI_ID 4 | ARG UMAMI_URL 5 | ARG UMAMI_DOMAINS 6 | 7 | COPY ./web/package.json ./web/bun.lock ./ 8 | RUN bun install --frozen-lockfile 9 | COPY ./web . 10 | RUN bunx astro telemetry disable && bun run build 11 | 12 | FROM golang:1.24-alpine AS builder 13 | WORKDIR /app 14 | COPY go.mod go.sum ./ 15 | RUN go mod download 16 | COPY . . 17 | COPY --from=bun-builder /app/dist ./web/dist/ 18 | RUN CGO_ENABLED=0 go build -ldflags '-s -w' -o tmail cmd/main.go 19 | 20 | FROM alpine AS runner 21 | WORKDIR /app 22 | COPY --from=builder /app/tmail . 23 | 24 | ENV HOST=127.0.0.1 25 | ENV PORT=3000 26 | EXPOSE 3000 27 | CMD ["/app/tmail"] 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 sunls24 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README-en.md: -------------------------------------------------------------------------------- 1 | ## 📮 Temporary Mail 2 | 3 | **(V2 version under reconstruction)** 4 | 5 | Using Golang + Astro + React + Long Polling to get emails in real-time 6 | 7 | - [x] Email CSS style isolation 8 | - [x] Real-time email retrieval using long polling 9 | - [x] Dark theme and language switching 10 | - [x] Support for viewing and downloading attachments 11 | 12 | Anonymous disposable email to protect your personal email address from spam. 13 | 14 | ## 🎉 What can it do? 15 | 16 | - Use for registering on websites where you don't want to expose your real email address, avoiding spam. 17 | - Use for creating multiple accounts on the same platform without needing multiple email addresses. 18 | 19 | ## ⚠️ Warning 20 | 21 | - **❗Received emails are only retained for 10 days** 22 | - **❗Randomly generated email addresses can be used by anyone, do not use for registering important accounts** -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 📮 临时邮箱 2 | 3 | **(V2版本重构中)** 4 | 5 | 使用 Golang + Astro + React + Long Polling 实时获取邮件 6 | 7 | - [x] 邮件 CSS 样式隔离 8 | - [x] 长轮训实时获取邮件 9 | - [x] 暗色主题和语言切换 10 | - [x] 支持查看和下载附件 11 | 12 | [English](README-en.md) 13 | 14 | 匿名的一次性邮箱,保护您的个人电子邮件地址免受垃圾邮件的骚扰。 15 | 16 | [🧰 自建部署教程](deploy.md) 17 | 18 | ## 🎉 可以做什么? 19 | 20 | - 用于注册不想暴露自己真实邮件地址的网站,以避免垃圾邮件的骚扰。 21 | - 用于在同一个平台注册多个账号,而又不需要去注册多个邮箱。 22 | 23 | ## ⚠️ 注意 24 | 25 | - **❗接收到的邮件内容仅能保留10天** 26 | - **❗随机生成的邮箱地址任何人都可以使用,请勿用于注册重要账号** -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/rs/zerolog/log" 5 | "tmail/internal" 6 | ) 7 | 8 | func main() { 9 | err := internal.NewApp().Run() 10 | if err != nil { 11 | log.Panic().Err(err).Send() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/caarlos0/env/v11" 5 | ) 6 | 7 | type Config struct { 8 | Host string `env:"HOST"` 9 | Port string `env:"PORT" envDefault:"3000"` 10 | DomainList []string `env:"DOMAIN_LIST"` 11 | AdminAddress string `env:"ADMIN_ADDRESS"` 12 | BaseDir string `env:"BASE_DIR" envDefault:"fs"` 13 | DB Database `envPrefix:"DB_"` 14 | } 15 | 16 | type Database struct { 17 | Driver string `env:"DRIVER" envDefault:"postgres"` 18 | Host string `env:"HOST"` 19 | Port string `env:"PORT" envDefault:"5432"` 20 | User string `env:"USER" envDefault:"postgres"` 21 | Pass string `env:"PASS"` 22 | Name string `env:"NAME" envDefault:"tmail"` 23 | } 24 | 25 | func MustNew() *Config { 26 | var cfg Config 27 | if err := env.Parse(&cfg); err != nil { 28 | panic(err) 29 | } 30 | return &cfg 31 | } 32 | -------------------------------------------------------------------------------- /deploy.md: -------------------------------------------------------------------------------- 1 | # 🧰 自建部署教程 (v2.0.0+) 2 | 3 | ## 邮件接收原理 4 | 5 | 使用 Cloudflare 的邮件转发功能,将接收到的所有邮件通过 Workers 转发到本程序中。 6 | 7 | **所以自建的邮箱域名必须使用 Cloudflare 进行 DNS 解析** 8 | 9 | ## 开启邮件转发 & 创建 Workers 10 | 11 | - 首先开启邮件转发,按照官方流程来就行 12 | 13 | - 创建一个 Workers,模板随便选都可以 14 | 15 |  16 | 17 | 创建好之后点击`Code editor`编辑代码,将[此处](doc/workers.js)的代码粘贴进去,需要将其中的域名`mail.sunls.de`替换为自己的,然后别忘记点击`Save and deploy`部署: 18 | 19 |  20 | 21 | - 然后需要添加一条`Catch-All`的规则,注意要选择`Send to a Worker`,如图: 22 | 23 |  24 | 25 | ## 环境变量配置 26 | 27 | ### 数据库配置 28 | 29 | **目前仅支持 PostgreSQL** 30 | 31 | - `DB_HOST`: 数据库地址 32 | - `DB_PASS`: 数据库密码 33 | - `DB_NAME`: 数据库名称,默认`tmail` 34 | 35 | ### 必须 36 | - `DOMAIN_LIST`: 支持的域名列表,使用`,`分割,例如: `isco.eu.org,chato.eu.org` 37 | 38 | ### 非必须 39 | - `ADMIN_ADDRESS`: 管理员邮箱地址,可以查看所有邮件 (默认返回最新100条) 40 | - `HOST`: 服务监听地址,默认为`127.0.0.1` 41 | - `PORT`: 服务监听端口,默认为`3000` 42 | 43 | ### 统计 44 | - `UMAMI_ID`: Umami 统计的 website-id 45 | - `UMAMI_URL`: Umami 统计的 script.js 地址 46 | - `UMAMI_DOMAINS`: Umami 统计只在特定域名运行,逗号分割 47 | 48 | ## 部署 49 | 50 | _请修改其中的环境变量配置_ 51 | 52 | ### Docker 53 | 54 | ```shell 55 | docker run --name tmail -d --restart unless-stopped -e 'DB_HOST=127.0.0.1' -e 'DB_PASS=postgres' -e 'HOST=0.0.0.0' -e 'DOMAIN_LIST=isco.eu.org,chato.eu.org' -p 3000:3000 sunls24/tmail 56 | ``` 57 | 58 | ### Docker Compose & Caddy (推荐) 59 | 60 | _如果不需要反向代理,需要设置`HOST=0.0.0.0`环境变量_ 61 | 62 | **docker-compose.yaml** 63 | 64 | ```yaml 65 | version: "3.0" 66 | 67 | services: 68 | tmail: 69 | container_name: tmail 70 | image: sunls24/tmail:latest 71 | network_mode: host 72 | restart: unless-stopped 73 | environment: 74 | - "DB_HOST=127.0.0.1" 75 | - "DB_PASS=postgres" 76 | - "DOMAIN_LIST=isco.eu.org,chato.eu.org" 77 | volumes: 78 | - ./tmail:/app/fs 79 | ``` 80 | 81 | **Caddyfile** 82 | 83 | ```text 84 | mail.example.com { 85 | encode zstd gzip 86 | @cache path /_astro/* /*.webp /favicon.svg 87 | header @cache Cache-Control "public, max-age=31536000, immutable" 88 | reverse_proxy 127.0.0.1:3000 89 | } 90 | ``` 91 | -------------------------------------------------------------------------------- /doc/email-routing.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunls24/tmail/10ee1c20969e01051aca951276cc7bc6af60e257/doc/email-routing.webp -------------------------------------------------------------------------------- /doc/workers-create.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunls24/tmail/10ee1c20969e01051aca951276cc7bc6af60e257/doc/workers-create.webp -------------------------------------------------------------------------------- /doc/workers-edit.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunls24/tmail/10ee1c20969e01051aca951276cc7bc6af60e257/doc/workers-edit.webp -------------------------------------------------------------------------------- /doc/workers.js: -------------------------------------------------------------------------------- 1 | export default { 2 | async email(message, env, ctx) { 3 | await fetch(`https://mail.sunls.de/api/report?to=${message.to}`, { 4 | method: "POST", 5 | headers: { 6 | "Content-Type": "application/octet-stream", 7 | }, 8 | body: await new Response(message.raw).arrayBuffer(), 9 | }); 10 | } 11 | } -------------------------------------------------------------------------------- /ent/attachment.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "fmt" 7 | "strings" 8 | "tmail/ent/attachment" 9 | "tmail/ent/envelope" 10 | 11 | "entgo.io/ent" 12 | "entgo.io/ent/dialect/sql" 13 | ) 14 | 15 | // Attachment is the model entity for the Attachment schema. 16 | type Attachment struct { 17 | config `json:"-"` 18 | // ID of the ent. 19 | ID string `json:"id,omitempty"` 20 | // Filename holds the value of the "filename" field. 21 | Filename string `json:"filename,omitempty"` 22 | // Filepath holds the value of the "filepath" field. 23 | Filepath string `json:"filepath,omitempty"` 24 | // ContentType holds the value of the "contentType" field. 25 | ContentType string `json:"contentType,omitempty"` 26 | // Edges holds the relations/edges for other nodes in the graph. 27 | // The values are being populated by the AttachmentQuery when eager-loading is set. 28 | Edges AttachmentEdges `json:"edges"` 29 | envelope_attachments *int 30 | selectValues sql.SelectValues 31 | } 32 | 33 | // AttachmentEdges holds the relations/edges for other nodes in the graph. 34 | type AttachmentEdges struct { 35 | // Owner holds the value of the owner edge. 36 | Owner *Envelope `json:"owner,omitempty"` 37 | // loadedTypes holds the information for reporting if a 38 | // type was loaded (or requested) in eager-loading or not. 39 | loadedTypes [1]bool 40 | } 41 | 42 | // OwnerOrErr returns the Owner value or an error if the edge 43 | // was not loaded in eager-loading, or loaded but was not found. 44 | func (e AttachmentEdges) OwnerOrErr() (*Envelope, error) { 45 | if e.Owner != nil { 46 | return e.Owner, nil 47 | } else if e.loadedTypes[0] { 48 | return nil, &NotFoundError{label: envelope.Label} 49 | } 50 | return nil, &NotLoadedError{edge: "owner"} 51 | } 52 | 53 | // scanValues returns the types for scanning values from sql.Rows. 54 | func (*Attachment) scanValues(columns []string) ([]any, error) { 55 | values := make([]any, len(columns)) 56 | for i := range columns { 57 | switch columns[i] { 58 | case attachment.FieldID, attachment.FieldFilename, attachment.FieldFilepath, attachment.FieldContentType: 59 | values[i] = new(sql.NullString) 60 | case attachment.ForeignKeys[0]: // envelope_attachments 61 | values[i] = new(sql.NullInt64) 62 | default: 63 | values[i] = new(sql.UnknownType) 64 | } 65 | } 66 | return values, nil 67 | } 68 | 69 | // assignValues assigns the values that were returned from sql.Rows (after scanning) 70 | // to the Attachment fields. 71 | func (a *Attachment) assignValues(columns []string, values []any) error { 72 | if m, n := len(values), len(columns); m < n { 73 | return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) 74 | } 75 | for i := range columns { 76 | switch columns[i] { 77 | case attachment.FieldID: 78 | if value, ok := values[i].(*sql.NullString); !ok { 79 | return fmt.Errorf("unexpected type %T for field id", values[i]) 80 | } else if value.Valid { 81 | a.ID = value.String 82 | } 83 | case attachment.FieldFilename: 84 | if value, ok := values[i].(*sql.NullString); !ok { 85 | return fmt.Errorf("unexpected type %T for field filename", values[i]) 86 | } else if value.Valid { 87 | a.Filename = value.String 88 | } 89 | case attachment.FieldFilepath: 90 | if value, ok := values[i].(*sql.NullString); !ok { 91 | return fmt.Errorf("unexpected type %T for field filepath", values[i]) 92 | } else if value.Valid { 93 | a.Filepath = value.String 94 | } 95 | case attachment.FieldContentType: 96 | if value, ok := values[i].(*sql.NullString); !ok { 97 | return fmt.Errorf("unexpected type %T for field contentType", values[i]) 98 | } else if value.Valid { 99 | a.ContentType = value.String 100 | } 101 | case attachment.ForeignKeys[0]: 102 | if value, ok := values[i].(*sql.NullInt64); !ok { 103 | return fmt.Errorf("unexpected type %T for edge-field envelope_attachments", value) 104 | } else if value.Valid { 105 | a.envelope_attachments = new(int) 106 | *a.envelope_attachments = int(value.Int64) 107 | } 108 | default: 109 | a.selectValues.Set(columns[i], values[i]) 110 | } 111 | } 112 | return nil 113 | } 114 | 115 | // Value returns the ent.Value that was dynamically selected and assigned to the Attachment. 116 | // This includes values selected through modifiers, order, etc. 117 | func (a *Attachment) Value(name string) (ent.Value, error) { 118 | return a.selectValues.Get(name) 119 | } 120 | 121 | // QueryOwner queries the "owner" edge of the Attachment entity. 122 | func (a *Attachment) QueryOwner() *EnvelopeQuery { 123 | return NewAttachmentClient(a.config).QueryOwner(a) 124 | } 125 | 126 | // Update returns a builder for updating this Attachment. 127 | // Note that you need to call Attachment.Unwrap() before calling this method if this Attachment 128 | // was returned from a transaction, and the transaction was committed or rolled back. 129 | func (a *Attachment) Update() *AttachmentUpdateOne { 130 | return NewAttachmentClient(a.config).UpdateOne(a) 131 | } 132 | 133 | // Unwrap unwraps the Attachment entity that was returned from a transaction after it was closed, 134 | // so that all future queries will be executed through the driver which created the transaction. 135 | func (a *Attachment) Unwrap() *Attachment { 136 | _tx, ok := a.config.driver.(*txDriver) 137 | if !ok { 138 | panic("ent: Attachment is not a transactional entity") 139 | } 140 | a.config.driver = _tx.drv 141 | return a 142 | } 143 | 144 | // String implements the fmt.Stringer. 145 | func (a *Attachment) String() string { 146 | var builder strings.Builder 147 | builder.WriteString("Attachment(") 148 | builder.WriteString(fmt.Sprintf("id=%v, ", a.ID)) 149 | builder.WriteString("filename=") 150 | builder.WriteString(a.Filename) 151 | builder.WriteString(", ") 152 | builder.WriteString("filepath=") 153 | builder.WriteString(a.Filepath) 154 | builder.WriteString(", ") 155 | builder.WriteString("contentType=") 156 | builder.WriteString(a.ContentType) 157 | builder.WriteByte(')') 158 | return builder.String() 159 | } 160 | 161 | // Attachments is a parsable slice of Attachment. 162 | type Attachments []*Attachment 163 | -------------------------------------------------------------------------------- /ent/attachment/attachment.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package attachment 4 | 5 | import ( 6 | "entgo.io/ent/dialect/sql" 7 | "entgo.io/ent/dialect/sql/sqlgraph" 8 | ) 9 | 10 | const ( 11 | // Label holds the string label denoting the attachment type in the database. 12 | Label = "attachment" 13 | // FieldID holds the string denoting the id field in the database. 14 | FieldID = "id" 15 | // FieldFilename holds the string denoting the filename field in the database. 16 | FieldFilename = "filename" 17 | // FieldFilepath holds the string denoting the filepath field in the database. 18 | FieldFilepath = "filepath" 19 | // FieldContentType holds the string denoting the contenttype field in the database. 20 | FieldContentType = "content_type" 21 | // EdgeOwner holds the string denoting the owner edge name in mutations. 22 | EdgeOwner = "owner" 23 | // Table holds the table name of the attachment in the database. 24 | Table = "attachments" 25 | // OwnerTable is the table that holds the owner relation/edge. 26 | OwnerTable = "attachments" 27 | // OwnerInverseTable is the table name for the Envelope entity. 28 | // It exists in this package in order to avoid circular dependency with the "envelope" package. 29 | OwnerInverseTable = "envelopes" 30 | // OwnerColumn is the table column denoting the owner relation/edge. 31 | OwnerColumn = "envelope_attachments" 32 | ) 33 | 34 | // Columns holds all SQL columns for attachment fields. 35 | var Columns = []string{ 36 | FieldID, 37 | FieldFilename, 38 | FieldFilepath, 39 | FieldContentType, 40 | } 41 | 42 | // ForeignKeys holds the SQL foreign-keys that are owned by the "attachments" 43 | // table and are not defined as standalone fields in the schema. 44 | var ForeignKeys = []string{ 45 | "envelope_attachments", 46 | } 47 | 48 | // ValidColumn reports if the column name is valid (part of the table columns). 49 | func ValidColumn(column string) bool { 50 | for i := range Columns { 51 | if column == Columns[i] { 52 | return true 53 | } 54 | } 55 | for i := range ForeignKeys { 56 | if column == ForeignKeys[i] { 57 | return true 58 | } 59 | } 60 | return false 61 | } 62 | 63 | var ( 64 | // FilenameValidator is a validator for the "filename" field. It is called by the builders before save. 65 | FilenameValidator func(string) error 66 | // FilepathValidator is a validator for the "filepath" field. It is called by the builders before save. 67 | FilepathValidator func(string) error 68 | // ContentTypeValidator is a validator for the "contentType" field. It is called by the builders before save. 69 | ContentTypeValidator func(string) error 70 | // IDValidator is a validator for the "id" field. It is called by the builders before save. 71 | IDValidator func(string) error 72 | ) 73 | 74 | // OrderOption defines the ordering options for the Attachment queries. 75 | type OrderOption func(*sql.Selector) 76 | 77 | // ByID orders the results by the id field. 78 | func ByID(opts ...sql.OrderTermOption) OrderOption { 79 | return sql.OrderByField(FieldID, opts...).ToFunc() 80 | } 81 | 82 | // ByFilename orders the results by the filename field. 83 | func ByFilename(opts ...sql.OrderTermOption) OrderOption { 84 | return sql.OrderByField(FieldFilename, opts...).ToFunc() 85 | } 86 | 87 | // ByFilepath orders the results by the filepath field. 88 | func ByFilepath(opts ...sql.OrderTermOption) OrderOption { 89 | return sql.OrderByField(FieldFilepath, opts...).ToFunc() 90 | } 91 | 92 | // ByContentType orders the results by the contentType field. 93 | func ByContentType(opts ...sql.OrderTermOption) OrderOption { 94 | return sql.OrderByField(FieldContentType, opts...).ToFunc() 95 | } 96 | 97 | // ByOwnerField orders the results by owner field. 98 | func ByOwnerField(field string, opts ...sql.OrderTermOption) OrderOption { 99 | return func(s *sql.Selector) { 100 | sqlgraph.OrderByNeighborTerms(s, newOwnerStep(), sql.OrderByField(field, opts...)) 101 | } 102 | } 103 | func newOwnerStep() *sqlgraph.Step { 104 | return sqlgraph.NewStep( 105 | sqlgraph.From(Table, FieldID), 106 | sqlgraph.To(OwnerInverseTable, FieldID), 107 | sqlgraph.Edge(sqlgraph.M2O, true, OwnerTable, OwnerColumn), 108 | ) 109 | } 110 | -------------------------------------------------------------------------------- /ent/attachment/where.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package attachment 4 | 5 | import ( 6 | "tmail/ent/predicate" 7 | 8 | "entgo.io/ent/dialect/sql" 9 | "entgo.io/ent/dialect/sql/sqlgraph" 10 | ) 11 | 12 | // ID filters vertices based on their ID field. 13 | func ID(id string) predicate.Attachment { 14 | return predicate.Attachment(sql.FieldEQ(FieldID, id)) 15 | } 16 | 17 | // IDEQ applies the EQ predicate on the ID field. 18 | func IDEQ(id string) predicate.Attachment { 19 | return predicate.Attachment(sql.FieldEQ(FieldID, id)) 20 | } 21 | 22 | // IDNEQ applies the NEQ predicate on the ID field. 23 | func IDNEQ(id string) predicate.Attachment { 24 | return predicate.Attachment(sql.FieldNEQ(FieldID, id)) 25 | } 26 | 27 | // IDIn applies the In predicate on the ID field. 28 | func IDIn(ids ...string) predicate.Attachment { 29 | return predicate.Attachment(sql.FieldIn(FieldID, ids...)) 30 | } 31 | 32 | // IDNotIn applies the NotIn predicate on the ID field. 33 | func IDNotIn(ids ...string) predicate.Attachment { 34 | return predicate.Attachment(sql.FieldNotIn(FieldID, ids...)) 35 | } 36 | 37 | // IDGT applies the GT predicate on the ID field. 38 | func IDGT(id string) predicate.Attachment { 39 | return predicate.Attachment(sql.FieldGT(FieldID, id)) 40 | } 41 | 42 | // IDGTE applies the GTE predicate on the ID field. 43 | func IDGTE(id string) predicate.Attachment { 44 | return predicate.Attachment(sql.FieldGTE(FieldID, id)) 45 | } 46 | 47 | // IDLT applies the LT predicate on the ID field. 48 | func IDLT(id string) predicate.Attachment { 49 | return predicate.Attachment(sql.FieldLT(FieldID, id)) 50 | } 51 | 52 | // IDLTE applies the LTE predicate on the ID field. 53 | func IDLTE(id string) predicate.Attachment { 54 | return predicate.Attachment(sql.FieldLTE(FieldID, id)) 55 | } 56 | 57 | // IDEqualFold applies the EqualFold predicate on the ID field. 58 | func IDEqualFold(id string) predicate.Attachment { 59 | return predicate.Attachment(sql.FieldEqualFold(FieldID, id)) 60 | } 61 | 62 | // IDContainsFold applies the ContainsFold predicate on the ID field. 63 | func IDContainsFold(id string) predicate.Attachment { 64 | return predicate.Attachment(sql.FieldContainsFold(FieldID, id)) 65 | } 66 | 67 | // Filename applies equality check predicate on the "filename" field. It's identical to FilenameEQ. 68 | func Filename(v string) predicate.Attachment { 69 | return predicate.Attachment(sql.FieldEQ(FieldFilename, v)) 70 | } 71 | 72 | // Filepath applies equality check predicate on the "filepath" field. It's identical to FilepathEQ. 73 | func Filepath(v string) predicate.Attachment { 74 | return predicate.Attachment(sql.FieldEQ(FieldFilepath, v)) 75 | } 76 | 77 | // ContentType applies equality check predicate on the "contentType" field. It's identical to ContentTypeEQ. 78 | func ContentType(v string) predicate.Attachment { 79 | return predicate.Attachment(sql.FieldEQ(FieldContentType, v)) 80 | } 81 | 82 | // FilenameEQ applies the EQ predicate on the "filename" field. 83 | func FilenameEQ(v string) predicate.Attachment { 84 | return predicate.Attachment(sql.FieldEQ(FieldFilename, v)) 85 | } 86 | 87 | // FilenameNEQ applies the NEQ predicate on the "filename" field. 88 | func FilenameNEQ(v string) predicate.Attachment { 89 | return predicate.Attachment(sql.FieldNEQ(FieldFilename, v)) 90 | } 91 | 92 | // FilenameIn applies the In predicate on the "filename" field. 93 | func FilenameIn(vs ...string) predicate.Attachment { 94 | return predicate.Attachment(sql.FieldIn(FieldFilename, vs...)) 95 | } 96 | 97 | // FilenameNotIn applies the NotIn predicate on the "filename" field. 98 | func FilenameNotIn(vs ...string) predicate.Attachment { 99 | return predicate.Attachment(sql.FieldNotIn(FieldFilename, vs...)) 100 | } 101 | 102 | // FilenameGT applies the GT predicate on the "filename" field. 103 | func FilenameGT(v string) predicate.Attachment { 104 | return predicate.Attachment(sql.FieldGT(FieldFilename, v)) 105 | } 106 | 107 | // FilenameGTE applies the GTE predicate on the "filename" field. 108 | func FilenameGTE(v string) predicate.Attachment { 109 | return predicate.Attachment(sql.FieldGTE(FieldFilename, v)) 110 | } 111 | 112 | // FilenameLT applies the LT predicate on the "filename" field. 113 | func FilenameLT(v string) predicate.Attachment { 114 | return predicate.Attachment(sql.FieldLT(FieldFilename, v)) 115 | } 116 | 117 | // FilenameLTE applies the LTE predicate on the "filename" field. 118 | func FilenameLTE(v string) predicate.Attachment { 119 | return predicate.Attachment(sql.FieldLTE(FieldFilename, v)) 120 | } 121 | 122 | // FilenameContains applies the Contains predicate on the "filename" field. 123 | func FilenameContains(v string) predicate.Attachment { 124 | return predicate.Attachment(sql.FieldContains(FieldFilename, v)) 125 | } 126 | 127 | // FilenameHasPrefix applies the HasPrefix predicate on the "filename" field. 128 | func FilenameHasPrefix(v string) predicate.Attachment { 129 | return predicate.Attachment(sql.FieldHasPrefix(FieldFilename, v)) 130 | } 131 | 132 | // FilenameHasSuffix applies the HasSuffix predicate on the "filename" field. 133 | func FilenameHasSuffix(v string) predicate.Attachment { 134 | return predicate.Attachment(sql.FieldHasSuffix(FieldFilename, v)) 135 | } 136 | 137 | // FilenameEqualFold applies the EqualFold predicate on the "filename" field. 138 | func FilenameEqualFold(v string) predicate.Attachment { 139 | return predicate.Attachment(sql.FieldEqualFold(FieldFilename, v)) 140 | } 141 | 142 | // FilenameContainsFold applies the ContainsFold predicate on the "filename" field. 143 | func FilenameContainsFold(v string) predicate.Attachment { 144 | return predicate.Attachment(sql.FieldContainsFold(FieldFilename, v)) 145 | } 146 | 147 | // FilepathEQ applies the EQ predicate on the "filepath" field. 148 | func FilepathEQ(v string) predicate.Attachment { 149 | return predicate.Attachment(sql.FieldEQ(FieldFilepath, v)) 150 | } 151 | 152 | // FilepathNEQ applies the NEQ predicate on the "filepath" field. 153 | func FilepathNEQ(v string) predicate.Attachment { 154 | return predicate.Attachment(sql.FieldNEQ(FieldFilepath, v)) 155 | } 156 | 157 | // FilepathIn applies the In predicate on the "filepath" field. 158 | func FilepathIn(vs ...string) predicate.Attachment { 159 | return predicate.Attachment(sql.FieldIn(FieldFilepath, vs...)) 160 | } 161 | 162 | // FilepathNotIn applies the NotIn predicate on the "filepath" field. 163 | func FilepathNotIn(vs ...string) predicate.Attachment { 164 | return predicate.Attachment(sql.FieldNotIn(FieldFilepath, vs...)) 165 | } 166 | 167 | // FilepathGT applies the GT predicate on the "filepath" field. 168 | func FilepathGT(v string) predicate.Attachment { 169 | return predicate.Attachment(sql.FieldGT(FieldFilepath, v)) 170 | } 171 | 172 | // FilepathGTE applies the GTE predicate on the "filepath" field. 173 | func FilepathGTE(v string) predicate.Attachment { 174 | return predicate.Attachment(sql.FieldGTE(FieldFilepath, v)) 175 | } 176 | 177 | // FilepathLT applies the LT predicate on the "filepath" field. 178 | func FilepathLT(v string) predicate.Attachment { 179 | return predicate.Attachment(sql.FieldLT(FieldFilepath, v)) 180 | } 181 | 182 | // FilepathLTE applies the LTE predicate on the "filepath" field. 183 | func FilepathLTE(v string) predicate.Attachment { 184 | return predicate.Attachment(sql.FieldLTE(FieldFilepath, v)) 185 | } 186 | 187 | // FilepathContains applies the Contains predicate on the "filepath" field. 188 | func FilepathContains(v string) predicate.Attachment { 189 | return predicate.Attachment(sql.FieldContains(FieldFilepath, v)) 190 | } 191 | 192 | // FilepathHasPrefix applies the HasPrefix predicate on the "filepath" field. 193 | func FilepathHasPrefix(v string) predicate.Attachment { 194 | return predicate.Attachment(sql.FieldHasPrefix(FieldFilepath, v)) 195 | } 196 | 197 | // FilepathHasSuffix applies the HasSuffix predicate on the "filepath" field. 198 | func FilepathHasSuffix(v string) predicate.Attachment { 199 | return predicate.Attachment(sql.FieldHasSuffix(FieldFilepath, v)) 200 | } 201 | 202 | // FilepathEqualFold applies the EqualFold predicate on the "filepath" field. 203 | func FilepathEqualFold(v string) predicate.Attachment { 204 | return predicate.Attachment(sql.FieldEqualFold(FieldFilepath, v)) 205 | } 206 | 207 | // FilepathContainsFold applies the ContainsFold predicate on the "filepath" field. 208 | func FilepathContainsFold(v string) predicate.Attachment { 209 | return predicate.Attachment(sql.FieldContainsFold(FieldFilepath, v)) 210 | } 211 | 212 | // ContentTypeEQ applies the EQ predicate on the "contentType" field. 213 | func ContentTypeEQ(v string) predicate.Attachment { 214 | return predicate.Attachment(sql.FieldEQ(FieldContentType, v)) 215 | } 216 | 217 | // ContentTypeNEQ applies the NEQ predicate on the "contentType" field. 218 | func ContentTypeNEQ(v string) predicate.Attachment { 219 | return predicate.Attachment(sql.FieldNEQ(FieldContentType, v)) 220 | } 221 | 222 | // ContentTypeIn applies the In predicate on the "contentType" field. 223 | func ContentTypeIn(vs ...string) predicate.Attachment { 224 | return predicate.Attachment(sql.FieldIn(FieldContentType, vs...)) 225 | } 226 | 227 | // ContentTypeNotIn applies the NotIn predicate on the "contentType" field. 228 | func ContentTypeNotIn(vs ...string) predicate.Attachment { 229 | return predicate.Attachment(sql.FieldNotIn(FieldContentType, vs...)) 230 | } 231 | 232 | // ContentTypeGT applies the GT predicate on the "contentType" field. 233 | func ContentTypeGT(v string) predicate.Attachment { 234 | return predicate.Attachment(sql.FieldGT(FieldContentType, v)) 235 | } 236 | 237 | // ContentTypeGTE applies the GTE predicate on the "contentType" field. 238 | func ContentTypeGTE(v string) predicate.Attachment { 239 | return predicate.Attachment(sql.FieldGTE(FieldContentType, v)) 240 | } 241 | 242 | // ContentTypeLT applies the LT predicate on the "contentType" field. 243 | func ContentTypeLT(v string) predicate.Attachment { 244 | return predicate.Attachment(sql.FieldLT(FieldContentType, v)) 245 | } 246 | 247 | // ContentTypeLTE applies the LTE predicate on the "contentType" field. 248 | func ContentTypeLTE(v string) predicate.Attachment { 249 | return predicate.Attachment(sql.FieldLTE(FieldContentType, v)) 250 | } 251 | 252 | // ContentTypeContains applies the Contains predicate on the "contentType" field. 253 | func ContentTypeContains(v string) predicate.Attachment { 254 | return predicate.Attachment(sql.FieldContains(FieldContentType, v)) 255 | } 256 | 257 | // ContentTypeHasPrefix applies the HasPrefix predicate on the "contentType" field. 258 | func ContentTypeHasPrefix(v string) predicate.Attachment { 259 | return predicate.Attachment(sql.FieldHasPrefix(FieldContentType, v)) 260 | } 261 | 262 | // ContentTypeHasSuffix applies the HasSuffix predicate on the "contentType" field. 263 | func ContentTypeHasSuffix(v string) predicate.Attachment { 264 | return predicate.Attachment(sql.FieldHasSuffix(FieldContentType, v)) 265 | } 266 | 267 | // ContentTypeEqualFold applies the EqualFold predicate on the "contentType" field. 268 | func ContentTypeEqualFold(v string) predicate.Attachment { 269 | return predicate.Attachment(sql.FieldEqualFold(FieldContentType, v)) 270 | } 271 | 272 | // ContentTypeContainsFold applies the ContainsFold predicate on the "contentType" field. 273 | func ContentTypeContainsFold(v string) predicate.Attachment { 274 | return predicate.Attachment(sql.FieldContainsFold(FieldContentType, v)) 275 | } 276 | 277 | // HasOwner applies the HasEdge predicate on the "owner" edge. 278 | func HasOwner() predicate.Attachment { 279 | return predicate.Attachment(func(s *sql.Selector) { 280 | step := sqlgraph.NewStep( 281 | sqlgraph.From(Table, FieldID), 282 | sqlgraph.Edge(sqlgraph.M2O, true, OwnerTable, OwnerColumn), 283 | ) 284 | sqlgraph.HasNeighbors(s, step) 285 | }) 286 | } 287 | 288 | // HasOwnerWith applies the HasEdge predicate on the "owner" edge with a given conditions (other predicates). 289 | func HasOwnerWith(preds ...predicate.Envelope) predicate.Attachment { 290 | return predicate.Attachment(func(s *sql.Selector) { 291 | step := newOwnerStep() 292 | sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { 293 | for _, p := range preds { 294 | p(s) 295 | } 296 | }) 297 | }) 298 | } 299 | 300 | // And groups predicates with the AND operator between them. 301 | func And(predicates ...predicate.Attachment) predicate.Attachment { 302 | return predicate.Attachment(sql.AndPredicates(predicates...)) 303 | } 304 | 305 | // Or groups predicates with the OR operator between them. 306 | func Or(predicates ...predicate.Attachment) predicate.Attachment { 307 | return predicate.Attachment(sql.OrPredicates(predicates...)) 308 | } 309 | 310 | // Not applies the not operator on the given predicate. 311 | func Not(p predicate.Attachment) predicate.Attachment { 312 | return predicate.Attachment(sql.NotPredicates(p)) 313 | } 314 | -------------------------------------------------------------------------------- /ent/attachment_create.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | "errors" 8 | "fmt" 9 | "tmail/ent/attachment" 10 | "tmail/ent/envelope" 11 | 12 | "entgo.io/ent/dialect/sql/sqlgraph" 13 | "entgo.io/ent/schema/field" 14 | ) 15 | 16 | // AttachmentCreate is the builder for creating a Attachment entity. 17 | type AttachmentCreate struct { 18 | config 19 | mutation *AttachmentMutation 20 | hooks []Hook 21 | } 22 | 23 | // SetFilename sets the "filename" field. 24 | func (ac *AttachmentCreate) SetFilename(s string) *AttachmentCreate { 25 | ac.mutation.SetFilename(s) 26 | return ac 27 | } 28 | 29 | // SetFilepath sets the "filepath" field. 30 | func (ac *AttachmentCreate) SetFilepath(s string) *AttachmentCreate { 31 | ac.mutation.SetFilepath(s) 32 | return ac 33 | } 34 | 35 | // SetContentType sets the "contentType" field. 36 | func (ac *AttachmentCreate) SetContentType(s string) *AttachmentCreate { 37 | ac.mutation.SetContentType(s) 38 | return ac 39 | } 40 | 41 | // SetID sets the "id" field. 42 | func (ac *AttachmentCreate) SetID(s string) *AttachmentCreate { 43 | ac.mutation.SetID(s) 44 | return ac 45 | } 46 | 47 | // SetOwnerID sets the "owner" edge to the Envelope entity by ID. 48 | func (ac *AttachmentCreate) SetOwnerID(id int) *AttachmentCreate { 49 | ac.mutation.SetOwnerID(id) 50 | return ac 51 | } 52 | 53 | // SetNillableOwnerID sets the "owner" edge to the Envelope entity by ID if the given value is not nil. 54 | func (ac *AttachmentCreate) SetNillableOwnerID(id *int) *AttachmentCreate { 55 | if id != nil { 56 | ac = ac.SetOwnerID(*id) 57 | } 58 | return ac 59 | } 60 | 61 | // SetOwner sets the "owner" edge to the Envelope entity. 62 | func (ac *AttachmentCreate) SetOwner(e *Envelope) *AttachmentCreate { 63 | return ac.SetOwnerID(e.ID) 64 | } 65 | 66 | // Mutation returns the AttachmentMutation object of the builder. 67 | func (ac *AttachmentCreate) Mutation() *AttachmentMutation { 68 | return ac.mutation 69 | } 70 | 71 | // Save creates the Attachment in the database. 72 | func (ac *AttachmentCreate) Save(ctx context.Context) (*Attachment, error) { 73 | return withHooks(ctx, ac.sqlSave, ac.mutation, ac.hooks) 74 | } 75 | 76 | // SaveX calls Save and panics if Save returns an error. 77 | func (ac *AttachmentCreate) SaveX(ctx context.Context) *Attachment { 78 | v, err := ac.Save(ctx) 79 | if err != nil { 80 | panic(err) 81 | } 82 | return v 83 | } 84 | 85 | // Exec executes the query. 86 | func (ac *AttachmentCreate) Exec(ctx context.Context) error { 87 | _, err := ac.Save(ctx) 88 | return err 89 | } 90 | 91 | // ExecX is like Exec, but panics if an error occurs. 92 | func (ac *AttachmentCreate) ExecX(ctx context.Context) { 93 | if err := ac.Exec(ctx); err != nil { 94 | panic(err) 95 | } 96 | } 97 | 98 | // check runs all checks and user-defined validators on the builder. 99 | func (ac *AttachmentCreate) check() error { 100 | if _, ok := ac.mutation.Filename(); !ok { 101 | return &ValidationError{Name: "filename", err: errors.New(`ent: missing required field "Attachment.filename"`)} 102 | } 103 | if v, ok := ac.mutation.Filename(); ok { 104 | if err := attachment.FilenameValidator(v); err != nil { 105 | return &ValidationError{Name: "filename", err: fmt.Errorf(`ent: validator failed for field "Attachment.filename": %w`, err)} 106 | } 107 | } 108 | if _, ok := ac.mutation.Filepath(); !ok { 109 | return &ValidationError{Name: "filepath", err: errors.New(`ent: missing required field "Attachment.filepath"`)} 110 | } 111 | if v, ok := ac.mutation.Filepath(); ok { 112 | if err := attachment.FilepathValidator(v); err != nil { 113 | return &ValidationError{Name: "filepath", err: fmt.Errorf(`ent: validator failed for field "Attachment.filepath": %w`, err)} 114 | } 115 | } 116 | if _, ok := ac.mutation.ContentType(); !ok { 117 | return &ValidationError{Name: "contentType", err: errors.New(`ent: missing required field "Attachment.contentType"`)} 118 | } 119 | if v, ok := ac.mutation.ContentType(); ok { 120 | if err := attachment.ContentTypeValidator(v); err != nil { 121 | return &ValidationError{Name: "contentType", err: fmt.Errorf(`ent: validator failed for field "Attachment.contentType": %w`, err)} 122 | } 123 | } 124 | if v, ok := ac.mutation.ID(); ok { 125 | if err := attachment.IDValidator(v); err != nil { 126 | return &ValidationError{Name: "id", err: fmt.Errorf(`ent: validator failed for field "Attachment.id": %w`, err)} 127 | } 128 | } 129 | return nil 130 | } 131 | 132 | func (ac *AttachmentCreate) sqlSave(ctx context.Context) (*Attachment, error) { 133 | if err := ac.check(); err != nil { 134 | return nil, err 135 | } 136 | _node, _spec := ac.createSpec() 137 | if err := sqlgraph.CreateNode(ctx, ac.driver, _spec); err != nil { 138 | if sqlgraph.IsConstraintError(err) { 139 | err = &ConstraintError{msg: err.Error(), wrap: err} 140 | } 141 | return nil, err 142 | } 143 | if _spec.ID.Value != nil { 144 | if id, ok := _spec.ID.Value.(string); ok { 145 | _node.ID = id 146 | } else { 147 | return nil, fmt.Errorf("unexpected Attachment.ID type: %T", _spec.ID.Value) 148 | } 149 | } 150 | ac.mutation.id = &_node.ID 151 | ac.mutation.done = true 152 | return _node, nil 153 | } 154 | 155 | func (ac *AttachmentCreate) createSpec() (*Attachment, *sqlgraph.CreateSpec) { 156 | var ( 157 | _node = &Attachment{config: ac.config} 158 | _spec = sqlgraph.NewCreateSpec(attachment.Table, sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeString)) 159 | ) 160 | if id, ok := ac.mutation.ID(); ok { 161 | _node.ID = id 162 | _spec.ID.Value = id 163 | } 164 | if value, ok := ac.mutation.Filename(); ok { 165 | _spec.SetField(attachment.FieldFilename, field.TypeString, value) 166 | _node.Filename = value 167 | } 168 | if value, ok := ac.mutation.Filepath(); ok { 169 | _spec.SetField(attachment.FieldFilepath, field.TypeString, value) 170 | _node.Filepath = value 171 | } 172 | if value, ok := ac.mutation.ContentType(); ok { 173 | _spec.SetField(attachment.FieldContentType, field.TypeString, value) 174 | _node.ContentType = value 175 | } 176 | if nodes := ac.mutation.OwnerIDs(); len(nodes) > 0 { 177 | edge := &sqlgraph.EdgeSpec{ 178 | Rel: sqlgraph.M2O, 179 | Inverse: true, 180 | Table: attachment.OwnerTable, 181 | Columns: []string{attachment.OwnerColumn}, 182 | Bidi: false, 183 | Target: &sqlgraph.EdgeTarget{ 184 | IDSpec: sqlgraph.NewFieldSpec(envelope.FieldID, field.TypeInt), 185 | }, 186 | } 187 | for _, k := range nodes { 188 | edge.Target.Nodes = append(edge.Target.Nodes, k) 189 | } 190 | _node.envelope_attachments = &nodes[0] 191 | _spec.Edges = append(_spec.Edges, edge) 192 | } 193 | return _node, _spec 194 | } 195 | 196 | // AttachmentCreateBulk is the builder for creating many Attachment entities in bulk. 197 | type AttachmentCreateBulk struct { 198 | config 199 | err error 200 | builders []*AttachmentCreate 201 | } 202 | 203 | // Save creates the Attachment entities in the database. 204 | func (acb *AttachmentCreateBulk) Save(ctx context.Context) ([]*Attachment, error) { 205 | if acb.err != nil { 206 | return nil, acb.err 207 | } 208 | specs := make([]*sqlgraph.CreateSpec, len(acb.builders)) 209 | nodes := make([]*Attachment, len(acb.builders)) 210 | mutators := make([]Mutator, len(acb.builders)) 211 | for i := range acb.builders { 212 | func(i int, root context.Context) { 213 | builder := acb.builders[i] 214 | var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { 215 | mutation, ok := m.(*AttachmentMutation) 216 | if !ok { 217 | return nil, fmt.Errorf("unexpected mutation type %T", m) 218 | } 219 | if err := builder.check(); err != nil { 220 | return nil, err 221 | } 222 | builder.mutation = mutation 223 | var err error 224 | nodes[i], specs[i] = builder.createSpec() 225 | if i < len(mutators)-1 { 226 | _, err = mutators[i+1].Mutate(root, acb.builders[i+1].mutation) 227 | } else { 228 | spec := &sqlgraph.BatchCreateSpec{Nodes: specs} 229 | // Invoke the actual operation on the latest mutation in the chain. 230 | if err = sqlgraph.BatchCreate(ctx, acb.driver, spec); err != nil { 231 | if sqlgraph.IsConstraintError(err) { 232 | err = &ConstraintError{msg: err.Error(), wrap: err} 233 | } 234 | } 235 | } 236 | if err != nil { 237 | return nil, err 238 | } 239 | mutation.id = &nodes[i].ID 240 | mutation.done = true 241 | return nodes[i], nil 242 | }) 243 | for i := len(builder.hooks) - 1; i >= 0; i-- { 244 | mut = builder.hooks[i](mut) 245 | } 246 | mutators[i] = mut 247 | }(i, ctx) 248 | } 249 | if len(mutators) > 0 { 250 | if _, err := mutators[0].Mutate(ctx, acb.builders[0].mutation); err != nil { 251 | return nil, err 252 | } 253 | } 254 | return nodes, nil 255 | } 256 | 257 | // SaveX is like Save, but panics if an error occurs. 258 | func (acb *AttachmentCreateBulk) SaveX(ctx context.Context) []*Attachment { 259 | v, err := acb.Save(ctx) 260 | if err != nil { 261 | panic(err) 262 | } 263 | return v 264 | } 265 | 266 | // Exec executes the query. 267 | func (acb *AttachmentCreateBulk) Exec(ctx context.Context) error { 268 | _, err := acb.Save(ctx) 269 | return err 270 | } 271 | 272 | // ExecX is like Exec, but panics if an error occurs. 273 | func (acb *AttachmentCreateBulk) ExecX(ctx context.Context) { 274 | if err := acb.Exec(ctx); err != nil { 275 | panic(err) 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /ent/attachment_delete.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | "tmail/ent/attachment" 8 | "tmail/ent/predicate" 9 | 10 | "entgo.io/ent/dialect/sql" 11 | "entgo.io/ent/dialect/sql/sqlgraph" 12 | "entgo.io/ent/schema/field" 13 | ) 14 | 15 | // AttachmentDelete is the builder for deleting a Attachment entity. 16 | type AttachmentDelete struct { 17 | config 18 | hooks []Hook 19 | mutation *AttachmentMutation 20 | } 21 | 22 | // Where appends a list predicates to the AttachmentDelete builder. 23 | func (ad *AttachmentDelete) Where(ps ...predicate.Attachment) *AttachmentDelete { 24 | ad.mutation.Where(ps...) 25 | return ad 26 | } 27 | 28 | // Exec executes the deletion query and returns how many vertices were deleted. 29 | func (ad *AttachmentDelete) Exec(ctx context.Context) (int, error) { 30 | return withHooks(ctx, ad.sqlExec, ad.mutation, ad.hooks) 31 | } 32 | 33 | // ExecX is like Exec, but panics if an error occurs. 34 | func (ad *AttachmentDelete) ExecX(ctx context.Context) int { 35 | n, err := ad.Exec(ctx) 36 | if err != nil { 37 | panic(err) 38 | } 39 | return n 40 | } 41 | 42 | func (ad *AttachmentDelete) sqlExec(ctx context.Context) (int, error) { 43 | _spec := sqlgraph.NewDeleteSpec(attachment.Table, sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeString)) 44 | if ps := ad.mutation.predicates; len(ps) > 0 { 45 | _spec.Predicate = func(selector *sql.Selector) { 46 | for i := range ps { 47 | ps[i](selector) 48 | } 49 | } 50 | } 51 | affected, err := sqlgraph.DeleteNodes(ctx, ad.driver, _spec) 52 | if err != nil && sqlgraph.IsConstraintError(err) { 53 | err = &ConstraintError{msg: err.Error(), wrap: err} 54 | } 55 | ad.mutation.done = true 56 | return affected, err 57 | } 58 | 59 | // AttachmentDeleteOne is the builder for deleting a single Attachment entity. 60 | type AttachmentDeleteOne struct { 61 | ad *AttachmentDelete 62 | } 63 | 64 | // Where appends a list predicates to the AttachmentDelete builder. 65 | func (ado *AttachmentDeleteOne) Where(ps ...predicate.Attachment) *AttachmentDeleteOne { 66 | ado.ad.mutation.Where(ps...) 67 | return ado 68 | } 69 | 70 | // Exec executes the deletion query. 71 | func (ado *AttachmentDeleteOne) Exec(ctx context.Context) error { 72 | n, err := ado.ad.Exec(ctx) 73 | switch { 74 | case err != nil: 75 | return err 76 | case n == 0: 77 | return &NotFoundError{attachment.Label} 78 | default: 79 | return nil 80 | } 81 | } 82 | 83 | // ExecX is like Exec, but panics if an error occurs. 84 | func (ado *AttachmentDeleteOne) ExecX(ctx context.Context) { 85 | if err := ado.Exec(ctx); err != nil { 86 | panic(err) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /ent/attachment_update.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | "errors" 8 | "fmt" 9 | "tmail/ent/attachment" 10 | "tmail/ent/envelope" 11 | "tmail/ent/predicate" 12 | 13 | "entgo.io/ent/dialect/sql" 14 | "entgo.io/ent/dialect/sql/sqlgraph" 15 | "entgo.io/ent/schema/field" 16 | ) 17 | 18 | // AttachmentUpdate is the builder for updating Attachment entities. 19 | type AttachmentUpdate struct { 20 | config 21 | hooks []Hook 22 | mutation *AttachmentMutation 23 | } 24 | 25 | // Where appends a list predicates to the AttachmentUpdate builder. 26 | func (au *AttachmentUpdate) Where(ps ...predicate.Attachment) *AttachmentUpdate { 27 | au.mutation.Where(ps...) 28 | return au 29 | } 30 | 31 | // SetFilename sets the "filename" field. 32 | func (au *AttachmentUpdate) SetFilename(s string) *AttachmentUpdate { 33 | au.mutation.SetFilename(s) 34 | return au 35 | } 36 | 37 | // SetNillableFilename sets the "filename" field if the given value is not nil. 38 | func (au *AttachmentUpdate) SetNillableFilename(s *string) *AttachmentUpdate { 39 | if s != nil { 40 | au.SetFilename(*s) 41 | } 42 | return au 43 | } 44 | 45 | // SetFilepath sets the "filepath" field. 46 | func (au *AttachmentUpdate) SetFilepath(s string) *AttachmentUpdate { 47 | au.mutation.SetFilepath(s) 48 | return au 49 | } 50 | 51 | // SetNillableFilepath sets the "filepath" field if the given value is not nil. 52 | func (au *AttachmentUpdate) SetNillableFilepath(s *string) *AttachmentUpdate { 53 | if s != nil { 54 | au.SetFilepath(*s) 55 | } 56 | return au 57 | } 58 | 59 | // SetContentType sets the "contentType" field. 60 | func (au *AttachmentUpdate) SetContentType(s string) *AttachmentUpdate { 61 | au.mutation.SetContentType(s) 62 | return au 63 | } 64 | 65 | // SetNillableContentType sets the "contentType" field if the given value is not nil. 66 | func (au *AttachmentUpdate) SetNillableContentType(s *string) *AttachmentUpdate { 67 | if s != nil { 68 | au.SetContentType(*s) 69 | } 70 | return au 71 | } 72 | 73 | // SetOwnerID sets the "owner" edge to the Envelope entity by ID. 74 | func (au *AttachmentUpdate) SetOwnerID(id int) *AttachmentUpdate { 75 | au.mutation.SetOwnerID(id) 76 | return au 77 | } 78 | 79 | // SetNillableOwnerID sets the "owner" edge to the Envelope entity by ID if the given value is not nil. 80 | func (au *AttachmentUpdate) SetNillableOwnerID(id *int) *AttachmentUpdate { 81 | if id != nil { 82 | au = au.SetOwnerID(*id) 83 | } 84 | return au 85 | } 86 | 87 | // SetOwner sets the "owner" edge to the Envelope entity. 88 | func (au *AttachmentUpdate) SetOwner(e *Envelope) *AttachmentUpdate { 89 | return au.SetOwnerID(e.ID) 90 | } 91 | 92 | // Mutation returns the AttachmentMutation object of the builder. 93 | func (au *AttachmentUpdate) Mutation() *AttachmentMutation { 94 | return au.mutation 95 | } 96 | 97 | // ClearOwner clears the "owner" edge to the Envelope entity. 98 | func (au *AttachmentUpdate) ClearOwner() *AttachmentUpdate { 99 | au.mutation.ClearOwner() 100 | return au 101 | } 102 | 103 | // Save executes the query and returns the number of nodes affected by the update operation. 104 | func (au *AttachmentUpdate) Save(ctx context.Context) (int, error) { 105 | return withHooks(ctx, au.sqlSave, au.mutation, au.hooks) 106 | } 107 | 108 | // SaveX is like Save, but panics if an error occurs. 109 | func (au *AttachmentUpdate) SaveX(ctx context.Context) int { 110 | affected, err := au.Save(ctx) 111 | if err != nil { 112 | panic(err) 113 | } 114 | return affected 115 | } 116 | 117 | // Exec executes the query. 118 | func (au *AttachmentUpdate) Exec(ctx context.Context) error { 119 | _, err := au.Save(ctx) 120 | return err 121 | } 122 | 123 | // ExecX is like Exec, but panics if an error occurs. 124 | func (au *AttachmentUpdate) ExecX(ctx context.Context) { 125 | if err := au.Exec(ctx); err != nil { 126 | panic(err) 127 | } 128 | } 129 | 130 | // check runs all checks and user-defined validators on the builder. 131 | func (au *AttachmentUpdate) check() error { 132 | if v, ok := au.mutation.Filename(); ok { 133 | if err := attachment.FilenameValidator(v); err != nil { 134 | return &ValidationError{Name: "filename", err: fmt.Errorf(`ent: validator failed for field "Attachment.filename": %w`, err)} 135 | } 136 | } 137 | if v, ok := au.mutation.Filepath(); ok { 138 | if err := attachment.FilepathValidator(v); err != nil { 139 | return &ValidationError{Name: "filepath", err: fmt.Errorf(`ent: validator failed for field "Attachment.filepath": %w`, err)} 140 | } 141 | } 142 | if v, ok := au.mutation.ContentType(); ok { 143 | if err := attachment.ContentTypeValidator(v); err != nil { 144 | return &ValidationError{Name: "contentType", err: fmt.Errorf(`ent: validator failed for field "Attachment.contentType": %w`, err)} 145 | } 146 | } 147 | return nil 148 | } 149 | 150 | func (au *AttachmentUpdate) sqlSave(ctx context.Context) (n int, err error) { 151 | if err := au.check(); err != nil { 152 | return n, err 153 | } 154 | _spec := sqlgraph.NewUpdateSpec(attachment.Table, attachment.Columns, sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeString)) 155 | if ps := au.mutation.predicates; len(ps) > 0 { 156 | _spec.Predicate = func(selector *sql.Selector) { 157 | for i := range ps { 158 | ps[i](selector) 159 | } 160 | } 161 | } 162 | if value, ok := au.mutation.Filename(); ok { 163 | _spec.SetField(attachment.FieldFilename, field.TypeString, value) 164 | } 165 | if value, ok := au.mutation.Filepath(); ok { 166 | _spec.SetField(attachment.FieldFilepath, field.TypeString, value) 167 | } 168 | if value, ok := au.mutation.ContentType(); ok { 169 | _spec.SetField(attachment.FieldContentType, field.TypeString, value) 170 | } 171 | if au.mutation.OwnerCleared() { 172 | edge := &sqlgraph.EdgeSpec{ 173 | Rel: sqlgraph.M2O, 174 | Inverse: true, 175 | Table: attachment.OwnerTable, 176 | Columns: []string{attachment.OwnerColumn}, 177 | Bidi: false, 178 | Target: &sqlgraph.EdgeTarget{ 179 | IDSpec: sqlgraph.NewFieldSpec(envelope.FieldID, field.TypeInt), 180 | }, 181 | } 182 | _spec.Edges.Clear = append(_spec.Edges.Clear, edge) 183 | } 184 | if nodes := au.mutation.OwnerIDs(); len(nodes) > 0 { 185 | edge := &sqlgraph.EdgeSpec{ 186 | Rel: sqlgraph.M2O, 187 | Inverse: true, 188 | Table: attachment.OwnerTable, 189 | Columns: []string{attachment.OwnerColumn}, 190 | Bidi: false, 191 | Target: &sqlgraph.EdgeTarget{ 192 | IDSpec: sqlgraph.NewFieldSpec(envelope.FieldID, field.TypeInt), 193 | }, 194 | } 195 | for _, k := range nodes { 196 | edge.Target.Nodes = append(edge.Target.Nodes, k) 197 | } 198 | _spec.Edges.Add = append(_spec.Edges.Add, edge) 199 | } 200 | if n, err = sqlgraph.UpdateNodes(ctx, au.driver, _spec); err != nil { 201 | if _, ok := err.(*sqlgraph.NotFoundError); ok { 202 | err = &NotFoundError{attachment.Label} 203 | } else if sqlgraph.IsConstraintError(err) { 204 | err = &ConstraintError{msg: err.Error(), wrap: err} 205 | } 206 | return 0, err 207 | } 208 | au.mutation.done = true 209 | return n, nil 210 | } 211 | 212 | // AttachmentUpdateOne is the builder for updating a single Attachment entity. 213 | type AttachmentUpdateOne struct { 214 | config 215 | fields []string 216 | hooks []Hook 217 | mutation *AttachmentMutation 218 | } 219 | 220 | // SetFilename sets the "filename" field. 221 | func (auo *AttachmentUpdateOne) SetFilename(s string) *AttachmentUpdateOne { 222 | auo.mutation.SetFilename(s) 223 | return auo 224 | } 225 | 226 | // SetNillableFilename sets the "filename" field if the given value is not nil. 227 | func (auo *AttachmentUpdateOne) SetNillableFilename(s *string) *AttachmentUpdateOne { 228 | if s != nil { 229 | auo.SetFilename(*s) 230 | } 231 | return auo 232 | } 233 | 234 | // SetFilepath sets the "filepath" field. 235 | func (auo *AttachmentUpdateOne) SetFilepath(s string) *AttachmentUpdateOne { 236 | auo.mutation.SetFilepath(s) 237 | return auo 238 | } 239 | 240 | // SetNillableFilepath sets the "filepath" field if the given value is not nil. 241 | func (auo *AttachmentUpdateOne) SetNillableFilepath(s *string) *AttachmentUpdateOne { 242 | if s != nil { 243 | auo.SetFilepath(*s) 244 | } 245 | return auo 246 | } 247 | 248 | // SetContentType sets the "contentType" field. 249 | func (auo *AttachmentUpdateOne) SetContentType(s string) *AttachmentUpdateOne { 250 | auo.mutation.SetContentType(s) 251 | return auo 252 | } 253 | 254 | // SetNillableContentType sets the "contentType" field if the given value is not nil. 255 | func (auo *AttachmentUpdateOne) SetNillableContentType(s *string) *AttachmentUpdateOne { 256 | if s != nil { 257 | auo.SetContentType(*s) 258 | } 259 | return auo 260 | } 261 | 262 | // SetOwnerID sets the "owner" edge to the Envelope entity by ID. 263 | func (auo *AttachmentUpdateOne) SetOwnerID(id int) *AttachmentUpdateOne { 264 | auo.mutation.SetOwnerID(id) 265 | return auo 266 | } 267 | 268 | // SetNillableOwnerID sets the "owner" edge to the Envelope entity by ID if the given value is not nil. 269 | func (auo *AttachmentUpdateOne) SetNillableOwnerID(id *int) *AttachmentUpdateOne { 270 | if id != nil { 271 | auo = auo.SetOwnerID(*id) 272 | } 273 | return auo 274 | } 275 | 276 | // SetOwner sets the "owner" edge to the Envelope entity. 277 | func (auo *AttachmentUpdateOne) SetOwner(e *Envelope) *AttachmentUpdateOne { 278 | return auo.SetOwnerID(e.ID) 279 | } 280 | 281 | // Mutation returns the AttachmentMutation object of the builder. 282 | func (auo *AttachmentUpdateOne) Mutation() *AttachmentMutation { 283 | return auo.mutation 284 | } 285 | 286 | // ClearOwner clears the "owner" edge to the Envelope entity. 287 | func (auo *AttachmentUpdateOne) ClearOwner() *AttachmentUpdateOne { 288 | auo.mutation.ClearOwner() 289 | return auo 290 | } 291 | 292 | // Where appends a list predicates to the AttachmentUpdate builder. 293 | func (auo *AttachmentUpdateOne) Where(ps ...predicate.Attachment) *AttachmentUpdateOne { 294 | auo.mutation.Where(ps...) 295 | return auo 296 | } 297 | 298 | // Select allows selecting one or more fields (columns) of the returned entity. 299 | // The default is selecting all fields defined in the entity schema. 300 | func (auo *AttachmentUpdateOne) Select(field string, fields ...string) *AttachmentUpdateOne { 301 | auo.fields = append([]string{field}, fields...) 302 | return auo 303 | } 304 | 305 | // Save executes the query and returns the updated Attachment entity. 306 | func (auo *AttachmentUpdateOne) Save(ctx context.Context) (*Attachment, error) { 307 | return withHooks(ctx, auo.sqlSave, auo.mutation, auo.hooks) 308 | } 309 | 310 | // SaveX is like Save, but panics if an error occurs. 311 | func (auo *AttachmentUpdateOne) SaveX(ctx context.Context) *Attachment { 312 | node, err := auo.Save(ctx) 313 | if err != nil { 314 | panic(err) 315 | } 316 | return node 317 | } 318 | 319 | // Exec executes the query on the entity. 320 | func (auo *AttachmentUpdateOne) Exec(ctx context.Context) error { 321 | _, err := auo.Save(ctx) 322 | return err 323 | } 324 | 325 | // ExecX is like Exec, but panics if an error occurs. 326 | func (auo *AttachmentUpdateOne) ExecX(ctx context.Context) { 327 | if err := auo.Exec(ctx); err != nil { 328 | panic(err) 329 | } 330 | } 331 | 332 | // check runs all checks and user-defined validators on the builder. 333 | func (auo *AttachmentUpdateOne) check() error { 334 | if v, ok := auo.mutation.Filename(); ok { 335 | if err := attachment.FilenameValidator(v); err != nil { 336 | return &ValidationError{Name: "filename", err: fmt.Errorf(`ent: validator failed for field "Attachment.filename": %w`, err)} 337 | } 338 | } 339 | if v, ok := auo.mutation.Filepath(); ok { 340 | if err := attachment.FilepathValidator(v); err != nil { 341 | return &ValidationError{Name: "filepath", err: fmt.Errorf(`ent: validator failed for field "Attachment.filepath": %w`, err)} 342 | } 343 | } 344 | if v, ok := auo.mutation.ContentType(); ok { 345 | if err := attachment.ContentTypeValidator(v); err != nil { 346 | return &ValidationError{Name: "contentType", err: fmt.Errorf(`ent: validator failed for field "Attachment.contentType": %w`, err)} 347 | } 348 | } 349 | return nil 350 | } 351 | 352 | func (auo *AttachmentUpdateOne) sqlSave(ctx context.Context) (_node *Attachment, err error) { 353 | if err := auo.check(); err != nil { 354 | return _node, err 355 | } 356 | _spec := sqlgraph.NewUpdateSpec(attachment.Table, attachment.Columns, sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeString)) 357 | id, ok := auo.mutation.ID() 358 | if !ok { 359 | return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "Attachment.id" for update`)} 360 | } 361 | _spec.Node.ID.Value = id 362 | if fields := auo.fields; len(fields) > 0 { 363 | _spec.Node.Columns = make([]string, 0, len(fields)) 364 | _spec.Node.Columns = append(_spec.Node.Columns, attachment.FieldID) 365 | for _, f := range fields { 366 | if !attachment.ValidColumn(f) { 367 | return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} 368 | } 369 | if f != attachment.FieldID { 370 | _spec.Node.Columns = append(_spec.Node.Columns, f) 371 | } 372 | } 373 | } 374 | if ps := auo.mutation.predicates; len(ps) > 0 { 375 | _spec.Predicate = func(selector *sql.Selector) { 376 | for i := range ps { 377 | ps[i](selector) 378 | } 379 | } 380 | } 381 | if value, ok := auo.mutation.Filename(); ok { 382 | _spec.SetField(attachment.FieldFilename, field.TypeString, value) 383 | } 384 | if value, ok := auo.mutation.Filepath(); ok { 385 | _spec.SetField(attachment.FieldFilepath, field.TypeString, value) 386 | } 387 | if value, ok := auo.mutation.ContentType(); ok { 388 | _spec.SetField(attachment.FieldContentType, field.TypeString, value) 389 | } 390 | if auo.mutation.OwnerCleared() { 391 | edge := &sqlgraph.EdgeSpec{ 392 | Rel: sqlgraph.M2O, 393 | Inverse: true, 394 | Table: attachment.OwnerTable, 395 | Columns: []string{attachment.OwnerColumn}, 396 | Bidi: false, 397 | Target: &sqlgraph.EdgeTarget{ 398 | IDSpec: sqlgraph.NewFieldSpec(envelope.FieldID, field.TypeInt), 399 | }, 400 | } 401 | _spec.Edges.Clear = append(_spec.Edges.Clear, edge) 402 | } 403 | if nodes := auo.mutation.OwnerIDs(); len(nodes) > 0 { 404 | edge := &sqlgraph.EdgeSpec{ 405 | Rel: sqlgraph.M2O, 406 | Inverse: true, 407 | Table: attachment.OwnerTable, 408 | Columns: []string{attachment.OwnerColumn}, 409 | Bidi: false, 410 | Target: &sqlgraph.EdgeTarget{ 411 | IDSpec: sqlgraph.NewFieldSpec(envelope.FieldID, field.TypeInt), 412 | }, 413 | } 414 | for _, k := range nodes { 415 | edge.Target.Nodes = append(edge.Target.Nodes, k) 416 | } 417 | _spec.Edges.Add = append(_spec.Edges.Add, edge) 418 | } 419 | _node = &Attachment{config: auo.config} 420 | _spec.Assign = _node.assignValues 421 | _spec.ScanValues = _node.scanValues 422 | if err = sqlgraph.UpdateNode(ctx, auo.driver, _spec); err != nil { 423 | if _, ok := err.(*sqlgraph.NotFoundError); ok { 424 | err = &NotFoundError{attachment.Label} 425 | } else if sqlgraph.IsConstraintError(err) { 426 | err = &ConstraintError{msg: err.Error(), wrap: err} 427 | } 428 | return nil, err 429 | } 430 | auo.mutation.done = true 431 | return _node, nil 432 | } 433 | -------------------------------------------------------------------------------- /ent/enttest/enttest.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package enttest 4 | 5 | import ( 6 | "context" 7 | 8 | "tmail/ent" 9 | // required by schema hooks. 10 | _ "tmail/ent/runtime" 11 | 12 | "tmail/ent/migrate" 13 | 14 | "entgo.io/ent/dialect/sql/schema" 15 | ) 16 | 17 | type ( 18 | // TestingT is the interface that is shared between 19 | // testing.T and testing.B and used by enttest. 20 | TestingT interface { 21 | FailNow() 22 | Error(...any) 23 | } 24 | 25 | // Option configures client creation. 26 | Option func(*options) 27 | 28 | options struct { 29 | opts []ent.Option 30 | migrateOpts []schema.MigrateOption 31 | } 32 | ) 33 | 34 | // WithOptions forwards options to client creation. 35 | func WithOptions(opts ...ent.Option) Option { 36 | return func(o *options) { 37 | o.opts = append(o.opts, opts...) 38 | } 39 | } 40 | 41 | // WithMigrateOptions forwards options to auto migration. 42 | func WithMigrateOptions(opts ...schema.MigrateOption) Option { 43 | return func(o *options) { 44 | o.migrateOpts = append(o.migrateOpts, opts...) 45 | } 46 | } 47 | 48 | func newOptions(opts []Option) *options { 49 | o := &options{} 50 | for _, opt := range opts { 51 | opt(o) 52 | } 53 | return o 54 | } 55 | 56 | // Open calls ent.Open and auto-run migration. 57 | func Open(t TestingT, driverName, dataSourceName string, opts ...Option) *ent.Client { 58 | o := newOptions(opts) 59 | c, err := ent.Open(driverName, dataSourceName, o.opts...) 60 | if err != nil { 61 | t.Error(err) 62 | t.FailNow() 63 | } 64 | migrateSchema(t, c, o) 65 | return c 66 | } 67 | 68 | // NewClient calls ent.NewClient and auto-run migration. 69 | func NewClient(t TestingT, opts ...Option) *ent.Client { 70 | o := newOptions(opts) 71 | c := ent.NewClient(o.opts...) 72 | migrateSchema(t, c, o) 73 | return c 74 | } 75 | func migrateSchema(t TestingT, c *ent.Client, o *options) { 76 | tables, err := schema.CopyTables(migrate.Tables) 77 | if err != nil { 78 | t.Error(err) 79 | t.FailNow() 80 | } 81 | if err := migrate.Create(context.Background(), c.Schema, tables, o.migrateOpts...); err != nil { 82 | t.Error(err) 83 | t.FailNow() 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /ent/envelope.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "fmt" 7 | "strings" 8 | "time" 9 | "tmail/ent/envelope" 10 | 11 | "entgo.io/ent" 12 | "entgo.io/ent/dialect/sql" 13 | ) 14 | 15 | // Envelope is the model entity for the Envelope schema. 16 | type Envelope struct { 17 | config `json:"-"` 18 | // ID of the ent. 19 | ID int `json:"id,omitempty"` 20 | // To holds the value of the "to" field. 21 | To string `json:"to,omitempty"` 22 | // From holds the value of the "from" field. 23 | From string `json:"from,omitempty"` 24 | // Subject holds the value of the "subject" field. 25 | Subject string `json:"subject,omitempty"` 26 | // Content holds the value of the "content" field. 27 | Content string `json:"content,omitempty"` 28 | // CreatedAt holds the value of the "created_at" field. 29 | CreatedAt time.Time `json:"created_at,omitempty"` 30 | // Edges holds the relations/edges for other nodes in the graph. 31 | // The values are being populated by the EnvelopeQuery when eager-loading is set. 32 | Edges EnvelopeEdges `json:"edges"` 33 | selectValues sql.SelectValues 34 | } 35 | 36 | // EnvelopeEdges holds the relations/edges for other nodes in the graph. 37 | type EnvelopeEdges struct { 38 | // Attachments holds the value of the attachments edge. 39 | Attachments []*Attachment `json:"attachments,omitempty"` 40 | // loadedTypes holds the information for reporting if a 41 | // type was loaded (or requested) in eager-loading or not. 42 | loadedTypes [1]bool 43 | } 44 | 45 | // AttachmentsOrErr returns the Attachments value or an error if the edge 46 | // was not loaded in eager-loading. 47 | func (e EnvelopeEdges) AttachmentsOrErr() ([]*Attachment, error) { 48 | if e.loadedTypes[0] { 49 | return e.Attachments, nil 50 | } 51 | return nil, &NotLoadedError{edge: "attachments"} 52 | } 53 | 54 | // scanValues returns the types for scanning values from sql.Rows. 55 | func (*Envelope) scanValues(columns []string) ([]any, error) { 56 | values := make([]any, len(columns)) 57 | for i := range columns { 58 | switch columns[i] { 59 | case envelope.FieldID: 60 | values[i] = new(sql.NullInt64) 61 | case envelope.FieldTo, envelope.FieldFrom, envelope.FieldSubject, envelope.FieldContent: 62 | values[i] = new(sql.NullString) 63 | case envelope.FieldCreatedAt: 64 | values[i] = new(sql.NullTime) 65 | default: 66 | values[i] = new(sql.UnknownType) 67 | } 68 | } 69 | return values, nil 70 | } 71 | 72 | // assignValues assigns the values that were returned from sql.Rows (after scanning) 73 | // to the Envelope fields. 74 | func (e *Envelope) assignValues(columns []string, values []any) error { 75 | if m, n := len(values), len(columns); m < n { 76 | return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) 77 | } 78 | for i := range columns { 79 | switch columns[i] { 80 | case envelope.FieldID: 81 | value, ok := values[i].(*sql.NullInt64) 82 | if !ok { 83 | return fmt.Errorf("unexpected type %T for field id", value) 84 | } 85 | e.ID = int(value.Int64) 86 | case envelope.FieldTo: 87 | if value, ok := values[i].(*sql.NullString); !ok { 88 | return fmt.Errorf("unexpected type %T for field to", values[i]) 89 | } else if value.Valid { 90 | e.To = value.String 91 | } 92 | case envelope.FieldFrom: 93 | if value, ok := values[i].(*sql.NullString); !ok { 94 | return fmt.Errorf("unexpected type %T for field from", values[i]) 95 | } else if value.Valid { 96 | e.From = value.String 97 | } 98 | case envelope.FieldSubject: 99 | if value, ok := values[i].(*sql.NullString); !ok { 100 | return fmt.Errorf("unexpected type %T for field subject", values[i]) 101 | } else if value.Valid { 102 | e.Subject = value.String 103 | } 104 | case envelope.FieldContent: 105 | if value, ok := values[i].(*sql.NullString); !ok { 106 | return fmt.Errorf("unexpected type %T for field content", values[i]) 107 | } else if value.Valid { 108 | e.Content = value.String 109 | } 110 | case envelope.FieldCreatedAt: 111 | if value, ok := values[i].(*sql.NullTime); !ok { 112 | return fmt.Errorf("unexpected type %T for field created_at", values[i]) 113 | } else if value.Valid { 114 | e.CreatedAt = value.Time 115 | } 116 | default: 117 | e.selectValues.Set(columns[i], values[i]) 118 | } 119 | } 120 | return nil 121 | } 122 | 123 | // Value returns the ent.Value that was dynamically selected and assigned to the Envelope. 124 | // This includes values selected through modifiers, order, etc. 125 | func (e *Envelope) Value(name string) (ent.Value, error) { 126 | return e.selectValues.Get(name) 127 | } 128 | 129 | // QueryAttachments queries the "attachments" edge of the Envelope entity. 130 | func (e *Envelope) QueryAttachments() *AttachmentQuery { 131 | return NewEnvelopeClient(e.config).QueryAttachments(e) 132 | } 133 | 134 | // Update returns a builder for updating this Envelope. 135 | // Note that you need to call Envelope.Unwrap() before calling this method if this Envelope 136 | // was returned from a transaction, and the transaction was committed or rolled back. 137 | func (e *Envelope) Update() *EnvelopeUpdateOne { 138 | return NewEnvelopeClient(e.config).UpdateOne(e) 139 | } 140 | 141 | // Unwrap unwraps the Envelope entity that was returned from a transaction after it was closed, 142 | // so that all future queries will be executed through the driver which created the transaction. 143 | func (e *Envelope) Unwrap() *Envelope { 144 | _tx, ok := e.config.driver.(*txDriver) 145 | if !ok { 146 | panic("ent: Envelope is not a transactional entity") 147 | } 148 | e.config.driver = _tx.drv 149 | return e 150 | } 151 | 152 | // String implements the fmt.Stringer. 153 | func (e *Envelope) String() string { 154 | var builder strings.Builder 155 | builder.WriteString("Envelope(") 156 | builder.WriteString(fmt.Sprintf("id=%v, ", e.ID)) 157 | builder.WriteString("to=") 158 | builder.WriteString(e.To) 159 | builder.WriteString(", ") 160 | builder.WriteString("from=") 161 | builder.WriteString(e.From) 162 | builder.WriteString(", ") 163 | builder.WriteString("subject=") 164 | builder.WriteString(e.Subject) 165 | builder.WriteString(", ") 166 | builder.WriteString("content=") 167 | builder.WriteString(e.Content) 168 | builder.WriteString(", ") 169 | builder.WriteString("created_at=") 170 | builder.WriteString(e.CreatedAt.Format(time.ANSIC)) 171 | builder.WriteByte(')') 172 | return builder.String() 173 | } 174 | 175 | // Envelopes is a parsable slice of Envelope. 176 | type Envelopes []*Envelope 177 | -------------------------------------------------------------------------------- /ent/envelope/envelope.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package envelope 4 | 5 | import ( 6 | "time" 7 | 8 | "entgo.io/ent/dialect/sql" 9 | "entgo.io/ent/dialect/sql/sqlgraph" 10 | ) 11 | 12 | const ( 13 | // Label holds the string label denoting the envelope type in the database. 14 | Label = "envelope" 15 | // FieldID holds the string denoting the id field in the database. 16 | FieldID = "id" 17 | // FieldTo holds the string denoting the to field in the database. 18 | FieldTo = "to" 19 | // FieldFrom holds the string denoting the from field in the database. 20 | FieldFrom = "from" 21 | // FieldSubject holds the string denoting the subject field in the database. 22 | FieldSubject = "subject" 23 | // FieldContent holds the string denoting the content field in the database. 24 | FieldContent = "content" 25 | // FieldCreatedAt holds the string denoting the created_at field in the database. 26 | FieldCreatedAt = "created_at" 27 | // EdgeAttachments holds the string denoting the attachments edge name in mutations. 28 | EdgeAttachments = "attachments" 29 | // Table holds the table name of the envelope in the database. 30 | Table = "envelopes" 31 | // AttachmentsTable is the table that holds the attachments relation/edge. 32 | AttachmentsTable = "attachments" 33 | // AttachmentsInverseTable is the table name for the Attachment entity. 34 | // It exists in this package in order to avoid circular dependency with the "attachment" package. 35 | AttachmentsInverseTable = "attachments" 36 | // AttachmentsColumn is the table column denoting the attachments relation/edge. 37 | AttachmentsColumn = "envelope_attachments" 38 | ) 39 | 40 | // Columns holds all SQL columns for envelope fields. 41 | var Columns = []string{ 42 | FieldID, 43 | FieldTo, 44 | FieldFrom, 45 | FieldSubject, 46 | FieldContent, 47 | FieldCreatedAt, 48 | } 49 | 50 | // ValidColumn reports if the column name is valid (part of the table columns). 51 | func ValidColumn(column string) bool { 52 | for i := range Columns { 53 | if column == Columns[i] { 54 | return true 55 | } 56 | } 57 | return false 58 | } 59 | 60 | var ( 61 | // ToValidator is a validator for the "to" field. It is called by the builders before save. 62 | ToValidator func(string) error 63 | // FromValidator is a validator for the "from" field. It is called by the builders before save. 64 | FromValidator func(string) error 65 | // SubjectValidator is a validator for the "subject" field. It is called by the builders before save. 66 | SubjectValidator func(string) error 67 | // ContentValidator is a validator for the "content" field. It is called by the builders before save. 68 | ContentValidator func(string) error 69 | // DefaultCreatedAt holds the default value on creation for the "created_at" field. 70 | DefaultCreatedAt func() time.Time 71 | ) 72 | 73 | // OrderOption defines the ordering options for the Envelope queries. 74 | type OrderOption func(*sql.Selector) 75 | 76 | // ByID orders the results by the id field. 77 | func ByID(opts ...sql.OrderTermOption) OrderOption { 78 | return sql.OrderByField(FieldID, opts...).ToFunc() 79 | } 80 | 81 | // ByTo orders the results by the to field. 82 | func ByTo(opts ...sql.OrderTermOption) OrderOption { 83 | return sql.OrderByField(FieldTo, opts...).ToFunc() 84 | } 85 | 86 | // ByFrom orders the results by the from field. 87 | func ByFrom(opts ...sql.OrderTermOption) OrderOption { 88 | return sql.OrderByField(FieldFrom, opts...).ToFunc() 89 | } 90 | 91 | // BySubject orders the results by the subject field. 92 | func BySubject(opts ...sql.OrderTermOption) OrderOption { 93 | return sql.OrderByField(FieldSubject, opts...).ToFunc() 94 | } 95 | 96 | // ByContent orders the results by the content field. 97 | func ByContent(opts ...sql.OrderTermOption) OrderOption { 98 | return sql.OrderByField(FieldContent, opts...).ToFunc() 99 | } 100 | 101 | // ByCreatedAt orders the results by the created_at field. 102 | func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { 103 | return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() 104 | } 105 | 106 | // ByAttachmentsCount orders the results by attachments count. 107 | func ByAttachmentsCount(opts ...sql.OrderTermOption) OrderOption { 108 | return func(s *sql.Selector) { 109 | sqlgraph.OrderByNeighborsCount(s, newAttachmentsStep(), opts...) 110 | } 111 | } 112 | 113 | // ByAttachments orders the results by attachments terms. 114 | func ByAttachments(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { 115 | return func(s *sql.Selector) { 116 | sqlgraph.OrderByNeighborTerms(s, newAttachmentsStep(), append([]sql.OrderTerm{term}, terms...)...) 117 | } 118 | } 119 | func newAttachmentsStep() *sqlgraph.Step { 120 | return sqlgraph.NewStep( 121 | sqlgraph.From(Table, FieldID), 122 | sqlgraph.To(AttachmentsInverseTable, FieldID), 123 | sqlgraph.Edge(sqlgraph.O2M, false, AttachmentsTable, AttachmentsColumn), 124 | ) 125 | } 126 | -------------------------------------------------------------------------------- /ent/envelope/where.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package envelope 4 | 5 | import ( 6 | "time" 7 | "tmail/ent/predicate" 8 | 9 | "entgo.io/ent/dialect/sql" 10 | "entgo.io/ent/dialect/sql/sqlgraph" 11 | ) 12 | 13 | // ID filters vertices based on their ID field. 14 | func ID(id int) predicate.Envelope { 15 | return predicate.Envelope(sql.FieldEQ(FieldID, id)) 16 | } 17 | 18 | // IDEQ applies the EQ predicate on the ID field. 19 | func IDEQ(id int) predicate.Envelope { 20 | return predicate.Envelope(sql.FieldEQ(FieldID, id)) 21 | } 22 | 23 | // IDNEQ applies the NEQ predicate on the ID field. 24 | func IDNEQ(id int) predicate.Envelope { 25 | return predicate.Envelope(sql.FieldNEQ(FieldID, id)) 26 | } 27 | 28 | // IDIn applies the In predicate on the ID field. 29 | func IDIn(ids ...int) predicate.Envelope { 30 | return predicate.Envelope(sql.FieldIn(FieldID, ids...)) 31 | } 32 | 33 | // IDNotIn applies the NotIn predicate on the ID field. 34 | func IDNotIn(ids ...int) predicate.Envelope { 35 | return predicate.Envelope(sql.FieldNotIn(FieldID, ids...)) 36 | } 37 | 38 | // IDGT applies the GT predicate on the ID field. 39 | func IDGT(id int) predicate.Envelope { 40 | return predicate.Envelope(sql.FieldGT(FieldID, id)) 41 | } 42 | 43 | // IDGTE applies the GTE predicate on the ID field. 44 | func IDGTE(id int) predicate.Envelope { 45 | return predicate.Envelope(sql.FieldGTE(FieldID, id)) 46 | } 47 | 48 | // IDLT applies the LT predicate on the ID field. 49 | func IDLT(id int) predicate.Envelope { 50 | return predicate.Envelope(sql.FieldLT(FieldID, id)) 51 | } 52 | 53 | // IDLTE applies the LTE predicate on the ID field. 54 | func IDLTE(id int) predicate.Envelope { 55 | return predicate.Envelope(sql.FieldLTE(FieldID, id)) 56 | } 57 | 58 | // To applies equality check predicate on the "to" field. It's identical to ToEQ. 59 | func To(v string) predicate.Envelope { 60 | return predicate.Envelope(sql.FieldEQ(FieldTo, v)) 61 | } 62 | 63 | // From applies equality check predicate on the "from" field. It's identical to FromEQ. 64 | func From(v string) predicate.Envelope { 65 | return predicate.Envelope(sql.FieldEQ(FieldFrom, v)) 66 | } 67 | 68 | // Subject applies equality check predicate on the "subject" field. It's identical to SubjectEQ. 69 | func Subject(v string) predicate.Envelope { 70 | return predicate.Envelope(sql.FieldEQ(FieldSubject, v)) 71 | } 72 | 73 | // Content applies equality check predicate on the "content" field. It's identical to ContentEQ. 74 | func Content(v string) predicate.Envelope { 75 | return predicate.Envelope(sql.FieldEQ(FieldContent, v)) 76 | } 77 | 78 | // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. 79 | func CreatedAt(v time.Time) predicate.Envelope { 80 | return predicate.Envelope(sql.FieldEQ(FieldCreatedAt, v)) 81 | } 82 | 83 | // ToEQ applies the EQ predicate on the "to" field. 84 | func ToEQ(v string) predicate.Envelope { 85 | return predicate.Envelope(sql.FieldEQ(FieldTo, v)) 86 | } 87 | 88 | // ToNEQ applies the NEQ predicate on the "to" field. 89 | func ToNEQ(v string) predicate.Envelope { 90 | return predicate.Envelope(sql.FieldNEQ(FieldTo, v)) 91 | } 92 | 93 | // ToIn applies the In predicate on the "to" field. 94 | func ToIn(vs ...string) predicate.Envelope { 95 | return predicate.Envelope(sql.FieldIn(FieldTo, vs...)) 96 | } 97 | 98 | // ToNotIn applies the NotIn predicate on the "to" field. 99 | func ToNotIn(vs ...string) predicate.Envelope { 100 | return predicate.Envelope(sql.FieldNotIn(FieldTo, vs...)) 101 | } 102 | 103 | // ToGT applies the GT predicate on the "to" field. 104 | func ToGT(v string) predicate.Envelope { 105 | return predicate.Envelope(sql.FieldGT(FieldTo, v)) 106 | } 107 | 108 | // ToGTE applies the GTE predicate on the "to" field. 109 | func ToGTE(v string) predicate.Envelope { 110 | return predicate.Envelope(sql.FieldGTE(FieldTo, v)) 111 | } 112 | 113 | // ToLT applies the LT predicate on the "to" field. 114 | func ToLT(v string) predicate.Envelope { 115 | return predicate.Envelope(sql.FieldLT(FieldTo, v)) 116 | } 117 | 118 | // ToLTE applies the LTE predicate on the "to" field. 119 | func ToLTE(v string) predicate.Envelope { 120 | return predicate.Envelope(sql.FieldLTE(FieldTo, v)) 121 | } 122 | 123 | // ToContains applies the Contains predicate on the "to" field. 124 | func ToContains(v string) predicate.Envelope { 125 | return predicate.Envelope(sql.FieldContains(FieldTo, v)) 126 | } 127 | 128 | // ToHasPrefix applies the HasPrefix predicate on the "to" field. 129 | func ToHasPrefix(v string) predicate.Envelope { 130 | return predicate.Envelope(sql.FieldHasPrefix(FieldTo, v)) 131 | } 132 | 133 | // ToHasSuffix applies the HasSuffix predicate on the "to" field. 134 | func ToHasSuffix(v string) predicate.Envelope { 135 | return predicate.Envelope(sql.FieldHasSuffix(FieldTo, v)) 136 | } 137 | 138 | // ToEqualFold applies the EqualFold predicate on the "to" field. 139 | func ToEqualFold(v string) predicate.Envelope { 140 | return predicate.Envelope(sql.FieldEqualFold(FieldTo, v)) 141 | } 142 | 143 | // ToContainsFold applies the ContainsFold predicate on the "to" field. 144 | func ToContainsFold(v string) predicate.Envelope { 145 | return predicate.Envelope(sql.FieldContainsFold(FieldTo, v)) 146 | } 147 | 148 | // FromEQ applies the EQ predicate on the "from" field. 149 | func FromEQ(v string) predicate.Envelope { 150 | return predicate.Envelope(sql.FieldEQ(FieldFrom, v)) 151 | } 152 | 153 | // FromNEQ applies the NEQ predicate on the "from" field. 154 | func FromNEQ(v string) predicate.Envelope { 155 | return predicate.Envelope(sql.FieldNEQ(FieldFrom, v)) 156 | } 157 | 158 | // FromIn applies the In predicate on the "from" field. 159 | func FromIn(vs ...string) predicate.Envelope { 160 | return predicate.Envelope(sql.FieldIn(FieldFrom, vs...)) 161 | } 162 | 163 | // FromNotIn applies the NotIn predicate on the "from" field. 164 | func FromNotIn(vs ...string) predicate.Envelope { 165 | return predicate.Envelope(sql.FieldNotIn(FieldFrom, vs...)) 166 | } 167 | 168 | // FromGT applies the GT predicate on the "from" field. 169 | func FromGT(v string) predicate.Envelope { 170 | return predicate.Envelope(sql.FieldGT(FieldFrom, v)) 171 | } 172 | 173 | // FromGTE applies the GTE predicate on the "from" field. 174 | func FromGTE(v string) predicate.Envelope { 175 | return predicate.Envelope(sql.FieldGTE(FieldFrom, v)) 176 | } 177 | 178 | // FromLT applies the LT predicate on the "from" field. 179 | func FromLT(v string) predicate.Envelope { 180 | return predicate.Envelope(sql.FieldLT(FieldFrom, v)) 181 | } 182 | 183 | // FromLTE applies the LTE predicate on the "from" field. 184 | func FromLTE(v string) predicate.Envelope { 185 | return predicate.Envelope(sql.FieldLTE(FieldFrom, v)) 186 | } 187 | 188 | // FromContains applies the Contains predicate on the "from" field. 189 | func FromContains(v string) predicate.Envelope { 190 | return predicate.Envelope(sql.FieldContains(FieldFrom, v)) 191 | } 192 | 193 | // FromHasPrefix applies the HasPrefix predicate on the "from" field. 194 | func FromHasPrefix(v string) predicate.Envelope { 195 | return predicate.Envelope(sql.FieldHasPrefix(FieldFrom, v)) 196 | } 197 | 198 | // FromHasSuffix applies the HasSuffix predicate on the "from" field. 199 | func FromHasSuffix(v string) predicate.Envelope { 200 | return predicate.Envelope(sql.FieldHasSuffix(FieldFrom, v)) 201 | } 202 | 203 | // FromEqualFold applies the EqualFold predicate on the "from" field. 204 | func FromEqualFold(v string) predicate.Envelope { 205 | return predicate.Envelope(sql.FieldEqualFold(FieldFrom, v)) 206 | } 207 | 208 | // FromContainsFold applies the ContainsFold predicate on the "from" field. 209 | func FromContainsFold(v string) predicate.Envelope { 210 | return predicate.Envelope(sql.FieldContainsFold(FieldFrom, v)) 211 | } 212 | 213 | // SubjectEQ applies the EQ predicate on the "subject" field. 214 | func SubjectEQ(v string) predicate.Envelope { 215 | return predicate.Envelope(sql.FieldEQ(FieldSubject, v)) 216 | } 217 | 218 | // SubjectNEQ applies the NEQ predicate on the "subject" field. 219 | func SubjectNEQ(v string) predicate.Envelope { 220 | return predicate.Envelope(sql.FieldNEQ(FieldSubject, v)) 221 | } 222 | 223 | // SubjectIn applies the In predicate on the "subject" field. 224 | func SubjectIn(vs ...string) predicate.Envelope { 225 | return predicate.Envelope(sql.FieldIn(FieldSubject, vs...)) 226 | } 227 | 228 | // SubjectNotIn applies the NotIn predicate on the "subject" field. 229 | func SubjectNotIn(vs ...string) predicate.Envelope { 230 | return predicate.Envelope(sql.FieldNotIn(FieldSubject, vs...)) 231 | } 232 | 233 | // SubjectGT applies the GT predicate on the "subject" field. 234 | func SubjectGT(v string) predicate.Envelope { 235 | return predicate.Envelope(sql.FieldGT(FieldSubject, v)) 236 | } 237 | 238 | // SubjectGTE applies the GTE predicate on the "subject" field. 239 | func SubjectGTE(v string) predicate.Envelope { 240 | return predicate.Envelope(sql.FieldGTE(FieldSubject, v)) 241 | } 242 | 243 | // SubjectLT applies the LT predicate on the "subject" field. 244 | func SubjectLT(v string) predicate.Envelope { 245 | return predicate.Envelope(sql.FieldLT(FieldSubject, v)) 246 | } 247 | 248 | // SubjectLTE applies the LTE predicate on the "subject" field. 249 | func SubjectLTE(v string) predicate.Envelope { 250 | return predicate.Envelope(sql.FieldLTE(FieldSubject, v)) 251 | } 252 | 253 | // SubjectContains applies the Contains predicate on the "subject" field. 254 | func SubjectContains(v string) predicate.Envelope { 255 | return predicate.Envelope(sql.FieldContains(FieldSubject, v)) 256 | } 257 | 258 | // SubjectHasPrefix applies the HasPrefix predicate on the "subject" field. 259 | func SubjectHasPrefix(v string) predicate.Envelope { 260 | return predicate.Envelope(sql.FieldHasPrefix(FieldSubject, v)) 261 | } 262 | 263 | // SubjectHasSuffix applies the HasSuffix predicate on the "subject" field. 264 | func SubjectHasSuffix(v string) predicate.Envelope { 265 | return predicate.Envelope(sql.FieldHasSuffix(FieldSubject, v)) 266 | } 267 | 268 | // SubjectEqualFold applies the EqualFold predicate on the "subject" field. 269 | func SubjectEqualFold(v string) predicate.Envelope { 270 | return predicate.Envelope(sql.FieldEqualFold(FieldSubject, v)) 271 | } 272 | 273 | // SubjectContainsFold applies the ContainsFold predicate on the "subject" field. 274 | func SubjectContainsFold(v string) predicate.Envelope { 275 | return predicate.Envelope(sql.FieldContainsFold(FieldSubject, v)) 276 | } 277 | 278 | // ContentEQ applies the EQ predicate on the "content" field. 279 | func ContentEQ(v string) predicate.Envelope { 280 | return predicate.Envelope(sql.FieldEQ(FieldContent, v)) 281 | } 282 | 283 | // ContentNEQ applies the NEQ predicate on the "content" field. 284 | func ContentNEQ(v string) predicate.Envelope { 285 | return predicate.Envelope(sql.FieldNEQ(FieldContent, v)) 286 | } 287 | 288 | // ContentIn applies the In predicate on the "content" field. 289 | func ContentIn(vs ...string) predicate.Envelope { 290 | return predicate.Envelope(sql.FieldIn(FieldContent, vs...)) 291 | } 292 | 293 | // ContentNotIn applies the NotIn predicate on the "content" field. 294 | func ContentNotIn(vs ...string) predicate.Envelope { 295 | return predicate.Envelope(sql.FieldNotIn(FieldContent, vs...)) 296 | } 297 | 298 | // ContentGT applies the GT predicate on the "content" field. 299 | func ContentGT(v string) predicate.Envelope { 300 | return predicate.Envelope(sql.FieldGT(FieldContent, v)) 301 | } 302 | 303 | // ContentGTE applies the GTE predicate on the "content" field. 304 | func ContentGTE(v string) predicate.Envelope { 305 | return predicate.Envelope(sql.FieldGTE(FieldContent, v)) 306 | } 307 | 308 | // ContentLT applies the LT predicate on the "content" field. 309 | func ContentLT(v string) predicate.Envelope { 310 | return predicate.Envelope(sql.FieldLT(FieldContent, v)) 311 | } 312 | 313 | // ContentLTE applies the LTE predicate on the "content" field. 314 | func ContentLTE(v string) predicate.Envelope { 315 | return predicate.Envelope(sql.FieldLTE(FieldContent, v)) 316 | } 317 | 318 | // ContentContains applies the Contains predicate on the "content" field. 319 | func ContentContains(v string) predicate.Envelope { 320 | return predicate.Envelope(sql.FieldContains(FieldContent, v)) 321 | } 322 | 323 | // ContentHasPrefix applies the HasPrefix predicate on the "content" field. 324 | func ContentHasPrefix(v string) predicate.Envelope { 325 | return predicate.Envelope(sql.FieldHasPrefix(FieldContent, v)) 326 | } 327 | 328 | // ContentHasSuffix applies the HasSuffix predicate on the "content" field. 329 | func ContentHasSuffix(v string) predicate.Envelope { 330 | return predicate.Envelope(sql.FieldHasSuffix(FieldContent, v)) 331 | } 332 | 333 | // ContentEqualFold applies the EqualFold predicate on the "content" field. 334 | func ContentEqualFold(v string) predicate.Envelope { 335 | return predicate.Envelope(sql.FieldEqualFold(FieldContent, v)) 336 | } 337 | 338 | // ContentContainsFold applies the ContainsFold predicate on the "content" field. 339 | func ContentContainsFold(v string) predicate.Envelope { 340 | return predicate.Envelope(sql.FieldContainsFold(FieldContent, v)) 341 | } 342 | 343 | // CreatedAtEQ applies the EQ predicate on the "created_at" field. 344 | func CreatedAtEQ(v time.Time) predicate.Envelope { 345 | return predicate.Envelope(sql.FieldEQ(FieldCreatedAt, v)) 346 | } 347 | 348 | // CreatedAtNEQ applies the NEQ predicate on the "created_at" field. 349 | func CreatedAtNEQ(v time.Time) predicate.Envelope { 350 | return predicate.Envelope(sql.FieldNEQ(FieldCreatedAt, v)) 351 | } 352 | 353 | // CreatedAtIn applies the In predicate on the "created_at" field. 354 | func CreatedAtIn(vs ...time.Time) predicate.Envelope { 355 | return predicate.Envelope(sql.FieldIn(FieldCreatedAt, vs...)) 356 | } 357 | 358 | // CreatedAtNotIn applies the NotIn predicate on the "created_at" field. 359 | func CreatedAtNotIn(vs ...time.Time) predicate.Envelope { 360 | return predicate.Envelope(sql.FieldNotIn(FieldCreatedAt, vs...)) 361 | } 362 | 363 | // CreatedAtGT applies the GT predicate on the "created_at" field. 364 | func CreatedAtGT(v time.Time) predicate.Envelope { 365 | return predicate.Envelope(sql.FieldGT(FieldCreatedAt, v)) 366 | } 367 | 368 | // CreatedAtGTE applies the GTE predicate on the "created_at" field. 369 | func CreatedAtGTE(v time.Time) predicate.Envelope { 370 | return predicate.Envelope(sql.FieldGTE(FieldCreatedAt, v)) 371 | } 372 | 373 | // CreatedAtLT applies the LT predicate on the "created_at" field. 374 | func CreatedAtLT(v time.Time) predicate.Envelope { 375 | return predicate.Envelope(sql.FieldLT(FieldCreatedAt, v)) 376 | } 377 | 378 | // CreatedAtLTE applies the LTE predicate on the "created_at" field. 379 | func CreatedAtLTE(v time.Time) predicate.Envelope { 380 | return predicate.Envelope(sql.FieldLTE(FieldCreatedAt, v)) 381 | } 382 | 383 | // HasAttachments applies the HasEdge predicate on the "attachments" edge. 384 | func HasAttachments() predicate.Envelope { 385 | return predicate.Envelope(func(s *sql.Selector) { 386 | step := sqlgraph.NewStep( 387 | sqlgraph.From(Table, FieldID), 388 | sqlgraph.Edge(sqlgraph.O2M, false, AttachmentsTable, AttachmentsColumn), 389 | ) 390 | sqlgraph.HasNeighbors(s, step) 391 | }) 392 | } 393 | 394 | // HasAttachmentsWith applies the HasEdge predicate on the "attachments" edge with a given conditions (other predicates). 395 | func HasAttachmentsWith(preds ...predicate.Attachment) predicate.Envelope { 396 | return predicate.Envelope(func(s *sql.Selector) { 397 | step := newAttachmentsStep() 398 | sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { 399 | for _, p := range preds { 400 | p(s) 401 | } 402 | }) 403 | }) 404 | } 405 | 406 | // And groups predicates with the AND operator between them. 407 | func And(predicates ...predicate.Envelope) predicate.Envelope { 408 | return predicate.Envelope(sql.AndPredicates(predicates...)) 409 | } 410 | 411 | // Or groups predicates with the OR operator between them. 412 | func Or(predicates ...predicate.Envelope) predicate.Envelope { 413 | return predicate.Envelope(sql.OrPredicates(predicates...)) 414 | } 415 | 416 | // Not applies the not operator on the given predicate. 417 | func Not(p predicate.Envelope) predicate.Envelope { 418 | return predicate.Envelope(sql.NotPredicates(p)) 419 | } 420 | -------------------------------------------------------------------------------- /ent/envelope_create.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | "errors" 8 | "fmt" 9 | "time" 10 | "tmail/ent/attachment" 11 | "tmail/ent/envelope" 12 | 13 | "entgo.io/ent/dialect/sql/sqlgraph" 14 | "entgo.io/ent/schema/field" 15 | ) 16 | 17 | // EnvelopeCreate is the builder for creating a Envelope entity. 18 | type EnvelopeCreate struct { 19 | config 20 | mutation *EnvelopeMutation 21 | hooks []Hook 22 | } 23 | 24 | // SetTo sets the "to" field. 25 | func (ec *EnvelopeCreate) SetTo(s string) *EnvelopeCreate { 26 | ec.mutation.SetTo(s) 27 | return ec 28 | } 29 | 30 | // SetFrom sets the "from" field. 31 | func (ec *EnvelopeCreate) SetFrom(s string) *EnvelopeCreate { 32 | ec.mutation.SetFrom(s) 33 | return ec 34 | } 35 | 36 | // SetSubject sets the "subject" field. 37 | func (ec *EnvelopeCreate) SetSubject(s string) *EnvelopeCreate { 38 | ec.mutation.SetSubject(s) 39 | return ec 40 | } 41 | 42 | // SetContent sets the "content" field. 43 | func (ec *EnvelopeCreate) SetContent(s string) *EnvelopeCreate { 44 | ec.mutation.SetContent(s) 45 | return ec 46 | } 47 | 48 | // SetCreatedAt sets the "created_at" field. 49 | func (ec *EnvelopeCreate) SetCreatedAt(t time.Time) *EnvelopeCreate { 50 | ec.mutation.SetCreatedAt(t) 51 | return ec 52 | } 53 | 54 | // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. 55 | func (ec *EnvelopeCreate) SetNillableCreatedAt(t *time.Time) *EnvelopeCreate { 56 | if t != nil { 57 | ec.SetCreatedAt(*t) 58 | } 59 | return ec 60 | } 61 | 62 | // AddAttachmentIDs adds the "attachments" edge to the Attachment entity by IDs. 63 | func (ec *EnvelopeCreate) AddAttachmentIDs(ids ...string) *EnvelopeCreate { 64 | ec.mutation.AddAttachmentIDs(ids...) 65 | return ec 66 | } 67 | 68 | // AddAttachments adds the "attachments" edges to the Attachment entity. 69 | func (ec *EnvelopeCreate) AddAttachments(a ...*Attachment) *EnvelopeCreate { 70 | ids := make([]string, len(a)) 71 | for i := range a { 72 | ids[i] = a[i].ID 73 | } 74 | return ec.AddAttachmentIDs(ids...) 75 | } 76 | 77 | // Mutation returns the EnvelopeMutation object of the builder. 78 | func (ec *EnvelopeCreate) Mutation() *EnvelopeMutation { 79 | return ec.mutation 80 | } 81 | 82 | // Save creates the Envelope in the database. 83 | func (ec *EnvelopeCreate) Save(ctx context.Context) (*Envelope, error) { 84 | ec.defaults() 85 | return withHooks(ctx, ec.sqlSave, ec.mutation, ec.hooks) 86 | } 87 | 88 | // SaveX calls Save and panics if Save returns an error. 89 | func (ec *EnvelopeCreate) SaveX(ctx context.Context) *Envelope { 90 | v, err := ec.Save(ctx) 91 | if err != nil { 92 | panic(err) 93 | } 94 | return v 95 | } 96 | 97 | // Exec executes the query. 98 | func (ec *EnvelopeCreate) Exec(ctx context.Context) error { 99 | _, err := ec.Save(ctx) 100 | return err 101 | } 102 | 103 | // ExecX is like Exec, but panics if an error occurs. 104 | func (ec *EnvelopeCreate) ExecX(ctx context.Context) { 105 | if err := ec.Exec(ctx); err != nil { 106 | panic(err) 107 | } 108 | } 109 | 110 | // defaults sets the default values of the builder before save. 111 | func (ec *EnvelopeCreate) defaults() { 112 | if _, ok := ec.mutation.CreatedAt(); !ok { 113 | v := envelope.DefaultCreatedAt() 114 | ec.mutation.SetCreatedAt(v) 115 | } 116 | } 117 | 118 | // check runs all checks and user-defined validators on the builder. 119 | func (ec *EnvelopeCreate) check() error { 120 | if _, ok := ec.mutation.To(); !ok { 121 | return &ValidationError{Name: "to", err: errors.New(`ent: missing required field "Envelope.to"`)} 122 | } 123 | if v, ok := ec.mutation.To(); ok { 124 | if err := envelope.ToValidator(v); err != nil { 125 | return &ValidationError{Name: "to", err: fmt.Errorf(`ent: validator failed for field "Envelope.to": %w`, err)} 126 | } 127 | } 128 | if _, ok := ec.mutation.From(); !ok { 129 | return &ValidationError{Name: "from", err: errors.New(`ent: missing required field "Envelope.from"`)} 130 | } 131 | if v, ok := ec.mutation.From(); ok { 132 | if err := envelope.FromValidator(v); err != nil { 133 | return &ValidationError{Name: "from", err: fmt.Errorf(`ent: validator failed for field "Envelope.from": %w`, err)} 134 | } 135 | } 136 | if _, ok := ec.mutation.Subject(); !ok { 137 | return &ValidationError{Name: "subject", err: errors.New(`ent: missing required field "Envelope.subject"`)} 138 | } 139 | if v, ok := ec.mutation.Subject(); ok { 140 | if err := envelope.SubjectValidator(v); err != nil { 141 | return &ValidationError{Name: "subject", err: fmt.Errorf(`ent: validator failed for field "Envelope.subject": %w`, err)} 142 | } 143 | } 144 | if _, ok := ec.mutation.Content(); !ok { 145 | return &ValidationError{Name: "content", err: errors.New(`ent: missing required field "Envelope.content"`)} 146 | } 147 | if v, ok := ec.mutation.Content(); ok { 148 | if err := envelope.ContentValidator(v); err != nil { 149 | return &ValidationError{Name: "content", err: fmt.Errorf(`ent: validator failed for field "Envelope.content": %w`, err)} 150 | } 151 | } 152 | if _, ok := ec.mutation.CreatedAt(); !ok { 153 | return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "Envelope.created_at"`)} 154 | } 155 | return nil 156 | } 157 | 158 | func (ec *EnvelopeCreate) sqlSave(ctx context.Context) (*Envelope, error) { 159 | if err := ec.check(); err != nil { 160 | return nil, err 161 | } 162 | _node, _spec := ec.createSpec() 163 | if err := sqlgraph.CreateNode(ctx, ec.driver, _spec); err != nil { 164 | if sqlgraph.IsConstraintError(err) { 165 | err = &ConstraintError{msg: err.Error(), wrap: err} 166 | } 167 | return nil, err 168 | } 169 | id := _spec.ID.Value.(int64) 170 | _node.ID = int(id) 171 | ec.mutation.id = &_node.ID 172 | ec.mutation.done = true 173 | return _node, nil 174 | } 175 | 176 | func (ec *EnvelopeCreate) createSpec() (*Envelope, *sqlgraph.CreateSpec) { 177 | var ( 178 | _node = &Envelope{config: ec.config} 179 | _spec = sqlgraph.NewCreateSpec(envelope.Table, sqlgraph.NewFieldSpec(envelope.FieldID, field.TypeInt)) 180 | ) 181 | if value, ok := ec.mutation.To(); ok { 182 | _spec.SetField(envelope.FieldTo, field.TypeString, value) 183 | _node.To = value 184 | } 185 | if value, ok := ec.mutation.From(); ok { 186 | _spec.SetField(envelope.FieldFrom, field.TypeString, value) 187 | _node.From = value 188 | } 189 | if value, ok := ec.mutation.Subject(); ok { 190 | _spec.SetField(envelope.FieldSubject, field.TypeString, value) 191 | _node.Subject = value 192 | } 193 | if value, ok := ec.mutation.Content(); ok { 194 | _spec.SetField(envelope.FieldContent, field.TypeString, value) 195 | _node.Content = value 196 | } 197 | if value, ok := ec.mutation.CreatedAt(); ok { 198 | _spec.SetField(envelope.FieldCreatedAt, field.TypeTime, value) 199 | _node.CreatedAt = value 200 | } 201 | if nodes := ec.mutation.AttachmentsIDs(); len(nodes) > 0 { 202 | edge := &sqlgraph.EdgeSpec{ 203 | Rel: sqlgraph.O2M, 204 | Inverse: false, 205 | Table: envelope.AttachmentsTable, 206 | Columns: []string{envelope.AttachmentsColumn}, 207 | Bidi: false, 208 | Target: &sqlgraph.EdgeTarget{ 209 | IDSpec: sqlgraph.NewFieldSpec(attachment.FieldID, field.TypeString), 210 | }, 211 | } 212 | for _, k := range nodes { 213 | edge.Target.Nodes = append(edge.Target.Nodes, k) 214 | } 215 | _spec.Edges = append(_spec.Edges, edge) 216 | } 217 | return _node, _spec 218 | } 219 | 220 | // EnvelopeCreateBulk is the builder for creating many Envelope entities in bulk. 221 | type EnvelopeCreateBulk struct { 222 | config 223 | err error 224 | builders []*EnvelopeCreate 225 | } 226 | 227 | // Save creates the Envelope entities in the database. 228 | func (ecb *EnvelopeCreateBulk) Save(ctx context.Context) ([]*Envelope, error) { 229 | if ecb.err != nil { 230 | return nil, ecb.err 231 | } 232 | specs := make([]*sqlgraph.CreateSpec, len(ecb.builders)) 233 | nodes := make([]*Envelope, len(ecb.builders)) 234 | mutators := make([]Mutator, len(ecb.builders)) 235 | for i := range ecb.builders { 236 | func(i int, root context.Context) { 237 | builder := ecb.builders[i] 238 | builder.defaults() 239 | var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { 240 | mutation, ok := m.(*EnvelopeMutation) 241 | if !ok { 242 | return nil, fmt.Errorf("unexpected mutation type %T", m) 243 | } 244 | if err := builder.check(); err != nil { 245 | return nil, err 246 | } 247 | builder.mutation = mutation 248 | var err error 249 | nodes[i], specs[i] = builder.createSpec() 250 | if i < len(mutators)-1 { 251 | _, err = mutators[i+1].Mutate(root, ecb.builders[i+1].mutation) 252 | } else { 253 | spec := &sqlgraph.BatchCreateSpec{Nodes: specs} 254 | // Invoke the actual operation on the latest mutation in the chain. 255 | if err = sqlgraph.BatchCreate(ctx, ecb.driver, spec); err != nil { 256 | if sqlgraph.IsConstraintError(err) { 257 | err = &ConstraintError{msg: err.Error(), wrap: err} 258 | } 259 | } 260 | } 261 | if err != nil { 262 | return nil, err 263 | } 264 | mutation.id = &nodes[i].ID 265 | if specs[i].ID.Value != nil { 266 | id := specs[i].ID.Value.(int64) 267 | nodes[i].ID = int(id) 268 | } 269 | mutation.done = true 270 | return nodes[i], nil 271 | }) 272 | for i := len(builder.hooks) - 1; i >= 0; i-- { 273 | mut = builder.hooks[i](mut) 274 | } 275 | mutators[i] = mut 276 | }(i, ctx) 277 | } 278 | if len(mutators) > 0 { 279 | if _, err := mutators[0].Mutate(ctx, ecb.builders[0].mutation); err != nil { 280 | return nil, err 281 | } 282 | } 283 | return nodes, nil 284 | } 285 | 286 | // SaveX is like Save, but panics if an error occurs. 287 | func (ecb *EnvelopeCreateBulk) SaveX(ctx context.Context) []*Envelope { 288 | v, err := ecb.Save(ctx) 289 | if err != nil { 290 | panic(err) 291 | } 292 | return v 293 | } 294 | 295 | // Exec executes the query. 296 | func (ecb *EnvelopeCreateBulk) Exec(ctx context.Context) error { 297 | _, err := ecb.Save(ctx) 298 | return err 299 | } 300 | 301 | // ExecX is like Exec, but panics if an error occurs. 302 | func (ecb *EnvelopeCreateBulk) ExecX(ctx context.Context) { 303 | if err := ecb.Exec(ctx); err != nil { 304 | panic(err) 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /ent/envelope_delete.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | "tmail/ent/envelope" 8 | "tmail/ent/predicate" 9 | 10 | "entgo.io/ent/dialect/sql" 11 | "entgo.io/ent/dialect/sql/sqlgraph" 12 | "entgo.io/ent/schema/field" 13 | ) 14 | 15 | // EnvelopeDelete is the builder for deleting a Envelope entity. 16 | type EnvelopeDelete struct { 17 | config 18 | hooks []Hook 19 | mutation *EnvelopeMutation 20 | } 21 | 22 | // Where appends a list predicates to the EnvelopeDelete builder. 23 | func (ed *EnvelopeDelete) Where(ps ...predicate.Envelope) *EnvelopeDelete { 24 | ed.mutation.Where(ps...) 25 | return ed 26 | } 27 | 28 | // Exec executes the deletion query and returns how many vertices were deleted. 29 | func (ed *EnvelopeDelete) Exec(ctx context.Context) (int, error) { 30 | return withHooks(ctx, ed.sqlExec, ed.mutation, ed.hooks) 31 | } 32 | 33 | // ExecX is like Exec, but panics if an error occurs. 34 | func (ed *EnvelopeDelete) ExecX(ctx context.Context) int { 35 | n, err := ed.Exec(ctx) 36 | if err != nil { 37 | panic(err) 38 | } 39 | return n 40 | } 41 | 42 | func (ed *EnvelopeDelete) sqlExec(ctx context.Context) (int, error) { 43 | _spec := sqlgraph.NewDeleteSpec(envelope.Table, sqlgraph.NewFieldSpec(envelope.FieldID, field.TypeInt)) 44 | if ps := ed.mutation.predicates; len(ps) > 0 { 45 | _spec.Predicate = func(selector *sql.Selector) { 46 | for i := range ps { 47 | ps[i](selector) 48 | } 49 | } 50 | } 51 | affected, err := sqlgraph.DeleteNodes(ctx, ed.driver, _spec) 52 | if err != nil && sqlgraph.IsConstraintError(err) { 53 | err = &ConstraintError{msg: err.Error(), wrap: err} 54 | } 55 | ed.mutation.done = true 56 | return affected, err 57 | } 58 | 59 | // EnvelopeDeleteOne is the builder for deleting a single Envelope entity. 60 | type EnvelopeDeleteOne struct { 61 | ed *EnvelopeDelete 62 | } 63 | 64 | // Where appends a list predicates to the EnvelopeDelete builder. 65 | func (edo *EnvelopeDeleteOne) Where(ps ...predicate.Envelope) *EnvelopeDeleteOne { 66 | edo.ed.mutation.Where(ps...) 67 | return edo 68 | } 69 | 70 | // Exec executes the deletion query. 71 | func (edo *EnvelopeDeleteOne) Exec(ctx context.Context) error { 72 | n, err := edo.ed.Exec(ctx) 73 | switch { 74 | case err != nil: 75 | return err 76 | case n == 0: 77 | return &NotFoundError{envelope.Label} 78 | default: 79 | return nil 80 | } 81 | } 82 | 83 | // ExecX is like Exec, but panics if an error occurs. 84 | func (edo *EnvelopeDeleteOne) ExecX(ctx context.Context) { 85 | if err := edo.Exec(ctx); err != nil { 86 | panic(err) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /ent/generate.go: -------------------------------------------------------------------------------- 1 | package ent 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | _ "github.com/lib/pq" 7 | cfg "tmail/config" 8 | ) 9 | 10 | //go:generate go run -mod=mod entgo.io/ent/cmd/ent generate ./schema 11 | 12 | func New(db cfg.Database) (*Client, error) { 13 | client, err := Open(db.Driver, 14 | fmt.Sprintf("host=%s port=%s dbname=%s user=%s password=%s sslmode=disable timezone=Asia/Shanghai", 15 | db.Host, db.Port, db.Name, db.User, db.Pass)) 16 | if err != nil { 17 | return nil, err 18 | } 19 | return client, client.Schema.Create(context.TODO()) 20 | } 21 | -------------------------------------------------------------------------------- /ent/hook/hook.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package hook 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | "tmail/ent" 9 | ) 10 | 11 | // The AttachmentFunc type is an adapter to allow the use of ordinary 12 | // function as Attachment mutator. 13 | type AttachmentFunc func(context.Context, *ent.AttachmentMutation) (ent.Value, error) 14 | 15 | // Mutate calls f(ctx, m). 16 | func (f AttachmentFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { 17 | if mv, ok := m.(*ent.AttachmentMutation); ok { 18 | return f(ctx, mv) 19 | } 20 | return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.AttachmentMutation", m) 21 | } 22 | 23 | // The EnvelopeFunc type is an adapter to allow the use of ordinary 24 | // function as Envelope mutator. 25 | type EnvelopeFunc func(context.Context, *ent.EnvelopeMutation) (ent.Value, error) 26 | 27 | // Mutate calls f(ctx, m). 28 | func (f EnvelopeFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { 29 | if mv, ok := m.(*ent.EnvelopeMutation); ok { 30 | return f(ctx, mv) 31 | } 32 | return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.EnvelopeMutation", m) 33 | } 34 | 35 | // Condition is a hook condition function. 36 | type Condition func(context.Context, ent.Mutation) bool 37 | 38 | // And groups conditions with the AND operator. 39 | func And(first, second Condition, rest ...Condition) Condition { 40 | return func(ctx context.Context, m ent.Mutation) bool { 41 | if !first(ctx, m) || !second(ctx, m) { 42 | return false 43 | } 44 | for _, cond := range rest { 45 | if !cond(ctx, m) { 46 | return false 47 | } 48 | } 49 | return true 50 | } 51 | } 52 | 53 | // Or groups conditions with the OR operator. 54 | func Or(first, second Condition, rest ...Condition) Condition { 55 | return func(ctx context.Context, m ent.Mutation) bool { 56 | if first(ctx, m) || second(ctx, m) { 57 | return true 58 | } 59 | for _, cond := range rest { 60 | if cond(ctx, m) { 61 | return true 62 | } 63 | } 64 | return false 65 | } 66 | } 67 | 68 | // Not negates a given condition. 69 | func Not(cond Condition) Condition { 70 | return func(ctx context.Context, m ent.Mutation) bool { 71 | return !cond(ctx, m) 72 | } 73 | } 74 | 75 | // HasOp is a condition testing mutation operation. 76 | func HasOp(op ent.Op) Condition { 77 | return func(_ context.Context, m ent.Mutation) bool { 78 | return m.Op().Is(op) 79 | } 80 | } 81 | 82 | // HasAddedFields is a condition validating `.AddedField` on fields. 83 | func HasAddedFields(field string, fields ...string) Condition { 84 | return func(_ context.Context, m ent.Mutation) bool { 85 | if _, exists := m.AddedField(field); !exists { 86 | return false 87 | } 88 | for _, field := range fields { 89 | if _, exists := m.AddedField(field); !exists { 90 | return false 91 | } 92 | } 93 | return true 94 | } 95 | } 96 | 97 | // HasClearedFields is a condition validating `.FieldCleared` on fields. 98 | func HasClearedFields(field string, fields ...string) Condition { 99 | return func(_ context.Context, m ent.Mutation) bool { 100 | if exists := m.FieldCleared(field); !exists { 101 | return false 102 | } 103 | for _, field := range fields { 104 | if exists := m.FieldCleared(field); !exists { 105 | return false 106 | } 107 | } 108 | return true 109 | } 110 | } 111 | 112 | // HasFields is a condition validating `.Field` on fields. 113 | func HasFields(field string, fields ...string) Condition { 114 | return func(_ context.Context, m ent.Mutation) bool { 115 | if _, exists := m.Field(field); !exists { 116 | return false 117 | } 118 | for _, field := range fields { 119 | if _, exists := m.Field(field); !exists { 120 | return false 121 | } 122 | } 123 | return true 124 | } 125 | } 126 | 127 | // If executes the given hook under condition. 128 | // 129 | // hook.If(ComputeAverage, And(HasFields(...), HasAddedFields(...))) 130 | func If(hk ent.Hook, cond Condition) ent.Hook { 131 | return func(next ent.Mutator) ent.Mutator { 132 | return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.Value, error) { 133 | if cond(ctx, m) { 134 | return hk(next).Mutate(ctx, m) 135 | } 136 | return next.Mutate(ctx, m) 137 | }) 138 | } 139 | } 140 | 141 | // On executes the given hook only for the given operation. 142 | // 143 | // hook.On(Log, ent.Delete|ent.Create) 144 | func On(hk ent.Hook, op ent.Op) ent.Hook { 145 | return If(hk, HasOp(op)) 146 | } 147 | 148 | // Unless skips the given hook only for the given operation. 149 | // 150 | // hook.Unless(Log, ent.Update|ent.UpdateOne) 151 | func Unless(hk ent.Hook, op ent.Op) ent.Hook { 152 | return If(hk, Not(HasOp(op))) 153 | } 154 | 155 | // FixedError is a hook returning a fixed error. 156 | func FixedError(err error) ent.Hook { 157 | return func(ent.Mutator) ent.Mutator { 158 | return ent.MutateFunc(func(context.Context, ent.Mutation) (ent.Value, error) { 159 | return nil, err 160 | }) 161 | } 162 | } 163 | 164 | // Reject returns a hook that rejects all operations that match op. 165 | // 166 | // func (T) Hooks() []ent.Hook { 167 | // return []ent.Hook{ 168 | // Reject(ent.Delete|ent.Update), 169 | // } 170 | // } 171 | func Reject(op ent.Op) ent.Hook { 172 | hk := FixedError(fmt.Errorf("%s operation is not allowed", op)) 173 | return On(hk, op) 174 | } 175 | 176 | // Chain acts as a list of hooks and is effectively immutable. 177 | // Once created, it will always hold the same set of hooks in the same order. 178 | type Chain struct { 179 | hooks []ent.Hook 180 | } 181 | 182 | // NewChain creates a new chain of hooks. 183 | func NewChain(hooks ...ent.Hook) Chain { 184 | return Chain{append([]ent.Hook(nil), hooks...)} 185 | } 186 | 187 | // Hook chains the list of hooks and returns the final hook. 188 | func (c Chain) Hook() ent.Hook { 189 | return func(mutator ent.Mutator) ent.Mutator { 190 | for i := len(c.hooks) - 1; i >= 0; i-- { 191 | mutator = c.hooks[i](mutator) 192 | } 193 | return mutator 194 | } 195 | } 196 | 197 | // Append extends a chain, adding the specified hook 198 | // as the last ones in the mutation flow. 199 | func (c Chain) Append(hooks ...ent.Hook) Chain { 200 | newHooks := make([]ent.Hook, 0, len(c.hooks)+len(hooks)) 201 | newHooks = append(newHooks, c.hooks...) 202 | newHooks = append(newHooks, hooks...) 203 | return Chain{newHooks} 204 | } 205 | 206 | // Extend extends a chain, adding the specified chain 207 | // as the last ones in the mutation flow. 208 | func (c Chain) Extend(chain Chain) Chain { 209 | return c.Append(chain.hooks...) 210 | } 211 | -------------------------------------------------------------------------------- /ent/migrate/migrate.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package migrate 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | "io" 9 | 10 | "entgo.io/ent/dialect" 11 | "entgo.io/ent/dialect/sql/schema" 12 | ) 13 | 14 | var ( 15 | // WithGlobalUniqueID sets the universal ids options to the migration. 16 | // If this option is enabled, ent migration will allocate a 1<<32 range 17 | // for the ids of each entity (table). 18 | // Note that this option cannot be applied on tables that already exist. 19 | WithGlobalUniqueID = schema.WithGlobalUniqueID 20 | // WithDropColumn sets the drop column option to the migration. 21 | // If this option is enabled, ent migration will drop old columns 22 | // that were used for both fields and edges. This defaults to false. 23 | WithDropColumn = schema.WithDropColumn 24 | // WithDropIndex sets the drop index option to the migration. 25 | // If this option is enabled, ent migration will drop old indexes 26 | // that were defined in the schema. This defaults to false. 27 | // Note that unique constraints are defined using `UNIQUE INDEX`, 28 | // and therefore, it's recommended to enable this option to get more 29 | // flexibility in the schema changes. 30 | WithDropIndex = schema.WithDropIndex 31 | // WithForeignKeys enables creating foreign-key in schema DDL. This defaults to true. 32 | WithForeignKeys = schema.WithForeignKeys 33 | ) 34 | 35 | // Schema is the API for creating, migrating and dropping a schema. 36 | type Schema struct { 37 | drv dialect.Driver 38 | } 39 | 40 | // NewSchema creates a new schema client. 41 | func NewSchema(drv dialect.Driver) *Schema { return &Schema{drv: drv} } 42 | 43 | // Create creates all schema resources. 44 | func (s *Schema) Create(ctx context.Context, opts ...schema.MigrateOption) error { 45 | return Create(ctx, s, Tables, opts...) 46 | } 47 | 48 | // Create creates all table resources using the given schema driver. 49 | func Create(ctx context.Context, s *Schema, tables []*schema.Table, opts ...schema.MigrateOption) error { 50 | migrate, err := schema.NewMigrate(s.drv, opts...) 51 | if err != nil { 52 | return fmt.Errorf("ent/migrate: %w", err) 53 | } 54 | return migrate.Create(ctx, tables...) 55 | } 56 | 57 | // WriteTo writes the schema changes to w instead of running them against the database. 58 | // 59 | // if err := client.Schema.WriteTo(context.Background(), os.Stdout); err != nil { 60 | // log.Fatal(err) 61 | // } 62 | func (s *Schema) WriteTo(ctx context.Context, w io.Writer, opts ...schema.MigrateOption) error { 63 | return Create(ctx, &Schema{drv: &schema.WriteDriver{Writer: w, Driver: s.drv}}, Tables, opts...) 64 | } 65 | -------------------------------------------------------------------------------- /ent/migrate/schema.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package migrate 4 | 5 | import ( 6 | "entgo.io/ent/dialect/sql/schema" 7 | "entgo.io/ent/schema/field" 8 | ) 9 | 10 | var ( 11 | // AttachmentsColumns holds the columns for the "attachments" table. 12 | AttachmentsColumns = []*schema.Column{ 13 | {Name: "id", Type: field.TypeString, Size: 32}, 14 | {Name: "filename", Type: field.TypeString}, 15 | {Name: "filepath", Type: field.TypeString}, 16 | {Name: "content_type", Type: field.TypeString}, 17 | {Name: "envelope_attachments", Type: field.TypeInt, Nullable: true}, 18 | } 19 | // AttachmentsTable holds the schema information for the "attachments" table. 20 | AttachmentsTable = &schema.Table{ 21 | Name: "attachments", 22 | Columns: AttachmentsColumns, 23 | PrimaryKey: []*schema.Column{AttachmentsColumns[0]}, 24 | ForeignKeys: []*schema.ForeignKey{ 25 | { 26 | Symbol: "attachments_envelopes_attachments", 27 | Columns: []*schema.Column{AttachmentsColumns[4]}, 28 | RefColumns: []*schema.Column{EnvelopesColumns[0]}, 29 | OnDelete: schema.SetNull, 30 | }, 31 | }, 32 | } 33 | // EnvelopesColumns holds the columns for the "envelopes" table. 34 | EnvelopesColumns = []*schema.Column{ 35 | {Name: "id", Type: field.TypeInt, Increment: true}, 36 | {Name: "to", Type: field.TypeString}, 37 | {Name: "from", Type: field.TypeString}, 38 | {Name: "subject", Type: field.TypeString}, 39 | {Name: "content", Type: field.TypeString}, 40 | {Name: "created_at", Type: field.TypeTime}, 41 | } 42 | // EnvelopesTable holds the schema information for the "envelopes" table. 43 | EnvelopesTable = &schema.Table{ 44 | Name: "envelopes", 45 | Columns: EnvelopesColumns, 46 | PrimaryKey: []*schema.Column{EnvelopesColumns[0]}, 47 | Indexes: []*schema.Index{ 48 | { 49 | Name: "envelope_to", 50 | Unique: false, 51 | Columns: []*schema.Column{EnvelopesColumns[1]}, 52 | }, 53 | }, 54 | } 55 | // Tables holds all the tables in the schema. 56 | Tables = []*schema.Table{ 57 | AttachmentsTable, 58 | EnvelopesTable, 59 | } 60 | ) 61 | 62 | func init() { 63 | AttachmentsTable.ForeignKeys[0].RefTable = EnvelopesTable 64 | } 65 | -------------------------------------------------------------------------------- /ent/predicate/predicate.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package predicate 4 | 5 | import ( 6 | "entgo.io/ent/dialect/sql" 7 | ) 8 | 9 | // Attachment is the predicate function for attachment builders. 10 | type Attachment func(*sql.Selector) 11 | 12 | // Envelope is the predicate function for envelope builders. 13 | type Envelope func(*sql.Selector) 14 | -------------------------------------------------------------------------------- /ent/runtime.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "time" 7 | "tmail/ent/attachment" 8 | "tmail/ent/envelope" 9 | "tmail/ent/schema" 10 | ) 11 | 12 | // The init function reads all schema descriptors with runtime code 13 | // (default values, validators, hooks and policies) and stitches it 14 | // to their package variables. 15 | func init() { 16 | attachmentFields := schema.Attachment{}.Fields() 17 | _ = attachmentFields 18 | // attachmentDescFilename is the schema descriptor for filename field. 19 | attachmentDescFilename := attachmentFields[1].Descriptor() 20 | // attachment.FilenameValidator is a validator for the "filename" field. It is called by the builders before save. 21 | attachment.FilenameValidator = attachmentDescFilename.Validators[0].(func(string) error) 22 | // attachmentDescFilepath is the schema descriptor for filepath field. 23 | attachmentDescFilepath := attachmentFields[2].Descriptor() 24 | // attachment.FilepathValidator is a validator for the "filepath" field. It is called by the builders before save. 25 | attachment.FilepathValidator = attachmentDescFilepath.Validators[0].(func(string) error) 26 | // attachmentDescContentType is the schema descriptor for contentType field. 27 | attachmentDescContentType := attachmentFields[3].Descriptor() 28 | // attachment.ContentTypeValidator is a validator for the "contentType" field. It is called by the builders before save. 29 | attachment.ContentTypeValidator = attachmentDescContentType.Validators[0].(func(string) error) 30 | // attachmentDescID is the schema descriptor for id field. 31 | attachmentDescID := attachmentFields[0].Descriptor() 32 | // attachment.IDValidator is a validator for the "id" field. It is called by the builders before save. 33 | attachment.IDValidator = func() func(string) error { 34 | validators := attachmentDescID.Validators 35 | fns := [...]func(string) error{ 36 | validators[0].(func(string) error), 37 | validators[1].(func(string) error), 38 | } 39 | return func(id string) error { 40 | for _, fn := range fns { 41 | if err := fn(id); err != nil { 42 | return err 43 | } 44 | } 45 | return nil 46 | } 47 | }() 48 | envelopeFields := schema.Envelope{}.Fields() 49 | _ = envelopeFields 50 | // envelopeDescTo is the schema descriptor for to field. 51 | envelopeDescTo := envelopeFields[0].Descriptor() 52 | // envelope.ToValidator is a validator for the "to" field. It is called by the builders before save. 53 | envelope.ToValidator = envelopeDescTo.Validators[0].(func(string) error) 54 | // envelopeDescFrom is the schema descriptor for from field. 55 | envelopeDescFrom := envelopeFields[1].Descriptor() 56 | // envelope.FromValidator is a validator for the "from" field. It is called by the builders before save. 57 | envelope.FromValidator = envelopeDescFrom.Validators[0].(func(string) error) 58 | // envelopeDescSubject is the schema descriptor for subject field. 59 | envelopeDescSubject := envelopeFields[2].Descriptor() 60 | // envelope.SubjectValidator is a validator for the "subject" field. It is called by the builders before save. 61 | envelope.SubjectValidator = envelopeDescSubject.Validators[0].(func(string) error) 62 | // envelopeDescContent is the schema descriptor for content field. 63 | envelopeDescContent := envelopeFields[3].Descriptor() 64 | // envelope.ContentValidator is a validator for the "content" field. It is called by the builders before save. 65 | envelope.ContentValidator = envelopeDescContent.Validators[0].(func(string) error) 66 | // envelopeDescCreatedAt is the schema descriptor for created_at field. 67 | envelopeDescCreatedAt := envelopeFields[4].Descriptor() 68 | // envelope.DefaultCreatedAt holds the default value on creation for the created_at field. 69 | envelope.DefaultCreatedAt = envelopeDescCreatedAt.Default.(func() time.Time) 70 | } 71 | -------------------------------------------------------------------------------- /ent/runtime/runtime.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package runtime 4 | 5 | // The schema-stitching logic is generated in tmail/ent/runtime.go 6 | 7 | const ( 8 | Version = "v0.14.3" // Version of ent codegen. 9 | Sum = "h1:wokAV/kIlH9TeklJWGGS7AYJdVckr0DloWjIcO9iIIQ=" // Sum of ent codegen. 10 | ) 11 | -------------------------------------------------------------------------------- /ent/schema/attachment.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "entgo.io/ent" 5 | "entgo.io/ent/schema/edge" 6 | "entgo.io/ent/schema/field" 7 | ) 8 | 9 | // Attachment holds the schema definition for the Attachment entity. 10 | type Attachment struct { 11 | ent.Schema 12 | } 13 | 14 | // Fields of the Attachment. 15 | func (Attachment) Fields() []ent.Field { 16 | return []ent.Field{ 17 | field.String("id").MaxLen(32).NotEmpty(), 18 | field.String("filename").NotEmpty(), 19 | field.String("filepath").NotEmpty(), 20 | field.String("contentType").NotEmpty(), 21 | } 22 | } 23 | 24 | // Edges of the Attachment. 25 | func (Attachment) Edges() []ent.Edge { 26 | return []ent.Edge{ 27 | edge.From("owner", Envelope.Type).Ref("attachments").Unique(), 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ent/schema/envelope.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "entgo.io/ent" 5 | "entgo.io/ent/schema/edge" 6 | "entgo.io/ent/schema/field" 7 | "entgo.io/ent/schema/index" 8 | "time" 9 | ) 10 | 11 | // Envelope holds the schema definition for the Envelope entity. 12 | type Envelope struct { 13 | ent.Schema 14 | } 15 | 16 | // Fields of the Envelope. 17 | func (Envelope) Fields() []ent.Field { 18 | return []ent.Field{ 19 | field.String("to").NotEmpty(), 20 | field.String("from").NotEmpty(), 21 | field.String("subject").NotEmpty(), 22 | field.String("content").NotEmpty(), 23 | field.Time("created_at").Default(time.Now), 24 | } 25 | } 26 | 27 | func (Envelope) Indexes() []ent.Index { 28 | return []ent.Index{ 29 | index.Fields("to"), 30 | } 31 | } 32 | 33 | // Edges of the Envelope. 34 | func (Envelope) Edges() []ent.Edge { 35 | return []ent.Edge{ 36 | edge.To("attachments", Attachment.Type), 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ent/tx.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | "sync" 8 | 9 | "entgo.io/ent/dialect" 10 | ) 11 | 12 | // Tx is a transactional client that is created by calling Client.Tx(). 13 | type Tx struct { 14 | config 15 | // Attachment is the client for interacting with the Attachment builders. 16 | Attachment *AttachmentClient 17 | // Envelope is the client for interacting with the Envelope builders. 18 | Envelope *EnvelopeClient 19 | 20 | // lazily loaded. 21 | client *Client 22 | clientOnce sync.Once 23 | // ctx lives for the life of the transaction. It is 24 | // the same context used by the underlying connection. 25 | ctx context.Context 26 | } 27 | 28 | type ( 29 | // Committer is the interface that wraps the Commit method. 30 | Committer interface { 31 | Commit(context.Context, *Tx) error 32 | } 33 | 34 | // The CommitFunc type is an adapter to allow the use of ordinary 35 | // function as a Committer. If f is a function with the appropriate 36 | // signature, CommitFunc(f) is a Committer that calls f. 37 | CommitFunc func(context.Context, *Tx) error 38 | 39 | // CommitHook defines the "commit middleware". A function that gets a Committer 40 | // and returns a Committer. For example: 41 | // 42 | // hook := func(next ent.Committer) ent.Committer { 43 | // return ent.CommitFunc(func(ctx context.Context, tx *ent.Tx) error { 44 | // // Do some stuff before. 45 | // if err := next.Commit(ctx, tx); err != nil { 46 | // return err 47 | // } 48 | // // Do some stuff after. 49 | // return nil 50 | // }) 51 | // } 52 | // 53 | CommitHook func(Committer) Committer 54 | ) 55 | 56 | // Commit calls f(ctx, m). 57 | func (f CommitFunc) Commit(ctx context.Context, tx *Tx) error { 58 | return f(ctx, tx) 59 | } 60 | 61 | // Commit commits the transaction. 62 | func (tx *Tx) Commit() error { 63 | txDriver := tx.config.driver.(*txDriver) 64 | var fn Committer = CommitFunc(func(context.Context, *Tx) error { 65 | return txDriver.tx.Commit() 66 | }) 67 | txDriver.mu.Lock() 68 | hooks := append([]CommitHook(nil), txDriver.onCommit...) 69 | txDriver.mu.Unlock() 70 | for i := len(hooks) - 1; i >= 0; i-- { 71 | fn = hooks[i](fn) 72 | } 73 | return fn.Commit(tx.ctx, tx) 74 | } 75 | 76 | // OnCommit adds a hook to call on commit. 77 | func (tx *Tx) OnCommit(f CommitHook) { 78 | txDriver := tx.config.driver.(*txDriver) 79 | txDriver.mu.Lock() 80 | txDriver.onCommit = append(txDriver.onCommit, f) 81 | txDriver.mu.Unlock() 82 | } 83 | 84 | type ( 85 | // Rollbacker is the interface that wraps the Rollback method. 86 | Rollbacker interface { 87 | Rollback(context.Context, *Tx) error 88 | } 89 | 90 | // The RollbackFunc type is an adapter to allow the use of ordinary 91 | // function as a Rollbacker. If f is a function with the appropriate 92 | // signature, RollbackFunc(f) is a Rollbacker that calls f. 93 | RollbackFunc func(context.Context, *Tx) error 94 | 95 | // RollbackHook defines the "rollback middleware". A function that gets a Rollbacker 96 | // and returns a Rollbacker. For example: 97 | // 98 | // hook := func(next ent.Rollbacker) ent.Rollbacker { 99 | // return ent.RollbackFunc(func(ctx context.Context, tx *ent.Tx) error { 100 | // // Do some stuff before. 101 | // if err := next.Rollback(ctx, tx); err != nil { 102 | // return err 103 | // } 104 | // // Do some stuff after. 105 | // return nil 106 | // }) 107 | // } 108 | // 109 | RollbackHook func(Rollbacker) Rollbacker 110 | ) 111 | 112 | // Rollback calls f(ctx, m). 113 | func (f RollbackFunc) Rollback(ctx context.Context, tx *Tx) error { 114 | return f(ctx, tx) 115 | } 116 | 117 | // Rollback rollbacks the transaction. 118 | func (tx *Tx) Rollback() error { 119 | txDriver := tx.config.driver.(*txDriver) 120 | var fn Rollbacker = RollbackFunc(func(context.Context, *Tx) error { 121 | return txDriver.tx.Rollback() 122 | }) 123 | txDriver.mu.Lock() 124 | hooks := append([]RollbackHook(nil), txDriver.onRollback...) 125 | txDriver.mu.Unlock() 126 | for i := len(hooks) - 1; i >= 0; i-- { 127 | fn = hooks[i](fn) 128 | } 129 | return fn.Rollback(tx.ctx, tx) 130 | } 131 | 132 | // OnRollback adds a hook to call on rollback. 133 | func (tx *Tx) OnRollback(f RollbackHook) { 134 | txDriver := tx.config.driver.(*txDriver) 135 | txDriver.mu.Lock() 136 | txDriver.onRollback = append(txDriver.onRollback, f) 137 | txDriver.mu.Unlock() 138 | } 139 | 140 | // Client returns a Client that binds to current transaction. 141 | func (tx *Tx) Client() *Client { 142 | tx.clientOnce.Do(func() { 143 | tx.client = &Client{config: tx.config} 144 | tx.client.init() 145 | }) 146 | return tx.client 147 | } 148 | 149 | func (tx *Tx) init() { 150 | tx.Attachment = NewAttachmentClient(tx.config) 151 | tx.Envelope = NewEnvelopeClient(tx.config) 152 | } 153 | 154 | // txDriver wraps the given dialect.Tx with a nop dialect.Driver implementation. 155 | // The idea is to support transactions without adding any extra code to the builders. 156 | // When a builder calls to driver.Tx(), it gets the same dialect.Tx instance. 157 | // Commit and Rollback are nop for the internal builders and the user must call one 158 | // of them in order to commit or rollback the transaction. 159 | // 160 | // If a closed transaction is embedded in one of the generated entities, and the entity 161 | // applies a query, for example: Attachment.QueryXXX(), the query will be executed 162 | // through the driver which created this transaction. 163 | // 164 | // Note that txDriver is not goroutine safe. 165 | type txDriver struct { 166 | // the driver we started the transaction from. 167 | drv dialect.Driver 168 | // tx is the underlying transaction. 169 | tx dialect.Tx 170 | // completion hooks. 171 | mu sync.Mutex 172 | onCommit []CommitHook 173 | onRollback []RollbackHook 174 | } 175 | 176 | // newTx creates a new transactional driver. 177 | func newTx(ctx context.Context, drv dialect.Driver) (*txDriver, error) { 178 | tx, err := drv.Tx(ctx) 179 | if err != nil { 180 | return nil, err 181 | } 182 | return &txDriver{tx: tx, drv: drv}, nil 183 | } 184 | 185 | // Tx returns the transaction wrapper (txDriver) to avoid Commit or Rollback calls 186 | // from the internal builders. Should be called only by the internal builders. 187 | func (tx *txDriver) Tx(context.Context) (dialect.Tx, error) { return tx, nil } 188 | 189 | // Dialect returns the dialect of the driver we started the transaction from. 190 | func (tx *txDriver) Dialect() string { return tx.drv.Dialect() } 191 | 192 | // Close is a nop close. 193 | func (*txDriver) Close() error { return nil } 194 | 195 | // Commit is a nop commit for the internal builders. 196 | // User must call `Tx.Commit` in order to commit the transaction. 197 | func (*txDriver) Commit() error { return nil } 198 | 199 | // Rollback is a nop rollback for the internal builders. 200 | // User must call `Tx.Rollback` in order to rollback the transaction. 201 | func (*txDriver) Rollback() error { return nil } 202 | 203 | // Exec calls tx.Exec. 204 | func (tx *txDriver) Exec(ctx context.Context, query string, args, v any) error { 205 | return tx.tx.Exec(ctx, query, args, v) 206 | } 207 | 208 | // Query calls tx.Query. 209 | func (tx *txDriver) Query(ctx context.Context, query string, args, v any) error { 210 | return tx.tx.Query(ctx, query, args, v) 211 | } 212 | 213 | var _ dialect.Driver = (*txDriver)(nil) 214 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module tmail 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | entgo.io/ent v0.14.3 7 | github.com/caarlos0/env/v11 v11.3.1 8 | github.com/jhillyerd/enmime v1.3.0 9 | github.com/labstack/echo/v4 v4.13.3 10 | github.com/lib/pq v1.10.9 11 | github.com/rs/zerolog v1.33.0 12 | ) 13 | 14 | require ( 15 | ariga.io/atlas v0.31.1-0.20250212144724-069be8033e83 // indirect 16 | github.com/agext/levenshtein v1.2.1 // indirect 17 | github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect 18 | github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect 19 | github.com/bmatcuk/doublestar v1.3.4 // indirect 20 | github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect 21 | github.com/go-openapi/inflect v0.19.0 // indirect 22 | github.com/go-test/deep v1.1.1 // indirect 23 | github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f // indirect 24 | github.com/google/go-cmp v0.6.0 // indirect 25 | github.com/google/uuid v1.3.0 // indirect 26 | github.com/hashicorp/hcl/v2 v2.13.0 // indirect 27 | github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 // indirect 28 | github.com/labstack/gommon v0.4.2 // indirect 29 | github.com/mattn/go-colorable v0.1.14 // indirect 30 | github.com/mattn/go-isatty v0.0.20 // indirect 31 | github.com/mattn/go-runewidth v0.0.16 // indirect 32 | github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect 33 | github.com/olekukonko/tablewriter v0.0.5 // indirect 34 | github.com/pkg/errors v0.9.1 // indirect 35 | github.com/rivo/uniseg v0.4.7 // indirect 36 | github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect 37 | github.com/valyala/bytebufferpool v1.0.0 // indirect 38 | github.com/valyala/fasttemplate v1.2.2 // indirect 39 | github.com/zclconf/go-cty v1.14.4 // indirect 40 | github.com/zclconf/go-cty-yaml v1.1.0 // indirect 41 | golang.org/x/crypto v0.33.0 // indirect 42 | golang.org/x/mod v0.23.0 // indirect 43 | golang.org/x/net v0.35.0 // indirect 44 | golang.org/x/sys v0.30.0 // indirect 45 | golang.org/x/text v0.22.0 // indirect 46 | golang.org/x/time v0.8.0 // indirect 47 | ) 48 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | ariga.io/atlas v0.31.1-0.20250212144724-069be8033e83 h1:nX4HXncwIdvQ8/8sIUIf1nyCkK8qdBaHQ7EtzPpuiGE= 2 | ariga.io/atlas v0.31.1-0.20250212144724-069be8033e83/go.mod h1:Oe1xWPuu5q9LzyrWfbZmEZxFYeu4BHTyzfjeW2aZp/w= 3 | entgo.io/ent v0.14.3 h1:wokAV/kIlH9TeklJWGGS7AYJdVckr0DloWjIcO9iIIQ= 4 | entgo.io/ent v0.14.3/go.mod h1:aDPE/OziPEu8+OWbzy4UlvWmD2/kbRuWfK2A40hcxJM= 5 | github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= 6 | github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= 7 | github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= 8 | github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= 9 | github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= 10 | github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= 11 | github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= 12 | github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= 13 | github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0= 14 | github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= 15 | github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA= 16 | github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U= 17 | github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a h1:MISbI8sU/PSK/ztvmWKFcI7UGb5/HQT7B+i3a2myKgI= 18 | github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a/go.mod h1:2GxOXOlEPAMFPfp014mK1SWq8G8BN8o7/dfYqJrVGn8= 19 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 20 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 21 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 22 | github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4= 23 | github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4= 24 | github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= 25 | github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= 26 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 27 | github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7wCLuiqMaUh5SJkkzI2gDs+FgLs= 28 | github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14= 29 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 30 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 31 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 32 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 33 | github.com/hashicorp/hcl/v2 v2.13.0 h1:0Apadu1w6M11dyGFxWnmhhcMjkbAiKCv7G1r/2QgCNc= 34 | github.com/hashicorp/hcl/v2 v2.13.0/go.mod h1:e4z5nxYlWNPdDSNYX+ph14EvWYMFm3eP0zIUqPc2jr0= 35 | github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 h1:iCHtR9CQyktQ5+f3dMVZfwD2KWJUgm7M0gdL9NGr8KA= 36 | github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= 37 | github.com/jhillyerd/enmime v1.3.0 h1:LV5kzfLidiOr8qRGIpYYmUZCnhrPbcFAnAFUnWn99rw= 38 | github.com/jhillyerd/enmime v1.3.0/go.mod h1:6c6jg5HdRRV2FtvVL69LjiX1M8oE0xDX9VEhV3oy4gs= 39 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 40 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 41 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 42 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 43 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 44 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 45 | github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY= 46 | github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g= 47 | github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= 48 | github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= 49 | github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= 50 | github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 51 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 52 | github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= 53 | github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= 54 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 55 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 56 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 57 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 58 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 59 | github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= 60 | github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 61 | github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= 62 | github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= 63 | github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM= 64 | github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= 65 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= 66 | github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= 67 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 68 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 69 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 70 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 71 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 72 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 73 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 74 | github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= 75 | github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= 76 | github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= 77 | github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= 78 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 79 | github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo= 80 | github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM= 81 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 82 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 83 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 84 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 85 | github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= 86 | github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= 87 | github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8= 88 | github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= 89 | github.com/zclconf/go-cty-yaml v1.1.0 h1:nP+jp0qPHv2IhUVqmQSzjvqAWcObN0KBkUl2rWBdig0= 90 | github.com/zclconf/go-cty-yaml v1.1.0/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs= 91 | golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= 92 | golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= 93 | golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= 94 | golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 95 | golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= 96 | golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= 97 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 98 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 99 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 100 | golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= 101 | golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 102 | golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= 103 | golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= 104 | golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= 105 | golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 106 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 107 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 108 | -------------------------------------------------------------------------------- /internal/api/context.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "github.com/labstack/echo/v4" 6 | "net/http" 7 | "tmail/config" 8 | "tmail/ent" 9 | ) 10 | 11 | type Context struct { 12 | echo.Context 13 | *config.Config 14 | ent *ent.Client 15 | } 16 | 17 | func Middleware(client *ent.Client, cfg *config.Config) echo.MiddlewareFunc { 18 | return func(next echo.HandlerFunc) echo.HandlerFunc { 19 | return func(c echo.Context) error { 20 | return next(&Context{c, cfg, client}) 21 | } 22 | } 23 | } 24 | 25 | func Wrap(fn func(*Context) error) echo.HandlerFunc { 26 | return func(c echo.Context) error { 27 | return fn(c.(*Context)) 28 | } 29 | } 30 | 31 | func (c *Context) Bad(msg string) error { 32 | return echo.NewHTTPError(http.StatusBadRequest, msg) 33 | } 34 | 35 | //goland:noinspection SpellCheckingInspection 36 | func (c *Context) Badf(f string, args ...any) error { 37 | return c.Bad(fmt.Sprintf(f, args...)) 38 | } 39 | -------------------------------------------------------------------------------- /internal/api/domain.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import "net/http" 4 | 5 | func DomainList(ctx *Context) error { 6 | return ctx.JSON(http.StatusOK, ctx.DomainList) 7 | } 8 | -------------------------------------------------------------------------------- /internal/api/fetch.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | "time" 7 | "tmail/ent" 8 | "tmail/ent/attachment" 9 | "tmail/ent/envelope" 10 | "tmail/internal/pubsub" 11 | ) 12 | 13 | func Fetch(ctx *Context) error { 14 | to := ctx.QueryParam("to") 15 | if to == "" { 16 | return ctx.Bad("not found to address") 17 | } 18 | admin := to == ctx.AdminAddress 19 | query := ctx.ent.Envelope.Query(). 20 | Select(envelope.FieldID, envelope.FieldTo, envelope.FieldFrom, envelope.FieldSubject, envelope.FieldCreatedAt). 21 | Order(ent.Desc(envelope.FieldID)) 22 | if !admin { 23 | query.Where(envelope.To(to)) 24 | } else { 25 | query.Limit(100) 26 | } 27 | list, err := query.All(ctx.Request().Context()) 28 | if err != nil { 29 | return err 30 | } 31 | return ctx.JSON(http.StatusOK, list) 32 | } 33 | 34 | type MailDetail struct { 35 | Content string `json:"content"` 36 | Attachments []AttachmentDetail `json:"attachments"` 37 | } 38 | 39 | type AttachmentDetail struct { 40 | ID string `json:"id"` 41 | Filename string `json:"filename"` 42 | } 43 | 44 | func FetchDetail(ctx *Context) error { 45 | idStr := ctx.Param("id") 46 | if idStr == "" { 47 | return ctx.Bad("not found id param") 48 | } 49 | id, err := strconv.Atoi(idStr) 50 | if err != nil { 51 | return ctx.Bad("invalid id param: " + idStr) 52 | } 53 | e, err := ctx.ent.Envelope.Query(). 54 | Select(envelope.FieldContent). 55 | Where(envelope.ID(id)). 56 | Only(ctx.Request().Context()) 57 | if ent.IsNotFound(err) { 58 | return ctx.Badf("envelope %d not found", id) 59 | } 60 | if err != nil { 61 | return err 62 | } 63 | dbAttachments, _ := e.QueryAttachments().All(ctx.Request().Context()) 64 | attachments := make([]AttachmentDetail, 0, len(dbAttachments)) 65 | for _, a := range dbAttachments { 66 | attachments = append(attachments, AttachmentDetail{ 67 | ID: a.ID, 68 | Filename: a.Filename, 69 | }) 70 | } 71 | 72 | return ctx.JSON(http.StatusOK, MailDetail{ 73 | Content: e.Content, 74 | Attachments: attachments, 75 | }) 76 | } 77 | 78 | func FetchLatest(ctx *Context) error { 79 | to := ctx.QueryParam("to") 80 | if to == "" { 81 | return ctx.Bad("not found to address") 82 | } 83 | admin := to == ctx.AdminAddress 84 | if !admin { 85 | idStr := ctx.QueryParam("id") 86 | id, err := strconv.Atoi(idStr) 87 | if err != nil { 88 | return ctx.Bad("invalid id param: " + idStr) 89 | } 90 | e, err := ctx.ent.Envelope.Query(). 91 | Select(envelope.FieldID, envelope.FieldTo, envelope.FieldFrom, envelope.FieldSubject, envelope.FieldCreatedAt). 92 | Where(envelope.IDGT(id), envelope.To(to)). 93 | Order(ent.Asc(envelope.FieldID)). 94 | First(ctx.Request().Context()) 95 | if err == nil { 96 | return ctx.JSON(http.StatusOK, e) 97 | } 98 | if !ent.IsNotFound(err) { 99 | return err 100 | } 101 | } else { 102 | to = pubsub.SubAll 103 | } 104 | 105 | ch, cancel := pubsub.Subscribe(to) 106 | defer cancel() 107 | select { 108 | case e := <-ch: 109 | return ctx.JSON(http.StatusOK, e) 110 | case <-time.After(time.Minute): 111 | return ctx.NoContent(http.StatusNoContent) 112 | case <-ctx.Request().Context().Done(): 113 | return nil 114 | } 115 | } 116 | 117 | func Download(ctx *Context) error { 118 | id := ctx.Param("id") 119 | if id == "" { 120 | return ctx.Bad("not found id param") 121 | } 122 | 123 | a, err := ctx.ent.Attachment.Query().Where(attachment.ID(id)).First(ctx.Request().Context()) 124 | if err != nil { 125 | return err 126 | } 127 | 128 | return ctx.Attachment(a.Filepath, a.Filename) 129 | } 130 | -------------------------------------------------------------------------------- /internal/api/report.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "github.com/jhillyerd/enmime" 6 | "github.com/rs/zerolog/log" 7 | "os" 8 | "path/filepath" 9 | "tmail/internal/pubsub" 10 | "tmail/internal/utils" 11 | ) 12 | 13 | func Report(ctx *Context) error { 14 | to := ctx.QueryParam("to") 15 | if to == "" { 16 | return nil 17 | } 18 | envelope, err := enmime.ReadEnvelope(ctx.Request().Body) 19 | if err != nil { 20 | return err 21 | } 22 | subject := envelope.GetHeader("subject") 23 | from := envelope.GetHeader("from") 24 | content := envelope.HTML 25 | if content == "" { 26 | content = envelope.Text 27 | } 28 | 29 | log.Info().Msgf("Report: %s <- %s", to, from) 30 | e, err := ctx.ent.Envelope.Create(). 31 | SetTo(to). 32 | SetFrom(from). 33 | SetSubject(subject). 34 | SetContent(content). 35 | Save(ctx.Request().Context()) 36 | if err == nil { 37 | go pubsub.Publish(e) 38 | go saveAttachment(ctx, envelope.Attachments, to, e.ID) 39 | } 40 | return err 41 | } 42 | 43 | func saveAttachment(ctx *Context, attachments []*enmime.Part, to string, ownerID int) { 44 | const maxSize = 200000000 // 200M 45 | if len(attachments) == 0 { 46 | return 47 | } 48 | 49 | var dir = filepath.Join(ctx.BaseDir, utils.Md5(to)[:16]) 50 | if err := os.MkdirAll(dir, 0o755); err != nil { 51 | log.Err(err).Msg("MkdirAll") 52 | return 53 | } 54 | 55 | for _, a := range attachments { 56 | if a.FileName == "" || len(a.Content) > maxSize { 57 | continue 58 | } 59 | 60 | name := utils.Md5(a.FileName) 61 | fp := filepath.Join(dir, name) 62 | log.Info().Msgf("Attachment: %s -> %s", a.FileName, fp) 63 | if err := os.WriteFile(fp, a.Content, 0o644); err != nil { 64 | log.Err(err).Msg("WriteFile") 65 | continue 66 | } 67 | 68 | _, err := ctx.ent.Attachment.Create(). 69 | SetID(filepath.Base(dir) + name[:16]). 70 | SetFilename(a.FileName). 71 | SetFilepath(fp). 72 | SetContentType(a.ContentType). 73 | SetOwnerID(ownerID). 74 | Save(context.TODO()) 75 | if err != nil { 76 | _ = os.Remove(fp) 77 | log.Err(err).Msg("Attachment Save") 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /internal/app.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "fmt" 5 | "github.com/labstack/echo/v4" 6 | "github.com/labstack/echo/v4/middleware" 7 | "github.com/rs/zerolog" 8 | "github.com/rs/zerolog/log" 9 | "net/http" 10 | "os" 11 | "strings" 12 | "tmail/config" 13 | "tmail/ent" 14 | "tmail/internal/api" 15 | "tmail/internal/constant" 16 | "tmail/internal/route" 17 | "tmail/internal/schedule" 18 | "tmail/web" 19 | ) 20 | 21 | type App struct { 22 | } 23 | 24 | func NewApp() App { 25 | return App{} 26 | } 27 | 28 | func (App) init() { 29 | log.Logger = log.Logger.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: "06-01-02 15:04:05"}) 30 | } 31 | 32 | func (app App) Run() error { 33 | app.init() 34 | cfg := config.MustNew() 35 | client, err := ent.New(cfg.DB) 36 | if err != nil { 37 | return err 38 | } 39 | defer client.Close() 40 | 41 | schedule.New(client).Run() 42 | 43 | e := echo.New() 44 | e.Pre(i18n) 45 | e.Use(api.Middleware(client, cfg)) 46 | e.Use(middleware.RecoverWithConfig(middleware.RecoverConfig{ 47 | DisablePrintStack: true, 48 | })) 49 | e.HTTPErrorHandler = func(err error, c echo.Context) { 50 | e.DefaultHTTPErrorHandler(err, c) 51 | //goland:noinspection GoTypeAssertionOnErrors 52 | if _, ok := err.(*echo.HTTPError); !ok { 53 | log.Err(err).Send() 54 | } 55 | } 56 | 57 | route.Register(e) 58 | e.StaticFS("/", echo.MustSubFS(web.FS, "dist")) 59 | return e.Start(fmt.Sprintf("%s:%s", cfg.Host, cfg.Port)) 60 | } 61 | 62 | func i18n(next echo.HandlerFunc) echo.HandlerFunc { 63 | return func(c echo.Context) error { 64 | if c.Request().URL.Path != "/" { 65 | return next(c) 66 | } 67 | 68 | c.Request().URL.Path += getLang(c.Request()) + "/" 69 | return next(c) 70 | } 71 | } 72 | 73 | func getLang(req *http.Request) string { 74 | if isGoogleBot(req) { 75 | return constant.LangZh 76 | } 77 | 78 | if al := req.Header.Get("Accept-Language"); al != "" && al[:2] == constant.LangZh { 79 | return constant.LangZh 80 | } 81 | 82 | return constant.LangEn 83 | } 84 | 85 | func isGoogleBot(req *http.Request) bool { 86 | ua := req.Header.Get("User-Agent") 87 | return strings.Contains(strings.ToLower(ua), "googlebot") 88 | } 89 | -------------------------------------------------------------------------------- /internal/constant/constant.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | const ( 4 | LangEn = "en" 5 | LangZh = "zh" 6 | ) 7 | -------------------------------------------------------------------------------- /internal/pubsub/pubsub.go: -------------------------------------------------------------------------------- 1 | package pubsub 2 | 3 | import ( 4 | "sync" 5 | "tmail/ent" 6 | ) 7 | 8 | const ( 9 | SubAll = "all" 10 | ) 11 | 12 | var ( 13 | index int 14 | m sync.RWMutex 15 | subscribers = map[string]map[int]chan *ent.Envelope{} 16 | ) 17 | 18 | func Subscribe(to string) (<-chan *ent.Envelope, func()) { 19 | ch := make(chan *ent.Envelope, 1) 20 | m.Lock() 21 | defer m.Unlock() 22 | id := index 23 | index++ 24 | 25 | if _, ok := subscribers[to]; !ok { 26 | subscribers[to] = map[int]chan *ent.Envelope{} 27 | } 28 | subscribers[to][id] = ch 29 | 30 | return ch, func() { 31 | m.Lock() 32 | defer m.Unlock() 33 | delete(subscribers[to], id) 34 | if len(subscribers[to]) == 0 { 35 | delete(subscribers, to) 36 | } 37 | close(ch) 38 | } 39 | } 40 | 41 | func Publish(e *ent.Envelope) { 42 | if len(subscribers) == 0 || (len(subscribers[e.To]) == 0 && len(subscribers[SubAll]) == 0) { 43 | return 44 | } 45 | e.Content = "" 46 | m.RLock() 47 | defer m.RUnlock() 48 | for _, sub := range subscribers[e.To] { 49 | sub <- e 50 | } 51 | for _, sub := range subscribers[SubAll] { 52 | sub <- e 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /internal/route/route.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "github.com/labstack/echo/v4" 5 | "tmail/internal/api" 6 | ) 7 | 8 | func Register(e *echo.Echo) { 9 | g := e.Group("/api") 10 | g.POST("/report", api.Wrap(api.Report)) 11 | g.GET("/fetch", api.Wrap(api.Fetch)) 12 | g.GET("/fetch/:id", api.Wrap(api.FetchDetail)) 13 | g.GET("/fetch/latest", api.Wrap(api.FetchLatest)) 14 | g.GET("/download/:id", api.Wrap(api.Download)) 15 | g.GET("/domain", api.Wrap(api.DomainList)) 16 | } 17 | -------------------------------------------------------------------------------- /internal/schedule/schedule.go: -------------------------------------------------------------------------------- 1 | package schedule 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/rs/zerolog/log" 7 | "os" 8 | "time" 9 | "tmail/ent" 10 | "tmail/ent/attachment" 11 | "tmail/ent/envelope" 12 | ) 13 | 14 | type Scheduler struct { 15 | db *ent.Client 16 | } 17 | 18 | func New(db *ent.Client) *Scheduler { 19 | return &Scheduler{db: db} 20 | } 21 | 22 | func (s *Scheduler) Run() { 23 | go s.cleanUpExpired() 24 | } 25 | 26 | func (s *Scheduler) cleanUpExpired() { 27 | run(func() { 28 | expired := time.Now().Add(-time.Hour * 240) 29 | list, err := s.db.Attachment.Query().Where(attachment.HasOwnerWith(envelope.CreatedAtLT(expired))).All(context.TODO()) 30 | if err != nil { 31 | log.Err(err).Msg("Attachment Query") 32 | return 33 | } 34 | for _, a := range list { 35 | _ = os.Remove(a.Filepath) 36 | } 37 | count, err := s.db.Attachment.Delete().Where(attachment.HasOwnerWith(envelope.CreatedAtLT(expired))).Exec(context.TODO()) 38 | if err != nil { 39 | log.Err(err).Msg("Attachment Delete") 40 | return 41 | } 42 | if count > 0 { 43 | log.Info().Msgf("clean up attachment %d", count) 44 | } 45 | count, err = s.db.Envelope.Delete().Where(envelope.CreatedAtLT(expired)).Exec(context.TODO()) 46 | if err != nil { 47 | log.Err(err).Msg("Envelope Delete") 48 | return 49 | } 50 | if count > 0 { 51 | log.Info().Msgf("clean up expired %d", count) 52 | } 53 | }, time.Hour) 54 | } 55 | 56 | func run(fn func(), dur time.Duration) { 57 | for { 58 | select { 59 | case <-time.Tick(dur): 60 | go func() { 61 | defer func() { 62 | if err := recover(); err != nil { 63 | log.Error().Msg(fmt.Sprint(err)) 64 | } 65 | }() 66 | fn() 67 | }() 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /internal/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | ) 7 | 8 | func Md5(str string) string { 9 | h := md5.New() 10 | h.Write([]byte(str)) 11 | return hex.EncodeToString(h.Sum(nil)) 12 | } 13 | -------------------------------------------------------------------------------- /internal/utils/utils_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestMd5(t *testing.T) { 9 | fmt.Println(Md5("123456@isco.eu.org")) 10 | } 11 | -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | 4 | # generated types 5 | .astro/ 6 | 7 | # dependencies 8 | node_modules/ 9 | 10 | # logs 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | 23 | # jetbrains setting folder 24 | .idea/ 25 | 26 | .vscode -------------------------------------------------------------------------------- /web/.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | public 3 | node_modules 4 | -------------------------------------------------------------------------------- /web/.prettierrc.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://prettier.io/docs/en/configuration.html 3 | * @type {import("prettier").Config} 4 | */ 5 | export default { 6 | semi: false, 7 | trailingComma: "es5", 8 | plugins: ["prettier-plugin-astro", "prettier-plugin-tailwindcss"], 9 | overrides: [ 10 | { 11 | files: "*.astro", 12 | options: { 13 | parser: "astro", 14 | }, 15 | }, 16 | ], 17 | } 18 | -------------------------------------------------------------------------------- /web/astro.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import react from "@astrojs/react" 3 | import tailwindcss from "@tailwindcss/vite" 4 | import { defineConfig } from "astro/config" 5 | 6 | // https://astro.build/config 7 | export default defineConfig({ 8 | devToolbar: { 9 | enabled: false, 10 | }, 11 | integrations: [react()], 12 | vite: { 13 | plugins: [tailwindcss()], 14 | server: { 15 | proxy: { 16 | "/api": "https://mail.sunls.de", 17 | // "/api": "http://127.0.0.1:3000", 18 | }, 19 | }, 20 | }, 21 | }) 22 | -------------------------------------------------------------------------------- /web/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "", 8 | "css": "src/style/global.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } 22 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tmail", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "scripts": { 6 | "dev": "astro dev", 7 | "build": "astro build", 8 | "preview": "astro preview", 9 | "fmt": "prettier . --write" 10 | }, 11 | "dependencies": { 12 | "@astrojs/react": "^4.2.1", 13 | "@nanostores/persistent": "^0.10.2", 14 | "@nanostores/react": "^0.8.4", 15 | "@radix-ui/react-alert-dialog": "^1.1.6", 16 | "@radix-ui/react-select": "^2.1.6", 17 | "@radix-ui/react-slot": "^1.1.2", 18 | "@tailwindcss/vite": "^4.0.14", 19 | "@types/react": "^19.0.12", 20 | "@types/react-dom": "^19.0.4", 21 | "astro": "^5.5.3", 22 | "class-variance-authority": "^0.7.1", 23 | "clsx": "^2.1.1", 24 | "lucide-react": "^0.483.0", 25 | "nanostores": "^0.11.4", 26 | "prettier": "^3.5.3", 27 | "prettier-plugin-astro": "^0.14.1", 28 | "prettier-plugin-tailwindcss": "^0.6.11", 29 | "react": "^19.0.0", 30 | "react-dom": "^19.0.0", 31 | "sonner": "^2.0.1", 32 | "tailwind-merge": "^3.0.2", 33 | "tailwindcss": "^4.0.14", 34 | "tailwindcss-animate": "^1.0.7" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /web/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunls24/tmail/10ee1c20969e01051aca951276cc7bc6af60e257/web/public/apple-touch-icon.png -------------------------------------------------------------------------------- /web/public/favicon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/components/Actions.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from "react" 2 | import { Button } from "@/components/ui/button.tsx" 3 | import { Dices, FilePenLine, GalleryVerticalEnd } from "lucide-react" 4 | import { $address, $domainList, updateAddress } from "@/lib/store/store.ts" 5 | import { randomAddress } from "@/lib/utils.ts" 6 | import { toast } from "sonner" 7 | import EditAddress from "@/components/EditAddress.tsx" 8 | import History from "@/components/History.tsx" 9 | import { type language, useTranslations } from "@/i18n/ui.ts" 10 | import { clsx } from "clsx" 11 | 12 | function Actions({ lang, className }: { lang: string; className?: string }) { 13 | const t = useMemo(() => useTranslations(lang as language), []) 14 | 15 | function onRandom() { 16 | const address = $address.get() 17 | const domain = address ? address.split("@")[1] : $domainList.get()[0] 18 | const newAddress = randomAddress(domain) 19 | updateAddress(newAddress) 20 | toast.success(t("randomNew") + " " + newAddress) 21 | } 22 | 23 | return ( 24 |