Skip to content

Commit

Permalink
add tag following
Browse files Browse the repository at this point in the history
  • Loading branch information
Mubangizi committed Jul 12, 2024
1 parent fa8ed70 commit a99a285
Show file tree
Hide file tree
Showing 11 changed files with 256 additions and 10 deletions.
89 changes: 89 additions & 0 deletions api_docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3760,6 +3760,95 @@ paths:
500:
description: "Internal Server Error"

"/tags/{tag_id}/following":
get:
tags:
- tags
consumes:
- application/json
produces:
- application/json
parameters:
- in: header
name: Authorization
required: true
description: "Bearer [token]"
- in: path
name: tag_id
required: true
type: string
- in: query
name: page
type: integer
description: Page number
- in: query
name: per_page
type: integer
description: Number of items per page
responses:
200:
description: "Success"
404:
description: "Tag not found"
400:
description: "Bad request"
500:
description: "Internal Server Error"

post:
tags:
- tags
consumes:
- application/json
produces:
- application/json
parameters:
- in: header
name: Authorization
required: true
description: "Bearer [token]"
type: string
- in: path
name: tag_id
required: true
type: string
responses:
201:
description: "Success"
404:
description: "Tag not found"
400:
description: "Bad request"
500:
description: "Internal Server Error"

delete:
tags:
- tags
consumes:
- application/json
produces:
- application/json
parameters:
- in: header
name: Authorization
required: true
description: "Bearer [token]"
type: string
- in: path
name: tag_id
required: true
type: string
responses:
200:
description: "Success"
404:
description: "Tag not found"
400:
description: "Bad request"
500:
description: "Internal Server Error"

components:
securitySchemes:
bearerAuth:
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,4 @@
from .system_status import SystemSummaryView
from .project_users import ProjectUsersView, ProjectUsersTransferView, ProjectUsersHandleInviteView, ProjectFollowingView
from .activity_feed import ActivityFeedView
from .tags import TagsView, TagsDetailView
from .tags import TagsView, TagsDetailView, TagFollowingView
5 changes: 2 additions & 3 deletions app/controllers/project_users.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
from functools import partial
import json
from app.models.project_users import ProjectUser
from flask_restful import Resource, request
from app.schemas import ProjectUserSchema, UserSchema, AnonymousUsersSchema, ProjectFollowerSchema
from app.schemas import ProjectUserSchema, AnonymousUsersSchema, UserIndexSchema
from app.models.user import User
from app.models.role import User
from app.models.project import Project
Expand Down Expand Up @@ -522,7 +521,7 @@ def post(self, project_id):
@ jwt_required
def get(self, project_id):
project = Project.get_by_id(project_id)
follower_schema = ProjectFollowerSchema(many=True)
follower_schema = UserIndexSchema(many=True)

followers = project.followers
users_data, errors = follower_schema.dumps(followers)
Expand Down
76 changes: 73 additions & 3 deletions app/controllers/tags.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@

from flask_restful import Resource, request
from flask_jwt_extended import jwt_required
import json
from app.schemas.project_users import UserIndexSchema
from app.schemas.tags import TagSchema, TagsDetailSchema
from app.models.tags import Tag
from flask_restful import Resource, request
from flask_jwt_extended import jwt_required, get_jwt_identity
from app.models.tags import Tag, TagFollowers
from app.helpers.decorators import admin_required


Expand Down Expand Up @@ -90,3 +92,71 @@ def delete(self, tag_id):
status='success',
message=f"Tag {tag_id} successfully deleted"
), 200


class TagFollowingView(Resource):
@ jwt_required
def post(self, tag_id):
current_user_id = get_jwt_identity()
tag = Tag.get_by_id(tag_id)

if not tag:
return dict(status='fail', message=f'Tag with id {tag_id} not found'), 404


existing_tag_follow = TagFollowers.find_first(
user_id=current_user_id, tag_id=tag_id)
if existing_tag_follow:
return dict(status='fail', message=f'You are already following tag with id {tag_id}'), 409

new_tag_follow = TagFollowers(
user_id=current_user_id, tag_id=tag_id)

