Code Monkey home page Code Monkey logo

cfpexchange's Introduction

CFP Exchange logo CFP.Exchange Build CfpExchange

An online community website used to share interesting call for papers with speakers.

This website is built using ASP.NET Core 3.1 and hosted on Azure: https://cfp.exchange.

Connectionstrings and other secrets

This project uses a number of secrets to connect to various APIs and databases. For this we use the ASP.NET Core user secrets.

To enter the connectionstring for your development database, use the following steps:

On Windows

Create a file in %APPDATA%\microsoft\UserSecrets\CfpExchangeSecrets\secrets.json and paste in the example below - then update it with your own secret values!

On Mac or Linux

Create a file in ~/.microsoft/usersecrets/CfpExchangeSecrets/secrets.json and paste in the example below - then update it with your own secret values!

User Secrets example

The file should have the following layout:

{
    "TwitterConsumerKey": "key",
    "TwitterConsumerSecret": "secret",
    "TwitterOAuthToken": "token",
    "TwitterOAuthTokenSecret": "tokensecret",
    "AdminEmailaddress": "[email protected]",
    "EmailSettings": {
        "ApiKey": "MailGunApiKey",
        "ApiUri": "https://api.mailgun.net/v3/yourdomain.com/messages",
        "From": "No-Reply CFP Exchange <[email protected]>"
    },
    "MapsApiKey": "key",
    "ConnectionStrings": {
        "CfpExchangeDb":  "ConnectionStringToTheCfpExchangeDatabase" 
    },
    "UrlPreviewApiKey": "key",
    "FeatureToggle": {
        "HostOwnImages": true
    },
    "ServicebusConnectionString": "ConnectionStringToSendToTheServiceBusQueues"
}
  • MapsApiKey is an API key for the Azure Maps service
  • UrlPreviewApiKey is the Azure Cognitive Services URL Preview key (experimental) functionality for this is implemented but not currently used.

Azure Functions settings

In order to run the Azure Functions project, you need to create a file called local.settings.json in the root directory of this project.

The contents should look like the following:

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "AzureWebJobsDashboard": "UseDevelopmentStorage=true",
    "ServicebusQueueConnectionString": "ConnectionStringToTheServiceBusQueue",
    "StorageAccountName": "ConnectionStringToTheBlobStorageAccount",
    "CfpExchangeDb": "ConnectionStringToTheCfpExchangeDatabase"
  }
}

All of these settings can be found within the Azure Portal once your environment is deployed to Azure via the ARM template.

cfpexchange's People

Contributors

dependabot[bot] avatar faniereynders avatar hnky avatar jandev avatar jfversluis avatar pheonix25 avatar rickvdbosch avatar sebastianschuetze avatar staal-it avatar wmeints avatar wouterdekort avatar ylinssen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

cfpexchange's Issues

Create generic images/identity

We have some basic logo and identity in place right now. But it could be better. New kind of logo or something identifiable would be nice.

Also, at least we need some images for the meta tags in #24.

GetMetadata is broken

It appears the route cfp/getMetadata returns a 404. Probably has something to do with the Route attributes I added earlier.

This is probably the reason one can't submit a new CFP (which returns a 400) due to some missing fields.

Make event date clickable to add to calendar

When the event date is known on cfp.exchange it would be nice to be able to add it to your calendar.

I don't have any hands-on experience with building something like this, is there a universal format which works for most calendars? I think most systems that do this provide multiple formats?

Error handling in new CFP submission

The process of error handling could be better.

Right now, a JS popup is shown which doesn't even give any relevant information. Would be even better if there would be a difference between actually saving the CFP and all other stuff (downloading the image, sending the tweet). Now the process just halts regardless of what went wrong.

Design/UX can be improved

I feel design can be better. It feels a bit cluttered here and there.
Things I'm not happy about atm:

  • Font in the date picker
  • Date picker opening on tab out of event URL
  • Showing the event date info in CFPs red header
  • Details page with all the things that are being added
  • Input for tags on submit form

Implement CFP of the day

There is a big CFP of the day on the homepage. This is however not really a CFP of the day. Come up with a way to select a CFP each day.

Or, if we can't easily do this, just skip it (for now).

Detect duplicates

Come up with a smart way to be able to detect (possible) duplicates

Log in via social providers

I would love to have the ability to log in via some of the social providers.

No need to have local accounts (accounts not created via a social provider) in my opinion, because that's just a lot of hassle. Only thing which needs to be stored in the CFP Exchange repository is the Name an identifier and maybe the E-mail address if you want to send out mailings later on (take into consideration the GDPR before sending out those mails).

