follow ppl

This commit is contained in:
Freya Murphy 2024-04-05 12:58:11 -04:00
parent 530bbf0587
commit b6ae609ee3
Signed by: freya
GPG key ID: 744AB800E383AE52
14 changed files with 409 additions and 86 deletions

View file

@ -0,0 +1,15 @@
CREATE VIEW api.follow AS
SELECT
f.id,
f.follower_id,
f.followee_id,
f.value,
f.created,
f.modified
FROM
admin.follow f;
GRANT SELECT ON TABLE api.follow
TO rest_anon, rest_user;
GRANT SELECT ON TABLE admin.follow
TO rest_anon, rest_user;

View file

@ -0,0 +1,32 @@
CREATE FUNCTION _api.follow_delete()
RETURNS TRIGGER
LANGUAGE plpgsql VOLATILE
AS $BODY$
DECLARE
_user_id INTEGER;
BEGIN
_user_id = _api.get_user_id();
IF OLD.follower_id <> _user_id THEN
PERFORM _api.raise_deny();
END IF;
UPDATE admin.follow SET
value = FALSE,
modified = clock_timestamp()
WHERE id = OLD.id;
END
$BODY$;
GRANT EXECUTE ON FUNCTION _api.follow_delete()
TO rest_user;
GRANT DELETE ON TABLE api.follow
TO rest_user;
GRANT UPDATE ON TABLE admin.follow
TO rest_user;
CREATE TRIGGER api_follow_delete_trgr
INSTEAD OF DELETE
ON api.follow
FOR EACH ROW
EXECUTE PROCEDURE _api.follow_delete();

View file

@ -0,0 +1,46 @@
CREATE FUNCTION _api.follow_insert()
RETURNS TRIGGER
LANGUAGE plpgsql VOLATILE
AS $BODY$
DECLARE
_user_id INTEGER;
BEGIN
_user_id = _api.get_user_id();
IF NEW.followee_id IS NULL THEN
-- for now
PERFORM _api.raise_deny();
END IF;
NEW.value := COALESCE(NEW.value, TRUE);
INSERT INTO admin.follow (
follower_id,
followee_id,
value
) VALUES (
_user_id,
NEW.followee_id,
NEW.value
)
RETURNING id
INTO NEW.id;
RETURN NEW;
END
$BODY$;
GRANT EXECUTE ON FUNCTION _api.follow_insert()
TO rest_user;
GRANT INSERT ON TABLE api.follow
TO rest_user;
GRANT INSERT ON TABLE admin.follow
TO rest_user;
GRANT UPDATE ON TABLE sys.follow_id_seq
TO rest_user;
CREATE TRIGGER api_follow_insert_trgr
INSTEAD OF INSERT
ON api.follow
FOR EACH ROW
EXECUTE PROCEDURE _api.follow_insert();

View file

@ -0,0 +1,44 @@
CREATE FUNCTION _api.follow_update()
RETURNS TRIGGER
LANGUAGE plpgsql VOLATILE
AS $BODY$
DECLARE
_user_id INTEGER;
_changed BOOLEAN;
BEGIN
_user_id = _api.get_user_id();
_changed = FALSE;
IF OLD.follower_id <> _user_id THEN
PERFORM _api.raise_deny();
END IF;
NEW.value = COALESCE(NEW.value, OLD.value);
IF NEW.value IS DISTINCT FROM OLD.value THEN
_changed = TRUE;
END IF;
IF _changed THEN
UPDATE admin.follow SET
value = NEW.value,
modified = clock_timestamp()
WHERE id = OLD.id;
END IF;
RETURN NEW;
END
$BODY$;
GRANT EXECUTE ON FUNCTION _api.follow_update()
TO rest_user;
GRANT UPDATE ON TABLE api.follow
TO rest_user;
GRANT UPDATE ON TABLE admin.follow
TO rest_user;
CREATE TRIGGER api_follow_update_trgr
INSTEAD OF UPDATE
ON api.follow
FOR EACH ROW
EXECUTE PROCEDURE _api.follow_update();

View file

@ -45,6 +45,12 @@ GRANT USAGE ON SCHEMA _api TO rest_anon, rest_user;
\i /db/rest/like/api_like_update.sql; \i /db/rest/like/api_like_update.sql;
\i /db/rest/like/api_like_delete.sql; \i /db/rest/like/api_like_delete.sql;
-- follow
\i /db/rest/follow/api_follow.sql;
\i /db/rest/follow/api_follow_insert.sql;
\i /db/rest/follow/api_follow_update.sql;
\i /db/rest/follow/api_follow_delete.sql;
-- media -- media
\i /db/rest/media/_api_serve_user_media.sql; \i /db/rest/media/_api_serve_user_media.sql;
\i /db/rest/media/_api_serve_system_media.sql; \i /db/rest/media/_api_serve_system_media.sql;

View file

