This commit is contained in:
ivamp 2024-11-10 00:11:00 +08:00
parent a60608bdbc
commit d6fabedb23
27 changed files with 1645 additions and 42 deletions

View File

@ -33,6 +33,7 @@ import (
"leafdev.top/Ecosystem/recommender/internal/service/jwks"
"leafdev.top/Ecosystem/recommender/internal/service/post"
"leafdev.top/Ecosystem/recommender/internal/service/stream"
"leafdev.top/Ecosystem/recommender/internal/service/user"
)
// Injectors from wire.go:
@ -52,7 +53,10 @@ func CreateApp() (*base.Application, error) {
categoryService := category.NewService(query)
postController := application_v1.NewPostController(authService, applicationService, postService, categoryService)
categoryController := application_v1.NewCategoryController(authService, applicationService, postService, categoryService)
handlers := http.NewHandler(applicationController, application_v1ApplicationController, postController, categoryController)
userService := user.NewService(query, postService, loggerLogger)
redisRedis := redis.NewRedis(config)
userController := application_v1.NewUserController(authService, applicationService, userService, postService, loggerLogger, redisRedis)
handlers := http.NewHandler(applicationController, application_v1ApplicationController, postController, categoryController, userController)
api := router.NewApiRoute(handlers)
swaggerRouter := router.NewSwaggerRoute()
ginLoggerMiddleware := middleware.NewGinLoggerMiddleware(loggerLogger)
@ -67,8 +71,7 @@ func CreateApp() (*base.Application, error) {
grpcInterceptor := grpc.NewInterceptor(interceptorAuth, interceptorLogger)
grpcHandlers := grpc.NewHandler(documentService, grpcInterceptor)
handlerHandler := handler.NewHandler(grpcHandlers, handlers)
serviceService := service.NewService(loggerLogger, jwksJWKS, streamService, authService, applicationService, postService, categoryService)
redisRedis := redis.NewRedis(config)
serviceService := service.NewService(loggerLogger, jwksJWKS, streamService, authService, applicationService, postService, categoryService, userService)
batchBatch := batch.NewBatch(loggerLogger)
s3S3 := s3.NewS3(config)
baseApplication := base.NewApplication(config, httpServer, handlerHandler, loggerLogger, serviceService, httpMiddleware, redisRedis, batchBatch, s3S3, db, query)

View File

@ -22,6 +22,7 @@ redis:
port: 6379
password: ""
db: 0
prefix: "recommender_"
jwks:
url: ""

View File

@ -697,6 +697,120 @@ const docTemplate = `{
}
}
}
},
"/applications/v1/users/_dislike": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "从用户的标签喜好中移除内容",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"application_api"
],
"summary": "Dislike",
"parameters": [
{
"description": "UserLikePost",
"name": "UserLikePost",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.UserLikePost"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.ResponseBody"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/entity.Category"
}
}
}
]
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.ResponseBody"
}
}
}
}
},
"/applications/v1/users/_like": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "将标签附加到用户名",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"application_api"
],
"summary": "Like",
"parameters": [
{
"description": "UserLikePost",
"name": "UserLikePost",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.UserLikePost"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.ResponseBody"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/entity.Category"
}
}
}
]
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.ResponseBody"
}
}
}
}
}
},
"definitions": {
@ -900,6 +1014,17 @@ const docTemplate = `{
}
}
},
"request.UserLikePost": {
"type": "object",
"properties": {
"external_user_id": {
"type": "string"
},
"post_id": {
"type": "integer"
}
}
},
"response.ResponseBody": {
"type": "object",
"properties": {

View File

@ -688,6 +688,120 @@
}
}
}
},
"/applications/v1/users/_dislike": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "从用户的标签喜好中移除内容",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"application_api"
],
"summary": "Dislike",
"parameters": [
{
"description": "UserLikePost",
"name": "UserLikePost",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.UserLikePost"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.ResponseBody"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/entity.Category"
}
}
}
]
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.ResponseBody"
}
}
}
}
},
"/applications/v1/users/_like": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "将标签附加到用户名",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"application_api"
],
"summary": "Like",
"parameters": [
{
"description": "UserLikePost",
"name": "UserLikePost",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.UserLikePost"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.ResponseBody"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/entity.Category"
}
}
}
]
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.ResponseBody"
}
}
}
}
}
},
"definitions": {
@ -891,6 +1005,17 @@
}
}
},
"request.UserLikePost": {
"type": "object",
"properties": {
"external_user_id": {
"type": "string"
},
"post_id": {
"type": "integer"
}
}
},
"response.ResponseBody": {
"type": "object",
"properties": {

View File

@ -133,6 +133,13 @@ definitions:
- target_id
- title
type: object
request.UserLikePost:
properties:
external_user_id:
type: string
post_id:
type: integer
type: object
response.ResponseBody:
properties:
data: {}
@ -541,6 +548,72 @@ paths:
summary: 新建资源
tags:
- application_api
/applications/v1/users/_dislike:
post:
consumes:
- application/json
description: 从用户的标签喜好中移除内容
parameters:
- description: UserLikePost
in: body
name: UserLikePost
required: true
schema:
$ref: '#/definitions/request.UserLikePost'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.ResponseBody'
- properties:
data:
$ref: '#/definitions/entity.Category'
type: object
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.ResponseBody'
security:
- ApiKeyAuth: []
summary: Dislike
tags:
- application_api
/applications/v1/users/_like:
post:
consumes:
- application/json
description: 将标签附加到用户名
parameters:
- description: UserLikePost
in: body
name: UserLikePost
required: true
schema:
$ref: '#/definitions/request.UserLikePost'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.ResponseBody'
- properties:
data:
$ref: '#/definitions/entity.Category'
type: object
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.ResponseBody'
security:
- ApiKeyAuth: []
summary: Like
tags:
- application_api
securityDefinitions:
ApiKeyAuth:
in: header

View File

@ -34,6 +34,7 @@ func main() {
entity.ApplicationToken{},
entity.UserTagScore{},
entity.Category{},
entity.ExternalUser{},
)
// Generate Type Safe API with Dynamic SQL defined on Querier interface for `model.User` and `model.Company`

View File

@ -53,6 +53,7 @@ type Redis struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
Password string `yaml:"password"`
Prefix string `yaml:"prefix"`
DB int `yaml:"db"`
}

View File

@ -12,6 +12,7 @@ import (
type Redis struct {
Client *redis.Client
Locker *redislock.Client
config *conf.Config
}
func NewRedis(c *conf.Config) *Redis {
@ -32,7 +33,12 @@ func NewRedis(c *conf.Config) *Redis {
var r = &Redis{
Client: client,
Locker: locker,
config: c,
}
return r
}
func (r *Redis) Prefix(key string) string {
return fmt.Sprintf("%s:%s", r.config.Redis.Prefix, key)
}

View File

@ -0,0 +1,486 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package dao
import (
"context"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"gorm.io/gorm/schema"
"gorm.io/gen"
"gorm.io/gen/field"
"gorm.io/plugin/dbresolver"
"leafdev.top/Ecosystem/recommender/internal/entity"
)
func newExternalUser(db *gorm.DB, opts ...gen.DOOption) externalUser {
_externalUser := externalUser{}
_externalUser.externalUserDo.UseDB(db, opts...)
_externalUser.externalUserDo.UseModel(&entity.ExternalUser{})
tableName := _externalUser.externalUserDo.TableName()
_externalUser.ALL = field.NewAsterisk(tableName)
_externalUser.Id = field.NewUint(tableName, "id")
_externalUser.CreatedAt = field.NewTime(tableName, "created_at")
_externalUser.UpdatedAt = field.NewTime(tableName, "updated_at")
_externalUser.Name = field.NewString(tableName, "name")
_externalUser.Email = field.NewString(tableName, "email")
_externalUser.ExternalId = field.NewString(tableName, "external_id")
_externalUser.Summary = field.NewString(tableName, "summary")
_externalUser.ApplicationId = field.NewUint(tableName, "application_id")
_externalUser.Application = externalUserBelongsToApplication{
db: db.Session(&gorm.Session{}),
RelationField: field.NewRelation("Application", "entity.Application"),
}
_externalUser.fillFieldMap()
return _externalUser
}
type externalUser struct {
externalUserDo
ALL field.Asterisk
Id field.Uint
CreatedAt field.Time
UpdatedAt field.Time
Name field.String
Email field.String
ExternalId field.String
Summary field.String
ApplicationId field.Uint
Application externalUserBelongsToApplication
fieldMap map[string]field.Expr
}
func (e externalUser) Table(newTableName string) *externalUser {
e.externalUserDo.UseTable(newTableName)
return e.updateTableName(newTableName)
}
func (e externalUser) As(alias string) *externalUser {
e.externalUserDo.DO = *(e.externalUserDo.As(alias).(*gen.DO))
return e.updateTableName(alias)
}
func (e *externalUser) updateTableName(table string) *externalUser {
e.ALL = field.NewAsterisk(table)
e.Id = field.NewUint(table, "id")
e.CreatedAt = field.NewTime(table, "created_at")
e.UpdatedAt = field.NewTime(table, "updated_at")
e.Name = field.NewString(table, "name")
e.Email = field.NewString(table, "email")
e.ExternalId = field.NewString(table, "external_id")
e.Summary = field.NewString(table, "summary")
e.ApplicationId = field.NewUint(table, "application_id")
e.fillFieldMap()
return e
}
func (e *externalUser) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
_f, ok := e.fieldMap[fieldName]
if !ok || _f == nil {
return nil, false
}
_oe, ok := _f.(field.OrderExpr)
return _oe, ok
}
func (e *externalUser) fillFieldMap() {
e.fieldMap = make(map[string]field.Expr, 9)
e.fieldMap["id"] = e.Id
e.fieldMap["created_at"] = e.CreatedAt
e.fieldMap["updated_at"] = e.UpdatedAt
e.fieldMap["name"] = e.Name
e.fieldMap["email"] = e.Email
e.fieldMap["external_id"] = e.ExternalId
e.fieldMap["summary"] = e.Summary
e.fieldMap["application_id"] = e.ApplicationId
}
func (e externalUser) clone(db *gorm.DB) externalUser {
e.externalUserDo.ReplaceConnPool(db.Statement.ConnPool)
return e
}
func (e externalUser) replaceDB(db *gorm.DB) externalUser {
e.externalUserDo.ReplaceDB(db)
return e
}
type externalUserBelongsToApplication struct {
db *gorm.DB
field.RelationField
}
func (a externalUserBelongsToApplication) Where(conds ...field.Expr) *externalUserBelongsToApplication {
if len(conds) == 0 {
return &a
}
exprs := make([]clause.Expression, 0, len(conds))
for _, cond := range conds {
exprs = append(exprs, cond.BeCond().(clause.Expression))
}
a.db = a.db.Clauses(clause.Where{Exprs: exprs})
return &a
}
func (a externalUserBelongsToApplication) WithContext(ctx context.Context) *externalUserBelongsToApplication {
a.db = a.db.WithContext(ctx)
return &a
}
func (a externalUserBelongsToApplication) Session(session *gorm.Session) *externalUserBelongsToApplication {
a.db = a.db.Session(session)
return &a
}
func (a externalUserBelongsToApplication) Model(m *entity.ExternalUser) *externalUserBelongsToApplicationTx {
return &externalUserBelongsToApplicationTx{a.db.Model(m).Association(a.Name())}
}
type externalUserBelongsToApplicationTx struct{ tx *gorm.Association }
func (a externalUserBelongsToApplicationTx) Find() (result *entity.Application, err error) {
return result, a.tx.Find(&result)
}
func (a externalUserBelongsToApplicationTx) Append(values ...*entity.Application) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Append(targetValues...)
}
func (a externalUserBelongsToApplicationTx) Replace(values ...*entity.Application) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Replace(targetValues...)
}
func (a externalUserBelongsToApplicationTx) Delete(values ...*entity.Application) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Delete(targetValues...)
}
func (a externalUserBelongsToApplicationTx) Clear() error {
return a.tx.Clear()
}
func (a externalUserBelongsToApplicationTx) Count() int64 {
return a.tx.Count()
}
type externalUserDo struct{ gen.DO }
type IExternalUserDo interface {
gen.SubQuery
Debug() IExternalUserDo
WithContext(ctx context.Context) IExternalUserDo
WithResult(fc func(tx gen.Dao)) gen.ResultInfo
ReplaceDB(db *gorm.DB)
ReadDB() IExternalUserDo
WriteDB() IExternalUserDo
As(alias string) gen.Dao
Session(config *gorm.Session) IExternalUserDo
Columns(cols ...field.Expr) gen.Columns
Clauses(conds ...clause.Expression) IExternalUserDo
Not(conds ...gen.Condition) IExternalUserDo
Or(conds ...gen.Condition) IExternalUserDo
Select(conds ...field.Expr) IExternalUserDo
Where(conds ...gen.Condition) IExternalUserDo
Order(conds ...field.Expr) IExternalUserDo
Distinct(cols ...field.Expr) IExternalUserDo
Omit(cols ...field.Expr) IExternalUserDo
Join(table schema.Tabler, on ...field.Expr) IExternalUserDo
LeftJoin(table schema.Tabler, on ...field.Expr) IExternalUserDo
RightJoin(table schema.Tabler, on ...field.Expr) IExternalUserDo
Group(cols ...field.Expr) IExternalUserDo
Having(conds ...gen.Condition) IExternalUserDo
Limit(limit int) IExternalUserDo
Offset(offset int) IExternalUserDo
Count() (count int64, err error)
Scopes(funcs ...func(gen.Dao) gen.Dao) IExternalUserDo
Unscoped() IExternalUserDo
Create(values ...*entity.ExternalUser) error
CreateInBatches(values []*entity.ExternalUser, batchSize int) error
Save(values ...*entity.ExternalUser) error
First() (*entity.ExternalUser, error)
Take() (*entity.ExternalUser, error)
Last() (*entity.ExternalUser, error)
Find() ([]*entity.ExternalUser, error)
FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*entity.ExternalUser, err error)
FindInBatches(result *[]*entity.ExternalUser, batchSize int, fc func(tx gen.Dao, batch int) error) error
Pluck(column field.Expr, dest interface{}) error
Delete(...*entity.ExternalUser) (info gen.ResultInfo, err error)
Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error)
UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)
Updates(value interface{}) (info gen.ResultInfo, err error)
UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error)
UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)
UpdateColumns(value interface{}) (info gen.ResultInfo, err error)
UpdateFrom(q gen.SubQuery) gen.Dao
Attrs(attrs ...field.AssignExpr) IExternalUserDo
Assign(attrs ...field.AssignExpr) IExternalUserDo
Joins(fields ...field.RelationField) IExternalUserDo
Preload(fields ...field.RelationField) IExternalUserDo
FirstOrInit() (*entity.ExternalUser, error)
FirstOrCreate() (*entity.ExternalUser, error)
FindByPage(offset int, limit int) (result []*entity.ExternalUser, count int64, err error)
ScanByPage(result interface{}, offset int, limit int) (count int64, err error)
Scan(result interface{}) (err error)
Returning(value interface{}, columns ...string) IExternalUserDo
UnderlyingDB() *gorm.DB
schema.Tabler
}
func (e externalUserDo) Debug() IExternalUserDo {
return e.withDO(e.DO.Debug())
}
func (e externalUserDo) WithContext(ctx context.Context) IExternalUserDo {
return e.withDO(e.DO.WithContext(ctx))
}
func (e externalUserDo) ReadDB() IExternalUserDo {
return e.Clauses(dbresolver.Read)
}
func (e externalUserDo) WriteDB() IExternalUserDo {
return e.Clauses(dbresolver.Write)
}
func (e externalUserDo) Session(config *gorm.Session) IExternalUserDo {
return e.withDO(e.DO.Session(config))
}
func (e externalUserDo) Clauses(conds ...clause.Expression) IExternalUserDo {
return e.withDO(e.DO.Clauses(conds...))
}
func (e externalUserDo) Returning(value interface{}, columns ...string) IExternalUserDo {
return e.withDO(e.DO.Returning(value, columns...))
}
func (e externalUserDo) Not(conds ...gen.Condition) IExternalUserDo {
return e.withDO(e.DO.Not(conds...))
}
func (e externalUserDo) Or(conds ...gen.Condition) IExternalUserDo {
return e.withDO(e.DO.Or(conds...))
}
func (e externalUserDo) Select(conds ...field.Expr) IExternalUserDo {
return e.withDO(e.DO.Select(conds...))
}
func (e externalUserDo) Where(conds ...gen.Condition) IExternalUserDo {
return e.withDO(e.DO.Where(conds...))
}
func (e externalUserDo) Order(conds ...field.Expr) IExternalUserDo {
return e.withDO(e.DO.Order(conds...))
}
func (e externalUserDo) Distinct(cols ...field.Expr) IExternalUserDo {
return e.withDO(e.DO.Distinct(cols...))
}
func (e externalUserDo) Omit(cols ...field.Expr) IExternalUserDo {
return e.withDO(e.DO.Omit(cols...))
}
func (e externalUserDo) Join(table schema.Tabler, on ...field.Expr) IExternalUserDo {
return e.withDO(e.DO.Join(table, on...))
}
func (e externalUserDo) LeftJoin(table schema.Tabler, on ...field.Expr) IExternalUserDo {
return e.withDO(e.DO.LeftJoin(table, on...))
}
func (e externalUserDo) RightJoin(table schema.Tabler, on ...field.Expr) IExternalUserDo {
return e.withDO(e.DO.RightJoin(table, on...))
}
func (e externalUserDo) Group(cols ...field.Expr) IExternalUserDo {
return e.withDO(e.DO.Group(cols...))
}
func (e externalUserDo) Having(conds ...gen.Condition) IExternalUserDo {
return e.withDO(e.DO.Having(conds...))
}
func (e externalUserDo) Limit(limit int) IExternalUserDo {
return e.withDO(e.DO.Limit(limit))
}
func (e externalUserDo) Offset(offset int) IExternalUserDo {
return e.withDO(e.DO.Offset(offset))
}
func (e externalUserDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IExternalUserDo {
return e.withDO(e.DO.Scopes(funcs...))
}
func (e externalUserDo) Unscoped() IExternalUserDo {
return e.withDO(e.DO.Unscoped())
}
func (e externalUserDo) Create(values ...*entity.ExternalUser) error {
if len(values) == 0 {
return nil
}
return e.DO.Create(values)
}
func (e externalUserDo) CreateInBatches(values []*entity.ExternalUser, batchSize int) error {
return e.DO.CreateInBatches(values, batchSize)
}
// Save : !!! underlying implementation is different with GORM
// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values)
func (e externalUserDo) Save(values ...*entity.ExternalUser) error {
if len(values) == 0 {
return nil
}
return e.DO.Save(values)
}
func (e externalUserDo) First() (*entity.ExternalUser, error) {
if result, err := e.DO.First(); err != nil {
return nil, err
} else {
return result.(*entity.ExternalUser), nil
}
}
func (e externalUserDo) Take() (*entity.ExternalUser, error) {
if result, err := e.DO.Take(); err != nil {
return nil, err
} else {
return result.(*entity.ExternalUser), nil
}
}
func (e externalUserDo) Last() (*entity.ExternalUser, error) {
if result, err := e.DO.Last(); err != nil {
return nil, err
} else {
return result.(*entity.ExternalUser), nil
}
}
func (e externalUserDo) Find() ([]*entity.ExternalUser, error) {
result, err := e.DO.Find()
return result.([]*entity.ExternalUser), err
}
func (e externalUserDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*entity.ExternalUser, err error) {
buf := make([]*entity.ExternalUser, 0, batchSize)
err = e.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error {
defer func() { results = append(results, buf...) }()
return fc(tx, batch)
})
return results, err
}
func (e externalUserDo) FindInBatches(result *[]*entity.ExternalUser, batchSize int, fc func(tx gen.Dao, batch int) error) error {
return e.DO.FindInBatches(result, batchSize, fc)
}
func (e externalUserDo) Attrs(attrs ...field.AssignExpr) IExternalUserDo {
return e.withDO(e.DO.Attrs(attrs...))
}
func (e externalUserDo) Assign(attrs ...field.AssignExpr) IExternalUserDo {
return e.withDO(e.DO.Assign(attrs...))
}
func (e externalUserDo) Joins(fields ...field.RelationField) IExternalUserDo {
for _, _f := range fields {
e = *e.withDO(e.DO.Joins(_f))
}
return &e
}
func (e externalUserDo) Preload(fields ...field.RelationField) IExternalUserDo {
for _, _f := range fields {
e = *e.withDO(e.DO.Preload(_f))
}
return &e
}
func (e externalUserDo) FirstOrInit() (*entity.ExternalUser, error) {
if result, err := e.DO.FirstOrInit(); err != nil {
return nil, err
} else {
return result.(*entity.ExternalUser), nil
}
}
func (e externalUserDo) FirstOrCreate() (*entity.ExternalUser, error) {
if result, err := e.DO.FirstOrCreate(); err != nil {
return nil, err
} else {
return result.(*entity.ExternalUser), nil
}
}
func (e externalUserDo) FindByPage(offset int, limit int) (result []*entity.ExternalUser, count int64, err error) {
result, err = e.Offset(offset).Limit(limit).Find()
if err != nil {
return
}
if size := len(result); 0 < limit && 0 < size && size < limit {
count = int64(size + offset)
return
}
count, err = e.Offset(-1).Limit(-1).Count()
return
}
func (e externalUserDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) {
count, err = e.Count()
if err != nil {
return
}
err = e.Offset(offset).Limit(limit).Scan(result)
return
}
func (e externalUserDo) Scan(result interface{}) (err error) {
return e.DO.Scan(result)
}
func (e externalUserDo) Delete(models ...*entity.ExternalUser) (result gen.ResultInfo, err error) {
return e.DO.Delete(models)
}
func (e *externalUserDo) withDO(do gen.Dao) *externalUserDo {
e.DO = *do.(*gen.DO)
return e
}

