0

Merge remote-tracking branch 'origin/develop' into webv2

This commit is contained in:
Gabe Kangas 2022-07-11 21:21:07 -07:00
commit 506d1fa4cf
No known key found for this signature in database
GPG Key ID: 9A56337728BC81EA
16 changed files with 695 additions and 2043 deletions

View File

@ -1,40 +0,0 @@
# See https://docs.earthly.dev/ci-integration/vendor-specific-guides/gh-actions-integration
# for details.
name: Build nightly docker
on:
workflow_dispatch:
schedule:
- cron: '0 2 * * *'
jobs:
Docker:
runs-on: ubuntu-latest
steps:
- uses: earthly/actions-setup@v1
with:
version: 'latest' # or pin to an specific version, e.g. "v0.6.10"
- name: Earthly version
run: earthly --version
- name: Log into GitHub Container Registry
env:
GH_CR_PAT: ${{ secrets.GH_CR_PAT }}
run: echo "${{ secrets.GH_CR_PAT }}" | docker login https://ghcr.io -u ${{ github.actor }} --password-stdin
if: env.GH_CR_PAT != null
- name: Set up QEMU
id: qemu
uses: docker/setup-qemu-action@v1
with:
image: tonistiigi/binfmt:latest
platforms: all
- uses: actions/checkout@v3
- name: Checkout and build
if: env.GH_CR_PAT != null
env:
GH_CR_PAT: ${{ secrets.GH_CR_PAT }}
run: cd build/release && ./docker-nightly-earthly.sh

View File

@ -1,24 +1,40 @@
# See https://docs.earthly.dev/ci-integration/vendor-specific-guides/gh-actions-integration
# for details.
name: Build nightly docker
on:
workflow_dispatch:
schedule:
- cron: "0 2 * * *"
- cron: '0 2 * * *'
jobs:
Docker:
runs-on: ubuntu-latest
steps:
- uses: earthly/actions-setup@v1
with:
version: 'latest' # or pin to an specific version, e.g. "v0.6.10"
- name: Earthly version
run: earthly --version
- name: Log into GitHub Container Registry
env:
env:
GH_CR_PAT: ${{ secrets.GH_CR_PAT }}
run: echo "${{ secrets.GH_CR_PAT }}" | docker login https://ghcr.io -u ${{ github.actor }} --password-stdin
if: env.GH_CR_PAT != null
- name: Set up QEMU
id: qemu
uses: docker/setup-qemu-action@v1
with:
image: tonistiigi/binfmt:latest
platforms: all
- uses: actions/checkout@v3
- name: Setup and run
env:
- name: Checkout and build
if: env.GH_CR_PAT != null
env:
GH_CR_PAT: ${{ secrets.GH_CR_PAT }}
run: cd build/release && ./docker-nightly.sh
if: env.GH_CR_PAT != null

View File

@ -178,11 +178,11 @@ func GetResolvedPublicKeyFromIRI(publicKeyIRI string) (vocab.W3IDSecurityV1Publi
}
if err != nil {
err = errors.Wrap(err, "error resolving publickey from iri")
err = errors.Wrap(err, "error resolving publickey from iri, actor may not be valid: "+publicKeyIRI)
}
if !resolved {
err = errors.New("error resolving publickey from iri")
err = errors.New("error resolving publickey from iri, actor may not be valid: " + publicKeyIRI)
}
return pubkey, err

View File

@ -1,14 +0,0 @@
#!/bin/sh
# Docker build
# Must authenticate first: https://docs.github.com/en/packages/using-github-packages-with-your-projects-ecosystem/configuring-docker-for-use-with-github-packages#authenticating-to-github-packages
DOCKER_IMAGE="owncast-earthly"
DATE=$(date +"%Y%m%d")
VERSION="${DATE}-nightly"
echo "Building Docker image ${DOCKER_IMAGE}..."
# Change to the root directory of the repository
cd $(git rev-parse --show-toplevel)
earthly --ci --push +docker-all --image="ghcr.io/owncast/${DOCKER_IMAGE}" --tag=nightly --version="${VERSION}"