@ -155,6 +155,15 @@ a, button {
background-color: var(--surface2); background-color: var(--surface2);
} }
.btn-alt.btn-blue {
background-color: var(--blue);
color: var(--white);
}
.btn-alt.btn-blue:hover {
background-color: var(--blue-alt);
}
.btn-wide { .btn-wide {
width: auto; width: auto;
flex-grow: 1; flex-grow: 1;
@ -604,3 +613,11 @@ input[type=radio] {
width: fit-content; width: fit-content;
outline: none !important; outline: none !important;
} }
.container {
padding: 1rem;
}
.grow {
flex-grow: 1;
}

View file

@ -10,19 +10,14 @@
.post, #new-post { .post, #new-post {
margin-bottom: 1rem; margin-bottom: 1rem;
width: 40rem; max-width: 40rem;
width: 100%;
} }
.post { .post {
padding-bottom: 0; padding-bottom: 0;
} }
@media(max-width: 40rem) {
.post, #new-post {
width: 100%;
}
}
.post .likes { .post .likes {
display: block; display: block;
padding-top: .25rem; padding-top: .25rem;

View file

@ -10,7 +10,6 @@
flex-direction: row; flex-direction: row;
justify-content: center; justify-content: center;
background-color: var(--surface0); background-color: var(--surface0);
margin-bottom: 1rem;
border-bottom: 1px solid var(--surface1); border-bottom: 1px solid var(--surface1);
} }
@ -85,10 +84,9 @@
.tab { .tab {
max-width: 80rem; max-width: 80rem;
width: 100%; width: 100%;
height: 100%;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
padding: 0 1rem;
margin-bottom: 1rem;
} }
#post-container { #post-container {
@ -106,3 +104,22 @@
td:nth-child(1) { td:nth-child(1) {
padding-right: 2rem; padding-right: 2rem;
} }
#about-tab {
width: auto;
}
#follow-container {
margin-left: auto;
margin-right: 2rem;
}
#follow-container > a {
width: 10rem;
padding: .5rem;
}
#follow-container > a > span {
width: 100%;
text-align: center;
}

View file

@ -24,13 +24,15 @@ class People_model extends Model {
case 'follower': { case 'follower': {
$query = $query $query = $query
->join('admin.follow f', 'f.follower_id = u.id AND f.followee_id', 'INNER') ->join('admin.follow f', 'f.follower_id = u.id AND f.followee_id', 'INNER')
->eq($filter_uid); ->eq($filter_uid)
->where('f.value = TRUE');
} break; } break;
case 'followee': { case 'followee': {
$query = $query $query = $query
->join('admin.follow f', 'f.followee_id = u.id AND f.follower_id', 'INNER') ->join('admin.follow f', 'f.followee_id = u.id AND f.follower_id', 'INNER')
->eq($filter_uid); ->eq($filter_uid)
->where('f.value = TRUE');
} break; } break;
} }
} }

View file

@ -29,8 +29,36 @@ class Profile_model extends Model {
return NULL; return NULL;
} }
$following = FALSE;
$followed = FALSE;
$follow_id = NULL;
if ($this->main->session) {
$sid = $this->main->user()['id'];
$res = $this->db->select('f.value, f.id')
->from('admin.follow f')
->where('f.follower_id')
->eq($sid)
->where('f.followee_id')
->eq($uid)
->row();
$following = $res ? $res['value'] : FALSE;
$follow_id = $res ? $res['id'] : NULL;
$res = $this->db->select('f.value')
->from('admin.follow f')
->where('f.follower_id')
->eq($uid)
->where('f.followee_id')
->eq($sid)
->row();
$followed = $res ? $res['value'] : FALSE;
}
$data = parent::get_data(); $data = parent::get_data();
$data['user'] = $user; $data['user'] = $user;
$data['following'] = $following;
$data['followed'] = $followed;
$data['follow_id'] = $follow_id;
$data['title'] = lang('title', sub: [$user['first_name']]); $data['title'] = lang('title', sub: [$user['first_name']]);
return $data; return $data;
} }

View file

@ -1,6 +1,6 @@
<?php /* Copyright (c) 2024 Freya Murphy */ ?> <?php /* Copyright (c) 2024 Freya Murphy */ ?>
<?php /* vi: syntax=php */ ?> <?php /* vi: syntax=php */ ?>
<div id="main-content"> <div id="main-content" class="container">
<?php if ($self): ?> <?php if ($self): ?>
<div id="new-post" class="card"> <div id="new-post" class="card">
<div class="row grow"> <div class="row grow">

View file

