新增语言学习后台服务

- 分类管理:支持分类的增删改查
- 单词管理:支持单词的增删改查,包含音标、释义、例句等字段
- 更新 README 项目说明

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 22:38:09 +08:00
parent ea1b2569b5
commit 71a6bc4aa4
30 changed files with 1577 additions and 1 deletions
+21
View File
@@ -0,0 +1,21 @@
package repository
import (
"context"
"fmt"
"github.com/jackc/pgx/v5/pgxpool"
)
// NewPool creates a new pgx connection pool.
func NewPool(ctx context.Context, databaseURL string) (*pgxpool.Pool, error) {
pool, err := pgxpool.New(ctx, databaseURL)
if err != nil {
return nil, fmt.Errorf("failed to create connection pool: %w", err)
}
if err := pool.Ping(ctx); err != nil {
pool.Close()
return nil, fmt.Errorf("failed to ping database: %w", err)
}
return pool, nil
}
@@ -0,0 +1,122 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
// source: category.sql
package queries
import (
"context"
)
const createCategory = `-- name: CreateCategory :one
INSERT INTO categories (name, sort_order)
VALUES ($1, $2)
RETURNING id, name, sort_order, created_at, updated_at
`
type CreateCategoryParams struct {
Name string `json:"name"`
SortOrder int32 `json:"sort_order"`
}
func (q *Queries) CreateCategory(ctx context.Context, arg CreateCategoryParams) (Category, error) {
row := q.db.QueryRow(ctx, createCategory, arg.Name, arg.SortOrder)
var i Category
err := row.Scan(
&i.ID,
&i.Name,
&i.SortOrder,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const deleteCategory = `-- name: DeleteCategory :exec
DELETE FROM categories WHERE id = $1
`
func (q *Queries) DeleteCategory(ctx context.Context, id int32) error {
_, err := q.db.Exec(ctx, deleteCategory, id)
return err
}
const getCategory = `-- name: GetCategory :one
SELECT id, name, sort_order, created_at, updated_at
FROM categories
WHERE id = $1
`
func (q *Queries) GetCategory(ctx context.Context, id int32) (Category, error) {
row := q.db.QueryRow(ctx, getCategory, id)
var i Category
err := row.Scan(
&i.ID,
&i.Name,
&i.SortOrder,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const listCategories = `-- name: ListCategories :many
SELECT id, name, sort_order, created_at, updated_at
FROM categories
ORDER BY sort_order ASC, id ASC
`
func (q *Queries) ListCategories(ctx context.Context) ([]Category, error) {
rows, err := q.db.Query(ctx, listCategories)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Category{}
for rows.Next() {
var i Category
if err := rows.Scan(
&i.ID,
&i.Name,
&i.SortOrder,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const updateCategory = `-- name: UpdateCategory :one
UPDATE categories
SET name = $2,
sort_order = $3,
updated_at = NOW()
WHERE id = $1
RETURNING id, name, sort_order, created_at, updated_at
`
type UpdateCategoryParams struct {
ID int32 `json:"id"`
Name string `json:"name"`
SortOrder int32 `json:"sort_order"`
}
func (q *Queries) UpdateCategory(ctx context.Context, arg UpdateCategoryParams) (Category, error) {
row := q.db.QueryRow(ctx, updateCategory, arg.ID, arg.Name, arg.SortOrder)
var i Category
err := row.Scan(
&i.ID,
&i.Name,
&i.SortOrder,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
+32
View File
@@ -0,0 +1,32 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
package queries
import (
"context"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
)
type DBTX interface {
Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error)
Query(context.Context, string, ...interface{}) (pgx.Rows, error)
QueryRow(context.Context, string, ...interface{}) pgx.Row
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
type Queries struct {
db DBTX
}
func (q *Queries) WithTx(tx pgx.Tx) *Queries {
return &Queries{
db: tx,
}
}
@@ -0,0 +1,31 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
package queries
import (
"time"
)
type Category struct {
ID int32 `json:"id"`
Name string `json:"name"`
SortOrder int32 `json:"sort_order"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type Word struct {
ID int32 `json:"id"`
CategoryID int32 `json:"category_id"`
Word string `json:"word"`
Translation string `json:"translation"`
Phonetic *string `json:"phonetic"`
ExampleSentence *string `json:"example_sentence"`
AudioUrl *string `json:"audio_url"`
ImageUrl *string `json:"image_url"`
SortOrder int32 `json:"sort_order"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
@@ -0,0 +1,25 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
package queries
import (
"context"
)
type Querier interface {
CountWords(ctx context.Context, arg CountWordsParams) (int64, error)
CreateCategory(ctx context.Context, arg CreateCategoryParams) (Category, error)
CreateWord(ctx context.Context, arg CreateWordParams) (Word, error)
DeleteCategory(ctx context.Context, id int32) error
DeleteWord(ctx context.Context, id int32) error
GetCategory(ctx context.Context, id int32) (Category, error)
GetWord(ctx context.Context, id int32) (Word, error)
ListCategories(ctx context.Context) ([]Category, error)
ListWords(ctx context.Context, arg ListWordsParams) ([]Word, error)
UpdateCategory(ctx context.Context, arg UpdateCategoryParams) (Category, error)
UpdateWord(ctx context.Context, arg UpdateWordParams) (Word, error)
}
var _ Querier = (*Queries)(nil)
@@ -0,0 +1,227 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
// source: word.sql
package queries
import (
"context"
)
const countWords = `-- name: CountWords :one
SELECT COUNT(*) AS total
FROM words w
WHERE
($1::int = 0 OR w.category_id = $1)
AND ($2::text = '' OR w.word ILIKE '%' || $2 || '%' OR w.translation ILIKE '%' || $2 || '%')
`
type CountWordsParams struct {
CategoryID int32 `json:"category_id"`
Keyword string `json:"keyword"`
}
func (q *Queries) CountWords(ctx context.Context, arg CountWordsParams) (int64, error) {
row := q.db.QueryRow(ctx, countWords, arg.CategoryID, arg.Keyword)
var total int64
err := row.Scan(&total)
return total, err
}
const createWord = `-- name: CreateWord :one
INSERT INTO words (category_id, word, translation, phonetic, example_sentence, audio_url, image_url, sort_order)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
RETURNING id, category_id, word, translation, phonetic,
example_sentence, audio_url, image_url, sort_order,
created_at, updated_at
`
type CreateWordParams struct {
CategoryID int32 `json:"category_id"`
Word string `json:"word"`
Translation string `json:"translation"`
Phonetic *string `json:"phonetic"`
ExampleSentence *string `json:"example_sentence"`
AudioUrl *string `json:"audio_url"`
ImageUrl *string `json:"image_url"`
SortOrder int32 `json:"sort_order"`
}
func (q *Queries) CreateWord(ctx context.Context, arg CreateWordParams) (Word, error) {
row := q.db.QueryRow(ctx, createWord,
arg.CategoryID,
arg.Word,
arg.Translation,
arg.Phonetic,
arg.ExampleSentence,
arg.AudioUrl,
arg.ImageUrl,
arg.SortOrder,
)
var i Word
err := row.Scan(
&i.ID,
&i.CategoryID,
&i.Word,
&i.Translation,
&i.Phonetic,
&i.ExampleSentence,
&i.AudioUrl,
&i.ImageUrl,
&i.SortOrder,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const deleteWord = `-- name: DeleteWord :exec
DELETE FROM words WHERE id = $1
`
func (q *Queries) DeleteWord(ctx context.Context, id int32) error {
_, err := q.db.Exec(ctx, deleteWord, id)
return err
}
const getWord = `-- name: GetWord :one
SELECT id, category_id, word, translation, phonetic,
example_sentence, audio_url, image_url, sort_order,
created_at, updated_at
FROM words
WHERE id = $1
`
func (q *Queries) GetWord(ctx context.Context, id int32) (Word, error) {
row := q.db.QueryRow(ctx, getWord, id)
var i Word
err := row.Scan(
&i.ID,
&i.CategoryID,
&i.Word,
&i.Translation,
&i.Phonetic,
&i.ExampleSentence,
&i.AudioUrl,
&i.ImageUrl,
&i.SortOrder,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const listWords = `-- name: ListWords :many
SELECT w.id, w.category_id, w.word, w.translation, w.phonetic,
w.example_sentence, w.audio_url, w.image_url, w.sort_order,
w.created_at, w.updated_at
FROM words w
WHERE
($3::int = 0 OR w.category_id = $3)
AND ($4::text = '' OR w.word ILIKE '%' || $4 || '%' OR w.translation ILIKE '%' || $4 || '%')
ORDER BY w.sort_order ASC, w.id ASC
LIMIT $1 OFFSET $2
`
type ListWordsParams struct {
Limit int32 `json:"limit"`
Offset int32 `json:"offset"`
CategoryID int32 `json:"category_id"`
Keyword string `json:"keyword"`
}
func (q *Queries) ListWords(ctx context.Context, arg ListWordsParams) ([]Word, error) {
rows, err := q.db.Query(ctx, listWords,
arg.Limit,
arg.Offset,
arg.CategoryID,
arg.Keyword,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Word{}
for rows.Next() {
var i Word
if err := rows.Scan(
&i.ID,
&i.CategoryID,
&i.Word,
&i.Translation,
&i.Phonetic,
&i.ExampleSentence,
&i.AudioUrl,
&i.ImageUrl,
&i.SortOrder,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const updateWord = `-- name: UpdateWord :one
UPDATE words
SET category_id = $2,
word = $3,
translation = $4,
phonetic = $5,
example_sentence = $6,
audio_url = $7,
image_url = $8,
sort_order = $9,
updated_at = NOW()
WHERE id = $1
RETURNING id, category_id, word, translation, phonetic,
example_sentence, audio_url, image_url, sort_order,
created_at, updated_at
`
type UpdateWordParams struct {
ID int32 `json:"id"`
CategoryID int32 `json:"category_id"`
Word string `json:"word"`
Translation string `json:"translation"`
Phonetic *string `json:"phonetic"`
ExampleSentence *string `json:"example_sentence"`
AudioUrl *string `json:"audio_url"`
ImageUrl *string `json:"image_url"`
SortOrder int32 `json:"sort_order"`
}
func (q *Queries) UpdateWord(ctx context.Context, arg UpdateWordParams) (Word, error) {
row := q.db.QueryRow(ctx, updateWord,
arg.ID,
arg.CategoryID,
arg.Word,
arg.Translation,
arg.Phonetic,
arg.ExampleSentence,
arg.AudioUrl,
arg.ImageUrl,
arg.SortOrder,
)
var i Word
err := row.Scan(
&i.ID,
&i.CategoryID,
&i.Word,
&i.Translation,
&i.Phonetic,
&i.ExampleSentence,
&i.AudioUrl,
&i.ImageUrl,
&i.SortOrder,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}