The userbase (at the moment) are all developers/IT-pro's, so the GitHub & Twitter social providers should suffice.
Of course adding Google & Microsoft accounts is doable also.

Some documentation or .NET Core 2.1 integration:
https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/?view=aspnetcore-2.1
Covers all major providers

Fix EF migrations

For the love of.. I always run into trouble with EF migrations.
I somehow managed to create a migration without the Cfp.ProvidedExpenses property. Fixed it manually now, but I guess whenever a new migration is generated the property will be dropped, or when installed clean, this will cause trouble.

Need to figure out how to fix this, if fixing is needed at all.

Tweets not being sent on new CFP

Has probably something to do with #75.

I did add the settings back manually right now, but still, the tweet doesn't go out. Don't have a proper error message yet.

Move to VSTS Public Projects and setup builds etc. there

I have access to the public projects preview on VSTS. With moving to ARM templates etc. it would be great to host and build everything there.

All PRs need to be merged etc. first then. Maybe I will just move the building process there, have to look into what is possible.

Create new database, migrations are failing

I just deleted my database and created a new one. Afterward, I did an Update-Database and saw a lot of errors occurring. Because of this, the site isn't able to load anymore.

It's a bit much to copy the complete error over here, but this is an excerpt of the first part.

Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (31ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
      VALUES (N'20180609180239_AddedExpensesToCfpAndUserStuff', N'2.0.2-rtm-10011');
failApplication startup exception: Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while updating the entries. See the inner exception for details. ---> System.Data.SqlClient.SqlException: Invalid column name 'CfpDecisionDate'.
Invalid column name 'DuplicateOfId'.
Invalid column name 'EventTags'.
Invalid column name 'EventTimezone'.
Invalid column name 'EventTwitterHandle'.
Invalid column name 'ProvidesAccommodation'.
Invalid column name 'ProvidesTravelAssistance'.
Invalid column name 'Remarks'.
Invalid column name 'Slug'.
Invalid column name 'CfpDecisionDate'.
Invalid column name 'DuplicateOfId'.
Invalid column name 'EventTags'.
Invalid column name 'EventTimezone'.
Invalid column name 'EventTwitterHandle'.
Invalid column name 'ProvidesAccommodation'.
Invalid column name 'ProvidesTravelAssistance'.
Invalid column name 'Remarks'.
Invalid column name 'Slug'.
   at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
   at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
   at System.Data.SqlClient.SqlDataReader.TryConsumeMetaData()
   at System.Data.SqlClient.SqlDataReader.get_MetaData()
   at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
   at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async, Int32 timeout, Task& task, Boolean asyncWrite, SqlDataReader ds)
   at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, TaskCompletionSource`1 completion, Int32 timeout, Task& task, Boolean asyncWrite, String method)
   at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior)
   at System.Data.SqlClient.SqlCommand.ExecuteDbDataReader(CommandBehavior behavior)
   at System.Data.Common.DbCommand.ExecuteReader()
   at Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommand.Execute(IRelationalConnection connection, DbCommandMethod executeMethod, IReadOnlyDictionary`2 parameterValues)
   at Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommand.ExecuteReader(IRelationalConnection connection, IReadOnlyDictionary`2 parameterValues)
   at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.Execute(IRelationalConnection connection)
   --- End of inner exception stack trace ---
   at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.Execute(IRelationalConnection connection)
   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.Execute(Tuple`2 parameters)
   at Microsoft.EntityFrameworkCore.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)
   at Microsoft.EntityFrameworkCore.ExecutionStrategyExtensions.Execute[TState,TResult](IExecutionStrategy strategy, TState state, Func`2 operation)
   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.Execute(IEnumerable`1 commandBatches, IRelationalConnection connection)
   at Microsoft.EntityFrameworkCore.Storage.RelationalDatabase.SaveChanges(IReadOnlyList`1 entries)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(IReadOnlyList`1 entriesToSave)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(Boolean acceptAllChangesOnSuccess)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChanges()
   at CfpExchange.Data.CfpContextExtensions.EnsureSeeded(CfpContext context) in D:\Projecten\CfpExchange\CfpExchange\Data\CfpContext.cs:line 149
   at CfpExchange.Startup.Configure(IApplicationBuilder app, IHostingEnvironment env) in D:\Projecten\CfpExchange\CfpExchange\Startup.cs:line 117
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Microsoft.AspNetCore.Hosting.ConventionBasedStartup.Configure(IApplicationBuilder app)
   at Microsoft.AspNetCore.Hosting.Internal.AutoRequestServicesStartupFilter.<>c__DisplayClass0_0.<Configure>b__0(IApplicationBuilder builder)
   at Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication()
: CfpExchange.Startup[0]
      Failed to migrate or seed database