saved_tag_follow = new_tag_follow.save()

if not saved_tag_follow:
return dict(status='fail', message='Internal Server Error'), 500

return dict(
status='success',
message=f'You are now following tag with id {tag_id}'
), 201

@ jwt_required
def get(self, tag_id):
tag = Tag.get_by_id(tag_id)
follower_schema = UserIndexSchema(many=True)

followers = tag.followers
users_data, errors = follower_schema.dumps(followers)

if errors:
return dict(status='fail', message=errors), 400

return dict(
status='success',
data=dict(followers=json.loads(users_data))
), 200

@ jwt_required
def delete(self, tag_id):
current_user_id = get_jwt_identity()
tag = Tag.get_by_id(tag_id)

if not tag:
return dict(status='fail', message=f'Tag with id {tag_id} not found'), 404

existing_tag_follow = TagFollowers.find_first(
user_id=current_user_id, tag_id=tag_id)
if not existing_tag_follow:
return dict(status='fail', message=f'You are not following tag with id {tag_id}'), 409

deleted_tag = existing_tag_follow.delete()

if not deleted_tag:
return dict(status='fail', message='Internal Server Error'), 500

return dict(
status='success',
message=f'You are nolonger following tag with id {tag_id}'
), 201
17 changes: 16 additions & 1 deletion app/models/tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class Tag(ModelMixin):
is_super_tag = db.Column(db.Boolean, default=False)
projects = db.relationship("ProjectTag", back_populates="tag")
date_created = db.Column(db.DateTime, default=db.func.current_timestamp())
followers = db.relationship('TagFollowers', back_populates='tag')

def __repr__(self):
return f"<Tag {self.name}>"
Expand All @@ -31,6 +32,20 @@ class ProjectTag(ModelMixin):
date_created = db.Column(db.DateTime, default=db.func.current_timestamp())
project = db.relationship("Project", back_populates="tags")
tag = db.relationship("Tag", back_populates="projects")

def __repr__(self):
return f"<ProjectTag {self.project.name}, {self.tag.name}>"


class TagFollowers(ModelMixin):
_tablename_ = "tag_followers"

id = db.Column(UUID(as_uuid=True), primary_key=True,
server_default=sa_text("uuid_generate_v4()"))
user_id = db.Column('user_id', UUID(as_uuid=True),
db.ForeignKey('user.id'), nullable=False)
tag_id = db.Column(UUID(as_uuid=True),
db.ForeignKey('tag.id'), nullable=False)
date_created = db.Column(db.DateTime, default=db.func.current_timestamp())
user = db.relationship("User", back_populates="followed_tags")
tag = db.relationship("Tag", back_populates="followers")
2 changes: 2 additions & 0 deletions app/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ class User(ModelMixin):
followed_projects = db.relationship(
'ProjectFollowers', back_populates='user')
is_public = db.Column(db.Boolean, default=True)
followed_tags = db.relationship(
'TagFollowers', back_populates='user')

def __init__(self, email, name, password, organisation=None):
""" initialize with email, username and password """
Expand Down
3 changes: 2 additions & 1 deletion app/routes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
UserAdminUpdateView, AppRevertView, ProjectGetCostsView, TransactionRecordView, CreditTransactionRecordView, CreditPurchaseTransactionRecordView,
BillingInvoiceView, BillingInvoiceNotificationView, SystemSummaryView, CreditDetailView, ProjectUsersView, ProjectUsersTransferView, AppReviseView,
ProjectUsersHandleInviteView, ClusterProjectsView, ProjectDisableView, ProjectEnableView, AppRedeployView, AppDisableView, AppEnableView,
TagsView, TagsDetailView,
TagsView, TagsDetailView, TagFollowingView,
UserDisableView, UserEnableView, AppDockerWebhookListenerView, UserFollowersView, UserFollowView, ProjectFollowingView, ActivityFeedView)
from app.controllers.app import AppRevisionsView
from app.controllers.billing_invoice import BillingInvoiceDetailView
Expand Down Expand Up @@ -153,6 +153,7 @@
# Tags Routes
api.add_resource(TagsView, '/tags')
api.add_resource(TagsDetailView, '/tags/<string:tag_id>')
api.add_resource(TagFollowingView, '/tags/<string:tag_id>/following')
# App routes
api.add_resource(AppsView, '/apps')
api.add_resource(AppDetailView, '/apps/<string:app_id>')
Expand Down
3 changes: 2 additions & 1 deletion app/schemas/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
from .monitoring_metrics import (UserGraphSchema, AppGraphSchema,
BillingMetricsSchema)
from .pod_logs import PodsLogsSchema
from .project_users import ProjectUserSchema, ProjectFollowerSchema
from .project_users import ProjectUserSchema, ProjectFollowerSchema, UserIndexSchema
from .credits import CreditSchema
from .credit_assignments import CreditAssignmentSchema
from .anonymous_users import AnonymousUsersSchema
from .tags import TagSchema, TagsProjectsSchema, TagsDetailSchema, TagFollowerSchema
11 changes: 11 additions & 0 deletions app/schemas/project_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ class UserRoleSchema(Schema):
id = fields.UUID()
email = fields.String()
name = fields.String()

