2018-08-28 18:17:22 +00:00
|
|
|
# User database abstractions
|
2018-06-13 00:16:19 +00:00
|
|
|
|
2018-11-02 02:05:21 +00:00
|
|
|
import psycopg2
|
2018-06-07 19:08:03 +00:00
|
|
|
|
|
|
|
class UserDatabase:
|
2018-11-02 02:05:21 +00:00
|
|
|
def __init__(self, connstr):
|
2018-06-13 00:16:19 +00:00
|
|
|
'''
|
2018-11-02 02:05:21 +00:00
|
|
|
Sets up the PostgreSQL connection to be used by this instance.
|
2018-06-13 00:16:19 +00:00
|
|
|
'''
|
2018-11-02 02:05:21 +00:00
|
|
|
self.db = psycopg2.connect(connstr)
|
2018-06-07 19:08:03 +00:00
|
|
|
cur = self.db.cursor()
|
2018-11-02 02:05:21 +00:00
|
|
|
cur.execute("""
|
|
|
|
CREATE TABLE IF NOT EXISTS userdata (
|
|
|
|
guild_id BIGINT,
|
|
|
|
user_id BIGINT,
|
|
|
|
zone TEXT NOT NULL,
|
|
|
|
last_active TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
|
|
PRIMARY KEY (guild_id, user_id)
|
|
|
|
)""")
|
2018-06-07 19:08:03 +00:00
|
|
|
self.db.commit()
|
|
|
|
cur.close()
|
|
|
|
|
|
|
|
def update_activity(self, serverid : str, authorid : str):
|
|
|
|
'''
|
|
|
|
If a user exists in the database, updates their last activity timestamp.
|
|
|
|
'''
|
|
|
|
c = self.db.cursor()
|
2018-11-02 02:05:21 +00:00
|
|
|
c.execute("""
|
|
|
|
UPDATE userdata SET last_active = now()
|
|
|
|
WHERE guild_id = %s AND user_id = %s
|
|
|
|
""", (serverid, authorid))
|
2018-06-07 19:08:03 +00:00
|
|
|
self.db.commit()
|
|
|
|
c.close()
|
|
|
|
|
|
|
|
def delete_user(self, serverid : str, authorid : str):
|
|
|
|
'''
|
|
|
|
Deletes existing user from the database.
|
|
|
|
'''
|
|
|
|
c = self.db.cursor()
|
2018-11-02 02:05:21 +00:00
|
|
|
c.execute("""
|
|
|
|
DELETE FROM userdata
|
|
|
|
WHERE guild_id = %s AND user_id = %s
|
|
|
|
""", (serverid, authorid))
|
2018-06-07 19:08:03 +00:00
|
|
|
self.db.commit()
|
|
|
|
c.close()
|
|
|
|
|
|
|
|
def update_user(self, serverid : str, authorid : str, zone : str):
|
|
|
|
'''
|
|
|
|
Insert or update user in the database.
|
|
|
|
Does not do any sanitizing of incoming values, as only a small set of
|
|
|
|
values are allowed anyway. This is enforced by the caller.
|
|
|
|
'''
|
|
|
|
self.delete_user(serverid, authorid)
|
|
|
|
c = self.db.cursor()
|
2018-11-02 02:05:21 +00:00
|
|
|
c.execute("""
|
|
|
|
INSERT INTO userdata (guild_id, user_id, zone) VALUES
|
|
|
|
(%s, %s, %s)
|
|
|
|
ON CONFLICT (guild_id, user_id)
|
|
|
|
DO UPDATE SET zone = EXCLUDED.zone
|
|
|
|
""", (serverid, authorid, zone))
|
2018-06-07 19:08:03 +00:00
|
|
|
self.db.commit()
|
|
|
|
c.close()
|
|
|
|
|
2020-03-01 21:59:20 +00:00
|
|
|
def get_user(self, serverid, userid):
|
2020-03-01 21:45:41 +00:00
|
|
|
'''
|
|
|
|
Retrieves the time zone name of a single user
|
|
|
|
'''
|
|
|
|
c = self.db.cursor()
|
|
|
|
c.execute("""
|
|
|
|
SELECT zone FROM userdata
|
2020-03-01 21:59:20 +00:00
|
|
|
WHERE guild_id = %s and user_id = %s
|
|
|
|
""", (serverid, userid))
|
2020-03-01 21:45:41 +00:00
|
|
|
result = c.fetchone()
|
|
|
|
c.close()
|
|
|
|
if result is None: return None
|
|
|
|
return result[0]
|
|
|
|
|
2020-03-01 21:59:20 +00:00
|
|
|
|
2018-06-07 19:08:03 +00:00
|
|
|
def get_list(self, serverid, userid=None):
|
|
|
|
'''
|
|
|
|
Retrieves a list of recent time zones based on
|
|
|
|
recent activity per user. For use in the list command.
|
|
|
|
'''
|
|
|
|
c = self.db.cursor()
|
|
|
|
if userid is None:
|
2018-11-02 02:05:21 +00:00
|
|
|
c.execute("""
|
|
|
|
SELECT zone, count(*) as ct FROM userdata
|
|
|
|
WHERE guild_id = %s
|
2019-09-07 20:03:37 +00:00
|
|
|
AND last_active >= now() - INTERVAL '30 DAYS' -- only users active in the last 30 days
|
2018-06-07 19:08:03 +00:00
|
|
|
GROUP BY zone -- separate by popularity
|
2019-09-07 20:03:37 +00:00
|
|
|
ORDER BY ct DESC LIMIT 20 -- top 20 zones are given
|
2018-11-02 02:05:21 +00:00
|
|
|
""", (serverid))
|
2018-06-07 19:08:03 +00:00
|
|
|
else:
|
2018-11-02 02:05:21 +00:00
|
|
|
c.execute("""
|
|
|
|
SELECT zone, '0' as ct FROM userdata
|
|
|
|
WHERE guild_id = %s AND user_id = %s
|
|
|
|
""", (serverid, userid))
|
2018-06-07 19:08:03 +00:00
|
|
|
|
|
|
|
results = c.fetchall()
|
|
|
|
c.close()
|
2018-08-28 18:17:22 +00:00
|
|
|
return [i[0] for i in results]
|
|
|
|
|
|
|
|
def get_list2(self, serverid):
|
|
|
|
'''
|
|
|
|
Retrieves data for the tz.list command.
|
|
|
|
Returns a dictionary. Keys are zone name, values are arrays with user IDs.
|
|
|
|
'''
|
|
|
|
c = self.db.cursor()
|
2018-11-02 02:05:21 +00:00
|
|
|
c.execute("""
|
|
|
|
SELECT zone, user_id
|
|
|
|
FROM userdata
|
2018-08-28 18:17:22 +00:00
|
|
|
WHERE
|
2019-09-07 20:03:37 +00:00
|
|
|
last_active >= now() - INTERVAL '30 DAYS' -- only users active in the last 30 days
|
2018-11-02 02:05:21 +00:00
|
|
|
AND guild_id = %(guild)s
|
2018-08-28 18:17:22 +00:00
|
|
|
AND zone in (SELECT zone from (
|
|
|
|
SELECT zone, count(*) as ct
|
2018-11-02 02:05:21 +00:00
|
|
|
FROM userdata
|
2018-08-28 18:17:22 +00:00
|
|
|
WHERE
|
2018-11-02 02:05:21 +00:00
|
|
|
guild_id = %(guild)s
|
2019-09-07 20:03:37 +00:00
|
|
|
AND last_active >= now() - INTERVAL '30 DAYS'
|
2018-08-28 18:17:22 +00:00
|
|
|
GROUP BY zone
|
2019-09-07 20:03:37 +00:00
|
|
|
LIMIT 20
|
2018-11-02 02:05:21 +00:00
|
|
|
) as pop_zones)
|
2018-11-02 04:09:22 +00:00
|
|
|
ORDER BY RANDOM() -- Randomize display order (expected by consumer)
|
2018-11-02 02:05:21 +00:00
|
|
|
""", {'guild': serverid})
|
2018-08-28 18:17:22 +00:00
|
|
|
result = {}
|
|
|
|
for row in c:
|
|
|
|
inlist = result.get(row[0])
|
|
|
|
if inlist is None:
|
|
|
|
result[row[0]] = []
|
|
|
|
inlist = result[row[0]]
|
|
|
|
inlist.append(row[1])
|
2018-09-01 20:26:47 +00:00
|
|
|
c.close()
|
|
|
|
return result
|
|
|
|
|
|
|
|
def get_unique_tz_count(self):
|
|
|
|
'''
|
|
|
|
Gets the number of unique time zones in the database.
|
|
|
|
'''
|
|
|
|
c = self.db.cursor()
|
2018-11-02 02:05:21 +00:00
|
|
|
c.execute('SELECT COUNT(DISTINCT zone) FROM userdata')
|
2018-09-01 20:26:47 +00:00
|
|
|
result = c.fetchall()
|
|
|
|
c.close()
|
|
|
|
return result[0][0]
|