System.Data.SqlClient.SqlException (0x80131904): ALTER TABLE DROP COLUMN failed because column 'ProvidedExpenses' does not exist in table 'Cfps'.
   at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
   at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
   at System.Data.SqlClient.SqlCommand.RunExecuteNonQueryTds(String methodName, Boolean async, Int32 timeout, Boolean asyncWrite)
   at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource`1 completion, Boolean sendToPipe, Int32 timeout, Boolean asyncWrite, String methodName)
   at System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
   at Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommand.Execute(IRelationalConnection connection, DbCommandMethod executeMethod, IReadOnlyDictionary`2 parameterValues)
   at Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommand.ExecuteNonQuery(IRelationalConnection connection, IReadOnlyDictionary`2 parameterValues)
   at Microsoft.EntityFrameworkCore.Migrations.MigrationCommand.ExecuteNonQuery(IRelationalConnection connection, IReadOnlyDictionary`2 parameterValues)
   at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationCommandExecutor.ExecuteNonQuery(IEnumerable`1 migrationCommands, IRelationalConnection connection)
   at Microsoft.EntityFrameworkCore.Migrations.Internal.Migrator.Migrate(String targetMigration)
   at Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.Migrate(DatabaseFacade databaseFacade)
   at CfpExchange.Startup.Configure(IApplicationBuilder app, IHostingEnvironment env) in D:\Projecten\CfpExchange\CfpExchange\Startup.cs:line 88
ClientConnectionId:3f7aba2d-dfb7-423f-ad89-795b800cdad7
Error Number:4924,State:1,Class:16
Microsoft.EntityFrameworkCore.Migrations[20402]
      Applying migration '20180612071509_TravelAndAccommodation'.
fail: Microsoft.EntityFrameworkCore.Database.Command[20102]
      Failed executing DbCommand (707ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      DECLARE @var0 sysname;
      SELECT @var0 = [d].[name]
      FROM [sys].[default_constraints] [d]
      INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id]
      WHERE ([d].[parent_object_id] = OBJECT_ID(N'Cfps') AND [c].[name] = N'ProvidedExpenses');
      IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Cfps] DROP CONSTRAINT [' + @var0 + '];');
      ALTER TABLE [Cfps] DROP COLUMN [ProvidedExpenses];
System.Data.SqlClient.SqlException (0x80131904): ALTER TABLE DROP COLUMN failed because column 'ProvidedExpenses' does not exist in table 'Cfps'.
   at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
   at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
   at System.Data.SqlClient.SqlCommand.RunExecuteNonQueryTds(String methodName, Boolean async, Int32 timeout, Boolean asyncWrite)
   at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource`1 completion, Boolean sendToPipe, Int32 timeout, Boolean asyncWrite, String methodName)
   at System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
   at Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommand.Execute(IRelationalConnection connection, DbCommandMethod executeMethod, IReadOnlyDictionary`2 parameterValues)
ClientConnectionId:3f7aba2d-dfb7-423f-ad89-795b800cdad7
Error Number:4924,State:1,Class:16

I'm not sure why this occurs as I haven't seen this before in this project.

By the way, this also happens when doing the auto-migrations whenever the site hits the database. Can only reproduce this on an actual SQL Server. When using the services.AddDbContext<CfpContext>(opt => opt.UseInMemoryDatabase("Cfps")); I don't see this error occurring.

Implement manual entry of event details

Right now the event website is scraped for details.
If this fails or is not sufficient, there is no way to supply the details yourself now. There is a button in place, but it's not working yet.

Mixed content on site due to loading external images

Because images of CFP's are loaded on this site, it's possible to have mixed content on the page.

Browsers will notify the user the site isn't safe (see image below of my Firefox).
mixedcontent

Only thing I can come up with is to download the images/external content and host it ourselves. Of course, this will cost a bit more bandwidth and storage.

Web App settings get erased on deploy with ARM

The manually added application settings are erased right now when a deploy happens.

This causes the Twitter connection and e-mail functionality to get interrupted. We need to add the settings from the secrets.json into the ARM template.

Date binding is broken

Just tried to add a new CFP, but it appears the binding of dates is broken.

The JSON which is being sent to the Submit looks like this.

{"eventUrl":"https://www.dddeastanglia.com/","eventImageUrl":"/images/noimage.svg","eventTitle":"DDD East Anglia Home Page","eventDescription":"DDD East Anglia developer conference website","locationName":"Cambridge, UK","locationLat":52.205337,"locationLng":0.12181699999996454,"cfpEndDate":"0001-01-01T00:00:00","eventStartDate":"0001-01-01T00:00:00","eventEndDate":"0001-01-01T00:00:00","cfpUrl":null,"providesAccommodation":0,"providesTravelAssistance":0,"submittedByName":"Jan"}

Of course, I've used the datepickers to add the correct dates to the appropriate fields.

Error in the browser (Edge) shows: [object Object] in an alert.

Prefill tags when scraping the event page

As supplying Tags is mandatory now and there isn't suggestion/autocomplete functionality yet, it might be cool to have the tags being prefilled when scraping the site for the event details.

Of course, this is quite hard to do by yourself.

It sounds like a great opportunity to do a deep-dive into AI, ML, etc.
Maybe use the tags of Stack Overflow as a baseline and see if any of those tags are mentioned somewhere on the homepage. If so, suggest them in the Tags box.

Mark duplicate events instead of deleting

Right now when a duplicate event is detected I simply delete it. If a user has linked to it somewhere, the link will be broken. It would be better to mark the event as a duplicate so we can inform the user or simply link them through to the other one.

Event description when submitting a CFP can be empty, causing problems

Somehow a CFP snuck by without a description, causing an HTTP 500 when trying to view it.

Multiple problems in here:

  • the description should be mandatory, so we need to check that
  • viewing the CFP shouldn't crash when there is no description. I notice a replacment of \n to
    going on that was the culprit

Ability to add an image when entering event details manually

Loosely coupled to #33, when a user is entering an event manually, you can only do the title and description now. The image should be in there as well. Whenever #10 happens to get implemented we can upload it to our own storage. For now maybe just let them enter a URL?

Implement endpoint to track links going out

The Cfp object now has a property to track the clicks on a CFP URL. It would make for some nice statistics to keep track of that. For this we would need an endpoint that first increments the out count and then redirects the user.

Randomize the random CFP

There is a random CFP shown at the top bar of the homepage. However, this is not random at this time. Implement some kind of randomizer.

Add decision date?

I see more and more events that state when they will inform their speakers about their attendance. It might be nice to have a field for this when submitting a CFP and showing it in the details whenever this data is available.

Any people seeing this? Or isn't it that common (yet) to bother right now.

Make CFP website insightful

Now, when the direct CFP link is added, you can navigate there by a button directly.
However, there is no link whatsoever to the event's website.

Open CFPs in a map view

A map always looks cool. Might be nice to show all the current open CFPs on a map around the world.

Add nifty OpenGraph/social media tags to relevant pages

To make the CFPs and links etc more sexy on social media, it would be great to add the right meta tags on the relevant pages.

  • When it's a general page, have some general image, title, description for cfp.exchange
  • When a CFP page is shared, have the image, title, etc. associated to the event

Add event's Twitter link to CFP details

There is now the possibility to collect the event's Twitter link.
This is used in the tweet that goes out when the CFP is added, but it would be great to have it in the CFP details as well.

Create a tweet when submitting a CFP asynchronous

At the moment a tweet is sent out when someone has created a new CFP.
While this works, it might make the submission slower as necessary.

Because there's a nice servicebus added for #59 , we can just add a new Queue to it for this process.

That way we can send a message to the queue, which should be vast.
An Azure Function will be triggered and send out the tweet. Implementation will be the same as now, just more awesome ๐Ÿ˜‰

Nice starter issue if you want to get started with ARM templates and Azure Functions.

Enrich tweet on new CFPs

  • Add #cfp tag to tweet on new CFP. This tag seems to be used for cfps at this time advertised on Twitter.
  • Add location data on tweet, this will (probably?) help people nearby discover the tweet better.
  • Add Twitter handle as optional to a CFP to mention the event when the tweet gets posted to make event organizers more aware of CFP Exchange

Solve confusion around Twitter handle in submission form

Adding the Twitter handle to the submission form causes some confusion. Users think that it is their own Twitter handle, while it is actually the event's one.

Could solve this by moving the 'Your name' field to the bottom, or some other solution.

Implement "report an issue" functionality

Report simple "report issue" functionality for a CFP.
Whenever someone finds the info for a CFP is wrong or is a duplicate they can report this and the administrator can look into it

Examine if we can do without the AlwaysOn for the Azure Function

Right now in the ARM template, the Function App is specified as Always On. To accommodate this, we need a scaled up hosting plan which impacts costs. We need to examine if we can do without the AlwaysOn option and just pay per invocation of the function. This brings down costs which is always good ๐Ÿค‘

Date -1 bug is back

The bug from #28 is back again.

Has to do something with the conversion from the JavaScript ISODate back to the .NET date. Not sure what would be the least dirty solution here.

Add support for tags

When submitting a CFP, let the user supply tags to allow other users to filter out certain CFPs suited for them

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.