add visit user commment/media tweets support

pull/354/head
Michael Li 1 year ago
parent a45981fa28
commit f656b027a3
No known key found for this signature in database

@ -7,6 +7,38 @@ All notable changes to paopao-ce are documented in this file.
- use compiler profile-guided optimization (PGO) to further optimize builds. [#327](https://github.com/rocboss/paopao-ce/pull/327) - use compiler profile-guided optimization (PGO) to further optimize builds. [#327](https://github.com/rocboss/paopao-ce/pull/327)
- frontend: re-add stars page embed to profile page. [#339](https://github.com/rocboss/paopao-ce/pull/339) - frontend: re-add stars page embed to profile page. [#339](https://github.com/rocboss/paopao-ce/pull/339)
- simple support for user posts filter by style(post/comment/media/star). [#345](https://github.com/rocboss/paopao-ce/pull/345) - simple support for user posts filter by style(post/comment/media/star). [#345](https://github.com/rocboss/paopao-ce/pull/345)
mirgration database first(sql ddl file in `scripts/migration/**/*_create_view_post_filter.up.sql`):
```sql
CREATE VIEW p_post_by_media AS
SELECT post.*
FROM
( SELECT DISTINCT post_id FROM p_post_content WHERE ( TYPE = 3 OR TYPE = 4 OR TYPE = 7 OR TYPE = 8 ) AND is_del = 0 ) media
JOIN p_post post ON media.post_id = post.ID
WHERE
post.is_del = 0;
CREATE VIEW p_post_by_comment AS
SELECT P.*
FROM
(
SELECT
post_id
FROM
p_comment
WHERE
is_del = 0 UNION
SELECT
post_id
FROM
p_comment_reply reply
JOIN p_comment COMMENT ON reply.comment_id = COMMENT.ID
WHERE
reply.is_del = 0 AND COMMENT.is_del = 0
)
C JOIN p_post P ON C.post_id = P.ID
WHERE
P.is_del = 0;
```
- add user highlight tweet support include custom tweet set to highlight and list in user/profile page. - add user highlight tweet support include custom tweet set to highlight and list in user/profile page.
### Changed ### Changed

@ -102,7 +102,7 @@ services:
- paopao-network - paopao-network
backend: backend:
image: bitbus/paopao-ce:0.3 image: bitbus/paopao-ce:0.4
restart: always restart: always
depends_on: depends_on:
- db - db

@ -28,7 +28,7 @@ require (
github.com/minio/minio-go/v7 v7.0.61 github.com/minio/minio-go/v7 v7.0.61
github.com/onsi/ginkgo/v2 v2.11.0 github.com/onsi/ginkgo/v2 v2.11.0
github.com/onsi/gomega v1.27.10 github.com/onsi/gomega v1.27.10
github.com/pyroscope-io/client v0.7.1 github.com/pyroscope-io/client v0.7.2-0.20230804044655-36760e422a95
github.com/redis/rueidis v1.0.14 github.com/redis/rueidis v1.0.14
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
github.com/smartwalle/alipay/v3 v3.2.15 github.com/smartwalle/alipay/v3 v3.2.15
@ -102,7 +102,7 @@ require (
github.com/mozillazg/go-httpheader v0.2.1 // indirect github.com/mozillazg/go-httpheader v0.2.1 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pyroscope-io/godeltaprof v0.1.0 // indirect github.com/pyroscope-io/godeltaprof v0.1.2 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/rs/xid v1.5.0 // indirect github.com/rs/xid v1.5.0 // indirect

@ -1105,10 +1105,10 @@ github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/pyroscope-io/client v0.7.1 h1:yFRhj3vbgjBxehvxQmedmUWJQ4CAfCHhn+itPsuWsHw= github.com/pyroscope-io/client v0.7.2-0.20230804044655-36760e422a95 h1:ZpE0l+tD3ykwYWQh/UVdIpxHmr9B6DTtOUBiGw3lB2E=
github.com/pyroscope-io/client v0.7.1/go.mod h1:4h21iOU4pUOq0prKyDlvYRL+SCKsBc5wKiEtV+rJGqU= github.com/pyroscope-io/client v0.7.2-0.20230804044655-36760e422a95/go.mod h1:FEocnjn+Ngzxy6EtU9ZxXWRvQ0+pffkrBxHLnPpxwi8=
github.com/pyroscope-io/godeltaprof v0.1.0 h1:UBqtjt0yZi4jTxqZmLAs34XG6ycS3vUTlhEUSq4NHLE= github.com/pyroscope-io/godeltaprof v0.1.2 h1:MdlEmYELd5w+lvIzmZvXGNMVzW2Qc9jDMuJaPOR75g4=
github.com/pyroscope-io/godeltaprof v0.1.0/go.mod h1:psMITXp90+8pFenXkKIpNhrfmI9saQnPbba27VIaiQE= github.com/pyroscope-io/godeltaprof v0.1.2/go.mod h1:psMITXp90+8pFenXkKIpNhrfmI9saQnPbba27VIaiQE=
github.com/redis/rueidis v1.0.14 h1:qdFZahk1F/2L+sZeOECx5E2N5J4Qc51b7ezSUpQXJfs= github.com/redis/rueidis v1.0.14 h1:qdFZahk1F/2L+sZeOECx5E2N5J4Qc51b7ezSUpQXJfs=
github.com/redis/rueidis v1.0.14/go.mod h1:8B+r5wdnjwK3lTFml5VtxjzGOQAC+5UmujoD12pDrEo= github.com/redis/rueidis v1.0.14/go.mod h1:8B+r5wdnjwK3lTFml5VtxjzGOQAC+5UmujoD12pDrEo=
github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=

@ -29,6 +29,8 @@ const (
TableContactGroup = "contact_group" TableContactGroup = "contact_group"
TableMessage = "message" TableMessage = "message"
TablePost = "post" TablePost = "post"
TablePostByComment = "post_by_comment"
TablePostByMedia = "post_by_media"
TablePostAttachmentBill = "post_attachment_bill" TablePostAttachmentBill = "post_attachment_bill"
TablePostCollection = "post_collection" TablePostCollection = "post_collection"
TablePostContent = "post_content" TablePostContent = "post_content"

@ -309,6 +309,8 @@ func (s *databaseConf) TableNames() (res TableNameMap) {
TableContactGroup, TableContactGroup,
TableMessage, TableMessage,
TablePost, TablePost,
TablePostByComment,
TablePostByMedia,
TablePostAttachmentBill, TablePostAttachmentBill,
TablePostCollection, TablePostCollection,
TablePostContent, TablePostContent,

@ -24,6 +24,8 @@ type TweetService interface {
GetPostContentsByIDs(ids []int64) ([]*ms.PostContent, error) GetPostContentsByIDs(ids []int64) ([]*ms.PostContent, error)
GetPostContentByID(id int64) (*ms.PostContent, error) GetPostContentByID(id int64) (*ms.PostContent, error)
ListUserStarTweets(user *cs.VistUser, limit int, offset int) ([]*ms.PostStar, int64, error) ListUserStarTweets(user *cs.VistUser, limit int, offset int) ([]*ms.PostStar, int64, error)
ListUserMediaTweets(user *cs.VistUser, limit int, offset int) ([]*ms.Post, int64, error)
ListUserCommentTweets(user *cs.VistUser, limit int, offset int) ([]*ms.Post, int64, error)
} }
// TweetManageService 推文管理服务,包括创建/删除/更新推文 // TweetManageService 推文管理服务,包括创建/删除/更新推文

@ -21,6 +21,10 @@ const (
PostVisitInvalid PostVisitInvalid
) )
type PostByMedia = Post
type PostByComment = Post
type Post struct { type Post struct {
*Model *Model
UserID int64 `json:"user_id"` UserID int64 `json:"user_id"`

@ -21,6 +21,8 @@ var (
_contactGroup_ string _contactGroup_ string
_message_ string _message_ string
_post_ string _post_ string
_post_by_comment_ string
_post_by_media_ string
_postAttachmentBill_ string _postAttachmentBill_ string
_postCollection_ string _postCollection_ string
_postContent_ string _postContent_ string
@ -44,6 +46,8 @@ func initTableName() {
_contactGroup_ = m[conf.TableContactGroup] _contactGroup_ = m[conf.TableContactGroup]
_message_ = m[conf.TableMessage] _message_ = m[conf.TableMessage]
_post_ = m[conf.TablePost] _post_ = m[conf.TablePost]
_post_by_comment_ = m[conf.TablePostByComment]
_post_by_media_ = m[conf.TablePostByMedia]
_postAttachmentBill_ = m[conf.TablePostAttachmentBill] _postAttachmentBill_ = m[conf.TablePostAttachmentBill]
_postCollection_ = m[conf.TablePostCollection] _postCollection_ = m[conf.TablePostCollection]
_postContent_ = m[conf.TablePostContent] _postContent_ = m[conf.TablePostContent]

@ -426,6 +426,39 @@ func (s *tweetSrv) ListUserStarTweets(user *cs.VistUser, limit int, offset int)
return return
} }
func (s *tweetSrv) getUserTweets(db *gorm.DB, user *cs.VistUser, limit int, offset int) (res []*ms.Post, total int64, err error) {
visibilities := []core.PostVisibleT{core.PostVisitPublic}
switch user.RelTyp {
case cs.RelationAdmin, cs.RelationSelf:
visibilities = append(visibilities, core.PostVisitPrivate, core.PostVisitFriend)
case cs.RelationFriend:
visibilities = append(visibilities, core.PostVisitFriend)
case cs.RelationGuest:
fallthrough
default:
// nothing
}
db = db.Where("user_id=? AND visibility IN ? AND is_del=0", user.UserId, visibilities).Order("latest_replied_on DESC")
if offset >= 0 && limit > 0 {
db = db.Offset(offset).Limit(limit)
}
if err = db.Find(&res).Error; err != nil {
return
}
err = db.Count(&total).Error
return
}
func (s *tweetSrv) ListUserMediaTweets(user *cs.VistUser, limit int, offset int) ([]*ms.Post, int64, error) {
db := s.db.Table(_post_by_media_)
return s.getUserTweets(db, user, limit, offset)
}
func (s *tweetSrv) ListUserCommentTweets(user *cs.VistUser, limit int, offset int) ([]*ms.Post, int64, error) {
db := s.db.Table(_post_by_comment_)
return s.getUserTweets(db, user, limit, offset)
}
func (s *tweetSrv) GetUserPostStarCount(userID int64) (int64, error) { func (s *tweetSrv) GetUserPostStarCount(userID int64) (int64, error) {
star := &dbr.PostStar{ star := &dbr.PostStar{
UserID: userID, UserID: userID,

@ -66,14 +66,11 @@ func (s *looseSrv) GetUserTweets(req *web.GetUserTweetsReq) (res *web.GetUserTwe
if xerr != nil { if xerr != nil {
return nil, err return nil, err
} }
switch req.Style { switch req.Style {
case web.UserPostsStyleComment: case web.UserPostsStyleComment, web.UserPostsStyleMedia:
res, err = s.getUserCommentTweets(req, user) res, err = s.listUserTweets(req, user)
case web.UserPostsStyleHighlight: case web.UserPostsStyleHighlight:
res, err = s.getUserPostTweets(req, user, true) res, err = s.getUserPostTweets(req, user, true)
case web.UserPostsStyleMedia:
res, err = s.getUserMediaTweets(req, user)
case web.UserPostsStyleStar: case web.UserPostsStyleStar:
res, err = s.getUserStarTweets(req, user) res, err = s.getUserStarTweets(req, user)
case web.UserPostsStylePost: case web.UserPostsStylePost:
@ -84,30 +81,6 @@ func (s *looseSrv) GetUserTweets(req *web.GetUserTweetsReq) (res *web.GetUserTwe
return return
} }
func (s *looseSrv) getUserCommentTweets(req *web.GetUserTweetsReq, user *cs.VistUser) (*web.GetUserTweetsResp, mir.Error) {
// TODO: add implement logic
resp := base.PageRespFrom(nil, req.Page, req.PageSize, 0)
return (*web.GetUserTweetsResp)(resp), nil
}
func (s *looseSrv) getUserHighlightTweets(req *web.GetUserTweetsReq, user *cs.VistUser) (*web.GetUserTweetsResp, mir.Error) {
// TODO: add implement logic
resp := base.PageRespFrom(nil, req.Page, req.PageSize, 0)
return (*web.GetUserTweetsResp)(resp), nil
}
func (s *looseSrv) getUserMediaTweets(req *web.GetUserTweetsReq, user *cs.VistUser) (*web.GetUserTweetsResp, mir.Error) {
// TODO: add implement logic
resp := base.PageRespFrom(nil, req.Page, req.PageSize, 0)
return (*web.GetUserTweetsResp)(resp), nil
}
func (s *looseSrv) getSelfMediaTweets(req *web.GetUserTweetsReq) (*web.GetUserTweetsResp, mir.Error) {
// TODO: add implement logic
resp := base.PageRespFrom(nil, req.Page, req.PageSize, 0)
return (*web.GetUserTweetsResp)(resp), nil
}
func (s *looseSrv) getUserStarTweets(req *web.GetUserTweetsReq, user *cs.VistUser) (*web.GetUserTweetsResp, mir.Error) { func (s *looseSrv) getUserStarTweets(req *web.GetUserTweetsReq, user *cs.VistUser) (*web.GetUserTweetsResp, mir.Error) {
stars, totalRows, err := s.Ds.ListUserStarTweets(user, req.PageSize, (req.Page-1)*req.PageSize) stars, totalRows, err := s.Ds.ListUserStarTweets(user, req.PageSize, (req.Page-1)*req.PageSize)
if err != nil { if err != nil {
@ -129,6 +102,33 @@ func (s *looseSrv) getUserStarTweets(req *web.GetUserTweetsReq, user *cs.VistUse
return (*web.GetUserTweetsResp)(resp), nil return (*web.GetUserTweetsResp)(resp), nil
} }
func (s *looseSrv) listUserTweets(req *web.GetUserTweetsReq, user *cs.VistUser) (*web.GetUserTweetsResp, mir.Error) {
var (
tweets []*ms.Post
total int64
err error
)
if req.Style == web.UserPostsStyleComment {
tweets, total, err = s.Ds.ListUserCommentTweets(user, req.PageSize, (req.Page-1)*req.PageSize)
} else if req.Style == web.UserPostsStyleMedia {
tweets, total, err = s.Ds.ListUserMediaTweets(user, req.PageSize, (req.Page-1)*req.PageSize)
} else {
logrus.Errorf("s.listUserTweets unknow style: %s", req.Style)
return nil, web.ErrGetPostsFailed
}
if err != nil {
logrus.Errorf("s.listUserTweets err: %s", err)
return nil, web.ErrGetPostsFailed
}
postFormated, err := s.Ds.MergePosts(tweets)
if err != nil {
logrus.Errorf("s.listUserTweets err: %s", err)
return nil, web.ErrGetPostsFailed
}
resp := base.PageRespFrom(postFormated, req.Page, req.PageSize, total)
return (*web.GetUserTweetsResp)(resp), nil
}
func (s *looseSrv) getUserPostTweets(req *web.GetUserTweetsReq, user *cs.VistUser, isHighlight bool) (*web.GetUserTweetsResp, mir.Error) { func (s *looseSrv) getUserPostTweets(req *web.GetUserTweetsReq, user *cs.VistUser, isHighlight bool) (*web.GetUserTweetsResp, mir.Error) {
visibilities := []core.PostVisibleT{core.PostVisitPublic} visibilities := []core.PostVisibleT{core.PostVisitPublic}
switch user.RelTyp { switch user.RelTyp {

@ -0,0 +1,5 @@
DROP VIEW IF EXISTS p_post_by_media;
DROP VIEW IF EXISTS p_post_by_comment;

@ -0,0 +1,30 @@
CREATE VIEW p_post_by_media AS
SELECT post.*
FROM
( SELECT DISTINCT post_id FROM p_post_content WHERE ( TYPE = 3 OR TYPE = 4 OR TYPE = 7 OR TYPE = 8 ) AND is_del = 0 ) media
JOIN p_post post ON media.post_id = post.ID
WHERE
post.is_del = 0;
CREATE VIEW p_post_by_comment AS
SELECT P.*
FROM
(
SELECT
post_id
FROM
p_comment
WHERE
is_del = 0 UNION
SELECT
post_id
FROM
p_comment_reply reply
JOIN p_comment COMMENT ON reply.comment_id = COMMENT.ID
WHERE
reply.is_del = 0
AND COMMENT.is_del = 0
)
C JOIN p_post P ON C.post_id = P.ID
WHERE
P.is_del = 0;

@ -0,0 +1,5 @@
DROP VIEW IF EXISTS p_post_by_media;
DROP VIEW IF EXISTS p_post_by_comment;

@ -0,0 +1,30 @@
CREATE VIEW p_post_by_media AS
SELECT post.*
FROM
( SELECT DISTINCT post_id FROM p_post_content WHERE ( TYPE = 3 OR TYPE = 4 OR TYPE = 7 OR TYPE = 8 ) AND is_del = 0 ) media
JOIN p_post post ON media.post_id = post.ID
WHERE
post.is_del = 0;
CREATE VIEW p_post_by_comment AS
SELECT P.*
FROM
(
SELECT
post_id
FROM
p_comment
WHERE
is_del = 0 UNION
SELECT
post_id
FROM
p_comment_reply reply
JOIN p_comment COMMENT ON reply.comment_id = COMMENT.ID
WHERE
reply.is_del = 0
AND COMMENT.is_del = 0
)
C JOIN p_post P ON C.post_id = P.ID
WHERE
P.is_del = 0;

@ -0,0 +1,5 @@
DROP VIEW IF EXISTS p_post_by_media;
DROP VIEW IF EXISTS p_post_by_comment;

@ -0,0 +1,30 @@
CREATE VIEW p_post_by_media AS
SELECT post.*
FROM
( SELECT DISTINCT post_id FROM p_post_content WHERE ( TYPE = 3 OR TYPE = 4 OR TYPE = 7 OR TYPE = 8 ) AND is_del = 0 ) media
JOIN p_post post ON media.post_id = post.ID
WHERE
post.is_del = 0;
CREATE VIEW p_post_by_comment AS
SELECT P.*
FROM
(
SELECT
post_id
FROM
p_comment
WHERE
is_del = 0 UNION
SELECT
post_id
FROM
p_comment_reply reply
JOIN p_comment COMMENT ON reply.comment_id = COMMENT.ID
WHERE
reply.is_del = 0
AND COMMENT.is_del = 0
)
C JOIN p_post P ON C.post_id = P.ID
WHERE
P.is_del = 0;

@ -395,4 +395,37 @@ CREATE TABLE `p_wallet_statement` (
KEY `idx_wallet_statement_user_id` (`user_id`) USING BTREE KEY `idx_wallet_statement_user_id` (`user_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=10010 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='钱包流水'; ) ENGINE=InnoDB AUTO_INCREMENT=10010 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='钱包流水';
DROP VIEW IF EXISTS p_post_by_media;
CREATE VIEW p_post_by_media AS
SELECT post.*
FROM
( SELECT DISTINCT post_id FROM p_post_content WHERE ( TYPE = 3 OR TYPE = 4 OR TYPE = 7 OR TYPE = 8 ) AND is_del = 0 ) media
JOIN p_post post ON media.post_id = post.ID
WHERE
post.is_del = 0;
DROP VIEW IF EXISTS p_post_by_comment;
CREATE VIEW p_post_by_comment AS
SELECT P.*
FROM
(
SELECT
post_id
FROM
p_comment
WHERE
is_del = 0 UNION
SELECT
post_id
FROM
p_comment_reply reply
JOIN p_comment COMMENT ON reply.comment_id = COMMENT.ID
WHERE
reply.is_del = 0
AND COMMENT.is_del = 0
)
C JOIN p_post P ON C.post_id = P.ID
WHERE
P.is_del = 0;
SET FOREIGN_KEY_CHECKS = 1; SET FOREIGN_KEY_CHECKS = 1;

@ -325,3 +325,36 @@ CREATE TABLE p_wallet_statement (
is_del SMALLINT NOT NULL DEFAULT 0 is_del SMALLINT NOT NULL DEFAULT 0
); );
CREATE INDEX idx_wallet_statement_user_id ON p_wallet_statement USING btree (user_id); CREATE INDEX idx_wallet_statement_user_id ON p_wallet_statement USING btree (user_id);
DROP VIEW IF EXISTS p_post_by_media;
CREATE VIEW p_post_by_media AS
SELECT post.*
FROM
( SELECT DISTINCT post_id FROM p_post_content WHERE ( TYPE = 3 OR TYPE = 4 OR TYPE = 7 OR TYPE = 8 ) AND is_del = 0 ) media
JOIN p_post post ON media.post_id = post.ID
WHERE
post.is_del = 0;
DROP VIEW IF EXISTS p_post_by_comment;
CREATE VIEW p_post_by_comment AS
SELECT P.*
FROM
(
SELECT
post_id
FROM
p_comment
WHERE
is_del = 0 UNION
SELECT
post_id
FROM
p_comment_reply reply
JOIN p_comment COMMENT ON reply.comment_id = COMMENT.ID
WHERE
reply.is_del = 0
AND COMMENT.is_del = 0
)
C JOIN p_post P ON C.post_id = P.ID
WHERE
P.is_del = 0;

@ -356,6 +356,39 @@ CREATE TABLE "p_wallet_statement" (
PRIMARY KEY ("id") PRIMARY KEY ("id")
); );
DROP VIEW IF EXISTS p_post_by_media;
CREATE VIEW p_post_by_media AS
SELECT post.*
FROM
( SELECT DISTINCT post_id FROM p_post_content WHERE ( TYPE = 3 OR TYPE = 4 OR TYPE = 7 OR TYPE = 8 ) AND is_del = 0 ) media
JOIN p_post post ON media.post_id = post.ID
WHERE
post.is_del = 0;
DROP VIEW IF EXISTS p_post_by_comment;
CREATE VIEW p_post_by_comment AS
SELECT P.*
FROM
(
SELECT
post_id
FROM
p_comment
WHERE
is_del = 0 UNION
SELECT
post_id
FROM
p_comment_reply reply
JOIN p_comment COMMENT ON reply.comment_id = COMMENT.ID
WHERE
reply.is_del = 0
AND COMMENT.is_del = 0
)
C JOIN p_post P ON C.post_id = P.ID
WHERE
P.is_del = 0;
-- ---------------------------- -- ----------------------------
-- Indexes structure for table p_attachment -- Indexes structure for table p_attachment
-- ---------------------------- -- ----------------------------

Loading…
Cancel
Save