View File

@ -1,29 +1,14 @@
#!/bin/sh
# Docker build
# Must authenticate first: https://docs.github.com/en/packages/using-github-packages-with-your-projects-ecosystem/configuring-docker-for-use-with-github-packages#authenticating-to-github-packages
DOCKER_IMAGE="owncast"
DATE=$(date +"%Y%m%d")
VERSION="${DATE}-nightly"
GIT_COMMIT=$(git rev-list -1 HEAD)
# Create production build of Tailwind CSS
pushd ../../build/javascript >> /dev/null
# Install the tailwind & postcss CLIs
npm install --quiet --no-progress
# Run the tailwind CLI and pipe it to postcss for minification.
# Save it to a temp directory that we will reference below.
NODE_ENV="production" ./node_modules/.bin/tailwind build | ./node_modules/.bin/postcss > "../../webroot/js/web_modules/tailwindcss/dist/tailwind.min.css"
popd
echo "Building Docker image ${DOCKER_IMAGE}..."
# Change to the root directory of the repository
cd $(git rev-parse --show-toplevel)
# Docker build
docker build --build-arg NAME=docker --build-arg VERSION=${VERSION} --build-arg GIT_COMMIT=$GIT_COMMIT -t ghcr.io/owncast/${DOCKER_IMAGE}:nightly .
# Dockerhub
# You must be authenticated via `docker login` with your Dockerhub credentials first.
# docker push gabekangas/owncast:nightly
docker push ghcr.io/owncast/${DOCKER_IMAGE}:nightly
earthly --ci --push +docker-all --image="ghcr.io/owncast/${DOCKER_IMAGE}" --tag=nightly --version="${VERSION}"

View File

@ -686,7 +686,7 @@ paths:
type: string
example: https://fediverse.biz/authorize_interaction?uri=https://my.owncast.server/federation/user/streamer
/api/chat/updatemessagevisibility:
/api/chat/messagevisibility:
post:
summary: Update the visibility of chat messages.
description: Pass an array of IDs you want to change the chat visibility of.
@ -951,7 +951,7 @@ paths:
type: string
format: date-time
/api/admin/chat/updatemessagevisibility:
/api/admin/chat/messagevisibility:
post:
summary: Update the visibility of chat messages.
description: Pass an array of IDs you want to change the chat visibility of.
@ -1984,7 +1984,7 @@ paths:
type: string
format: date-time
/api/integrations/chat/updatemessagevisibility:
/api/integrations/chat/messagevisibility:
post:
summary: Update the visibility of chat messages.
description: Pass an array of IDs you want to change the chat visibility of.

View File