@ -8,9 +8,116 @@
<div class="pfp-wrapper"> <div class="pfp-wrapper">
<?=pfp($user)?> <?=pfp($user)?>
</div> </div>
<div class="col content"> <div class="col content grow">
<div class="row grow">
<div class="col">
<strong class="name"><?=$this->format_model->name($user)?></strong> <strong class="name"><?=$this->format_model->name($user)?></strong>
<span class="dim"><?=$user['follower_count'] . ' ' . lang('followers')?></span> <span class="dim"><?=$user['follower_count'] . ' ' . lang('followers')?></span>
</div>
<?php if (!isset($self) || $self['id'] != $user['id']): ?>
<div id="follow-container">
<?=ilang(
'action_follow',
id: 'action-follow-follow',
class: 'btn btn-alt',
style: (!$following && !$followed) ? '' : 'display: none',
sub: [$user['first_name']]
)?>
<?=ilang(
'action_follow_back',
id: 'action-follow-follow-back',
class: 'btn btn-alt',
style: (!$following && $followed) ? '' : 'display: none',
sub: [$user['first_name']]
)?>
<?=ilang(
'action_following',
id: 'action-follow-following',
class: 'btn btn-alt btn-blue',
style: ($following && !$followed) ? '' : 'display: none',
sub: [$user['first_name']]
)?>
<?=ilang(
'action_friends',
id: 'action-follow-friends',
class: 'btn btn-alt btn-blue',
style: ($following && $followed) ? '' : 'display: none',
sub: [$user['first_name']]
)?>
</div>
<script>
let following = <?=json_encode($following)?>;
let followed = <?=json_encode($followed)?>;
let followId = <?=json_encode($follow_id)?>;
let followee_id = <?=json_encode($user['id'])?>;
let btns = {};
const disableBtn = (btn) => {
btn.css('display', 'none');
};
const enableBtn = (btn) => {
btn.css('display', '');
};
const updateFollow = () => {
for (let btn of Object.values(btns)) {
disableBtn(btn);
}
if (!following && !followed) {
enableBtn(btns['follow']);
} else if (!following && followed) {
enableBtn(btns['follow-back']);
} else if (following && !followed) {
enableBtn(btns['following']);
} else if (following && followed) {
enableBtn(btns['friends']);
}
}
const onPatchFollow = (data) => {
following = data[0].value;
updateFollow();
}
const onPostFollow = (data) => {
followId = data[0].id;
following = true;
updateFollow();
}
const onClickFollow = () => {
if (followId) {
$.ajax({
url: '/api/follow?id=eq.' + followId,
method: 'PATCH',
data: JSON.stringify({ followee_id, value: !following }),
success: onPatchFollow
});
} else {
$.ajax({
url: '/api/follow',
method: 'POST',
data: JSON.stringify({ followee_id, value: !following }),
success: onPostFollow,
});
}
}
const loadBtn = (name) => {
let btn = $('#action-follow-' + name);
btn.on('click', onClickFollow);
btns[name] = btn;
};
loadBtn('follow');
loadBtn('follow-back');
loadBtn('following');
loadBtn('friends');
</script>
<?php endif; ?>
</div>
<?php if(strlen($user['profile_bio']) > 0): ?> <?php if(strlen($user['profile_bio']) > 0): ?>
<br> <br>
<strong><?=lang('bio')?></strong> <strong><?=lang('bio')?></strong>
@ -43,13 +150,14 @@
</div> </div>
</div> </div>
</div> </div>
<div id="tab-container" class="container">
<div id="tab-posts" class="tab"> <div id="tab-posts" class="tab">
<?php <?php
$_GET['user_id'] = $user['id']; $_GET['user_id'] = $user['id'];
$this->post_controller->index(); $this->post_controller->index();
?> ?>
</div> </div>
<div id="tab-about" class="tab"> <div id="tab-about" class="tab card">
<h1><?=lang('about_general')?></h1> <h1><?=lang('about_general')?></h1>
<table> <table>
<tr> <tr>

View file

@ -18,6 +18,7 @@ function lang($key, $default = NULL, $sub = NULL) {
function ilang($key, function ilang($key,
$class = NULL, $class = NULL,
$style = NULL,
$id = NULL, $id = NULL,
$href = NULL, $href = NULL,
$click = NULL, $click = NULL,
@ -42,6 +43,9 @@ function ilang($key,
if ($class) { if ($class) {
echo 'class="' . $class . '" '; echo 'class="' . $class . '" ';
} }
if ($style) {
echo 'style="' . $style . '" ';
}
if ($id) { if ($id) {
echo 'id="' . $id . '" '; echo 'id="' . $id . '" ';
} }

View file

@ -16,6 +16,15 @@ $lang['action_followers_tip'] = 'View %s\'s followres';
$lang['action_following_text'] = 'Following'; $lang['action_following_text'] = 'Following';
$lang['action_following_tip'] = 'View who %s is following'; $lang['action_following_tip'] = 'View who %s is following';
$lang['action_follow_text'] = 'Follow';
$lang['action_follow_tip'] = 'Follow %s';
$lang['action_follow_back_text'] = 'Follow Back';
$lang['action_follow_back_tip'] = 'Follow %s';
$lang['action_following_text'] = 'Followed';
$lang['action_following_tip'] = 'Unfollow %s';
$lang['action_friends_text'] = 'Friends';
$lang['action_friends_tip'] = 'Unfollow %s';
$lang['action_load_posts_text'] = 'Load more posts'; $lang['action_load_posts_text'] = 'Load more posts';
$lang['action_load_posts_tip'] = 'Load more posts'; $lang['action_load_posts_tip'] = 'Load more posts';