View File

@ -20,6 +20,7 @@ var (
Application *application
ApplicationToken *applicationToken
Category *category
ExternalUser *externalUser
Post *post
PostTag *postTag
Tag *tag
@ -33,6 +34,7 @@ func SetDefault(db *gorm.DB, opts ...gen.DOOption) {
Application = &Q.Application
ApplicationToken = &Q.ApplicationToken
Category = &Q.Category
ExternalUser = &Q.ExternalUser
Post = &Q.Post
PostTag = &Q.PostTag
Tag = &Q.Tag
@ -47,6 +49,7 @@ func Use(db *gorm.DB, opts ...gen.DOOption) *Query {
Application: newApplication(db, opts...),
ApplicationToken: newApplicationToken(db, opts...),
Category: newCategory(db, opts...),
ExternalUser: newExternalUser(db, opts...),
Post: newPost(db, opts...),
PostTag: newPostTag(db, opts...),
Tag: newTag(db, opts...),
@ -62,6 +65,7 @@ type Query struct {
Application application
ApplicationToken applicationToken
Category category
ExternalUser externalUser
Post post
PostTag postTag
Tag tag
@ -78,6 +82,7 @@ func (q *Query) clone(db *gorm.DB) *Query {
Application: q.Application.clone(db),
ApplicationToken: q.ApplicationToken.clone(db),
Category: q.Category.clone(db),
ExternalUser: q.ExternalUser.clone(db),
Post: q.Post.clone(db),
PostTag: q.PostTag.clone(db),
Tag: q.Tag.clone(db),
@ -101,6 +106,7 @@ func (q *Query) ReplaceDB(db *gorm.DB) *Query {
Application: q.Application.replaceDB(db),
ApplicationToken: q.ApplicationToken.replaceDB(db),
Category: q.Category.replaceDB(db),
ExternalUser: q.ExternalUser.replaceDB(db),
Post: q.Post.replaceDB(db),
PostTag: q.PostTag.replaceDB(db),
Tag: q.Tag.replaceDB(db),
@ -114,6 +120,7 @@ type queryCtx struct {
Application IApplicationDo
ApplicationToken IApplicationTokenDo
Category ICategoryDo
ExternalUser IExternalUserDo
Post IPostDo
PostTag IPostTagDo
Tag ITagDo
@ -127,6 +134,7 @@ func (q *Query) WithContext(ctx context.Context) *queryCtx {
Application: q.Application.WithContext(ctx),
ApplicationToken: q.ApplicationToken.WithContext(ctx),
Category: q.Category.WithContext(ctx),
ExternalUser: q.ExternalUser.WithContext(ctx),
Post: q.Post.WithContext(ctx),
PostTag: q.PostTag.WithContext(ctx),
Tag: q.Tag.WithContext(ctx),

View File

@ -27,9 +27,15 @@ func newUserLike(db *gorm.DB, opts ...gen.DOOption) userLike {
tableName := _userLike.userLikeDo.TableName()
_userLike.ALL = field.NewAsterisk(tableName)
_userLike.UserId = field.NewString(tableName, "user_id")
_userLike.ExternalUserId = field.NewUint(tableName, "external_user_id")
_userLike.PostId = field.NewUint(tableName, "post_id")
_userLike.Type = field.NewString(tableName, "type")
_userLike.ApplicationId = field.NewUint(tableName, "application_id")
_userLike.Application = userLikeBelongsToApplication{
db: db.Session(&gorm.Session{}),
RelationField: field.NewRelation("Application", "entity.Application"),
}
_userLike.fillFieldMap()
@ -39,10 +45,12 @@ func newUserLike(db *gorm.DB, opts ...gen.DOOption) userLike {
type userLike struct {
userLikeDo
ALL field.Asterisk
UserId field.String
PostId field.Uint
Type field.String
ALL field.Asterisk
ExternalUserId field.Uint
PostId field.Uint
Type field.String
ApplicationId field.Uint
Application userLikeBelongsToApplication
fieldMap map[string]field.Expr
}
@ -59,9 +67,10 @@ func (u userLike) As(alias string) *userLike {
func (u *userLike) updateTableName(table string) *userLike {
u.ALL = field.NewAsterisk(table)
u.UserId = field.NewString(table, "user_id")
u.ExternalUserId = field.NewUint(table, "external_user_id")
u.PostId = field.NewUint(table, "post_id")
u.Type = field.NewString(table, "type")
u.ApplicationId = field.NewUint(table, "application_id")
u.fillFieldMap()
@ -78,10 +87,12 @@ func (u *userLike) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
}
func (u *userLike) fillFieldMap() {
u.fieldMap = make(map[string]field.Expr, 3)
u.fieldMap["user_id"] = u.UserId
u.fieldMap = make(map[string]field.Expr, 5)
u.fieldMap["external_user_id"] = u.ExternalUserId
u.fieldMap["post_id"] = u.PostId
u.fieldMap["type"] = u.Type
u.fieldMap["application_id"] = u.ApplicationId
}
func (u userLike) clone(db *gorm.DB) userLike {
@ -94,6 +105,77 @@ func (u userLike) replaceDB(db *gorm.DB) userLike {
return u
}
type userLikeBelongsToApplication struct {
db *gorm.DB
field.RelationField
}
func (a userLikeBelongsToApplication) Where(conds ...field.Expr) *userLikeBelongsToApplication {
if len(conds) == 0 {
return &a
}
exprs := make([]clause.Expression, 0, len(conds))
for _, cond := range conds {
exprs = append(exprs, cond.BeCond().(clause.Expression))
}
a.db = a.db.Clauses(clause.Where{Exprs: exprs})
return &a
}
func (a userLikeBelongsToApplication) WithContext(ctx context.Context) *userLikeBelongsToApplication {
a.db = a.db.WithContext(ctx)
return &a
}
func (a userLikeBelongsToApplication) Session(session *gorm.Session) *userLikeBelongsToApplication {
a.db = a.db.Session(session)
return &a
}
func (a userLikeBelongsToApplication) Model(m *entity.UserLike) *userLikeBelongsToApplicationTx {
return &userLikeBelongsToApplicationTx{a.db.Model(m).Association(a.Name())}
}
type userLikeBelongsToApplicationTx struct{ tx *gorm.Association }
func (a userLikeBelongsToApplicationTx) Find() (result *entity.Application, err error) {
return result, a.tx.Find(&result)
}
func (a userLikeBelongsToApplicationTx) Append(values ...*entity.Application) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Append(targetValues...)
}
func (a userLikeBelongsToApplicationTx) Replace(values ...*entity.Application) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Replace(targetValues...)
}
func (a userLikeBelongsToApplicationTx) Delete(values ...*entity.Application) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Delete(targetValues...)
}
func (a userLikeBelongsToApplicationTx) Clear() error {
return a.tx.Clear()
}
func (a userLikeBelongsToApplicationTx) Count() int64 {
return a.tx.Count()
}
type userLikeDo struct{ gen.DO }
type IUserLikeDo interface {

View File

@ -27,9 +27,10 @@ func newUserTagScore(db *gorm.DB, opts ...gen.DOOption) userTagScore {
tableName := _userTagScore.userTagScoreDo.TableName()
_userTagScore.ALL = field.NewAsterisk(tableName)
_userTagScore.UserId = field.NewString(tableName, "user_id")
_userTagScore.ExternalUserId = field.NewUint(tableName, "external_user_id")
_userTagScore.TagId = field.NewUint(tableName, "tag_id")
_userTagScore.Score = field.NewInt(tableName, "score")
_userTagScore.ApplicationId = field.NewUint(tableName, "application_id")
_userTagScore.Tag = userTagScoreBelongsToTag{
db: db.Session(&gorm.Session{}),
@ -41,6 +42,12 @@ func newUserTagScore(db *gorm.DB, opts ...gen.DOOption) userTagScore {
},
}
_userTagScore.Application = userTagScoreBelongsToApplication{
db: db.Session(&gorm.Session{}),
RelationField: field.NewRelation("Application", "entity.Application"),
}
_userTagScore.fillFieldMap()
return _userTagScore
@ -49,11 +56,14 @@ func newUserTagScore(db *gorm.DB, opts ...gen.DOOption) userTagScore {
type userTagScore struct {
userTagScoreDo
ALL field.Asterisk
UserId field.String
TagId field.Uint
Score field.Int
Tag userTagScoreBelongsToTag
ALL field.Asterisk
ExternalUserId field.Uint
TagId field.Uint
Score field.Int
ApplicationId field.Uint
Tag userTagScoreBelongsToTag
Application userTagScoreBelongsToApplication
fieldMap map[string]field.Expr
}
@ -70,9 +80,10 @@ func (u userTagScore) As(alias string) *userTagScore {
func (u *userTagScore) updateTableName(table string) *userTagScore {
u.ALL = field.NewAsterisk(table)
u.UserId = field.NewString(table, "user_id")
u.ExternalUserId = field.NewUint(table, "external_user_id")
u.TagId = field.NewUint(table, "tag_id")
u.Score = field.NewInt(table, "score")
u.ApplicationId = field.NewUint(table, "application_id")
u.fillFieldMap()
@ -89,10 +100,11 @@ func (u *userTagScore) GetFieldByName(fieldName string) (field.OrderExpr, bool)
}
func (u *userTagScore) fillFieldMap() {
u.fieldMap = make(map[string]field.Expr, 4)
u.fieldMap["user_id"] = u.UserId
u.fieldMap = make(map[string]field.Expr, 6)
u.fieldMap["external_user_id"] = u.ExternalUserId
u.fieldMap["tag_id"] = u.TagId
u.fieldMap["score"] = u.Score
u.fieldMap["application_id"] = u.ApplicationId
}
@ -181,6 +193,77 @@ func (a userTagScoreBelongsToTagTx) Count() int64 {
return a.tx.Count()
}
type userTagScoreBelongsToApplication struct {
db *gorm.DB
field.RelationField
}
func (a userTagScoreBelongsToApplication) Where(conds ...field.Expr) *userTagScoreBelongsToApplication {
if len(conds) == 0 {
return &a
}
exprs := make([]clause.Expression, 0, len(conds))
for _, cond := range conds {
exprs = append(exprs, cond.BeCond().(clause.Expression))
}
a.db = a.db.Clauses(clause.Where{Exprs: exprs})
return &a
}
func (a userTagScoreBelongsToApplication) WithContext(ctx context.Context) *userTagScoreBelongsToApplication {
a.db = a.db.WithContext(ctx)
return &a
}
func (a userTagScoreBelongsToApplication) Session(session *gorm.Session) *userTagScoreBelongsToApplication {
a.db = a.db.Session(session)
return &a
}
func (a userTagScoreBelongsToApplication) Model(m *entity.UserTagScore) *userTagScoreBelongsToApplicationTx {
return &userTagScoreBelongsToApplicationTx{a.db.Model(m).Association(a.Name())}
}
type userTagScoreBelongsToApplicationTx struct{ tx *gorm.Association }
func (a userTagScoreBelongsToApplicationTx) Find() (result *entity.Application, err error) {
return result, a.tx.Find(&result)
}
func (a userTagScoreBelongsToApplicationTx) Append(values ...*entity.Application) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Append(targetValues...)
}
func (a userTagScoreBelongsToApplicationTx) Replace(values ...*entity.Application) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Replace(targetValues...)
}
func (a userTagScoreBelongsToApplicationTx) Delete(values ...*entity.Application) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Delete(targetValues...)
}
func (a userTagScoreBelongsToApplicationTx) Clear() error {
return a.tx.Clear()
}
func (a userTagScoreBelongsToApplicationTx) Count() int64 {
return a.tx.Count()
}
type userTagScoreDo struct{ gen.DO }
type IUserTagScoreDo interface {

View File

@ -11,10 +11,27 @@ func (u *User) TableName() string {
return "users"
}
type ExternalUser struct {
Model
Id schema.EntityId `gorm:"primarykey" json:"id"`
Name string `json:"name"`
Email string `json:"email"`
ExternalId string `json:"external_id"`
Summary string `json:"summary"`
ApplicationId schema.EntityId `json:"application_id"`
Application *Application
}
func (u *ExternalUser) TableName() string {
return "external_users"
}
type UserLike struct {
UserId schema.UserId `gorm:"primarykey" json:"user_id"`
PostId schema.EntityId `gorm:"primarykey" json:"post_id"`
Type schema.UserLikeType `json:"type"`
ExternalUserId schema.EntityId `gorm:"primarykey" json:"external_user_id"`
PostId schema.EntityId `gorm:"primarykey" json:"post_id"`
Type schema.UserLikeType `json:"type"`
Application *Application
ApplicationId schema.EntityId `json:"application_id"`
}
func (u *UserLike) TableName() string {
@ -22,10 +39,12 @@ func (u *UserLike) TableName() string {
}
type UserTagScore struct {
UserId schema.UserId `gorm:"primarykey" json:"user_id"`
TagId schema.EntityId `gorm:"primarykey" json:"tag_id"`
Tag *Tag
Score int `json:"score"`
ExternalUserId schema.EntityId `gorm:"primarykey" json:"external_user_id"`
TagId schema.EntityId `gorm:"primarykey" json:"tag_id"`
Tag *Tag
Score int `json:"score"`
Application *Application
ApplicationId schema.EntityId `json:"application_id"`
}
func (u *UserTagScore) TableName() string {

View File

@ -0,0 +1,199 @@
package application_v1
import (
"github.com/gin-gonic/gin"
"leafdev.top/Ecosystem/recommender/internal/base/logger"
"leafdev.top/Ecosystem/recommender/internal/base/redis"
"leafdev.top/Ecosystem/recommender/internal/handler/http/request"
"leafdev.top/Ecosystem/recommender/internal/handler/http/response"
"leafdev.top/Ecosystem/recommender/internal/service/application"
"leafdev.top/Ecosystem/recommender/internal/service/auth"
"leafdev.top/Ecosystem/recommender/internal/service/post"
"leafdev.top/Ecosystem/recommender/internal/service/user"
"leafdev.top/Ecosystem/recommender/pkg/consts"
"net/http"
"time"
)
const TaskProcessing = "user_likes"
var LockTTL = time.Minute * 10
type UserController struct {
authService *auth.Service
applicationService *application.Service
userService *user.Service
postService *post.Service
logger *logger.Logger
redis *redis.Redis
}
func NewUserController(
authService *auth.Service,
applicationService *application.Service,
userService *user.Service,
postService *post.Service,
logger *logger.Logger,
redis *redis.Redis,
) *UserController {
return &UserController{
authService: authService,
applicationService: applicationService,
userService: userService,
postService: postService,
logger: logger,
redis: redis,
}
}
// Like godoc
// @Summary Like
// @Description 将标签附加到用户名
// @Tags application_api
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Param UserLikePost body request.UserLikePost true "UserLikePost"
// @Success 200 {object} response.ResponseBody{data=entity.Category}
// @Failure 400 {object} response.ResponseBody
// @Router /applications/v1/users/_like [post]
func (uc *UserController) Like(c *gin.Context) {
app, err := uc.authService.GetApplication(c)
if err != nil {
response.Ctx(c).Error(err).Status(http.StatusBadRequest).Send()
return
}
var userLikePostRequest = &request.UserLikePost{}
if err := c.ShouldBindJSON(userLikePostRequest); err != nil {
response.Ctx(c).Error(err).Status(http.StatusBadRequest).Send()
return
}
externalUser, err := uc.userService.GetOrCreateExternalUser(c, userLikePostRequest.ExternalUserId, app)
if err != nil {
response.Ctx(c).Error(err).Status(http.StatusInternalServerError).Send()
return
}
// posts
postEntity, err := uc.postService.GetPostById(c, userLikePostRequest.PostId)
if err != nil {
response.Ctx(c).Error(err).Status(http.StatusInternalServerError).Send()
return
}
if postEntity.ApplicationId != app.Id {
response.Ctx(c).Status(http.StatusNotFound).Send()
return
}
// 检测是否有
var cacheKey = uc.redis.Prefix(TaskProcessing + ":" + userLikePostRequest.PostId.String())
// if exists
exists, err := uc.redis.Client.Exists(c, cacheKey).Result()
if err != nil {
response.Ctx(c).Error(err).Status(http.StatusInternalServerError).Send()
return
}
if exists > 0 {
response.Ctx(c).Status(http.StatusTooEarly).Error(consts.ErrAnotherOperationInProgress).Send()
return
}
_, err = uc.redis.Client.Set(c, cacheKey, userLikePostRequest.ExternalUserId, LockTTL).Result()
go func(prefix string) {
err = uc.userService.LikePost(c, externalUser, app, postEntity)
if err != nil {
response.Ctx(c).Error(err).Status(http.StatusInternalServerError).Send()
return
}
_, err := uc.redis.Client.Del(c, prefix).Result()
if err != nil {
uc.logger.Sugar.Error(err)
}
}(cacheKey)
response.Ctx(c).Status(http.StatusNoContent).Send()
return
}
// Dislike godoc
// @Summary Dislike
// @Description 从用户的标签喜好中移除内容
// @Tags application_api
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Param UserLikePost body request.UserLikePost true "UserLikePost"
// @Success 200 {object} response.ResponseBody{data=entity.Category}
// @Failure 400 {object} response.ResponseBody
// @Router /applications/v1/users/_dislike [post]
func (uc *UserController) Dislike(c *gin.Context) {
app, err := uc.authService.GetApplication(c)
if err != nil {
response.Ctx(c).Error(err).Status(http.StatusBadRequest).Send()
return
}
var userDislikePostRequest = &request.UserDislikePost{}
if err := c.ShouldBindJSON(userDislikePostRequest); err != nil {
response.Ctx(c).Error(err).Status(http.StatusBadRequest).Send()
return
}
externalUser, err := uc.userService.GetOrCreateExternalUser(c, userDislikePostRequest.ExternalUserId, app)
if err != nil {
response.Ctx(c).Error(err).Status(http.StatusInternalServerError).Send()
return
}
// posts
postEntity, err := uc.postService.GetPostById(c, userDislikePostRequest.PostId)
if err != nil {
response.Ctx(c).Error(err).Status(http.StatusInternalServerError).Send()
return
}
if postEntity.ApplicationId != app.Id {
response.Ctx(c).Status(http.StatusNotFound).Send()
return
}
// 检测是否有
var cacheKey = uc.redis.Prefix(TaskProcessing + ":" + userDislikePostRequest.PostId.String())
exists, err := uc.redis.Client.Exists(c, cacheKey).Result()
if err != nil {
response.Ctx(c).Error(err).Status(http.StatusInternalServerError).Send()
return
}
if exists > 0 {
response.Ctx(c).Status(http.StatusTooEarly).Error(consts.ErrAnotherOperationInProgress).Send()
return
}
_, err = uc.redis.Client.Set(c, cacheKey, userDislikePostRequest.ExternalUserId, LockTTL).Result()
go func(prefix string) {
err = uc.userService.DislikePost(c, externalUser, app, postEntity)
if err != nil {
response.Ctx(c).Error(err).Status(http.StatusInternalServerError).Send()
return
}
_, err := uc.redis.Client.Del(c, prefix).Result()
if err != nil {
uc.logger.Sugar.Error(err)
}
}(cacheKey)
response.Ctx(c).Status(http.StatusNoContent).Send()
return
}

View File

@ -17,6 +17,7 @@ var ProviderSet = wire.NewSet(
application_v1.NewApplicationController,
application_v1.NewPostController,
application_v1.NewCategoryController,
application_v1.NewUserController,
NewHandler,
)
@ -46,6 +47,7 @@ type Handlers struct {
ApplicationApi *application_v1.ApplicationController
ApplicationPostApi *application_v1.PostController
ApplicationCategoryApi *application_v1.CategoryController
ApplicationUserApi *application_v1.UserController
}
func NewHandler(
@ -53,11 +55,14 @@ func NewHandler(
applicationApiController *application_v1.ApplicationController,
applicationPostApi *application_v1.PostController,
applicationCategoryApi *application_v1.CategoryController,
applicationUserApi *application_v1.UserController,
) *Handlers {
return &Handlers{
ApplicationController: applicationController,
ApplicationApi: applicationApiController,
ApplicationPostApi: applicationPostApi,
ApplicationCategoryApi: applicationCategoryApi,
ApplicationUserApi: applicationUserApi,
}
}

View File

@ -9,3 +9,13 @@ type ApplicationId struct {
type ApplicationCreateRequest struct {
Name string `json:"name"`
}
type UserLikePost struct {
PostId schema.EntityId `json:"post_id" uri:"post_id"`
ExternalUserId string `json:"external_user_id"`
}
type UserDislikePost struct {
PostId schema.EntityId `json:"post_id" uri:"post_id"`
ExternalUserId string `json:"external_user_id"`
}

View File

@ -23,6 +23,22 @@ CREATE TABLE `application_tokens`
foreign key (application_id) references applications (id) on delete cascade
);
CREATE TABLE `external_users`
(
id serial NOT NULL,
name varchar(255) DEFAULT NULL,
email varchar(255) DEFAULT NULL,
external_id varchar(255) NOT NULL,
summary varchar(255) DEFAULT NULL,
application_id bigint unsigned NOT NULL,
created_at timestamp DEFAULT CURRENT_TIMESTAMP,
updated_at timestamp DEFAULT CURRENT_TIMESTAMP,
primary key (id),
index (external_id),
foreign key (application_id) references applications (id) on delete cascade
);
CREATE TABLE `categories`
(
id serial NOT NULL,
@ -88,27 +104,29 @@ CREATE TABLE `post_tags`
-- user tag scores
CREATE TABLE `user_tag_scores`
(
user_id varchar(255) NOT NULL,
tag_id bigint unsigned NOT NULL,
score int NOT NULL,
application_id bigint unsigned NOT NULL,
primary key (user_id, tag_id),
external_user_id bigint unsigned NOT NULL,
tag_id bigint unsigned NOT NULL,
score int NOT NULL,
application_id bigint unsigned NOT NULL,
primary key (external_user_id, tag_id),
index (score, application_id),
foreign key (tag_id) references tags (id) on delete cascade,
foreign key (application_id) references applications (id) on delete cascade
foreign key (application_id) references applications (id) on delete cascade,
foreign key (external_user_id) references external_users (id) on delete cascade
);
-- user likes
CREATE TABLE `user_likes`
(
user_id varchar(255) NOT NULL,
post_id bigint unsigned NOT NULL,
type enum ('like', 'dislike') NOT NULL,
application_id bigint unsigned NOT NULL,
primary key (user_id, post_id),
external_user_id bigint unsigned NOT NULL,
post_id bigint unsigned NOT NULL,
type enum ('like', 'dislike') NOT NULL,
application_id bigint unsigned NOT NULL,
primary key (external_user_id, post_id),
index (type, application_id),
foreign key (post_id) references posts (id) on delete cascade,
foreign key (application_id) references applications (id) on delete cascade
foreign key (application_id) references applications (id) on delete cascade,
foreign key (external_user_id) references external_users (id) on delete cascade
);
@ -122,3 +140,4 @@ DROP TABLE IF EXISTS `tags`;
DROP TABLE IF EXISTS `categories`;
DROP TABLE IF EXISTS `application_tokens`;
DROP TABLE IF EXISTS `applications`;
DROP TABLE IF EXISTS `external_users`;

View File

@ -45,4 +45,7 @@ func (a *Api) InitApplicationApi(r *gin.RouterGroup) {
r.POST("/categories", a.h.ApplicationCategoryApi.Save)
r.GET("/categories/:category_id", a.h.ApplicationCategoryApi.Get)
r.DELETE("/categories/:category_id", a.h.ApplicationCategoryApi.Delete)
r.POST("/users/_like", a.h.ApplicationUserApi.Like)
r.POST("/users/_dislike", a.h.ApplicationUserApi.Dislike)
}

View File

@ -30,6 +30,7 @@ type User struct {
}
type UserId string
type ExternalUserId uint
func (u UserId) String() string {
return string(u)

View File

@ -51,3 +51,20 @@ func (s *Service) DeletePost(c context.Context, post *entity.Post) error {
func (s *Service) GetPostById(c context.Context, postId schema.EntityId) (*entity.Post, error) {
return s.dao.WithContext(c).Post.Preload(s.dao.Post.Application).Where(s.dao.Post.Id.Eq(postId.Uint())).First()
}
func (s *Service) GetPostTags(c context.Context, postEntity *entity.Post) ([]*entity.Tag, error) {
postTags, err := s.dao.WithContext(c).PostTag.Preload(s.dao.PostTag.Tag).
Where(s.dao.PostTag.PostId.Eq(postEntity.Id.Uint())).Find()
if err != nil {
return nil, err
}
var tags []*entity.Tag
for _, postTag := range postTags {
tags = append(tags, postTag.Tag)
}
return tags, err
}

View File

@ -28,7 +28,6 @@ func (s *Service) GetTag(c context.Context, name string, applicationEntity *enti
FirstOrCreate()
}
// Has Bind
func (s *Service) HasBindTag(c context.Context, post *entity.Post, tagName string) (bool, error) {
tag, err := s.GetTag(c, tagName, post.Application)
if err != nil {
@ -46,12 +45,15 @@ func (s *Service) HasBindTag(c context.Context, post *entity.Post, tagName strin
func (s *Service) BindTag(c context.Context, post *entity.Post, tagName string) error {
tag, err := s.GetTag(c, tagName, post.Application)
if err != nil {
return err
}
bind, err := s.HasBindTag(c, post, tag.Name)
if err != nil {
return err
}
if !bind {
err = s.dao.WithContext(c).PostTag.Create(&entity.PostTag{
PostId: &post.Id,
@ -59,7 +61,7 @@ func (s *Service) BindTag(c context.Context, post *entity.Post, tagName string)
})
}
return err
return nil
}
func (s *Service) MarkAsProcessed(c context.Context, post *entity.Post) error {

View File

@ -8,6 +8,7 @@ import (
"leafdev.top/Ecosystem/recommender/internal/service/jwks"
"leafdev.top/Ecosystem/recommender/internal/service/post"
"leafdev.top/Ecosystem/recommender/internal/service/stream"
"leafdev.top/Ecosystem/recommender/internal/service/user"
"github.com/google/wire"
)
@ -20,6 +21,7 @@ type Service struct {
Application *application.Service
Post *post.Service
Category *category.Service
User *user.Service
}
var Provider = wire.NewSet(
@ -29,6 +31,7 @@ var Provider = wire.NewSet(
application.NewService,
post.NewService,
category.NewService,
user.NewService,
NewService,
)
@ -40,6 +43,7 @@ func NewService(
application *application.Service,
post *post.Service,
category *category.Service,
user *user.Service,
) *Service {
return &Service{
logger,
@ -49,5 +53,6 @@ func NewService(
application,
post,
category,
user,
}
}

View File

@ -0,0 +1,35 @@
package user
import (
"context"
"leafdev.top/Ecosystem/recommender/internal/entity"
)
func (s *Service) GetOrCreateExternalUser(c context.Context, externalUserId string, applicationEntity *entity.Application) (*entity.ExternalUser, error) {
//Where(s.dao.UserTagScore.ExternalUserId.Eq(externalUserEntity.Id.Uint())).
count, err := s.dao.WithContext(c).ExternalUser.
Where(s.dao.ExternalUser.ApplicationId.Eq(applicationEntity.Id.Uint())).
Where(s.dao.ExternalUser.ExternalId.Eq(externalUserId)).Count()
if err != nil {
return nil, err
}
if count > 0 {
eu, err := s.dao.WithContext(c).ExternalUser.
Where(s.dao.ExternalUser.ApplicationId.Eq(applicationEntity.Id.Uint())).
Where(s.dao.ExternalUser.ExternalId.Eq(externalUserId)).First()
if err != nil {
return nil, err
}
return eu, nil
}
eu := &entity.ExternalUser{
ApplicationId: applicationEntity.Id,
ExternalId: externalUserId,
}
err = s.dao.WithContext(c).ExternalUser.Create(eu)
return eu, err
}

View File

@ -0,0 +1,148 @@
package user
import (
"context"
"leafdev.top/Ecosystem/recommender/internal/entity"
"leafdev.top/Ecosystem/recommender/internal/schema"
"time"
)
const TaskProcessing = "user_likes"
var LockTTL = time.Minute * 10
func (s *Service) HasLiked(c context.Context, externalUserEntity *entity.ExternalUser, applicationEntity *entity.Application, postEntity *entity.Post) (bool, error) {
count, err := s.dao.WithContext(c).UserLike.Where(s.dao.UserLike.ExternalUserId.Eq(externalUserEntity.Id.Uint())).
Where(s.dao.UserLike.PostId.Eq(postEntity.Id.Uint())).
Where(s.dao.UserLike.ApplicationId.Eq(applicationEntity.Id.Uint())).Count()
if err != nil {
return false, err
}
return count > 0, nil
}
func (s *Service) UpdateLike(c context.Context, externalUserEntity *entity.ExternalUser, applicationEntity *entity.Application, postEntity *entity.Post, likeType schema.UserLikeType) error {
count, err := s.dao.WithContext(c).UserLike.Where(s.dao.UserLike.ExternalUserId.Eq(externalUserEntity.Id.Uint())).
Where(s.dao.UserLike.PostId.Eq(postEntity.Id.Uint())).
Where(s.dao.UserLike.ApplicationId.Eq(applicationEntity.Id.Uint())).Count()
if err != nil {
return err
}
if count > 0 {
// update
_, err = s.dao.WithContext(c).UserLike.Where(s.dao.UserLike.ExternalUserId.Eq(externalUserEntity.Id.Uint())).
Where(s.dao.UserLike.PostId.Eq(postEntity.Id.Uint())).
Where(s.dao.UserLike.ApplicationId.Eq(applicationEntity.Id.Uint())).
Update(s.dao.UserLike.Type, likeType)
return err
}
var userLike = &entity.UserLike{
ApplicationId: applicationEntity.Id,
ExternalUserId: externalUserEntity.Id,
PostId: postEntity.Id,
Type: likeType,
}
err = s.dao.WithContext(c).UserLike.Create(userLike)
return err
}
func (s *Service) LikePost(c context.Context, externalUserEntity *entity.ExternalUser, applicationEntity *entity.Application, postEntity *entity.Post) error {
//// 检测是否有
//var cacheKey = s.redis.Prefix(TaskProcessing)
//lock, err := s.redis.Locker.Obtain(c, cacheKey, LockTTL, nil)
//if err != nil {
// return err
//}
//defer func(lock *redislock.Lock, ctx context.Context) {
// err := lock.Release(ctx)
// if err != nil {
// s.logger.Sugar.Error(err)
// }
//}(lock, c)
// get tags
postTags, err := s.postService.GetPostTags(c, postEntity)
if err != nil {
return err
}
hasLike, err := s.HasLiked(c, externalUserEntity, applicationEntity, postEntity)
if err != nil {
return err
}
if hasLike {
return nil
}
err = s.UpdateLike(c, externalUserEntity, applicationEntity, postEntity, schema.UserLikeTypeLike)
if err != nil {
return err
}
err = s.BindTags(context.Background(), externalUserEntity, applicationEntity, postTags)
if err != nil {
s.logger.Sugar.Error(err)
}
return nil
}
func (s *Service) DislikePost(c context.Context, externalUserEntity *entity.ExternalUser, applicationEntity *entity.Application, postEntity *entity.Post) error {
// 检测是否有
//var cacheKey = s.redis.Prefix(TaskProcessing)
//lock, err := s.redis.Locker.Obtain(c, cacheKey, LockTTL, nil)
//if err != nil {
// return err
//}
//
//defer func(lock *redislock.Lock, ctx context.Context) {
// err := lock.Release(ctx)
// if err != nil {
// s.logger.Sugar.Error(err)
// }
//}(lock, c)
// get tags
postTags, err := s.postService.GetPostTags(c, postEntity)
if err != nil {
return err
}
hasLike, err := s.HasLiked(c, externalUserEntity, applicationEntity, postEntity)
if err != nil {
return err
}
if !hasLike {
return nil
}
err = s.UpdateLike(c, externalUserEntity, applicationEntity, postEntity, schema.UserLikeTypeDislike)
if err != nil {
return err
}
_, err = s.dao.WithContext(c).UserLike.Where(s.dao.UserLike.ExternalUserId.Eq(externalUserEntity.Id.Uint())).
Where(s.dao.UserLike.PostId.Eq(postEntity.Id.Uint())).
Where(s.dao.UserLike.ApplicationId.Eq(applicationEntity.Id.Uint())).Count()
if err != nil {
return err
}
err = s.RemoveTags(context.Background(), externalUserEntity, applicationEntity, postTags)
if err != nil {
s.logger.Sugar.Error(err)
}
return nil
}

View File

@ -0,0 +1,25 @@
package user
import (
"leafdev.top/Ecosystem/recommender/internal/base/logger"
"leafdev.top/Ecosystem/recommender/internal/dao"
"leafdev.top/Ecosystem/recommender/internal/service/post"
)
type Service struct {
dao *dao.Query
postService *post.Service
logger *logger.Logger
}
func NewService(
dao *dao.Query,
postService *post.Service,
logger *logger.Logger,
) *Service {
return &Service{
dao: dao,
postService: postService,
logger: logger,
}
}

View File

@ -0,0 +1,114 @@
package user
import (
"context"
"leafdev.top/Ecosystem/recommender/internal/entity"
)
func (s *Service) HasBindTag(c context.Context, externalUserEntity *entity.ExternalUser, applicationEntity *entity.Application, tagEntity *entity.Tag) (bool, error) {
count, err := s.dao.WithContext(c).UserTagScore.
Where(s.dao.UserTagScore.ExternalUserId.Eq(externalUserEntity.Id.Uint())).
Where(s.dao.UserTagScore.TagId.Eq(tagEntity.Id.Uint())).
Where(s.dao.UserTagScore.ApplicationId.Eq(applicationEntity.Id.Uint())).Count()
if err != nil {
return false, err
}
return count > 0, nil
}
func (s *Service) BindTags(c context.Context, externalUserEntity *entity.ExternalUser, applicationEntity *entity.Application, tags []*entity.Tag) error {
// 检测用户是否绑定 Tag如果绑定则更新否则创建
for _, tag := range tags {
var uts *entity.UserTagScore
hasBind, err := s.HasBindTag(c, externalUserEntity, applicationEntity, tag)
if err != nil {
return err
}
if hasBind {
// score + 1
uts, err = s.dao.WithContext(c).UserTagScore.
Where(s.dao.UserTagScore.ExternalUserId.Eq(externalUserEntity.Id.Uint())).
Where(s.dao.UserTagScore.TagId.Eq(tag.Id.Uint())).
Where(s.dao.UserTagScore.ApplicationId.Eq(applicationEntity.Id.Uint())).First()
if err != nil {
return err
}
uts.Score += 1
err = s.dao.WithContext(c).UserTagScore.Save(uts)
if err != nil {
return err
}
continue
}
uts = &entity.UserTagScore{
ApplicationId: applicationEntity.Id,
TagId: tag.Id,
ExternalUserId: externalUserEntity.Id,
Score: 1,
}
err = s.dao.WithContext(c).UserTagScore.Create(uts)
if err != nil {
return err
}
}
return nil
}
func (s *Service) RemoveTags(c context.Context, externalUserEntity *entity.ExternalUser, applicationEntity *entity.Application, tags []*entity.Tag) error {
for _, tag := range tags {
var uts *entity.UserTagScore
hasBind, err := s.HasBindTag(c, externalUserEntity, applicationEntity, tag)
if err != nil {
return err
}
if hasBind {
// score - 1
uts, err = s.dao.WithContext(c).UserTagScore.
Where(s.dao.UserTagScore.ExternalUserId.Eq(externalUserEntity.Id.Uint())).
Where(s.dao.UserTagScore.TagId.Eq(tag.Id.Uint())).
Where(s.dao.UserTagScore.ApplicationId.Eq(applicationEntity.Id.Uint())).First()
if err != nil {
return err
}
//if uts.Score-1 < 0 {
// continue
//}
uts.Score -= 1
_, err = s.dao.WithContext(c).UserTagScore.
Where(s.dao.UserTagScore.ExternalUserId.Eq(externalUserEntity.Id.Uint())).
Where(s.dao.UserTagScore.TagId.Eq(tag.Id.Uint())).
Where(s.dao.UserTagScore.ApplicationId.Eq(applicationEntity.Id.Uint())).Update(s.dao.UserTagScore.Score, uts.Score)
if err != nil {
return err
}
continue
} else {
uts = &entity.UserTagScore{
ApplicationId: applicationEntity.Id,
TagId: tag.Id,
ExternalUserId: externalUserEntity.Id,
Score: -1,
}
err = s.dao.WithContext(c).UserTagScore.Create(uts)
if err != nil {
return err
}
}
}
return nil
}

7
pkg/consts/posts.go Normal file
View File

@ -0,0 +1,7 @@
package consts
import "errors"
var (
ErrAnotherOperationInProgress = errors.New("another operation in progress")
)