@ -131,7 +131,7 @@ func Start() error {
http.HandleFunc("/api/admin/chat/messages", middleware.RequireAdminAuth(admin.GetChatMessages))
// Update chat message visibility
http.HandleFunc("/api/admin/chat/updatemessagevisibility", middleware.RequireAdminAuth(admin.UpdateMessageVisibility))
http.HandleFunc("/api/admin/chat/messagevisibility", middleware.RequireAdminAuth(admin.UpdateMessageVisibility))
// Enable/disable a user
http.HandleFunc("/api/admin/chat/users/setenabled", middleware.RequireAdminAuth(admin.UpdateUserEnabled))
@ -318,7 +318,7 @@ func Start() error {
// Inline chat moderation actions
// Update chat message visibility
http.HandleFunc("/api/chat/updatemessagevisibility", middleware.RequireUserModerationScopeAccesstoken(admin.UpdateMessageVisibility))
http.HandleFunc("/api/chat/messagevisibility", middleware.RequireUserModerationScopeAccesstoken(admin.UpdateMessageVisibility))
// Enable/disable a user
http.HandleFunc("/api/chat/users/setenabled", middleware.RequireUserModerationScopeAccesstoken(admin.UpdateUserEnabled))

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -7,112 +7,112 @@ const registerChat = require('./lib/chat').registerChat;
const sendChatMessage = require('./lib/chat').sendChatMessage;
const testVisibilityMessage = {
body: 'message ' + Math.floor(Math.random() * 100),
type: 'CHAT',
body: 'message ' + Math.floor(Math.random() * 100),
type: 'CHAT',
};
var messageId;
const establishedUserFailedChatMessage = {
body: 'this message should fail to send ' + Math.floor(Math.random() * 100),
type: 'CHAT',
body: 'this message should fail to send ' + Math.floor(Math.random() * 100),
type: 'CHAT',
};
test('can send a chat message', async (done) => {
const registration = await registerChat();
const accessToken = registration.accessToken;
const registration = await registerChat();
const accessToken = registration.accessToken;
sendChatMessage(testVisibilityMessage, accessToken, done);
sendChatMessage(testVisibilityMessage, accessToken, done);
});
test('verify we can make API call to mark message as hidden', async (done) => {
const registration = await registerChat();
const accessToken = registration.accessToken;
const ws = new WebSocket(
`ws://localhost:8080/ws?accessToken=${accessToken}`,
{
origin: 'http://localhost:8080',
}
);
const registration = await registerChat();
const accessToken = registration.accessToken;
const ws = new WebSocket(
`ws://localhost:8080/ws?accessToken=${accessToken}`,
{
origin: 'http://localhost:8080',
}
);
// Verify the visibility change comes through the websocket
ws.on('message', async function incoming(message) {
const messages = message.split('\n');
messages.forEach(async function (message) {
const event = JSON.parse(message);
// Verify the visibility change comes through the websocket
ws.on('message', async function incoming(message) {
const messages = message.split('\n');
messages.forEach(async function (message) {
const event = JSON.parse(message);
if (event.type === 'VISIBILITY-UPDATE') {
ws.close();
done();
}
});
});
if (event.type === 'VISIBILITY-UPDATE') {
ws.close();
done();
}
});
});
const res = await request
.get('/api/admin/chat/messages')
.auth('admin', 'abc123')
.expect(200);
const res = await request
.get('/api/admin/chat/messages')
.auth('admin', 'abc123')
.expect(200);
const message = res.body[0];
messageId = message.id;
await request
.post('/api/admin/chat/updatemessagevisibility')
.auth('admin', 'abc123')
.send({ idArray: [messageId], visible: false })
.expect(200);
const message = res.body[0];
messageId = message.id;
await request
.post('/api/admin/chat/messagevisibility')
.auth('admin', 'abc123')
.send({ idArray: [messageId], visible: false })
.expect(200);
});
test('verify message has become hidden', async (done) => {
await new Promise((r) => setTimeout(r, 2000));
await new Promise((r) => setTimeout(r, 2000));
const res = await request
.get('/api/admin/chat/messages')
.expect(200)
.auth('admin', 'abc123');
const res = await request
.get('/api/admin/chat/messages')
.expect(200)
.auth('admin', 'abc123');
const message = res.body.filter((obj) => {
return obj.id === messageId;
});
expect(message.length).toBe(1);
// expect(message[0].hiddenAt).toBeTruthy();
done();
const message = res.body.filter((obj) => {
return obj.id === messageId;
});
expect(message.length).toBe(1);
// expect(message[0].hiddenAt).toBeTruthy();
done();
});
test('can enable established chat user mode', async (done) => {
await request
.post('/api/admin/config/chat/establishedusermode')
.auth('admin', 'abc123')
.send({ value: true })
.expect(200);
done();
await request
.post('/api/admin/config/chat/establishedusermode')
.auth('admin', 'abc123')
.send({ value: true })
.expect(200);
done();
});
test('can send a message after established user mode is enabled', async (done) => {
const registration = await registerChat();
const accessToken = registration.accessToken;
const registration = await registerChat();
const accessToken = registration.accessToken;
sendChatMessage(establishedUserFailedChatMessage, accessToken, done);
sendChatMessage(establishedUserFailedChatMessage, accessToken, done);
});
test('verify rejected message is not in the chat feed', async (done) => {
const res = await request
.get('/api/admin/chat/messages')
.expect(200)
.auth('admin', 'abc123');
const res = await request
.get('/api/admin/chat/messages')
.expect(200)
.auth('admin', 'abc123');
const message = res.body.filter((obj) => {
return obj.body === establishedUserFailedChatMessage.body;
});
const message = res.body.filter((obj) => {
return obj.body === establishedUserFailedChatMessage.body;
});
expect(message.length).toBe(0);
done();
expect(message.length).toBe(0);
done();
});
test('can disable established chat user mode', async (done) => {
await request
.post('/api/admin/config/chat/establishedusermode')
.auth('admin', 'abc123')
.send({ value: false })
.expect(200);
done();
await request
.post('/api/admin/config/chat/establishedusermode')
.auth('admin', 'abc123')
.send({ value: false })
.expect(200);
done();
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1188,9 +1188,9 @@
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
},
"moment": {
"version": "2.29.2",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz",
"integrity": "sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg=="
"version": "2.29.4",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
},
"ms": {
"version": "2.1.2",

View File

@ -7,7 +7,7 @@ export const URL_CONFIG = `/api/config`;
export const URL_VIEWER_PING = `/api/ping`;
// inline moderation actions
export const URL_HIDE_MESSAGE = `/api/chat/updatemessagevisibility`;
export const URL_HIDE_MESSAGE = `/api/chat/messagevisibility`;
export const URL_BAN_USER = `/api/chat/users/setenabled`;
// TODO: This directory is customizable in the config. So we should expose this via the config API.

View File

@ -3,132 +3,133 @@ import htm from '/js/web_modules/htm.js';
import { URL_FOLLOWERS } from '/js/utils/constants.js';
const html = htm.bind(h);
export default class FollowerList extends Component {
constructor(props) {
super(props);
constructor(props) {
super(props);
this.state = {
followers: [],
followersPage: 0,
currentPage: 0,
total: 0,
};
}
this.state = {
followers: [],
followersPage: 0,
currentPage: 0,
total: 0,
};
}
componentDidMount() {
try {
this.getFollowers();
} catch (e) {
console.error('followers error: ', e);
}
}
componentDidMount() {
try {
this.getFollowers();
} catch (e) {
console.error('followers error: ', e);
}
}
async getFollowers() {
const { currentPage } = this.state;
const limit = 24;
const offset = currentPage * limit;
const u = `${URL_FOLLOWERS}?offset=${offset}&limit=${limit}`;
const response = await fetch(u);
const followers = await response.json();
async getFollowers(requestedPage) {
const limit = 24;
const offset = requestedPage * limit;
const u = `${URL_FOLLOWERS}?offset=${offset || 0}&limit=${limit}`;
const response = await fetch(u);
const followers = await response.json();
const pages = Math.ceil(followers.total / limit);
this.setState({
followers: followers.results,
total: response.total,
});
}
this.setState({
followers: followers.results,
total: followers.total,
pages: pages,
});
}
changeFollowersPage(page) {
this.setState({ currentPage: page });
this.getFollowers();
}
changeFollowersPage(requestedPage) {
this.setState({ currentPage: requestedPage });
this.getFollowers(requestedPage);
}
render() {
const { followers, total, currentPage } = this.state;
if (!followers) {
return null;
}
render() {
const { followers, total, pages, currentPage } = this.state;
if (!followers) {
return null;
}
const noFollowersInfo = html`<div class="col-span-4">
<p class="mb-5 text-2xl">Be the first to follow this live stream.</p>
<p class="text-md">
By following this stream you'll get updates when it goes live, receive
posts from the streamer, and be featured here as a follower.
</p>
<p class="text-md mt-5">
Learn more about ${' '}
<a class="underline" href="https://en.wikipedia.org/wiki/Fediverse"
>The Fediverse</a
>, where you can follow this server as well as so much more.
</p>
</div>`;
const noFollowersInfo = html`<div class="col-span-4">
<p class="mb-5 text-2xl">Be the first to follow this live stream.</p>
<p class="text-md">
By following this stream you'll get updates when it goes live, receive
posts from the streamer, and be featured here as a follower.
</p>
<p class="text-md mt-5">
Learn more about ${' '}
<a class="underline" href="https://en.wikipedia.org/wiki/Fediverse"
>The Fediverse</a
>, where you can follow this server as well as so much more.
</p>
</div>`;
const paginationControls =
total > 1 &&
Array(total)
.fill()
.map((x, n) => {
const activePageClass =
n === currentPage &&
'bg-indigo-600 rounded-full shadow-md focus:shadow-md text-white';
return html` <li class="page-item active w-10">
<a
class="page-link relative block cursor-pointer hover:no-underline py-1.5 px-3 border-0 rounded-full hover:text-gray-800 hover:bg-gray-200 outline-none transition-all duration-300 ${activePageClass}"
onClick=${() => this.changeFollowersPage(n)}
>
${n + 1}
</a>
</li>`;
});
const paginationControls =
pages > 1 &&
Array(pages)
.fill()
.map((x, n) => {
const activePageClass =
n === currentPage &&
'bg-indigo-600 rounded-full shadow-md focus:shadow-md text-white';
return html` <li class="page-item active w-10">
<a
class="page-link relative block cursor-pointer hover:no-underline py-1.5 px-3 border-0 rounded-full hover:text-gray-800 hover:bg-gray-200 outline-none transition-all duration-300 ${activePageClass}"
onClick=${() => this.changeFollowersPage(n)}
>
${n + 1}
</a>
</li>`;
});
return html`
<div>
<div
class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"
>
${followers.length === 0 && noFollowersInfo}
${followers.map((follower) => {
return html` <${SingleFollower} user=${follower} /> `;
})}
</div>
<div class="flex">
<nav aria-label="Tab pages">
<ul class="flex list-style-none flex-wrap">
${paginationControls}
</ul>
</nav>
</div>
</div>
`;
}
return html`
<div>
<div
class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"
>
${followers.length === 0 && noFollowersInfo}
${followers.map((follower) => {
return html` <${SingleFollower} user=${follower} /> `;
})}
</div>
<div class="flex">
<nav aria-label="Tab pages">
<ul class="flex list-style-none flex-wrap">
${paginationControls}
</ul>
</nav>
</div>
</div>
`;
}
}
function SingleFollower(props) {
const { user } = props;
const { name, username, link, image } = user;
const { user } = props;
const { name, username, link, image } = user;
var displayName = name;
var displayUsername = username;
var displayName = name;
var displayUsername = username;
if (!displayName) {
displayName = displayUsername.split('@', 1)[0];
}
return html`
<a
href=${link}
class="following-list-follower block bg-white flex p-2 rounded-xl shadow border hover:no-underline mb-3 mr-3"
target="_blank"
>
<img
src="${image || '/img/logo.svg'}"
class="w-16 h-16 rounded-full"
onError=${({ currentTarget }) => {
currentTarget.onerror = null;
currentTarget.src = '/img/logo.svg';
}}
/>
<div class="p-3 truncate flex-grow">
<p class="font-semibold text-gray-700 truncate">${displayName}</p>
<p class="text-sm text-gray-500 truncate">${displayUsername}</p>
</div>
</a>
`;
if (!displayName) {
displayName = displayUsername.split('@', 1)[0];
}
return html`
<a
href=${link}
class="following-list-follower block bg-white flex p-2 rounded-xl shadow border hover:no-underline mb-3 mr-3"
target="_blank"
>
<img
src="${image || '/img/logo.svg'}"
class="w-16 h-16 rounded-full"
onError=${({ currentTarget }) => {
currentTarget.onerror = null;
currentTarget.src = '/img/logo.svg';
}}
/>
<div class="p-3 truncate flex-grow">
<p class="font-semibold text-gray-700 truncate">${displayName}</p>
<p class="text-sm text-gray-500 truncate">${displayUsername}</p>
</div>
</a>
`;
}