class UserIndexSchema(Schema):
id = fields.Method("get_id", dump_only=True)
email = fields.Method("get_email", dump_only=True)
name = fields.Method("get_name", dump_only=True)
def get_id(self, obj):
return str(obj.user.id)
def get_email(self, obj):
return obj.user.email
def get_name(self, obj):
return obj.user.name
class ProjectUserSchema(Schema):

# id = fields.UUID(dump_only=True)
Expand Down
5 changes: 5 additions & 0 deletions app/schemas/tags.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from app.schemas.project import ProjectListSchema
from app.schemas.project_users import UserRoleSchema
from marshmallow import Schema, fields, validate, pre_load


Expand Down Expand Up @@ -27,3 +28,7 @@ def get_is_super_tag(self, obj):

class TagsDetailSchema(TagSchema):
projects = fields.Nested(ProjectListSchema, many=False, dump_only=True)


class TagFollowerSchema(Schema):
user = fields.Nested(UserRoleSchema, many=False, dump_only=True)
53 changes: 53 additions & 0 deletions migrations/versions/824b7f125075_.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""empty message
Revision ID: 824b7f125075
Revises: 403631504272
Create Date: 2024-07-12 06:57:24.734462
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision = '824b7f125075'
down_revision = '403631504272'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('tag_followers',
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('uuid_generate_v4()'), nullable=False),
sa.Column('user_id', postgresql.UUID(as_uuid=True), nullable=False),
sa.Column('tag_id', postgresql.UUID(as_uuid=True), nullable=False),
sa.Column('date_created', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['tag_id'], ['tag.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.drop_table('project_database')
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('project_database',
sa.Column('id', postgresql.UUID(), server_default=sa.text('uuid_generate_v4()'), autoincrement=False, nullable=False),
sa.Column('project_id', postgresql.UUID(), autoincrement=False, nullable=True),
sa.Column('date_created', postgresql.TIMESTAMP(), autoincrement=False, nullable=True),
sa.Column('host', sa.VARCHAR(length=256), autoincrement=False, nullable=True),
sa.Column('name', sa.VARCHAR(length=256), autoincrement=False, nullable=False),
sa.Column('password', sa.VARCHAR(length=256), autoincrement=False, nullable=False),
sa.Column('user', sa.VARCHAR(length=256), autoincrement=False, nullable=False),
sa.Column('port', sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column('database_flavour_name', sa.VARCHAR(length=256), autoincrement=False, nullable=True),
sa.Column('deleted', sa.BOOLEAN(), autoincrement=False, nullable=True),
sa.Column('disabled', sa.BOOLEAN(), autoincrement=False, nullable=True),
sa.Column('admin_disabled', sa.BOOLEAN(), autoincrement=False, nullable=True),
sa.ForeignKeyConstraint(['project_id'], ['project.id'], name='project_database_project_id_fkey'),
sa.PrimaryKeyConstraint('id', name='project_database_pkey')
)
op.drop_table('tag_followers')
# ### end Alembic commands ###

0 comments on commit a99a285

Please sign in to comment.